summaryrefslogtreecommitdiff
path: root/engine/src/ui/dear_imgui.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-07-16 17:39:21 +0200
committerHampusM <hampus@hampusmat.com>2026-07-16 17:39:21 +0200
commitaf7e3ed5b4afdf78972749b627c2765c9e5945fb (patch)
tree3b6bf23e2dbc15394e13faeb854a2dd7b817ec37 /engine/src/ui/dear_imgui.rs
parent6e3864ded2ca04cbe5918c6b3efc7d49d07d4afd (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.rs1039
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)
+ }
+}