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 Auto,
110 AutoScaled(f32),
112 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 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 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 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 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 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 self.raw_input.events.push(egui::Event::Copy);
304 } else if self.modifiers.command && key == Key::X {
305 self.raw_input.events.push(egui::Event::Cut);
307 } else if self.modifiers.command && key == Key::V {
308 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 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 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 } 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 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 _ => 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 pub fn dpi(&self) -> f32 {
441 self.dpi_data.scaled_dpi()
442 }
443
444 pub fn dpi_without_scaling(&self) -> f32 {
446 self.dpi_data.dpi
447 }
448
449 pub fn dpi_scale_factor(&self) -> f32 {
451 self.dpi_data.scale
452 }
453}