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}