Skip to main content

egui_sdl3_platform/
platform.rs

1use egui::{Modifiers, Pos2};
2use sdl3::{
3    event::{Event, WindowEvent},
4    mouse::{Cursor, MouseButton, SystemCursor},
5};
6
7use crate::conversions::ToEguiKey;
8
9/// The sdl3 platform for egui
10pub struct Platform {
11    // The cursors for the platform
12    cursor: Option<Cursor>,
13    system_cursor: SystemCursor,
14    // The position of the mouse pointer
15    pointer_pos: Pos2,
16    // The egui modifiers
17    modifiers: Modifiers,
18    // The raw input
19    raw_input: egui::RawInput,
20
21    start_time: std::time::Instant,
22    pixels_per_point: f32,
23
24    // The egui context
25    egui_ctx: egui::Context,
26}
27
28impl Platform {
29    /// Construct a new [`Platform`]
30    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    /// Handle a sdl3 event
56    pub fn handle_event(&mut self, event: &Event, video: &sdl3::VideoSubsystem) {
57        match event {
58            // Handle reizing
59            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            // Handle the mouse button being held down
72            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            // Handle the mouse button being released
90            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            // Handle mouse motion
108            Event::MouseMotion { x, y, .. } => {
109                // Update the pointer position
110                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            // Handle the mouse scrolling
117            Event::MouseWheel { x, y, .. } => {
118                // Calculate the delta
119                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            // Handle a key being pressed
130            Event::KeyDown {
131                keycode, keymod, ..
132            } => {
133                // Make sure there is a keycode
134                if let Some(keycode) = keycode {
135                    // Update modifiers
136                    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                    // Convert the keycode to an egui key
153                    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                        // Push the event
171                        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            // Handle a key being released
183            Event::KeyUp {
184                keycode, keymod, ..
185            } => {
186                // Make sure there is a keycode
187                if let Some(keycode) = keycode {
188                    // Update modifiers
189                    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                    // Convert the keycode to an egui key
206                    if let Some(key) = keycode.to_egui_key() {
207                        // Push the event
208                        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            // Handle text input
220            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    /// Return the processed context
234    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        // Set the pixels per point
238        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        // Begin the frame
246        self.egui_ctx.begin_pass(self.raw_input.take());
247        // Return the ctx
248        self.egui_ctx.clone()
249    }
250
251    /// Stop drawing the egui frame and return the full output
252    pub fn end_frame(
253        &mut self,
254        video: &mut sdl3::VideoSubsystem,
255    ) -> crate::Result<egui::FullOutput> {
256        // Get the egui output
257        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            // Update the cursor icon
269            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    /// Tessellate the egui frame
297    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}