diff --git a/imgui-examples/examples/support_gfx/mod.rs b/imgui-examples/examples/support_gfx/mod.rs index b7dd7fb..a1c1348 100644 --- a/imgui-examples/examples/support_gfx/mod.rs +++ b/imgui-examples/examples/support_gfx/mod.rs @@ -1,13 +1,11 @@ use imgui::{FontGlyphRange, ImFontConfig, Context, Ui}; use imgui_gfx_renderer::{Renderer, Shaders}; -use imgui_winit_support; +use imgui_winit_support::{WinitPlatform, HiDpiMode}; use std::time::Instant; #[cfg(feature = "opengl")] pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ui: F) { - use gfx::{self, Device}; - use gfx_window_glutin; - use glutin; + use gfx::{Device}; type ColorFormat = gfx::format::Rgba8; type DepthFormat = gfx::format::DepthStencil; @@ -60,10 +58,10 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ } imgui.set_ini_filename(None); - // In the examples we only use integer DPI factors, because the UI can get very blurry - // otherwise. This might or might not be what you want in a real application. - let hidpi_factor = window.get_hidpi_factor().round(); + let mut platform = WinitPlatform::init(&mut imgui); + platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded); + let hidpi_factor = platform.hidpi_factor(); let font_size = (13.0 * hidpi_factor) as f32; imgui.fonts().add_default_font_with_config( @@ -89,8 +87,6 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ let mut renderer = Renderer::init(&mut imgui, &mut factory, shaders, main_color.clone()) .expect("Failed to initialize renderer"); - imgui_winit_support::configure_keys(&mut imgui); - let mut last_frame = Instant::now(); let mut quit = false; @@ -101,12 +97,7 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ WindowEvent::{CloseRequested, Resized}, }; - imgui_winit_support::handle_event( - &mut imgui, - &event, - window.get_hidpi_factor(), - hidpi_factor, - ); + platform.handle_event(imgui.io_mut(), &window, &event); if let Event::WindowEvent { event, .. } = event { match event { @@ -123,16 +114,11 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ break; } - let now = Instant::now(); - let delta = now - last_frame; - let delta_s = delta.as_secs() as f32 + delta.subsec_nanos() as f32 / 1_000_000_000.0; - last_frame = now; + let io = imgui.io_mut(); + platform.prepare_frame(io, &window).expect("Failed to start frame"); + last_frame = io.update_delta_time(last_frame); - imgui_winit_support::update_mouse_cursor(&imgui, &window); - - let frame_size = imgui_winit_support::get_frame_size(&window, hidpi_factor).unwrap(); - - let ui = imgui.frame(frame_size, delta_s); + let ui = imgui.frame(); if !run_ui(&ui) { break; } @@ -266,6 +252,7 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ } encoder.clear(&main_color, clear_color); + platform.prepare_render(&ui, &window); renderer .render(ui, &mut factory, &mut encoder) .expect("Rendering failed"); diff --git a/imgui-gfx-renderer/src/lib.rs b/imgui-gfx-renderer/src/lib.rs index 9c112df..4f1ca5d 100644 --- a/imgui-gfx-renderer/src/lib.rs +++ b/imgui-gfx-renderer/src/lib.rs @@ -4,7 +4,7 @@ use gfx::pso::{PipelineData, PipelineState}; use gfx::texture::{FilterMethod, SamplerInfo, WrapMode}; use gfx::traits::FactoryExt; use gfx::{CommandBuffer, Encoder, Factory, IntoIndexBuffer, Rect, Resources, Slice}; -use imgui::{Context, DrawList, FrameSize, ImDrawIdx, ImDrawVert, ImTexture, Textures, Ui}; +use imgui::{Context, DrawList, ImDrawIdx, ImDrawVert, ImTexture, Textures, Ui}; pub type RendererResult = Result; @@ -261,10 +261,8 @@ impl Renderer { factory: &mut F, encoder: &mut Encoder, ) -> RendererResult<()> { - let FrameSize { - logical_size: (width, height), - hidpi_factor, - } = ui.frame_size(); + let [width, height] = ui.io().display_size; + let hidpi_factor = ui.io().display_framebuffer_scale[0]; if !(width > 0.0 && height > 0.0) { return Ok(()); diff --git a/imgui-glium-examples/examples/support/mod.rs b/imgui-glium-examples/examples/support/mod.rs index 5a81f25..e730a5e 100644 --- a/imgui-glium-examples/examples/support/mod.rs +++ b/imgui-glium-examples/examples/support/mod.rs @@ -3,7 +3,7 @@ use glium::{ Texture2d, }; use imgui::{FontGlyphRange, ImFontConfig, self, Ui}; -use imgui_winit_support; +use imgui_winit_support::{HiDpiMode, WinitPlatform}; use std::rc::Rc; use std::time::Instant; @@ -29,10 +29,10 @@ where let mut imgui = imgui::Context::create(); imgui.set_ini_filename(None); - // In the examples we only use integer DPI factors, because the UI can get very blurry - // otherwise. This might or might not be what you want in a real application. - let hidpi_factor = window.get_hidpi_factor().round(); + let mut platform = WinitPlatform::init(&mut imgui); + platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded); + let hidpi_factor = platform.hidpi_factor(); let font_size = (13.0 * hidpi_factor) as f32; imgui.fonts().add_default_font_with_config( @@ -53,12 +53,10 @@ where &FontGlyphRange::japanese(), ); - imgui.set_font_global_scale((1.0 / hidpi_factor) as f32); + imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; let mut renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer"); - imgui_winit_support::configure_keys(&mut imgui); - let mut last_frame = Instant::now(); let mut quit = false; @@ -66,12 +64,7 @@ where events_loop.poll_events(|event| { use glium::glutin::{Event, WindowEvent::CloseRequested}; - imgui_winit_support::handle_event( - &mut imgui, - &event, - window.get_hidpi_factor(), - hidpi_factor, - ); + platform.handle_event(imgui.io_mut(), &window, &event); if let Event::WindowEvent { event, .. } = event { match event { @@ -81,16 +74,12 @@ where } }); - let now = Instant::now(); - let delta = now - last_frame; - let delta_s = delta.as_secs() as f32 + delta.subsec_nanos() as f32 / 1_000_000_000.0; - last_frame = now; - - imgui_winit_support::update_mouse_cursor(&imgui, &window); - - let frame_size = imgui_winit_support::get_frame_size(&window, hidpi_factor).unwrap(); - - let ui = imgui.frame(frame_size, delta_s); + let io = imgui.io_mut(); + platform + .prepare_frame(io, &window) + .expect("Failed to start frame"); + last_frame = io.update_delta_time(last_frame); + let ui = imgui.frame(); if !run_ui(&ui, display.get_context(), renderer.textures()) { break; } @@ -102,6 +91,7 @@ where clear_color[2], clear_color[3], ); + platform.prepare_render(&ui, &window); renderer.render(&mut target, ui).expect("Rendering failed"); target.finish().unwrap(); diff --git a/imgui-glium-examples/examples/test_window_impl.rs b/imgui-glium-examples/examples/test_window_impl.rs index ed07b3a..4b85a14 100644 --- a/imgui-glium-examples/examples/test_window_impl.rs +++ b/imgui-glium-examples/examples/test_window_impl.rs @@ -1051,17 +1051,17 @@ fn show_example_app_custom_rendering(ui: &Ui, state: &mut CustomRenderingState, if state.adding_line { adding_preview = true; state.points.push(mouse_pos_in_canvas); - if !ui.imgui().is_mouse_down(ImMouseButton::Left) { + if !ui.imgui().is_mouse_down(MouseButton::Left) { state.adding_line = false; adding_preview = false; } } if ui.is_item_hovered() { - if !state.adding_line && ui.imgui().is_mouse_clicked(ImMouseButton::Left) { + if !state.adding_line && ui.imgui().is_mouse_clicked(MouseButton::Left) { state.points.push(mouse_pos_in_canvas); state.adding_line = true; } - if ui.imgui().is_mouse_clicked(ImMouseButton::Right) && !state.points.is_empty() { + if ui.imgui().is_mouse_clicked(MouseButton::Right) && !state.points.is_empty() { state.adding_line = false; adding_preview = false; state.points.pop(); diff --git a/imgui-glium-renderer/src/lib.rs b/imgui-glium-renderer/src/lib.rs index ac7fedf..6de0826 100644 --- a/imgui-glium-renderer/src/lib.rs +++ b/imgui-glium-renderer/src/lib.rs @@ -4,7 +4,7 @@ use glium::program; use glium::texture; use glium::vertex; use glium::{uniform, DrawError, IndexBuffer, Program, Surface, Texture2d, VertexBuffer}; -use imgui::{self, DrawList, FrameSize, ImTexture, Textures, Ui}; +use imgui::{self, DrawList, ImTexture, Textures, Ui}; use std::borrow::Cow; use std::fmt; use std::rc::Rc; @@ -85,10 +85,8 @@ impl Renderer { pub fn render<'a, S: Surface>(&mut self, surface: &mut S, ui: Ui<'a>) -> RendererResult<()> { let _ = self.ctx.insert_debug_marker("imgui-rs: starting rendering"); - let FrameSize { - logical_size: (width, height), - hidpi_factor, - } = ui.frame_size(); + let [width, height] = ui.io().display_size; + let hidpi_factor = ui.io().display_framebuffer_scale[0]; if !(width > 0.0 && height > 0.0) { return Ok(()); } diff --git a/imgui-sys/src/enums.rs b/imgui-sys/src/enums.rs index 1c990ef..ed12470 100644 --- a/imgui-sys/src/enums.rs +++ b/imgui-sys/src/enums.rs @@ -26,102 +26,3 @@ impl ImGuiDataType { ImGuiDataType::Double, ]; } - -/// A key identifier (ImGui-side enum) -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ImGuiKey { - Tab, - LeftArrow, - RightArrow, - UpArrow, - DownArrow, - PageUp, - PageDown, - Home, - End, - Insert, - Delete, - Backspace, - Space, - Enter, - Escape, - /// for text edit CTRL+A: select all - A, - /// for text edit CTRL+C: copy - C, - /// for text edit CTRL+V: paste - V, - /// for text edit CTRL+X: cut - X, - /// for text edit CTRL+Y: redo - Y, - /// for text edit CTRL+Z: undo - Z, -} -impl ImGuiKey { - /// All possible `ImGuiKey` variants - pub const VARIANTS: [ImGuiKey; 21] = [ - ImGuiKey::Tab, - ImGuiKey::LeftArrow, - ImGuiKey::RightArrow, - ImGuiKey::UpArrow, - ImGuiKey::DownArrow, - ImGuiKey::PageUp, - ImGuiKey::PageDown, - ImGuiKey::Home, - ImGuiKey::End, - ImGuiKey::Insert, - ImGuiKey::Delete, - ImGuiKey::Backspace, - ImGuiKey::Space, - ImGuiKey::Enter, - ImGuiKey::Escape, - ImGuiKey::A, - ImGuiKey::C, - ImGuiKey::V, - ImGuiKey::X, - ImGuiKey::Y, - ImGuiKey::Z, - ]; - pub const COUNT: usize = 21; -} - -/// A mouse cursor identifier -/// -/// User code may request binding to display given cursor, which is why we have some cursors that -/// are marked unused here -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ImGuiMouseCursor { - None = -1, - Arrow = 0, - /// When hovering over InputText, etc. - TextInput, - /// (Unused by imgui functions) - ResizeAll, - /// When hovering over an horizontal border - ResizeNS, - /// When hovering over a vertical border or a column - ResizeEW, - /// When hovering over the bottom-left corner of a window - ResizeNESW, - /// When hovering over the bottom-right corner of a window - ResizeNWSE, - /// (Unused by imgui functions. Use for e.g. hyperlinks) - Hand, -} -impl ImGuiMouseCursor { - /// All possible `ImGuiMouseCursor` variants, except None - pub const VARIANTS: [ImGuiMouseCursor; 8] = [ - // None variant intentionally skipped - ImGuiMouseCursor::Arrow, - ImGuiMouseCursor::TextInput, - ImGuiMouseCursor::ResizeAll, - ImGuiMouseCursor::ResizeNS, - ImGuiMouseCursor::ResizeEW, - ImGuiMouseCursor::ResizeNESW, - ImGuiMouseCursor::ResizeNWSE, - ImGuiMouseCursor::Hand, - ]; -} diff --git a/imgui-sys/src/lib.rs b/imgui-sys/src/lib.rs index de8d921..bda4140 100644 --- a/imgui-sys/src/lib.rs +++ b/imgui-sys/src/lib.rs @@ -62,15 +62,22 @@ pub use bindings::{ ImGuiIO_ClearInputCharacters, ImGuiInputTextCallback, ImGuiInputTextCallbackData, ImGuiInputTextCallbackData_DeleteChars, ImGuiInputTextCallbackData_HasSelection, ImGuiInputTextCallbackData_ImGuiInputTextCallbackData, ImGuiInputTextCallbackData_InsertChars, - ImGuiInputTextCallbackData_destroy, ImGuiKey_COUNT, ImGuiListClipper, ImGuiNavInput_, - ImGuiNavInput_Activate, ImGuiNavInput_COUNT, ImGuiNavInput_Cancel, ImGuiNavInput_DpadDown, - ImGuiNavInput_DpadLeft, ImGuiNavInput_DpadRight, ImGuiNavInput_DpadUp, ImGuiNavInput_FocusNext, - ImGuiNavInput_FocusPrev, ImGuiNavInput_Input, ImGuiNavInput_InternalStart_, - ImGuiNavInput_KeyDown_, ImGuiNavInput_KeyLeft_, ImGuiNavInput_KeyMenu_, - ImGuiNavInput_KeyRight_, ImGuiNavInput_KeyTab_, ImGuiNavInput_KeyUp_, ImGuiNavInput_LStickDown, - ImGuiNavInput_LStickLeft, ImGuiNavInput_LStickRight, ImGuiNavInput_LStickUp, - ImGuiNavInput_Menu, ImGuiNavInput_TweakFast, ImGuiNavInput_TweakSlow, ImGuiPayload, - ImGuiSizeCallback, ImGuiStorage, ImGuiStyle, ImGuiStyleVar, ImGuiStyleVar_, + ImGuiInputTextCallbackData_destroy, ImGuiKey, ImGuiKey_, ImGuiKey_A, ImGuiKey_Backspace, + ImGuiKey_C, ImGuiKey_COUNT, ImGuiKey_Delete, ImGuiKey_DownArrow, ImGuiKey_End, ImGuiKey_Enter, + ImGuiKey_Escape, ImGuiKey_Home, ImGuiKey_Insert, ImGuiKey_LeftArrow, ImGuiKey_PageDown, + ImGuiKey_PageUp, ImGuiKey_RightArrow, ImGuiKey_Space, ImGuiKey_Tab, ImGuiKey_UpArrow, + ImGuiKey_V, ImGuiKey_X, ImGuiKey_Y, ImGuiKey_Z, ImGuiListClipper, ImGuiMouseCursor, + ImGuiMouseCursor_, ImGuiMouseCursor_Arrow, ImGuiMouseCursor_COUNT, ImGuiMouseCursor_Hand, + ImGuiMouseCursor_None, ImGuiMouseCursor_ResizeAll, ImGuiMouseCursor_ResizeEW, + ImGuiMouseCursor_ResizeNESW, ImGuiMouseCursor_ResizeNS, ImGuiMouseCursor_ResizeNWSE, + ImGuiMouseCursor_TextInput, ImGuiNavInput_, ImGuiNavInput_Activate, ImGuiNavInput_COUNT, + ImGuiNavInput_Cancel, ImGuiNavInput_DpadDown, ImGuiNavInput_DpadLeft, ImGuiNavInput_DpadRight, + ImGuiNavInput_DpadUp, ImGuiNavInput_FocusNext, ImGuiNavInput_FocusPrev, ImGuiNavInput_Input, + ImGuiNavInput_InternalStart_, ImGuiNavInput_KeyDown_, ImGuiNavInput_KeyLeft_, + ImGuiNavInput_KeyMenu_, ImGuiNavInput_KeyRight_, ImGuiNavInput_KeyTab_, ImGuiNavInput_KeyUp_, + ImGuiNavInput_LStickDown, ImGuiNavInput_LStickLeft, ImGuiNavInput_LStickRight, + ImGuiNavInput_LStickUp, ImGuiNavInput_Menu, ImGuiNavInput_TweakFast, ImGuiNavInput_TweakSlow, + ImGuiPayload, ImGuiSizeCallback, ImGuiStorage, ImGuiStyle, ImGuiStyleVar, ImGuiStyleVar_, ImGuiStyleVar_Alpha, ImGuiStyleVar_ButtonTextAlign, ImGuiStyleVar_COUNT, ImGuiStyleVar_ChildBorderSize, ImGuiStyleVar_ChildRounding, ImGuiStyleVar_FrameBorderSize, ImGuiStyleVar_FramePadding, ImGuiStyleVar_FrameRounding, ImGuiStyleVar_GrabMinSize, diff --git a/imgui-winit-support/src/lib.rs b/imgui-winit-support/src/lib.rs index e658dec..b435377 100644 --- a/imgui-winit-support/src/lib.rs +++ b/imgui-winit-support/src/lib.rs @@ -1,259 +1,402 @@ -//! This crate provides support functions to simplify integrating imgui-rs with winit. +//! This crate provides a winit-based backend platform for imgui-rs. +//! +//! A backend platform handles window/input device events and manages their state. //! //! # Using the library //! -//! In your initialization code call `configure_keys`: +//! There are five things you need to do to use this library correctly: +//! +//! 1. Initialize a `WinitPlatform` instance +//! 2. Attach it to a winit `Window` +//! 3. Pass events to the platform (every frame) +//! 4. Call frame preparation callback (every frame) +//! 5. Call render preparation callback (every frame) +//! +//! ## Complete example (without a renderer) //! //! ```rust,no_run //! use imgui::Context; +//! use imgui_winit_support::{HiDpiMode, WinitPlatform}; +//! use std::time::Instant; +//! use winit::{Event, EventsLoop, Window, WindowEvent}; //! -//! # fn main() { -//! let mut imgui = Context::create(); -//! imgui_winit_support::configure_keys(&mut imgui); -//! # } -//! ``` +//! fn main() { +//! let mut events_loop = EventsLoop::new(); +//! let mut window = Window::new(&events_loop).unwrap(); //! -//! In your main loop you should already be retrieving events from winit and handling them. All -//! you need to do is pass each event to `imgui_winit_support` as well: +//! let mut imgui = Context::create(); +//! // configure imgui-rs Context if necessary //! -//! ```rust,no_run -//! # use imgui::Context; -//! # use winit::EventsLoop; -//! # fn main() { -//! # let mut events_loop = EventsLoop::new(); -//! # let mut imgui = Context::create(); -//! # let window_hidpi_factor = 1.0; -//! # let app_hidpi_factor = 1.0; -//! events_loop.poll_events(|event| { -//! // do application-specific stuff with event +//! let mut platform = WinitPlatform::init(&mut imgui); // step 1 +//! platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Default); // step 2 //! -//! imgui_winit_support::handle_event( -//! &mut imgui, -//! &event, -//! window_hidpi_factor, -//! app_hidpi_factor -//! ); -//! }); -//! # } -//! ``` +//! let mut last_frame = Instant::now(); +//! let mut run = true; +//! while run { +//! events_loop.poll_events(|event| { +//! platform.handle_event(imgui.io_mut(), &window, &event); // step 3 //! -//! # Advanced use cases -//! -//! In more advanced use cases you might want to handle and filter events yourself and call some of -//! the various smaller helper functions exported by the library. -//! -//! For example, you might want to customize mouse wheel line scrolling amount: -//! -//! ```rust,no_run -//! # use imgui::Context; -//! # use winit::{EventsLoop, Event, WindowEvent, MouseScrollDelta, TouchPhase}; -//! # fn main() { -//! # let mut events_loop = EventsLoop::new(); -//! # let mut imgui = Context::create(); -//! # let window_hidpi_factor = 1.0; -//! # let app_hidpi_factor = 1.0; -//! events_loop.poll_events(|event| { -//! // do application-specific stuff with event -//! -//! // default handling for events -//! imgui_winit_support::handle_event( -//! &mut imgui, -//! &event, -//! window_hidpi_factor, -//! app_hidpi_factor -//! ); -//! -//! // Scroll 10 times the pixels per line by handling LineDelta events again and -//! // overriding the mouse wheel value -//! if let Event::WindowEvent { event, .. } = event { -//! match event { -//! WindowEvent::MouseWheel { -//! delta: MouseScrollDelta::LineDelta(_, lines), -//! phase: TouchPhase::Moved, -//! .. -//! } => { -//! imgui.set_mouse_wheel(lines * 10.0); +//! // application-specific event handling +//! // for example: +//! if let Event::WindowEvent { event, .. } = event { +//! match event { +//! WindowEvent::CloseRequested => run = false, +//! _ => (), +//! } //! } -//! _ => () -//! } +//! }); +//! +//! platform.prepare_frame(imgui.io_mut(), &window) // step 4 +//! .expect("Failed to prepare frame"); +//! last_frame = imgui.io_mut().update_delta_time(last_frame); +//! let ui = imgui.frame(); +//! +//! // application-specific rendering *under the UI* +//! +//! // construct the UI +//! +//! platform.prepare_render(&ui, &window); // step 5 +//! // render the UI with a renderer +//! // renderer.render(..., ui).expect("UI rendering failed"); +//! +//! // application-specific rendering *over the UI* //! } -//! }); -//! # } +//! } //! ``` -use imgui::{Context, FrameSize, ImGuiKey, ImGuiMouseCursor}; +use imgui::{self, BackendFlags, ConfigFlags, Context, ImString, Io, Key, Ui}; +use std::cmp::Ordering; +use winit::dpi::{LogicalPosition, LogicalSize}; use winit::{ - ElementState, Event, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta, + DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseCursor, MouseScrollDelta, TouchPhase, VirtualKeyCode, Window, WindowEvent, }; -/// Configure imgui key map with winit `VirtualKeyCode` values -pub fn configure_keys(imgui: &mut Context) { - imgui.set_imgui_key(ImGuiKey::Tab, VirtualKeyCode::Tab as _); - imgui.set_imgui_key(ImGuiKey::LeftArrow, VirtualKeyCode::Left as _); - imgui.set_imgui_key(ImGuiKey::RightArrow, VirtualKeyCode::Right as _); - imgui.set_imgui_key(ImGuiKey::UpArrow, VirtualKeyCode::Up as _); - imgui.set_imgui_key(ImGuiKey::DownArrow, VirtualKeyCode::Down as _); - imgui.set_imgui_key(ImGuiKey::PageUp, VirtualKeyCode::PageUp as _); - imgui.set_imgui_key(ImGuiKey::PageDown, VirtualKeyCode::PageDown as _); - imgui.set_imgui_key(ImGuiKey::Home, VirtualKeyCode::Home as _); - imgui.set_imgui_key(ImGuiKey::End, VirtualKeyCode::End as _); - imgui.set_imgui_key(ImGuiKey::Delete, VirtualKeyCode::Delete as _); - imgui.set_imgui_key(ImGuiKey::Backspace, VirtualKeyCode::Back as _); - imgui.set_imgui_key(ImGuiKey::Enter, VirtualKeyCode::Return as _); - imgui.set_imgui_key(ImGuiKey::Escape, VirtualKeyCode::Escape as _); - imgui.set_imgui_key(ImGuiKey::A, VirtualKeyCode::A as _); - imgui.set_imgui_key(ImGuiKey::C, VirtualKeyCode::C as _); - imgui.set_imgui_key(ImGuiKey::V, VirtualKeyCode::V as _); - imgui.set_imgui_key(ImGuiKey::X, VirtualKeyCode::X as _); - imgui.set_imgui_key(ImGuiKey::Y, VirtualKeyCode::Y as _); - imgui.set_imgui_key(ImGuiKey::Z, VirtualKeyCode::Z as _); +/// winit backend platform state +#[derive(Debug)] +pub struct WinitPlatform { + hidpi_mode: ActiveHiDpiMode, + hidpi_factor: f64, } -/// Update imgui keyboard state -pub fn handle_keyboard_input(imgui: &mut Context, event: KeyboardInput) { - handle_modifiers(imgui, event.modifiers); - if let Some(key) = event.virtual_keycode { - let state_bool = event.state == ElementState::Pressed; - imgui.set_key(key as _, state_bool); - match key { - VirtualKeyCode::LShift | VirtualKeyCode::RShift => imgui.set_key_shift(state_bool), - VirtualKeyCode::LControl | VirtualKeyCode::RControl => imgui.set_key_ctrl(state_bool), - VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => imgui.set_key_alt(state_bool), - VirtualKeyCode::LWin | VirtualKeyCode::RWin => imgui.set_key_super(state_bool), +#[derive(Copy, Clone, Debug, PartialEq)] +enum ActiveHiDpiMode { + Default, + Rounded, + Locked, +} + +/// DPI factor handling mode. +/// +/// Applications that use imgui-rs might want to customize the used DPI factor and not use +/// directly the value coming from winit. +/// +/// **Note: if you use a mode other than default and the DPI factor is adjusted, winit and imgui-rs +/// will use different logical coordinates, so be careful if you pass around logical size or +/// position values.** +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum HiDpiMode { + /// The DPI factor from winit is used directly without adjustment + Default, + /// The DPI factor from winit is rounded to an integer value. + /// + /// This prevents the user interface from becoming blurry with non-integer scaling. + Rounded, + /// The DPI factor from winit is ignored, and the included value is used instead. + /// + /// This is useful if you want to force some DPI factor (e.g. 1.0) and not care about the value + /// coming from winit. + Locked(f64), +} + +impl HiDpiMode { + fn apply(&self, hidpi_factor: f64) -> (ActiveHiDpiMode, f64) { + match *self { + HiDpiMode::Default => (ActiveHiDpiMode::Default, hidpi_factor), + HiDpiMode::Rounded => (ActiveHiDpiMode::Rounded, hidpi_factor.round()), + HiDpiMode::Locked(value) => (ActiveHiDpiMode::Locked, value), + } + } +} + +impl WinitPlatform { + /// Initializes a winit platform instance and configures imgui. + /// + /// This function configures imgui-rs in the following ways: + /// + /// * backend flags are updated + /// * keys are configured + /// * platform name is set + pub fn init(imgui: &mut Context) -> WinitPlatform { + let io = imgui.io_mut(); + io.backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS); + io.backend_flags.insert(BackendFlags::HAS_SET_MOUSE_POS); + io[Key::Tab] = VirtualKeyCode::Tab as _; + io[Key::LeftArrow] = VirtualKeyCode::Left as _; + io[Key::RightArrow] = VirtualKeyCode::Right as _; + io[Key::UpArrow] = VirtualKeyCode::Up as _; + io[Key::DownArrow] = VirtualKeyCode::Down as _; + io[Key::PageUp] = VirtualKeyCode::PageUp as _; + io[Key::PageDown] = VirtualKeyCode::PageDown as _; + io[Key::Home] = VirtualKeyCode::Home as _; + io[Key::End] = VirtualKeyCode::End as _; + io[Key::Insert] = VirtualKeyCode::Insert as _; + io[Key::Delete] = VirtualKeyCode::Delete as _; + io[Key::Backspace] = VirtualKeyCode::Back as _; + io[Key::Space] = VirtualKeyCode::Space as _; + io[Key::Enter] = VirtualKeyCode::Return as _; + io[Key::Escape] = VirtualKeyCode::Escape as _; + io[Key::A] = VirtualKeyCode::A as _; + io[Key::C] = VirtualKeyCode::C as _; + io[Key::V] = VirtualKeyCode::V as _; + io[Key::X] = VirtualKeyCode::X as _; + io[Key::Y] = VirtualKeyCode::Y as _; + io[Key::Z] = VirtualKeyCode::Z as _; + imgui.set_platform_name(Some(ImString::from(format!( + "imgui-winit-support {}", + env!("CARGO_PKG_VERSION") + )))); + WinitPlatform { + hidpi_mode: ActiveHiDpiMode::Default, + hidpi_factor: 1.0, + } + } + /// Attaches the platform instance to a winit window. + /// + /// This function configures imgui-rs in the following ways: + /// + /// * framebuffer scale (= DPI factor) is set + /// * display size is set + pub fn attach_window(&mut self, io: &mut Io, window: &Window, hidpi_mode: HiDpiMode) { + let (hidpi_mode, hidpi_factor) = hidpi_mode.apply(window.get_hidpi_factor()); + self.hidpi_mode = hidpi_mode; + self.hidpi_factor = hidpi_factor; + io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32]; + if let Some(logical_size) = window.get_inner_size() { + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + } + /// Returns the current DPI factor. + /// + /// The value might not be the same as the winit DPI factor (depends on the used DPI mode) + pub fn hidpi_factor(&self) -> f64 { + self.hidpi_factor + } + /// Scales a logical size coming from winit using the current DPI mode. + /// + /// This utility function is useful if you are using a DPI mode other than default, and want + /// your application to use the same logical coordinates as imgui-rs. + pub fn scale_size_from_winit(&self, window: &Window, logical_size: LogicalSize) -> LogicalSize { + match self.hidpi_mode { + ActiveHiDpiMode::Default => logical_size, + _ => logical_size + .to_physical(window.get_hidpi_factor()) + .to_logical(self.hidpi_factor), + } + } + /// Scales a logical position coming from winit using the current DPI mode. + /// + /// This utility function is useful if you are using a DPI mode other than default, and want + /// your application to use the same logical coordinates as imgui-rs. + pub fn scale_pos_from_winit( + &self, + window: &Window, + logical_pos: LogicalPosition, + ) -> LogicalPosition { + match self.hidpi_mode { + ActiveHiDpiMode::Default => logical_pos, + _ => logical_pos + .to_physical(window.get_hidpi_factor()) + .to_logical(self.hidpi_factor), + } + } + /// Scales a logical position for winit using the current DPI mode. + /// + /// This utility function is useful if you are using a DPI mode other than default, and want + /// your application to use the same logical coordinates as imgui-rs. + pub fn scale_pos_for_winit( + &self, + window: &Window, + logical_pos: LogicalPosition, + ) -> LogicalPosition { + match self.hidpi_mode { + ActiveHiDpiMode::Default => logical_pos, + _ => logical_pos + .to_physical(self.hidpi_factor) + .to_logical(window.get_hidpi_factor()), + } + } + /// Handles a winit event. + /// + /// This function performs the following actions (depends on the event): + /// + /// * window size / dpi factor changes are applied + /// * keyboard state is updated + /// * mouse state is updated + pub fn handle_event(&mut self, io: &mut Io, window: &Window, event: &Event) { + match *event { + Event::WindowEvent { + window_id, + ref event, + } if window_id == window.id() => { + self.handle_window_event(io, window, event); + } + // Track key release events outside our window. If we don't do this, + // we might never see the release event if some other window gets focus. + Event::DeviceEvent { + event: + DeviceEvent::Key(KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + .. + }), + .. + } => { + io.keys_down[key as usize] = false; + match key { + VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = false, + VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = false, + VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = false, + VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = false, + _ => (), + } + } _ => (), } } -} - -/// Update imgui keyboard modifier state -pub fn handle_modifiers(imgui: &mut Context, modifiers: ModifiersState) { - imgui.set_key_shift(modifiers.shift); - imgui.set_key_ctrl(modifiers.ctrl); - imgui.set_key_alt(modifiers.alt); - imgui.set_key_super(modifiers.logo); -} - -/// Update imgui mouse wheel position -pub fn handle_mouse_scroll_delta( - imgui: &mut Context, - delta: MouseScrollDelta, - window_hidpi_factor: f64, - app_hidpi_factor: f64, -) { - match delta { - MouseScrollDelta::LineDelta(_, y) => imgui.set_mouse_wheel(y), - MouseScrollDelta::PixelDelta(pos) => { - let pos = pos - .to_physical(window_hidpi_factor) - .to_logical(app_hidpi_factor); - imgui.set_mouse_wheel(pos.y as f32) + fn handle_window_event(&mut self, io: &mut Io, window: &Window, event: &WindowEvent) { + match *event { + WindowEvent::Resized(logical_size) => { + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + WindowEvent::HiDpiFactorChanged(scale) => { + let hidpi_factor = match self.hidpi_mode { + ActiveHiDpiMode::Default => scale, + ActiveHiDpiMode::Rounded => scale.round(), + _ => return, + }; + // Mouse position needs to be changed while we still have both the old and the new + // values + if io.mouse_pos[0].is_finite() && io.mouse_pos[1].is_finite() { + io.mouse_pos = [ + io.mouse_pos[0] * (hidpi_factor / self.hidpi_factor) as f32, + io.mouse_pos[1] * (hidpi_factor / self.hidpi_factor) as f32, + ]; + } + self.hidpi_factor = hidpi_factor; + io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32]; + // Window size might change too if we are using DPI rounding + if let Some(logical_size) = window.get_inner_size() { + let logical_size = self.scale_size_from_winit(window, logical_size); + io.display_size = [logical_size.width as f32, logical_size.height as f32]; + } + } + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(key), + state, + .. + }, + .. + } => { + let pressed = state == ElementState::Pressed; + io.keys_down[key as usize] = pressed; + match key { + VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = pressed, + VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = pressed, + VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = pressed, + VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = pressed, + _ => (), + } + } + WindowEvent::ReceivedCharacter(ch) => io.add_input_character(ch), + WindowEvent::CursorMoved { position, .. } => { + let position = self.scale_pos_from_winit(window, position); + io.mouse_pos = [position.x as f32, position.y as f32]; + } + WindowEvent::MouseWheel { + delta, + phase: TouchPhase::Moved, + .. + } => match delta { + MouseScrollDelta::LineDelta(h, v) => { + io.mouse_wheel_h = h; + io.mouse_wheel = v; + } + MouseScrollDelta::PixelDelta(pos) => { + match pos.x.partial_cmp(&0.0) { + Some(Ordering::Greater) => io.mouse_wheel_h += 1.0, + Some(Ordering::Less) => io.mouse_wheel_h -= 1.0, + _ => (), + } + match pos.y.partial_cmp(&0.0) { + Some(Ordering::Greater) => io.mouse_wheel += 1.0, + Some(Ordering::Less) => io.mouse_wheel -= 1.0, + _ => (), + } + } + }, + WindowEvent::MouseInput { state, button, .. } => { + let pressed = state == ElementState::Pressed; + match button { + MouseButton::Left => io.mouse_down[0] = pressed, + MouseButton::Right => io.mouse_down[1] = pressed, + MouseButton::Middle => io.mouse_down[2] = pressed, + MouseButton::Other(idx @ 0...4) => io.mouse_down[idx as usize] = pressed, + _ => (), + } + } + _ => (), + } + } + /// Frame preparation callback. + /// + /// Call this before calling the imgui-rs context `frame` function. + /// This function performs the following actions: + /// + /// * mouse cursor is repositioned (if requested by imgui-rs) + pub fn prepare_frame(&self, io: &mut Io, window: &Window) -> Result<(), String> { + if io.want_set_mouse_pos { + let logical_pos = self.scale_pos_for_winit( + window, + LogicalPosition::new(f64::from(io.mouse_pos[0]), f64::from(io.mouse_pos[1])), + ); + window.set_cursor_position(logical_pos) + } else { + Ok(()) + } + } + /// Render preparation callback. + /// + /// Call this before calling the imgui-rs UI `render_with`/`render` function. + /// This function performs the following actions: + /// + /// * mouse cursor is changed and/or hidden (if requested by imgui-rs) + pub fn prepare_render(&self, ui: &Ui, window: &Window) { + let io = ui.io(); + if !io + .config_flags + .contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE) + { + match ui.mouse_cursor() { + Some(mouse_cursor) if !io.mouse_draw_cursor => { + window.hide_cursor(false); + window.set_cursor(match mouse_cursor { + imgui::MouseCursor::Arrow => MouseCursor::Arrow, + imgui::MouseCursor::TextInput => MouseCursor::Text, + imgui::MouseCursor::ResizeAll => MouseCursor::Move, + imgui::MouseCursor::ResizeNS => MouseCursor::NsResize, + imgui::MouseCursor::ResizeEW => MouseCursor::EwResize, + imgui::MouseCursor::ResizeNESW => MouseCursor::NeswResize, + imgui::MouseCursor::ResizeNWSE => MouseCursor::NwseResize, + imgui::MouseCursor::Hand => MouseCursor::Hand, + }); + } + _ => window.hide_cursor(true), + } } } } - -/// Update imgui mouse button state -pub fn handle_mouse_button_state(imgui: &mut Context, button: MouseButton, state: ElementState) { - let mut states = imgui.mouse_down(); - let state_bool = state == ElementState::Pressed; - match button { - MouseButton::Left => states[0] = state_bool, - MouseButton::Right => states[1] = state_bool, - MouseButton::Middle => states[2] = state_bool, - MouseButton::Other(idx @ 0...4) => states[idx as usize] = state_bool, - _ => (), - } - imgui.set_mouse_down(states); -} - -/// Update imgui state from winit event -pub fn handle_event( - imgui: &mut Context, - event: &Event, - window_hidpi_factor: f64, - app_hidpi_factor: f64, -) { - if let Event::WindowEvent { ref event, .. } = event { - handle_window_event(imgui, event, window_hidpi_factor, app_hidpi_factor) - } -} - -/// Update imgui state from winit window event -pub fn handle_window_event( - imgui: &mut Context, - event: &WindowEvent, - window_hidpi_factor: f64, - app_hidpi_factor: f64, -) { - use self::WindowEvent::*; - match event { - KeyboardInput { input, .. } => handle_keyboard_input(imgui, *input), - ReceivedCharacter(ch) => imgui.add_input_character(*ch), - CursorMoved { - position, - modifiers, - .. - } => { - let position = position - .to_physical(window_hidpi_factor) - .to_logical(app_hidpi_factor); - imgui.set_mouse_pos(position.x as f32, position.y as f32); - handle_modifiers(imgui, *modifiers); - } - MouseWheel { - delta, - modifiers, - phase: TouchPhase::Moved, - .. - } => { - handle_mouse_scroll_delta(imgui, *delta, window_hidpi_factor, app_hidpi_factor); - handle_modifiers(imgui, *modifiers); - } - MouseInput { - state, - button, - modifiers, - .. - } => { - handle_mouse_button_state(imgui, *button, *state); - handle_modifiers(imgui, *modifiers); - } - _ => (), - } -} - -/// Update winit window mouse cursor state -pub fn update_mouse_cursor(imgui: &Context, window: &Window) { - let mouse_cursor = imgui.mouse_cursor(); - if imgui.mouse_draw_cursor() || mouse_cursor == ImGuiMouseCursor::None { - // Hide OS cursor - window.hide_cursor(true); - } else { - // Set OS cursor - window.hide_cursor(false); - window.set_cursor(match mouse_cursor { - ImGuiMouseCursor::None => unreachable!("mouse_cursor was None!"), - ImGuiMouseCursor::Arrow => MouseCursor::Arrow, - ImGuiMouseCursor::TextInput => MouseCursor::Text, - ImGuiMouseCursor::ResizeAll => MouseCursor::Move, - ImGuiMouseCursor::ResizeNS => MouseCursor::NsResize, - ImGuiMouseCursor::ResizeEW => MouseCursor::EwResize, - ImGuiMouseCursor::ResizeNESW => MouseCursor::NeswResize, - ImGuiMouseCursor::ResizeNWSE => MouseCursor::NwseResize, - ImGuiMouseCursor::Hand => MouseCursor::Hand, - }); - } -} - -/// Get the current frame size for imgui frame rendering. -/// -/// Returns `None` if the window no longer exists -pub fn get_frame_size(window: &Window, app_hidpi_factor: f64) -> Option { - window.get_inner_size().map(|logical_size| FrameSize { - logical_size: logical_size - .to_physical(window.get_hidpi_factor()) - .to_logical(app_hidpi_factor) - .into(), - hidpi_factor: app_hidpi_factor, - }) -} diff --git a/src/context.rs b/src/context.rs index 57dee75..a89f514 100644 --- a/src/context.rs +++ b/src/context.rs @@ -367,4 +367,10 @@ impl Context { &mut *(sys::igGetStyle() as *mut Style) } } + pub fn frame<'ui, 'a: 'ui>(&'a mut self) -> Ui<'ui> { + unsafe { + sys::igNewFrame(); + } + Ui { ctx: self } + } } diff --git a/src/input/keyboard.rs b/src/input/keyboard.rs new file mode 100644 index 0000000..ee56736 --- /dev/null +++ b/src/input/keyboard.rs @@ -0,0 +1,93 @@ +use crate::sys; +use crate::Ui; + +/// A key identifier +#[repr(u32)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum Key { + Tab = sys::ImGuiKey_Tab, + LeftArrow = sys::ImGuiKey_LeftArrow, + RightArrow = sys::ImGuiKey_RightArrow, + UpArrow = sys::ImGuiKey_UpArrow, + DownArrow = sys::ImGuiKey_DownArrow, + PageUp = sys::ImGuiKey_PageUp, + PageDown = sys::ImGuiKey_PageDown, + Home = sys::ImGuiKey_Home, + End = sys::ImGuiKey_End, + Insert = sys::ImGuiKey_Insert, + Delete = sys::ImGuiKey_Delete, + Backspace = sys::ImGuiKey_Backspace, + Space = sys::ImGuiKey_Space, + Enter = sys::ImGuiKey_Enter, + Escape = sys::ImGuiKey_Escape, + A = sys::ImGuiKey_A, + C = sys::ImGuiKey_C, + V = sys::ImGuiKey_V, + X = sys::ImGuiKey_X, + Y = sys::ImGuiKey_Y, + Z = sys::ImGuiKey_Z, +} + +impl Key { + /// All possible `Key` variants + pub const VARIANTS: [Key; Key::COUNT] = [ + Key::Tab, + Key::LeftArrow, + Key::RightArrow, + Key::UpArrow, + Key::DownArrow, + Key::PageUp, + Key::PageDown, + Key::Home, + Key::End, + Key::Insert, + Key::Delete, + Key::Backspace, + Key::Space, + Key::Enter, + Key::Escape, + Key::A, + Key::C, + Key::V, + Key::X, + Key::Y, + Key::Z, + ]; + /// Total count of `Key` variants + pub const COUNT: usize = sys::ImGuiKey_COUNT as usize; +} + +#[test] +fn test_key_variants() { + for (idx, &value) in Key::VARIANTS.iter().enumerate() { + assert_eq!(idx, value as usize); + } +} + +impl<'ui> Ui<'ui> { + /// Returns the key index of the given key identifier. + /// + /// Equivalent to indexing the Io struct `key_map` field: `ui.io().key_map[key]` + pub fn key_index(&self, key: Key) -> u32 { + unsafe { sys::igGetKeyIndex(key as i32) as u32 } + } + /// Returns true if the key is being held. + /// + /// Equivalent to indexing the Io struct `keys_down` field: `ui.io().keys_down[key_index]` + pub fn is_key_down(&self, key_index: u32) -> bool { + unsafe { sys::igIsKeyDown(key_index as i32) } + } + /// Returns true if the key was pressed (went from !down to down). + /// + /// Affected by key repeat settings (`io.key_repeat_delay`, `io.key_repeat_rate`) + pub fn is_key_pressed(&self, key_index: u32) -> bool { + unsafe { sys::igIsKeyPressed(key_index as i32, true) } + } + /// Returns a count of key presses using the given repeat rate/delay settings. + /// + /// Usually returns 0 or 1, but might be >1 if `rate` is small enough that `io.delta_time` > + /// `rate`. + pub fn key_pressed_amount(&self, key_index: u32, repeat_delay: f32, rate: f32) -> u32 { + unsafe { sys::igGetKeyPressedAmount(key_index as i32, repeat_delay, rate) as u32 } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 0000000..73cdf76 --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,2 @@ +pub mod keyboard; +pub mod mouse; diff --git a/src/input/mouse.rs b/src/input/mouse.rs new file mode 100644 index 0000000..5fa45ee --- /dev/null +++ b/src/input/mouse.rs @@ -0,0 +1,172 @@ +use crate::sys; +use crate::Ui; + +/// Represents one of the supported mouse buttons +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum MouseButton { + Left = 0, + Right = 1, + Middle = 2, + Extra1 = 3, + Extra2 = 4, +} + +impl MouseButton { + /// All possible `MouseButton` varirants + pub const VARIANTS: [MouseButton; MouseButton::COUNT] = [ + MouseButton::Left, + MouseButton::Right, + MouseButton::Middle, + MouseButton::Extra1, + MouseButton::Extra2, + ]; + /// Total count of `MouseButton` variants + pub const COUNT: usize = 5; +} + +#[test] +fn test_mouse_button_variants() { + for (idx, &value) in MouseButton::VARIANTS.iter().enumerate() { + assert_eq!(idx, value as usize); + } +} + +/// Mouse cursor type identifier +#[repr(i32)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum MouseCursor { + Arrow = sys::ImGuiMouseCursor_Arrow, + /// Automatically used when hovering over text inputs, etc. + TextInput = sys::ImGuiMouseCursor_TextInput, + /// Not used automatically + ResizeAll = sys::ImGuiMouseCursor_ResizeAll, + /// Automatically used when hovering over a horizontal border + ResizeNS = sys::ImGuiMouseCursor_ResizeNS, + /// Automatically used when hovering over a vertical border or a column + ResizeEW = sys::ImGuiMouseCursor_ResizeEW, + /// Automatically used when hovering over the bottom-left corner of a window + ResizeNESW = sys::ImGuiMouseCursor_ResizeNESW, + /// Automatically used when hovering over the bottom-right corner of a window + ResizeNWSE = sys::ImGuiMouseCursor_ResizeNWSE, + /// Not used automatically, use for e.g. hyperlinks + Hand = sys::ImGuiMouseCursor_Hand, +} + +impl MouseCursor { + /// All possible `MouseCursor` varirants + pub const VARIANTS: [MouseCursor; MouseCursor::COUNT] = [ + MouseCursor::Arrow, + MouseCursor::TextInput, + MouseCursor::ResizeAll, + MouseCursor::ResizeNS, + MouseCursor::ResizeEW, + MouseCursor::ResizeNESW, + MouseCursor::ResizeNWSE, + MouseCursor::Hand, + ]; + /// Total count of `MouseCursor` variants + pub const COUNT: usize = sys::ImGuiMouseCursor_COUNT as usize; +} + +#[test] +fn test_mouse_cursor_variants() { + for (idx, &value) in MouseCursor::VARIANTS.iter().enumerate() { + assert_eq!(idx, value as usize); + } +} + +impl<'ui> Ui<'ui> { + /// Returns true if the given mouse button is held down. + /// + /// Equivalent to indexing the Io struct with the button, e.g. `ui.io()[button]`. + pub fn is_mouse_down(&self, button: MouseButton) -> bool { + unsafe { sys::igIsMouseDown(button as i32) } + } + /// Returns true if any mouse button is held down + pub fn is_any_mouse_down(&self) -> bool { + unsafe { sys::igIsAnyMouseDown() } + } + /// Returns true if the given mouse button was clicked (went from !down to down) + pub fn is_mouse_clicked(&self, button: MouseButton) -> bool { + unsafe { sys::igIsMouseClicked(button as i32, false) } + } + /// Returns true if the given mouse button was double-clicked + pub fn is_mouse_double_clicked(&self, button: MouseButton) -> bool { + unsafe { sys::igIsMouseDoubleClicked(button as i32) } + } + /// Returns true if the given mouse button was released (went from down to !down) + pub fn is_mouse_released(&self, button: MouseButton) -> bool { + unsafe { sys::igIsMouseReleased(button as i32) } + } + /// Returns true if the mouse is currently dragging with the given mouse button held down + pub fn is_mouse_dragging(&self, button: MouseButton) -> bool { + unsafe { sys::igIsMouseDragging(button as i32, -1.0) } + } + /// Returns true if the mouse is currently dragging with the given mouse button held down. + /// + /// If the given threshold is invalid or negative, the global distance threshold is used + /// (`io.mouse_drag_threshold`). + pub fn is_mouse_dragging_with_threshold(&self, button: MouseButton, threshold: f32) -> bool { + unsafe { sys::igIsMouseDragging(button as i32, threshold) } + } + /// Returns true if the mouse is hovering over the given bounding rect. + /// + /// Clipped by current clipping settings, but disregards other factors like focus, window + /// ordering, modal popup blocking. + pub fn is_mouse_hovering_rect(r_min: [f32; 2], r_max: [f32; 2]) -> bool { + unsafe { sys::igIsMouseHoveringRect(r_min.into(), r_max.into(), true) } + } + /// Returns the mouse position backed up at the time of opening a popup + pub fn mouse_pos_on_opening_current_popup(&self) -> [f32; 2] { + unsafe { sys::igGetMousePosOnOpeningCurrentPopup_nonUDT2().into() } + } + /// Returns the delta from the initial clicking position. + /// + /// This is locked and returns [0.0, 0.0] until the mouse has moved past the global distance + /// threshold (`io.mouse_drag_threshold`). + pub fn mouse_drag_delta(&self, button: MouseButton) -> [f32; 2] { + unsafe { sys::igGetMouseDragDelta_nonUDT2(button as i32, -1.0).into() } + } + /// Returns the delta from the initial clicking position. + /// + /// This is locked and returns [0.0, 0.0] until the mouse has moved past the given threshold. + /// If the given threshold is invalid or negative, the global distance threshold is used + /// (`io.mouse_drag_threshold`). + pub fn mouse_drag_delta_with_threshold(&self, button: MouseButton, threshold: f32) -> [f32; 2] { + unsafe { sys::igGetMouseDragDelta_nonUDT2(button as i32, threshold).into() } + } + /// Resets the current delta from initial clicking position. + pub fn reset_mouse_drag_delta(&self, button: MouseButton) { + // This mutates the Io struct, but targets an internal field so there can't be any + // references to it + unsafe { sys::igResetMouseDragDelta(button as i32) } + } + /// Get the currently desired mouse cursor type. + /// + /// Returns `None` if no cursor should be displayed + pub fn mouse_cursor(&self) -> Option { + match unsafe { sys::igGetMouseCursor() } { + sys::ImGuiMouseCursor_Arrow => Some(MouseCursor::Arrow), + sys::ImGuiMouseCursor_TextInput => Some(MouseCursor::TextInput), + sys::ImGuiMouseCursor_ResizeAll => Some(MouseCursor::ResizeAll), + sys::ImGuiMouseCursor_ResizeNS => Some(MouseCursor::ResizeNS), + sys::ImGuiMouseCursor_ResizeEW => Some(MouseCursor::ResizeEW), + sys::ImGuiMouseCursor_ResizeNESW => Some(MouseCursor::ResizeNESW), + sys::ImGuiMouseCursor_ResizeNWSE => Some(MouseCursor::ResizeNWSE), + sys::ImGuiMouseCursor_Hand => Some(MouseCursor::Hand), + _ => None, + } + } + /// Set the desired mouse cursor type. + /// + /// Passing `None` hides the mouse cursor. + pub fn set_mouse_cursor(&self, cursor_type: Option) { + unsafe { + sys::igSetMouseCursor( + cursor_type + .map(|x| x as i32) + .unwrap_or(sys::ImGuiMouseCursor_None), + ); + } + } +} diff --git a/src/input.rs b/src/input_widget.rs similarity index 100% rename from src/input.rs rename to src/input_widget.rs diff --git a/src/io.rs b/src/io.rs index c1cf6ae..eb7fd47 100644 --- a/src/io.rs +++ b/src/io.rs @@ -4,6 +4,8 @@ use std::ops::{Index, IndexMut}; use std::os::raw::{c_char, c_int, c_void}; use std::time::Instant; +use crate::input::keyboard::Key; +use crate::input::mouse::MouseButton; use crate::internal::{ImVector, RawCast}; use crate::sys; @@ -334,6 +336,45 @@ impl Io { } } +impl Index for Io { + type Output = u32; + fn index(&self, index: Key) -> &u32 { + &self.key_map[index as usize] + } +} + +impl IndexMut for Io { + fn index_mut(&mut self, index: Key) -> &mut u32 { + &mut self.key_map[index as usize] + } +} + +impl Index for Io { + type Output = f32; + fn index(&self, index: NavInput) -> &f32 { + &self.nav_inputs[index as usize] + } +} + +impl IndexMut for Io { + fn index_mut(&mut self, index: NavInput) -> &mut f32 { + &mut self.nav_inputs[index as usize] + } +} + +impl Index for Io { + type Output = bool; + fn index(&self, index: MouseButton) -> &bool { + &self.mouse_down[index as usize] + } +} + +impl IndexMut for Io { + fn index_mut(&mut self, index: MouseButton) -> &mut bool { + &mut self.mouse_down[index as usize] + } +} + #[test] fn test_io_memory_layout() { use std::mem; diff --git a/src/lib.rs b/src/lib.rs index 9c8d004..b62399d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,9 @@ pub use self::drag::{ }; pub use self::fonts::{FontGlyphRange, ImFont, ImFontAtlas, ImFontConfig}; pub use self::image::{ImTexture, Image, ImageButton, Textures}; -pub use self::input::{ +pub use self::input::keyboard::*; +pub use self::input::mouse::*; +pub use self::input_widget::{ InputFloat, InputFloat2, InputFloat3, InputFloat4, InputInt, InputInt2, InputInt3, InputInt4, InputText, InputTextMultiline, }; @@ -40,8 +42,8 @@ pub use self::string::{ImStr, ImString}; pub use self::style::*; pub use self::sys::{ ImDrawIdx, ImDrawVert, ImGuiColorEditFlags, ImGuiFocusedFlags, ImGuiHoveredFlags, - ImGuiInputTextFlags, ImGuiKey, ImGuiMouseCursor, ImGuiSelectableFlags, ImGuiTreeNodeFlags, - ImGuiWindowFlags, ImVec2, ImVec4, + ImGuiInputTextFlags, ImGuiSelectableFlags, ImGuiTreeNodeFlags, ImGuiWindowFlags, ImVec2, + ImVec4, }; pub use self::trees::{CollapsingHeader, TreeNode}; pub use self::window::Window; @@ -55,6 +57,7 @@ mod drag; mod fonts; mod image; mod input; +mod input_widget; mod internal; mod io; mod menus; @@ -84,31 +87,6 @@ pub fn get_version() -> &'static str { } } -/// Represents one of the buttons of the mouse -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum ImMouseButton { - Left = 0, - Right = 1, - Middle = 2, - Extra1 = 3, - Extra2 = 4, -} - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct FrameSize { - pub logical_size: (f64, f64), - pub hidpi_factor: f64, -} - -impl FrameSize { - pub fn new(width: f64, height: f64, hidpi_factor: f64) -> FrameSize { - FrameSize { - logical_size: (width, height), - hidpi_factor, - } - } -} - impl Context { pub fn fonts(&mut self) -> ImFontAtlas { unsafe { ImFontAtlas::from_ptr(self.io_mut().fonts) } @@ -174,7 +152,10 @@ impl Context { } pub fn display_framebuffer_scale(&self) -> (f32, f32) { let io = self.io(); - (io.display_framebuffer_scale[0], io.display_framebuffer_scale[1]) + ( + io.display_framebuffer_scale[0], + io.display_framebuffer_scale[1], + ) } pub fn mouse_pos(&self) -> (f32, f32) { let io = self.io(); @@ -206,7 +187,7 @@ impl Context { let io = self.io(); io.mouse_wheel } - pub fn mouse_drag_delta(&self, button: ImMouseButton) -> (f32, f32) { + pub fn mouse_drag_delta(&self, button: MouseButton) -> (f32, f32) { let delta = unsafe { sys::igGetMouseDragDelta_nonUDT2(button as c_int, -1.0) }; delta.into() } @@ -220,38 +201,25 @@ impl Context { let io = self.io(); io.mouse_draw_cursor } - /// Set currently displayed cursor. - /// Requires support in the windowing back-end if OS cursor is used. - /// OS cursor is used if `mouse_draw_cursor` is set to `false` with - /// [set_mouse_draw_cursor](#method.set_mouse_draw_cursor). - pub fn set_mouse_cursor(&self, cursor: ImGuiMouseCursor) { - unsafe { - sys::igSetMouseCursor(cursor); - } - } - /// Get currently displayed cursor. - pub fn mouse_cursor(&self) -> ImGuiMouseCursor { - unsafe { sys::igGetMouseCursor() } - } /// Returns `true` if mouse is currently dragging with the `button` provided /// as argument. - pub fn is_mouse_dragging(&self, button: ImMouseButton) -> bool { + pub fn is_mouse_dragging(&self, button: MouseButton) -> bool { unsafe { sys::igIsMouseDragging(button as c_int, -1.0) } } /// Returns `true` if the `button` provided as argument is currently down. - pub fn is_mouse_down(&self, button: ImMouseButton) -> bool { + pub fn is_mouse_down(&self, button: MouseButton) -> bool { unsafe { sys::igIsMouseDown(button as c_int) } } /// Returns `true` if the `button` provided as argument is being clicked. - pub fn is_mouse_clicked(&self, button: ImMouseButton) -> bool { + pub fn is_mouse_clicked(&self, button: MouseButton) -> bool { unsafe { sys::igIsMouseClicked(button as c_int, false) } } /// Returns `true` if the `button` provided as argument is being double-clicked. - pub fn is_mouse_double_clicked(&self, button: ImMouseButton) -> bool { + pub fn is_mouse_double_clicked(&self, button: MouseButton) -> bool { unsafe { sys::igIsMouseDoubleClicked(button as c_int) } } /// Returns `true` if the `button` provided as argument was released - pub fn is_mouse_released(&self, button: ImMouseButton) -> bool { + pub fn is_mouse_released(&self, button: MouseButton) -> bool { unsafe { sys::igIsMouseReleased(button as c_int) } } pub fn key_ctrl(&self) -> bool { @@ -290,23 +258,23 @@ impl Context { let io = self.io_mut(); io.keys_down[key as usize] = pressed; } - pub fn set_imgui_key(&mut self, key: ImGuiKey, mapping: u8) { + pub fn set_imgui_key(&mut self, key: Key, mapping: u8) { let io = self.io_mut(); io.key_map[key as usize] = u32::from(mapping); } - /// Map [`ImGuiKey`] values into user's key index - pub fn get_key_index(&self, key: ImGuiKey) -> usize { - unsafe { sys::igGetKeyIndex(key) as usize } + /// Map [`Key`] values into user's key index + pub fn get_key_index(&self, key: Key) -> usize { + unsafe { sys::igGetKeyIndex(key as i32) as usize } } /// Return whether specific key is being held /// /// # Example /// /// ```rust - /// use imgui::{ImGuiKey, Ui}; + /// use imgui::{Key, Ui}; /// /// fn test(ui: &Ui) { - /// let delete_key_index = ui.imgui().get_key_index(ImGuiKey::Delete); + /// let delete_key_index = ui.imgui().get_key_index(Key::Delete); /// if ui.imgui().is_key_down(delete_key_index) { /// println!("Delete is being held!"); /// } @@ -339,23 +307,6 @@ impl Context { pub fn get_frame_rate(&self) -> f32 { self.io().framerate } - pub fn frame<'ui, 'a: 'ui>(&'a mut self, frame_size: FrameSize, delta_time: f32) -> Ui<'ui> { - { - let io = self.io_mut(); - io.display_size[0] = frame_size.logical_size.0 as c_float; - io.display_size[1] = frame_size.logical_size.1 as c_float; - io.display_framebuffer_scale[0] = frame_size.hidpi_factor as c_float; - io.display_framebuffer_scale[1] = frame_size.hidpi_factor as c_float; - io.delta_time = delta_time; - } - unsafe { - sys::igNewFrame(); - } - Ui { - imgui: self, - frame_size, - } - } } pub struct DrawData<'a> { @@ -435,8 +386,7 @@ pub struct DrawList<'a> { } pub struct Ui<'ui> { - imgui: &'ui Context, - frame_size: FrameSize, + ctx: &'ui Context, } static FMT: &'static [u8] = b"%s\0"; @@ -446,18 +396,18 @@ fn fmt_ptr() -> *const c_char { } impl<'ui> Ui<'ui> { - pub fn frame_size(&self) -> FrameSize { - self.frame_size + pub fn io(&self) -> &Io { + unsafe { &*(sys::igGetIO() as *const Io) } } pub fn imgui(&self) -> &Context { - self.imgui + self.ctx } pub fn want_capture_mouse(&self) -> bool { - let io = self.imgui.io(); + let io = self.io(); io.want_capture_mouse } pub fn want_capture_keyboard(&self) -> bool { - let io = self.imgui.io(); + let io = self.io(); io.want_capture_keyboard } pub fn set_keyboard_focus_here(&self, offset: i32) { @@ -466,19 +416,19 @@ impl<'ui> Ui<'ui> { } } pub fn framerate(&self) -> f32 { - let io = self.imgui.io(); + let io = self.io(); io.framerate } pub fn metrics_render_vertices(&self) -> i32 { - let io = self.imgui.io(); + let io = self.io(); io.metrics_render_vertices } pub fn metrics_render_indices(&self) -> i32 { - let io = self.imgui.io(); + let io = self.io(); io.metrics_render_indices } pub fn metrics_active_windows(&self) -> i32 { - let io = self.imgui.io(); + let io = self.io(); io.metrics_active_windows } pub fn render(self, f: F) -> Result<(), E> @@ -1157,7 +1107,7 @@ impl<'ui> Ui<'ui> { /// ```rust,no_run /// # use imgui::*; /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// # let ui = imgui.frame(); /// if ui.button(im_str!("Show modal"), (0.0, 0.0)) { /// ui.open_popup(im_str!("modal")); /// } @@ -1237,7 +1187,7 @@ impl<'ui> Ui<'ui> { /// ```rust,no_run /// # use imgui::*; /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// # let ui = imgui.frame(); /// # let mut selected_radio_value = 2; /// ui.radio_button(im_str!("Item 1"), &mut selected_radio_value, 1); /// ui.radio_button(im_str!("Item 2"), &mut selected_radio_value, 2); @@ -1254,7 +1204,7 @@ impl<'ui> Ui<'ui> { /// ```rust,no_run /// # use imgui::*; /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// # let ui = imgui.frame(); /// # let mut radio_button_test = "cats".to_string(); /// if ui.radio_button_bool(im_str!("Cats"), radio_button_test == "cats") { /// radio_button_test = "cats".to_string(); @@ -1347,7 +1297,7 @@ impl<'ui> Ui<'ui> { /// ```rust,no_run /// # use imgui::*; /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// # let ui = imgui.frame(); /// ui.progress_bar(0.6) /// .size((100.0, 12.0)) /// .overlay_text(im_str!("Progress!")) @@ -1365,7 +1315,7 @@ impl<'ui> Ui<'ui> { /// ```rust,no_run /// # use imgui::*; /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// # let ui = imgui.frame(); /// ui.window(im_str!("ChatWindow")) /// .title_bar(true) /// .scrollable(false) @@ -1395,7 +1345,7 @@ impl<'ui> Ui<'ui> { /// ```rust,no_run /// # use imgui::*; /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// # let ui = imgui.frame(); /// ui.with_style_var(StyleVar::Alpha(0.2), || { /// ui.text(im_str!("AB")); /// }); @@ -1413,7 +1363,7 @@ impl<'ui> Ui<'ui> { /// ```rust,no_run /// # use imgui::*; /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// # let ui = imgui.frame(); /// # let styles = [StyleVar::Alpha(0.2), StyleVar::WindowPadding([1.0, 1.0])]; /// ui.with_style_vars(&styles, || { /// ui.text(im_str!("A")); @@ -1513,7 +1463,7 @@ impl<'ui> Ui<'ui> { /// ```rust,no_run /// # use imgui::*; /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// # let ui = imgui.frame(); /// ui.with_color_var(StyleColor::Text, [1.0, 0.0, 0.0, 1.0], || { /// ui.text_wrapped(im_str!("AB")); /// }); @@ -1539,7 +1489,7 @@ impl<'ui> Ui<'ui> { /// ```rust,no_run /// # use imgui::*; /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// # let ui = imgui.frame(); /// let red = [1.0, 0.0, 0.0, 1.0]; /// let green = [0.0, 1.0, 0.0, 1.0]; /// # let vars = [(StyleColor::Text, red), (StyleColor::TextDisabled, green)];