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 = 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, } 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, } 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, ) -> 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>, mut context: Single, ) { 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::(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,)>, mut context: Single, delta_time: Single, mut assets: Single, mut render_passes: Single, shader_context: Single, keyboard: Single, mouse: Single, mouse_buttons: Single, mut state: Local, ) -> 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, 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, RenderingObjectId, Mesh)> { let shader_asset = if let Some(shader_asset) = assets.get_handle_to_loaded::(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::(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, 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::, _>::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 = 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 { 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:: { x: rect.x.into(), y: rect.y.into() }; let rect_size = Dimens:: { 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::, _>::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 { 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 { 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 { 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>, 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, ) -> 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 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 for dear_imgui_rs::TextureId { fn from(texture_id: TextureLookupId) -> Self { Self::new(texture_id.inner) } }