1use egui::{Modifiers, Pos2};
2use sdl3::{
3 event::{Event, WindowEvent},
4 mouse::{Cursor, MouseButton, SystemCursor},
5};
6
7use crate::conversions::ToEguiKey;
8
9pub struct Platform {
11 cursor: Option<Cursor>,
13 system_cursor: SystemCursor,
14 pointer_pos: Pos2,
16 modifiers: Modifiers,
18 raw_input: egui::RawInput,
20
21 start_time: std::time::Instant,
22 pixels_per_point: f32,
23
24 egui_ctx: egui::Context,
26}
27
28impl Platform {
29 pub fn new(sdl: &sdl3::Sdl, window: &sdl3::video::Window) -> crate::Result<Self> {
31 sdl.video()?.text_input().start(window);
32 Ok(Self {
33 cursor: Cursor::from_system(SystemCursor::Arrow)
34 .map_err(|e| log::warn!("Failed to get cursor from systems cursor: {}", e))
35 .ok(),
36 system_cursor: SystemCursor::Arrow,
37 pointer_pos: Pos2::ZERO,
38 raw_input: egui::RawInput {
39 screen_rect: Some(egui::Rect::from_min_size(
40 egui::Pos2::ZERO,
41 egui::Vec2 {
42 x: window.size_in_pixels().0 as f32,
43 y: window.size_in_pixels().1 as f32,
44 },
45 )),
46 ..Default::default()
47 },
48 pixels_per_point: 1.0,
49 modifiers: Modifiers::default(),
50 egui_ctx: egui::Context::default(),
51 start_time: std::time::Instant::now(),
52 })
53 }
54
55 pub fn handle_event(&mut self, event: &Event, video: &sdl3::VideoSubsystem) {
57 match event {
58 Event::Window {
60 win_event: WindowEvent::Resized(w, h),
61 ..
62 } => {
63 self.raw_input.screen_rect = Some(egui::Rect::from_min_size(
64 egui::Pos2::ZERO,
65 egui::Vec2 {
66 x: *w as f32,
67 y: *h as f32,
68 },
69 ));
70 }
71 Event::MouseButtonDown { mouse_btn, .. } => {
73 let btn = match mouse_btn {
74 MouseButton::Left => Some(egui::PointerButton::Primary),
75 MouseButton::Middle => Some(egui::PointerButton::Middle),
76 MouseButton::Right => Some(egui::PointerButton::Secondary),
77 _ => None,
78 };
79 if let Some(btn) = btn {
80 self.raw_input.events.push(egui::Event::PointerButton {
81 pos: self.pointer_pos,
82 button: btn,
83 pressed: true,
84 modifiers: self.modifiers,
85 });
86 }
87 self.egui_ctx.wants_pointer_input();
88 }
89 Event::MouseButtonUp { mouse_btn, .. } => {
91 let btn = match mouse_btn {
92 MouseButton::Left => Some(egui::PointerButton::Primary),
93 MouseButton::Middle => Some(egui::PointerButton::Middle),
94 MouseButton::Right => Some(egui::PointerButton::Secondary),
95 _ => None,
96 };
97 if let Some(btn) = btn {
98 self.raw_input.events.push(egui::Event::PointerButton {
99 pos: self.pointer_pos,
100 button: btn,
101 pressed: false,
102 modifiers: self.modifiers,
103 });
104 }
105 self.egui_ctx.wants_pointer_input();
106 }
107 Event::MouseMotion { x, y, .. } => {
109 self.pointer_pos = egui::Pos2::new(*x, *y) / self.pixels_per_point;
111 self.raw_input
112 .events
113 .push(egui::Event::PointerMoved(self.pointer_pos));
114 self.egui_ctx.wants_pointer_input();
115 }
116 Event::MouseWheel { x, y, .. } => {
118 let delta = egui::Vec2::new(*x, *y) * 32.0;
120
121 self.raw_input.events.push(egui::Event::MouseWheel {
122 unit: egui::MouseWheelUnit::Point,
123 delta,
124 modifiers: egui::Modifiers::NONE,
125 });
126 self.egui_ctx.wants_pointer_input();
127 }
128
129 Event::KeyDown {
131 keycode, keymod, ..
132 } => {
133 if let Some(keycode) = keycode {
135 use sdl3::keyboard::Mod;
137 let alt = keymod.contains(Mod::LALTMOD) || keymod.contains(Mod::RALTMOD);
138 let ctrl = keymod.contains(Mod::LCTRLMOD) || keymod.contains(Mod::RCTRLMOD);
139 let shift = keymod.contains(Mod::LSHIFTMOD) || keymod.contains(Mod::RSHIFTMOD);
140 let mac_cmd = keymod.contains(Mod::LGUIMOD);
141 let command = keymod.contains(Mod::LCTRLMOD) || keymod.contains(Mod::LGUIMOD);
142
143 self.modifiers = Modifiers {
144 alt,
145 ctrl,
146 shift,
147 mac_cmd,
148 command,
149 };
150 self.raw_input.modifiers = self.modifiers;
151
152 if let Some(key) = keycode.to_egui_key() {
154 if self.modifiers.ctrl {
155 match key {
156 egui::Key::C => self.raw_input.events.push(egui::Event::Copy),
157 egui::Key::X => self.raw_input.events.push(egui::Event::Cut),
158 egui::Key::V => {
159 let clipboard = video.clipboard();
160 if clipboard.has_clipboard_text() {
161 self.raw_input.events.push(egui::Event::Paste(
162 clipboard.clipboard_text().unwrap(),
163 ));
164 }
165 }
166 _ => {}
167 }
168 }
169
170 self.raw_input.events.push(egui::Event::Key {
172 key,
173 physical_key: Some(key),
174 pressed: true,
175 repeat: false,
176 modifiers: self.modifiers,
177 });
178 }
179 }
180 self.egui_ctx.wants_keyboard_input();
181 }
182 Event::KeyUp {
184 keycode, keymod, ..
185 } => {
186 if let Some(keycode) = keycode {
188 use sdl3::keyboard::Mod;
190 let alt = keymod.contains(Mod::LALTMOD) || keymod.contains(Mod::RALTMOD);
191 let ctrl = keymod.contains(Mod::LCTRLMOD) || keymod.contains(Mod::RCTRLMOD);
192 let shift = keymod.contains(Mod::LSHIFTMOD) || keymod.contains(Mod::RSHIFTMOD);
193 let mac_cmd = keymod.contains(Mod::LGUIMOD);
194 let command = keymod.contains(Mod::LCTRLMOD) || keymod.contains(Mod::LGUIMOD);
195
196 self.modifiers = Modifiers {
197 alt,
198 ctrl,
199 shift,
200 mac_cmd,
201 command,
202 };
203 self.raw_input.modifiers = self.modifiers;
204
205 if let Some(key) = keycode.to_egui_key() {
207 self.raw_input.events.push(egui::Event::Key {
209 key,
210 physical_key: Some(key),
211 pressed: false,
212 repeat: false,
213 modifiers: self.modifiers,
214 });
215 }
216 }
217 self.egui_ctx.wants_keyboard_input();
218 }
219 Event::TextInput { text, .. } => {
221 self.raw_input.events.push(egui::Event::Text(text.clone()));
222 self.egui_ctx.wants_keyboard_input();
223 }
224
225 _ => {}
226 }
227 }
228
229 pub fn context(&self) -> &egui::Context {
230 &self.egui_ctx
231 }
232
233 pub fn begin_frame(&mut self, window: &sdl3::video::Window) -> egui::Context {
235 let screen_size = window.size();
236 self.pixels_per_point = window.display_scale();
237 self.egui_ctx.set_pixels_per_point(self.pixels_per_point);
239 self.raw_input.screen_rect = Some(egui::Rect::from_min_size(
240 egui::Pos2::ZERO,
241 egui::vec2(screen_size.0 as f32, screen_size.1 as f32) / self.pixels_per_point,
242 ));
243 self.raw_input.time = Some(self.start_time.elapsed().as_secs_f64());
244
245 self.egui_ctx.begin_pass(self.raw_input.take());
247 self.egui_ctx.clone()
249 }
250
251 pub fn end_frame(
253 &mut self,
254 video: &mut sdl3::VideoSubsystem,
255 ) -> crate::Result<egui::FullOutput> {
256 let output = self.egui_ctx.end_pass();
258
259 for c in &output.platform_output.commands {
260 if let egui::OutputCommand::CopyText(text) = c {
261 if let Err(e) = video.clipboard().set_clipboard_text(text) {
262 log::error!("Failed to assign text to clipboard: {}", e);
263 }
264 }
265 }
266
267 if let Some(cursor) = &mut self.cursor {
268 let new_cursor = match output.platform_output.cursor_icon {
270 egui::CursorIcon::Crosshair => SystemCursor::Crosshair,
271 egui::CursorIcon::Default => SystemCursor::Arrow,
272 egui::CursorIcon::Grab => SystemCursor::Hand,
273 egui::CursorIcon::Grabbing => SystemCursor::SizeAll,
274 egui::CursorIcon::Move => SystemCursor::SizeAll,
275 egui::CursorIcon::PointingHand => SystemCursor::Hand,
276 egui::CursorIcon::ResizeHorizontal => SystemCursor::SizeWE,
277 egui::CursorIcon::ResizeNeSw => SystemCursor::SizeNESW,
278 egui::CursorIcon::ResizeNwSe => SystemCursor::SizeNWSE,
279 egui::CursorIcon::ResizeVertical => SystemCursor::SizeNS,
280 egui::CursorIcon::Text => SystemCursor::IBeam,
281 egui::CursorIcon::NotAllowed | egui::CursorIcon::NoDrop => SystemCursor::No,
282 egui::CursorIcon::Wait => SystemCursor::Wait,
283 _ => SystemCursor::Arrow,
284 };
285
286 if self.system_cursor != new_cursor {
287 self.system_cursor = new_cursor;
288 *cursor = Cursor::from_system(new_cursor)?;
289 cursor.set();
290 }
291 }
292
293 Ok(output)
294 }
295
296 pub fn tessellate(&self, full_output: &egui::FullOutput) -> Vec<egui::ClippedPrimitive> {
298 self.egui_ctx
299 .tessellate(full_output.shapes.clone(), self.egui_ctx.pixels_per_point())
300 }
301}