egui_skia_sdl2_event/
lib.rs

1use std::time::Instant;
2
3use egui::{Key, Modifiers, PointerButton, Pos2, RawInput, Rect};
4use sdl2::event::WindowEvent;
5use sdl2::keyboard::Keycode;
6use sdl2::keyboard::Mod;
7use sdl2::mouse::{Cursor, MouseButton, SystemCursor};
8use sdl2::video::Window;
9use sdl2::VideoSubsystem;
10
11pub struct FusedCursor {
12    pub cursor: sdl2::mouse::Cursor,
13    pub icon: sdl2::mouse::SystemCursor,
14}
15
16impl FusedCursor {
17    pub fn new() -> Self {
18        Self {
19            cursor: sdl2::mouse::Cursor::from_system(sdl2::mouse::SystemCursor::Arrow).unwrap(),
20            icon: sdl2::mouse::SystemCursor::Arrow,
21        }
22    }
23}
24
25impl Default for FusedCursor {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31pub fn translate_virtual_key_code(key: sdl2::keyboard::Keycode) -> Option<egui::Key> {
32    use Keycode::*;
33
34    Some(match key {
35        Left => Key::ArrowLeft,
36        Up => Key::ArrowUp,
37        Right => Key::ArrowRight,
38        Down => Key::ArrowDown,
39
40        Escape => Key::Escape,
41        Tab => Key::Tab,
42        Backspace => Key::Backspace,
43        Space => Key::Space,
44        Return => Key::Enter,
45
46        Insert => Key::Insert,
47        Home => Key::Home,
48        Delete => Key::Delete,
49        End => Key::End,
50        PageDown => Key::PageDown,
51        PageUp => Key::PageUp,
52
53        Kp0 | Num0 => Key::Num0,
54        Kp1 | Num1 => Key::Num1,
55        Kp2 | Num2 => Key::Num2,
56        Kp3 | Num3 => Key::Num3,
57        Kp4 | Num4 => Key::Num4,
58        Kp5 | Num5 => Key::Num5,
59        Kp6 | Num6 => Key::Num6,
60        Kp7 | Num7 => Key::Num7,
61        Kp8 | Num8 => Key::Num8,
62        Kp9 | Num9 => Key::Num9,
63
64        A => Key::A,
65        B => Key::B,
66        C => Key::C,
67        D => Key::D,
68        E => Key::E,
69        F => Key::F,
70        G => Key::G,
71        H => Key::H,
72        I => Key::I,
73        J => Key::J,
74        K => Key::K,
75        L => Key::L,
76        M => Key::M,
77        N => Key::N,
78        O => Key::O,
79        P => Key::P,
80        Q => Key::Q,
81        R => Key::R,
82        S => Key::S,
83        T => Key::T,
84        U => Key::U,
85        V => Key::V,
86        W => Key::W,
87        X => Key::X,
88        Y => Key::Y,
89        Z => Key::Z,
90
91        _ => {
92            return None;
93        }
94    })
95}
96
97pub struct EguiSDL2State {
98    start_time: std::time::Instant,
99    raw_input: RawInput,
100    modifiers: Modifiers,
101    dpi_mode: DpiMode,
102    dpi_data: DpiData,
103    mouse_pointer_position: egui::Pos2,
104    fused_cursor: FusedCursor,
105}
106
107pub enum DpiMode {
108    /// The correct dpi value of the UI will be calculated automatically based on your system settings.
109    Auto,
110    /// Same as `DpiMode::Auto` but uses a custom scaling factor. Negative or zero scaling factor will result in panic.
111    AutoScaled(f32),
112    /// Uses custom dpi value directly.
113    Custom(f32),
114}
115
116impl DpiMode {
117    fn get_dpi(&self, window: &Window, video_subsystem: &VideoSubsystem) -> DpiData {
118        match &self {
119            DpiMode::Auto => get_auto_dpi(window, video_subsystem, 1.0),
120            DpiMode::AutoScaled(scale) => {
121                assert!(
122                    scale > &0.0,
123                    "AutoScaled scale value cannot be zero or negative!"
124                );
125                get_auto_dpi(window, video_subsystem, *scale)
126            }
127            DpiMode::Custom(c) => DpiData {
128                dpi: *c,
129                scale: 1.0,
130                apply_to_mouse_position: should_scale_input(),
131            },
132        }
133    }
134}
135
136struct DpiData {
137    dpi: f32,
138    scale: f32,
139    apply_to_mouse_position: bool,
140}
141
142impl DpiData {
143    fn scaled_dpi(&self) -> f32 {
144        self.dpi * self.scale
145    }
146}
147
148fn should_scale_input() -> bool {
149    cfg!(target_os = "linux")
150}
151
152fn get_auto_dpi(window: &Window, video_subsystem: &VideoSubsystem, scale: f32) -> DpiData {
153    if should_scale_input() {
154        // Unfortunately it won't work on linux because the allow_highdpi seems to be ignored,
155        // so we have to do this workaround
156        let dpi = video_subsystem
157            .display_dpi(window.display_index().unwrap_or(0))
158            .map(|(_, dpi, _)| dpi / 96.0)
159            .unwrap_or(1.0);
160
161        DpiData {
162            dpi: dpi,
163            scale: scale,
164            apply_to_mouse_position: true,
165        }
166    } else {
167        // This seems to be the recommended way to get the dpi
168        // https://wiki.libsdl.org/SDL2/SDL_GetDisplayDPI
169        DpiData {
170            dpi: (window.drawable_size().0 as f32 / window.size().0 as f32),
171            scale: scale,
172            apply_to_mouse_position: false,
173        }
174    }
175}
176
177impl EguiSDL2State {
178    pub fn sdl2_input_to_egui(&mut self, window: &sdl2::video::Window, event: &sdl2::event::Event) {
179        fn sdl_button_to_egui(btn: &MouseButton) -> Option<PointerButton> {
180            match btn {
181                MouseButton::Left => Some(egui::PointerButton::Primary),
182                MouseButton::Middle => Some(egui::PointerButton::Middle),
183                MouseButton::Right => Some(egui::PointerButton::Secondary),
184                _ => None,
185            }
186        }
187
188        use sdl2::event::Event::*;
189        if event.get_window_id() != Some(window.id()) {
190            return;
191        }
192        match event {
193            // handle when window Resized and SizeChanged.
194            Window { win_event, .. } => match win_event {
195                WindowEvent::Resized(x, y) | sdl2::event::WindowEvent::SizeChanged(x, y) => {
196                    self.update_screen_rect(window);
197                    self.dpi_data = self.dpi_mode.get_dpi(window, window.subsystem());
198                }
199                _ => (),
200            },
201            MouseButtonDown { mouse_btn, .. } => {
202                if let Some(pressed) = sdl_button_to_egui(mouse_btn) {
203                    self.raw_input.events.push(egui::Event::PointerButton {
204                        pos: self.mouse_pointer_position,
205                        button: pressed,
206                        pressed: true,
207                        modifiers: self.modifiers,
208                    });
209                }
210            }
211            MouseButtonUp { mouse_btn, .. } => {
212                if let Some(released) = sdl_button_to_egui(mouse_btn) {
213                    self.raw_input.events.push(egui::Event::PointerButton {
214                        pos: self.mouse_pointer_position,
215                        button: released,
216                        pressed: false,
217                        modifiers: self.modifiers,
218                    });
219                }
220            }
221
222            MouseMotion { x, y, .. } => {
223                let factor = if self.dpi_data.apply_to_mouse_position {
224                    self.dpi_data.scale
225                } else {
226                    1.0
227                };
228
229                self.mouse_pointer_position = egui::pos2(*x as f32 / factor, *y as f32 / factor);
230                self.raw_input
231                    .events
232                    .push(egui::Event::PointerMoved(self.mouse_pointer_position));
233            }
234
235            KeyUp {
236                keycode, keymod, repeat, ..
237            } => {
238                let key_code = match keycode {
239                    Some(key_code) => key_code,
240                    _ => return,
241                };
242                let key = match translate_virtual_key_code(*key_code) {
243                    Some(key) => key,
244                    _ => return,
245                };
246                self.modifiers = Modifiers {
247                    alt: (*keymod & Mod::LALTMOD == Mod::LALTMOD)
248                        || (*keymod & Mod::RALTMOD == Mod::RALTMOD),
249                    ctrl: (*keymod & Mod::LCTRLMOD == Mod::LCTRLMOD)
250                        || (*keymod & Mod::RCTRLMOD == Mod::RCTRLMOD),
251                    shift: (*keymod & Mod::LSHIFTMOD == Mod::LSHIFTMOD)
252                        || (*keymod & Mod::RSHIFTMOD == Mod::RSHIFTMOD),
253                    mac_cmd: *keymod & Mod::LGUIMOD == Mod::LGUIMOD,
254
255                    //TOD: Test on both windows and mac
256                    command: (*keymod & Mod::LCTRLMOD == Mod::LCTRLMOD)
257                        || (*keymod & Mod::LGUIMOD == Mod::LGUIMOD),
258                };
259
260                self.raw_input.events.push(egui::Event::Key {
261                    key,
262                    pressed: false,
263                    repeat: *repeat,
264                    modifiers: self.modifiers,
265                });
266            }
267
268            KeyDown {
269                keycode, keymod, repeat, ..
270            } => {
271                let key_code = match keycode {
272                    Some(key_code) => key_code,
273                    _ => return,
274                };
275
276                let key = match translate_virtual_key_code(*key_code) {
277                    Some(key) => key,
278                    _ => return,
279                };
280                self.modifiers = Modifiers {
281                    alt: (*keymod & Mod::LALTMOD == Mod::LALTMOD)
282                        || (*keymod & Mod::RALTMOD == Mod::RALTMOD),
283                    ctrl: (*keymod & Mod::LCTRLMOD == Mod::LCTRLMOD)
284                        || (*keymod & Mod::RCTRLMOD == Mod::RCTRLMOD),
285                    shift: (*keymod & Mod::LSHIFTMOD == Mod::LSHIFTMOD)
286                        || (*keymod & Mod::RSHIFTMOD == Mod::RSHIFTMOD),
287                    mac_cmd: *keymod & Mod::LGUIMOD == Mod::LGUIMOD,
288
289                    //TOD: Test on both windows and mac
290                    command: (*keymod & Mod::LCTRLMOD == Mod::LCTRLMOD)
291                        || (*keymod & Mod::LGUIMOD == Mod::LGUIMOD),
292                };
293
294                self.raw_input.events.push(egui::Event::Key {
295                    key,
296                    pressed: true,
297                    repeat: *repeat,
298                    modifiers: self.modifiers,
299                });
300
301                if self.modifiers.command && key == Key::C {
302                    // println!("copy event");
303                    self.raw_input.events.push(egui::Event::Copy);
304                } else if self.modifiers.command && key == Key::X {
305                    // println!("cut event");
306                    self.raw_input.events.push(egui::Event::Cut);
307                } else if self.modifiers.command && key == Key::V {
308                    // println!("paste");
309                    if let Ok(contents) = window.subsystem().clipboard().clipboard_text() {
310                        self.raw_input.events.push(egui::Event::Text(contents));
311                    }
312                }
313            }
314
315            TextInput { text, .. } => {
316                self.raw_input.events.push(egui::Event::Text(text.clone()));
317            }
318            MouseWheel { x, y, .. } => {
319                let delta = egui::vec2(*x as f32 * 8.0, *y as f32 * 8.0);
320                let sdl = window.subsystem().sdl();
321                // zoom:
322                if sdl.keyboard().mod_state() & Mod::LCTRLMOD == Mod::LCTRLMOD
323                    || sdl.keyboard().mod_state() & Mod::RCTRLMOD == Mod::RCTRLMOD
324                {
325                    let zoom_delta = (delta.y / 125.0).exp();
326                    self.raw_input.events.push(egui::Event::Zoom(zoom_delta));
327                }
328                // horizontal scroll:
329                else if sdl.keyboard().mod_state() & Mod::LSHIFTMOD == Mod::LSHIFTMOD
330                    || sdl.keyboard().mod_state() & Mod::RSHIFTMOD == Mod::RSHIFTMOD
331                {
332                    self.raw_input
333                        .events
334                        .push(egui::Event::Scroll(egui::vec2(delta.x + delta.y, 0.0)));
335                    // regular scroll:
336                } else {
337                    self.raw_input
338                        .events
339                        .push(egui::Event::Scroll(egui::vec2(delta.x, delta.y)));
340                }
341            }
342            _ => {}
343        }
344    }
345
346    pub fn update_screen_rect(&mut self, window: &Window) {
347        let size = window.size();
348        let rect = egui::vec2(size.0 as f32, size.1 as f32);
349        self.raw_input.screen_rect = Some(Rect::from_min_size(Pos2::new(0f32, 0f32), rect));
350    }
351
352    pub fn take_egui_input(&mut self, window: &Window) -> RawInput {
353        self.raw_input.time = Some(self.start_time.elapsed().as_secs_f64());
354
355        let pixels_per_point = self.dpi_data.scaled_dpi();
356
357        let drawable_size = window.drawable_size();
358        let screen_size_in_points =
359            egui::vec2(drawable_size.0 as f32, drawable_size.1 as f32) / pixels_per_point;
360
361        self.raw_input.pixels_per_point = Some(pixels_per_point);
362        self.raw_input.screen_rect =
363            if screen_size_in_points.x > 0.0 && screen_size_in_points.y > 0.0 {
364                Some(egui::Rect::from_min_size(
365                    egui::Pos2::ZERO,
366                    screen_size_in_points,
367                ))
368            } else {
369                None
370            };
371
372        self.raw_input.take()
373    }
374
375    /// Creates new EguiSDL2State.
376    ///
377    /// Panics if `DpiMode::AutoScaled` value is zero or less than zero.
378    pub fn new(window: &Window, video_subsystem: &VideoSubsystem, dpi_mode: DpiMode) -> Self {
379        let dpi_data = dpi_mode.get_dpi(window, video_subsystem);
380        let raw_input = RawInput {
381            pixels_per_point: Some(dpi_data.dpi),
382            ..RawInput::default()
383        };
384        let modifiers = Modifiers::default();
385
386        EguiSDL2State {
387            start_time: Instant::now(),
388            raw_input,
389            modifiers,
390            dpi_mode,
391            dpi_data,
392            mouse_pointer_position: egui::Pos2::new(0.0, 0.0),
393            fused_cursor: FusedCursor::new(),
394        }
395    }
396
397    pub fn process_output(&mut self, window: &Window, egui_output: &egui::PlatformOutput) {
398        if !egui_output.copied_text.is_empty() {
399            let copied_text = egui_output.copied_text.clone();
400            {
401                let result = window
402                    .subsystem()
403                    .clipboard()
404                    .set_clipboard_text(&copied_text);
405                if result.is_err() {
406                    dbg!("Unable to set clipboard content to SDL clipboard.");
407                }
408            }
409        }
410        EguiSDL2State::translate_cursor(&mut self.fused_cursor, egui_output.cursor_icon);
411    }
412
413    fn translate_cursor(fused: &mut FusedCursor, cursor_icon: egui::CursorIcon) {
414        let tmp_icon = match cursor_icon {
415            egui::CursorIcon::Crosshair => SystemCursor::Crosshair,
416            egui::CursorIcon::Default => SystemCursor::Arrow,
417            egui::CursorIcon::Grab => SystemCursor::Hand,
418            egui::CursorIcon::Grabbing => SystemCursor::SizeAll,
419            egui::CursorIcon::Move => SystemCursor::SizeAll,
420            egui::CursorIcon::PointingHand => SystemCursor::Hand,
421            egui::CursorIcon::ResizeHorizontal => SystemCursor::SizeWE,
422            egui::CursorIcon::ResizeNeSw => SystemCursor::SizeNESW,
423            egui::CursorIcon::ResizeNwSe => SystemCursor::SizeNWSE,
424            egui::CursorIcon::ResizeVertical => SystemCursor::SizeNS,
425            egui::CursorIcon::Text => SystemCursor::IBeam,
426            egui::CursorIcon::NotAllowed | egui::CursorIcon::NoDrop => SystemCursor::No,
427            egui::CursorIcon::Wait => SystemCursor::Wait,
428            //There doesn't seem to be a suitable SDL equivalent...
429            _ => SystemCursor::Arrow,
430        };
431
432        if tmp_icon != fused.icon {
433            fused.cursor = Cursor::from_system(tmp_icon).unwrap();
434            fused.icon = tmp_icon;
435            fused.cursor.set();
436        }
437    }
438
439    /// Gets the current dpi value including scaling.
440    pub fn dpi(&self) -> f32 {
441        self.dpi_data.scaled_dpi()
442    }
443
444    /// Gets the current dpi value not including scaling.
445    pub fn dpi_without_scaling(&self) -> f32 {
446        self.dpi_data.dpi
447    }
448
449    /// Gets the dpi scaling factor.
450    pub fn dpi_scale_factor(&self) -> f32 {
451        self.dpi_data.scale
452    }
453}