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    current_cursor: Option<CurrentCursor>,
49    clipboard: sdl2::clipboard::ClipboardUtil,
50    window_size: (u32, u32), // cache value and update on events
51}
52
53/// Represents currently active cursor.
54///
55/// Contains egui icon and allocation of sdl2 cursor.
56struct CurrentCursor {
57    icon: egui::CursorIcon,
58    cursor: Option<sdl2::mouse::Cursor>, // keep reference
59}
60
61impl State {
62    pub fn new(window: &Window, egui_ctx: egui::Context, viewport_id: egui::ViewportId) -> Self {
63        let screen_rect = new_screen_rect(&egui_ctx, window);
64        let mut egui_input = egui::RawInput {
65            focused: false, // event will tell us when we have focus
66            screen_rect,
67            ..Default::default()
68        };
69        egui_input
70            .viewports
71            .entry(egui::ViewportId::ROOT)
72            .or_default()
73            .native_pixels_per_point = Some(native_pixels_per_point(window));
74        let clipboard = window.subsystem().clipboard();
75        let window_size = window.size();
76
77        State {
78            egui_ctx,
79            viewport_id,
80            clipboard,
81            start_time: std::time::Instant::now(),
82            egui_input,
83            pointer_pos_in_points: None,
84            current_cursor: None,
85            window_size,
86        }
87    }
88
89    #[inline]
90    pub fn get_window_size(&self) -> (u32, u32) {
91        self.window_size
92    }
93
94    #[inline]
95    pub fn get_pointer_pos_in_points(&self) -> Option<egui::Pos2> {
96        self.pointer_pos_in_points
97    }
98
99    #[inline]
100    pub fn set_theme(&mut self, theme: egui::Theme) {
101        self.egui_input.system_theme.replace(theme);
102    }
103
104    /// Call with the output given by `egui`.
105    ///
106    /// This will, if needed:
107    /// * update the cursor
108    /// * copy text to the clipboard
109    /// * open any clicked urls
110    /// *
111    #[inline]
112    pub fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
113        for command in &platform_output.commands {
114            match command {
115                egui::OutputCommand::CopyText(text) => {
116                    let result = self.clipboard.set_clipboard_text(text);
117
118                    if result.is_err() {
119                        log::warn!("Failed to set copied text to clipboard");
120                    }
121                }
122                egui::OutputCommand::CopyImage(_color_image) => {
123                    log::warn!("CopyImage is not supported")
124                }
125                egui::OutputCommand::OpenUrl(_url) => {
126                    #[cfg(feature = "links")]
127                    if let Err(err) = webbrowser::open(&_url.url) {
128                        log::warn!("Failed to open url: {}", err);
129                    }
130
131                    #[cfg(not(feature = "links"))]
132                    {
133                        log::warn!("Cannot open url - feature \"links\" not enabled.");
134                    }
135                }
136            }
137        }
138
139        self.set_cursor_icon(platform_output.cursor_icon);
140    }
141
142    /// Prepare for a new frame by extracting the accumulated input,
143    ///
144    /// as well as setting [the time](egui::RawInput::time)
145    ///
146    /// You need to set [`egui::RawInput::viewports`] yourself though.
147    #[inline]
148    pub fn take_egui_input(&mut self) -> egui::RawInput {
149        self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
150        // Tell egui which viewport is now active:
151        self.egui_input.viewport_id = self.viewport_id;
152
153        self.egui_input.take()
154    }
155
156    /// Call this when there is a new event.
157    ///
158    /// The result can be extracted with [`Self::take_egui_input`].
159    pub fn on_event(
160        &mut self,
161        window: &sdl2::video::Window,
162        event: &sdl2::event::Event,
163    ) -> EventResponse {
164        use sdl2::event::Event::*;
165        match event {
166            Window { win_event, .. } => self.on_window_event(*win_event, window),
167            MouseButtonDown {
168                mouse_btn, x, y, ..
169            } => self.on_mouse_button_event(window, *mouse_btn, true, *x, *y),
170            MouseButtonUp {
171                mouse_btn, x, y, ..
172            } => self.on_mouse_button_event(window, *mouse_btn, false, *x, *y),
173            MouseMotion { x, y, .. } => {
174                let pos = poiner_pos_in_points(&self.egui_ctx, window, *x as f32, *y as f32);
175                self.pointer_pos_in_points = Some(pos);
176                self.egui_input.events.push(egui::Event::PointerMoved(pos));
177                EventResponse {
178                    repaint: true,
179                    consumed: self.egui_ctx.is_using_pointer(),
180                }
181            }
182            MouseWheel { x, y, .. } => {
183                let dx = *x as f32;
184                let dy = *y as f32;
185
186                if self.egui_input.modifiers.command {
187                    // zoom
188                    let delta = (dy / 125.0).exp();
189                    self.egui_input.events.push(egui::Event::Zoom(delta));
190                } else if self.egui_input.modifiers.shift {
191                    // horizontal scroll
192                    self.egui_input.events.push(egui::Event::MouseWheel {
193                        unit: MouseWheelUnit::Line,
194                        delta: egui::vec2(dx + dy, 0.0),
195                        modifiers: self.egui_input.modifiers,
196                    });
197                } else {
198                    // regular scroll
199                    self.egui_input.events.push(egui::Event::MouseWheel {
200                        unit: MouseWheelUnit::Line,
201                        delta: egui::vec2(dx, dy),
202                        modifiers: self.egui_input.modifiers,
203                    });
204                }
205                EventResponse {
206                    repaint: true,
207                    consumed: self.egui_ctx.wants_pointer_input(),
208                }
209            }
210            KeyUp {
211                keycode: Some(kc),
212                scancode: Some(sc),
213                keymod,
214                repeat,
215                ..
216            } => self.on_keyboard_event(*kc, *sc, *keymod, false, *repeat),
217            KeyDown {
218                keycode: Some(kc),
219                scancode: Some(sc),
220                keymod,
221                repeat,
222                ..
223            } => {
224                let resp = self.on_keyboard_event(*kc, *sc, *keymod, true, *repeat);
225
226                if self.egui_input.modifiers.command && *kc == Keycode::C {
227                    self.egui_input.events.push(egui::Event::Copy);
228                } else if self.egui_input.modifiers.command && *kc == Keycode::X {
229                    self.egui_input.events.push(egui::Event::Cut);
230                } else if self.egui_input.modifiers.command && *kc == Keycode::V {
231                    if let Ok(contents) = self.clipboard.clipboard_text() {
232                        self.egui_input.events.push(egui::Event::Text(contents));
233                    }
234                }
235
236                resp
237            }
238            TextInput { text, .. } => {
239                let mut resp = EventResponse {
240                    consumed: true,
241                    repaint: false,
242                };
243                if !text.is_empty() {
244                    // On some platforms we get here when the user presses Cmd-C (copy), ctrl-W, etc.
245                    // We need to ignore these characters that are side-effects of commands.
246                    let is_cmd = self.egui_input.modifiers.ctrl
247                        || self.egui_input.modifiers.command
248                        || self.egui_input.modifiers.mac_cmd;
249
250                    if !is_cmd {
251                        self.egui_input
252                            .events
253                            .push(egui::Event::Text(text.to_owned()));
254
255                        resp.repaint = true;
256                    }
257                }
258
259                resp
260            }
261            DropFile { filename, .. } => {
262                self.egui_input.dropped_files.push(egui::DroppedFile {
263                    path: Some(std::path::PathBuf::from(filename)),
264                    ..Default::default()
265                });
266                EventResponse {
267                    repaint: true,
268                    consumed: false,
269                }
270            }
271            FingerDown {
272                touch_id,
273                finger_id,
274                x,
275                y,
276                pressure,
277                ..
278            } => self.on_touch(
279                window,
280                TouchInfo {
281                    phase: egui::TouchPhase::Start,
282                    touch_id: *touch_id,
283                    finger_id: *finger_id,
284                    x: *x,
285                    y: *y,
286                    pressure: *pressure,
287                },
288            ),
289            FingerUp {
290                touch_id,
291                finger_id,
292                x,
293                y,
294                pressure,
295                ..
296            } => self.on_touch(
297                window,
298                TouchInfo {
299                    phase: egui::TouchPhase::End,
300                    touch_id: *touch_id,
301                    finger_id: *finger_id,
302                    x: *x,
303                    y: *y,
304                    pressure: *pressure,
305                },
306            ),
307            FingerMotion {
308                touch_id,
309                finger_id,
310                x,
311                y,
312                pressure,
313                ..
314            } => self.on_touch(
315                window,
316                TouchInfo {
317                    phase: egui::TouchPhase::Move,
318                    touch_id: *touch_id,
319                    finger_id: *finger_id,
320                    x: *x,
321                    y: *y,
322                    pressure: *pressure,
323                },
324            ),
325            _ => EventResponse::default(),
326        }
327    }
328
329    #[inline]
330    fn on_touch(&mut self, window: &Window, info: TouchInfo) -> EventResponse {
331        let consumed = match info.phase {
332            egui::TouchPhase::Start | egui::TouchPhase::End | egui::TouchPhase::Cancel => {
333                self.egui_ctx.wants_pointer_input()
334            }
335            egui::TouchPhase::Move => self.egui_ctx.is_using_pointer(),
336        };
337
338        let pos = poiner_pos_in_points(&self.egui_ctx, window, info.x, info.y);
339        self.pointer_pos_in_points = Some(pos);
340        self.egui_input.events.push(egui::Event::Touch {
341            device_id: egui::TouchDeviceId(info.touch_id as u64),
342            id: egui::TouchId::from(info.finger_id as u64),
343            phase: info.phase,
344            pos,
345            force: Some(info.pressure),
346        });
347
348        EventResponse {
349            repaint: true,
350            consumed,
351        }
352    }
353
354    fn on_window_event(&mut self, event: WindowEvent, window: &Window) -> EventResponse {
355        match event {
356            WindowEvent::Minimized
357            | WindowEvent::Maximized
358            | WindowEvent::Resized(_, _)
359            | WindowEvent::SizeChanged(_, _) => {
360                self.on_size_chage(window);
361                EventResponse {
362                    repaint: true,
363                    consumed: false,
364                }
365            }
366            WindowEvent::Shown
367            | WindowEvent::Hidden
368            | WindowEvent::Exposed
369            | WindowEvent::Moved(_, _)
370            | WindowEvent::Restored
371            | WindowEvent::Enter
372            | WindowEvent::Close => EventResponse {
373                consumed: false,
374                repaint: true,
375            },
376            WindowEvent::Leave => {
377                self.pointer_pos_in_points = None;
378                self.egui_input.events.push(egui::Event::PointerGone);
379                EventResponse {
380                    repaint: true,
381                    consumed: false,
382                }
383            }
384            WindowEvent::TakeFocus | WindowEvent::FocusGained => {
385                self.egui_input.focused = true;
386                self.egui_input
387                    .events
388                    .push(egui::Event::WindowFocused(true));
389                EventResponse {
390                    repaint: true,
391                    consumed: false,
392                }
393            }
394            WindowEvent::FocusLost => {
395                self.egui_input.focused = false;
396                self.egui_input
397                    .events
398                    .push(egui::Event::WindowFocused(false));
399                EventResponse {
400                    repaint: true,
401                    consumed: false,
402                }
403            }
404            WindowEvent::HitTest
405            | WindowEvent::ICCProfChanged
406            | WindowEvent::DisplayChanged(_)
407            | WindowEvent::None => EventResponse::default(),
408        }
409    }
410
411    fn on_mouse_button_event(
412        &mut self,
413        window: &Window,
414        button: MouseButton,
415        pressed: bool,
416        x: i32,
417        y: i32,
418    ) -> EventResponse {
419        let Some(button) = into_egui_button(button) else {
420            return EventResponse::default();
421        };
422
423        let pos = poiner_pos_in_points(&self.egui_ctx, window, x as f32, y as f32);
424        self.pointer_pos_in_points = Some(pos);
425        self.egui_input.events.push(egui::Event::PointerButton {
426            pos,
427            button,
428            pressed,
429            modifiers: self.egui_input.modifiers,
430        });
431        EventResponse {
432            repaint: true,
433            consumed: self.egui_ctx.wants_pointer_input(),
434        }
435    }
436
437    fn on_keyboard_event(
438        &mut self,
439        keycode: Keycode,
440        scancode: Scancode,
441        keymod: Mod,
442        pressed: bool,
443        repeat: bool,
444    ) -> EventResponse {
445        let Some(key) = into_egui_key(keycode) else {
446            return EventResponse::default();
447        };
448
449        self.egui_input.modifiers = into_egui_modifiers(keymod);
450        self.egui_input.events.push(egui::Event::Key {
451            key,
452            physical_key: into_egui_physical_key(scancode),
453            pressed,
454            repeat,
455            modifiers: self.egui_input.modifiers,
456        });
457        // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes.
458        let consumed = self.egui_ctx.wants_keyboard_input() || key == Key::Tab;
459        EventResponse {
460            repaint: true,
461            consumed,
462        }
463    }
464
465    #[inline]
466    fn on_size_chage(&mut self, window: &Window) {
467        self.window_size = window.size();
468        self.egui_input.screen_rect = new_screen_rect(&self.egui_ctx, window);
469        self.egui_input
470            .viewports
471            .entry(self.viewport_id)
472            .or_default()
473            .native_pixels_per_point = Some(native_pixels_per_point(window));
474    }
475
476    #[inline]
477    fn set_cursor_icon(&mut self, cursor_icon: egui::CursorIcon) {
478        if let Some(cursor) = &self.current_cursor {
479            if cursor.icon == cursor_icon {
480                return;
481            }
482        }
483
484        if self.pointer_pos_in_points.is_some() {
485            let system_cursor = into_sdl2_cursor(cursor_icon);
486            let mut current_cursor = CurrentCursor {
487                icon: cursor_icon,
488                cursor: None,
489            };
490
491            match Cursor::from_system(system_cursor) {
492                Ok(cursor) => {
493                    cursor.set();
494                    current_cursor.cursor = Some(cursor);
495                }
496                Err(e) => {
497                    log::warn!("Failed to set cursor: {e}")
498                }
499            }
500            self.current_cursor.replace(current_cursor);
501        } else {
502            self.current_cursor = None;
503        }
504    }
505}
506
507#[inline]
508pub fn poiner_pos_in_points(
509    egui_ctx: &egui::Context,
510    window: &Window,
511    x: f32,
512    y: f32,
513) -> egui::Pos2 {
514    let pixels_per_point = pixels_per_point(egui_ctx, window);
515    egui::pos2(x, y) / pixels_per_point
516}
517
518#[inline]
519pub fn into_egui_modifiers(m: Mod) -> Modifiers {
520    let mut mods = Modifiers::NONE;
521
522    if m.intersects(Mod::LCTRLMOD | Mod::RCTRLMOD) {
523        mods.ctrl = true;
524        mods.command = true;
525    }
526
527    if m.intersects(Mod::LSHIFTMOD | Mod::RSHIFTMOD) {
528        mods.shift = true;
529    }
530
531    if m.intersects(Mod::LALTMOD | Mod::RALTMOD) {
532        mods.alt = true;
533    }
534
535    if m.intersects(Mod::LGUIMOD | Mod::RGUIMOD) {
536        mods.mac_cmd = true;
537        mods.command = true;
538    }
539
540    mods
541}
542
543#[inline]
544fn into_sdl2_cursor(cursor_icon: egui::CursorIcon) -> SystemCursor {
545    match cursor_icon {
546        egui::CursorIcon::Crosshair => SystemCursor::Crosshair,
547        egui::CursorIcon::Default => SystemCursor::Arrow,
548        egui::CursorIcon::Grab => SystemCursor::Hand,
549        egui::CursorIcon::Grabbing => SystemCursor::SizeAll,
550        egui::CursorIcon::Move => SystemCursor::SizeAll,
551        egui::CursorIcon::PointingHand => SystemCursor::Hand,
552        egui::CursorIcon::ResizeHorizontal => SystemCursor::SizeWE,
553        egui::CursorIcon::ResizeNeSw => SystemCursor::SizeNESW,
554        egui::CursorIcon::ResizeNwSe => SystemCursor::SizeNWSE,
555        egui::CursorIcon::ResizeVertical => SystemCursor::SizeNS,
556        egui::CursorIcon::Text => SystemCursor::IBeam,
557        egui::CursorIcon::NotAllowed | egui::CursorIcon::NoDrop => SystemCursor::No,
558        egui::CursorIcon::Wait => SystemCursor::Wait,
559        //There doesn't seem to be a suitable SDL equivalent...
560        _ => SystemCursor::Arrow,
561    }
562}
563
564#[inline]
565pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
566    let (width, height) = window.drawable_size();
567    egui::vec2(width as f32, height as f32)
568}
569
570#[inline]
571pub fn pixels_per_point(egui_ctx: &egui::Context, window: &Window) -> f32 {
572    let native_pixels_per_point = native_pixels_per_point(window);
573    let egui_zoom_factor = egui_ctx.zoom_factor();
574    egui_zoom_factor * native_pixels_per_point
575}
576
577#[inline]
578fn new_screen_rect(egui_ctx: &egui::Context, window: &Window) -> Option<Rect> {
579    let screen_size_in_pixels = screen_size_in_pixels(window);
580    let screen_size_in_points = screen_size_in_pixels / pixels_per_point(egui_ctx, window);
581
582    (screen_size_in_points.x > 0.0 && screen_size_in_points.y > 0.0)
583        .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points))
584}
585
586#[inline]
587pub fn native_pixels_per_point(window: &Window) -> f32 {
588    let (win_w, win_h) = window.size();
589    let (draw_w, _draw_h) = window.drawable_size();
590
591    if win_w > 0 && win_h > 0 {
592        draw_w as f32 / win_w as f32
593    } else {
594        1.0
595    }
596}
597
598#[inline]
599pub fn into_egui_button(btn: MouseButton) -> Option<PointerButton> {
600    match btn {
601        MouseButton::Left => Some(egui::PointerButton::Primary),
602        MouseButton::Middle => Some(egui::PointerButton::Middle),
603        MouseButton::Right => Some(egui::PointerButton::Secondary),
604        MouseButton::Unknown => None,
605        MouseButton::X1 => Some(egui::PointerButton::Extra1),
606        MouseButton::X2 => Some(egui::PointerButton::Extra2),
607    }
608}
609
610pub fn into_egui_key(key: Keycode) -> Option<Key> {
611    Some(match key {
612        Keycode::Left => Key::ArrowLeft,
613        Keycode::Up => Key::ArrowUp,
614        Keycode::Right => Key::ArrowRight,
615        Keycode::Down => Key::ArrowDown,
616
617        Keycode::Escape => Key::Escape,
618        Keycode::Tab => Key::Tab,
619        Keycode::Backspace => Key::Backspace,
620        Keycode::Space => Key::Space,
621        Keycode::Return => Key::Enter,
622
623        Keycode::Insert => Key::Insert,
624        Keycode::Home => Key::Home,
625        Keycode::Delete => Key::Delete,
626        Keycode::End => Key::End,
627        Keycode::PageDown => Key::PageDown,
628        Keycode::PageUp => Key::PageUp,
629
630        Keycode::Kp0 | Keycode::Num0 => Key::Num0,
631        Keycode::Kp1 | Keycode::Num1 => Key::Num1,
632        Keycode::Kp2 | Keycode::Num2 => Key::Num2,
633        Keycode::Kp3 | Keycode::Num3 => Key::Num3,
634        Keycode::Kp4 | Keycode::Num4 => Key::Num4,
635        Keycode::Kp5 | Keycode::Num5 => Key::Num5,
636        Keycode::Kp6 | Keycode::Num6 => Key::Num6,
637        Keycode::Kp7 | Keycode::Num7 => Key::Num7,
638        Keycode::Kp8 | Keycode::Num8 => Key::Num8,
639        Keycode::Kp9 | Keycode::Num9 => Key::Num9,
640
641        Keycode::A => Key::A,
642        Keycode::B => Key::B,
643        Keycode::C => Key::C,
644        Keycode::D => Key::D,
645        Keycode::E => Key::E,
646        Keycode::F => Key::F,
647        Keycode::G => Key::G,
648        Keycode::H => Key::H,
649        Keycode::I => Key::I,
650        Keycode::J => Key::J,
651        Keycode::K => Key::K,
652        Keycode::L => Key::L,
653        Keycode::M => Key::M,
654        Keycode::N => Key::N,
655        Keycode::O => Key::O,
656        Keycode::P => Key::P,
657        Keycode::Q => Key::Q,
658        Keycode::R => Key::R,
659        Keycode::S => Key::S,
660        Keycode::T => Key::T,
661        Keycode::U => Key::U,
662        Keycode::V => Key::V,
663        Keycode::W => Key::W,
664        Keycode::X => Key::X,
665        Keycode::Y => Key::Y,
666        Keycode::Z => Key::Z,
667
668        Keycode::F1 => Key::F1,
669        Keycode::F2 => Key::F2,
670        Keycode::F3 => Key::F3,
671        Keycode::F4 => Key::F4,
672        Keycode::F5 => Key::F5,
673        Keycode::F6 => Key::F6,
674        Keycode::F7 => Key::F7,
675        Keycode::F8 => Key::F8,
676        Keycode::F9 => Key::F9,
677        Keycode::F10 => Key::F10,
678        Keycode::F11 => Key::F11,
679        Keycode::F12 => Key::F12,
680
681        Keycode::Minus => Key::Minus,
682        Keycode::Equals => Key::Equals,
683        Keycode::Semicolon => Key::Semicolon,
684        Keycode::Comma => Key::Comma,
685        Keycode::Period => Key::Period,
686        Keycode::Slash => Key::Slash,
687        Keycode::Backslash => Key::Backslash,
688
689        _ => {
690            return None;
691        }
692    })
693}
694
695pub fn into_egui_physical_key(scancode: Scancode) -> Option<Key> {
696    match scancode {
697        Scancode::A => Some(Key::A),
698        Scancode::B => Some(Key::B),
699        Scancode::C => Some(Key::C),
700        Scancode::D => Some(Key::D),
701        Scancode::E => Some(Key::E),
702        Scancode::F => Some(Key::F),
703        Scancode::G => Some(Key::G),
704        Scancode::H => Some(Key::H),
705        Scancode::I => Some(Key::I),
706        Scancode::J => Some(Key::J),
707        Scancode::K => Some(Key::K),
708        Scancode::L => Some(Key::L),
709        Scancode::M => Some(Key::M),
710        Scancode::N => Some(Key::N),
711        Scancode::O => Some(Key::O),
712        Scancode::P => Some(Key::P),
713        Scancode::Q => Some(Key::Q),
714        Scancode::R => Some(Key::R),
715        Scancode::S => Some(Key::S),
716        Scancode::T => Some(Key::T),
717        Scancode::U => Some(Key::U),
718        Scancode::V => Some(Key::V),
719        Scancode::W => Some(Key::W),
720        Scancode::X => Some(Key::X),
721        Scancode::Y => Some(Key::Y),
722        Scancode::Z => Some(Key::Z),
723
724        Scancode::Num0 => Some(Key::Num0),
725        Scancode::Num1 => Some(Key::Num1),
726        Scancode::Num2 => Some(Key::Num2),
727        Scancode::Num3 => Some(Key::Num3),
728        Scancode::Num4 => Some(Key::Num4),
729        Scancode::Num5 => Some(Key::Num5),
730        Scancode::Num6 => Some(Key::Num6),
731        Scancode::Num7 => Some(Key::Num7),
732        Scancode::Num8 => Some(Key::Num8),
733        Scancode::Num9 => Some(Key::Num9),
734
735        Scancode::F1 => Some(Key::F1),
736        Scancode::F2 => Some(Key::F2),
737        Scancode::F3 => Some(Key::F3),
738        Scancode::F4 => Some(Key::F4),
739        Scancode::F5 => Some(Key::F5),
740        Scancode::F6 => Some(Key::F6),
741        Scancode::F7 => Some(Key::F7),
742        Scancode::F8 => Some(Key::F8),
743        Scancode::F9 => Some(Key::F9),
744        Scancode::F10 => Some(Key::F10),
745        Scancode::F11 => Some(Key::F11),
746        Scancode::F12 => Some(Key::F12),
747
748        Scancode::Up => Some(Key::ArrowUp),
749        Scancode::Down => Some(Key::ArrowDown),
750        Scancode::Left => Some(Key::ArrowLeft),
751        Scancode::Right => Some(Key::ArrowRight),
752
753        Scancode::Return => Some(Key::Enter),
754        Scancode::Escape => Some(Key::Escape),
755        Scancode::Backspace => Some(Key::Backspace),
756        Scancode::Tab => Some(Key::Tab),
757        Scancode::Space => Some(Key::Space),
758
759        _ => None,
760    }
761}
762
763struct TouchInfo {
764    phase: egui::TouchPhase,
765    touch_id: i64,
766    finger_id: i64,
767    x: f32,
768    y: f32,
769    pressure: f32,
770}