use dear_imgui_rs::Context;
use dear_imgui_rs::input::MouseSource;
use winit::event::{DeviceEvent, ElementState, Ime, KeyEvent, MouseScrollDelta, TouchPhase};
use winit::window::Window;
use crate::input::{to_imgui_mouse_button, winit_key_to_imgui_key};
use crate::sanitize;
pub fn handle_keyboard_input(event: &KeyEvent, imgui_ctx: &mut Context) -> bool {
let io = imgui_ctx.io_mut();
if event.state.is_pressed()
&& let Some(txt) = &event.text
{
for ch in txt.chars() {
if ch != '\u{7f}' {
io.add_input_character(ch);
}
}
}
if let Some(imgui_key) = winit_key_to_imgui_key(&event.logical_key, event.physical_key) {
let pressed = event.state == ElementState::Pressed;
io.add_key_event(imgui_key, pressed);
return io.want_capture_keyboard();
}
false
}
pub fn handle_mouse_wheel(
delta: MouseScrollDelta,
phase: TouchPhase,
scale_factor: f64,
imgui_ctx: &mut Context,
) -> bool {
if phase == TouchPhase::Cancelled {
return imgui_ctx.io().want_capture_mouse();
}
let io = imgui_ctx.io_mut();
io.add_mouse_source_event(MouseSource::Mouse);
io.add_mouse_wheel_event(normalize_mouse_wheel_delta(delta, scale_factor));
io.want_capture_mouse()
}
pub fn handle_mouse_button(
button: winit::event::MouseButton,
state: ElementState,
imgui_ctx: &mut Context,
) -> bool {
if let Some(imgui_button) = to_imgui_mouse_button(button) {
let pressed = state == ElementState::Pressed;
{
let io = imgui_ctx.io_mut();
io.add_mouse_source_event(MouseSource::Mouse);
io.add_mouse_button_event(imgui_button, pressed);
}
return imgui_ctx.io().want_capture_mouse();
}
false
}
pub fn handle_cursor_moved(position: [f64; 2], imgui_ctx: &mut Context) -> bool {
let Some(position) = sanitize::finite_vec2_f64_to_f32(position) else {
return imgui_ctx.io().want_capture_mouse();
};
{
let io = imgui_ctx.io_mut();
io.add_mouse_source_event(MouseSource::Mouse);
io.add_mouse_pos_event(position);
}
imgui_ctx.io().want_capture_mouse()
}
pub(crate) fn modifier_key_events(
modifiers: &winit::event::Modifiers,
) -> [(dear_imgui_rs::Key, bool); 4] {
let state = modifiers.state();
[
(dear_imgui_rs::Key::ModShift, state.shift_key()),
(dear_imgui_rs::Key::ModCtrl, state.control_key()),
(dear_imgui_rs::Key::ModAlt, state.alt_key()),
(dear_imgui_rs::Key::ModSuper, state.super_key()),
]
}
fn normalize_mouse_wheel_delta(delta: MouseScrollDelta, scale_factor: f64) -> [f32; 2] {
match delta {
MouseScrollDelta::LineDelta(horizontal, vertical) => [
sanitize::finite_or_zero(horizontal),
sanitize::finite_or_zero(vertical),
],
MouseScrollDelta::PixelDelta(position) => {
let scale_factor = sanitize::positive_finite_or(scale_factor, 1.0);
[
sanitize::finite_f64_to_f32(position.x / scale_factor * 0.01).unwrap_or(0.0),
sanitize::finite_f64_to_f32(position.y / scale_factor * 0.01).unwrap_or(0.0),
]
}
}
}
pub fn handle_modifiers_changed(modifiers: &winit::event::Modifiers, imgui_ctx: &mut Context) {
let io = imgui_ctx.io_mut();
for (key, pressed) in modifier_key_events(modifiers) {
io.add_key_event(key, pressed);
}
}
pub fn handle_ime_event(ime: &Ime, imgui_ctx: &mut Context) {
match ime {
Ime::Preedit(_text, _cursor_range) => {
}
Ime::Commit(text) => {
for ch in text.chars() {
if !ch.is_control() {
imgui_ctx.io_mut().add_input_character(ch);
}
}
}
Ime::Enabled => {
}
Ime::Disabled => {
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TouchAction {
Press,
Move,
Release,
}
pub(crate) fn touch_transition(
active_id: Option<u64>,
event_id: u64,
phase: TouchPhase,
) -> (Option<u64>, Option<TouchAction>) {
match phase {
TouchPhase::Started if active_id.is_none() => (Some(event_id), Some(TouchAction::Press)),
TouchPhase::Moved if active_id == Some(event_id) => (active_id, Some(TouchAction::Move)),
TouchPhase::Ended | TouchPhase::Cancelled if active_id == Some(event_id) => {
(None, Some(TouchAction::Release))
}
_ => (active_id, None),
}
}
pub(crate) fn touch_logical_position(
touch: &winit::event::Touch,
window: &Window,
) -> Option<[f32; 2]> {
let position = touch
.location
.to_logical::<f64>(sanitize::positive_finite_or(window.scale_factor(), 1.0));
sanitize::finite_position(position)
}
pub(crate) fn handle_touch_event_at(
action: TouchAction,
position: Option<[f32; 2]>,
viewport: Option<dear_imgui_rs::Id>,
imgui_ctx: &mut Context,
) -> bool {
let io = imgui_ctx.io_mut();
io.add_mouse_source_event(MouseSource::TouchScreen);
if let Some(viewport) = viewport {
io.add_mouse_viewport_event(viewport);
}
if let Some(position) = position {
io.add_mouse_pos_event(position);
}
match action {
TouchAction::Press => {
io.add_mouse_button_event(dear_imgui_rs::input::MouseButton::Left, true);
}
TouchAction::Move => {}
TouchAction::Release => {
io.add_mouse_button_event(dear_imgui_rs::input::MouseButton::Left, false);
}
}
true
}
pub fn handle_device_event(_event: &DeviceEvent) {
}
pub fn handle_focused(focused: bool, imgui_ctx: &mut Context) -> bool {
imgui_ctx.io_mut().add_focus_event(focused);
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::test_sync::lock_context;
use dear_imgui_rs::Context;
use winit::event::{ElementState, MouseButton, TouchPhase};
use winit::keyboard::ModifiersState;
#[test]
fn test_keyboard_input_handling() {
}
#[test]
fn test_mouse_button_handling() {
let _guard = lock_context();
let mut ctx = Context::create();
let handled = handle_mouse_button(MouseButton::Left, ElementState::Pressed, &mut ctx);
let _ = handled; }
#[test]
fn test_cursor_moved() {
let _guard = lock_context();
let mut ctx = Context::create();
let handled = handle_cursor_moved([100.0, 200.0], &mut ctx);
let _ = handled; }
#[test]
fn test_cursor_moved_ignores_non_finite_position() {
let _guard = lock_context();
let mut ctx = Context::create();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
handle_cursor_moved([f64::NAN, f64::INFINITY], &mut ctx);
}));
assert!(result.is_ok());
}
#[test]
fn test_mouse_wheel_ignores_non_finite_line_delta() {
let _guard = lock_context();
let mut ctx = Context::create();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
handle_mouse_wheel(
MouseScrollDelta::LineDelta(f32::NAN, f32::INFINITY),
TouchPhase::Moved,
1.0,
&mut ctx,
);
}));
assert!(result.is_ok());
}
#[test]
fn pixel_wheel_preserves_fractional_logical_motion() {
assert_eq!(
normalize_mouse_wheel_delta(
MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition::new(25.0, -10.0)),
2.0,
),
[0.125, -0.05]
);
}
#[test]
fn test_modifiers_changed_updates_key_mods() {
let _guard = lock_context();
let mut ctx = Context::create();
let io = ctx.io_mut();
io.set_display_size([1.0, 1.0]);
ctx.font_atlas()
.try_claim_legacy_renderer()
.expect("the input-event test uses headless legacy rendering")
.build();
let macos_behaviors = ctx.io().config_macosx_behaviors();
let modifiers: winit::event::Modifiers = ModifiersState::CONTROL.into();
handle_modifiers_changed(&modifiers, &mut ctx);
let ui = ctx.frame();
if macos_behaviors {
assert!(!ui.io().key_ctrl());
assert!(ui.io().key_super());
} else {
assert!(ui.io().key_ctrl());
assert!(!ui.io().key_super());
}
assert!(!ui.io().key_shift());
assert!(!ui.io().key_alt());
}
#[test]
fn touch_transition_keeps_the_first_finger_until_release() {
assert_eq!(
touch_transition(None, 7, TouchPhase::Started),
(Some(7), Some(TouchAction::Press))
);
assert_eq!(
touch_transition(Some(7), 8, TouchPhase::Started),
(Some(7), None)
);
assert_eq!(
touch_transition(Some(7), 7, TouchPhase::Moved),
(Some(7), Some(TouchAction::Move))
);
assert_eq!(
touch_transition(Some(7), 7, TouchPhase::Ended),
(None, Some(TouchAction::Release))
);
}
}