2021-10-06 11:31:35 -04:00

297 lines
9.7 KiB
Rust

//! This crate provides a SDL 2 based backend platform for imgui-rs.
//!
//! A backend platform handles window/input device events and manages their
//! state.
//!
//! # Using the library
//!
//! 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)
//!
//! For a complete example, take a look at the imgui-rs' GitHub repository.
use std::time::Instant;
use imgui::{BackendFlags, ConfigFlags, Context, Io, Key, MouseCursor};
use sdl2::{
event::Event,
keyboard::{Mod, Scancode},
mouse::{Cursor, MouseButton, MouseState, SystemCursor},
video::Window,
EventPump,
};
/// State of a single mouse button. Used so that we can detect cases where mouse
/// press and release occur on the same frame (seems surprisingly frequent on
/// macOS now...)
#[derive(Debug, Clone, Copy, Default)]
struct Button {
pressed_this_frame: bool,
state: bool,
}
impl Button {
const fn new() -> Button {
Button {
pressed_this_frame: false,
state: false,
}
}
fn get(&self) -> bool {
self.pressed_this_frame || self.state
}
fn set(&mut self, pressed: bool) {
self.state = pressed;
if pressed {
self.pressed_this_frame = true;
}
}
}
/// Handle changes in the key modifier states.
fn handle_key_modifier(io: &mut Io, keymod: &Mod) {
io.key_shift = keymod.intersects(Mod::LSHIFTMOD | Mod::RSHIFTMOD);
io.key_ctrl = keymod.intersects(Mod::LCTRLMOD | Mod::RCTRLMOD);
io.key_alt = keymod.intersects(Mod::LALTMOD | Mod::RALTMOD);
io.key_super = 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,
}
}
/// Returns `true` if the provided event is associated with the provided window.
///
/// # Example
/// ```rust,no_run
/// // 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()
}
/// SDL 2 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,
mouse_buttons: [Button; 5],
}
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 init(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);
io[Key::Tab] = Scancode::Tab as _;
io[Key::LeftArrow] = Scancode::Left as _;
io[Key::RightArrow] = Scancode::Right as _;
io[Key::UpArrow] = Scancode::Up as _;
io[Key::DownArrow] = Scancode::Down as _;
io[Key::PageUp] = Scancode::PageUp as _;
io[Key::PageDown] = Scancode::PageDown as _;
io[Key::Home] = Scancode::Home as _;
io[Key::End] = Scancode::End as _;
io[Key::Insert] = Scancode::Insert as _;
io[Key::Delete] = Scancode::Delete as _;
io[Key::Backspace] = Scancode::Backspace as _;
io[Key::Space] = Scancode::Space as _;
io[Key::Enter] = Scancode::Return as _;
io[Key::Escape] = Scancode::Escape as _;
io[Key::KeyPadEnter] = Scancode::KpEnter as _;
io[Key::A] = Scancode::A as _;
io[Key::C] = Scancode::C as _;
io[Key::V] = Scancode::V as _;
io[Key::X] = Scancode::X as _;
io[Key::Y] = Scancode::Y as _;
io[Key::Z] = Scancode::Z as _;
imgui.set_platform_name(Some(format!(
"imgui-sdl2-support {}",
env!("CARGO_PKG_VERSION")
)));
SdlPlatform {
cursor_instance: None,
last_frame: Instant::now(),
mouse_buttons: [Button::new(); 5],
}
}
/// 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) {
let io = context.io_mut();
match *event {
Event::MouseWheel { x, y, .. } => {
io.mouse_wheel = y as f32;
io.mouse_wheel_h = x as f32;
}
Event::MouseButtonDown { mouse_btn, .. } => {
self.handle_mouse_button(&mouse_btn, true);
}
Event::MouseButtonUp { mouse_btn, .. } => {
self.handle_mouse_button(&mouse_btn, false);
}
Event::TextInput { ref text, .. } => {
text.chars().for_each(|c| io.add_input_character(c));
}
Event::KeyDown {
scancode: Some(key),
keymod,
..
} => {
io.keys_down[key as usize] = true;
handle_key_modifier(io, &keymod);
}
Event::KeyUp {
scancode: Some(key),
keymod,
..
} => {
io.keys_down[key as usize] = false;
handle_key_modifier(io, &keymod);
}
_ => {}
}
}
/// 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),
];
// Update mouse button state
for (io_down, button) in io.mouse_down.iter_mut().zip(&mut self.mouse_buttons) {
*io_down = button.get();
*button = Button::new();
}
// 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;
}
}
}
}
}
impl SdlPlatform {
fn handle_mouse_button(&mut self, button: &MouseButton, pressed: bool) {
match button {
MouseButton::Left => self.mouse_buttons[0].set(pressed),
MouseButton::Right => self.mouse_buttons[1].set(pressed),
MouseButton::Middle => self.mouse_buttons[2].set(pressed),
MouseButton::X1 => self.mouse_buttons[3].set(pressed),
MouseButton::X2 => self.mouse_buttons[4].set(pressed),
_ => {}
}
}
}