extended_winit_imgui_support.rs
|
1 //! This crate provides a winit-based backend platform for imgui_supp-rs.
2 //!
3 //! A backend platform handles window/input device events and manages their
4 //! state.
5 //!
6 //! # Using the library
7 //!
8 //! There are five things you need to do to use this library correctly:
9 //!
10 //! 1. Initialize a `WinitPlatform` instance
11 //! 2. Attach it to a winit `Window`
12 //! 3. Pass events to the platform (every frame)
13 //! 4. Call frame preparation callback (every frame)
14 //! 5. Call render preparation callback (every frame)
15 //!
16 //! ## Complete example for winit 0.20+ (without a renderer)
17 //!
18 //! ```rust,no_run,ignore
19 //! # // TODO: Remove ignore when only one winit version is used
20 //! use imgui_supp::Context;
21 //! use imgui_winit_support::{HiDpiMode, WinitPlatform};
22 //! use std::time::Instant;
23 //! use winit::event::{Event, WindowEvent};
24 //! use winit::event_loop::{ControlFlow, EventLoop};
25 //! use winit::window::{Window};
26 //!
27 //! let mut event_loop = EventLoop::new();
28 //! let mut window = Window::new(&event_loop).unwrap();
29 //!
30 //! let mut imgui_supp = Context::create();
31 //! // configure imgui_supp-rs Context if necessary
32 //!
33 //! let mut platform = WinitPlatform::init(&mut imgui_supp); // step 1
34 //! platform.attach_window(imgui_supp.io_mut(), &window, HiDpiMode::Default); // step 2
35 //!
36 //! let mut last_frame = Instant::now();
37 //! let mut run = true;
38 //! event_loop.run(move |event, _, control_flow| {
39 //! match event {
40 //! Event::NewEvents(_) => {
41 //! // other application-specific logic
42 //! last_frame = imgui_supp.io_mut().update_delta_time(last_frame);
43 //! },
44 //! Event::MainEventsCleared => {
45 //! // other application-specific logic
46 //! platform.prepare_frame(imgui_supp.io_mut(), &window) // step 4
47 //! .expect("Failed to prepare frame");
48 //! window.request_redraw();
49 //! }
50 //! Event::RedrawRequested(_) => {
51 //! let ui = imgui_supp.frame();
52 //! // application-specific rendering *under the UI*
53 //!
54 //! // construct the UI
55 //!
56 //! platform.prepare_render(&ui, &window); // step 5
57 //! // render the UI with a renderer
58 //! let draw_data = ui.render();
59 //! // renderer.render(..., draw_data).expect("UI rendering failed");
60 //!
61 //! // application-specific rendering *over the UI*
62 //! },
63 //! Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
64 //! *control_flow = ControlFlow::Exit;
65 //! }
66 //! // other application-specific event handling
67 //! event => {
68 //! platform.handle_event(imgui_supp.io_mut(), &window, &event); // step 3
69 //! // other application-specific event handling
70 //! }
71 //! }
72 //! })
73 //! ```
74 //!
75 //! ## `winit` versions and features.
76 //!
77 //! This crate has several features which control the version of winit which is
78 //! used.
79 //!
80 //! The following versions are supported, controlled by the listed feature.
81 //!
82 //! - The `winit-24` feature supports winit versions `0.24`. This is
83 //! on by default, so to use any other version you need to disable this crates
84 //! default features.
85 //! - The `winit-23` feature uses winit versions compatible with `0.23`.
86 //! - The `winit-22` feature uses winit versions compatible with `0.22`.
87 //! - The `winit-20` feature should support winit either `0.20` or winit `0.21`.
88 //! - The `winit-19` feature should support winits older than `0.19` (possibly
89 //! back to winit 0.16.*, but this isn't regularly tested and may not work).
90 //!
91 //! If multiple `winit-*` features are enabled, and it is a debug build (as
92 //! determined by `debug_assertions`), we will log a warning to stderr during
93 //! init. This can be disabled by either turning on the `no-warn-on-multiple`
94 //! feature, fixing the configuration, or disabling `debug_assertions`.
95 //!
96 //! Conversely, if no `winit-*` features are enabled, we will fail to compile.
97 //! This is not an issue generally, as by default we turn on `winit-24`.
98 //!
99 //! All of this is in attempt to preserve the additive nature of features (while
100 //! still helping users notice project configuration issues), however it's done
101 //! fairly weakly as our this crate's API isn't 100% identical across winit
102 //! versions.
103 //!
104 //! ### Using an older `winit` version
105 //!
106 //! To use an older version, you must configure `default-features = false` in
107 //! your `Cargo.toml`:
108 //!
109 //! ```toml
110 //! [dependencies.imgui_supp-winit-support]
111 //! version = "0.6"
112 //! features = ["winit-$YOUR_VERSION_HERE"]
113 //! default-features = false
114 //! ```
115 //!
116 //! ### Old `winit` compatibility
117 //!
118 //! No guarantee is made on how long this crate will support legacy versions of
119 //! `winit`, but we'll try to follow these rules:
120 //!
121 //! - Versions which are still in widespread use in the ecosystem will be
122 //! supported while that is true (for example, 0.19 at the time of writing is
123 //! quite old, but used by the most recent version of several popular crates).
124 //!
125 //! - Versions which are not a significant maintenance burden will be supported
126 //! (for example, supporting versions older than winit 0.19 given that we
127 //! support 0.19).
128 //!
129 //! - Explicitly removing support for a feature-indicated version will be
130 //! considered a breaking change.
131 //!
132 //! - Changing the default feature to the new latest `winit` version is *not* a
133 //! breaking change.
134
135 #[cfg(feature = "winit-24")]
136 use winit_24 as winit;
137
138 #[cfg(all(not(feature = "winit-24"), feature = "winit-23"))]
139 use winit_23 as winit;
140
141 #[cfg(all(
142 not(any(feature = "winit-24", feature = "winit-23")),
143 feature = "winit-22",
144 ))]
145 use winit_22 as winit;
146
147 #[cfg(all(
148 not(any(feature = "winit-24", feature = "winit-23", feature = "winit-22")),
149 feature = "winit-20",
150 ))]
151 use winit_20 as winit;
152
153 #[cfg(all(
154 not(any(
155 feature = "winit-24",
156 feature = "winit-23",
157 feature = "winit-22",
158 feature = "winit-20"
159 )),
160 feature = "winit-19",
161 ))]
162 use winit_19 as winit;
163
164 use imgui::{self, BackendFlags, ConfigFlags, Context, ImString, Io, Key, Ui};
165 use std::cell::Cell;
166 use std::cmp::Ordering;
167 use winit::dpi::{LogicalPosition, LogicalSize};
168
169 #[cfg(all(
170 not(any(
171 feature = "winit-24",
172 feature = "winit-23",
173 feature = "winit-22",
174 feature = "winit-20"
175 )),
176 feature = "winit-19",
177 ))]
178 use winit::{
179 DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseCursor, MouseScrollDelta,
180 TouchPhase, VirtualKeyCode, Window, WindowEvent,
181 };
182
183 #[cfg(any(
184 feature = "winit-20",
185 feature = "winit-22",
186 feature = "winit-23",
187 feature = "winit-24"
188 ))]
189 use winit::{
190 error::ExternalError,
191 event::{
192 DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase,
193 VirtualKeyCode, WindowEvent,
194 },
195 window::{CursorIcon as MouseCursor, Window},
196 };
197 use crate::owned_event::{OwnedEvent, OwnedWindowEvent};
198
199 // Ensure at least one is enabled
200 #[cfg(not(any(
201 feature = "winit-19",
202 feature = "winit-20",
203 feature = "winit-22",
204 feature = "winit-23",
205 feature = "winit-24",
206 )))]
207 compile_error!("Please enable at least one version of `winit` (see documentation for details).");
208
209 // FIXME(thom): make updading winit and adding a new feature less of a hassle here.
210 fn check_multiple_winits() {
211 use std::io::Write as _;
212 use std::sync::atomic::{AtomicBool, Ordering};
213 // bail out for release builds or if we've been explicitly disabled.
214 if cfg!(any(not(debug_assertions), feature = "no-warn-on-multiple")) {
215 return;
216 }
217 let winits_enabled = cfg!(feature = "winit-24") as usize
218 + cfg!(feature = "winit-23") as usize
219 + cfg!(feature = "winit-22") as usize
220 + cfg!(feature = "winit-20") as usize
221 + cfg!(feature = "winit-19") as usize;
222
223 // Only complain once even if we're called multiple times.
224 static COMPLAINED: AtomicBool = AtomicBool::new(false);
225 // Note that the `Ordering` basically doesn't matter here, but even if it
226 // did, `Relaxed` is still correct because we're only interested in the
227 // effects on a single atomic variable.
228 if winits_enabled <= 1
229 || COMPLAINED
230 .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
231 .is_err()
232 {
233 return;
234 }
235 let mut err = Vec::with_capacity(512);
236
237 // Log the complaint into a buffer first — in practice this is enough to
238 // ensure atomicity.
239 let _ = writeln!(
240 err,
241 "Warning (imgui_supp-winit-support): More than one `winit-*` version feature is enabled \
242 (this likely indicates misconfiguration, see documentation for details)."
243 );
244 let feats = [
245 ("winit-24", cfg!(feature = "winit-24"), " (default)"),
246 ("winit-23", cfg!(feature = "winit-23"), ""),
247 ("winit-22", cfg!(feature = "winit-22"), ""),
248 ("winit-20", cfg!(feature = "winit-20"), ""),
249 ("winit-19", cfg!(feature = "winit-19"), ""),
250 ];
251 for &(name, enabled, extra) in &feats {
252 if enabled {
253 let _ = writeln!(err, " `feature = {:?}` is enabled{}", name, extra);
254 }
255 }
256 if cfg!(feature = "winit-24") && winits_enabled == 2 {
257 let _ = writeln!(
258 err,
259 " Perhaps you are missing a `default-features = false`?",
260 );
261 }
262 let _ = writeln!(
263 err,
264 " (Note: This warning is only present in debug builds, and \
265 can be disabled using the \"no-warn-on-multiple\" feature)"
266 );
267 let _ = std::io::stderr().write_all(&err);
268 }
269
270 /// State of a single mouse button. Used so that we can detect cases where mouse
271 /// press and release occur on the same frame (seems surprisingly frequent on
272 /// macOS now...)
273 #[derive(Debug, Clone, Default)]
274 struct Button {
275 pressed_this_frame: Cell<bool>,
276 state: Cell<bool>,
277 }
278
279 impl Button {
280 // we can use this in an array initializer, unlike `Default::default()` or a
281 // `const fn new()`.
282 #[allow(clippy::declare_interior_mutable_const)]
283 const INIT: Button = Self {
284 pressed_this_frame: Cell::new(false),
285 state: Cell::new(false),
286 };
287 fn set(&self, pressed: bool) {
288 self.state.set(pressed);
289 if pressed {
290 self.pressed_this_frame.set(true);
291 }
292 }
293 fn get(&self) -> bool {
294 // If we got a press this frame, record it even if we got a release
295 // too — this way we don't drop mouse clicks where the release comes
296 // in on the same frame as the press. (This mirrors what Dear ImGUI
297 // seems to do in the `imgui_impl_*`)
298 self.pressed_this_frame.replace(false) || self.state.get()
299 }
300 }
301
302 /// winit backend platform state
303 #[derive(Debug)]
304 pub struct WinitPlatform {
305 hidpi_mode: ActiveHiDpiMode,
306 hidpi_factor: f64,
307 cursor_cache: Option<CursorSettings>,
308 mouse_buttons: [Button; 5],
309 }
310
311 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
312 struct CursorSettings {
313 cursor: Option<imgui::MouseCursor>,
314 draw_cursor: bool,
315 }
316
317 fn to_winit_cursor(cursor: imgui::MouseCursor) -> MouseCursor {
318 match cursor {
319 imgui::MouseCursor::Arrow => MouseCursor::Default,
320 imgui::MouseCursor::TextInput => MouseCursor::Text,
321 imgui::MouseCursor::ResizeAll => MouseCursor::Move,
322 imgui::MouseCursor::ResizeNS => MouseCursor::NsResize,
323 imgui::MouseCursor::ResizeEW => MouseCursor::EwResize,
324 imgui::MouseCursor::ResizeNESW => MouseCursor::NeswResize,
325 imgui::MouseCursor::ResizeNWSE => MouseCursor::NwseResize,
326 imgui::MouseCursor::Hand => MouseCursor::Hand,
327 imgui::MouseCursor::NotAllowed => MouseCursor::NotAllowed,
328 }
329 }
330
331 impl CursorSettings {
332 #[cfg(all(
333 not(any(
334 feature = "winit-24",
335 feature = "winit-23",
336 feature = "winit-22",
337 feature = "winit-20"
338 )),
339 feature = "winit-19",
340 ))]
341 fn apply(&self, window: &Window) {
342 match self.cursor {
343 Some(mouse_cursor) if !self.draw_cursor => {
344 window.hide_cursor(false);
345 window.set_cursor(to_winit_cursor(mouse_cursor));
346 }
347 _ => window.hide_cursor(true),
348 }
349 }
350 #[cfg(any(
351 feature = "winit-20",
352 feature = "winit-22",
353 feature = "winit-23",
354 feature = "winit-24"
355 ))]
356 fn apply(&self, window: &Window) {
357 match self.cursor {
358 Some(mouse_cursor) if !self.draw_cursor => {
359 window.set_cursor_visible(true);
360 window.set_cursor_icon(to_winit_cursor(mouse_cursor));
361 }
362 _ => window.set_cursor_visible(false),
363 }
364 }
365 }
366
367 #[derive(Copy, Clone, Debug, PartialEq)]
368 enum ActiveHiDpiMode {
369 Default,
370 Rounded,
371 Locked,
372 }
373
374 /// DPI factor handling mode.
375 ///
376 /// Applications that use imgui_supp-rs might want to customize the used DPI factor and not use
377 /// directly the value coming from winit.
378 ///
379 /// **Note: if you use a mode other than default and the DPI factor is adjusted, winit and imgui_supp-rs
380 /// will use different logical coordinates, so be careful if you pass around logical size or
381 /// position values.**
382 #[derive(Copy, Clone, Debug, PartialEq)]
383 pub enum HiDpiMode {
384 /// The DPI factor from winit is used directly without adjustment
385 Default,
386 /// The DPI factor from winit is rounded to an integer value.
387 ///
388 /// This prevents the user interface from becoming blurry with non-integer scaling.
389 Rounded,
390 /// The DPI factor from winit is ignored, and the included value is used instead.
391 ///
392 /// This is useful if you want to force some DPI factor (e.g. 1.0) and not care about the value
393 /// coming from winit.
394 Locked(f64),
395 }
396
397 impl HiDpiMode {
398 fn apply(&self, hidpi_factor: f64) -> (ActiveHiDpiMode, f64) {
399 match *self {
400 HiDpiMode::Default => (ActiveHiDpiMode::Default, hidpi_factor),
401 HiDpiMode::Rounded => (ActiveHiDpiMode::Rounded, hidpi_factor.round()),
402 HiDpiMode::Locked(value) => (ActiveHiDpiMode::Locked, value),
403 }
404 }
405 }
406
407 impl WinitPlatform {
408 /// Initializes a winit platform instance and configures imgui_supp.
409 ///
410 /// This function configures imgui_supp-rs in the following ways:
411 ///
412 /// * backend flags are updated
413 /// * keys are configured
414 /// * platform name is set
415 pub fn init(imgui: &mut Context) -> WinitPlatform {
416 // noop in non-debug builds, if disabled, or if called a second time.
417 check_multiple_winits();
418 let io = imgui.io_mut();
419 io.backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS);
420 io.backend_flags.insert(BackendFlags::HAS_SET_MOUSE_POS);
421 io[Key::Tab] = VirtualKeyCode::Tab as _;
422 io[Key::LeftArrow] = VirtualKeyCode::Left as _;
423 io[Key::RightArrow] = VirtualKeyCode::Right as _;
424 io[Key::UpArrow] = VirtualKeyCode::Up as _;
425 io[Key::DownArrow] = VirtualKeyCode::Down as _;
426 io[Key::PageUp] = VirtualKeyCode::PageUp as _;
427 io[Key::PageDown] = VirtualKeyCode::PageDown as _;
428 io[Key::Home] = VirtualKeyCode::Home as _;
429 io[Key::End] = VirtualKeyCode::End as _;
430 io[Key::Insert] = VirtualKeyCode::Insert as _;
431 io[Key::Delete] = VirtualKeyCode::Delete as _;
432 io[Key::Backspace] = VirtualKeyCode::Back as _;
433 io[Key::Space] = VirtualKeyCode::Space as _;
434 io[Key::Enter] = VirtualKeyCode::Return as _;
435 io[Key::Escape] = VirtualKeyCode::Escape as _;
436 io[Key::KeyPadEnter] = VirtualKeyCode::NumpadEnter as _;
437 io[Key::A] = VirtualKeyCode::A as _;
438 io[Key::C] = VirtualKeyCode::C as _;
439 io[Key::V] = VirtualKeyCode::V as _;
440 io[Key::X] = VirtualKeyCode::X as _;
441 io[Key::Y] = VirtualKeyCode::Y as _;
442 io[Key::Z] = VirtualKeyCode::Z as _;
443 imgui.set_platform_name(Some(ImString::from(format!(
444 "imgui_supp-winit-support {}",
445 env!("CARGO_PKG_VERSION")
446 ))));
447 WinitPlatform {
448 hidpi_mode: ActiveHiDpiMode::Default,
449 hidpi_factor: 1.0,
450 cursor_cache: None,
451 mouse_buttons: [Button::INIT; 5],
452 }
453 }
454 /// Attaches the platform instance to a winit window.
455 ///
456 /// This function configures imgui_supp-rs in the following ways:
457 ///
458 /// * framebuffer scale (= DPI factor) is set
459 /// * display size is set
460 #[cfg(all(
461 not(any(
462 feature = "winit-24",
463 feature = "winit-23",
464 feature = "winit-22",
465 feature = "winit-20"
466 )),
467 feature = "winit-19",
468 ))]
469 pub fn attach_window(&mut self, io: &mut Io, window: &Window, hidpi_mode: HiDpiMode) {
470 let (hidpi_mode, hidpi_factor) = hidpi_mode.apply(window.get_hidpi_factor());
471 self.hidpi_mode = hidpi_mode;
472 self.hidpi_factor = hidpi_factor;
473 io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32];
474 if let Some(logical_size) = window.get_inner_size() {
475 let logical_size = self.scale_size_from_winit(window, logical_size);
476 io.display_size = [logical_size.width as f32, logical_size.height as f32];
477 }
478 }
479 /// Attaches the platform instance to a winit window.
480 ///
481 /// This function configures imgui_supp-rs in the following ways:
482 ///
483 /// * framebuffer scale (= DPI factor) is set
484 /// * display size is set
485 #[cfg(any(
486 feature = "winit-20",
487 feature = "winit-22",
488 feature = "winit-23",
489 feature = "winit-24"
490 ))]
491 pub fn attach_window(&mut self, io: &mut Io, window: &Window, hidpi_mode: HiDpiMode) {
492 let (hidpi_mode, hidpi_factor) = hidpi_mode.apply(window.scale_factor());
493 self.hidpi_mode = hidpi_mode;
494 self.hidpi_factor = hidpi_factor;
495 io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32];
496 let logical_size = window.inner_size().to_logical(hidpi_factor);
497 let logical_size = self.scale_size_from_winit(window, logical_size);
498 io.display_size = [logical_size.width as f32, logical_size.height as f32];
499 }
500 /// Returns the current DPI factor.
501 ///
502 /// The value might not be the same as the winit DPI factor (depends on the used DPI mode)
503 pub fn hidpi_factor(&self) -> f64 {
504 self.hidpi_factor
505 }
506 /// Scales a logical size coming from winit using the current DPI mode.
507 ///
508 /// This utility function is useful if you are using a DPI mode other than default, and want
509 /// your application to use the same logical coordinates as imgui_supp-rs.
510 #[cfg(all(
511 not(any(
512 feature = "winit-24",
513 feature = "winit-23",
514 feature = "winit-22",
515 feature = "winit-20"
516 )),
517 feature = "winit-19",
518 ))]
519 pub fn scale_size_from_winit(&self, window: &Window, logical_size: LogicalSize) -> LogicalSize {
520 match self.hidpi_mode {
521 ActiveHiDpiMode::Default => logical_size,
522 _ => logical_size
523 .to_physical(window.get_hidpi_factor())
524 .to_logical(self.hidpi_factor),
525 }
526 }
527 /// Scales a logical size coming from winit using the current DPI mode.
528 ///
529 /// This utility function is useful if you are using a DPI mode other than default, and want
530 /// your application to use the same logical coordinates as imgui_supp-rs.
531 #[cfg(any(
532 feature = "winit-20",
533 feature = "winit-22",
534 feature = "winit-23",
535 feature = "winit-24"
536 ))]
537 pub fn scale_size_from_winit(
538 &self,
539 window: &Window,
540 logical_size: LogicalSize<f64>,
541 ) -> LogicalSize<f64> {
542 match self.hidpi_mode {
543 ActiveHiDpiMode::Default => logical_size,
544 _ => logical_size
545 .to_physical::<f64>(window.scale_factor())
546 .to_logical(self.hidpi_factor),
547 }
548 }
549 /// Scales a logical position coming from winit using the current DPI mode.
550 ///
551 /// This utility function is useful if you are using a DPI mode other than default, and want
552 /// your application to use the same logical coordinates as imgui_supp-rs.
553 #[cfg(all(
554 not(any(
555 feature = "winit-24",
556 feature = "winit-23",
557 feature = "winit-22",
558 feature = "winit-20"
559 )),
560 feature = "winit-19",
561 ))]
562 pub fn scale_pos_from_winit(
563 &self,
564 window: &Window,
565 logical_pos: LogicalPosition,
566 ) -> LogicalPosition {
567 match self.hidpi_mode {
568 ActiveHiDpiMode::Default => logical_pos,
569 _ => logical_pos
570 .to_physical(window.get_hidpi_factor())
571 .to_logical(self.hidpi_factor),
572 }
573 }
574 /// Scales a logical position coming from winit using the current DPI mode.
575 ///
576 /// This utility function is useful if you are using a DPI mode other than default, and want
577 /// your application to use the same logical coordinates as imgui_supp-rs.
578 #[cfg(any(
579 feature = "winit-20",
580 feature = "winit-22",
581 feature = "winit-23",
582 feature = "winit-24"
583 ))]
584 pub fn scale_pos_from_winit(
585 &self,
586 window: &Window,
587 logical_pos: LogicalPosition<f64>,
588 ) -> LogicalPosition<f64> {
589 match self.hidpi_mode {
590 ActiveHiDpiMode::Default => logical_pos,
591 _ => logical_pos
592 .to_physical::<f64>(window.scale_factor())
593 .to_logical(self.hidpi_factor),
594 }
595 }
596 /// Scales a logical position for winit using the current DPI mode.
597 ///
598 /// This utility function is useful if you are using a DPI mode other than default, and want
599 /// your application to use the same logical coordinates as imgui_supp-rs.
600 #[cfg(all(
601 not(any(
602 feature = "winit-24",
603 feature = "winit-23",
604 feature = "winit-22",
605 feature = "winit-20"
606 )),
607 feature = "winit-19",
608 ))]
609 pub fn scale_pos_for_winit(
610 &self,
611 window: &Window,
612 logical_pos: LogicalPosition,
613 ) -> LogicalPosition {
614 match self.hidpi_mode {
615 ActiveHiDpiMode::Default => logical_pos,
616 _ => logical_pos
617 .to_physical(self.hidpi_factor)
618 .to_logical(window.get_hidpi_factor()),
619 }
620 }
621 /// Scales a logical position for winit using the current DPI mode.
622 ///
623 /// This utility function is useful if you are using a DPI mode other than default, and want
624 /// your application to use the same logical coordinates as imgui_supp-rs.
625 #[cfg(any(
626 feature = "winit-20",
627 feature = "winit-22",
628 feature = "winit-23",
629 feature = "winit-24"
630 ))]
631 pub fn scale_pos_for_winit(
632 &self,
633 window: &Window,
634 logical_pos: LogicalPosition<f64>,
635 ) -> LogicalPosition<f64> {
636 match self.hidpi_mode {
637 ActiveHiDpiMode::Default => logical_pos,
638 _ => logical_pos
639 .to_physical::<f64>(self.hidpi_factor)
640 .to_logical(window.scale_factor()),
641 }
642 }
643 /// Handles a winit event.
644 ///
645 /// This function performs the following actions (depends on the event):
646 ///
647 /// * window size / dpi factor changes are applied
648 /// * keyboard state is updated
649 /// * mouse state is updated
650 #[cfg(all(
651 not(any(
652 feature = "winit-24",
653 feature = "winit-23",
654 feature = "winit-22",
655 feature = "winit-20"
656 )),
657 feature = "winit-19",
658 ))]
659 pub fn handle_event(&mut self, io: &mut Io, window: &Window, event: &Event) {
660 match *event {
661 Event::WindowEvent {
662 window_id,
663 ref event,
664 } if window_id == window.id() => {
665 self.handle_window_event(io, window, event);
666 }
667 // Track key release events outside our window. If we don't do this,
668 // we might never see the release event if some other window gets focus.
669 Event::DeviceEvent {
670 event:
671 DeviceEvent::Key(KeyboardInput {
672 state: ElementState::Released,
673 virtual_keycode: Some(key),
674 ..
675 }),
676 ..
677 } => {
678 io.keys_down[key as usize] = false;
679 match key {
680 VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = false,
681 VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = false,
682 VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = false,
683 VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = false,
684 _ => (),
685 }
686 }
687 _ => (),
688 }
689 }
690 /// Handles a winit event.
691 ///
692 /// This function performs the following actions (depends on the event):
693 ///
694 /// * window size / dpi factor changes are applied
695 /// * keyboard state is updated
696 /// * mouse state is updated
697 #[cfg(all(
698 not(any(feature = "winit-24", feature = "winit-23", feature = "winit-22")),
699 feature = "winit-20",
700 ))]
701 pub fn handle_event<T>(&mut self, io: &mut Io, window: &Window, event: &Event<T>) {
702 match *event {
703 Event::WindowEvent {
704 window_id,
705 ref event,
706 } if window_id == window.id() => {
707 self.handle_window_event(io, window, event);
708 }
709 // Track key release events outside our window. If we don't do this,
710 // we might never see the release event if some other window gets focus.
711 Event::DeviceEvent {
712 event:
713 DeviceEvent::Key(KeyboardInput {
714 state: ElementState::Released,
715 virtual_keycode: Some(key),
716 ..
717 }),
718 ..
719 } => {
720 io.keys_down[key as usize] = false;
721 }
722
723 // We need to track modifiers separately because some system like macOS, will
724 // not reliably send modifier states during certain events like ScreenCapture.
725 // Gotta let the people show off their pretty imgui_supp widgets!
726 Event::DeviceEvent {
727 event: DeviceEvent::ModifiersChanged(modifiers),
728 ..
729 } => {
730 io.key_shift = modifiers.shift();
731 io.key_ctrl = modifiers.ctrl();
732 io.key_alt = modifiers.alt();
733 io.key_super = modifiers.logo();
734 }
735 _ => (),
736 }
737 }
738 /// Handles a winit event.
739 ///
740 /// This function performs the following actions (depends on the event):
741 ///
742 /// * window size / dpi factor changes are applied
743 /// * keyboard state is updated
744 /// * mouse state is updated
745 #[cfg(any(feature = "winit-22", feature = "winit-23", feature = "winit-24"))]
746 pub fn handle_event<T>(&mut self, io: &mut Io, window: &Window, event: &OwnedEvent<T>) {
747 match *event {
748 OwnedEvent::WindowEvent {
749 window_id,
750 ref event,
751 } if window_id == window.id() => {
752 // We need to track modifiers separately because some system like macOS, will
753 // not reliably send modifier states during certain events like ScreenCapture.
754 // Gotta let the people show off their pretty imgui_supp widgets!
755 if let OwnedWindowEvent::ModifiersChanged(modifiers) = event {
756 io.key_shift = modifiers.shift();
757 io.key_ctrl = modifiers.ctrl();
758 io.key_alt = modifiers.alt();
759 io.key_super = modifiers.logo();
760 }
761
762 self.handle_window_event(io, window, event);
763 }
764 // Track key release events outside our window. If we don't do this,
765 // we might never see the release event if some other window gets focus.
766 OwnedEvent::DeviceEvent {
767 event:
768 DeviceEvent::Key(KeyboardInput {
769 state: ElementState::Released,
770 virtual_keycode: Some(key),
771 ..
772 }),
773 ..
774 } => {
775 io.keys_down[key as usize] = false;
776 }
777 _ => (),
778 }
779 }
780 #[cfg(all(
781 not(any(
782 feature = "winit-24",
783 feature = "winit-23",
784 feature = "winit-22",
785 feature = "winit-20"
786 )),
787 feature = "winit-19",
788 ))]
789 fn handle_window_event(&mut self, io: &mut Io, window: &Window, event: &WindowEvent) {
790 match *event {
791 WindowEvent::Resized(logical_size) => {
792 let logical_size = self.scale_size_from_winit(window, logical_size);
793 io.display_size = [logical_size.width as f32, logical_size.height as f32];
794 }
795 WindowEvent::HiDpiFactorChanged(scale) => {
796 let hidpi_factor = match self.hidpi_mode {
797 ActiveHiDpiMode::Default => scale,
798 ActiveHiDpiMode::Rounded => scale.round(),
799 _ => return,
800 };
801 // Mouse position needs to be changed while we still have both the old and the new
802 // values
803 if io.mouse_pos[0].is_finite() && io.mouse_pos[1].is_finite() {
804 io.mouse_pos = [
805 io.mouse_pos[0] * (hidpi_factor / self.hidpi_factor) as f32,
806 io.mouse_pos[1] * (hidpi_factor / self.hidpi_factor) as f32,
807 ];
808 }
809 self.hidpi_factor = hidpi_factor;
810 io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32];
811 // Window size might change too if we are using DPI rounding
812 if let Some(logical_size) = window.get_inner_size() {
813 let logical_size = self.scale_size_from_winit(window, logical_size);
814 io.display_size = [logical_size.width as f32, logical_size.height as f32];
815 }
816 }
817 WindowEvent::KeyboardInput {
818 input:
819 KeyboardInput {
820 virtual_keycode: Some(key),
821 state,
822 ..
823 },
824 ..
825 } => {
826 io.keys_down[key as usize] = state == ElementState::Pressed;
827 }
828 WindowEvent::ReceivedCharacter(ch) => {
829 // Exclude the backspace key ('\u{7f}'). Otherwise we will insert this char and then
830 // delete it.
831 if ch != '\u{7f}' {
832 io.add_input_character(ch)
833 }
834 }
835 WindowEvent::CursorMoved { position, .. } => {
836 let position = self.scale_pos_from_winit(window, position);
837 io.mouse_pos = [position.x as f32, position.y as f32];
838 }
839 WindowEvent::MouseWheel {
840 delta,
841 phase: TouchPhase::Moved,
842 ..
843 } => match delta {
844 MouseScrollDelta::LineDelta(h, v) => {
845 io.mouse_wheel_h = h;
846 io.mouse_wheel = v;
847 }
848 MouseScrollDelta::PixelDelta(pos) => {
849 match pos.x.partial_cmp(&0.0) {
850 Some(Ordering::Greater) => io.mouse_wheel_h += 1.0,
851 Some(Ordering::Less) => io.mouse_wheel_h -= 1.0,
852 _ => (),
853 }
854 match pos.y.partial_cmp(&0.0) {
855 Some(Ordering::Greater) => io.mouse_wheel += 1.0,
856 Some(Ordering::Less) => io.mouse_wheel -= 1.0,
857 _ => (),
858 }
859 }
860 },
861 WindowEvent::MouseInput { state, button, .. } => {
862 let pressed = state == ElementState::Pressed;
863 match button {
864 MouseButton::Left => self.mouse_buttons[0].set(pressed),
865 MouseButton::Right => self.mouse_buttons[1].set(pressed),
866 MouseButton::Middle => self.mouse_buttons[2].set(pressed),
867 MouseButton::Other(idx @ 0..=4) => {
868 self.mouse_buttons[idx as usize].set(pressed)
869 }
870 _ => (),
871 }
872 }
873 _ => (),
874 }
875 }
876 #[cfg(all(
877 not(any(feature = "winit-23", feature = "winit-24")),
878 any(feature = "winit-20", feature = "winit-22")
879 ))]
880 fn handle_window_event(&mut self, io: &mut Io, window: &Window, event: &WindowEvent) {
881 match *event {
882 WindowEvent::Resized(physical_size) => {
883 let logical_size = physical_size.to_logical(window.scale_factor());
884 let logical_size = self.scale_size_from_winit(window, logical_size);
885 io.display_size = [logical_size.width as f32, logical_size.height as f32];
886 }
887 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
888 let hidpi_factor = match self.hidpi_mode {
889 ActiveHiDpiMode::Default => scale_factor,
890 ActiveHiDpiMode::Rounded => scale_factor.round(),
891 _ => return,
892 };
893 // Mouse position needs to be changed while we still have both the old and the new
894 // values
895 if io.mouse_pos[0].is_finite() && io.mouse_pos[1].is_finite() {
896 io.mouse_pos = [
897 io.mouse_pos[0] * (hidpi_factor / self.hidpi_factor) as f32,
898 io.mouse_pos[1] * (hidpi_factor / self.hidpi_factor) as f32,
899 ];
900 }
901 self.hidpi_factor = hidpi_factor;
902 io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32];
903 // Window size might change too if we are using DPI rounding
904 let logical_size = window.inner_size().to_logical(scale_factor);
905 let logical_size = self.scale_size_from_winit(window, logical_size);
906 io.display_size = [logical_size.width as f32, logical_size.height as f32];
907 }
908 WindowEvent::KeyboardInput {
909 input:
910 KeyboardInput {
911 virtual_keycode: Some(key),
912 state,
913 ..
914 },
915 ..
916 } => {
917 let pressed = state == ElementState::Pressed;
918 io.keys_down[key as usize] = pressed;
919
920 // This is a bit redundant here, but we'll leave it in. The OS occasionally
921 // fails to send modifiers keys, but it doesn't seem to send false-positives,
922 // so double checking isn't terrible in case some system *doesn't* send
923 // device events sometimes.
924 match key {
925 VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = pressed,
926 VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = pressed,
927 VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = pressed,
928 VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = pressed,
929 _ => (),
930 }
931 }
932 WindowEvent::ReceivedCharacter(ch) => {
933 // Exclude the backspace key ('\u{7f}'). Otherwise we will insert this char and then
934 // delete it.
935 if ch != '\u{7f}' {
936 io.add_input_character(ch)
937 }
938 }
939 WindowEvent::CursorMoved { position, .. } => {
940 let position = position.to_logical(window.scale_factor());
941 let position = self.scale_pos_from_winit(window, position);
942 io.mouse_pos = [position.x as f32, position.y as f32];
943 }
944 WindowEvent::MouseWheel {
945 delta,
946 phase: TouchPhase::Moved,
947 ..
948 } => match delta {
949 MouseScrollDelta::LineDelta(h, v) => {
950 io.mouse_wheel_h = h;
951 io.mouse_wheel = v;
952 }
953 MouseScrollDelta::PixelDelta(pos) => {
954 match pos.x.partial_cmp(&0.0) {
955 Some(Ordering::Greater) => io.mouse_wheel_h += 1.0,
956 Some(Ordering::Less) => io.mouse_wheel_h -= 1.0,
957 _ => (),
958 }
959 match pos.y.partial_cmp(&0.0) {
960 Some(Ordering::Greater) => io.mouse_wheel += 1.0,
961 Some(Ordering::Less) => io.mouse_wheel -= 1.0,
962 _ => (),
963 }
964 }
965 },
966 WindowEvent::MouseInput { state, button, .. } => {
967 let pressed = state == ElementState::Pressed;
968 match button {
969 MouseButton::Left => self.mouse_buttons[0].set(pressed),
970 MouseButton::Right => self.mouse_buttons[1].set(pressed),
971 MouseButton::Middle => self.mouse_buttons[2].set(pressed),
972 MouseButton::Other(idx @ 0..=4) => {
973 self.mouse_buttons[idx as usize].set(pressed)
974 }
975 _ => (),
976 }
977 }
978 _ => (),
979 }
980 }
981
982 #[cfg(any(feature = "winit-23", feature = "winit-24"))]
983 fn handle_window_event(&mut self, io: &mut Io, window: &Window, event: &OwnedWindowEvent) {
984 match *event {
985 OwnedWindowEvent::Resized(physical_size) => {
986 let logical_size = physical_size.to_logical(window.scale_factor());
987 let logical_size = self.scale_size_from_winit(window, logical_size);
988 io.display_size = [logical_size.width as f32, logical_size.height as f32];
989 }
990 OwnedWindowEvent::ScaleFactorChanged { scale_factor, .. } => {
991 let hidpi_factor = match self.hidpi_mode {
992 ActiveHiDpiMode::Default => scale_factor,
993 ActiveHiDpiMode::Rounded => scale_factor.round(),
994 _ => return,
995 };
996 // Mouse position needs to be changed while we still have both the old and the new
997 // values
998 if io.mouse_pos[0].is_finite() && io.mouse_pos[1].is_finite() {
999 io.mouse_pos = [
1000 io.mouse_pos[0] * (hidpi_factor / self.hidpi_factor) as f32,
1001 io.mouse_pos[1] * (hidpi_factor / self.hidpi_factor) as f32,
1002 ];
1003 }
1004 self.hidpi_factor = hidpi_factor;
1005 io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32];
1006 // Window size might change too if we are using DPI rounding
1007 let logical_size = window.inner_size().to_logical(scale_factor);
1008 let logical_size = self.scale_size_from_winit(window, logical_size);
1009 io.display_size = [logical_size.width as f32, logical_size.height as f32];
1010 }
1011 OwnedWindowEvent::KeyboardInput {
1012 input:
1013 KeyboardInput {
1014 virtual_keycode: Some(key),
1015 state,
1016 ..
1017 },
1018 ..
1019 } => {
1020 let pressed = state == ElementState::Pressed;
1021 io.keys_down[key as usize] = pressed;
1022
1023 // This is a bit redundant here, but we'll leave it in. The OS occasionally
1024 // fails to send modifiers keys, but it doesn't seem to send false-positives,
1025 // so double checking isn't terrible in case some system *doesn't* send
1026 // device events sometimes.
1027 match key {
1028 VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = pressed,
1029 VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = pressed,
1030 VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = pressed,
1031 VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = pressed,
1032 _ => (),
1033 }
1034 }
1035 OwnedWindowEvent::ReceivedCharacter(ch) => {
1036 // Exclude the backspace key ('\u{7f}'). Otherwise we will insert this char and then
1037 // delete it.
1038 if ch != '\u{7f}' {
1039 io.add_input_character(ch)
1040 }
1041 }
1042 OwnedWindowEvent::CursorMoved { position, .. } => {
1043 let position = position.to_logical(window.scale_factor());
1044 let position = self.scale_pos_from_winit(window, position);
1045 io.mouse_pos = [position.x as f32, position.y as f32];
1046 }
1047 OwnedWindowEvent::MouseWheel {
1048 delta,
1049 phase: TouchPhase::Moved,
1050 ..
1051 } => match delta {
1052 MouseScrollDelta::LineDelta(h, v) => {
1053 io.mouse_wheel_h = h;
1054 io.mouse_wheel = v;
1055 }
1056 MouseScrollDelta::PixelDelta(pos) => {
1057 let pos = pos.to_logical::<f64>(self.hidpi_factor);
1058 match pos.x.partial_cmp(&0.0) {
1059 Some(Ordering::Greater) => io.mouse_wheel_h += 1.0,
1060 Some(Ordering::Less) => io.mouse_wheel_h -= 1.0,
1061 _ => (),
1062 }
1063 match pos.y.partial_cmp(&0.0) {
1064 Some(Ordering::Greater) => io.mouse_wheel += 1.0,
1065 Some(Ordering::Less) => io.mouse_wheel -= 1.0,
1066 _ => (),
1067 }
1068 }
1069 },
1070 OwnedWindowEvent::MouseInput { state, button, .. } => {
1071 let pressed = state == ElementState::Pressed;
1072 match button {
1073 MouseButton::Left => self.mouse_buttons[0].set(pressed),
1074 MouseButton::Right => self.mouse_buttons[1].set(pressed),
1075 MouseButton::Middle => self.mouse_buttons[2].set(pressed),
1076 MouseButton::Other(idx @ 0..=4) => {
1077 self.mouse_buttons[idx as usize].set(pressed)
1078 }
1079 _ => (),
1080 }
1081 }
1082 _ => (),
1083 }
1084 }
1085 /// Frame preparation callback.
1086 ///
1087 /// Call this before calling the imgui_supp-rs context `frame` function.
1088 /// This function performs the following actions:
1089 ///
1090 /// * mouse cursor is repositioned (if requested by imgui_supp-rs)
1091 #[cfg(all(
1092 not(any(
1093 feature = "winit-24",
1094 feature = "winit-23",
1095 feature = "winit-22",
1096 feature = "winit-20"
1097 )),
1098 feature = "winit-19",
1099 ))]
1100 pub fn prepare_frame(&self, io: &mut Io, window: &Window) -> Result<(), String> {
1101 self.copy_mouse_to_io(&mut io.mouse_down);
1102 if io.want_set_mouse_pos {
1103 let logical_pos = self.scale_pos_for_winit(
1104 window,
1105 LogicalPosition::new(f64::from(io.mouse_pos[0]), f64::from(io.mouse_pos[1])),
1106 );
1107 window.set_cursor_position(logical_pos)
1108 } else {
1109 Ok(())
1110 }
1111 }
1112 /// Frame preparation callback.
1113 ///
1114 /// Call this before calling the imgui_supp-rs context `frame` function.
1115 /// This function performs the following actions:
1116 ///
1117 /// * mouse cursor is repositioned (if requested by imgui_supp-rs)
1118 #[cfg(any(
1119 feature = "winit-20",
1120 feature = "winit-22",
1121 feature = "winit-23",
1122 feature = "winit-24"
1123 ))]
1124 pub fn prepare_frame(&self, io: &mut Io, window: &Window) -> Result<(), ExternalError> {
1125 self.copy_mouse_to_io(&mut io.mouse_down);
1126 if io.want_set_mouse_pos {
1127 let logical_pos = self.scale_pos_for_winit(
1128 window,
1129 LogicalPosition::new(f64::from(io.mouse_pos[0]), f64::from(io.mouse_pos[1])),
1130 );
1131 window.set_cursor_position(logical_pos)
1132 } else {
1133 Ok(())
1134 }
1135 }
1136
1137 fn copy_mouse_to_io(&self, io_mouse_down: &mut [bool]) {
1138 for (io_down, button) in io_mouse_down.iter_mut().zip(&self.mouse_buttons) {
1139 *io_down = button.get();
1140 }
1141 }
1142
1143 /// Render preparation callback.
1144 ///
1145 /// Call this before calling the imgui_supp-rs UI `render_with`/`render` function.
1146 /// This function performs the following actions:
1147 ///
1148 /// * mouse cursor is changed and/or hidden (if requested by imgui_supp-rs)
1149 pub fn prepare_render(&mut self, ui: &Ui, window: &Window) {
1150 let io = ui.io();
1151 if !io
1152 .config_flags
1153 .contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE)
1154 {
1155 let cursor = CursorSettings {
1156 cursor: ui.mouse_cursor(),
1157 draw_cursor: io.mouse_draw_cursor,
1158 };
1159 if self.cursor_cache != Some(cursor) {
1160 cursor.apply(window);
1161 self.cursor_cache = Some(cursor);
1162 }
1163 }
1164 }
1165 }
1166