1use std::ffi::c_void;
7
8use dear_imgui_rs::{BackendFlags, ConfigFlags, Context};
9use winit::dpi::{LogicalPosition, LogicalSize};
10use winit::event::{Event, WindowEvent};
11use winit::window::{Window, WindowAttributes};
12
13#[cfg(not(target_arch = "wasm32"))]
14use std::time::Instant;
15#[cfg(target_arch = "wasm32")]
16use web_time::Instant;
17
18use crate::cursor::CursorSettings;
19use crate::events;
20
21type SetImeDataCallback = unsafe extern "C" fn(
22 *mut dear_imgui_rs::sys::ImGuiContext,
23 *mut dear_imgui_rs::sys::ImGuiViewport,
24 *mut dear_imgui_rs::sys::ImGuiPlatformImeData,
25);
26
27unsafe extern "C" fn imgui_winit_set_ime_data(
31 ctx: *mut dear_imgui_rs::sys::ImGuiContext,
32 viewport: *mut dear_imgui_rs::sys::ImGuiViewport,
33 data: *mut dear_imgui_rs::sys::ImGuiPlatformImeData,
34) {
35 use dear_imgui_rs::sys::{ImGuiPlatformImeData, ImGuiViewport};
36
37 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
38 if viewport.is_null() || data.is_null() {
39 return;
40 }
41
42 let pio = platform_io_for_ime_context(ctx);
44 if pio.is_null() {
45 return;
46 }
47
48 let user_data = (*pio).Platform_ImeUserData as *const Window;
49 if user_data.is_null() {
50 return;
51 }
52
53 let window: &Window = &*user_data;
56 let ime: &ImGuiPlatformImeData = &*data;
57 let vp: &ImGuiViewport = &*viewport;
58
59 if !ime.WantVisible && !ime.WantTextInput {
61 return;
62 }
63
64 let rel_x = (ime.InputPos.x - vp.Pos.x) as f64;
67 let rel_y = (ime.InputPos.y - vp.Pos.y) as f64;
68
69 let pos = LogicalPosition::new(rel_x, rel_y);
70
71 let line_h = if ime.InputLineHeight > 0.0 {
74 ime.InputLineHeight as f64
75 } else {
76 16.0
77 };
78 let size = LogicalSize::new(line_h, line_h);
79
80 window.set_ime_cursor_area(pos, size);
81 }));
82 if res.is_err() {
83 eprintln!("dear-imgui-winit: panic in Platform_SetImeDataFn");
84 std::process::abort();
85 }
86}
87
88fn is_winit_set_ime_data(callback: Option<SetImeDataCallback>) -> bool {
89 callback.is_some_and(|callback| {
90 std::ptr::fn_addr_eq(callback, imgui_winit_set_ime_data as SetImeDataCallback)
91 })
92}
93
94unsafe fn platform_io_for_ime_context(
95 ctx: *mut dear_imgui_rs::sys::ImGuiContext,
96) -> *mut dear_imgui_rs::sys::ImGuiPlatformIO {
97 if ctx.is_null() {
98 unsafe { dear_imgui_rs::sys::igGetPlatformIO_Nil() }
99 } else {
100 unsafe { dear_imgui_rs::sys::igGetPlatformIO_ContextPtr(ctx) }
101 }
102}
103
104#[derive(Copy, Clone, Debug, PartialEq, Default)]
106pub enum HiDpiMode {
107 #[default]
109 Default,
110 Locked(f64),
112 Rounded,
114}
115
116pub struct WinitPlatform {
118 hidpi_mode: HiDpiMode,
119 hidpi_factor: f64,
120 cursor_cache: Option<CursorSettings>,
121 ime_enabled: bool,
122 ime_auto_manage: bool,
123 last_frame: Instant,
124}
125
126impl WinitPlatform {
127 pub fn new(imgui_ctx: &mut Context) -> Self {
139 let _ = imgui_ctx.set_platform_name(Some(format!(
141 "dear-imgui-winit {}",
142 env!("CARGO_PKG_VERSION")
143 )));
144
145 let io = imgui_ctx.io_mut();
146
147 let mut backend_flags = io.backend_flags();
149 backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS | BackendFlags::HAS_SET_MOUSE_POS);
150
151 #[cfg(feature = "multi-viewport")]
152 {
153 backend_flags.insert(BackendFlags::PLATFORM_HAS_VIEWPORTS);
162 backend_flags.insert(BackendFlags::HAS_MOUSE_HOVERED_VIEWPORT);
164 }
167
168 io.set_backend_flags(backend_flags);
169
170 Self {
171 hidpi_mode: HiDpiMode::default(),
172 hidpi_factor: 1.0,
173 cursor_cache: None,
174 ime_enabled: false,
175 ime_auto_manage: true,
176 last_frame: Instant::now(),
177 }
178 }
179
180 pub fn set_hidpi_mode(&mut self, hidpi_mode: HiDpiMode) {
182 self.hidpi_mode = hidpi_mode;
183 }
184
185 pub fn set_ime_allowed(&mut self, window: &Window, allowed: bool) {
193 window.set_ime_allowed(allowed);
194 self.ime_enabled = allowed;
195 }
196
197 pub fn ime_enabled(&self) -> bool {
202 self.ime_enabled
203 }
204
205 pub fn set_ime_auto_management(&mut self, enabled: bool) {
212 self.ime_auto_manage = enabled;
213 }
214
215 pub fn hidpi_factor(&self) -> f64 {
217 self.hidpi_factor
218 }
219
220 pub fn attach_window(
222 &mut self,
223 window: &Window,
224 hidpi_mode: HiDpiMode,
225 imgui_ctx: &mut Context,
226 ) {
227 self.hidpi_mode = hidpi_mode;
228 self.hidpi_factor = match hidpi_mode {
229 HiDpiMode::Default => window.scale_factor(),
230 HiDpiMode::Locked(factor) => factor,
231 HiDpiMode::Rounded => window.scale_factor().round(),
232 };
233
234 let logical_size = window.inner_size().to_logical(window.scale_factor());
236 let logical_size = self.scale_size_from_winit(window, logical_size);
237 let io = imgui_ctx.io_mut();
238
239 io.set_display_size([logical_size.width as f32, logical_size.height as f32]);
240 io.set_display_framebuffer_scale([self.hidpi_factor as f32, self.hidpi_factor as f32]);
241
242 self.set_ime_allowed(window, true);
246
247 unsafe {
250 let pio = imgui_ctx.platform_io_mut().as_raw_mut();
251 if !pio.is_null() {
252 if (*pio).Platform_SetImeDataFn.is_none() {
253 (*pio).Platform_SetImeDataFn = Some(imgui_winit_set_ime_data);
254 (*pio).Platform_ImeUserData = window as *const Window as *mut c_void;
255 } else if is_winit_set_ime_data((*pio).Platform_SetImeDataFn) {
256 (*pio).Platform_ImeUserData = window as *const Window as *mut c_void;
257 }
258 }
259 }
260 }
261
262 pub fn detach_window(&mut self, window: &Window, imgui_ctx: &mut Context) {
268 window.set_ime_allowed(false);
269 self.ime_enabled = false;
270
271 unsafe {
272 let pio = imgui_ctx.platform_io_mut().as_raw_mut();
273 if pio.is_null() || !is_winit_set_ime_data((*pio).Platform_SetImeDataFn) {
274 return;
275 }
276
277 let window_ptr = window as *const Window as *mut c_void;
278 if (*pio).Platform_ImeUserData == window_ptr {
279 (*pio).Platform_ImeUserData = std::ptr::null_mut();
280 (*pio).Platform_SetImeDataFn = None;
281 }
282 }
283 }
284
285 pub fn handle_event<T>(
294 &mut self,
295 imgui_ctx: &mut Context,
296 window: &Window,
297 event: &Event<T>,
298 ) -> bool {
299 match event {
300 Event::WindowEvent { event, .. } => {
301 self.handle_window_event_internal(imgui_ctx, window, event)
302 }
303 Event::DeviceEvent { event, .. } => {
304 events::handle_device_event(event);
305 false
306 }
307 _ => false,
308 }
309 }
310
311 pub fn handle_window_event(
317 &mut self,
318 imgui_ctx: &mut Context,
319 window: &Window,
320 event: &WindowEvent,
321 ) -> bool {
322 self.handle_window_event_internal(imgui_ctx, window, event)
323 }
324
325 fn handle_window_event_internal(
327 &mut self,
328 imgui_ctx: &mut Context,
329 window: &Window,
330 event: &WindowEvent,
331 ) -> bool {
332 match event {
333 WindowEvent::Resized(physical_size) => {
334 let logical_size = physical_size.to_logical(window.scale_factor());
335 let logical_size = self.scale_size_from_winit(window, logical_size);
336 imgui_ctx
337 .io_mut()
338 .set_display_size([logical_size.width as f32, logical_size.height as f32]);
339 false
340 }
341 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
342 let new_hidpi = match self.hidpi_mode {
343 HiDpiMode::Default => *scale_factor,
344 HiDpiMode::Locked(factor) => factor,
345 HiDpiMode::Rounded => scale_factor.round(),
346 };
347 {
349 let io = imgui_ctx.io_mut();
350 let mouse = io.mouse_pos();
351 if mouse[0].is_finite() && mouse[1].is_finite() && self.hidpi_factor > 0.0 {
352 let scale = (new_hidpi / self.hidpi_factor) as f32;
353 io.set_mouse_pos([mouse[0] * scale, mouse[1] * scale]);
354 }
355 }
356 self.hidpi_factor = new_hidpi;
357
358 let logical_size = window.inner_size().to_logical(window.scale_factor());
359 let logical_size = self.scale_size_from_winit(window, logical_size);
360 let io = imgui_ctx.io_mut();
361 io.set_display_size([logical_size.width as f32, logical_size.height as f32]);
362 io.set_display_framebuffer_scale([
363 self.hidpi_factor as f32,
364 self.hidpi_factor as f32,
365 ]);
366 false
367 }
368 WindowEvent::KeyboardInput { event, .. } => {
369 events::handle_keyboard_input(event, imgui_ctx)
370 }
371 WindowEvent::CursorMoved { position, .. } => {
372 #[cfg(feature = "multi-viewport")]
374 {
375 if imgui_ctx
376 .io()
377 .config_flags()
378 .contains(dear_imgui_rs::ConfigFlags::VIEWPORTS_ENABLE)
379 {
380 let main_viewport_id = imgui_ctx.main_viewport().id();
382 imgui_ctx
383 .io_mut()
384 .add_mouse_viewport_event(main_viewport_id.into());
385 let scale = window.scale_factor();
387 let pos_logical = position.to_logical::<f64>(scale);
388 if let Ok(base_phys) = window.inner_position() {
389 let base_logical = base_phys.to_logical::<f64>(scale);
390 let sx = base_logical.x + pos_logical.x;
391 let sy = base_logical.y + pos_logical.y;
392 return events::handle_cursor_moved([sx, sy], imgui_ctx);
393 } else if let Ok(base_phys) = window.outer_position() {
394 let base_logical = base_phys.to_logical::<f64>(scale);
395 let sx = base_logical.x + pos_logical.x;
396 let sy = base_logical.y + pos_logical.y;
397 return events::handle_cursor_moved([sx, sy], imgui_ctx);
398 }
399 }
400 }
401 let position = position.to_logical(window.scale_factor());
403 let position = self.scale_pos_from_winit(window, position);
404 events::handle_cursor_moved([position.x, position.y], imgui_ctx)
405 }
406 WindowEvent::MouseInput { button, state, .. } => {
407 events::handle_mouse_button(*button, *state, imgui_ctx)
408 }
409 WindowEvent::MouseWheel { delta, .. } => events::handle_mouse_wheel(*delta, imgui_ctx),
410 WindowEvent::CursorLeft { .. } => {
413 {
414 let io = imgui_ctx.io_mut();
415 io.add_mouse_pos_event([-f32::MAX, -f32::MAX]);
416 io.add_mouse_viewport_event(dear_imgui_rs::Id::default());
418 }
419 false
420 }
421 WindowEvent::ModifiersChanged(modifiers) => {
422 events::handle_modifiers_changed(modifiers, imgui_ctx);
423 false
424 }
425 WindowEvent::Ime(ime) => {
426 events::handle_ime_event(ime, imgui_ctx);
427 self.ime_enabled = !matches!(ime, winit::event::Ime::Disabled);
429 imgui_ctx.io().want_capture_keyboard()
430 }
431 WindowEvent::Touch(touch) => {
432 events::handle_touch_event(touch, window, imgui_ctx);
433 imgui_ctx.io().want_capture_mouse()
434 }
435 WindowEvent::Focused(focused) => events::handle_focused(*focused, imgui_ctx),
436 _ => false,
437 }
438 }
439
440 pub fn prepare_render(&mut self, imgui_ctx: &mut Context, window: &Window) {
442 let now = Instant::now();
443 let delta = now - self.last_frame;
444 let delta_s = delta.as_secs() as f32 + delta.subsec_nanos() as f32 / 1_000_000_000.0;
445 self.last_frame = now;
446
447 imgui_ctx.io_mut().set_delta_time(delta_s);
448
449 #[cfg(feature = "multi-viewport")]
453 {
454 if imgui_ctx
455 .io()
456 .config_flags()
457 .contains(ConfigFlags::VIEWPORTS_ENABLE)
458 {
459 let winit_scale = window.scale_factor();
460 let hidpi = match self.hidpi_mode {
461 HiDpiMode::Default => winit_scale,
462 HiDpiMode::Locked(factor) => factor,
463 HiDpiMode::Rounded => winit_scale.round(),
464 };
465 self.hidpi_factor = hidpi;
466
467 let logical_size = window.inner_size().to_logical(winit_scale);
468 let logical_size = self.scale_size_from_winit(window, logical_size);
469 let io = imgui_ctx.io_mut();
470 io.set_display_size([logical_size.width as f32, logical_size.height as f32]);
471 io.set_display_framebuffer_scale([hidpi as f32, hidpi as f32]);
472 }
473 }
474
475 if imgui_ctx.io().want_set_mouse_pos()
478 && !imgui_ctx
479 .io()
480 .config_flags()
481 .contains(ConfigFlags::VIEWPORTS_ENABLE)
482 {
483 let pos = imgui_ctx.io().mouse_pos();
484 let logical_pos = self
485 .scale_pos_for_winit(window, LogicalPosition::new(pos[0] as f64, pos[1] as f64));
486 let _ = window.set_cursor_position(logical_pos);
487 }
488 }
490
491 pub fn prepare_frame(&mut self, window: &Window, imgui_ctx: &mut Context) {
493 self.prepare_render(imgui_ctx, window);
494 }
495
496 pub fn set_software_cursor_enabled(&mut self, imgui_ctx: &mut Context, enabled: bool) {
499 imgui_ctx.io_mut().set_mouse_draw_cursor(enabled);
500 self.cursor_cache = None;
502 }
503
504 pub fn prepare_render_with_ui(&mut self, ui: &dear_imgui_rs::Ui, window: &Window) {
506 if self.ime_auto_manage {
510 let want_text = ui.io().want_text_input();
511 if want_text && !self.ime_enabled {
512 window.set_ime_allowed(true);
513 self.ime_enabled = true;
514 } else if !want_text && self.ime_enabled {
515 window.set_ime_allowed(false);
516 self.ime_enabled = false;
517 }
518 }
519
520 if !ui
522 .io()
523 .config_flags()
524 .contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE)
525 {
526 let cursor = CursorSettings {
528 cursor: ui.mouse_cursor(),
529 draw_cursor: ui.io().mouse_draw_cursor(),
530 };
531 if self.cursor_cache != Some(cursor) {
532 cursor.apply(window);
533 self.cursor_cache = Some(cursor);
534 }
535 }
536 }
537
538 pub fn scale_size_from_winit(
540 &self,
541 window: &Window,
542 logical_size: LogicalSize<f64>,
543 ) -> LogicalSize<f64> {
544 match self.hidpi_mode {
545 HiDpiMode::Default => logical_size,
546 _ => logical_size
548 .to_physical::<f64>(window.scale_factor())
549 .to_logical(self.hidpi_factor),
550 }
551 }
552
553 pub fn scale_pos_from_winit(
555 &self,
556 window: &Window,
557 logical_pos: LogicalPosition<f64>,
558 ) -> LogicalPosition<f64> {
559 match self.hidpi_mode {
560 HiDpiMode::Default => logical_pos,
561 _ => logical_pos
562 .to_physical::<f64>(window.scale_factor())
563 .to_logical(self.hidpi_factor),
564 }
565 }
566
567 pub fn scale_pos_for_winit(
569 &self,
570 window: &Window,
571 logical_pos: LogicalPosition<f64>,
572 ) -> LogicalPosition<f64> {
573 match self.hidpi_mode {
574 HiDpiMode::Default => logical_pos,
575 _ => logical_pos
576 .to_physical::<f64>(self.hidpi_factor)
577 .to_logical(window.scale_factor()),
578 }
579 }
580
581 pub fn create_window_attributes() -> WindowAttributes {
583 WindowAttributes::default()
584 .with_title("Dear ImGui Window")
585 .with_inner_size(LogicalSize::new(1024.0, 768.0))
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592 use crate::test_util::test_sync::lock_context;
593
594 #[test]
595 fn test_hidpi_mode_default() {
596 assert_eq!(HiDpiMode::default(), HiDpiMode::Default);
597 }
598
599 #[test]
600 fn test_platform_creation() {
601 let _guard = lock_context();
602 let mut ctx = Context::create();
603 let platform = WinitPlatform::new(&mut ctx);
604
605 assert_eq!(platform.hidpi_mode, HiDpiMode::Default);
606 assert_eq!(platform.hidpi_factor, 1.0);
607 assert_eq!(platform.cursor_cache, None);
608 assert!(!platform.ime_enabled);
609 }
610
611 #[test]
612 fn test_hidpi_mode_setting() {
613 let _guard = lock_context();
614 let mut ctx = Context::create();
615 let mut platform = WinitPlatform::new(&mut ctx);
616
617 platform.set_hidpi_mode(HiDpiMode::Locked(2.0));
618 assert_eq!(platform.hidpi_mode, HiDpiMode::Locked(2.0));
619
620 platform.set_hidpi_mode(HiDpiMode::Rounded);
621 assert_eq!(platform.hidpi_mode, HiDpiMode::Rounded);
622 }
623
624 #[test]
625 fn test_ime_callback_ownership_detection() {
626 unsafe extern "C" fn other_ime_callback(
627 _ctx: *mut dear_imgui_rs::sys::ImGuiContext,
628 _viewport: *mut dear_imgui_rs::sys::ImGuiViewport,
629 _data: *mut dear_imgui_rs::sys::ImGuiPlatformImeData,
630 ) {
631 }
632
633 assert!(is_winit_set_ime_data(Some(imgui_winit_set_ime_data)));
634 assert!(!is_winit_set_ime_data(Some(other_ime_callback)));
635 assert!(!is_winit_set_ime_data(None));
636 }
637
638 #[test]
639 fn ime_platform_io_lookup_uses_passed_context() {
640 let _guard = lock_context();
641
642 let ctx_a = Context::create();
643 let raw_a = ctx_a.as_raw();
644 let marker_a = std::ptr::NonNull::<Window>::dangling().as_ptr();
645 unsafe {
646 let platform_io_a = dear_imgui_rs::sys::igGetPlatformIO_ContextPtr(raw_a);
647 (*platform_io_a).Platform_ImeUserData = marker_a.cast();
648 dear_imgui_rs::sys::igSetCurrentContext(std::ptr::null_mut());
649 }
650
651 let ctx_b = Context::create();
652 let raw_b = ctx_b.as_raw();
653 let marker_b = std::ptr::NonNull::<u8>::dangling().as_ptr();
654 unsafe {
655 let platform_io_b = dear_imgui_rs::sys::igGetPlatformIO_ContextPtr(raw_b);
656 (*platform_io_b).Platform_ImeUserData = marker_b.cast();
657
658 let selected = platform_io_for_ime_context(raw_a);
659 assert_eq!((*selected).Platform_ImeUserData, marker_a.cast());
660 assert_ne!((*selected).Platform_ImeUserData, marker_b.cast());
661
662 dear_imgui_rs::sys::igSetCurrentContext(raw_a);
663 }
664 drop(ctx_a);
665 unsafe {
666 dear_imgui_rs::sys::igSetCurrentContext(raw_b);
667 }
668 drop(ctx_b);
669 }
670
671 #[test]
672 fn test_window_attributes_creation() {
673 let attrs = WinitPlatform::create_window_attributes();
674 let _ = attrs;
676 }
677}