diff options
Diffstat (limited to 'engine/src/ui/dear_imgui.rs')
| -rw-r--r-- | engine/src/ui/dear_imgui.rs | 1084 |
1 files changed, 1084 insertions, 0 deletions
diff --git a/engine/src/ui/dear_imgui.rs b/engine/src/ui/dear_imgui.rs new file mode 100644 index 0000000..0047394 --- /dev/null +++ b/engine/src/ui/dear_imgui.rs @@ -0,0 +1,1084 @@ +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::LazyLock; + +use dear_imgui_rs::{ + DrawCmd as ImguiDrawCmd, + Key as ImguiKey, + MouseButton as ImguiMouseButton, +}; +use ecs::component::local::Local; +use ecs::component::Component; +use ecs::error::Context as _; +use ecs::event::component::{Changed, EventMatchExt}; +use ecs::pair::Pair; +use ecs::query::term::With; +use ecs::sole::Single; +use ecs::system::initializable::Initializable; +use ecs::system::observer::Observe; +use ecs::system::Into; +use ecs::time::Time; +use ecs::{Component, Query, Sole}; +use intmap::IntMap; + +use crate::asset::{Assets, Handle as AssetHandle, Label as AssetLabel}; +use crate::data_types::dimens::Dimens; +use crate::input::keyboard::{Key, Keyboard}; +use crate::input::mouse::{Button as MouseButton, Buttons as MouseButtons, Mouse}; +use crate::mesh::vertex_buffer::{ + NamedVertexAttr as MeshNamedVertexAttr, + VertexAttrInfo as MeshVertexAttrInfo, + VertexBuffer as MeshVertexBuffer, + VertexLabel, +}; +use crate::mesh::{Mesh, VertexAttrType as MeshVertexAttrType}; +use crate::projection::{ + ClipVolume as ProjectionClipVolume, + Orthographic as OrthographicProjection, +}; +use crate::reflection::Reflection; +use crate::rendering::blending::{ + Config as RenderingBlendingConfiguration, + Equation as RenderingBlendingEquation, + Factor as RenderingBlendingFactor, +}; +use crate::rendering::object::Id as RenderingObjectId; +use crate::rendering::shader::cursor::{ + BindingTextureKind as ShaderBindingTextureKind, + BindingValue as ShaderBindingValue, + Cursor as ShaderCursor, +}; +use crate::rendering::shader::{ + Context as ShaderContext, + EntrypointFlags as ShaderEntrypointFlags, + ModuleSource as ShaderModuleSource, +}; +use crate::rendering::{ + AssetOrValue, + Command as RenderingCommand, + DrawMeshOptions, + DrawProperties, + DrawPropertiesUpdateFlags, + MeshUsage, + RenderPass, + RenderPasses, + ScissorBox, + SrgbaTextureDataType, + Surface, + SurfaceId, + TextureCreation, + TexturePixelDataFormat, + TextureUpdate, + PRE_RENDER_PHASE, +}; +use crate::texture::Properties as TextureProperties; +use crate::vector::Vec2; +use crate::windowing::dpi::PhysicalSize; +use crate::windowing::window::Window; + +mod reexports +{ + pub use dear_imgui_rs as bindings; +} + +pub use reexports::*; + +pub static SHADER_ASSET_LABEL: LazyLock<AssetLabel> = LazyLock::new(|| AssetLabel { + path: Path::new("").into(), + name: Some("imgui_shader".into()), +}); + +/// Dear Imgui context +#[derive(Sole)] +pub struct Context +{ + pub enabled: bool, + ctx: inner_context_wrapper::InnerContextWrapper, + texture_lookup: IntMap<TextureLookupId, RenderingObjectId>, + texture_id_lookup: + HashMap<dear_imgui_rs::SnapshotTextureId, dear_imgui_rs::TextureId>, +} + +impl Context +{ + pub fn frame(&mut self) -> Option<&mut bindings::Ui> + { + if !self.enabled { + return None; + } + + self.ctx.get_frame() + } + + pub fn register_texture( + &mut self, + texture_data: bindings::OwnedTextureData, + ) -> bindings::ManagedTextureId + { + self.ctx.register_texture(texture_data) + } +} + +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct Extension +{ + start_enabled: bool, + settings_ini_file_path: Option<PathBuf>, +} + +impl Extension +{ + #[must_use] + pub fn with_start_enabled(mut self, start_enabled: bool) -> Self + { + self.start_enabled = start_enabled; + self + } + + #[must_use] + pub fn with_settings_ini_file( + mut self, + settings_ini_file_path: Option<PathBuf>, + ) -> Self + { + self.settings_ini_file_path = settings_ini_file_path; + self + } +} + +impl ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: ecs::extension::Collector<'_>) + { + let mut context = Context { + enabled: self.start_enabled, + ctx: inner_context_wrapper::InnerContextWrapper::new(), + texture_lookup: IntMap::with_capacity(8), + texture_id_lookup: HashMap::with_capacity(8), + }; + + if let Err(err) = context + .ctx + .set_settings_ini_file_path(self.settings_ini_file_path) + { + tracing::error!("Failed to set path of imgui settings ini file: {err}"); + } + + const { + assert!( + dear_imgui_rs::HAS_FREETYPE, + "Freetype font rasterizer support is not enabled" + ); + } + + context.ctx.font_atlas().add_font(&[unsafe { + dear_imgui_rs::FontSource::ttf_data_with_size( + include_bytes!("../../res/FiraMono-Regular.ttf"), + 14.0, + ) + .with_config( + dear_imgui_rs::FontConfig::default() + .name("FiraMono-Regular.ttf") + .font_loader_flags( + dear_imgui_rs::FontLoaderFlags::BITMAP + | dear_imgui_rs::FontLoaderFlags::FORCE_AUTOHINT, + ), + ) + }]); + + collector.add_sole(context).ok(); + + collector.add_system( + *PRE_RENDER_PHASE, + update.into_system().initialize((State::default(),)), + ); + + collector.add_observer(handle_window_changed); + } +} + +#[tracing::instrument(skip_all)] +fn handle_window_changed( + observe: Observe<Pair<Changed, Window>>, + mut context: Single<Context>, +) +{ + let Ok(context) = context.get_mut() else { + unreachable!(); + }; + + let Some(event_match) = observe + .iter() + .find(|event_match| event_match.get_entity().has_component(TargetWindow::id())) + else { + return; + }; + + let window = event_match.get_ent_target_comp(); + + let hidpi_factor = window.scale_factor().round(); + + context + .ctx + .get_io_mut() + .set_display_framebuffer_scale([hidpi_factor as f32, hidpi_factor as f32]); + + let window_size_logical = PhysicalSize { + width: f64::from(window.inner_size.width), + height: f64::from(window.inner_size.height), + } + .to_logical::<f64>(hidpi_factor); + + context.ctx.get_io_mut().set_display_size([ + window_size_logical.width as f32, + window_size_logical.height as f32, + ]); +} + +fn update( + target_window_query: Query<(&Window, &Surface, With<TargetWindow>)>, + mut context: Single<Context>, + time: Single<Time>, + mut assets: Single<Assets>, + mut render_passes: Single<RenderPasses>, + shader_context: Single<ShaderContext>, + keyboard: Single<Keyboard>, + mouse: Single<Mouse>, + mouse_buttons: Single<MouseButtons>, + mut state: Local<State>, +) -> Result<(), crate::Error> +{ + let Ok(context) = context.get_mut() else { + unreachable!(); + }; + + let assets = assets.get_mut()?; + let render_passes = render_passes.get_mut()?; + let shader_context = shader_context.get()?; + let keyboard = keyboard.get()?; + let mouse = mouse.get()?; + let mouse_buttons = mouse_buttons.get()?; + + let Some((window, window_surface)) = target_window_query.iter().next() else { + return Ok(()); + }; + + let curr_state = std::mem::replace(&mut *state, State::Unreachable); + + match curr_state { + State::NotInitialized => { + let Some((shader_asset, mesh_obj_id, mesh)) = initialize_context( + context, + &window, + window_surface.id, + assets, + render_passes, + shader_context, + ) else { + *state = State::NotInitialized; + return Ok(()); + }; + + context.ctx.new_frame(); + + *state = State::Ready { shader_asset, mesh_obj_id, mesh }; + } + State::Ready { shader_asset, mesh_obj_id, mut mesh } => { + if !context.enabled { + *state = State::Ready { shader_asset, mesh_obj_id, mesh }; + return Ok(()); + } + + context + .ctx + .get_io_mut() + .set_delta_time(time.delta_time.as_secs_f32()); + + update_inputs(context, &window, keyboard, mouse, mouse_buttons); + + add_drawing_render_pass( + context, + render_passes, + shader_context, + window_surface.id, + shader_asset.clone(), + mesh_obj_id, + &mut mesh, + )?; + + context.ctx.new_frame(); + + *state = State::Ready { shader_asset, mesh_obj_id, mesh }; + } + State::Unreachable => unreachable!(), + } + + Ok(()) +} + +#[derive(Debug, Default, Clone, Copy, Component, Reflection)] +pub struct TargetWindow; + +#[derive(Debug, Default, Component)] +enum State +{ + #[default] + NotInitialized, + Ready + { + shader_asset: AssetHandle<ShaderModuleSource>, + mesh_obj_id: RenderingObjectId, + mesh: Mesh, + }, + Unreachable, +} + +fn initialize_context( + context: &mut Context, + window: &Window, + window_surface_id: SurfaceId, + assets: &mut Assets, + render_passes: &mut RenderPasses, + shader_context: &ShaderContext, +) -> Option<(AssetHandle<ShaderModuleSource>, RenderingObjectId, Mesh)> +{ + let Some(shader_asset) = + assets.get_handle_to_loaded::<ShaderModuleSource>(SHADER_ASSET_LABEL.clone()) + else { + assets.store_with_label( + SHADER_ASSET_LABEL.clone(), + ShaderModuleSource { + name: "imgui_shader.slang".into(), + file_path: Path::new("@engine/imgui_shader").into(), + source: include_str!("../../res/imgui_shader.slang").into(), + link_entrypoints: ShaderEntrypointFlags::VERTEX + | ShaderEntrypointFlags::FRAGMENT, + }, + ); + + // We want to wait with initializing until the shader have been processed by the + // shader context + return None; + }; + + #[allow(clippy::cast_possible_truncation)] + let hidpi_factor = window.scale_factor().round() as f32; + + context + .ctx + .get_io_mut() + .set_display_framebuffer_scale([hidpi_factor, hidpi_factor]); + + let window_size = Dimens { + width: window.inner_size.width as f32, + height: window.inner_size.height as f32, + }; + + #[allow(clippy::cast_possible_truncation)] + let window_logical_size = window_size / hidpi_factor; + + context + .ctx + .get_io_mut() + .set_display_size([window_logical_size.width, window_logical_size.height]); + + let mesh_obj_id = RenderingObjectId::new_sequential(); + + let mesh = Mesh::builder() + .vertices(MeshVertexBuffer::with_capacity( + &[ + MeshVertexAttrInfo { + label: VertexLabel::Position, + ty: MeshVertexAttrType::Float32Array { length: 3 }, + }, + MeshVertexAttrInfo { + label: VertexLabel::Color, + ty: MeshVertexAttrType::Float32Array { length: 4 }, + }, + MeshVertexAttrInfo { + label: VertexLabel::UvFromTopLeft, + ty: MeshVertexAttrType::Float32Array { length: 2 }, + }, + ], + 128, + )) + .indices([]) + .build(); + + render_passes.passes.push_back(RenderPass { + commands: vec![ + RenderingCommand::MakeCurrent(window_surface_id), + RenderingCommand::CreateShaderProgram( + RenderingObjectId::Asset(shader_asset.id()), + shader_context + .get_program(&shader_asset.id()) + .expect("Not possible") + .clone(), + ), + RenderingCommand::ActivateShader(RenderingObjectId::Asset(shader_asset.id())), + RenderingCommand::CreateMesh { + obj_id: mesh_obj_id, + mesh: AssetOrValue::Value(mesh.clone()), + usage: MeshUsage::Stream, + }, + ], + draw_properties: DrawProperties::default(), + }); + + Some((shader_asset, mesh_obj_id, mesh)) +} + +fn update_inputs( + context: &mut Context, + window: &Window, + keyboard: &Keyboard, + mouse: &Mouse, + mouse_buttons: &MouseButtons, +) +{ + let io = context.ctx.get_io_mut(); + + for (key, key_state) in keyboard.new_key_states() { + if let Some(key) = key_to_imgui_key(key) { + io.add_key_event(key, key_state.is_pressed()); + } + } + + for character in keyboard.text_keys().chars() { + if character == '\u{7f}' { + continue; + } + + io.add_input_character(character); + } + + io.add_mouse_source_event(dear_imgui_rs::input::MouseSource::Mouse); + + let mouse_pos = mouse.position.to_logical::<f64>(window.scale_factor()); + + #[allow(clippy::cast_possible_truncation)] + io.add_mouse_pos_event([mouse_pos.x as f32, mouse_pos.y as f32]); + + if !mouse.curr_tick_scroll_delta.is_zero() { + io.add_mouse_wheel_event([ + mouse.curr_tick_scroll_delta.hor_lines, + mouse.curr_tick_scroll_delta.vert_lines, + ]); + } + + for (mouse_button, mouse_button_state) in mouse_buttons.all_current() { + if let Some(mouse_button) = mouse_button_to_imgui_mouse_button(mouse_button) { + io.add_mouse_button_event(mouse_button, mouse_button_state.is_pressed()); + } + } +} + +fn add_drawing_render_pass( + context: &mut Context, + render_passes: &mut RenderPasses, + shader_context: &ShaderContext, + window_surface_id: SurfaceId, + shader_asset: AssetHandle<ShaderModuleSource>, + mesh_obj_id: RenderingObjectId, + mesh: &mut Mesh, +) -> Result<(), crate::Error> +{ + let render_pass = render_passes.passes.push_back_mut(RenderPass { + commands: Vec::new(), + draw_properties: DrawProperties { + blending_enabled: true, + blending_config: RenderingBlendingConfiguration { + equation: RenderingBlendingEquation::Add, + source_factor: RenderingBlendingFactor::SrcAlpha, + destination_factor: RenderingBlendingFactor::OneMinusSrcAlpha, + }, + depth_test_enabled: false, + face_culling_enabled: false, + scissor_test_enabled: true, + ..Default::default() + }, + }); + + let shader_program = shader_context + .get_program(&shader_asset.id()) + .expect("Not possible"); + + let shader_cursor = ShaderCursor::new( + shader_program + .reflection(0) + .unwrap() + .global_params_var_layout() + .unwrap(), + ); + + let Context { + enabled: _, + ctx, + texture_lookup, + texture_id_lookup, + } = context; + + let render_frame = ctx.render(); + + let mut tex_feedbacks = Vec::with_capacity(render_frame.texture_requests().len()); + + for texture_request in render_frame.texture_requests() { + match texture_request.operation() { + dear_imgui_rs::TextureOp::Create { + format, + width, + height, + row_pitch: _, + pixels, + } => { + if !matches!(format, dear_imgui_rs::TextureFormat::RGBA32) { + unimplemented!(); + } + + let texture_lookup_id = TextureLookupId::new(); + + tracing::trace!( + snapshot_tex_id = ?texture_request.texture(), + tex_lookup_id = ?texture_lookup_id, + "Performing texture operation: Create" + ); + + let texture_object_id = RenderingObjectId::new_sequential(); + + render_pass.commands.push(RenderingCommand::CreateTexture { + obj_id: texture_object_id, + pixel_data_format: TexturePixelDataFormat::Srgba( + SrgbaTextureDataType::UnsignedByte, + ), + creation: TextureCreation::Texture2D { + size: Dimens { width: *width, height: *height }, + image: Some(pixels.clone().into_boxed_slice()), + }, + properties: TextureProperties::default(), + }); + + texture_lookup.insert(texture_lookup_id, texture_object_id); + + texture_id_lookup + .insert(texture_request.texture(), texture_lookup_id.into()); + + let texture_feedback = + match texture_request.uploaded(texture_lookup_id.into()) { + Ok(texture_feedback) => texture_feedback, + Err( + dear_imgui_rs::TextureFeedbackError::UploadForDestroy + | dear_imgui_rs::TextureFeedbackError::DestroyForUpload + | dear_imgui_rs::TextureFeedbackError::NullTextureId, + ) => unreachable!(), + }; + + tex_feedbacks.push(texture_feedback); + } + dear_imgui_rs::TextureOp::Update { format, width: _, height: _, rects } => { + if !matches!(format, dear_imgui_rs::TextureFormat::RGBA32) { + unimplemented!(); + } + + let texture_lookup_id = + TextureLookupId::from(texture_id_lookup[&texture_request.texture()]); + + tracing::trace!( + snapshot_tex_id = ?texture_request.texture(), + tex_lookup_id = ?texture_lookup_id, + "Performing texture operation: Update" + ); + + let Some(texture_object_id) = texture_lookup.get(texture_lookup_id) + else { + tracing::error!( + snapshot_tex_id = ?texture_request.texture(), + tex_lookup_id = ?texture_lookup_id, + "Unknown texture. Skipping update", + ); + continue; + }; + + render_pass.commands.reserve(rects.len()); + + for rect in rects { + render_pass.commands.push(RenderingCommand::UpdateTexture { + obj_id: *texture_object_id, + pixels: rect.data.clone().into_boxed_slice(), + pixel_data_format: TexturePixelDataFormat::Srgba( + SrgbaTextureDataType::UnsignedByte, + ), + update: TextureUpdate::Texture2D { + size: Dimens::<u32> { + width: rect.rect.w.into(), + height: rect.rect.h.into(), + }, + offset: Vec2 { + x: u32::from(rect.rect.x), + y: u32::from(rect.rect.y), + }, + }, + }); + } + + let texture_feedback = + match texture_request.uploaded(texture_lookup_id.into()) { + Ok(texture_feedback) => texture_feedback, + Err( + dear_imgui_rs::TextureFeedbackError::UploadForDestroy + | dear_imgui_rs::TextureFeedbackError::DestroyForUpload + | dear_imgui_rs::TextureFeedbackError::NullTextureId, + ) => unreachable!(), + }; + + tex_feedbacks.push(texture_feedback); + } + dear_imgui_rs::TextureOp::Destroy => { + let texture_lookup_id = + TextureLookupId::from(texture_id_lookup[&texture_request.texture()]); + + tracing::trace!( + snapshot_tex_id = ?texture_request.texture(), + tex_lookup_id = ?texture_lookup_id, + "Performing texture operation: Destroy" + ); + + let Some(texture_object_id) = texture_lookup.remove(texture_lookup_id) + else { + tracing::error!( + texture = ?texture_lookup_id, + "Unknown texture. Skipping destroyal", + ); + continue; + }; + + render_pass + .commands + .push(RenderingCommand::RemoveTexture(texture_object_id)); + + let texture_feedback = match texture_request.destroyed() { + Ok(texture_feedback) => texture_feedback, + Err( + dear_imgui_rs::TextureFeedbackError::UploadForDestroy + | dear_imgui_rs::TextureFeedbackError::DestroyForUpload + | dear_imgui_rs::TextureFeedbackError::NullTextureId, + ) => unreachable!(), + }; + + tex_feedbacks.push(texture_feedback); + } + } + } + + let frame = render_frame + .reconcile_texture_feedback(tex_feedbacks) + .with_context(|| "Failed to reconcile texture feedback")?; + + let draw_data = frame.draw_data(); + + let [display_width, display_height] = draw_data.display_size(); + + // let [scale_width, scale_height] = draw_data.framebuffer_scale; + + let shader_object_id = RenderingObjectId::Asset(shader_asset.id()); + + render_pass.commands.extend([ + RenderingCommand::MakeCurrent(window_surface_id), + RenderingCommand::ActivateShader(shader_object_id), + RenderingCommand::SetShaderBinding( + shader_object_id, + shader_cursor + .field("Uniforms") + .field("projection") + .binding( + OrthographicProjection::builder() + .near(1.0) + .far(-1.0) + .viewport_origin(Vec2 { x: 0.0, y: 1.0 }) + .size(crate::projection::OrthographicSize::FixedSize(Dimens { + width: display_width, + height: -display_height, + })) + .build() + .to_matrix_rh( + // Window size can be 0x0 since it will not be used + PhysicalSize::default(), + ProjectionClipVolume::NegOneToOne, + ) + .into(), + )?, + ), + ]); + + for draw_list in draw_data.draw_lists() { + mesh.vertex_buf_mut().clear(); + + for vertex in draw_list.vtx_buffer() { + mesh.vertex_buf_mut().push(( + MeshNamedVertexAttr::<[f32; 3]> { + label: VertexLabel::Position, + value: [vertex.pos[0], vertex.pos[1], 0.0], + }, + MeshNamedVertexAttr { + label: VertexLabel::Color, + value: vertex.rgba().map(|elem| f32::from(elem) / 255.0), + }, + MeshNamedVertexAttr { + label: VertexLabel::UvFromTopLeft, + value: vertex.uv, + }, + )); + } + + mesh.set_indices(draw_list.idx_buffer().iter().map(|index| (*index).into())); + + render_pass.commands.push(RenderingCommand::UpdateMesh { + obj_id: mesh_obj_id, + mesh: mesh.clone(), + usage: MeshUsage::Stream, + }); + + for command in draw_list.commands() { + if let ImguiDrawCmd::Elements { count, cmd_params } = command { + let Some(scissor_box) = calc_draw_cmd_scissor_box(draw_data, &cmd_params) + else { + continue; + }; + + let tex_id = cmd_params.texture_id; + + let Some(texture_object_id) = + texture_lookup.get(TextureLookupId::from(tex_id)) + else { + tracing::error!( + "Unknown texture {}. Skipping skipping draw command", + tex_id.id() + ); + continue; + }; + + render_pass.commands.extend([ + RenderingCommand::UpdateDrawProperties( + DrawProperties { scissor_box, ..Default::default() }, + DrawPropertiesUpdateFlags::SCISSOR_BOX, + ), + RenderingCommand::SetShaderBinding( + shader_object_id, + shader_cursor.field("main_texture").binding( + ShaderBindingValue::Texture( + *texture_object_id, + ShaderBindingTextureKind::Texture2D, + ), + )?, + ), + RenderingCommand::DrawMesh( + mesh_obj_id, + DrawMeshOptions::builder() + .element_offset(cmd_params.idx_offset.try_into().unwrap()) + .vertex_offset(cmd_params.vtx_offset.try_into().unwrap()) + .element_cnt(count.try_into().unwrap()) + .build(), + ), + ]); + } + } + } + + Ok(()) +} + +fn calc_draw_cmd_scissor_box( + draw_data: &dear_imgui_rs::DrawData, + cmd_params: &dear_imgui_rs::DrawCmdParams, +) -> Option<ScissorBox> +{ + let clip_rect = cmd_params.clip_rect; + + let display_size = draw_data.display_size(); + let display_pos = draw_data.display_pos(); + let framebuffer_scale = draw_data.framebuffer_scale(); + + let clip_min_x = (clip_rect[0] - display_pos[0]) * framebuffer_scale[0]; + + let clip_min_y = (clip_rect[1] - display_pos[1]) * framebuffer_scale[1]; + + let clip_max_x = (clip_rect[2] - display_pos[0]) * framebuffer_scale[0]; + + let clip_max_y = (clip_rect[3] - display_pos[1]) * framebuffer_scale[1]; + + if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y { + return None; + } + + let fb_height = display_size[1] * framebuffer_scale[1]; + + Some(ScissorBox { + size: Some(Dimens { + width: (clip_max_x - clip_min_x) as u16, + height: (clip_max_y - clip_min_y) as u16, + }), + lower_left_corner_pos: Vec2 { + x: clip_min_x as u16, + y: (fb_height - clip_max_y) as u16, + }, + }) +} + +fn key_to_imgui_key(key: Key) -> Option<ImguiKey> +{ + match key { + Key::Backquote => Some(ImguiKey::GraveAccent), + Key::Backslash => Some(ImguiKey::Backslash), + Key::BracketLeft => Some(ImguiKey::LeftBracket), + Key::BracketRight => Some(ImguiKey::RightBracket), + Key::Comma => Some(ImguiKey::Comma), + Key::Digit0 => Some(ImguiKey::Key0), + Key::Digit1 => Some(ImguiKey::Key1), + Key::Digit2 => Some(ImguiKey::Key2), + Key::Digit3 => Some(ImguiKey::Key3), + Key::Digit4 => Some(ImguiKey::Key4), + Key::Digit5 => Some(ImguiKey::Key5), + Key::Digit6 => Some(ImguiKey::Key6), + Key::Digit7 => Some(ImguiKey::Key7), + Key::Digit8 => Some(ImguiKey::Key8), + Key::Digit9 => Some(ImguiKey::Key9), + Key::Equal => Some(ImguiKey::Equal), + Key::A => Some(ImguiKey::A), + Key::B => Some(ImguiKey::B), + Key::C => Some(ImguiKey::C), + Key::D => Some(ImguiKey::D), + Key::E => Some(ImguiKey::E), + Key::F => Some(ImguiKey::F), + Key::G => Some(ImguiKey::G), + Key::H => Some(ImguiKey::H), + Key::I => Some(ImguiKey::I), + Key::J => Some(ImguiKey::J), + Key::K => Some(ImguiKey::K), + Key::L => Some(ImguiKey::L), + Key::M => Some(ImguiKey::M), + Key::N => Some(ImguiKey::N), + Key::O => Some(ImguiKey::O), + Key::P => Some(ImguiKey::P), + Key::Q => Some(ImguiKey::Q), + Key::R => Some(ImguiKey::R), + Key::S => Some(ImguiKey::S), + Key::T => Some(ImguiKey::T), + Key::U => Some(ImguiKey::U), + Key::V => Some(ImguiKey::V), + Key::W => Some(ImguiKey::W), + Key::X => Some(ImguiKey::X), + Key::Y => Some(ImguiKey::Y), + Key::Z => Some(ImguiKey::Z), + Key::Minus => Some(ImguiKey::Minus), + Key::Period => Some(ImguiKey::Period), + Key::Quote => Some(ImguiKey::Apostrophe), + Key::Semicolon => Some(ImguiKey::Semicolon), + Key::Slash => Some(ImguiKey::Slash), + Key::AltLeft => Some(ImguiKey::LeftAlt), + Key::AltRight => Some(ImguiKey::RightAlt), + Key::Backspace => Some(ImguiKey::Backspace), + Key::CapsLock => Some(ImguiKey::CapsLock), + Key::ControlLeft => Some(ImguiKey::LeftCtrl), + Key::ControlRight => Some(ImguiKey::RightCtrl), + Key::Enter => Some(ImguiKey::Enter), + Key::SuperLeft => Some(ImguiKey::LeftSuper), + Key::SuperRight => Some(ImguiKey::RightSuper), + Key::ShiftLeft => Some(ImguiKey::LeftShift), + Key::ShiftRight => Some(ImguiKey::RightShift), + Key::Space => Some(ImguiKey::Space), + Key::Tab => Some(ImguiKey::Tab), + Key::Delete => Some(ImguiKey::Delete), + Key::End => Some(ImguiKey::End), + Key::Home => Some(ImguiKey::Home), + Key::Insert => Some(ImguiKey::Insert), + Key::PageDown => Some(ImguiKey::PageDown), + Key::PageUp => Some(ImguiKey::PageUp), + Key::ArrowDown => Some(ImguiKey::DownArrow), + Key::ArrowLeft => Some(ImguiKey::LeftArrow), + Key::ArrowRight => Some(ImguiKey::RightArrow), + Key::ArrowUp => Some(ImguiKey::UpArrow), + Key::NumLock => Some(ImguiKey::NumLock), + Key::Numpad0 => Some(ImguiKey::Keypad0), + Key::Numpad1 => Some(ImguiKey::Keypad1), + Key::Numpad2 => Some(ImguiKey::Keypad2), + Key::Numpad3 => Some(ImguiKey::Keypad3), + Key::Numpad4 => Some(ImguiKey::Keypad4), + Key::Numpad5 => Some(ImguiKey::Keypad5), + Key::Numpad6 => Some(ImguiKey::Keypad6), + Key::Numpad7 => Some(ImguiKey::Keypad7), + Key::Numpad8 => Some(ImguiKey::Keypad8), + Key::Numpad9 => Some(ImguiKey::Keypad9), + Key::NumpadAdd => Some(ImguiKey::KeypadAdd), + Key::NumpadDecimal => Some(ImguiKey::KeypadDecimal), + Key::NumpadDivide => Some(ImguiKey::KeypadDivide), + Key::NumpadEnter => Some(ImguiKey::KeypadEnter), + Key::NumpadEqual => Some(ImguiKey::KeypadEqual), + Key::NumpadMultiply => Some(ImguiKey::KeypadMultiply), + Key::NumpadSubtract => Some(ImguiKey::KeypadSubtract), + Key::Escape => Some(ImguiKey::Escape), + Key::PrintScreen => Some(ImguiKey::PrintScreen), + Key::ScrollLock => Some(ImguiKey::ScrollLock), + Key::Pause => Some(ImguiKey::Pause), + Key::Meta => Some(ImguiKey::ModSuper), + Key::F1 => Some(ImguiKey::F1), + Key::F2 => Some(ImguiKey::F2), + Key::F3 => Some(ImguiKey::F3), + Key::F4 => Some(ImguiKey::F4), + Key::F5 => Some(ImguiKey::F5), + Key::F6 => Some(ImguiKey::F6), + Key::F7 => Some(ImguiKey::F7), + Key::F8 => Some(ImguiKey::F8), + Key::F9 => Some(ImguiKey::F9), + Key::F10 => Some(ImguiKey::F10), + Key::F11 => Some(ImguiKey::F11), + Key::F12 => Some(ImguiKey::F12), + _ => None, + } +} + +fn mouse_button_to_imgui_mouse_button( + mouse_button: MouseButton, +) -> Option<ImguiMouseButton> +{ + match mouse_button { + MouseButton::Left | MouseButton::Other(0) => Some(ImguiMouseButton::Left), + MouseButton::Right | MouseButton::Other(1) => Some(ImguiMouseButton::Right), + MouseButton::Middle | MouseButton::Other(2) => Some(ImguiMouseButton::Middle), + MouseButton::Other(3) => Some(ImguiMouseButton::Extra1), + MouseButton::Other(4) => Some(ImguiMouseButton::Extra2), + _ => None, + } +} + +mod inner_context_wrapper +{ + use std::path::PathBuf; + use std::pin::Pin; + use std::ptr::null_mut; + + pub struct InnerContextWrapper + { + ctx: Pin<Box<dear_imgui_rs::Context>>, + frame: *mut dear_imgui_rs::Ui, + renderer_consumer: dear_imgui_rs::SynchronousRendererConsumer, + } + + impl InnerContextWrapper + { + pub fn new() -> Self + { + let mut ctx = Box::pin(dear_imgui_rs::Context::create()); + + let _ = ctx.set_platform_name(Some("engine-windowing")); + let _ = ctx.set_renderer_name(Some("engine-rendering")); + + ctx.io_mut() + .set_backend_flags(dear_imgui_rs::BackendFlags::RENDERER_HAS_TEXTURES); + + let renderer_consumer = ctx.create_synchronous_renderer_consumer().unwrap(); + + Self { + ctx, + frame: null_mut(), + renderer_consumer, + } + } + + pub fn set_settings_ini_file_path( + &mut self, + path: Option<PathBuf>, + ) -> Result<(), dear_imgui_rs::ImGuiError> + { + self.ctx.set_ini_filename(path) + } + + pub fn register_texture( + &mut self, + texture_data: dear_imgui_rs::texture::OwnedTextureData, + ) -> dear_imgui_rs::ManagedTextureId + { + self.ctx.register_texture(texture_data) + } + + pub fn get_io_mut(&mut self) -> &mut dear_imgui_rs::Io + { + self.ctx.io_mut() + } + + pub fn font_atlas(&self) -> &dear_imgui_rs::FontAtlas + { + self.ctx.font_atlas() + } + + // pub fn create_renderer_consumer(&mut self) -> dear_imgui_rs::RendererConsumer + // { + // self.ctx.create_renderer_consumer().unwrap() + // } + + pub fn render(&mut self) -> dear_imgui_rs::PendingFrame<'_> + { + self.ctx.render(&self.renderer_consumer) + } + + pub fn new_frame(&mut self) + { + let frame = &raw mut *self.ctx.frame(); + + self.frame = frame; + } + + pub fn get_frame(&mut self) -> Option<&mut dear_imgui_rs::Ui> + { + unsafe { self.frame.as_mut() } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +struct TextureLookupId +{ + inner: u64, +} + +impl TextureLookupId +{ + fn new() -> Self + { + static NEXT: AtomicU64 = AtomicU64::new(1); + + Self { + inner: NEXT.fetch_add(1, Ordering::Relaxed), + } + } +} + +impl intmap::IntKey for TextureLookupId +{ + type Int = u64; + + const PRIME: Self::Int = <u64 as intmap::IntKey>::PRIME; + + fn into_int(self) -> Self::Int + { + self.inner + } +} + +impl From<dear_imgui_rs::TextureId> for TextureLookupId +{ + fn from(texture_id: dear_imgui_rs::TextureId) -> Self + { + assert!(!texture_id.is_null(), "Bad texture ID: is null"); + + Self { inner: texture_id.id() } + } +} + +impl From<TextureLookupId> for dear_imgui_rs::TextureId +{ + fn from(texture_id: TextureLookupId) -> Self + { + Self::new(texture_id.inner) + } +} |
