imgui_sdl2_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 sdl2::{
9    event::Event,
10    keyboard::{Mod, Scancode},
11    mouse::{Cursor, MouseState, SystemCursor},
12    video::Window,
13    EventPump,
14};
15
16/// SDL2 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-sdl2-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        context: &mut Context,
129        window: &Window,
130        event_pump: &EventPump,
131    ) {
132        let mouse_cursor = context.mouse_cursor();
133        let io = context.io_mut();
134
135        // Update delta time
136        let now = Instant::now();
137        io.update_delta_time(now.duration_since(self.last_frame));
138        self.last_frame = now;
139
140        let mouse_state = MouseState::new(event_pump);
141        let window_size = window.size();
142        let window_drawable_size = window.drawable_size();
143
144        // Set display size and scale here, since SDL 2 doesn't have
145        // any easy way to get the scale factor, and changes in said
146        // scale factor
147        io.display_size = [window_size.0 as f32, window_size.1 as f32];
148        io.display_framebuffer_scale = [
149            (window_drawable_size.0 as f32) / (window_size.0 as f32),
150            (window_drawable_size.1 as f32) / (window_size.1 as f32),
151        ];
152
153        // Set mouse position if requested by imgui-rs
154        if io.want_set_mouse_pos {
155            let mouse_util = window.subsystem().sdl().mouse();
156            mouse_util.warp_mouse_in_window(window, io.mouse_pos[0] as i32, io.mouse_pos[1] as i32);
157        }
158
159        // Update mouse cursor position
160        io.mouse_pos = [mouse_state.x() as f32, mouse_state.y() as f32];
161
162        // Update mouse cursor icon if requested
163        if !io
164            .config_flags
165            .contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE)
166        {
167            let mouse_util = window.subsystem().sdl().mouse();
168
169            match mouse_cursor {
170                Some(mouse_cursor) if !io.mouse_draw_cursor => {
171                    let cursor = Cursor::from_system(to_sdl_cursor(mouse_cursor)).unwrap();
172                    cursor.set();
173
174                    mouse_util.show_cursor(true);
175                    self.cursor_instance = Some(cursor);
176                }
177
178                _ => {
179                    mouse_util.show_cursor(false);
180                    self.cursor_instance = None;
181                }
182            }
183        }
184    }
185}
186
187/// Returns `true` if the provided event is associated with the provided window.
188///
189/// # Example
190/// ```rust,no_run
191/// # let mut event_pump: sdl2::EventPump = unimplemented!();
192/// # let window: sdl2::video::Window = unimplemented!();
193/// # let mut imgui = imgui::Context::create();
194/// # let mut platform = SdlPlatform::init(&mut imgui);
195/// use imgui_sdl2_support::{SdlPlatform, filter_event};
196/// // Assuming there are multiple windows, we only want to provide the events
197/// // of the window where we are rendering to imgui-rs
198/// for event in event_pump.poll_iter().filter(|event| filter_event(&window, event)) {
199///     platform.handle_event(&mut imgui, &event);
200/// }
201/// ```
202pub fn filter_event(window: &Window, event: &Event) -> bool {
203    Some(window.id()) == event.get_window_id()
204}
205
206impl SdlPlatform {
207    fn handle_mouse_button(
208        &mut self,
209        io: &mut Io,
210        button: &sdl2::mouse::MouseButton,
211        pressed: bool,
212    ) {
213        match button {
214            sdl2::mouse::MouseButton::Left => {
215                io.add_mouse_button_event(imgui::MouseButton::Left, pressed)
216            }
217            sdl2::mouse::MouseButton::Right => {
218                io.add_mouse_button_event(imgui::MouseButton::Right, pressed)
219            }
220            sdl2::mouse::MouseButton::Middle => {
221                io.add_mouse_button_event(imgui::MouseButton::Middle, pressed)
222            }
223            sdl2::mouse::MouseButton::X1 => {
224                io.add_mouse_button_event(imgui::MouseButton::Extra1, pressed)
225            }
226            sdl2::mouse::MouseButton::X2 => {
227                io.add_mouse_button_event(imgui::MouseButton::Extra2, pressed)
228            }
229            _ => {}
230        }
231    }
232}
233
234/// Handle changes in the key states.
235fn handle_key(io: &mut Io, key: &Scancode, pressed: bool) {
236    let igkey = match key {
237        Scancode::A => imgui::Key::A,
238        Scancode::B => imgui::Key::B,
239        Scancode::C => imgui::Key::C,
240        Scancode::D => imgui::Key::D,
241        Scancode::E => imgui::Key::E,
242        Scancode::F => imgui::Key::F,
243        Scancode::G => imgui::Key::G,
244        Scancode::H => imgui::Key::H,
245        Scancode::I => imgui::Key::I,
246        Scancode::J => imgui::Key::J,
247        Scancode::K => imgui::Key::K,
248        Scancode::L => imgui::Key::L,
249        Scancode::M => imgui::Key::M,
250        Scancode::N => imgui::Key::N,
251        Scancode::O => imgui::Key::O,
252        Scancode::P => imgui::Key::P,
253        Scancode::Q => imgui::Key::Q,
254        Scancode::R => imgui::Key::R,
255        Scancode::S => imgui::Key::S,
256        Scancode::T => imgui::Key::T,
257        Scancode::U => imgui::Key::U,
258        Scancode::V => imgui::Key::V,
259        Scancode::W => imgui::Key::W,
260        Scancode::X => imgui::Key::X,
261        Scancode::Y => imgui::Key::Y,
262        Scancode::Z => imgui::Key::Z,
263        Scancode::Num1 => imgui::Key::Keypad1,
264        Scancode::Num2 => imgui::Key::Keypad2,
265        Scancode::Num3 => imgui::Key::Keypad3,
266        Scancode::Num4 => imgui::Key::Keypad4,
267        Scancode::Num5 => imgui::Key::Keypad5,
268        Scancode::Num6 => imgui::Key::Keypad6,
269        Scancode::Num7 => imgui::Key::Keypad7,
270        Scancode::Num8 => imgui::Key::Keypad8,
271        Scancode::Num9 => imgui::Key::Keypad9,
272        Scancode::Num0 => imgui::Key::Keypad0,
273        Scancode::Return => imgui::Key::Enter, // TODO: Should this be treated as alias?
274        Scancode::Escape => imgui::Key::Escape,
275        Scancode::Backspace => imgui::Key::Backspace,
276        Scancode::Tab => imgui::Key::Tab,
277        Scancode::Space => imgui::Key::Space,
278        Scancode::Minus => imgui::Key::Minus,
279        Scancode::Equals => imgui::Key::Equal,
280        Scancode::LeftBracket => imgui::Key::LeftBracket,
281        Scancode::RightBracket => imgui::Key::RightBracket,
282        Scancode::Backslash => imgui::Key::Backslash,
283        Scancode::Semicolon => imgui::Key::Semicolon,
284        Scancode::Apostrophe => imgui::Key::Apostrophe,
285        Scancode::Grave => imgui::Key::GraveAccent,
286        Scancode::Comma => imgui::Key::Comma,
287        Scancode::Period => imgui::Key::Period,
288        Scancode::Slash => imgui::Key::Slash,
289        Scancode::CapsLock => imgui::Key::CapsLock,
290        Scancode::F1 => imgui::Key::F1,
291        Scancode::F2 => imgui::Key::F2,
292        Scancode::F3 => imgui::Key::F3,
293        Scancode::F4 => imgui::Key::F4,
294        Scancode::F5 => imgui::Key::F5,
295        Scancode::F6 => imgui::Key::F6,
296        Scancode::F7 => imgui::Key::F7,
297        Scancode::F8 => imgui::Key::F8,
298        Scancode::F9 => imgui::Key::F9,
299        Scancode::F10 => imgui::Key::F10,
300        Scancode::F11 => imgui::Key::F11,
301        Scancode::F12 => imgui::Key::F12,
302        Scancode::PrintScreen => imgui::Key::PrintScreen,
303        Scancode::ScrollLock => imgui::Key::ScrollLock,
304        Scancode::Pause => imgui::Key::Pause,
305        Scancode::Insert => imgui::Key::Insert,
306        Scancode::Home => imgui::Key::Home,
307        Scancode::PageUp => imgui::Key::PageUp,
308        Scancode::Delete => imgui::Key::Delete,
309        Scancode::End => imgui::Key::End,
310        Scancode::PageDown => imgui::Key::PageDown,
311        Scancode::Right => imgui::Key::RightArrow,
312        Scancode::Left => imgui::Key::LeftArrow,
313        Scancode::Down => imgui::Key::DownArrow,
314        Scancode::Up => imgui::Key::UpArrow,
315        Scancode::KpDivide => imgui::Key::KeypadDivide,
316        Scancode::KpMultiply => imgui::Key::KeypadMultiply,
317        Scancode::KpMinus => imgui::Key::KeypadSubtract,
318        Scancode::KpPlus => imgui::Key::KeypadAdd,
319        Scancode::KpEnter => imgui::Key::KeypadEnter,
320        Scancode::Kp1 => imgui::Key::Keypad1,
321        Scancode::Kp2 => imgui::Key::Keypad2,
322        Scancode::Kp3 => imgui::Key::Keypad3,
323        Scancode::Kp4 => imgui::Key::Keypad4,
324        Scancode::Kp5 => imgui::Key::Keypad5,
325        Scancode::Kp6 => imgui::Key::Keypad6,
326        Scancode::Kp7 => imgui::Key::Keypad7,
327        Scancode::Kp8 => imgui::Key::Keypad8,
328        Scancode::Kp9 => imgui::Key::Keypad9,
329        Scancode::Kp0 => imgui::Key::Keypad0,
330        Scancode::KpPeriod => imgui::Key::KeypadDecimal,
331        Scancode::Application => imgui::Key::Menu,
332        Scancode::KpEquals => imgui::Key::KeypadEqual,
333        Scancode::Menu => imgui::Key::Menu,
334        Scancode::LCtrl => imgui::Key::LeftCtrl,
335        Scancode::LShift => imgui::Key::LeftShift,
336        Scancode::LAlt => imgui::Key::LeftAlt,
337        Scancode::LGui => imgui::Key::LeftSuper,
338        Scancode::RCtrl => imgui::Key::RightCtrl,
339        Scancode::RShift => imgui::Key::RightShift,
340        Scancode::RAlt => imgui::Key::RightAlt,
341        Scancode::RGui => imgui::Key::RightSuper,
342        _ => {
343            // Ignore unknown keys
344            return;
345        }
346    };
347
348    io.add_key_event(igkey, pressed);
349}
350
351/// Handle changes in the key modifier states.
352fn handle_key_modifier(io: &mut Io, keymod: &Mod) {
353    io.add_key_event(
354        imgui::Key::ModShift,
355        keymod.intersects(Mod::LSHIFTMOD | Mod::RSHIFTMOD),
356    );
357    io.add_key_event(
358        imgui::Key::ModCtrl,
359        keymod.intersects(Mod::LCTRLMOD | Mod::RCTRLMOD),
360    );
361    io.add_key_event(
362        imgui::Key::ModAlt,
363        keymod.intersects(Mod::LALTMOD | Mod::RALTMOD),
364    );
365    io.add_key_event(
366        imgui::Key::ModSuper,
367        keymod.intersects(Mod::LGUIMOD | Mod::RGUIMOD),
368    );
369}
370
371/// Map an imgui::MouseCursor to an equivalent sdl2::mouse::SystemCursor.
372fn to_sdl_cursor(cursor: MouseCursor) -> SystemCursor {
373    match cursor {
374        MouseCursor::Arrow => SystemCursor::Arrow,
375        MouseCursor::TextInput => SystemCursor::IBeam,
376        MouseCursor::ResizeAll => SystemCursor::SizeAll,
377        MouseCursor::ResizeNS => SystemCursor::SizeNS,
378        MouseCursor::ResizeEW => SystemCursor::SizeWE,
379        MouseCursor::ResizeNESW => SystemCursor::SizeNESW,
380        MouseCursor::ResizeNWSE => SystemCursor::SizeNWSE,
381        MouseCursor::Hand => SystemCursor::Hand,
382        MouseCursor::NotAllowed => SystemCursor::No,
383    }
384}