diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2924bbc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "imgui-winit-support" +version = "0.12.0" +edition = "2021" +description = "winit support code for the imgui crate" +homepage = "https://github.com/imgui-rs/imgui-rs" +repository = "https://github.com/imgui-rs/imgui-rs" +documentation = "https://docs.rs/imgui-winit-support" +license = "MIT OR Apache-2.0" +categories = ["gui"] + +[dependencies] +imgui = "0.12.0" +winit = { version = "0.29.3", default-features = false } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..3b7a7f9 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 the imgui-rs developers + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..230b8c4 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021 The imgui-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f3c082 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# imgui-winit-support + +See the examples for [`imgui-glium-renderer`][glium] or [`imgui-glow-renderer`][glow] +for simple examples of this platform backend with different renderers + +[glium]: ../../imgui-glium-renderer/examples +[glow]: ../../imgui-glow-renderer/examples diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..82b9a95 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,5 @@ +See the examples for [`imgui-glium-renderer`][glium] or [`imgui-glow-renderer`][glow] +for simple examples of this platform backend with different renderers + +[glium]: ../../imgui-glium-renderer/examples +[glow]: ../../imgui-glow-renderer/examples diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ddc1c8e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,576 @@ +//! 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 +//! +//! 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) +//! +//! ```no_run +//! use imgui::Context; +//! use imgui_winit_support::{HiDpiMode, WinitPlatform}; +//! use std::time::Instant; +//! use winit::event::{Event, WindowEvent}; +//! use winit::event_loop::{ControlFlow, EventLoop}; +//! use winit::window::Window; +//! +//! let mut event_loop = EventLoop::new().expect("Failed to create EventLoop"); +//! let mut window = Window::new(&event_loop).unwrap(); +//! +//! let mut imgui = Context::create(); +//! // configure imgui-rs Context if necessary +//! +//! let mut platform = WinitPlatform::init(&mut imgui); // step 1 +//! platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Default); // step 2 +//! +//! let mut last_frame = Instant::now(); +//! let mut run = true; +//! event_loop.run(move |event, window_target| { +//! match event { +//! Event::NewEvents(_) => { +//! // other application-specific logic +//! let now = Instant::now(); +//! imgui.io_mut().update_delta_time(now - last_frame); +//! last_frame = now; +//! }, +//! Event::AboutToWait => { +//! // other application-specific logic +//! platform.prepare_frame(imgui.io_mut(), &window) // step 4 +//! .expect("Failed to prepare frame"); +//! window.request_redraw(); +//! } +//! Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { +//! 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 +//! let draw_data = imgui.render(); +//! // renderer.render(..., draw_data).expect("UI rendering failed"); +//! +//! // application-specific rendering *over the UI* +//! }, +//! Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { +//! window_target.exit(); +//! } +//! // other application-specific event handling +//! event => { +//! platform.handle_event(imgui.io_mut(), &window, &event); // step 3 +//! // other application-specific event handling +//! } +//! } +//! }).expect("EventLoop error"); +//! ``` + +use imgui::{self, BackendFlags, ConfigFlags, Context, Io, Key, Ui}; +use std::cmp::Ordering; + +// Re-export winit to make it easier for users to use the correct version. +pub use winit; +use winit::{ + dpi::{LogicalPosition, LogicalSize}, + keyboard::{Key as WinitKey, KeyLocation, NamedKey}, + platform::modifier_supplement::KeyEventExtModifierSupplement, +}; + +use winit::{ + error::ExternalError, + event::{ElementState, Event, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}, + window::{CursorIcon as MouseCursor, Window}, +}; + +/// winit backend platform state +#[derive(Debug)] +pub struct WinitPlatform { + hidpi_mode: ActiveHiDpiMode, + hidpi_factor: f64, + cursor_cache: Option, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +struct CursorSettings { + cursor: Option, + draw_cursor: bool, +} + +fn to_winit_cursor(cursor: imgui::MouseCursor) -> MouseCursor { + match cursor { + imgui::MouseCursor::Arrow => MouseCursor::Default, + 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::Grab, + imgui::MouseCursor::NotAllowed => MouseCursor::NotAllowed, + } +} + +impl CursorSettings { + fn apply(&self, window: &Window) { + match self.cursor { + Some(mouse_cursor) if !self.draw_cursor => { + window.set_cursor_visible(true); + window.set_cursor_icon(to_winit_cursor(mouse_cursor)); + } + _ => window.set_cursor_visible(false), + } + } +} + +#[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), + } + } +} + +fn to_imgui_mouse_button(button: MouseButton) -> Option { + match button { + MouseButton::Left | MouseButton::Other(0) => Some(imgui::MouseButton::Left), + MouseButton::Right | MouseButton::Other(1) => Some(imgui::MouseButton::Right), + MouseButton::Middle | MouseButton::Other(2) => Some(imgui::MouseButton::Middle), + MouseButton::Other(3) => Some(imgui::MouseButton::Extra1), + MouseButton::Other(4) => Some(imgui::MouseButton::Extra2), + _ => None, + } +} + +fn to_imgui_key(key: winit::keyboard::Key, location: KeyLocation) -> Option { + match (key.as_ref(), location) { + (WinitKey::Named(NamedKey::Tab), _) => Some(Key::Tab), + (WinitKey::Named(NamedKey::ArrowLeft), _) => Some(Key::LeftArrow), + (WinitKey::Named(NamedKey::ArrowRight), _) => Some(Key::RightArrow), + (WinitKey::Named(NamedKey::ArrowUp), _) => Some(Key::UpArrow), + (WinitKey::Named(NamedKey::ArrowDown), _) => Some(Key::DownArrow), + (WinitKey::Named(NamedKey::PageUp), _) => Some(Key::PageUp), + (WinitKey::Named(NamedKey::PageDown), _) => Some(Key::PageDown), + (WinitKey::Named(NamedKey::Home), _) => Some(Key::Home), + (WinitKey::Named(NamedKey::End), _) => Some(Key::End), + (WinitKey::Named(NamedKey::Insert), _) => Some(Key::Insert), + (WinitKey::Named(NamedKey::Delete), _) => Some(Key::Delete), + (WinitKey::Named(NamedKey::Backspace), _) => Some(Key::Backspace), + (WinitKey::Named(NamedKey::Space), _) => Some(Key::Space), + (WinitKey::Named(NamedKey::Enter), KeyLocation::Standard) => Some(Key::Enter), + (WinitKey::Named(NamedKey::Enter), KeyLocation::Numpad) => Some(Key::KeypadEnter), + (WinitKey::Named(NamedKey::Escape), _) => Some(Key::Escape), + (WinitKey::Named(NamedKey::Control), KeyLocation::Left) => Some(Key::LeftCtrl), + (WinitKey::Named(NamedKey::Control), KeyLocation::Right) => Some(Key::RightCtrl), + (WinitKey::Named(NamedKey::Shift), KeyLocation::Left) => Some(Key::LeftShift), + (WinitKey::Named(NamedKey::Shift), KeyLocation::Right) => Some(Key::RightShift), + (WinitKey::Named(NamedKey::Alt), KeyLocation::Left) => Some(Key::LeftAlt), + (WinitKey::Named(NamedKey::Alt), KeyLocation::Right) => Some(Key::RightAlt), + (WinitKey::Named(NamedKey::Super), KeyLocation::Left) => Some(Key::LeftSuper), + (WinitKey::Named(NamedKey::Super), KeyLocation::Right) => Some(Key::RightSuper), + (WinitKey::Named(NamedKey::ContextMenu), _) => Some(Key::Menu), + (WinitKey::Named(NamedKey::F1), _) => Some(Key::F1), + (WinitKey::Named(NamedKey::F2), _) => Some(Key::F2), + (WinitKey::Named(NamedKey::F3), _) => Some(Key::F3), + (WinitKey::Named(NamedKey::F4), _) => Some(Key::F4), + (WinitKey::Named(NamedKey::F5), _) => Some(Key::F5), + (WinitKey::Named(NamedKey::F6), _) => Some(Key::F6), + (WinitKey::Named(NamedKey::F7), _) => Some(Key::F7), + (WinitKey::Named(NamedKey::F8), _) => Some(Key::F8), + (WinitKey::Named(NamedKey::F9), _) => Some(Key::F9), + (WinitKey::Named(NamedKey::F10), _) => Some(Key::F10), + (WinitKey::Named(NamedKey::F11), _) => Some(Key::F11), + (WinitKey::Named(NamedKey::F12), _) => Some(Key::F12), + (WinitKey::Named(NamedKey::CapsLock), _) => Some(Key::CapsLock), + (WinitKey::Named(NamedKey::ScrollLock), _) => Some(Key::ScrollLock), + (WinitKey::Named(NamedKey::NumLock), _) => Some(Key::NumLock), + (WinitKey::Named(NamedKey::PrintScreen), _) => Some(Key::PrintScreen), + (WinitKey::Named(NamedKey::Pause), _) => Some(Key::Pause), + (WinitKey::Character("0"), KeyLocation::Standard) => Some(Key::Alpha0), + (WinitKey::Character("1"), KeyLocation::Standard) => Some(Key::Alpha1), + (WinitKey::Character("2"), KeyLocation::Standard) => Some(Key::Alpha2), + (WinitKey::Character("3"), KeyLocation::Standard) => Some(Key::Alpha3), + (WinitKey::Character("4"), KeyLocation::Standard) => Some(Key::Alpha4), + (WinitKey::Character("5"), KeyLocation::Standard) => Some(Key::Alpha5), + (WinitKey::Character("6"), KeyLocation::Standard) => Some(Key::Alpha6), + (WinitKey::Character("7"), KeyLocation::Standard) => Some(Key::Alpha7), + (WinitKey::Character("8"), KeyLocation::Standard) => Some(Key::Alpha8), + (WinitKey::Character("9"), KeyLocation::Standard) => Some(Key::Alpha9), + (WinitKey::Character("0"), KeyLocation::Numpad) => Some(Key::Keypad0), + (WinitKey::Character("1"), KeyLocation::Numpad) => Some(Key::Keypad1), + (WinitKey::Character("2"), KeyLocation::Numpad) => Some(Key::Keypad2), + (WinitKey::Character("3"), KeyLocation::Numpad) => Some(Key::Keypad3), + (WinitKey::Character("4"), KeyLocation::Numpad) => Some(Key::Keypad4), + (WinitKey::Character("5"), KeyLocation::Numpad) => Some(Key::Keypad5), + (WinitKey::Character("6"), KeyLocation::Numpad) => Some(Key::Keypad6), + (WinitKey::Character("7"), KeyLocation::Numpad) => Some(Key::Keypad7), + (WinitKey::Character("8"), KeyLocation::Numpad) => Some(Key::Keypad8), + (WinitKey::Character("9"), KeyLocation::Numpad) => Some(Key::Keypad9), + (WinitKey::Character("a"), _) => Some(Key::A), + (WinitKey::Character("b"), _) => Some(Key::B), + (WinitKey::Character("c"), _) => Some(Key::C), + (WinitKey::Character("d"), _) => Some(Key::D), + (WinitKey::Character("e"), _) => Some(Key::E), + (WinitKey::Character("f"), _) => Some(Key::F), + (WinitKey::Character("g"), _) => Some(Key::G), + (WinitKey::Character("h"), _) => Some(Key::H), + (WinitKey::Character("i"), _) => Some(Key::I), + (WinitKey::Character("j"), _) => Some(Key::J), + (WinitKey::Character("k"), _) => Some(Key::K), + (WinitKey::Character("l"), _) => Some(Key::L), + (WinitKey::Character("m"), _) => Some(Key::M), + (WinitKey::Character("n"), _) => Some(Key::N), + (WinitKey::Character("o"), _) => Some(Key::O), + (WinitKey::Character("p"), _) => Some(Key::P), + (WinitKey::Character("q"), _) => Some(Key::Q), + (WinitKey::Character("r"), _) => Some(Key::R), + (WinitKey::Character("s"), _) => Some(Key::S), + (WinitKey::Character("t"), _) => Some(Key::T), + (WinitKey::Character("u"), _) => Some(Key::U), + (WinitKey::Character("v"), _) => Some(Key::V), + (WinitKey::Character("w"), _) => Some(Key::W), + (WinitKey::Character("x"), _) => Some(Key::X), + (WinitKey::Character("y"), _) => Some(Key::Y), + (WinitKey::Character("z"), _) => Some(Key::Z), + (WinitKey::Character("'"), _) => Some(Key::Apostrophe), + (WinitKey::Character(","), KeyLocation::Standard) => Some(Key::Comma), + (WinitKey::Character("-"), KeyLocation::Standard) => Some(Key::Minus), + (WinitKey::Character("-"), KeyLocation::Numpad) => Some(Key::KeypadSubtract), + (WinitKey::Character("."), KeyLocation::Standard) => Some(Key::Period), + (WinitKey::Character("."), KeyLocation::Numpad) => Some(Key::KeypadDecimal), + (WinitKey::Character("/"), KeyLocation::Standard) => Some(Key::Slash), + (WinitKey::Character("/"), KeyLocation::Numpad) => Some(Key::KeypadDivide), + (WinitKey::Character(";"), _) => Some(Key::Semicolon), + (WinitKey::Character("="), KeyLocation::Standard) => Some(Key::Equal), + (WinitKey::Character("="), KeyLocation::Numpad) => Some(Key::KeypadEqual), + (WinitKey::Character("["), _) => Some(Key::LeftBracket), + (WinitKey::Character("\\"), _) => Some(Key::Backslash), + (WinitKey::Character("]"), _) => Some(Key::RightBracket), + (WinitKey::Character("`"), _) => Some(Key::GraveAccent), + (WinitKey::Character("*"), KeyLocation::Numpad) => Some(Key::KeypadMultiply), + (WinitKey::Character("+"), KeyLocation::Numpad) => Some(Key::KeypadAdd), + _ => None, + } +} + +fn handle_key_modifier(io: &mut Io, key: &WinitKey, down: bool) { + match key { + WinitKey::Named(NamedKey::Shift) => io.add_key_event(imgui::Key::ModShift, down), + WinitKey::Named(NamedKey::Control) => io.add_key_event(imgui::Key::ModCtrl, down), + WinitKey::Named(NamedKey::Alt) => io.add_key_event(imgui::Key::ModAlt, down), + WinitKey::Named(NamedKey::Super) => io.add_key_event(imgui::Key::ModSuper, down), + _ => {} + } +} + +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); + imgui.set_platform_name(Some(format!( + "imgui-winit-support {}", + env!("CARGO_PKG_VERSION") + ))); + WinitPlatform { + hidpi_mode: ActiveHiDpiMode::Default, + hidpi_factor: 1.0, + cursor_cache: None, + } + } + /// 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.scale_factor()); + self.hidpi_mode = hidpi_mode; + self.hidpi_factor = hidpi_factor; + io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32]; + let logical_size = window.inner_size().to_logical(hidpi_factor); + 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.scale_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.scale_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.scale_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(RawKeyEvent { + // physical_key, + // state: ElementState::Released, + // }), + // .. + // } => { + // if let Some(key) = to_imgui_key(key) { + // io.add_key_event(key, false); + // } + // } + _ => (), + } + } + fn handle_window_event(&mut self, io: &mut Io, window: &Window, event: &WindowEvent) { + match *event { + WindowEvent::Resized(physical_size) => { + let logical_size = physical_size.to_logical(window.scale_factor()); + 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::ScaleFactorChanged { scale_factor, .. } => { + let hidpi_factor = match self.hidpi_mode { + ActiveHiDpiMode::Default => scale_factor, + ActiveHiDpiMode::Rounded => scale_factor.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 + let logical_size = window.inner_size().to_logical(scale_factor); + 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::ModifiersChanged(modifiers) => { + let state = modifiers.state(); + // We need to track modifiers separately because some system like macOS, will + // not reliably send modifier states during certain events like ScreenCapture. + // Gotta let the people show off their pretty imgui widgets! + io.add_key_event(Key::ModShift, state.shift_key()); + io.add_key_event(Key::ModCtrl, state.control_key()); + io.add_key_event(Key::ModAlt, state.alt_key()); + io.add_key_event(Key::ModSuper, state.super_key()); + } + WindowEvent::KeyboardInput { ref event, .. } => { + if let Some(txt) = &event.text { + for ch in txt.chars() { + if ch != '\u{7f}' { + io.add_input_character(ch) + } + } + } + + let key = event.key_without_modifiers(); + let pressed = event.state == ElementState::Pressed; + + // We map both left and right ctrl to `ModCtrl`, etc. + // imgui is told both "left control is pressed" and + // "consider the control key is pressed". Allows + // applications to use either general "ctrl" or a + // specific key. Same applies to other modifiers. + // https://github.com/ocornut/imgui/issues/5047 + handle_key_modifier(io, &key, pressed); + + // Add main key event + if let Some(key) = to_imgui_key(key, event.location) { + io.add_key_event(key, pressed); + } + } + WindowEvent::CursorMoved { position, .. } => { + let position = position.to_logical(window.scale_factor()); + let position = self.scale_pos_from_winit(window, position); + io.add_mouse_pos_event([position.x as f32, position.y as f32]); + } + WindowEvent::MouseWheel { + delta, + phase: TouchPhase::Moved, + .. + } => { + let (h, v) = match delta { + MouseScrollDelta::LineDelta(h, v) => (h, v), + MouseScrollDelta::PixelDelta(pos) => { + let pos = pos.to_logical::(self.hidpi_factor); + let h = match pos.x.partial_cmp(&0.0) { + Some(Ordering::Greater) => 1.0, + Some(Ordering::Less) => -1.0, + _ => 0.0, + }; + let v = match pos.y.partial_cmp(&0.0) { + Some(Ordering::Greater) => 1.0, + Some(Ordering::Less) => -1.0, + _ => 0.0, + }; + (h, v) + } + }; + io.add_mouse_wheel_event([h, v]); + } + WindowEvent::MouseInput { state, button, .. } => { + if let Some(mb) = to_imgui_mouse_button(button) { + let pressed = state == ElementState::Pressed; + io.add_mouse_button_event(mb, pressed); + } + } + WindowEvent::Focused(newly_focused) => { + if !newly_focused { + // Set focus-lost to avoid stuck keys (like 'alt' + // when alt-tabbing) + io.app_focus_lost = true; + } + } + _ => (), + } + } + /// 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<(), ExternalError> { + 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(&mut self, ui: &Ui, window: &Window) { + let io = ui.io(); + if !io + .config_flags + .contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE) + { + let cursor = CursorSettings { + cursor: ui.mouse_cursor(), + draw_cursor: io.mouse_draw_cursor, + }; + if self.cursor_cache != Some(cursor) { + cursor.apply(window); + self.cursor_cache = Some(cursor); + } + } + } +}