Skip to main content

imgui_sdl3_support/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(rust_2018_idioms)]
3#![deny(missing_docs)]
4
5use std::time::Instant;
6
7use imgui::{BackendFlags, ConfigFlags, Context, Io, MouseCursor};
8use sdl3::{
9    event::Event,
10    keyboard::{Mod, Scancode},
11    mouse::{Cursor, MouseState, SystemCursor},
12    video::Window,
13    EventPump, Sdl,
14};
15
16/// sdl3 backend platform state.
17///
18/// A backend platform handles window/input device events and manages their
19/// state.
20///
21/// There are three things you need to do to use this library correctly:
22///
23/// 1. Initialize a `SdlPlatform` instance
24/// 2. Pass events to the platform (every frame)
25/// 3. Call frame preparation callback (every frame)
26pub struct SdlPlatform {
27    cursor_instance: Option<Cursor>, /* to avoid dropping cursor instances */
28    last_frame: Instant,
29}
30
31impl SdlPlatform {
32    /// Initializes a SDL platform instance and configures imgui.
33    ///
34    /// This function configures imgui-rs in the following ways:
35    ///
36    /// * backend flags are updated
37    /// * keys are configured
38    /// * platform name is set
39    pub fn new(imgui: &mut Context) -> SdlPlatform {
40        let io = imgui.io_mut();
41
42        io.backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS);
43        io.backend_flags.insert(BackendFlags::HAS_SET_MOUSE_POS);
44
45        imgui.set_platform_name(Some(format!(
46            "imgui-sdl3-support {}",
47            env!("CARGO_PKG_VERSION")
48        )));
49
50        SdlPlatform {
51            cursor_instance: None,
52            last_frame: Instant::now(),
53        }
54    }
55
56    /// Initializes a SDL platform instance and configures imgui.
57    ///
58    /// Deprecated since `0.13.0` -- use `new` instead
59    #[deprecated = "use `new` instead"]
60    pub fn init(imgui: &mut Context) -> SdlPlatform {
61        Self::new(imgui)
62    }
63    /// Handles a SDL event.
64    ///
65    /// This function performs the following actions (depends on the event):
66    ///
67    /// * keyboard state is updated
68    /// * mouse state is updated
69    pub fn handle_event(&mut self, context: &mut Context, event: &Event) -> bool {
70        let io = context.io_mut();
71
72        match *event {
73            Event::MouseWheel { x, y, .. } => {
74                io.add_mouse_wheel_event([x as f32, y as f32]);
75                true
76            }
77
78            Event::MouseButtonDown { mouse_btn, .. } => {
79                self.handle_mouse_button(io, &mouse_btn, true);
80                true
81            }
82
83            Event::MouseButtonUp { mouse_btn, .. } => {
84                self.handle_mouse_button(io, &mouse_btn, false);
85                true
86            }
87
88            Event::TextInput { ref text, .. } => {
89                text.chars().for_each(|c| io.add_input_character(c));
90                true
91            }
92
93            Event::KeyDown {
94                scancode: Some(key),
95                keymod,
96                ..
97            } => {
98                handle_key_modifier(io, &keymod);
99                handle_key(io, &key, true);
100                true
101            }
102
103            Event::KeyUp {
104                scancode: Some(key),
105                keymod,
106                ..
107            } => {
108                handle_key_modifier(io, &keymod);
109                handle_key(io, &key, false);
110                true
111            }
112
113            _ => false,
114        }
115    }
116
117    /// Frame preparation callback.
118    ///
119    /// Call this before calling the imgui-rs context `frame` function.
120    /// This function performs the following actions:
121    ///
122    /// * display size and the framebuffer scale is set
123    /// * mouse cursor is repositioned (if requested by imgui-rs)
124    /// * current mouse cursor position is passed to imgui-rs
125    /// * changes mouse cursor icon (if requested by imgui-rs)
126    pub fn prepare_frame(
127        &mut self,
128        sdl: &mut Sdl,
129        context: &mut Context,
130        window: &Window,
131        event_pump: &EventPump,
132    ) {
133        let mouse_cursor = context.mouse_cursor();
134        let io = context.io_mut();
135
136        // Update delta time
137        let now = Instant::now();
138        io.update_delta_time(now.duration_since(self.last_frame));
139        self.last_frame = now;
140
141        let mouse_state = MouseState::new(event_pump);
142        let window_size = window.size();
143        let window_drawable_size = window.size_in_pixels();
144
145        // Set display size and scale here, since SDL 2 doesn't have
146        // any easy way to get the scale factor, and changes in said
147        // scale factor
148        io.display_size = [window_size.0 as f32, window_size.1 as f32];
149        io.display_framebuffer_scale = [
150            (window_drawable_size.0 as f32) / (window_size.0 as f32),
151            (window_drawable_size.1 as f32) / (window_size.1 as f32),
152        ];
153
154        // Set mouse position if requested by imgui-rs
155        if io.want_set_mouse_pos {
156            let mouse_util = sdl.mouse();
157            mouse_util.warp_mouse_in_window(window, io.mouse_pos[0], io.mouse_pos[1]);
158        }
159
160        // Update mouse cursor position
161        io.mouse_pos = [mouse_state.x() as f32, mouse_state.y() as f32];
162
163        // Update mouse cursor icon if requested
164        if !io
165            .config_flags
166            .contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE)
167        {
168            let mouse_util = sdl.mouse();
169
170            match mouse_cursor {
171                Some(mouse_cursor) if !io.mouse_draw_cursor => {
172                    let cursor = Cursor::from_system(to_sdl_cursor(mouse_cursor)).unwrap();
173                    cursor.set();
174
175                    mouse_util.show_cursor(true);
176                    self.cursor_instance = Some(cursor);
177                }
178
179                _ => {
180                    mouse_util.show_cursor(false);
181                    self.cursor_instance = None;
182                }
183            }
184        }
185    }
186}
187
188/// Returns `true` if the provided event is associated with the provided window.
189///
190/// # Example
191/// ```rust,no_run
192/// # let mut event_pump: sdl3::EventPump = unimplemented!();
193/// # let window: sdl3::video::Window = unimplemented!();
194/// # let mut imgui = imgui::Context::create();
195/// # let mut platform = SdlPlatform::init(&mut imgui);
196/// use imgui_sdl3_support::{SdlPlatform, filter_event};
197/// // Assuming there are multiple windows, we only want to provide the events
198/// // of the window where we are rendering to imgui-rs
199/// for event in event_pump.poll_iter().filter(|event| filter_event(&window, event)) {
200///     platform.handle_event(&mut imgui, &event);
201/// }
202/// ```
203pub fn filter_event(window: &Window, event: &Event) -> bool {
204    Some(window.id()) == event.get_window_id()
205}
206
207impl SdlPlatform {
208    fn handle_mouse_button(
209        &mut self,
210        io: &mut Io,
211        button: &sdl3::mouse::MouseButton,
212        pressed: bool,
213    ) {
214        match button {
215            sdl3::mouse::MouseButton::Left => {
216                io.add_mouse_button_event(imgui::MouseButton::Left, pressed)
217            }
218            sdl3::mouse::MouseButton::Right => {
219                io.add_mouse_button_event(imgui::MouseButton::Right, pressed)
220            }
221            sdl3::mouse::MouseButton::Middle => {
222                io.add_mouse_button_event(imgui::MouseButton::Middle, pressed)
223            }
224            sdl3::mouse::MouseButton::X1 => {
225                io.add_mouse_button_event(imgui::MouseButton::Extra1, pressed)
226            }
227            sdl3::mouse::MouseButton::X2 => {
228                io.add_mouse_button_event(imgui::MouseButton::Extra2, pressed)
229            }
230            _ => {}
231        }
232    }
233}
234
235/// Handle changes in the key states.
236fn handle_key(io: &mut Io, key: &Scancode, pressed: bool) {
237    let igkey = match key {
238        Scancode::A => imgui::Key::A,
239        Scancode::B => imgui::Key::B,
240        Scancode::C => imgui::Key::C,
241        Scancode::D => imgui::Key::D,
242        Scancode::E => imgui::Key::E,
243        Scancode::F => imgui::Key::F,
244        Scancode::G => imgui::Key::G,
245        Scancode::H => imgui::Key::H,
246        Scancode::I => imgui::Key::I,
247        Scancode::J => imgui::Key::J,
248        Scancode::K => imgui::Key::K,
249        Scancode::L => imgui::Key::L,
250        Scancode::M => imgui::Key::M,
251        Scancode::N => imgui::Key::N,
252        Scancode::O => imgui::Key::O,
253        Scancode::P => imgui::Key::P,
254        Scancode::Q => imgui::Key::Q,
255        Scancode::R => imgui::Key::R,
256        Scancode::S => imgui::Key::S,
257        Scancode::T => imgui::Key::T,
258        Scancode::U => imgui::Key::U,
259        Scancode::V => imgui::Key::V,
260        Scancode::W => imgui::Key::W,
261        Scancode::X => imgui::Key::X,
262        Scancode::Y => imgui::Key::Y,
263        Scancode::Z => imgui::Key::Z,
264        Scancode::_1 => imgui::Key::Keypad1,
265        Scancode::_2 => imgui::Key::Keypad2,
266        Scancode::_3 => imgui::Key::Keypad3,
267        Scancode::_4 => imgui::Key::Keypad4,
268        Scancode::_5 => imgui::Key::Keypad5,
269        Scancode::_6 => imgui::Key::Keypad6,
270        Scancode::_7 => imgui::Key::Keypad7,
271        Scancode::_8 => imgui::Key::Keypad8,
272        Scancode::_9 => imgui::Key::Keypad9,
273        Scancode::_0 => imgui::Key::Keypad0,
274        Scancode::Return => imgui::Key::Enter, // TODO: Should this be treated as alias?
275        Scancode::Escape => imgui::Key::Escape,
276        Scancode::Backspace => imgui::Key::Backspace,
277        Scancode::Tab => imgui::Key::Tab,
278        Scancode::Space => imgui::Key::Space,
279        Scancode::Minus => imgui::Key::Minus,
280        Scancode::Equals => imgui::Key::Equal,
281        Scancode::LeftBracket => imgui::Key::LeftBracket,
282        Scancode::RightBracket => imgui::Key::RightBracket,
283        Scancode::Backslash => imgui::Key::Backslash,
284        Scancode::Semicolon => imgui::Key::Semicolon,
285        Scancode::Apostrophe => imgui::Key::Apostrophe,
286        Scancode::Grave => imgui::Key::GraveAccent,
287        Scancode::Comma => imgui::Key::Comma,
288        Scancode::Period => imgui::Key::Period,
289        Scancode::Slash => imgui::Key::Slash,
290        Scancode::CapsLock => imgui::Key::CapsLock,
291        Scancode::F1 => imgui::Key::F1,
292        Scancode::F2 => imgui::Key::F2,
293        Scancode::F3 => imgui::Key::F3,
294        Scancode::F4 => imgui::Key::F4,
295        Scancode::F5 => imgui::Key::F5,
296        Scancode::F6 => imgui::Key::F6,
297        Scancode::F7 => imgui::Key::F7,
298        Scancode::F8 => imgui::Key::F8,
299        Scancode::F9 => imgui::Key::F9,
300        Scancode::F10 => imgui::Key::F10,
301        Scancode::F11 => imgui::Key::F11,
302        Scancode::F12 => imgui::Key::F12,
303        Scancode::PrintScreen => imgui::Key::PrintScreen,
304        Scancode::ScrollLock => imgui::Key::ScrollLock,
305        Scancode::Pause => imgui::Key::Pause,
306        Scancode::Insert => imgui::Key::Insert,
307        Scancode::Home => imgui::Key::Home,
308        Scancode::PageUp => imgui::Key::PageUp,
309        Scancode::Delete => imgui::Key::Delete,
310        Scancode::End => imgui::Key::End,
311        Scancode::PageDown => imgui::Key::PageDown,
312        Scancode::Right => imgui::Key::RightArrow,
313        Scancode::Left => imgui::Key::LeftArrow,
314        Scancode::Down => imgui::Key::DownArrow,
315        Scancode::Up => imgui::Key::UpArrow,
316        Scancode::KpDivide => imgui::Key::KeypadDivide,
317        Scancode::KpMultiply => imgui::Key::KeypadMultiply,
318        Scancode::KpMinus => imgui::Key::KeypadSubtract,
319        Scancode::KpPlus => imgui::Key::KeypadAdd,
320        Scancode::KpEnter => imgui::Key::KeypadEnter,
321        Scancode::Kp1 => imgui::Key::Keypad1,
322        Scancode::Kp2 => imgui::Key::Keypad2,
323        Scancode::Kp3 => imgui::Key::Keypad3,
324        Scancode::Kp4 => imgui::Key::Keypad4,
325        Scancode::Kp5 => imgui::Key::Keypad5,
326        Scancode::Kp6 => imgui::Key::Keypad6,
327        Scancode::Kp7 => imgui::Key::Keypad7,
328        Scancode::Kp8 => imgui::Key::Keypad8,
329        Scancode::Kp9 => imgui::Key::Keypad9,
330        Scancode::Kp0 => imgui::Key::Keypad0,
331        Scancode::KpPeriod => imgui::Key::KeypadDecimal,
332        Scancode::Application => imgui::Key::Menu,
333        Scancode::KpEquals => imgui::Key::KeypadEqual,
334        Scancode::Menu => imgui::Key::Menu,
335        Scancode::LCtrl => imgui::Key::LeftCtrl,
336        Scancode::LShift => imgui::Key::LeftShift,
337        Scancode::LAlt => imgui::Key::LeftAlt,
338        Scancode::LGui => imgui::Key::LeftSuper,
339        Scancode::RCtrl => imgui::Key::RightCtrl,
340        Scancode::RShift => imgui::Key::RightShift,
341        Scancode::RAlt => imgui::Key::RightAlt,
342        Scancode::RGui => imgui::Key::RightSuper,
343        _ => {
344            // Ignore unknown keys
345            return;
346        }
347    };
348
349    io.add_key_event(igkey, pressed);
350}
351
352/// Handle changes in the key modifier states.
353fn handle_key_modifier(io: &mut Io, keymod: &Mod) {
354    // handle the actual modifiers:
355    io.add_key_event(
356        imgui::Key::ModShift,
357        keymod.intersects(Mod::LSHIFTMOD | Mod::RSHIFTMOD),
358    );
359    io.add_key_event(
360        imgui::Key::ModCtrl,
361        keymod.intersects(Mod::LCTRLMOD | Mod::RCTRLMOD),
362    );
363    io.add_key_event(
364        imgui::Key::ModAlt,
365        keymod.intersects(Mod::LALTMOD | Mod::RALTMOD),
366    );
367    io.add_key_event(
368        imgui::Key::ModSuper,
369        keymod.intersects(Mod::LGUIMOD | Mod::RGUIMOD),
370    );
371}
372
373/// Map an imgui::MouseCursor to an equivalent sdl3::mouse::SystemCursor.
374fn to_sdl_cursor(cursor: MouseCursor) -> SystemCursor {
375    match cursor {
376        MouseCursor::Arrow => SystemCursor::Arrow,
377        MouseCursor::TextInput => SystemCursor::IBeam,
378        MouseCursor::ResizeAll => SystemCursor::SizeAll,
379        MouseCursor::ResizeNS => SystemCursor::SizeNS,
380        MouseCursor::ResizeEW => SystemCursor::SizeWE,
381        MouseCursor::ResizeNESW => SystemCursor::SizeNESW,
382        MouseCursor::ResizeNWSE => SystemCursor::SizeNWSE,
383        MouseCursor::Hand => SystemCursor::Hand,
384        MouseCursor::NotAllowed => SystemCursor::No,
385    }
386}