Skip to main content

egui_sdl2/
state.rs

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