diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e845083..6a730d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: - rust: ["1.57"] + rust: ["1.60"] env: RUSTFLAGS: -D warnings @@ -81,7 +81,7 @@ jobs: matrix: rust: - stable - - "1.57" + - "1.60" os: - ubuntu-latest - macos-latest diff --git a/Cargo.toml b/Cargo.toml index 2ce437c..c0c9f5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "imgui-glow-renderer", "imgui-sdl2-support", "imgui-winit-support", + "imgui-winit-glow-renderer-viewports", "imgui-examples", "xtask", ] diff --git a/README.markdown b/README.markdown index af81b54..dcf13f3 100644 --- a/README.markdown +++ b/README.markdown @@ -60,7 +60,7 @@ be applicable to usage with any backend/renderer. ## Minimum Support Rust Version (MSRV) -The MSRV for `imgui-rs` and all of the backend crates is **1.57**. We update our MSRV periodically, and issue a minor bump for it. +The MSRV for `imgui-rs` and all of the backend crates is **1.60**. We update our MSRV periodically, and issue a minor bump for it. ## Choosing a backend platform and a renderer diff --git a/imgui-examples/examples/hello_world.rs b/imgui-examples/examples/hello_world.rs index 29f74ed..fccd32e 100644 --- a/imgui-examples/examples/hello_world.rs +++ b/imgui-examples/examples/hello_world.rs @@ -4,8 +4,10 @@ mod support; fn main() { let system = support::init(file!()); + let mut value = 0; let choices = ["test test this is 1", "test test this is 2"]; + system.main_loop(move |_, ui| { ui.window("Hello world") .size([300.0, 110.0], Condition::FirstUseEver) diff --git a/imgui-examples/examples/tables_api.rs b/imgui-examples/examples/tables_api.rs index 47a1d7d..1263617 100644 --- a/imgui-examples/examples/tables_api.rs +++ b/imgui-examples/examples/tables_api.rs @@ -190,7 +190,7 @@ struct HumanData { } impl HumanData { - pub fn sort_humans(humans: &mut Vec, specs: Specs<'_>) { + pub fn sort_humans(humans: &mut [Self], specs: Specs<'_>) { let spec = specs.iter().next().unwrap(); if let Some(kind) = spec.sort_direction() { match kind { diff --git a/imgui-winit-glow-renderer-viewports/Cargo.toml b/imgui-winit-glow-renderer-viewports/Cargo.toml new file mode 100644 index 0000000..62d4c32 --- /dev/null +++ b/imgui-winit-glow-renderer-viewports/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "imgui-winit-glow-renderer-viewports" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +imgui = { version="0.10.0", path="../imgui", features=["docking"] } + +glow = "0.11.2" +glutin = "0.30.3" +raw-window-handle = "0.5.0" +winit = "0.27.5" +thiserror = "1.0.38" +glutin-winit = "0.2.1" diff --git a/imgui-winit-glow-renderer-viewports/examples/viewports_basic.rs b/imgui-winit-glow-renderer-viewports/examples/viewports_basic.rs new file mode 100644 index 0000000..b49e70f --- /dev/null +++ b/imgui-winit-glow-renderer-viewports/examples/viewports_basic.rs @@ -0,0 +1,162 @@ +use std::{ffi::CString, num::NonZeroU32, time::Instant}; + +use glow::{Context, HasContext}; +use glutin::{ + config::ConfigTemplateBuilder, + context::ContextAttributesBuilder, + display::GetGlDisplay, + prelude::{ + GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentContextGlSurfaceAccessor, + }, + surface::{GlSurface, SurfaceAttributesBuilder, WindowSurface}, +}; +use glutin_winit::DisplayBuilder; +use imgui::ConfigFlags; +use imgui_winit_glow_renderer_viewports::Renderer; +use raw_window_handle::HasRawWindowHandle; +use winit::{dpi::LogicalSize, event::WindowEvent, event_loop::EventLoop, window::WindowBuilder}; + +fn main() { + let event_loop = EventLoop::new(); + + let window_builder = WindowBuilder::new() + .with_inner_size(LogicalSize::new(800.0, 600.0)) + .with_visible(true) + .with_resizable(true) + .with_title("Viewports example"); + + let template_builder = ConfigTemplateBuilder::new(); + let (window, gl_config) = DisplayBuilder::new() + .with_window_builder(Some(window_builder)) + .build(&event_loop, template_builder, |mut configs| { + configs.next().unwrap() + }) + .expect("Failed to create main window"); + + let window = window.unwrap(); + + let context_attribs = ContextAttributesBuilder::new().build(Some(window.raw_window_handle())); + let context = unsafe { + gl_config + .display() + .create_context(&gl_config, &context_attribs) + .expect("Failed to create main context") + }; + + let size = window.inner_size(); + let surface_attribs = SurfaceAttributesBuilder::::new().build( + window.raw_window_handle(), + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), + ); + let surface = unsafe { + gl_config + .display() + .create_window_surface(&gl_config, &surface_attribs) + .expect("Failed to create main surface") + }; + + let context = context + .make_current(&surface) + .expect("Failed to make current"); + + let glow = unsafe { + Context::from_loader_function(|name| { + let name = CString::new(name).unwrap(); + context.display().get_proc_address(&name) + }) + }; + + let mut imgui = imgui::Context::create(); + imgui + .io_mut() + .config_flags + .insert(ConfigFlags::DOCKING_ENABLE); + imgui + .io_mut() + .config_flags + .insert(ConfigFlags::VIEWPORTS_ENABLE); + imgui.set_ini_filename(None); + + let mut renderer = Renderer::new(&mut imgui, &window, &glow).expect("Failed to init Renderer"); + + let mut last_frame = Instant::now(); + + event_loop.run(move |event, window_target, control_flow| { + control_flow.set_poll(); + + renderer.handle_event(&mut imgui, &window, &event); + + match event { + winit::event::Event::NewEvents(_) => { + let now = Instant::now(); + imgui.io_mut().update_delta_time(now - last_frame); + last_frame = now; + } + winit::event::Event::WindowEvent { + window_id, + event: WindowEvent::CloseRequested, + } if window_id == window.id() => { + control_flow.set_exit(); + } + winit::event::Event::WindowEvent { + window_id, + event: WindowEvent::Resized(new_size), + } if window_id == window.id() => { + surface.resize( + &context, + NonZeroU32::new(new_size.width).unwrap(), + NonZeroU32::new(new_size.height).unwrap(), + ); + } + winit::event::Event::MainEventsCleared => { + window.request_redraw(); + } + winit::event::Event::RedrawRequested(_) => { + let ui = imgui.frame(); + + ui.dockspace_over_main_viewport(); + + ui.show_demo_window(&mut true); + ui.window("Style Editor").build(|| { + ui.show_default_style_editor(); + }); + + ui.end_frame_early(); + + renderer.prepare_render(&mut imgui, &window); + + imgui.update_platform_windows(); + renderer + .update_viewports(&mut imgui, window_target, &glow) + .expect("Failed to update viewports"); + + let draw_data = imgui.render(); + + if let Err(e) = context.make_current(&surface) { + // For some reason make_current randomly throws errors on windows. + // Until the reason for this is found, we just print it out instead of panicing. + eprintln!("Failed to make current: {e}"); + } + + unsafe { + glow.disable(glow::SCISSOR_TEST); + glow.clear(glow::COLOR_BUFFER_BIT); + } + + renderer + .render(&window, &glow, draw_data) + .expect("Failed to render main viewport"); + + surface + .swap_buffers(&context) + .expect("Failed to swap buffers"); + + renderer + .render_viewports(&glow, &mut imgui) + .expect("Failed to render viewports"); + } + _ => {} + } + }); +} diff --git a/imgui-winit-glow-renderer-viewports/src/fragment_shader.glsl b/imgui-winit-glow-renderer-viewports/src/fragment_shader.glsl new file mode 100644 index 0000000..505432c --- /dev/null +++ b/imgui-winit-glow-renderer-viewports/src/fragment_shader.glsl @@ -0,0 +1,13 @@ +#version 330 + +in vec2 v2f_UV; +in vec4 v2f_Color; + +uniform sampler2D u_FontTexture; + +layout(location = 0) out vec4 out_Color; + +void main() { + vec4 tex = texture(u_FontTexture, v2f_UV); + out_Color = v2f_Color * tex; +} diff --git a/imgui-winit-glow-renderer-viewports/src/lib.rs b/imgui-winit-glow-renderer-viewports/src/lib.rs new file mode 100644 index 0000000..3a2c8fa --- /dev/null +++ b/imgui-winit-glow-renderer-viewports/src/lib.rs @@ -0,0 +1,1143 @@ +use std::{ + cell::RefCell, + collections::{HashMap, VecDeque}, + num::NonZeroU32, + ptr::null_mut, + rc::Rc, + slice, +}; + +use glow::HasContext; +use glutin::{ + config::ConfigTemplateBuilder, + context::{ContextAttributesBuilder, NotCurrentContext}, + display::GetGlDisplay, + prelude::{GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentGlContext}, + surface::{GlSurface, Surface, SurfaceAttributesBuilder, WindowSurface}, +}; +use glutin_winit::DisplayBuilder; +use imgui::{BackendFlags, ConfigFlags, Id, Key, MouseButton, ViewportFlags}; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; +use thiserror::Error; +use winit::{ + dpi::{PhysicalPosition, PhysicalSize}, + event::{DeviceEvent, ElementState, KeyboardInput, TouchPhase, VirtualKeyCode}, + event_loop::EventLoopWindowTarget, + window::{CursorIcon, Window, WindowBuilder}, +}; + +const VERTEX_SHADER: &str = include_str!("vertex_shader.glsl"); +const FRAGMENT_SHADER: &str = include_str!("fragment_shader.glsl"); + +#[derive(Debug, Error)] +pub enum RendererError { + #[error("OpenGL shader creation failed: {0}")] + GlShaderCreationFailed(String), + #[error("OpenGL program creation failed: {0}")] + GlProgramCreationFailed(String), + #[error("OpenGL texture creation failed: {0}")] + GlTextureCreationFailed(String), + #[error("OpenGL buffer creation failed: {0}")] + GlBufferCreationFailed(String), + #[error("OpenGL vertex array creation failed: {0}")] + GlVertexArrayCreationFailed(String), + #[error("Failed to create viewport window")] + WindowCreationFailed, + #[error("Failed to create viewport window context")] + WindowContextCreationFailed, + #[error("Failed to create viewport window surface")] + WindowSurfaceCreationFailed, + #[error("Failed to make viewport context current")] + MakeCurrentFailed, + #[error("Failed to make swap buffers on surface")] + SwapBuffersFailed, +} + +#[derive(Debug)] +enum ViewportEvent { + Create(Id), + Destroy(Id), + SetPos(Id, [f32; 2]), + SetSize(Id, [f32; 2]), + SetVisible(Id), + SetFocus(Id), + SetTitle(Id, String), +} + +#[derive(Debug)] +pub struct Renderer { + gl_objects: GlObjects, + glutin_config: Option, + /// The tuple members have to stay in exactly this order + /// to ensure that surface, context and window are dropped in this order + extra_windows: HashMap< + Id, + ( + GlObjects, + Surface, + Option, + Window, + ), + >, + event_queue: Rc>>, + font_width: u32, + font_height: u32, + font_pixels: Vec, + last_cursor: CursorIcon, +} + +#[derive(Debug)] +struct GlObjects { + program: glow::Program, + font_texture: glow::Texture, + vao: glow::VertexArray, + vbo: glow::Buffer, + ibo: glow::Buffer, +} + +impl GlObjects { + pub fn new( + font_width: u32, + font_height: u32, + font_pixels: &[u8], + glow: &glow::Context, + ) -> Result { + let program = unsafe { + let vertex_shader = glow + .create_shader(glow::VERTEX_SHADER) + .map_err(RendererError::GlShaderCreationFailed)?; + glow.shader_source(vertex_shader, VERTEX_SHADER); + glow.compile_shader(vertex_shader); + assert!( + glow.get_shader_compile_status(vertex_shader), + "Vertex Shader contains error" + ); + + let fragment_shader = glow + .create_shader(glow::FRAGMENT_SHADER) + .map_err(RendererError::GlShaderCreationFailed)?; + glow.shader_source(fragment_shader, FRAGMENT_SHADER); + glow.compile_shader(fragment_shader); + assert!( + glow.get_shader_compile_status(fragment_shader), + "Fragment Shader contains error" + ); + + let program = glow + .create_program() + .map_err(RendererError::GlProgramCreationFailed)?; + glow.attach_shader(program, vertex_shader); + glow.attach_shader(program, fragment_shader); + glow.link_program(program); + assert!( + glow.get_program_link_status(program), + "Program contains error" + ); + + glow.delete_shader(vertex_shader); + glow.delete_shader(fragment_shader); + + program + }; + + let font_texture = unsafe { + let tex = glow + .create_texture() + .map_err(RendererError::GlTextureCreationFailed)?; + glow.bind_texture(glow::TEXTURE_2D, Some(tex)); + glow.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as i32, + ); + glow.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as i32, + ); + glow.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_S, + glow::CLAMP_TO_EDGE as i32, + ); + glow.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_T, + glow::CLAMP_TO_EDGE as i32, + ); + glow.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::RGBA as i32, + font_width as i32, + font_height as i32, + 0, + glow::RGBA, + glow::UNSIGNED_BYTE, + Some(font_pixels), + ); + + tex + }; + + let vbo = unsafe { + glow.create_buffer() + .map_err(RendererError::GlBufferCreationFailed)? + }; + let ibo = unsafe { + glow.create_buffer() + .map_err(RendererError::GlBufferCreationFailed)? + }; + + let vao = unsafe { + let vao = glow + .create_vertex_array() + .map_err(RendererError::GlVertexArrayCreationFailed)?; + + glow.bind_vertex_array(Some(vao)); + glow.bind_buffer(glow::ARRAY_BUFFER, Some(vbo)); + glow.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ibo)); + glow.enable_vertex_attrib_array(0); + glow.enable_vertex_attrib_array(1); + glow.enable_vertex_attrib_array(2); + glow.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, 20, 0); + glow.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, 20, 8); + glow.vertex_attrib_pointer_f32(2, 4, glow::UNSIGNED_BYTE, true, 20, 16); + glow.bind_vertex_array(None); + + vao + }; + + Ok(Self { + program, + font_texture, + vao, + vbo, + ibo, + }) + } +} + +#[derive(Debug)] +struct GlStateBackup { + viewport: [i32; 4], + blend_enabled: bool, + blend_func_src: i32, + blend_func_dst: i32, + scissor_enabled: bool, + scissor: [i32; 4], + vao: i32, + vbo: i32, + ibo: i32, + active_texture: i32, + texture: i32, + program: i32, +} + +impl GlStateBackup { + fn backup(context: &glow::Context) -> Self { + unsafe { + let mut viewport = [0; 4]; + context.get_parameter_i32_slice(glow::VIEWPORT, &mut viewport); + + let blend_enabled = context.is_enabled(glow::BLEND); + let blend_func_src = context.get_parameter_i32(glow::BLEND_SRC); + let blend_func_dst = context.get_parameter_i32(glow::BLEND_DST); + + let scissor_enabled = context.is_enabled(glow::SCISSOR_TEST); + let mut scissor = [0; 4]; + context.get_parameter_i32_slice(glow::SCISSOR_BOX, &mut scissor); + + let vao = context.get_parameter_i32(glow::VERTEX_ARRAY_BINDING); + let vbo = context.get_parameter_i32(glow::ARRAY_BUFFER_BINDING); + let ibo = context.get_parameter_i32(glow::ELEMENT_ARRAY_BUFFER_BINDING); + + let active_texture = context.get_parameter_i32(glow::ACTIVE_TEXTURE); + context.active_texture(0); + let texture = context.get_parameter_i32(glow::TEXTURE_BINDING_2D); + + let program = context.get_parameter_i32(glow::CURRENT_PROGRAM); + + Self { + viewport, + blend_enabled, + blend_func_src, + blend_func_dst, + scissor_enabled, + scissor, + vao, + vbo, + ibo, + active_texture, + texture, + program, + } + } + } + + fn restore(&self, context: &glow::Context) { + unsafe { + context.viewport( + self.viewport[0], + self.viewport[1], + self.viewport[2], + self.viewport[3], + ); + + Self::enable(context, glow::BLEND, self.blend_enabled); + context.blend_func(self.blend_func_src as _, self.blend_func_dst as _); + + Self::enable(context, glow::SCISSOR_TEST, self.scissor_enabled); + context.scissor( + self.scissor[0], + self.scissor[1], + self.scissor[2], + self.scissor[3], + ); + + if self.vao != 0 { + let vao = std::mem::transmute(self.vao); + context.bind_vertex_array(Some(vao)); + } else { + context.bind_vertex_array(None); + } + + if self.vbo != 0 { + let vbo = std::mem::transmute(self.vbo); + context.bind_buffer(glow::ARRAY_BUFFER, Some(vbo)); + } else { + context.bind_buffer(glow::ARRAY_BUFFER, None); + } + + if self.ibo != 0 { + let ibo = std::mem::transmute(self.ibo); + context.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ibo)); + } else { + context.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); + } + + if self.texture != 0 { + let texture = std::mem::transmute(self.texture); + context.bind_texture(glow::TEXTURE_2D, Some(texture)); + } else { + context.bind_texture(glow::TEXTURE_2D, None); + } + context.active_texture(self.active_texture as _); + + if self.program != 0 { + let program = std::mem::transmute(self.program); + context.use_program(Some(program)); + } else { + context.use_program(None); + } + } + } + + fn enable(context: &glow::Context, feature: u32, value: bool) { + unsafe { + if value { + context.enable(feature); + } else { + context.disable(feature); + } + } + } +} + +impl Renderer { + pub fn new( + imgui: &mut imgui::Context, + main_window: &Window, + gl_context: &glow::Context, + ) -> Result { + let io = imgui.io_mut(); + + // there is no good way to handle viewports on wayland, + // so we disable them + match main_window.raw_window_handle() { + RawWindowHandle::Wayland(_) => {} + _ => { + io.backend_flags + .insert(BackendFlags::PLATFORM_HAS_VIEWPORTS); + io.backend_flags + .insert(BackendFlags::RENDERER_HAS_VIEWPORTS); + } + } + + io.backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS); + io.backend_flags.insert(BackendFlags::HAS_SET_MOUSE_POS); + + io.backend_flags + .insert(BackendFlags::RENDERER_HAS_VTX_OFFSET); + + let window_size = main_window.inner_size().cast::(); + io.display_size = [window_size.width, window_size.height]; + io.display_framebuffer_scale = [1.0, 1.0]; + + let viewport = imgui.main_viewport_mut(); + + let main_pos = main_window + .inner_position() + .unwrap_or_default() + .cast::(); + + viewport.pos = [main_pos.x, main_pos.y]; + viewport.work_pos = viewport.pos; + viewport.size = [window_size.width, window_size.height]; + viewport.work_size = viewport.size; + viewport.dpi_scale = 1.0; + viewport.platform_user_data = Box::into_raw(Box::new(ViewportData { + pos: [main_pos.x, main_pos.y], + size: [window_size.width, window_size.height], + focus: true, + minimized: false, + })) + .cast(); + + let mut monitors = Vec::new(); + for monitor in main_window.available_monitors() { + monitors.push(imgui::PlatformMonitor { + main_pos: [monitor.position().x as f32, monitor.position().y as f32], + main_size: [monitor.size().width as f32, monitor.size().height as f32], + work_pos: [monitor.position().x as f32, monitor.position().y as f32], + work_size: [monitor.size().width as f32, monitor.size().height as f32], + dpi_scale: 1.0, + }); + } + imgui + .platform_io_mut() + .monitors + .replace_from_slice(&monitors); + + imgui.set_platform_name(Some(format!( + "imgui-winit-glow-renderer-viewports {}", + env!("CARGO_PKG_VERSION") + ))); + imgui.set_renderer_name(Some(format!( + "imgui-winit-glow-renderer-viewports {}", + env!("CARGO_PKG_VERSION") + ))); + + let event_queue = Rc::new(RefCell::new(VecDeque::new())); + + imgui.set_platform_backend(PlatformBackend { + event_queue: event_queue.clone(), + }); + imgui.set_renderer_backend(RendererBackend {}); + + let font_tex = imgui.fonts().build_rgba32_texture(); + let gl_objects = + GlObjects::new(font_tex.width, font_tex.height, font_tex.data, gl_context)?; + + Ok(Self { + gl_objects, + glutin_config: None, + extra_windows: HashMap::new(), + event_queue, + font_width: font_tex.width, + font_height: font_tex.height, + font_pixels: font_tex.data.to_vec(), + last_cursor: CursorIcon::Default, + }) + } + + pub fn handle_event( + &mut self, + imgui: &mut imgui::Context, + main_window: &Window, + event: &winit::event::Event, + ) { + match *event { + winit::event::Event::WindowEvent { + window_id, + ref event, + } => { + let (window, viewport) = if window_id == main_window.id() { + (main_window, imgui.main_viewport_mut()) + } else if let Some((id, wnd)) = + self.extra_windows.iter().find_map(|(id, (_, _, _, wnd))| { + if wnd.id() == window_id { + Some((*id, wnd)) + } else { + None + } + }) + { + if let Some(viewport) = imgui.viewport_by_id_mut(id) { + (wnd, viewport) + } else { + return; + } + } else { + return; + }; + + match *event { + winit::event::WindowEvent::Resized(new_size) => { + unsafe { + (*(viewport.platform_user_data.cast::())).size = + [new_size.width as f32, new_size.height as f32]; + } + + viewport.platform_request_resize = true; + + if window_id == main_window.id() { + imgui.io_mut().display_size = + [new_size.width as f32, new_size.height as f32]; + } + } + winit::event::WindowEvent::Moved(_) => unsafe { + let new_pos = window.inner_position().unwrap().cast::(); + (*(viewport.platform_user_data.cast::())).pos = + [new_pos.x as f32, new_pos.y as f32]; + + viewport.platform_request_move = true; + }, + winit::event::WindowEvent::CloseRequested if window_id != main_window.id() => { + viewport.platform_request_close = true; + } + winit::event::WindowEvent::ReceivedCharacter(c) => { + imgui.io_mut().add_input_character(c); + } + winit::event::WindowEvent::Focused(f) => unsafe { + (*(viewport.platform_user_data.cast::())).focus = f; + }, + winit::event::WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(key), + state: ElementState::Pressed, + .. + }, + .. + } => { + if let Some(key) = to_imgui_key(key) { + imgui.io_mut().add_key_event(key, true); + } + } + winit::event::WindowEvent::ModifiersChanged(modifiers) => { + imgui + .io_mut() + .add_key_event(Key::ModShift, modifiers.shift()); + imgui.io_mut().add_key_event(Key::ModCtrl, modifiers.ctrl()); + imgui.io_mut().add_key_event(Key::ModAlt, modifiers.alt()); + imgui + .io_mut() + .add_key_event(Key::ModSuper, modifiers.logo()); + } + winit::event::WindowEvent::CursorMoved { position, .. } => { + if imgui + .io() + .config_flags + .contains(ConfigFlags::VIEWPORTS_ENABLE) + { + let window_pos = + window.inner_position().unwrap_or_default().cast::(); + imgui.io_mut().add_mouse_pos_event([ + position.x as f32 + window_pos.x, + position.y as f32 + window_pos.y, + ]); + } else { + imgui + .io_mut() + .add_mouse_pos_event([position.x as f32, position.y as f32]); + } + } + winit::event::WindowEvent::MouseWheel { + delta, + phase: TouchPhase::Moved, + .. + } => match delta { + winit::event::MouseScrollDelta::LineDelta(h, v) => { + imgui.io_mut().add_mouse_wheel_event([h, v]); + } + winit::event::MouseScrollDelta::PixelDelta(pos) => { + let h = if pos.x > 0.0 { + 1.0 + } else if pos.x < 0.0 { + -1.0 + } else { + 0.0 + }; + let v = if pos.y > 0.0 { + 1.0 + } else if pos.y < 0.0 { + -1.0 + } else { + 0.0 + }; + imgui.io_mut().add_mouse_wheel_event([h, v]); + } + }, + winit::event::WindowEvent::MouseInput { state, button, .. } => { + let state = state == ElementState::Pressed; + + if let Some(button) = to_imgui_mouse_button(button) { + imgui.io_mut().add_mouse_button_event(button, state); + } + } + _ => {} + } + } + winit::event::Event::DeviceEvent { + event: + DeviceEvent::Key(KeyboardInput { + virtual_keycode: Some(key), + state: ElementState::Released, + .. + }), + .. + } => { + if let Some(key) = to_imgui_key(key) { + imgui.io_mut().add_key_event(key, false); + } + } + _ => {} + } + } + + pub fn update_viewports( + &mut self, + imgui: &mut imgui::Context, + window_target: &EventLoopWindowTarget, + glow: &glow::Context, + ) -> Result<(), RendererError> { + loop { + let event = self.event_queue.borrow_mut().pop_front(); + let event = if let Some(event) = event { + event + } else { + break; + }; + + match event { + ViewportEvent::Create(id) => { + if let Some(viewport) = imgui.viewport_by_id_mut(id) { + let extra_window = + self.create_extra_window(viewport, window_target, glow)?; + self.extra_windows.insert(id, extra_window); + } + } + ViewportEvent::Destroy(id) => { + self.extra_windows.remove(&id); + } + ViewportEvent::SetPos(id, pos) => { + if let Some((_, _, _, wnd)) = self.extra_windows.get(&id) { + wnd.set_outer_position(PhysicalPosition::new(pos[0], pos[1])); + } + } + ViewportEvent::SetSize(id, size) => { + if let Some((_, _, _, wnd)) = self.extra_windows.get(&id) { + wnd.set_inner_size(PhysicalSize::new(size[0], size[1])); + } + } + ViewportEvent::SetVisible(id) => { + if let Some((_, _, _, wnd)) = self.extra_windows.get(&id) { + wnd.set_visible(true); + } + } + ViewportEvent::SetFocus(id) => { + if let Some((_, _, _, wnd)) = self.extra_windows.get(&id) { + wnd.focus_window(); + } + } + ViewportEvent::SetTitle(id, title) => { + if let Some((_, _, _, wnd)) = self.extra_windows.get(&id) { + wnd.set_title(&title); + } + } + } + } + + Ok(()) + } + + fn to_winit_cursor(cursor: imgui::MouseCursor) -> winit::window::CursorIcon { + match cursor { + imgui::MouseCursor::Arrow => winit::window::CursorIcon::Default, + imgui::MouseCursor::TextInput => winit::window::CursorIcon::Text, + imgui::MouseCursor::ResizeAll => winit::window::CursorIcon::Move, + imgui::MouseCursor::ResizeNS => winit::window::CursorIcon::NsResize, + imgui::MouseCursor::ResizeEW => winit::window::CursorIcon::EwResize, + imgui::MouseCursor::ResizeNESW => winit::window::CursorIcon::NeswResize, + imgui::MouseCursor::ResizeNWSE => winit::window::CursorIcon::NwseResize, + imgui::MouseCursor::Hand => winit::window::CursorIcon::Hand, + imgui::MouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed, + } + } + + pub fn prepare_render(&mut self, imgui: &mut imgui::Context, main_window: &Window) { + if let Some(cursor) = imgui.mouse_cursor() { + let cursor = Self::to_winit_cursor(cursor); + + if self.last_cursor != cursor { + main_window.set_cursor_icon(cursor); + + for (_, _, _, wnd) in self.extra_windows.values() { + wnd.set_cursor_icon(cursor); + } + + self.last_cursor = cursor; + } + } + } + + fn create_extra_window( + &mut self, + viewport: &mut imgui::Viewport, + window_target: &EventLoopWindowTarget, + glow: &glow::Context, + ) -> Result< + ( + GlObjects, + Surface, + Option, + Window, + ), + RendererError, + > { + let window_builder = WindowBuilder::new() + .with_position(PhysicalPosition::new(viewport.pos[0], viewport.pos[1])) + .with_inner_size(PhysicalSize::new(viewport.size[0], viewport.size[1])) + .with_visible(false) + .with_resizable(true) + .with_decorations(!viewport.flags.contains(ViewportFlags::NO_DECORATION)); + + let window = if let Some(glutin_config) = &self.glutin_config { + glutin_winit::finalize_window(window_target, window_builder, glutin_config) + .map_err(|_| RendererError::WindowCreationFailed)? + } else { + let template_builder = ConfigTemplateBuilder::new(); + + let (window, cfg) = DisplayBuilder::new() + .with_window_builder(Some(window_builder)) + .build(window_target, template_builder, |mut configs| { + configs.next().unwrap() + }) + .map_err(|_| RendererError::WindowCreationFailed)?; + + self.glutin_config = Some(cfg); + + window.unwrap() + }; + + let glutin_config = self.glutin_config.as_ref().unwrap(); + + let context_attribs = + ContextAttributesBuilder::new().build(Some(window.raw_window_handle())); + let context = unsafe { + glutin_config + .display() + .create_context(glutin_config, &context_attribs) + .map_err(|_| RendererError::WindowContextCreationFailed)? + }; + + let surface_attribs = SurfaceAttributesBuilder::::new().build( + window.raw_window_handle(), + NonZeroU32::new(viewport.size[0] as u32).unwrap(), + NonZeroU32::new(viewport.size[1] as u32).unwrap(), + ); + let surface = unsafe { + glutin_config + .display() + .create_window_surface(glutin_config, &surface_attribs) + .map_err(|_| RendererError::WindowSurfaceCreationFailed)? + }; + + let context = context + .make_current(&surface) + .map_err(|_| RendererError::MakeCurrentFailed)?; + + let gl_objects = + GlObjects::new(self.font_width, self.font_height, &self.font_pixels, glow)?; + + Ok(( + gl_objects, + surface, + Some(context.make_not_current().unwrap()), + window, + )) + } + + pub fn render( + &mut self, + main_window: &Window, + glow: &glow::Context, + draw_data: &imgui::DrawData, + ) -> Result<(), RendererError> { + let backup = GlStateBackup::backup(glow); + let res = Self::render_window(main_window, glow, draw_data, &self.gl_objects); + backup.restore(glow); + res + } + + pub fn render_viewports( + &mut self, + glow: &glow::Context, + imgui: &mut imgui::Context, + ) -> Result<(), RendererError> { + for (id, (gl_objects, surface, context, wnd)) in &mut self.extra_windows { + if let Some(viewport) = imgui.viewport_by_id(*id) { + let current_context = context + .take() + .unwrap() + .make_current(surface) + .map_err(|_| RendererError::MakeCurrentFailed)?; + + unsafe { + glow.disable(glow::SCISSOR_TEST); + glow.clear(glow::COLOR_BUFFER_BIT); + } + Self::render_window(wnd, glow, viewport.draw_data(), gl_objects)?; + surface + .swap_buffers(¤t_context) + .map_err(|_| RendererError::SwapBuffersFailed)?; + + *context = Some(current_context.make_not_current().unwrap()); + } + } + + Ok(()) + } + + fn render_window( + window: &Window, + glow: &glow::Context, + draw_data: &imgui::DrawData, + gl_objects: &GlObjects, + ) -> Result<(), RendererError> { + unsafe { + let window_size = window.inner_size(); + + glow.viewport(0, 0, window_size.width as i32, window_size.height as i32); + + glow.enable(glow::BLEND); + glow.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); + glow.enable(glow::SCISSOR_TEST); + + glow.bind_vertex_array(Some(gl_objects.vao)); + glow.bind_buffer(glow::ARRAY_BUFFER, Some(gl_objects.vbo)); + glow.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(gl_objects.ibo)); + glow.active_texture(glow::TEXTURE0); + glow.bind_texture(glow::TEXTURE_2D, Some(gl_objects.font_texture)); + glow.use_program(Some(gl_objects.program)); + + let left = draw_data.display_pos[0]; + let right = draw_data.display_pos[0] + draw_data.display_size[0]; + let top = draw_data.display_pos[1]; + let bottom = draw_data.display_pos[1] + draw_data.display_size[1]; + + let matrix = [ + 2.0 / (right - left), + 0.0, + 0.0, + 0.0, + 0.0, + (2.0 / (top - bottom)), + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + (right + left) / (left - right), + (top + bottom) / (bottom - top), + 0.0, + 1.0, + ]; + + let loc = glow + .get_uniform_location(gl_objects.program, "u_Matrix") + .unwrap(); + glow.uniform_matrix_4_f32_slice(Some(&loc), false, &matrix); + + for list in draw_data.draw_lists() { + glow.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + slice::from_raw_parts( + list.vtx_buffer().as_ptr().cast(), + list.vtx_buffer().len() * 20, + ), + glow::STREAM_DRAW, + ); + glow.buffer_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + slice::from_raw_parts( + list.idx_buffer().as_ptr().cast(), + list.idx_buffer().len() * 2, + ), + glow::STREAM_DRAW, + ); + + for cmd in list.commands() { + if let imgui::DrawCmd::Elements { count, cmd_params } = cmd { + let clip_x1 = (cmd_params.clip_rect[0] - draw_data.display_pos[0]) as i32; + let clip_y1 = (cmd_params.clip_rect[1] - draw_data.display_pos[1]) as i32; + let clip_x2 = (cmd_params.clip_rect[2] - draw_data.display_pos[0]) as i32; + let clip_y2 = (cmd_params.clip_rect[3] - draw_data.display_pos[1]) as i32; + + glow.scissor( + clip_x1, + window_size.height as i32 - clip_y2, + clip_x2 - clip_x1, + clip_y2 - clip_y1, + ); + glow.draw_elements_base_vertex( + glow::TRIANGLES, + count as i32, + glow::UNSIGNED_SHORT, + (cmd_params.idx_offset * 2) as i32, + cmd_params.vtx_offset as i32, + ); + } + } + } + } + + Ok(()) + } +} + +struct ViewportData { + pos: [f32; 2], + size: [f32; 2], + focus: bool, + minimized: bool, +} + +struct PlatformBackend { + event_queue: Rc>>, +} + +impl imgui::PlatformViewportBackend for PlatformBackend { + fn create_window(&mut self, viewport: &mut imgui::Viewport) { + viewport.platform_user_data = Box::into_raw(Box::new(ViewportData { + pos: viewport.pos, + size: viewport.size, + focus: false, + minimized: false, + })) + .cast(); + self.event_queue + .borrow_mut() + .push_back(ViewportEvent::Create(viewport.id)); + } + + fn destroy_window(&mut self, viewport: &mut imgui::Viewport) { + unsafe { + drop(Box::from_raw( + viewport.platform_user_data.cast::(), + )); + } + viewport.platform_user_data = null_mut(); + + self.event_queue + .borrow_mut() + .push_back(ViewportEvent::Destroy(viewport.id)); + } + + fn show_window(&mut self, viewport: &mut imgui::Viewport) { + self.event_queue + .borrow_mut() + .push_back(ViewportEvent::SetVisible(viewport.id)); + } + + fn set_window_pos(&mut self, viewport: &mut imgui::Viewport, pos: [f32; 2]) { + self.event_queue + .borrow_mut() + .push_back(ViewportEvent::SetPos(viewport.id, pos)); + } + + fn get_window_pos(&mut self, viewport: &mut imgui::Viewport) -> [f32; 2] { + unsafe { (*(viewport.platform_user_data.cast::())).pos } + } + + fn set_window_size(&mut self, viewport: &mut imgui::Viewport, size: [f32; 2]) { + self.event_queue + .borrow_mut() + .push_back(ViewportEvent::SetSize(viewport.id, size)); + } + + fn get_window_size(&mut self, viewport: &mut imgui::Viewport) -> [f32; 2] { + unsafe { (*(viewport.platform_user_data.cast::())).size } + } + + fn set_window_focus(&mut self, viewport: &mut imgui::Viewport) { + self.event_queue + .borrow_mut() + .push_back(ViewportEvent::SetFocus(viewport.id)); + } + + fn get_window_focus(&mut self, viewport: &mut imgui::Viewport) -> bool { + unsafe { (*(viewport.platform_user_data.cast::())).focus } + } + + fn get_window_minimized(&mut self, viewport: &mut imgui::Viewport) -> bool { + unsafe { (*(viewport.platform_user_data.cast::())).minimized } + } + + fn set_window_title(&mut self, viewport: &mut imgui::Viewport, title: &str) { + self.event_queue + .borrow_mut() + .push_back(ViewportEvent::SetTitle(viewport.id, title.to_owned())); + } + + fn set_window_alpha(&mut self, _viewport: &mut imgui::Viewport, _alpha: f32) {} + + fn update_window(&mut self, _viewport: &mut imgui::Viewport) {} + + fn render_window(&mut self, _viewport: &mut imgui::Viewport) {} + + fn swap_buffers(&mut self, _viewport: &mut imgui::Viewport) {} + + fn create_vk_surface( + &mut self, + _viewport: &mut imgui::Viewport, + _instance: u64, + _out_surface: &mut u64, + ) -> i32 { + 0 + } +} + +struct RendererBackend {} + +impl imgui::RendererViewportBackend for RendererBackend { + fn create_window(&mut self, _viewport: &mut imgui::Viewport) {} + + fn destroy_window(&mut self, _viewport: &mut imgui::Viewport) {} + + fn set_window_size(&mut self, _viewport: &mut imgui::Viewport, _size: [f32; 2]) {} + + fn render_window(&mut self, _viewport: &mut imgui::Viewport) {} + + fn swap_buffers(&mut self, _viewport: &mut imgui::Viewport) {} +} + +fn to_imgui_key(keycode: VirtualKeyCode) -> Option { + match keycode { + VirtualKeyCode::Tab => Some(Key::Tab), + VirtualKeyCode::Left => Some(Key::LeftArrow), + VirtualKeyCode::Right => Some(Key::RightArrow), + VirtualKeyCode::Up => Some(Key::UpArrow), + VirtualKeyCode::Down => Some(Key::DownArrow), + VirtualKeyCode::PageUp => Some(Key::PageUp), + VirtualKeyCode::PageDown => Some(Key::PageDown), + VirtualKeyCode::Home => Some(Key::Home), + VirtualKeyCode::End => Some(Key::End), + VirtualKeyCode::Insert => Some(Key::Insert), + VirtualKeyCode::Delete => Some(Key::Delete), + VirtualKeyCode::Back => Some(Key::Backspace), + VirtualKeyCode::Space => Some(Key::Space), + VirtualKeyCode::Return => Some(Key::Enter), + VirtualKeyCode::Escape => Some(Key::Escape), + VirtualKeyCode::LControl => Some(Key::LeftCtrl), + VirtualKeyCode::LShift => Some(Key::LeftShift), + VirtualKeyCode::LAlt => Some(Key::LeftAlt), + VirtualKeyCode::LWin => Some(Key::LeftSuper), + VirtualKeyCode::RControl => Some(Key::RightCtrl), + VirtualKeyCode::RShift => Some(Key::RightShift), + VirtualKeyCode::RAlt => Some(Key::RightAlt), + VirtualKeyCode::RWin => Some(Key::RightSuper), + //VirtualKeyCode::Menu => Some(Key::Menu), // TODO: find out if there is a Menu key in winit + VirtualKeyCode::Key0 => Some(Key::Alpha0), + VirtualKeyCode::Key1 => Some(Key::Alpha1), + VirtualKeyCode::Key2 => Some(Key::Alpha2), + VirtualKeyCode::Key3 => Some(Key::Alpha3), + VirtualKeyCode::Key4 => Some(Key::Alpha4), + VirtualKeyCode::Key5 => Some(Key::Alpha5), + VirtualKeyCode::Key6 => Some(Key::Alpha6), + VirtualKeyCode::Key7 => Some(Key::Alpha7), + VirtualKeyCode::Key8 => Some(Key::Alpha8), + VirtualKeyCode::Key9 => Some(Key::Alpha9), + VirtualKeyCode::A => Some(Key::A), + VirtualKeyCode::B => Some(Key::B), + VirtualKeyCode::C => Some(Key::C), + VirtualKeyCode::D => Some(Key::D), + VirtualKeyCode::E => Some(Key::E), + VirtualKeyCode::F => Some(Key::F), + VirtualKeyCode::G => Some(Key::G), + VirtualKeyCode::H => Some(Key::H), + VirtualKeyCode::I => Some(Key::I), + VirtualKeyCode::J => Some(Key::J), + VirtualKeyCode::K => Some(Key::K), + VirtualKeyCode::L => Some(Key::L), + VirtualKeyCode::M => Some(Key::M), + VirtualKeyCode::N => Some(Key::N), + VirtualKeyCode::O => Some(Key::O), + VirtualKeyCode::P => Some(Key::P), + VirtualKeyCode::Q => Some(Key::Q), + VirtualKeyCode::R => Some(Key::R), + VirtualKeyCode::S => Some(Key::S), + VirtualKeyCode::T => Some(Key::T), + VirtualKeyCode::U => Some(Key::U), + VirtualKeyCode::V => Some(Key::V), + VirtualKeyCode::W => Some(Key::W), + VirtualKeyCode::X => Some(Key::X), + VirtualKeyCode::Y => Some(Key::Y), + VirtualKeyCode::Z => Some(Key::Z), + VirtualKeyCode::F1 => Some(Key::F1), + VirtualKeyCode::F2 => Some(Key::F2), + VirtualKeyCode::F3 => Some(Key::F3), + VirtualKeyCode::F4 => Some(Key::F4), + VirtualKeyCode::F5 => Some(Key::F5), + VirtualKeyCode::F6 => Some(Key::F6), + VirtualKeyCode::F7 => Some(Key::F7), + VirtualKeyCode::F8 => Some(Key::F8), + VirtualKeyCode::F9 => Some(Key::F9), + VirtualKeyCode::F10 => Some(Key::F10), + VirtualKeyCode::F11 => Some(Key::F11), + VirtualKeyCode::F12 => Some(Key::F12), + VirtualKeyCode::Apostrophe => Some(Key::Apostrophe), + VirtualKeyCode::Comma => Some(Key::Comma), + VirtualKeyCode::Minus => Some(Key::Minus), + VirtualKeyCode::Period => Some(Key::Period), + VirtualKeyCode::Slash => Some(Key::Slash), + VirtualKeyCode::Semicolon => Some(Key::Semicolon), + VirtualKeyCode::Equals => Some(Key::Equal), + VirtualKeyCode::LBracket => Some(Key::LeftBracket), + VirtualKeyCode::Backslash => Some(Key::Backslash), + VirtualKeyCode::RBracket => Some(Key::RightBracket), + VirtualKeyCode::Grave => Some(Key::GraveAccent), + VirtualKeyCode::Capital => Some(Key::CapsLock), + VirtualKeyCode::Scroll => Some(Key::ScrollLock), + VirtualKeyCode::Numlock => Some(Key::NumLock), + VirtualKeyCode::Snapshot => Some(Key::PrintScreen), + VirtualKeyCode::Pause => Some(Key::Pause), + VirtualKeyCode::Numpad0 => Some(Key::Keypad0), + VirtualKeyCode::Numpad1 => Some(Key::Keypad1), + VirtualKeyCode::Numpad2 => Some(Key::Keypad2), + VirtualKeyCode::Numpad3 => Some(Key::Keypad3), + VirtualKeyCode::Numpad4 => Some(Key::Keypad4), + VirtualKeyCode::Numpad5 => Some(Key::Keypad5), + VirtualKeyCode::Numpad6 => Some(Key::Keypad6), + VirtualKeyCode::Numpad7 => Some(Key::Keypad7), + VirtualKeyCode::Numpad8 => Some(Key::Keypad8), + VirtualKeyCode::Numpad9 => Some(Key::Keypad9), + VirtualKeyCode::NumpadDecimal => Some(Key::KeypadDecimal), + VirtualKeyCode::NumpadDivide => Some(Key::KeypadDivide), + VirtualKeyCode::NumpadMultiply => Some(Key::KeypadMultiply), + VirtualKeyCode::NumpadSubtract => Some(Key::KeypadSubtract), + VirtualKeyCode::NumpadAdd => Some(Key::KeypadAdd), + VirtualKeyCode::NumpadEnter => Some(Key::KeypadEnter), + VirtualKeyCode::NumpadEquals => Some(Key::KeypadEqual), + _ => None, + } +} + +fn to_imgui_mouse_button(button: winit::event::MouseButton) -> Option { + match button { + winit::event::MouseButton::Left | winit::event::MouseButton::Other(0) => { + Some(imgui::MouseButton::Left) + } + winit::event::MouseButton::Right | winit::event::MouseButton::Other(1) => { + Some(imgui::MouseButton::Right) + } + winit::event::MouseButton::Middle | winit::event::MouseButton::Other(2) => { + Some(imgui::MouseButton::Middle) + } + winit::event::MouseButton::Other(3) => Some(imgui::MouseButton::Extra1), + winit::event::MouseButton::Other(4) => Some(imgui::MouseButton::Extra2), + _ => None, + } +} diff --git a/imgui-winit-glow-renderer-viewports/src/vertex_shader.glsl b/imgui-winit-glow-renderer-viewports/src/vertex_shader.glsl new file mode 100644 index 0000000..058c47b --- /dev/null +++ b/imgui-winit-glow-renderer-viewports/src/vertex_shader.glsl @@ -0,0 +1,16 @@ +#version 330 + +layout(location = 0) in vec2 in_Position; +layout(location = 1) in vec2 in_UV; +layout(location = 2) in vec4 in_Color; + +uniform mat4 u_Matrix; + +out vec2 v2f_UV; +out vec4 v2f_Color; + +void main() { + gl_Position = u_Matrix * vec4(in_Position, 0.0, 1.0); + v2f_UV = in_UV; + v2f_Color = in_Color; +} diff --git a/imgui/src/context.rs b/imgui/src/context.rs index de346a8..67bb786 100644 --- a/imgui/src/context.rs +++ b/imgui/src/context.rs @@ -60,6 +60,13 @@ pub struct Context { // imgui a mutable pointer to it. clipboard_ctx: Box>, + // we need to store an owning reference to our PlatformViewportBackend and PlatformRendererBackend, + // so that it is ensured that PlatformIo::backend_platform_user_data and PlatformIo::backend_renderer_user_data remain valid + #[cfg(feature = "docking")] + platform_viewport_ctx: Box>, + #[cfg(feature = "docking")] + renderer_viewport_ctx: Box>, + ui: Ui, } @@ -239,6 +246,14 @@ impl Context { platform_name: None, renderer_name: None, clipboard_ctx: Box::new(ClipboardContext::dummy().into()), + #[cfg(feature = "docking")] + platform_viewport_ctx: Box::new(UnsafeCell::new( + crate::PlatformViewportContext::dummy(), + )), + #[cfg(feature = "docking")] + renderer_viewport_ctx: Box::new(UnsafeCell::new( + crate::RendererViewportContext::dummy(), + )), ui: Ui { buffer: UnsafeCell::new(crate::string::UiBuffer::new(1024)), }, @@ -328,6 +343,14 @@ impl SuspendedContext { platform_name: None, renderer_name: None, clipboard_ctx: Box::new(ClipboardContext::dummy().into()), + #[cfg(feature = "docking")] + platform_viewport_ctx: Box::new(UnsafeCell::new( + crate::PlatformViewportContext::dummy(), + )), + #[cfg(feature = "docking")] + renderer_viewport_ctx: Box::new(UnsafeCell::new( + crate::RendererViewportContext::dummy(), + )), ui: Ui { buffer: UnsafeCell::new(crate::string::UiBuffer::new(1024)), }, @@ -568,3 +591,151 @@ impl Context { } } } + +#[cfg(feature = "docking")] +impl Context { + /// Returns an immutable reference to the Context's [`PlatformIo`](crate::PlatformIo) object. + pub fn platform_io(&self) -> &crate::PlatformIo { + unsafe { + // safe because PlatformIo is a transparent wrapper around sys::ImGuiPlatformIO + // and &self ensures we have shared ownership of PlatformIo. + &*(sys::igGetPlatformIO() as *const crate::PlatformIo) + } + } + /// Returns a mutable reference to the Context's [`PlatformIo`](crate::PlatformIo) object. + pub fn platform_io_mut(&mut self) -> &mut crate::PlatformIo { + unsafe { + // safe because PlatformIo is a transparent wrapper around sys::ImGuiPlatformIO + // and &mut self ensures exclusive ownership of PlatformIo. + &mut *(sys::igGetPlatformIO() as *mut crate::PlatformIo) + } + } + /// Returns an immutable reference to the main [`Viewport`](crate::Viewport) + pub fn main_viewport(&self) -> &crate::Viewport { + unsafe { + // safe because Viewport is a transparent wrapper around sys::ImGuiViewport + // and &self ensures we have shared ownership. + &*(sys::igGetMainViewport() as *mut crate::Viewport) + } + } + /// Returns a mutable reference to the main [`Viewport`](crate::Viewport) + pub fn main_viewport_mut(&mut self) -> &mut crate::Viewport { + unsafe { + // safe because Viewport is a transparent wrapper around sys::ImGuiViewport + // and &mut self ensures we have exclusive ownership. + &mut *(sys::igGetMainViewport() as *mut crate::Viewport) + } + } + /// Tries to find and return a Viewport identified by `id`. + /// + /// # Returns + /// A [`Viewport`](crate::Viewport) with the given `id` + /// or `None` if no [`Viewport`](crate::Viewport) exists. + pub fn viewport_by_id(&self, id: crate::Id) -> Option<&crate::Viewport> { + unsafe { + // safe because Viewport is a transparent wrapper around sys::ImGuiViewport + // and &self ensures shared ownership. + (sys::igFindViewportByID(id.0) as *const crate::Viewport).as_ref() + } + } + /// Tries to find and return a Viewport identified by `id`. + /// + /// # Returns + /// A [`Viewport`](crate::Viewport) with the given `id` + /// or `None` if no [`Viewport`](crate::Viewport) exists. + pub fn viewport_by_id_mut(&mut self, id: crate::Id) -> Option<&mut crate::Viewport> { + unsafe { + // safe because Viewport is a transparent wrapper around sys::ImGuiViewport + // and &mut self ensures exclusive ownership. + (sys::igFindViewportByID(id.0) as *mut crate::Viewport).as_mut() + } + } + /// Returns an iterator containing every [`Viewport`](crate::Viewport) that currently exists. + pub fn viewports(&self) -> impl Iterator { + let slice = self.platform_io().viewports.as_slice(); + // safe because &self ensures shared ownership + unsafe { slice.iter().map(|ptr| &**ptr) } + } + /// Returns an iterator containing every [`Viewport`](crate::Viewport) that currently exists. + pub fn viewports_mut(&mut self) -> impl Iterator { + let slice = self.platform_io_mut().viewports.as_slice(); + // safe because &mut self ensures exclusive ownership + unsafe { slice.iter().map(|ptr| &mut **ptr) } + } + + /// Installs a [`PlatformViewportBackend`](crate::PlatformViewportBackend) that is used to + /// create platform windows on demand if a window is dragged outside of the main viewport. + pub fn set_platform_backend(&mut self, backend: T) { + let ctx = Box::new(UnsafeCell::new(crate::PlatformViewportContext { + backend: Box::new(backend), + })); + + let io = self.io_mut(); + io.backend_platform_user_data = ctx.get() as *mut _; + + let pio = self.platform_io_mut(); + pio.platform_create_window = Some(crate::platform_io::platform_create_window); + pio.platform_destroy_window = Some(crate::platform_io::platform_destroy_window); + pio.platform_show_window = Some(crate::platform_io::platform_show_window); + pio.platform_set_window_pos = Some(crate::platform_io::platform_set_window_pos); + // since pio.platform_get_window_pos is not a C compatible function, cimgui provides an extra function to set it. + unsafe { + crate::platform_io::ImGuiPlatformIO_Set_Platform_GetWindowPos( + pio, + crate::platform_io::platform_get_window_pos, + ); + } + pio.platform_set_window_size = Some(crate::platform_io::platform_set_window_size); + // since pio.platform_get_window_size is not a C compatible function, cimgui provides an extra function to set it. + unsafe { + crate::platform_io::ImGuiPlatformIO_Set_Platform_GetWindowSize( + pio, + crate::platform_io::platform_get_window_size, + ); + } + pio.platform_set_window_focus = Some(crate::platform_io::platform_set_window_focus); + pio.platform_get_window_focus = Some(crate::platform_io::platform_get_window_focus); + pio.platform_get_window_minimized = Some(crate::platform_io::platform_get_window_minimized); + pio.platform_set_window_title = Some(crate::platform_io::platform_set_window_title); + pio.platform_set_window_alpha = Some(crate::platform_io::platform_set_window_alpha); + pio.platform_update_window = Some(crate::platform_io::platform_update_window); + pio.platform_render_window = Some(crate::platform_io::platform_render_window); + pio.platform_swap_buffers = Some(crate::platform_io::platform_swap_buffers); + pio.platform_create_vk_surface = Some(crate::platform_io::platform_create_vk_surface); + + self.platform_viewport_ctx = ctx; + } + /// Installs a [`RendererViewportBackend`](crate::RendererViewportBackend) that is used to + /// render extra viewports created by ImGui. + pub fn set_renderer_backend(&mut self, backend: T) { + let ctx = Box::new(UnsafeCell::new(crate::RendererViewportContext { + backend: Box::new(backend), + })); + + let io = self.io_mut(); + io.backend_renderer_user_data = ctx.get() as *mut _; + + let pio = self.platform_io_mut(); + pio.renderer_create_window = Some(crate::platform_io::renderer_create_window); + pio.renderer_destroy_window = Some(crate::platform_io::renderer_destroy_window); + pio.renderer_set_window_size = Some(crate::platform_io::renderer_set_window_size); + pio.renderer_render_window = Some(crate::platform_io::renderer_render_window); + pio.renderer_swap_buffers = Some(crate::platform_io::renderer_swap_buffers); + + self.renderer_viewport_ctx = ctx; + } + /// Updates the extra Viewports created by ImGui. + /// Has to be called every frame if Viewports are enabled. + pub fn update_platform_windows(&mut self) { + unsafe { + sys::igUpdatePlatformWindows(); + } + } + /// Basically just calls the [`PlatformViewportBackend`](crate::PlatformViewportBackend) and [`RendererViewportBackend`](crate::RendererViewportBackend) + /// functions. If you render your extra viewports manually this function is not needed at all. + pub fn render_platform_windows_default(&mut self) { + unsafe { + sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut()); + } + } +} diff --git a/imgui/src/dock_space.rs b/imgui/src/dock_space.rs new file mode 100644 index 0000000..ecda162 --- /dev/null +++ b/imgui/src/dock_space.rs @@ -0,0 +1,15 @@ +use std::ptr::null; + +use crate::Ui; + +impl Ui { + pub fn dockspace_over_main_viewport(&self) { + unsafe { + sys::igDockSpaceOverViewport( + sys::igGetMainViewport(), + sys::ImGuiDockNodeFlags_PassthruCentralNode as i32, + null(), + ); + } + } +} diff --git a/imgui/src/internal.rs b/imgui/src/internal.rs index bcb7e6a..d6a38a7 100644 --- a/imgui/src/internal.rs +++ b/imgui/src/internal.rs @@ -1,6 +1,6 @@ //! Internal raw utilities (don't use unless you know what you're doing!) -use std::slice; +use std::{mem::size_of, slice}; /// A generic version of the raw imgui-sys ImVector struct types #[repr(C)] @@ -15,6 +15,24 @@ impl ImVector { pub fn as_slice(&self) -> &[T] { unsafe { slice::from_raw_parts(self.data, self.size as usize) } } + + #[inline] + pub fn as_slice_mut(&mut self) -> &mut [T] { + unsafe { slice::from_raw_parts_mut(self.data, self.size as usize) } + } + + pub fn replace_from_slice(&mut self, data: &[T]) { + unsafe { + sys::igMemFree(self.data as *mut _); + + let buffer_ptr = sys::igMemAlloc(size_of::() * data.len()) as *mut T; + buffer_ptr.copy_from_nonoverlapping(data.as_ptr(), data.len()); + + self.size = data.len() as i32; + self.capacity = data.len() as i32; + self.data = buffer_ptr; + } + } } #[test] diff --git a/imgui/src/io.rs b/imgui/src/io.rs index 13611bf..ada2ecf 100644 --- a/imgui/src/io.rs +++ b/imgui/src/io.rs @@ -55,6 +55,29 @@ bitflags! { #[cfg(feature = "docking")] const DOCKING_ENABLE = sys::ImGuiConfigFlags_DockingEnable; + + #[cfg(feature = "docking")] + const VIEWPORTS_ENABLE = sys::ImGuiConfigFlags_ViewportsEnable; + } +} + +#[cfg(feature = "docking")] +bitflags! { + #[repr(transparent)] + pub struct ViewportFlags: u32 { + const IS_PLATFORM_WINDOW = sys::ImGuiViewportFlags_IsPlatformWindow; + const IS_PLATFORM_MONITOR = sys::ImGuiViewportFlags_IsPlatformMonitor; + const OWNED_BY_APP = sys::ImGuiViewportFlags_OwnedByApp; + const NO_DECORATION = sys::ImGuiViewportFlags_NoDecoration; + const NO_TASK_BAR_ICON = sys::ImGuiViewportFlags_NoTaskBarIcon; + const NO_FOCUS_ON_APPEARING = sys::ImGuiViewportFlags_NoFocusOnAppearing; + const NO_FOCUS_ON_CLICK = sys::ImGuiViewportFlags_NoFocusOnClick; + const NO_INPUTS = sys::ImGuiViewportFlags_NoInputs; + const NO_RENDERER_CLEAR = sys::ImGuiViewportFlags_NoRendererClear; + const TOP_MOST = sys::ImGuiViewportFlags_TopMost; + const MINIMIZED = sys::ImGuiViewportFlags_Minimized; + const NO_AUTO_MERGE = sys::ImGuiViewportFlags_NoAutoMerge; + const CAN_HOST_OTHER_WINDOWS = sys::ImGuiViewportFlags_CanHostOtherWindows; } } @@ -74,6 +97,13 @@ bitflags! { /// /// This enables output of large meshes (64K+ vertices) while still using 16-bits indices. const RENDERER_HAS_VTX_OFFSET = sys::ImGuiBackendFlags_RendererHasVtxOffset; + + #[cfg(feature = "docking")] + /// Set if the platform backend supports viewports. + const PLATFORM_HAS_VIEWPORTS = sys::ImGuiBackendFlags_PlatformHasViewports; + #[cfg(feature = "docking")] + /// Set if the renderer backend supports viewports. + const RENDERER_HAS_VIEWPORTS = sys::ImGuiBackendFlags_RendererHasViewports; } } @@ -234,8 +264,8 @@ pub struct Io { pub(crate) backend_platform_name: *const c_char, pub(crate) backend_renderer_name: *const c_char, - backend_platform_user_data: *mut c_void, - backend_renderer_user_data: *mut c_void, + pub(crate) backend_platform_user_data: *mut c_void, + pub(crate) backend_renderer_user_data: *mut c_void, backend_language_user_data: *mut c_void, pub(crate) get_clipboard_text_fn: Option *const c_char>, diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 7f3fa80..f250f70 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -10,6 +10,8 @@ use std::os::raw::c_char; pub use self::clipboard::*; pub use self::color::ImColor32; pub use self::context::*; +#[cfg(feature = "docking")] +pub use self::dock_space::*; pub use self::drag_drop::{DragDropFlags, DragDropSource, DragDropTarget}; pub use self::draw_list::{ChannelsSplit, DrawListMut}; pub use self::fonts::atlas::*; @@ -22,6 +24,8 @@ pub use self::input_widget::*; pub use self::io::*; pub use self::layout::*; pub use self::list_clipper::ListClipper; +#[cfg(feature = "docking")] +pub use self::platform_io::*; pub use self::plothistogram::PlotHistogram; pub use self::plotlines::PlotLines; pub use self::popups::*; @@ -62,6 +66,8 @@ mod clipboard; pub mod color; mod columns; mod context; +#[cfg(feature = "docking")] +mod dock_space; pub mod drag_drop; pub mod draw_list; mod fonts; @@ -72,6 +78,8 @@ mod io; mod layout; mod list_clipper; mod math; +#[cfg(feature = "docking")] +mod platform_io; mod plothistogram; mod plotlines; mod popups; @@ -284,7 +292,8 @@ impl Ui { /// Previously, in v0.7, this was erroneously constructed with `From` /// implementations. Now, however, it is made from the `Ui` object /// directly, with a few deprecated helper methods here. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +#[repr(transparent)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash)] pub struct Id(pub(crate) u32); impl Id { diff --git a/imgui/src/platform_io.rs b/imgui/src/platform_io.rs new file mode 100644 index 0000000..72d040a --- /dev/null +++ b/imgui/src/platform_io.rs @@ -0,0 +1,558 @@ +use std::{ + ffi::{c_void, CStr}, + os::raw::{c_char, c_int}, +}; + +use crate::{ + internal::{ImVector, RawCast}, + Io, ViewportFlags, +}; + +/// Holds the information needed to enable multiple viewports. +#[repr(C)] +pub struct PlatformIo { + pub(crate) platform_create_window: Option, + pub(crate) platform_destroy_window: Option, + pub(crate) platform_show_window: Option, + pub(crate) platform_set_window_pos: Option, + pub(crate) platform_get_window_pos: Option sys::ImVec2>, + pub(crate) platform_set_window_size: Option, + pub(crate) platform_get_window_size: Option sys::ImVec2>, + pub(crate) platform_set_window_focus: Option, + pub(crate) platform_get_window_focus: Option bool>, + pub(crate) platform_get_window_minimized: Option bool>, + pub(crate) platform_set_window_title: + Option, + pub(crate) platform_set_window_alpha: Option, + pub(crate) platform_update_window: Option, + pub(crate) platform_render_window: Option, + pub(crate) platform_swap_buffers: Option, + pub(crate) platform_get_window_dpi_scale: Option f32>, + pub(crate) platform_on_changed_viewport: Option, + pub(crate) platform_create_vk_surface: + Option c_int>, + + pub(crate) renderer_create_window: Option, + pub(crate) renderer_destroy_window: Option, + pub(crate) renderer_set_window_size: Option, + pub(crate) renderer_render_window: Option, + pub(crate) renderer_swap_buffers: Option, + + /// Holds information about the available monitors. + /// Should be initialized and updated by the [`PlatformViewportBackend`]. + pub monitors: ImVector, + + pub(crate) viewports: ImVector<*mut Viewport>, +} + +unsafe impl RawCast for PlatformIo {} + +#[test] +#[cfg(test)] +fn test_platform_io_memory_layout() { + use std::mem; + assert_eq!( + mem::size_of::(), + mem::size_of::() + ); + assert_eq!( + mem::align_of::(), + mem::align_of::() + ); + use sys::ImGuiPlatformIO; + macro_rules! assert_field_offset { + ($l:ident, $r:ident) => { + assert_eq!( + memoffset::offset_of!(PlatformIo, $l), + memoffset::offset_of!(ImGuiPlatformIO, $r) + ); + }; + } + + assert_field_offset!(platform_create_window, Platform_CreateWindow); + assert_field_offset!(platform_destroy_window, Platform_DestroyWindow); + assert_field_offset!(platform_show_window, Platform_ShowWindow); + assert_field_offset!(platform_set_window_pos, Platform_SetWindowPos); + assert_field_offset!(platform_get_window_pos, Platform_GetWindowPos); + assert_field_offset!(platform_set_window_size, Platform_SetWindowSize); + assert_field_offset!(platform_get_window_size, Platform_GetWindowSize); + assert_field_offset!(platform_set_window_focus, Platform_SetWindowFocus); + assert_field_offset!(platform_get_window_focus, Platform_GetWindowFocus); + assert_field_offset!(platform_get_window_minimized, Platform_GetWindowMinimized); + assert_field_offset!(platform_set_window_title, Platform_SetWindowTitle); + assert_field_offset!(platform_set_window_alpha, Platform_SetWindowAlpha); + assert_field_offset!(platform_update_window, Platform_UpdateWindow); + assert_field_offset!(platform_render_window, Platform_RenderWindow); + assert_field_offset!(platform_swap_buffers, Platform_SwapBuffers); + assert_field_offset!(platform_get_window_dpi_scale, Platform_GetWindowDpiScale); + assert_field_offset!(platform_on_changed_viewport, Platform_OnChangedViewport); + assert_field_offset!(platform_create_vk_surface, Platform_CreateVkSurface); + + assert_field_offset!(renderer_create_window, Renderer_CreateWindow); + assert_field_offset!(renderer_destroy_window, Renderer_DestroyWindow); + assert_field_offset!(renderer_set_window_size, Renderer_SetWindowSize); + assert_field_offset!(renderer_render_window, Renderer_RenderWindow); + assert_field_offset!(renderer_swap_buffers, Renderer_SwapBuffers); + + assert_field_offset!(monitors, Monitors); + assert_field_offset!(viewports, Viewports); +} + +/// Trait holding functions needed when the platform integration supports viewports. +/// +/// Register it via [`Context::set_platform_backend()`](crate::context::Context::set_platform_backend()) +pub trait PlatformViewportBackend: 'static { + /// Called by imgui when a new [`Viewport`] is created. + /// + /// # Notes + /// This function should initiate the creation of a platform window. + /// The window should be invisible. + fn create_window(&mut self, viewport: &mut Viewport); + /// Called by imgui when a [`Viewport`] is about to be destroyed. + /// + /// # Notes + /// This function should initiate the destruction of the platform window. + fn destroy_window(&mut self, viewport: &mut Viewport); + /// Called by imgui to make a [`Viewport`] visible. + fn show_window(&mut self, viewport: &mut Viewport); + /// Called by imgui to reposition a [`Viewport`]. + /// + /// # Notes + /// `pos` specifies the position of the windows content area (excluding title bar etc.) + fn set_window_pos(&mut self, viewport: &mut Viewport, pos: [f32; 2]); + /// Called by imgui to get the position of a [`Viewport`]. + /// + /// # Notes + /// You should return the position of the window's content area (excluding title bar etc.) + fn get_window_pos(&mut self, viewport: &mut Viewport) -> [f32; 2]; + /// Called by imgui to set the size of a [`Viewport`]. + /// + /// # Notes + /// `size` specifies the size of the window's content area (excluding title bar etc.) + fn set_window_size(&mut self, viewport: &mut Viewport, size: [f32; 2]); + /// Called by imgui to get the size of a [`Viewport`]. + /// + /// # Notes + /// you should return the size of the window's content area (excluding title bar etc.) + fn get_window_size(&mut self, viewport: &mut Viewport) -> [f32; 2]; + /// Called by imgui to make a [`Viewport`] steal the focus. + fn set_window_focus(&mut self, viewport: &mut Viewport); + /// Called by imgui to query whether a [`Viewport`] is in focus. + fn get_window_focus(&mut self, viewport: &mut Viewport) -> bool; + /// Called by imgui to query whether a [`Viewport`] is minimized. + fn get_window_minimized(&mut self, viewport: &mut Viewport) -> bool; + /// Called by imgui to set a [`Viewport`] title. + fn set_window_title(&mut self, viewport: &mut Viewport, title: &str); + /// Called by imgui to set the opacity of an entire [`Viewport`]. + /// + /// If your backend does not support opactiy, it is safe to just do nothing in this function. + fn set_window_alpha(&mut self, viewport: &mut Viewport, alpha: f32); + fn update_window(&mut self, viewport: &mut Viewport); + fn render_window(&mut self, viewport: &mut Viewport); + fn swap_buffers(&mut self, viewport: &mut Viewport); + fn create_vk_surface( + &mut self, + viewport: &mut Viewport, + instance: u64, + out_surface: &mut u64, + ) -> i32; +} + +/// Used to get the current Contexts [`PlatformViewportContext`]. +fn get_platform_ctx() -> &'static mut PlatformViewportContext { + unsafe { + // should be safe as it is impossible to call any imgui function on a non-active context. + &mut *((*(sys::igGetIO() as *const Io)).backend_platform_user_data + as *mut PlatformViewportContext) + } +} + +/// Used to get the current Contexts [`RendererViewportContext`]. +fn get_renderer_ctx() -> &'static mut RendererViewportContext { + unsafe { + // should be safe as it is impossible to call any imgui function on a non-active context. + &mut *((*(sys::igGetIO() as *const Io)).backend_renderer_user_data + as *mut RendererViewportContext) + } +} + +pub(crate) extern "C" fn platform_create_window(viewport: *mut Viewport) { + let ctx = get_platform_ctx(); + ctx.backend.create_window(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn platform_destroy_window(viewport: *mut Viewport) { + let ctx = get_platform_ctx(); + ctx.backend.destroy_window(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn platform_show_window(viewport: *mut Viewport) { + let ctx = get_platform_ctx(); + ctx.backend.show_window(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn platform_set_window_pos(viewport: *mut Viewport, pos: sys::ImVec2) { + let ctx = get_platform_ctx(); + ctx.backend + .set_window_pos(unsafe { &mut *viewport }, [pos.x, pos.y]); +} +pub(crate) extern "C" fn platform_get_window_pos( + viewport: *mut Viewport, + out_pos: *mut sys::ImVec2, +) { + let ctx = get_platform_ctx(); + let pos = ctx.backend.get_window_pos(unsafe { &mut *viewport }); + unsafe { + *out_pos = sys::ImVec2::new(pos[0], pos[1]); + } +} +pub(crate) extern "C" fn platform_set_window_size(viewport: *mut Viewport, size: sys::ImVec2) { + let ctx = get_platform_ctx(); + ctx.backend + .set_window_size(unsafe { &mut *viewport }, [size.x, size.y]); +} +pub(crate) extern "C" fn platform_get_window_size( + viewport: *mut Viewport, + out_size: *mut sys::ImVec2, +) { + let ctx = get_platform_ctx(); + let size = ctx.backend.get_window_size(unsafe { &mut *viewport }); + unsafe { + *out_size = sys::ImVec2::new(size[0], size[1]); + } +} +pub(crate) extern "C" fn platform_set_window_focus(viewport: *mut Viewport) { + let ctx = get_platform_ctx(); + ctx.backend.set_window_focus(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn platform_get_window_focus(viewport: *mut Viewport) -> bool { + let ctx = get_platform_ctx(); + ctx.backend.get_window_focus(unsafe { &mut *viewport }) +} +pub(crate) extern "C" fn platform_get_window_minimized(viewport: *mut Viewport) -> bool { + let ctx = get_platform_ctx(); + ctx.backend.get_window_minimized(unsafe { &mut *viewport }) +} +pub(crate) extern "C" fn platform_set_window_title(viewport: *mut Viewport, title: *const c_char) { + let ctx = get_platform_ctx(); + let title = unsafe { CStr::from_ptr(title).to_str().unwrap() }; + ctx.backend + .set_window_title(unsafe { &mut *viewport }, title); +} +pub(crate) extern "C" fn platform_set_window_alpha(viewport: *mut Viewport, alpha: f32) { + let ctx = get_platform_ctx(); + ctx.backend + .set_window_alpha(unsafe { &mut *viewport }, alpha); +} +pub(crate) extern "C" fn platform_update_window(viewport: *mut Viewport) { + let ctx = get_platform_ctx(); + ctx.backend.update_window(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn platform_render_window(viewport: *mut Viewport, _arg: *mut c_void) { + let ctx = get_platform_ctx(); + ctx.backend.render_window(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn platform_swap_buffers(viewport: *mut Viewport, _arg: *mut c_void) { + let ctx = get_platform_ctx(); + ctx.backend.swap_buffers(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn platform_create_vk_surface( + viewport: *mut Viewport, + instance: u64, + _arg: *const c_void, + out_surface: *mut u64, +) -> c_int { + let ctx = get_platform_ctx(); + ctx.backend + .create_vk_surface(unsafe { &mut *viewport }, instance, unsafe { + &mut *out_surface + }) +} + +/// The default [`PlatformViewportBackend`], does nothing. +pub(crate) struct DummyPlatformViewportBackend {} +impl PlatformViewportBackend for DummyPlatformViewportBackend { + fn create_window(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn destroy_window(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn show_window(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn set_window_pos(&mut self, _viewport: &mut Viewport, _pos: [f32; 2]) { + unimplemented!() + } + + fn get_window_pos(&mut self, _viewport: &mut Viewport) -> [f32; 2] { + unimplemented!() + } + + fn set_window_size(&mut self, _viewport: &mut Viewport, _size: [f32; 2]) { + unimplemented!() + } + + fn get_window_size(&mut self, _viewport: &mut Viewport) -> [f32; 2] { + unimplemented!() + } + + fn set_window_focus(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn get_window_focus(&mut self, _viewport: &mut Viewport) -> bool { + unimplemented!() + } + + fn get_window_minimized(&mut self, _viewport: &mut Viewport) -> bool { + unimplemented!() + } + + fn set_window_title(&mut self, _viewport: &mut Viewport, _title: &str) { + unimplemented!() + } + + fn set_window_alpha(&mut self, _viewport: &mut Viewport, _alpha: f32) { + unimplemented!() + } + + fn update_window(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn render_window(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn swap_buffers(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn create_vk_surface( + &mut self, + _viewport: &mut Viewport, + _instance: u64, + _out_surface: &mut u64, + ) -> i32 { + unimplemented!() + } +} + +/// Just holds a [`PlatformViewportBackend`]. +pub(crate) struct PlatformViewportContext { + pub(crate) backend: Box, +} + +impl PlatformViewportContext { + pub(crate) fn dummy() -> Self { + Self { + backend: Box::new(DummyPlatformViewportBackend {}), + } + } +} + +/// Trait that holds optional functions for a rendering backend to support multiple viewports. +/// +/// It is completely fine to not use this Backend at all, as all functions are optional. +pub trait RendererViewportBackend: 'static { + /// Called after [`PlatformViewportBackend::create_window()`]. + fn create_window(&mut self, viewport: &mut Viewport); + /// Called before [`PlatformViewportBackend::destroy_window()`]. + fn destroy_window(&mut self, viewport: &mut Viewport); + /// Called after [`PlatformViewportBackend::set_window_size()`]. + fn set_window_size(&mut self, viewport: &mut Viewport, size: [f32; 2]); + fn render_window(&mut self, viewport: &mut Viewport); + fn swap_buffers(&mut self, viewport: &mut Viewport); +} + +pub(crate) extern "C" fn renderer_create_window(viewport: *mut Viewport) { + let ctx = get_renderer_ctx(); + ctx.backend.create_window(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn renderer_destroy_window(viewport: *mut Viewport) { + let ctx = get_renderer_ctx(); + ctx.backend.destroy_window(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn renderer_set_window_size(viewport: *mut Viewport, size: sys::ImVec2) { + let ctx = get_renderer_ctx(); + ctx.backend + .set_window_size(unsafe { &mut *viewport }, [size.x, size.y]); +} +pub(crate) extern "C" fn renderer_render_window(viewport: *mut Viewport, _arg: *mut c_void) { + let ctx = get_renderer_ctx(); + ctx.backend.render_window(unsafe { &mut *viewport }); +} +pub(crate) extern "C" fn renderer_swap_buffers(viewport: *mut Viewport, _arg: *mut c_void) { + let ctx = get_renderer_ctx(); + ctx.backend.swap_buffers(unsafe { &mut *viewport }); +} + +/// The default [`RendererViewportBackend`], does nothing. +pub(crate) struct DummyRendererViewportBackend {} +impl RendererViewportBackend for DummyRendererViewportBackend { + fn create_window(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn destroy_window(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn set_window_size(&mut self, _viewport: &mut Viewport, _size: [f32; 2]) { + unimplemented!() + } + + fn render_window(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } + + fn swap_buffers(&mut self, _viewport: &mut Viewport) { + unimplemented!() + } +} + +/// Just holds a [`RendererViewportBackend`]. +pub(crate) struct RendererViewportContext { + pub(crate) backend: Box, +} + +impl RendererViewportContext { + pub(crate) fn dummy() -> Self { + Self { + backend: Box::new(DummyRendererViewportBackend {}), + } + } +} + +/// Describes an ImGui Viewport. +#[repr(C)] +pub struct Viewport { + /// The unique ID of this Viewport. + pub id: crate::Id, + /// Flags that describe how the Viewport should behave. + pub flags: ViewportFlags, + pub pos: [f32; 2], + pub size: [f32; 2], + pub work_pos: [f32; 2], + pub work_size: [f32; 2], + pub dpi_scale: f32, + pub(crate) parent_viewport_id: crate::Id, + pub(crate) draw_data: *mut crate::DrawData, + + pub renderer_user_data: *mut c_void, + pub platform_user_data: *mut c_void, + pub platform_handle: *mut c_void, + pub platform_handle_raw: *mut c_void, + pub platform_window_created: bool, + pub platform_request_move: bool, + pub platform_request_resize: bool, + pub platform_request_close: bool, +} + +impl Viewport { + /// Returns the draw data of the respective Viewport. + pub fn draw_data(&self) -> &crate::DrawData { + unsafe { &*self.draw_data } + } +} + +#[test] +#[cfg(test)] +fn test_viewport_memory_layout() { + use std::mem; + assert_eq!( + mem::size_of::(), + mem::size_of::() + ); + assert_eq!( + mem::align_of::(), + mem::align_of::() + ); + use sys::ImGuiViewport; + macro_rules! assert_field_offset { + ($l:ident, $r:ident) => { + assert_eq!( + memoffset::offset_of!(Viewport, $l), + memoffset::offset_of!(ImGuiViewport, $r) + ); + }; + } + + assert_field_offset!(id, ID); + assert_field_offset!(flags, Flags); + assert_field_offset!(pos, Pos); + assert_field_offset!(size, Size); + assert_field_offset!(work_pos, WorkPos); + assert_field_offset!(work_size, WorkSize); + assert_field_offset!(dpi_scale, DpiScale); + assert_field_offset!(parent_viewport_id, ParentViewportId); + assert_field_offset!(draw_data, DrawData); + + assert_field_offset!(renderer_user_data, RendererUserData); + assert_field_offset!(platform_user_data, PlatformUserData); + assert_field_offset!(platform_handle, PlatformHandle); + assert_field_offset!(platform_handle_raw, PlatformHandleRaw); + assert_field_offset!(platform_window_created, PlatformWindowCreated); + assert_field_offset!(platform_request_move, PlatformRequestMove); + assert_field_offset!(platform_request_resize, PlatformRequestResize); + assert_field_offset!(platform_request_close, PlatformRequestClose); +} + +/// Describes a monitor that can be used by ImGui. +#[repr(C)] +pub struct PlatformMonitor { + /// Position of the monitor on the virtual desktop. + pub main_pos: [f32; 2], + /// Size of the monitor on the virtual desktop. + pub main_size: [f32; 2], + /// Working position of the monitor, should exclude task bar etc. + /// + /// Set to `main_pos` if not known. + pub work_pos: [f32; 2], + /// Working size of the monitor, should exclude task bar etc. + /// + /// Set to `work_size` if not known. + pub work_size: [f32; 2], + pub dpi_scale: f32, +} + +#[test] +#[cfg(test)] +fn test_platform_monitor_memory_layout() { + use std::mem; + assert_eq!( + mem::size_of::(), + mem::size_of::() + ); + assert_eq!( + mem::align_of::(), + mem::align_of::() + ); + use sys::ImGuiPlatformMonitor; + macro_rules! assert_field_offset { + ($l:ident, $r:ident) => { + assert_eq!( + memoffset::offset_of!(PlatformMonitor, $l), + memoffset::offset_of!(ImGuiPlatformMonitor, $r) + ); + }; + } + + assert_field_offset!(main_pos, MainPos); + assert_field_offset!(main_size, MainSize); + assert_field_offset!(work_pos, WorkPos); + assert_field_offset!(work_size, WorkSize); + assert_field_offset!(dpi_scale, DpiScale); +} + +extern "C" { + pub(crate) fn ImGuiPlatformIO_Set_Platform_GetWindowPos( + pio: *mut PlatformIo, + func: extern "C" fn(*mut Viewport, *mut sys::ImVec2), + ); + pub(crate) fn ImGuiPlatformIO_Set_Platform_GetWindowSize( + pio: *mut PlatformIo, + func: extern "C" fn(*mut Viewport, *mut sys::ImVec2), + ); +}