diff options
| author | HampusM <hampus@hampusmat.com> | 2026-07-16 17:39:21 +0200 |
|---|---|---|
| committer | HampusM <hampus@hampusmat.com> | 2026-07-16 17:39:21 +0200 |
| commit | af7e3ed5b4afdf78972749b627c2765c9e5945fb (patch) | |
| tree | 3b6bf23e2dbc15394e13faeb854a2dd7b817ec37 /engine/src/ui/dear_imgui.rs | |
| parent | 6e3864ded2ca04cbe5918c6b3efc7d49d07d4afd (diff) | |
refactor(engine): rename ui::imgui mod to dear_imgui
Diffstat (limited to 'engine/src/ui/dear_imgui.rs')
| -rw-r--r-- | engine/src/ui/dear_imgui.rs | 1039 |
1 files changed, 1039 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..7c5dbc3 --- /dev/null +++ b/engine/src/ui/dear_imgui.rs @@ -0,0 +1,1039 @@ +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::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::{Component, Query, Sole}; + +use crate::asset::{Assets, Handle as AssetHandle, Label as AssetLabel}; +use crate::data_types::color::Rgba; +use crate::data_types::dimens::Dimens; +use crate::delta_time::DeltaTime; +use crate::image::{ + Image, + PixelBuffer as ImagePixelBuffer, + PixelBufferLenIncorrectForSize as ImagePixelBufferLenIncorrectForSize, +}; +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, +}; +use crate::mesh::{ + Mesh, + VertexAttrType as MeshVertexAttrType, + POSITION_VERTEX_ATTRIB_NAME, +}; +use crate::projection::{ + ClipVolume as ProjectionClipVolume, + Orthographic as OrthographicProjection, +}; +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::{ + 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, + SurfaceId, + SurfaceSpec, + PRE_RENDER_PHASE, +}; +use crate::texture::{Properties as TextureProperties, Texture}; +use crate::util::MapVec; +use crate::vector::Vec2; +use crate::windowing::dpi::PhysicalSize; +use crate::windowing::window::Window; + +mod reexports +{ + pub use dear_imgui_rs as dear_imgui; +} + +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: MapVec<TextureLookupId, RenderingObjectId>, +} + +impl Context +{ + pub fn frame(&mut self) -> Option<&mut dear_imgui_rs::Ui> + { + if !self.enabled { + return None; + } + + self.ctx.get_frame() + } + + pub fn register_texture(&mut self, texture_data: &mut dear_imgui_rs::OwnedTextureData) + { + 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 +{ + pub fn with_start_enabled(mut self, start_enabled: bool) -> Self + { + self.start_enabled = start_enabled; + self + } + + 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: MapVec::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}"); + } + + 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: window.inner_size.width as f64, + height: window.inner_size.height as f64, + } + .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, &SurfaceSpec), (With<TargetWindow>,)>, + mut context: Single<Context>, + delta_time: Single<DeltaTime>, + 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 delta_time = delta_time.get()?; + 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_spec)) = 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_spec.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(delta_time.duration.as_secs_f32()); + + update_inputs(context, &window, keyboard, mouse, mouse_buttons); + + add_drawing_render_pass( + context, + render_passes, + shader_context, + window_surface_spec.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, Component)] +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 shader_asset = if let Some(shader_asset) = + assets.get_handle_to_loaded::<ShaderModuleSource>(SHADER_ASSET_LABEL.clone()) + { + shader_asset + } 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; + }; + + 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 = Dimens { + width: window.inner_size.width as f32, + height: window.inner_size.height as f32, + }; + + let window_logical_size = window_size / (hidpi_factor as f32); + + 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 { + name: POSITION_VERTEX_ATTRIB_NAME.into(), + ty: MeshVertexAttrType::Float32Array { length: 3 }, + }, + MeshVertexAttrInfo { + name: "color".into(), + ty: MeshVertexAttrType::Float32Array { length: 4 }, + }, + MeshVertexAttrInfo { + name: "texture_coords".into(), + 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()); + + 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, +) +{ + 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 } = context; + + let draw_data = ctx.render(); + + let mut textures = draw_data.textures_mut(); + + while let Some(mut texture_data) = textures.next() { + match texture_data.status() { + dear_imgui_rs::TextureStatus::WantCreate => { + let Some(texture_pixels) = texture_data.pixels() else { + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + }; + + if !matches!(texture_data.format(), dear_imgui_rs::TextureFormat::RGBA32) + { + unimplemented!(); + } + + let image = + match Image::from_pixels(ImagePixelBuffer::<Rgba<u8>, _>::new( + texture_pixels, + Dimens { + width: texture_data.width(), + height: texture_data.height(), + }, + )) { + Ok(image) => image, + Err(ImagePixelBufferLenIncorrectForSize) => { + tracing::error!("Not enough texture pixels for texture size"); + continue; + } + }; + + let texture_object_id = RenderingObjectId::new_sequential(); + + render_pass.commands.push(RenderingCommand::CreateTexture( + texture_object_id, + AssetOrValue::Value(Texture { + image, + properties: TextureProperties::default(), + }), + )); + + let texture_lookup_id = TextureLookupId::new(); + + texture_data.set_tex_id(texture_lookup_id.into()); + + texture_lookup.insert(texture_lookup_id, texture_object_id); + + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + } + dear_imgui_rs::TextureStatus::WantUpdates => { + if texture_data.tex_id().is_null() { + tracing::error!(concat!( + "Update was requested for texture with a null ID. Did you forget ", + "to set the texture status to TextureStatus::WantCreate?")); + + continue; + } + + if texture_data.pixels().is_none() { + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + } + + let mut rects: Vec<dear_imgui_rs::texture::TextureRect> = + texture_data.updates().collect(); + + if rects.is_empty() { + let rect = texture_data.update_rect(); + if rect.w > 0 && rect.h > 0 { + rects.push(rect); + } + } + + if rects.is_empty() { + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + } + + let Some(texture_object_id) = + texture_lookup.get(&TextureLookupId::from(texture_data.tex_id())) + else { + tracing::error!( + "Unknown texture {}. Skipping update", + texture_data.tex_id().id() + ); + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + }; + + render_pass.commands.extend(rects.iter().filter_map(|rect| { + let Some(rect_image) = get_texture_rect_image(&texture_data, rect) + else { + return None; + }; + + Some(RenderingCommand::UpdateTexture { + obj_id: texture_object_id.clone(), + image: rect_image, + offset: Vec2 { x: rect.x as u32, y: rect.y as u32 }, + }) + })); + + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + } + dear_imgui_rs::TextureStatus::WantDestroy => { + let Some(texture_object_id) = + texture_lookup.remove(TextureLookupId::from(texture_data.tex_id())) + else { + tracing::error!( + "Unknown texture {}. Skipping destroyal", + texture_data.tex_id().id() + ); + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + }; + + render_pass + .commands + .push(RenderingCommand::RemoveTexture(texture_object_id)); + } + _ => {} + } + } + + let [display_width, display_height] = draw_data.display_size; + + // let [scale_width, scale_height] = draw_data.framebuffer_scale; + + render_pass.commands.extend([ + RenderingCommand::MakeCurrent(window_surface_id), + RenderingCommand::ActivateShader(RenderingObjectId::Asset(shader_asset.id())), + RenderingCommand::SetShaderBinding( + shader_cursor + .field("Uniforms") + .field("projection") + .into_binding_location(), + ShaderBindingValue::FMat4x4( + OrthographicProjection::builder() + .near(1.0) + .far(-1.0) + .viewport_origin(Vec2 { x: 0.0, y: 1.0 }) + .size(crate::projection::OrthographicSize::WindowSize) + .build() + .to_matrix_rh( + Dimens { + width: display_width, + height: -display_height, + }, + ProjectionClipVolume::NegOneToOne, + ), + ), + ), + ]); + + 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]> { + name: POSITION_VERTEX_ATTRIB_NAME, + value: [vertex.pos[0], vertex.pos[1], 0.0], + }, + MeshNamedVertexAttr { + name: "color", + value: vertex.rgba().map(|elem| (elem as f32) / 255.0), + }, + MeshNamedVertexAttr { + name: "texture_coords", + 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() { + match command { + ImguiDrawCmd::Elements { count, cmd_params, raw_cmd } => { + let Some(scissor_box) = + calc_draw_cmd_scissor_box(draw_data, &cmd_params) + else { + continue; + }; + + let tex_id = dear_imgui_rs::TextureId::new(u64::from(unsafe { + let mut cmd_copy = *raw_cmd; + + dear_imgui_rs::sys::ImDrawCmd_GetTexID(&mut cmd_copy) + })); + + 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_cursor.field("main_texture").into_binding_location(), + ShaderBindingValue::Texture(texture_object_id.clone()), + ), + 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(), + ), + ]); + } + _ => {} + } + } + } +} + +fn get_texture_rect_image( + texture_data: &dear_imgui_rs::TextureData, + rect: &dear_imgui_rs::TextureRect, +) -> Option<Image> +{ + let texture_pixels = texture_data.pixels()?; + + let tex_width = texture_data.width(); + let tex_height = texture_data.height(); + + if tex_width == 0 || tex_height == 0 { + return None; + } + + let rect_pos = Vec2::<u32> { x: rect.x.into(), y: rect.y.into() }; + + let rect_size = Dimens::<u32> { + width: rect.w.into(), + height: rect.h.into(), + }; + + if rect_size.width == 0 + || rect_size.height == 0 + || rect_pos.x >= tex_width + || rect_pos.y >= tex_height + { + return None; + } + + let rect_size = Dimens { + width: rect_size.width.min(tex_width.saturating_sub(rect_pos.x)), + height: rect_size.height.min(tex_height.saturating_sub(rect_pos.y)), + }; + + match texture_data.format() { + dear_imgui_rs::TextureFormat::RGBA32 => { + let Ok(texture_image) = + Image::from_pixels(ImagePixelBuffer::<Rgba<u8>, _>::new( + texture_pixels, + Dimens { width: tex_width, height: tex_height }, + )) + else { + return None; + }; + + Some(texture_image.sub_view(rect_pos, rect_size).to_image()) + } + dear_imgui_rs::TextureFormat::Alpha8 => { + unimplemented!(); + } + } +} + +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 clip_min_x = + (clip_rect[0] - draw_data.display_pos[0]) * draw_data.framebuffer_scale[0]; + + let clip_min_y = + (clip_rect[1] - draw_data.display_pos[1]) * draw_data.framebuffer_scale[1]; + + let clip_max_x = + (clip_rect[2] - draw_data.display_pos[0]) * draw_data.framebuffer_scale[0]; + + let clip_max_y = + (clip_rect[3] - draw_data.display_pos[1]) * draw_data.framebuffer_scale[1]; + + if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y { + return None; + } + + let fb_height = draw_data.display_size[1] * draw_data.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, + } + + 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); + + Self { ctx, frame: null_mut() } + } + + 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: &mut dear_imgui_rs::texture::OwnedTextureData, + ) + { + self.ctx.register_user_texture(texture_data); + } + + pub fn get_io_mut(&mut self) -> &mut dear_imgui_rs::Io + { + self.ctx.io_mut() + } + + pub fn render(&mut self) -> &mut dear_imgui_rs::DrawData + { + self.ctx.render() + } + + 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 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) + } +} |
