Skip to main content

egui_sdl3/
state.rs

1//! State management for egui + SDL3 integration.
2//!
3//! This module provides [`State`], which is responsible for translating
4//! SDL3 events and window data into egui input/output.
5//! Each SDL3 window (viewport) should have its own [`State`] instance.
6//!
7//!  # Usage
8//! Typical usage is to:
9//! 1. Create a [`State`] from an SDL3 [`Window`]
10//! 2. Call [`State::on_event`] for every SDL3 event
11//! 3. Retrieve input via [`State::take_egui_input`] before each frame
12//! 4. Run your egui UI code
13//! 5. Apply [`egui::PlatformOutput`] (cursor, clipboard, etc.)
14//!
15use egui::{Key, Modifiers, MouseWheelUnit, PointerButton, Pos2, Rect};
16use sdl3::event::WindowEvent;
17use sdl3::keyboard::Keycode;
18use sdl3::keyboard::Mod;
19use sdl3::keyboard::Scancode;
20use sdl3::mouse::{Cursor, MouseButton, SystemCursor};
21use sdl3::video::Window;
22
23#[must_use]
24#[derive(Clone, Copy, Debug, Default)]
25pub struct EventResponse {
26    /// If true, egui consumed this event, i.e. wants exclusive use of this event
27    /// (e.g. a mouse click on an egui window, or entering text into a text field).
28    ///
29    /// For instance, if you use egui for a game, you should only
30    /// pass on the events to your game when [`Self::consumed`] is `false`.
31    ///
32    /// Note that egui uses `tab` to move focus between elements, so this will always be `true` for tabs.
33    pub consumed: bool,
34
35    /// Do we need an egui refresh because of this event?
36    pub repaint: bool,
37}
38
39/// Handles the integration between egui and a sdl3 Window.
40///
41/// Instantiate one of these per viewport/window.
42pub struct State {
43    egui_ctx: egui::Context,
44    egui_input: egui::RawInput,
45    start_time: std::time::Instant,
46    viewport_id: egui::ViewportId,
47    pointer_pos_in_points: Option<egui::Pos2>,
48    /// The finger currently driving synthesized pointer events. The first finger
49    /// down becomes the pointer; extra fingers only feed multi-touch gestures so
50    /// they don't emit phantom clicks. Cleared on its up/cancel.
51    pointer_touch_id: Option<u64>,
52    current_cursor: Option<CurrentCursor>,
53    clipboard: sdl3::clipboard::ClipboardUtil,
54    window_size: (u32, u32), // cache value and update on events
55    // Drawable size in pixels, cached and refreshed on resize. `take_egui_input`
56    // divides it by the *current* zoom each frame to rebuild `screen_rect`, so a
57    // `set_zoom_factor` after construction is reflected without waiting for a
58    // resize event (otherwise the UI lays out for the wrong rect until rotation).
59    drawable_size: (u32, u32),
60}
61
62/// Represents currently active cursor.
63///
64/// Contains egui icon and allocation of sdl3 cursor.
65struct CurrentCursor {
66    icon: egui::CursorIcon,
67    cursor: Option<sdl3::mouse::Cursor>, // keep reference
68}
69
70impl State {
71    pub fn new(window: &Window, egui_ctx: egui::Context, viewport_id: egui::ViewportId) -> Self {
72        let screen_rect = new_screen_rect(&egui_ctx, window);
73        let mut egui_input = egui::RawInput {
74            focused: false, // event will tell us when we have focus
75            screen_rect,
76            ..Default::default()
77        };
78        egui_input
79            .viewports
80            .entry(egui::ViewportId::ROOT)
81            .or_default()
82            .native_pixels_per_point = Some(native_pixels_per_point(window));
83        let clipboard = window.subsystem().clipboard();
84        let window_size = window.size();
85        let drawable_size = window.size_in_pixels();
86
87        State {
88            egui_ctx,
89            viewport_id,
90            clipboard,
91            start_time: std::time::Instant::now(),
92            egui_input,
93            pointer_pos_in_points: None,
94            pointer_touch_id: None,
95            current_cursor: None,
96            window_size,
97            drawable_size,
98        }
99    }
100
101    #[inline]
102    pub fn get_window_size(&self) -> (u32, u32) {
103        self.window_size
104    }
105
106    /// Drawable (physical pixel) size, cached and refreshed on resize.
107    ///
108    /// This is the size a GPU backend should use for its viewport/framebuffer:
109    /// on HiDPI it differs from [`Self::get_window_size`] (logical points).
110    #[inline]
111    pub fn get_drawable_size(&self) -> (u32, u32) {
112        self.drawable_size
113    }
114
115    #[inline]
116    pub fn get_pointer_pos_in_points(&self) -> Option<egui::Pos2> {
117        self.pointer_pos_in_points
118    }
119
120    #[inline]
121    pub fn set_theme(&mut self, theme: egui::Theme) {
122        self.egui_input.system_theme.replace(theme);
123    }
124
125    /// Call with the output given by `egui`.
126    ///
127    /// This will, if needed:
128    /// * update the cursor
129    /// * copy text to the clipboard
130    /// * open any clicked urls
131    /// *
132    #[inline]
133    pub fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
134        for command in &platform_output.commands {
135            match command {
136                egui::OutputCommand::CopyText(text) => {
137                    let result = self.clipboard.set_clipboard_text(text);
138
139                    if result.is_err() {
140                        log::warn!("Failed to set copied text to clipboard");
141                    }
142                }
143                egui::OutputCommand::CopyImage(_color_image) => {
144                    log::warn!("CopyImage is not supported")
145                }
146                egui::OutputCommand::OpenUrl(_url) => {
147                    #[cfg(feature = "links")]
148                    if let Err(err) = webbrowser::open(&_url.url) {
149                        log::warn!("Failed to open url: {}", err);
150                    }
151
152                    #[cfg(not(feature = "links"))]
153                    {
154                        log::warn!("Cannot open url - feature \"links\" not enabled.");
155                    }
156                }
157            }
158        }
159
160        self.set_cursor_icon(platform_output.cursor_icon);
161    }
162
163    /// Prepare for a new frame by extracting the accumulated input,
164    ///
165    /// as well as setting [the time](egui::RawInput::time)
166    ///
167    /// You need to set [`egui::RawInput::viewports`] yourself though.
168    #[inline]
169    pub fn take_egui_input(&mut self) -> egui::RawInput {
170        self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
171        // Tell egui which viewport is now active:
172        self.egui_input.viewport_id = self.viewport_id;
173
174        // Rebuild `screen_rect` from the cached drawable size and the *current*
175        // zoom factor every frame. `screen_rect` is in points (pixels / ppp), and
176        // ppp depends on `zoom_factor`, which the embedder can change after `new`
177        // (e.g. a HiDPI `set_zoom_factor` on Android). Recomputing here keeps the
178        // layout rect correct without needing a resize event to trigger it.
179        let native_ppp = self
180            .egui_input
181            .viewports
182            .get(&self.viewport_id)
183            .and_then(|v| v.native_pixels_per_point)
184            .unwrap_or(1.0);
185        let ppp = self.egui_ctx.zoom_factor() * native_ppp;
186        if ppp > 0.0 {
187            let points = egui::vec2(self.drawable_size.0 as f32, self.drawable_size.1 as f32) / ppp;
188            if points.x > 0.0 && points.y > 0.0 {
189                self.egui_input.screen_rect =
190                    Some(egui::Rect::from_min_size(egui::Pos2::ZERO, points));
191            }
192        }
193
194        self.egui_input.take()
195    }
196
197    /// Pixels-per-point for mapping window pixel coordinates to egui points.
198    ///
199    /// Built from the *cached* native pixels-per-point — the same value
200    /// [`Self::take_egui_input`] lays `screen_rect` out with (stored on the
201    /// viewport by [`Self::on_size_chage`]) — times the *live* zoom factor.
202    /// Reading the stored native ppp instead of re-querying the window keeps
203    /// pointer coordinates on the same basis as the layout and avoids an SDL
204    /// size query on every pointer event; reading `zoom_factor` live keeps
205    /// `set_zoom_factor`/ctrl-wheel zoom reflected within the same frame.
206    #[inline]
207    fn cached_pixels_per_point(&self) -> f32 {
208        let native_ppp = self
209            .egui_input
210            .viewports
211            .get(&self.viewport_id)
212            .and_then(|v| v.native_pixels_per_point)
213            .unwrap_or(1.0);
214        self.egui_ctx.zoom_factor() * native_ppp
215    }
216
217    /// Convert window pixel coordinates to egui points via [`Self::cached_pixels_per_point`].
218    #[inline]
219    fn pos_in_points(&self, x: f32, y: f32) -> egui::Pos2 {
220        let ppp = self.cached_pixels_per_point();
221        if ppp > 0.0 {
222            egui::pos2(x, y) / ppp
223        } else {
224            egui::pos2(x, y)
225        }
226    }
227
228    /// Call this when there is a new event.
229    ///
230    /// The result can be extracted with [`Self::take_egui_input`].
231    pub fn on_event(
232        &mut self,
233        window: &sdl3::video::Window,
234        event: &sdl3::event::Event,
235    ) -> EventResponse {
236        use sdl3::event::Event::*;
237        match event {
238            Window { win_event, .. } => self.on_window_event(*win_event, window),
239            MouseButtonDown {
240                mouse_btn, x, y, ..
241            } => self.on_mouse_button_event(*mouse_btn, true, *x, *y),
242            MouseButtonUp {
243                mouse_btn, x, y, ..
244            } => self.on_mouse_button_event(*mouse_btn, false, *x, *y),
245            MouseMotion { x, y, .. } => {
246                let pos = self.pos_in_points(*x, *y);
247                self.pointer_pos_in_points = Some(pos);
248                self.egui_input.events.push(egui::Event::PointerMoved(pos));
249                EventResponse {
250                    repaint: true,
251                    consumed: self.egui_ctx.egui_is_using_pointer(),
252                }
253            }
254            MouseWheel { x, y, .. } => {
255                let dx = *x;
256                let dy = *y;
257
258                if self.egui_input.modifiers.command {
259                    // zoom
260                    let delta = (dy / 125.0).exp();
261                    self.egui_input.events.push(egui::Event::Zoom(delta));
262                } else if self.egui_input.modifiers.shift {
263                    // horizontal scroll
264                    self.egui_input.events.push(egui::Event::MouseWheel {
265                        unit: MouseWheelUnit::Line,
266                        delta: egui::vec2(dx + dy, 0.0),
267                        phase: egui::TouchPhase::Move,
268                        modifiers: self.egui_input.modifiers,
269                    });
270                } else {
271                    // regular scroll
272                    self.egui_input.events.push(egui::Event::MouseWheel {
273                        unit: MouseWheelUnit::Line,
274                        delta: egui::vec2(dx, dy),
275                        phase: egui::TouchPhase::Move,
276                        modifiers: self.egui_input.modifiers,
277                    });
278                }
279                EventResponse {
280                    repaint: true,
281                    consumed: self.egui_ctx.egui_wants_pointer_input(),
282                }
283            }
284            KeyUp {
285                keycode: Some(kc),
286                scancode: Some(sc),
287                keymod,
288                repeat,
289                ..
290            } => self.on_keyboard_event(*kc, *sc, *keymod, false, *repeat),
291            KeyDown {
292                keycode: Some(kc),
293                scancode: Some(sc),
294                keymod,
295                repeat,
296                ..
297            } => {
298                let resp = self.on_keyboard_event(*kc, *sc, *keymod, true, *repeat);
299
300                if self.egui_input.modifiers.command && *kc == Keycode::C {
301                    self.egui_input.events.push(egui::Event::Copy);
302                } else if self.egui_input.modifiers.command && *kc == Keycode::X {
303                    self.egui_input.events.push(egui::Event::Cut);
304                } else if self.egui_input.modifiers.command && *kc == Keycode::V {
305                    if let Ok(contents) = self.clipboard.clipboard_text() {
306                        self.egui_input.events.push(egui::Event::Text(contents));
307                    }
308                }
309
310                resp
311            }
312            TextInput { text, .. } => {
313                let mut resp = EventResponse {
314                    consumed: true,
315                    repaint: false,
316                };
317                if !text.is_empty() {
318                    // On some platforms we get here when the user presses Cmd-C (copy), ctrl-W, etc.
319                    // We need to ignore these characters that are side-effects of commands.
320                    let is_cmd = self.egui_input.modifiers.ctrl
321                        || self.egui_input.modifiers.command
322                        || self.egui_input.modifiers.mac_cmd;
323
324                    if !is_cmd {
325                        self.egui_input
326                            .events
327                            .push(egui::Event::Text(text.to_owned()));
328
329                        resp.repaint = true;
330                    }
331                }
332
333                resp
334            }
335            DropFile { filename, .. } => {
336                self.egui_input.dropped_files.push(egui::DroppedFile {
337                    path: Some(std::path::PathBuf::from(filename)),
338                    ..Default::default()
339                });
340                EventResponse {
341                    repaint: true,
342                    consumed: false,
343                }
344            }
345            FingerDown {
346                touch_id,
347                finger_id,
348                x,
349                y,
350                pressure,
351                ..
352            } => self.on_touch(TouchInfo {
353                phase: egui::TouchPhase::Start,
354                touch_id: *touch_id,
355                finger_id: *finger_id,
356                x: *x,
357                y: *y,
358                pressure: *pressure,
359            }),
360            FingerUp {
361                touch_id,
362                finger_id,
363                x,
364                y,
365                pressure,
366                ..
367            } => self.on_touch(TouchInfo {
368                phase: egui::TouchPhase::End,
369                touch_id: *touch_id,
370                finger_id: *finger_id,
371                x: *x,
372                y: *y,
373                pressure: *pressure,
374            }),
375            FingerMotion {
376                touch_id,
377                finger_id,
378                x,
379                y,
380                pressure,
381                ..
382            } => self.on_touch(TouchInfo {
383                phase: egui::TouchPhase::Move,
384                touch_id: *touch_id,
385                finger_id: *finger_id,
386                x: *x,
387                y: *y,
388                pressure: *pressure,
389            }),
390            _ => EventResponse::default(),
391        }
392    }
393
394    #[inline]
395    fn on_touch(&mut self, info: TouchInfo) -> EventResponse {
396        let consumed = match info.phase {
397            egui::TouchPhase::Start | egui::TouchPhase::End | egui::TouchPhase::Cancel => {
398                self.egui_ctx.egui_wants_pointer_input()
399            }
400            egui::TouchPhase::Move => self.egui_ctx.egui_is_using_pointer(),
401        };
402
403        // SDL finger coordinates are normalized to the window (0.0..=1.0), unlike
404        // mouse events which arrive in window coordinates. Scale them to the
405        // window's pixel space so `pos_in_points` (which divides by ppp) yields
406        // the right egui position — otherwise every touch maps to ~(0,0). Use the
407        // *cached* window size so this numerator shares one size basis with the
408        // cached ppp denominator below; mixing a live size here with the cached
409        // ppp would misplace touches during a mid-resize transient.
410        let (win_w, win_h) = self.window_size;
411        let pixel_x = info.x * win_w as f32;
412        let pixel_y = info.y * win_h as f32;
413        let pos = self.pos_in_points(pixel_x, pixel_y);
414        self.egui_input.events.push(egui::Event::Touch {
415            device_id: egui::TouchDeviceId(info.touch_id),
416            id: egui::TouchId::from(info.finger_id),
417            phase: info.phase,
418            pos,
419            force: Some(info.pressure),
420        });
421
422        // egui's widget layer reacts to pointer events, not raw touch events, so
423        // synthesize a primary-button pointer stream from the first finger (the
424        // same thing egui-winit does). Without this, taps never click buttons on
425        // platforms where the windowing layer doesn't synthesize mouse events from
426        // touch (e.g. Android with SDL_TOUCH_MOUSE_EVENTS off). Extra fingers are
427        // left to the multi-touch event above so they don't emit phantom presses.
428        match info.phase {
429            egui::TouchPhase::Start if self.pointer_touch_id.is_none() => {
430                self.pointer_touch_id = Some(info.finger_id);
431                self.pointer_pos_in_points = Some(pos);
432                // Move to the press point first so egui has a current pointer pos.
433                self.egui_input.events.push(egui::Event::PointerMoved(pos));
434                self.egui_input.events.push(egui::Event::PointerButton {
435                    pos,
436                    button: egui::PointerButton::Primary,
437                    pressed: true,
438                    modifiers: self.egui_input.modifiers,
439                });
440            }
441            egui::TouchPhase::Move if self.pointer_touch_id == Some(info.finger_id) => {
442                self.pointer_pos_in_points = Some(pos);
443                self.egui_input.events.push(egui::Event::PointerMoved(pos));
444            }
445            egui::TouchPhase::End | egui::TouchPhase::Cancel
446                if self.pointer_touch_id == Some(info.finger_id) =>
447            {
448                self.pointer_touch_id = None;
449                if info.phase == egui::TouchPhase::End {
450                    self.egui_input.events.push(egui::Event::PointerButton {
451                        pos,
452                        button: egui::PointerButton::Primary,
453                        pressed: false,
454                        modifiers: self.egui_input.modifiers,
455                    });
456                }
457                // A touch pointer has no hover position once lifted; tell egui it's
458                // gone so the next press starts a fresh interaction.
459                self.egui_input.events.push(egui::Event::PointerGone);
460                self.pointer_pos_in_points = None;
461            }
462            _ => {}
463        }
464
465        EventResponse {
466            repaint: true,
467            consumed,
468        }
469    }
470
471    fn on_window_event(&mut self, event: WindowEvent, window: &Window) -> EventResponse {
472        match event {
473            WindowEvent::Minimized
474            | WindowEvent::Maximized
475            | WindowEvent::Resized(_, _)
476            | WindowEvent::PixelSizeChanged(_, _) => {
477                self.on_size_chage(window);
478                EventResponse {
479                    repaint: true,
480                    consumed: false,
481                }
482            }
483            WindowEvent::Shown
484            | WindowEvent::Hidden
485            | WindowEvent::Exposed
486            | WindowEvent::Moved(_, _)
487            | WindowEvent::Restored
488            | WindowEvent::MouseEnter
489            | WindowEvent::Occluded
490            | WindowEvent::CloseRequested => EventResponse {
491                consumed: false,
492                repaint: true,
493            },
494            WindowEvent::MouseLeave => {
495                self.pointer_pos_in_points = None;
496                self.egui_input.events.push(egui::Event::PointerGone);
497                EventResponse {
498                    repaint: true,
499                    consumed: false,
500                }
501            }
502            WindowEvent::FocusGained => {
503                self.egui_input.focused = true;
504                self.egui_input
505                    .events
506                    .push(egui::Event::WindowFocused(true));
507                EventResponse {
508                    repaint: true,
509                    consumed: false,
510                }
511            }
512            WindowEvent::FocusLost => {
513                self.egui_input.focused = false;
514                self.egui_input
515                    .events
516                    .push(egui::Event::WindowFocused(false));
517                EventResponse {
518                    repaint: true,
519                    consumed: false,
520                }
521            }
522            WindowEvent::HitTest(_, _)
523            | WindowEvent::ICCProfChanged
524            | WindowEvent::DisplayChanged(_)
525            | WindowEvent::None => EventResponse::default(),
526        }
527    }
528
529    fn on_mouse_button_event(
530        &mut self,
531        button: MouseButton,
532        pressed: bool,
533        x: f32,
534        y: f32,
535    ) -> EventResponse {
536        let Some(button) = into_egui_button(button) else {
537            return EventResponse::default();
538        };
539
540        let pos = self.pos_in_points(x, y);
541        self.pointer_pos_in_points = Some(pos);
542        self.egui_input.events.push(egui::Event::PointerButton {
543            pos,
544            button,
545            pressed,
546            modifiers: self.egui_input.modifiers,
547        });
548        EventResponse {
549            repaint: true,
550            consumed: self.egui_ctx.egui_wants_pointer_input(),
551        }
552    }
553
554    fn on_keyboard_event(
555        &mut self,
556        keycode: Keycode,
557        scancode: Scancode,
558        keymod: Mod,
559        pressed: bool,
560        repeat: bool,
561    ) -> EventResponse {
562        let Some(key) = into_egui_key(keycode) else {
563            return EventResponse::default();
564        };
565
566        self.egui_input.modifiers = into_egui_modifiers(keymod);
567        self.egui_input.events.push(egui::Event::Key {
568            key,
569            physical_key: into_egui_physical_key(scancode),
570            pressed,
571            repeat,
572            modifiers: self.egui_input.modifiers,
573        });
574        // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes.
575        let consumed = self.egui_ctx.egui_wants_keyboard_input() || key == Key::Tab;
576        EventResponse {
577            repaint: true,
578            consumed,
579        }
580    }
581
582    /// Refresh the cached window/drawable size and native pixels-per-point from
583    /// the live window. Call once per frame so an orientation change is reflected
584    /// even when the platform doesn't deliver a size-changed event (Android is
585    /// unreliable here — the surface can resize on rotation without an event).
586    #[inline]
587    pub fn sync_window_size(&mut self, window: &Window) {
588        self.on_size_chage(window);
589    }
590
591    #[inline]
592    fn on_size_chage(&mut self, window: &Window) {
593        self.window_size = window.size();
594        self.drawable_size = window.size_in_pixels();
595        self.egui_input.screen_rect = new_screen_rect(&self.egui_ctx, window);
596        self.egui_input
597            .viewports
598            .entry(self.viewport_id)
599            .or_default()
600            .native_pixels_per_point = Some(native_pixels_per_point(window));
601    }
602
603    #[inline]
604    fn set_cursor_icon(&mut self, cursor_icon: egui::CursorIcon) {
605        if let Some(cursor) = &self.current_cursor {
606            if cursor.icon == cursor_icon {
607                return;
608            }
609        }
610
611        if self.pointer_pos_in_points.is_some() {
612            let system_cursor = into_sdl3_cursor(cursor_icon);
613            let mut current_cursor = CurrentCursor {
614                icon: cursor_icon,
615                cursor: None,
616            };
617
618            match Cursor::from_system(system_cursor) {
619                Ok(cursor) => {
620                    cursor.set();
621                    current_cursor.cursor = Some(cursor);
622                }
623                Err(e) => {
624                    log::warn!("Failed to set cursor: {e}")
625                }
626            }
627            self.current_cursor.replace(current_cursor);
628        } else {
629            self.current_cursor = None;
630        }
631    }
632}
633
634#[inline]
635pub fn poiner_pos_in_points(
636    egui_ctx: &egui::Context,
637    window: &Window,
638    x: f32,
639    y: f32,
640) -> egui::Pos2 {
641    let pixels_per_point = pixels_per_point(egui_ctx, window);
642    egui::pos2(x, y) / pixels_per_point
643}
644
645#[inline]
646pub fn into_egui_modifiers(m: Mod) -> Modifiers {
647    let mut mods = Modifiers::NONE;
648
649    if m.intersects(Mod::LCTRLMOD | Mod::RCTRLMOD) {
650        mods.ctrl = true;
651        mods.command = true;
652    }
653
654    if m.intersects(Mod::LSHIFTMOD | Mod::RSHIFTMOD) {
655        mods.shift = true;
656    }
657
658    if m.intersects(Mod::LALTMOD | Mod::RALTMOD) {
659        mods.alt = true;
660    }
661
662    if m.intersects(Mod::LGUIMOD | Mod::RGUIMOD) {
663        mods.mac_cmd = true;
664        mods.command = true;
665    }
666
667    mods
668}
669
670#[inline]
671fn into_sdl3_cursor(cursor_icon: egui::CursorIcon) -> SystemCursor {
672    match cursor_icon {
673        egui::CursorIcon::Crosshair => SystemCursor::Crosshair,
674        egui::CursorIcon::Default => SystemCursor::Arrow,
675        egui::CursorIcon::Grab => SystemCursor::Hand,
676        egui::CursorIcon::Grabbing => SystemCursor::SizeAll,
677        egui::CursorIcon::Move => SystemCursor::SizeAll,
678        egui::CursorIcon::PointingHand => SystemCursor::Hand,
679        egui::CursorIcon::ResizeHorizontal => SystemCursor::SizeWE,
680        egui::CursorIcon::ResizeNeSw => SystemCursor::SizeNESW,
681        egui::CursorIcon::ResizeNwSe => SystemCursor::SizeNWSE,
682        egui::CursorIcon::ResizeVertical => SystemCursor::SizeNS,
683        egui::CursorIcon::Text => SystemCursor::IBeam,
684        egui::CursorIcon::NotAllowed | egui::CursorIcon::NoDrop => SystemCursor::No,
685        egui::CursorIcon::Wait => SystemCursor::Wait,
686        //There doesn't seem to be a suitable SDL equivalent...
687        _ => SystemCursor::Arrow,
688    }
689}
690
691#[inline]
692pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
693    let (width, height) = window.size_in_pixels();
694    egui::vec2(width as f32, height as f32)
695}
696
697#[inline]
698pub fn pixels_per_point(egui_ctx: &egui::Context, window: &Window) -> f32 {
699    let native_pixels_per_point = native_pixels_per_point(window);
700    let egui_zoom_factor = egui_ctx.zoom_factor();
701    egui_zoom_factor * native_pixels_per_point
702}
703
704#[inline]
705fn new_screen_rect(egui_ctx: &egui::Context, window: &Window) -> Option<Rect> {
706    let screen_size_in_pixels = screen_size_in_pixels(window);
707    let screen_size_in_points = screen_size_in_pixels / pixels_per_point(egui_ctx, window);
708
709    (screen_size_in_points.x > 0.0 && screen_size_in_points.y > 0.0)
710        .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points))
711}
712
713#[inline]
714pub fn native_pixels_per_point(window: &Window) -> f32 {
715    let (win_w, win_h) = window.size();
716    let (draw_w, _draw_h) = window.size_in_pixels();
717
718    if win_w > 0 && win_h > 0 {
719        draw_w as f32 / win_w as f32
720    } else {
721        1.0
722    }
723}
724
725#[inline]
726pub fn into_egui_button(btn: MouseButton) -> Option<PointerButton> {
727    match btn {
728        MouseButton::Left => Some(egui::PointerButton::Primary),
729        MouseButton::Middle => Some(egui::PointerButton::Middle),
730        MouseButton::Right => Some(egui::PointerButton::Secondary),
731        MouseButton::Unknown => None,
732        MouseButton::X1 => Some(egui::PointerButton::Extra1),
733        MouseButton::X2 => Some(egui::PointerButton::Extra2),
734    }
735}
736
737pub fn into_egui_key(key: Keycode) -> Option<Key> {
738    Some(match key {
739        Keycode::Left => Key::ArrowLeft,
740        Keycode::Up => Key::ArrowUp,
741        Keycode::Right => Key::ArrowRight,
742        Keycode::Down => Key::ArrowDown,
743
744        Keycode::Escape => Key::Escape,
745        Keycode::Tab => Key::Tab,
746        Keycode::Backspace => Key::Backspace,
747        Keycode::Space => Key::Space,
748        Keycode::Return => Key::Enter,
749
750        Keycode::Insert => Key::Insert,
751        Keycode::Home => Key::Home,
752        Keycode::Delete => Key::Delete,
753        Keycode::End => Key::End,
754        Keycode::PageDown => Key::PageDown,
755        Keycode::PageUp => Key::PageUp,
756
757        Keycode::Kp0 | Keycode::_0 => Key::Num0,
758        Keycode::Kp1 | Keycode::_1 => Key::Num1,
759        Keycode::Kp2 | Keycode::_2 => Key::Num2,
760        Keycode::Kp3 | Keycode::_3 => Key::Num3,
761        Keycode::Kp4 | Keycode::_4 => Key::Num4,
762        Keycode::Kp5 | Keycode::_5 => Key::Num5,
763        Keycode::Kp6 | Keycode::_6 => Key::Num6,
764        Keycode::Kp7 | Keycode::_7 => Key::Num7,
765        Keycode::Kp8 | Keycode::_8 => Key::Num8,
766        Keycode::Kp9 | Keycode::_9 => Key::Num9,
767
768        Keycode::A => Key::A,
769        Keycode::B => Key::B,
770        Keycode::C => Key::C,
771        Keycode::D => Key::D,
772        Keycode::E => Key::E,
773        Keycode::F => Key::F,
774        Keycode::G => Key::G,
775        Keycode::H => Key::H,
776        Keycode::I => Key::I,
777        Keycode::J => Key::J,
778        Keycode::K => Key::K,
779        Keycode::L => Key::L,
780        Keycode::M => Key::M,
781        Keycode::N => Key::N,
782        Keycode::O => Key::O,
783        Keycode::P => Key::P,
784        Keycode::Q => Key::Q,
785        Keycode::R => Key::R,
786        Keycode::S => Key::S,
787        Keycode::T => Key::T,
788        Keycode::U => Key::U,
789        Keycode::V => Key::V,
790        Keycode::W => Key::W,
791        Keycode::X => Key::X,
792        Keycode::Y => Key::Y,
793        Keycode::Z => Key::Z,
794
795        Keycode::F1 => Key::F1,
796        Keycode::F2 => Key::F2,
797        Keycode::F3 => Key::F3,
798        Keycode::F4 => Key::F4,
799        Keycode::F5 => Key::F5,
800        Keycode::F6 => Key::F6,
801        Keycode::F7 => Key::F7,
802        Keycode::F8 => Key::F8,
803        Keycode::F9 => Key::F9,
804        Keycode::F10 => Key::F10,
805        Keycode::F11 => Key::F11,
806        Keycode::F12 => Key::F12,
807
808        Keycode::Minus => Key::Minus,
809        Keycode::Equals => Key::Equals,
810        Keycode::Semicolon => Key::Semicolon,
811        Keycode::Comma => Key::Comma,
812        Keycode::Period => Key::Period,
813        Keycode::Slash => Key::Slash,
814        Keycode::Backslash => Key::Backslash,
815
816        _ => {
817            return None;
818        }
819    })
820}
821
822pub fn into_egui_physical_key(scancode: Scancode) -> Option<Key> {
823    match scancode {
824        Scancode::A => Some(Key::A),
825        Scancode::B => Some(Key::B),
826        Scancode::C => Some(Key::C),
827        Scancode::D => Some(Key::D),
828        Scancode::E => Some(Key::E),
829        Scancode::F => Some(Key::F),
830        Scancode::G => Some(Key::G),
831        Scancode::H => Some(Key::H),
832        Scancode::I => Some(Key::I),
833        Scancode::J => Some(Key::J),
834        Scancode::K => Some(Key::K),
835        Scancode::L => Some(Key::L),
836        Scancode::M => Some(Key::M),
837        Scancode::N => Some(Key::N),
838        Scancode::O => Some(Key::O),
839        Scancode::P => Some(Key::P),
840        Scancode::Q => Some(Key::Q),
841        Scancode::R => Some(Key::R),
842        Scancode::S => Some(Key::S),
843        Scancode::T => Some(Key::T),
844        Scancode::U => Some(Key::U),
845        Scancode::V => Some(Key::V),
846        Scancode::W => Some(Key::W),
847        Scancode::X => Some(Key::X),
848        Scancode::Y => Some(Key::Y),
849        Scancode::Z => Some(Key::Z),
850
851        Scancode::_0 => Some(Key::Num0),
852        Scancode::_1 => Some(Key::Num1),
853        Scancode::_2 => Some(Key::Num2),
854        Scancode::_3 => Some(Key::Num3),
855        Scancode::_4 => Some(Key::Num4),
856        Scancode::_5 => Some(Key::Num5),
857        Scancode::_6 => Some(Key::Num6),
858        Scancode::_7 => Some(Key::Num7),
859        Scancode::_8 => Some(Key::Num8),
860        Scancode::_9 => Some(Key::Num9),
861
862        Scancode::F1 => Some(Key::F1),
863        Scancode::F2 => Some(Key::F2),
864        Scancode::F3 => Some(Key::F3),
865        Scancode::F4 => Some(Key::F4),
866        Scancode::F5 => Some(Key::F5),
867        Scancode::F6 => Some(Key::F6),
868        Scancode::F7 => Some(Key::F7),
869        Scancode::F8 => Some(Key::F8),
870        Scancode::F9 => Some(Key::F9),
871        Scancode::F10 => Some(Key::F10),
872        Scancode::F11 => Some(Key::F11),
873        Scancode::F12 => Some(Key::F12),
874
875        Scancode::Up => Some(Key::ArrowUp),
876        Scancode::Down => Some(Key::ArrowDown),
877        Scancode::Left => Some(Key::ArrowLeft),
878        Scancode::Right => Some(Key::ArrowRight),
879
880        Scancode::Return => Some(Key::Enter),
881        Scancode::Escape => Some(Key::Escape),
882        Scancode::Backspace => Some(Key::Backspace),
883        Scancode::Tab => Some(Key::Tab),
884        Scancode::Space => Some(Key::Space),
885
886        _ => None,
887    }
888}
889
890struct TouchInfo {
891    phase: egui::TouchPhase,
892    touch_id: u64,
893    finger_id: u64,
894    x: f32,
895    y: f32,
896    pressure: f32,
897}