imgui-sdl2-support 0.13.0

sdl2 support code for the imgui crate
Documentation
#![doc = include_str!("../README.md")]
#![deny(rust_2018_idioms)]
#![deny(missing_docs)]

use std::time::Instant;

use imgui::{BackendFlags, ConfigFlags, Context, Io, MouseCursor};
use sdl2::{
    event::Event,
    keyboard::{Mod, Scancode},
    mouse::{Cursor, MouseState, SystemCursor},
    video::Window,
    EventPump,
};

/// SDL2 backend platform state.
///
/// A backend platform handles window/input device events and manages their
/// state.
///
/// There are three things you need to do to use this library correctly:
///
/// 1. Initialize a `SdlPlatform` instance
/// 2. Pass events to the platform (every frame)
/// 3. Call frame preparation callback (every frame)
pub struct SdlPlatform {
    cursor_instance: Option<Cursor>, /* to avoid dropping cursor instances */
    last_frame: Instant,
}

impl SdlPlatform {
    /// Initializes a SDL platform instance and configures imgui.
    ///
    /// This function configures imgui-rs in the following ways:
    ///
    /// * backend flags are updated
    /// * keys are configured
    /// * platform name is set
    pub fn new(imgui: &mut Context) -> SdlPlatform {
        let io = imgui.io_mut();

        io.backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS);
        io.backend_flags.insert(BackendFlags::HAS_SET_MOUSE_POS);

        imgui.set_platform_name(Some(format!(
            "imgui-sdl2-support {}",
            env!("CARGO_PKG_VERSION")
        )));

        SdlPlatform {
            cursor_instance: None,
            last_frame: Instant::now(),
        }
    }

    /// Initializes a SDL platform instance and configures imgui.
    ///
    /// Deprecated since `0.13.0` -- use `new` instead
    #[deprecated = "use `new` instead"]
    pub fn init(imgui: &mut Context) -> SdlPlatform {
        Self::new(imgui)
    }
    /// Handles a SDL event.
    ///
    /// This function performs the following actions (depends on the event):
    ///
    /// * keyboard state is updated
    /// * mouse state is updated
    pub fn handle_event(&mut self, context: &mut Context, event: &Event) -> bool {
        let io = context.io_mut();

        match *event {
            Event::MouseWheel { x, y, .. } => {
                io.add_mouse_wheel_event([x as f32, y as f32]);
                true
            }

            Event::MouseButtonDown { mouse_btn, .. } => {
                self.handle_mouse_button(io, &mouse_btn, true);
                true
            }

            Event::MouseButtonUp { mouse_btn, .. } => {
                self.handle_mouse_button(io, &mouse_btn, false);
                true
            }

            Event::TextInput { ref text, .. } => {
                text.chars().for_each(|c| io.add_input_character(c));
                true
            }

            Event::KeyDown {
                scancode: Some(key),
                keymod,
                ..
            } => {
                handle_key_modifier(io, &keymod);
                handle_key(io, &key, true);
                true
            }

            Event::KeyUp {
                scancode: Some(key),
                keymod,
                ..
            } => {
                handle_key_modifier(io, &keymod);
                handle_key(io, &key, false);
                true
            }

            _ => false,
        }
    }

    /// Frame preparation callback.
    ///
    /// Call this before calling the imgui-rs context `frame` function.
    /// This function performs the following actions:
    ///
    /// * display size and the framebuffer scale is set
    /// * mouse cursor is repositioned (if requested by imgui-rs)
    /// * current mouse cursor position is passed to imgui-rs
    /// * changes mouse cursor icon (if requested by imgui-rs)
    pub fn prepare_frame(
        &mut self,
        context: &mut Context,
        window: &Window,
        event_pump: &EventPump,
    ) {
        let mouse_cursor = context.mouse_cursor();
        let io = context.io_mut();

        // Update delta time
        let now = Instant::now();
        io.update_delta_time(now.duration_since(self.last_frame));
        self.last_frame = now;

        let mouse_state = MouseState::new(event_pump);
        let window_size = window.size();
        let window_drawable_size = window.drawable_size();

        // Set display size and scale here, since SDL 2 doesn't have
        // any easy way to get the scale factor, and changes in said
        // scale factor
        io.display_size = [window_size.0 as f32, window_size.1 as f32];
        io.display_framebuffer_scale = [
            (window_drawable_size.0 as f32) / (window_size.0 as f32),
            (window_drawable_size.1 as f32) / (window_size.1 as f32),
        ];

        // Set mouse position if requested by imgui-rs
        if io.want_set_mouse_pos {
            let mouse_util = window.subsystem().sdl().mouse();
            mouse_util.warp_mouse_in_window(window, io.mouse_pos[0] as i32, io.mouse_pos[1] as i32);
        }

        // Update mouse cursor position
        io.mouse_pos = [mouse_state.x() as f32, mouse_state.y() as f32];

        // Update mouse cursor icon if requested
        if !io
            .config_flags
            .contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE)
        {
            let mouse_util = window.subsystem().sdl().mouse();

            match mouse_cursor {
                Some(mouse_cursor) if !io.mouse_draw_cursor => {
                    let cursor = Cursor::from_system(to_sdl_cursor(mouse_cursor)).unwrap();
                    cursor.set();

                    mouse_util.show_cursor(true);
                    self.cursor_instance = Some(cursor);
                }

                _ => {
                    mouse_util.show_cursor(false);
                    self.cursor_instance = None;
                }
            }
        }
    }
}

/// Returns `true` if the provided event is associated with the provided window.
///
/// # Example
/// ```rust,no_run
/// # let mut event_pump: sdl2::EventPump = unimplemented!();
/// # let window: sdl2::video::Window = unimplemented!();
/// # let mut imgui = imgui::Context::create();
/// # let mut platform = SdlPlatform::init(&mut imgui);
/// use imgui_sdl2_support::{SdlPlatform, filter_event};
/// // Assuming there are multiple windows, we only want to provide the events
/// // of the window where we are rendering to imgui-rs
/// for event in event_pump.poll_iter().filter(|event| filter_event(&window, event)) {
///     platform.handle_event(&mut imgui, &event);
/// }
/// ```
pub fn filter_event(window: &Window, event: &Event) -> bool {
    Some(window.id()) == event.get_window_id()
}

impl SdlPlatform {
    fn handle_mouse_button(
        &mut self,
        io: &mut Io,
        button: &sdl2::mouse::MouseButton,
        pressed: bool,
    ) {
        match button {
            sdl2::mouse::MouseButton::Left => {
                io.add_mouse_button_event(imgui::MouseButton::Left, pressed)
            }
            sdl2::mouse::MouseButton::Right => {
                io.add_mouse_button_event(imgui::MouseButton::Right, pressed)
            }
            sdl2::mouse::MouseButton::Middle => {
                io.add_mouse_button_event(imgui::MouseButton::Middle, pressed)
            }
            sdl2::mouse::MouseButton::X1 => {
                io.add_mouse_button_event(imgui::MouseButton::Extra1, pressed)
            }
            sdl2::mouse::MouseButton::X2 => {
                io.add_mouse_button_event(imgui::MouseButton::Extra2, pressed)
            }
            _ => {}
        }
    }
}

/// Handle changes in the key states.
fn handle_key(io: &mut Io, key: &Scancode, pressed: bool) {
    let igkey = match key {
        Scancode::A => imgui::Key::A,
        Scancode::B => imgui::Key::B,
        Scancode::C => imgui::Key::C,
        Scancode::D => imgui::Key::D,
        Scancode::E => imgui::Key::E,
        Scancode::F => imgui::Key::F,
        Scancode::G => imgui::Key::G,
        Scancode::H => imgui::Key::H,
        Scancode::I => imgui::Key::I,
        Scancode::J => imgui::Key::J,
        Scancode::K => imgui::Key::K,
        Scancode::L => imgui::Key::L,
        Scancode::M => imgui::Key::M,
        Scancode::N => imgui::Key::N,
        Scancode::O => imgui::Key::O,
        Scancode::P => imgui::Key::P,
        Scancode::Q => imgui::Key::Q,
        Scancode::R => imgui::Key::R,
        Scancode::S => imgui::Key::S,
        Scancode::T => imgui::Key::T,
        Scancode::U => imgui::Key::U,
        Scancode::V => imgui::Key::V,
        Scancode::W => imgui::Key::W,
        Scancode::X => imgui::Key::X,
        Scancode::Y => imgui::Key::Y,
        Scancode::Z => imgui::Key::Z,
        Scancode::Num1 => imgui::Key::Keypad1,
        Scancode::Num2 => imgui::Key::Keypad2,
        Scancode::Num3 => imgui::Key::Keypad3,
        Scancode::Num4 => imgui::Key::Keypad4,
        Scancode::Num5 => imgui::Key::Keypad5,
        Scancode::Num6 => imgui::Key::Keypad6,
        Scancode::Num7 => imgui::Key::Keypad7,
        Scancode::Num8 => imgui::Key::Keypad8,
        Scancode::Num9 => imgui::Key::Keypad9,
        Scancode::Num0 => imgui::Key::Keypad0,
        Scancode::Return => imgui::Key::Enter, // TODO: Should this be treated as alias?
        Scancode::Escape => imgui::Key::Escape,
        Scancode::Backspace => imgui::Key::Backspace,
        Scancode::Tab => imgui::Key::Tab,
        Scancode::Space => imgui::Key::Space,
        Scancode::Minus => imgui::Key::Minus,
        Scancode::Equals => imgui::Key::Equal,
        Scancode::LeftBracket => imgui::Key::LeftBracket,
        Scancode::RightBracket => imgui::Key::RightBracket,
        Scancode::Backslash => imgui::Key::Backslash,
        Scancode::Semicolon => imgui::Key::Semicolon,
        Scancode::Apostrophe => imgui::Key::Apostrophe,
        Scancode::Grave => imgui::Key::GraveAccent,
        Scancode::Comma => imgui::Key::Comma,
        Scancode::Period => imgui::Key::Period,
        Scancode::Slash => imgui::Key::Slash,
        Scancode::CapsLock => imgui::Key::CapsLock,
        Scancode::F1 => imgui::Key::F1,
        Scancode::F2 => imgui::Key::F2,
        Scancode::F3 => imgui::Key::F3,
        Scancode::F4 => imgui::Key::F4,
        Scancode::F5 => imgui::Key::F5,
        Scancode::F6 => imgui::Key::F6,
        Scancode::F7 => imgui::Key::F7,
        Scancode::F8 => imgui::Key::F8,
        Scancode::F9 => imgui::Key::F9,
        Scancode::F10 => imgui::Key::F10,
        Scancode::F11 => imgui::Key::F11,
        Scancode::F12 => imgui::Key::F12,
        Scancode::PrintScreen => imgui::Key::PrintScreen,
        Scancode::ScrollLock => imgui::Key::ScrollLock,
        Scancode::Pause => imgui::Key::Pause,
        Scancode::Insert => imgui::Key::Insert,
        Scancode::Home => imgui::Key::Home,
        Scancode::PageUp => imgui::Key::PageUp,
        Scancode::Delete => imgui::Key::Delete,
        Scancode::End => imgui::Key::End,
        Scancode::PageDown => imgui::Key::PageDown,
        Scancode::Right => imgui::Key::RightArrow,
        Scancode::Left => imgui::Key::LeftArrow,
        Scancode::Down => imgui::Key::DownArrow,
        Scancode::Up => imgui::Key::UpArrow,
        Scancode::KpDivide => imgui::Key::KeypadDivide,
        Scancode::KpMultiply => imgui::Key::KeypadMultiply,
        Scancode::KpMinus => imgui::Key::KeypadSubtract,
        Scancode::KpPlus => imgui::Key::KeypadAdd,
        Scancode::KpEnter => imgui::Key::KeypadEnter,
        Scancode::Kp1 => imgui::Key::Keypad1,
        Scancode::Kp2 => imgui::Key::Keypad2,
        Scancode::Kp3 => imgui::Key::Keypad3,
        Scancode::Kp4 => imgui::Key::Keypad4,
        Scancode::Kp5 => imgui::Key::Keypad5,
        Scancode::Kp6 => imgui::Key::Keypad6,
        Scancode::Kp7 => imgui::Key::Keypad7,
        Scancode::Kp8 => imgui::Key::Keypad8,
        Scancode::Kp9 => imgui::Key::Keypad9,
        Scancode::Kp0 => imgui::Key::Keypad0,
        Scancode::KpPeriod => imgui::Key::KeypadDecimal,
        Scancode::Application => imgui::Key::Menu,
        Scancode::KpEquals => imgui::Key::KeypadEqual,
        Scancode::Menu => imgui::Key::Menu,
        Scancode::LCtrl => imgui::Key::LeftCtrl,
        Scancode::LShift => imgui::Key::LeftShift,
        Scancode::LAlt => imgui::Key::LeftAlt,
        Scancode::LGui => imgui::Key::LeftSuper,
        Scancode::RCtrl => imgui::Key::RightCtrl,
        Scancode::RShift => imgui::Key::RightShift,
        Scancode::RAlt => imgui::Key::RightAlt,
        Scancode::RGui => imgui::Key::RightSuper,
        _ => {
            // Ignore unknown keys
            return;
        }
    };

    io.add_key_event(igkey, pressed);
}

/// Handle changes in the key modifier states.
fn handle_key_modifier(io: &mut Io, keymod: &Mod) {
    io.add_key_event(
        imgui::Key::ModShift,
        keymod.intersects(Mod::LSHIFTMOD | Mod::RSHIFTMOD),
    );
    io.add_key_event(
        imgui::Key::ModCtrl,
        keymod.intersects(Mod::LCTRLMOD | Mod::RCTRLMOD),
    );
    io.add_key_event(
        imgui::Key::ModAlt,
        keymod.intersects(Mod::LALTMOD | Mod::RALTMOD),
    );
    io.add_key_event(
        imgui::Key::ModSuper,
        keymod.intersects(Mod::LGUIMOD | Mod::RGUIMOD),
    );
}

/// Map an imgui::MouseCursor to an equivalent sdl2::mouse::SystemCursor.
fn to_sdl_cursor(cursor: MouseCursor) -> SystemCursor {
    match cursor {
        MouseCursor::Arrow => SystemCursor::Arrow,
        MouseCursor::TextInput => SystemCursor::IBeam,
        MouseCursor::ResizeAll => SystemCursor::SizeAll,
        MouseCursor::ResizeNS => SystemCursor::SizeNS,
        MouseCursor::ResizeEW => SystemCursor::SizeWE,
        MouseCursor::ResizeNESW => SystemCursor::SizeNESW,
        MouseCursor::ResizeNWSE => SystemCursor::SizeNWSE,
        MouseCursor::Hand => SystemCursor::Hand,
        MouseCursor::NotAllowed => SystemCursor::No,
    }
}