1//! Largely adapted (and simplified) from `egui_winit_platform`:
2//! <https://github.com/hasenbanck/egui_winit_platform>
3
4use crate::clipboard::Clipboard;
5use egui::ahash::HashMapExt;
6use egui::{
7    Event, MouseWheelUnit, OutputCommand, PlatformOutput, TouchPhase, ViewportId, ViewportIdMap,
8    ViewportInfo,
9};
10use sdl3::event::Event as SdlEvent;
11use sdl3::event::WindowEvent as SdlWindowEvent;
12use sdl3::mouse::MouseWheelDirection;
13
14pub struct Platform {
15    window_id: u32,
16    window_display_scale: f32,
17    window_pixel_density: f32,
18    context: egui::Context,
19    raw_input: egui::RawInput,
20}
21
22impl Platform {
23    #[must_use]
24    pub fn new(window: &sdl3::video::Window) -> Self {
25        let context = egui::Context::default();
26
27        let display_scale = window.display_scale();
28        let pixel_density = window.pixel_density();
29
30        let mut viewports = ViewportIdMap::new();
31        viewports.insert(
32            ViewportId::ROOT,
33            ViewportInfo {
34                native_pixels_per_point: Some(display_scale),
35                ..ViewportInfo::default()
36            },
37        );
38
39        let (width, height) = window.size_in_pixels();
40        let raw_input = egui::RawInput {
41            viewport_id: context.viewport_id(),
42            viewports,
43            screen_rect: Some(egui::Rect::from_min_size(
44                egui::Pos2::default(),
45                egui::Vec2::new(width as f32, height as f32) / display_scale,
46            )),
47            ..egui::RawInput::default()
48        };
49
50        Self {
51            window_id: window.id(),
52            window_display_scale: display_scale,
53            window_pixel_density: pixel_density,
54            context,
55            raw_input,
56        }
57    }
58
59    pub fn handle_event(&mut self, event: &SdlEvent, clipboard: &mut Clipboard) {
60        match event {
61            &SdlEvent::Window { window_id, win_event, .. } if window_id == self.window_id => {
62                match win_event {
63                    SdlWindowEvent::PixelSizeChanged(width, height) => {
64                        self.raw_input.screen_rect = Some(egui::Rect::from_min_size(
65                            egui::Pos2::default(),
66                            egui::Vec2::new(width as f32, height as f32)
67                                / self.window_display_scale,
68                        ));
69                        self.context.request_repaint();
70                    }
71                    SdlWindowEvent::FocusGained => {
72                        self.raw_input.focused = true;
73                        self.context.request_repaint();
74                    }
75                    SdlWindowEvent::FocusLost => {
76                        self.raw_input.focused = false;
77                        self.context.request_repaint();
78                    }
79                    _ => {}
80                }
81            }
82            &SdlEvent::MouseMotion { window_id, x, y, .. } if window_id == self.window_id => {
83                let pointer_pos = self.mouse_pos_to_egui_pos(x, y);
84                self.raw_input.events.push(Event::PointerMoved(pointer_pos));
85            }
86            &SdlEvent::MouseButtonDown { window_id, mouse_btn, x, y, .. }
87                if window_id == self.window_id =>
88            {
89                let Some(egui_button) = sdl_mouse_button_to_egui(mouse_btn) else { return };
90
91                let pointer_pos = self.mouse_pos_to_egui_pos(x, y);
92                self.raw_input.events.push(Event::PointerButton {
93                    pos: pointer_pos,
94                    button: egui_button,
95                    pressed: true,
96                    modifiers: self.raw_input.modifiers,
97                });
98            }
99            &SdlEvent::MouseButtonUp { window_id, mouse_btn, x, y, .. }
100                if window_id == self.window_id =>
101            {
102                let Some(egui_button) = sdl_mouse_button_to_egui(mouse_btn) else { return };
103
104                let pointer_pos = self.mouse_pos_to_egui_pos(x, y);
105                self.raw_input.events.push(Event::PointerButton {
106                    pos: pointer_pos,
107                    button: egui_button,
108                    pressed: false,
109                    modifiers: self.raw_input.modifiers,
110                });
111            }
112            &SdlEvent::MouseWheel { window_id, direction, x, y, .. }
113                if window_id == self.window_id =>
114            {
115                // Multiplier of 15 somewhat arbitrary - scrolling is way too slow without any multiplier
116                let mut delta = 15.0 * egui::Vec2::new(x, y);
117                if direction == MouseWheelDirection::Flipped {
118                    delta *= -1.0;
119                }
120                self.raw_input.events.push(Event::MouseWheel {
121                    unit: MouseWheelUnit::Point,
122                    delta,
123                    phase: TouchPhase::Move,
124                    modifiers: self.raw_input.modifiers,
125                });
126            }
127            &SdlEvent::KeyDown { window_id, keycode: Some(keycode), keymod, repeat, .. }
128                if window_id == self.window_id =>
129            {
130                let modifiers = sdl_mod_to_egui(keymod);
131                self.raw_input.modifiers = modifiers;
132
133                let Some(egui_key) = sdl_keycode_to_egui(keycode) else { return };
134                self.raw_input.events.push(Event::Key {
135                    key: egui_key,
136                    physical_key: None,
137                    pressed: true,
138                    repeat,
139                    modifiers,
140                });
141
142                if modifiers.command {
143                    match keycode {
144                        sdl3::keyboard::Keycode::C => {
145                            self.raw_input.events.push(Event::Copy);
146                        }
147                        sdl3::keyboard::Keycode::X => {
148                            self.raw_input.events.push(Event::Cut);
149                        }
150                        sdl3::keyboard::Keycode::V => {
151                            self.raw_input.events.push(Event::Paste(clipboard.load()));
152                        }
153                        _ => {}
154                    }
155                }
156            }
157            &SdlEvent::KeyUp { window_id, keycode: Some(keycode), keymod, repeat, .. }
158                if window_id == self.window_id =>
159            {
160                let modifiers = sdl_mod_to_egui(keymod);
161                self.raw_input.modifiers = modifiers;
162
163                let Some(egui_key) = sdl_keycode_to_egui(keycode) else { return };
164                self.raw_input.events.push(Event::Key {
165                    key: egui_key,
166                    physical_key: None,
167                    pressed: false,
168                    repeat,
169                    modifiers,
170                });
171            }
172            SdlEvent::TextInput { window_id, text, .. }
173            | SdlEvent::TextEditing { window_id, text, .. }
174                if *window_id == self.window_id =>
175            {
176                self.raw_input.events.push(Event::Text(text.clone()));
177            }
178            _ => {}
179        }
180    }
181
182    fn mouse_pos_to_egui_pos(&self, x: f32, y: f32) -> egui::Pos2 {
183        let multiplier = self.window_pixel_density / self.window_display_scale;
184        [x * multiplier, y * multiplier].into()
185    }
186
187    pub(crate) fn has_pending_input_event(&self) -> bool {
188        !self.raw_input.events.is_empty()
189    }
190
191    #[must_use]
192    pub fn take_raw_input(&mut self, elapsed_secs: f64) -> egui::RawInput {
193        self.raw_input.time = Some(elapsed_secs);
194        self.raw_input.take()
195    }
196
197    #[must_use]
198    pub fn context(&self) -> &egui::Context {
199        &self.context
200    }
201
202    pub fn handle_egui_output(
203        &mut self,
204        platform_output: &PlatformOutput,
205        clipboard: &mut Clipboard,
206    ) {
207        for command in &platform_output.commands {
208            match command {
209                OutputCommand::CopyText(text) => {
210                    clipboard.store(text.clone());
211                }
212                OutputCommand::OpenUrl(url) => {
213                    if let Err(err) = open::that(&url.url) {
214                        log::error!("Error opening URL '{}': {err}", url.url);
215                    }
216                }
217                OutputCommand::CopyImage(_) => {}
218            }
219        }
220    }
221
222    pub fn request_cut(&mut self) {
223        self.raw_input.events.push(Event::Cut);
224    }
225
226    pub fn request_copy(&mut self) {
227        self.raw_input.events.push(Event::Copy);
228    }
229
230    pub fn request_paste(&mut self, text: String) {
231        self.raw_input.events.push(Event::Paste(text));
232    }
233}
234
235fn sdl_mouse_button_to_egui(button: sdl3::mouse::MouseButton) -> Option<egui::PointerButton> {
236    use sdl3::mouse::MouseButton::*;
237
238    match button {
239        Left => Some(egui::PointerButton::Primary),
240        Right => Some(egui::PointerButton::Secondary),
241        Middle => Some(egui::PointerButton::Middle),
242        X1 => Some(egui::PointerButton::Extra1),
243        X2 => Some(egui::PointerButton::Extra2),
244        Unknown => None,
245    }
246}
247
248fn sdl_keycode_to_egui(key: sdl3::keyboard::Keycode) -> Option<egui::Key> {
249    use egui::Key;
250    use sdl3::keyboard::Keycode;
251
252    match key {
253        Keycode::A => Some(Key::A),
254        Keycode::B => Some(Key::B),
255        Keycode::C => Some(Key::C),
256        Keycode::D => Some(Key::D),
257        Keycode::E => Some(Key::E),
258        Keycode::F => Some(Key::F),
259        Keycode::G => Some(Key::G),
260        Keycode::H => Some(Key::H),
261        Keycode::I => Some(Key::I),
262        Keycode::J => Some(Key::J),
263        Keycode::K => Some(Key::K),
264        Keycode::L => Some(Key::L),
265        Keycode::M => Some(Key::M),
266        Keycode::N => Some(Key::N),
267        Keycode::O => Some(Key::O),
268        Keycode::P => Some(Key::P),
269        Keycode::Q => Some(Key::Q),
270        Keycode::R => Some(Key::R),
271        Keycode::S => Some(Key::S),
272        Keycode::T => Some(Key::T),
273        Keycode::U => Some(Key::U),
274        Keycode::V => Some(Key::V),
275        Keycode::W => Some(Key::W),
276        Keycode::X => Some(Key::X),
277        Keycode::Y => Some(Key::Y),
278        Keycode::Z => Some(Key::Z),
279        Keycode::Kp0 | Keycode::_0 => Some(Key::Num0),
280        Keycode::Kp1 | Keycode::_1 => Some(Key::Num1),
281        Keycode::Kp2 | Keycode::_2 => Some(Key::Num2),
282        Keycode::Kp3 | Keycode::_3 => Some(Key::Num3),
283        Keycode::Kp4 | Keycode::_4 => Some(Key::Num4),
284        Keycode::Kp5 | Keycode::_5 => Some(Key::Num5),
285        Keycode::Kp6 | Keycode::_6 => Some(Key::Num6),
286        Keycode::Kp7 | Keycode::_7 => Some(Key::Num7),
287        Keycode::Kp8 | Keycode::_8 => Some(Key::Num8),
288        Keycode::Kp9 | Keycode::_9 => Some(Key::Num9),
289        Keycode::Left => Some(Key::ArrowLeft),
290        Keycode::Right => Some(Key::ArrowRight),
291        Keycode::Up => Some(Key::ArrowUp),
292        Keycode::Down => Some(Key::ArrowDown),
293        Keycode::Escape => Some(Key::Escape),
294        Keycode::Tab => Some(Key::Tab),
295        Keycode::Backspace | Keycode::KpBackspace => Some(Key::Backspace),
296        Keycode::Return => Some(Key::Enter),
297        Keycode::Space | Keycode::KpSpace => Some(Key::Space),
298        Keycode::Insert => Some(Key::Insert),
299        Keycode::Delete => Some(Key::Delete),
300        Keycode::Home => Some(Key::Home),
301        Keycode::End => Some(Key::End),
302        Keycode::PageUp => Some(Key::PageUp),
303        Keycode::PageDown => Some(Key::PageDown),
304        Keycode::Cut => Some(Key::Cut),
305        Keycode::Copy => Some(Key::Copy),
306        Keycode::Paste => Some(Key::Paste),
307        Keycode::Colon | Keycode::KpColon => Some(Key::Colon),
308        Keycode::Comma | Keycode::KpComma => Some(Key::Comma),
309        Keycode::Backslash => Some(Key::Backslash),
310        Keycode::Slash => Some(Key::Slash),
311        Keycode::Pipe => Some(Key::Pipe),
312        Keycode::Question => Some(Key::Questionmark),
313        Keycode::LeftBracket => Some(Key::OpenBracket),
314        Keycode::RightBracket => Some(Key::CloseBracket),
315        Keycode::LeftBrace | Keycode::KpLeftBrace => Some(Key::OpenCurlyBracket),
316        Keycode::RightBrace | Keycode::KpRightBrace => Some(Key::CloseCurlyBracket),
317        Keycode::Grave => Some(Key::Backtick),
318        Keycode::Minus | Keycode::KpMinus => Some(Key::Minus),
319        Keycode::Period | Keycode::KpPeriod => Some(Key::Period),
320        Keycode::Plus | Keycode::KpPlus => Some(Key::Plus),
321        Keycode::Equals | Keycode::KpEquals => Some(Key::Equals),
322        Keycode::Semicolon => Some(Key::Semicolon),
323        Keycode::Apostrophe => Some(Key::Quote),
324        Keycode::F1 => Some(Key::F1),
325        Keycode::F2 => Some(Key::F2),
326        Keycode::F3 => Some(Key::F3),
327        Keycode::F4 => Some(Key::F4),
328        Keycode::F5 => Some(Key::F5),
329        Keycode::F6 => Some(Key::F6),
330        Keycode::F7 => Some(Key::F7),
331        Keycode::F8 => Some(Key::F8),
332        Keycode::F9 => Some(Key::F9),
333        Keycode::F10 => Some(Key::F10),
334        Keycode::F11 => Some(Key::F11),
335        Keycode::F12 => Some(Key::F12),
336        Keycode::F13 => Some(Key::F13),
337        Keycode::F14 => Some(Key::F14),
338        Keycode::F15 => Some(Key::F15),
339        Keycode::F16 => Some(Key::F16),
340        Keycode::F17 => Some(Key::F17),
341        Keycode::F18 => Some(Key::F18),
342        Keycode::F19 => Some(Key::F19),
343        Keycode::F20 => Some(Key::F20),
344        Keycode::F21 => Some(Key::F21),
345        Keycode::F22 => Some(Key::F22),
346        Keycode::F23 => Some(Key::F23),
347        Keycode::F24 => Some(Key::F24),
348        _ => None,
349    }
350}
351
352fn sdl_mod_to_egui(sdl_mod: sdl3::keyboard::Mod) -> egui::Modifiers {
353    use sdl3::keyboard::Mod;
354
355    let ctrl = sdl_mod & (Mod::LCTRLMOD | Mod::RCTRLMOD) != Mod::NOMOD;
356    let gui = sdl_mod & (Mod::LGUIMOD | Mod::RGUIMOD) != Mod::NOMOD;
357
358    egui::Modifiers {
359        alt: sdl_mod & (Mod::LALTMOD | Mod::RALTMOD) != Mod::NOMOD,
360        ctrl,
361        shift: sdl_mod & (Mod::LSHIFTMOD | Mod::RSHIFTMOD) != Mod::NOMOD,
362        mac_cmd: cfg!(target_os = "macos") && gui,
363        command: cfg_select! {
364            target_os = "macos" => gui,
365            _ => ctrl,
366        },
367    }
368}