use std::collections::HashMap;
use glam::Vec2;
use martensite_core::{WidgetArena, WidgetId};
use crate::dpi::DpiScale;
use crate::hit_test::HitTester;
use crate::WindowEvent;
use crate::WindowId;
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct ModifierKeys: u8 {
const SHIFT = 1 << 0;
const CONTROL = 1 << 1;
const ALT = 1 << 2;
const COMMAND = 1 << 3;
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct PointerId(u32);
impl PointerId {
pub const PRIMARY: Self = Self::new(0);
pub const fn new(id: u32) -> Self {
Self(id)
}
pub const fn get(self) -> u32 {
self.0
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum PointerState {
Pressed,
Released,
Moved,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum MouseButton {
Left,
Right,
Middle,
Back,
Forward,
Other(u8),
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct PointerEvent {
pub pointer_id: PointerId,
pub position: Vec2,
pub state: PointerState,
pub button: Option<MouseButton>,
pub modifiers: ModifierKeys,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PointerCapture {
targets: HashMap<PointerId, WidgetId>,
}
impl PointerCapture {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn capture(&mut self, pointer_id: PointerId, widget_id: WidgetId) {
self.targets.insert(pointer_id, widget_id);
}
pub fn capture_primary(&mut self, widget_id: WidgetId) {
self.capture(PointerId::PRIMARY, widget_id);
}
pub fn release(&mut self, pointer_id: PointerId) {
self.targets.remove(&pointer_id);
}
pub fn release_primary(&mut self) {
self.release(PointerId::PRIMARY);
}
#[must_use]
pub fn captured(&self, pointer_id: PointerId) -> Option<WidgetId> {
self.targets.get(&pointer_id).copied()
}
#[must_use]
pub fn captured_primary(&self) -> Option<WidgetId> {
self.captured(PointerId::PRIMARY)
}
pub fn clear(&mut self) {
self.targets.clear();
}
#[must_use]
pub fn active_captures(&self) -> usize {
self.targets.len()
}
}
#[derive(Debug, Default)]
pub struct MouseTracker {
positions: HashMap<WindowId, Vec2>,
hovered: HashMap<WindowId, WidgetId>,
}
impl MouseTracker {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn update_position(&mut self, window_id: WindowId, pos: Vec2) {
self.positions.insert(window_id, pos);
}
#[must_use]
pub fn position(&self, window_id: WindowId) -> Option<Vec2> {
self.positions.get(&window_id).copied()
}
#[must_use]
pub fn hovered_widget(&self, window_id: WindowId) -> Option<WidgetId> {
self.hovered.get(&window_id).copied()
}
pub fn set_hovered(&mut self, window_id: WindowId, widget_id: Option<WidgetId>) {
match widget_id {
Some(id) => {
self.hovered.insert(window_id, id);
}
None => {
self.hovered.remove(&window_id);
}
}
}
pub fn clear_window(&mut self, window_id: WindowId) {
self.positions.remove(&window_id);
self.hovered.remove(&window_id);
}
#[must_use]
pub fn tracked_window_count(&self) -> usize {
self.positions.len()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum EventDispatchOutcome {
Handled(WidgetId),
Unhandled,
Ignored,
}
#[derive(Debug, Default)]
pub struct EventRouter {
capture: PointerCapture,
mouse: MouseTracker,
}
impl EventRouter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn pointer_capture(&self) -> &PointerCapture {
&self.capture
}
pub fn pointer_capture_mut(&mut self) -> &mut PointerCapture {
&mut self.capture
}
#[must_use]
pub fn mouse_tracker(&self) -> &MouseTracker {
&self.mouse
}
pub fn mouse_tracker_mut(&mut self) -> &mut MouseTracker {
&mut self.mouse
}
pub fn capture_pointer(&mut self, pointer_id: PointerId, widget_id: WidgetId) {
self.capture.capture(pointer_id, widget_id);
}
pub fn capture_pointer_primary(&mut self, widget_id: WidgetId) {
self.capture.capture_primary(widget_id);
}
pub fn release_pointer(&mut self, pointer_id: PointerId) {
self.capture.release(pointer_id);
}
pub fn release_pointer_primary(&mut self) {
self.capture.release_primary();
}
#[must_use]
pub fn captured_widget(&self, pointer_id: PointerId) -> Option<WidgetId> {
self.capture.captured(pointer_id)
}
#[must_use]
pub fn captured_widget_primary(&self) -> Option<WidgetId> {
self.capture.captured_primary()
}
pub fn route_pointer_event(
&mut self,
arena: &WidgetArena,
root: WidgetId,
window_id: WindowId,
event: &PointerEvent,
) -> EventDispatchOutcome {
self.mouse.update_position(window_id, event.position);
let target = if let Some(captured) = self.capture.captured(event.pointer_id) {
if arena.is_alive(captured) {
Some(captured)
} else {
self.capture.release(event.pointer_id);
let tester = HitTester::new(arena);
tester.hit_test(root, event.position).map(|r| r.widget_id)
}
} else {
let tester = HitTester::new(arena);
tester.hit_test(root, event.position).map(|r| r.widget_id)
};
self.mouse.set_hovered(window_id, target);
match target {
Some(id) => EventDispatchOutcome::Handled(id),
None => EventDispatchOutcome::Unhandled,
}
}
pub fn route_keyboard_event(&mut self, focused: Option<WidgetId>) -> EventDispatchOutcome {
match focused {
Some(id) => EventDispatchOutcome::Handled(id),
None => EventDispatchOutcome::Ignored,
}
}
pub fn route_scroll_event(&mut self, window_id: WindowId, delta: Vec2) -> EventDispatchOutcome {
let _ = delta;
match self.mouse.hovered_widget(window_id) {
Some(id) => EventDispatchOutcome::Handled(id),
None => EventDispatchOutcome::Unhandled,
}
}
}
#[must_use]
pub fn convert_mouse_button(button: winit::event::MouseButton) -> MouseButton {
match button {
winit::event::MouseButton::Left => MouseButton::Left,
winit::event::MouseButton::Right => MouseButton::Right,
winit::event::MouseButton::Middle => MouseButton::Middle,
winit::event::MouseButton::Back => MouseButton::Back,
winit::event::MouseButton::Forward => MouseButton::Forward,
_ => MouseButton::Other(button as u8 + 1),
}
}
#[must_use]
pub fn convert_modifiers(modifiers: &winit::event::Modifiers) -> ModifierKeys {
convert_modifiers_state(&modifiers.state())
}
#[must_use]
pub fn convert_modifiers_state(state: &winit::keyboard::ModifiersState) -> ModifierKeys {
let mut keys = ModifierKeys::empty();
if state.shift_key() {
keys |= ModifierKeys::SHIFT;
}
if state.control_key() {
keys |= ModifierKeys::CONTROL;
}
if state.alt_key() {
keys |= ModifierKeys::ALT;
}
if state.meta_key() {
keys |= ModifierKeys::COMMAND;
}
keys
}
#[must_use]
pub fn convert_window_event(event: &WindowEvent, scale: &DpiScale) -> Option<PointerEvent> {
use winit::event::{ElementState, WindowEvent};
match event {
WindowEvent::PointerMoved {
position, source, ..
} => {
let pos = physical_to_logical(*position, scale);
Some(PointerEvent {
pointer_id: pointer_id_from_source(source),
position: pos,
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
})
}
WindowEvent::PointerButton {
state,
position,
button,
..
} => {
let pos = physical_to_logical(*position, scale);
let (pointer_id, mb) = button_source_info(button);
let pointer_state = match state {
ElementState::Pressed => PointerState::Pressed,
ElementState::Released => PointerState::Released,
};
Some(PointerEvent {
pointer_id,
position: pos,
state: pointer_state,
button: mb,
modifiers: ModifierKeys::empty(),
})
}
_ => None,
}
}
fn physical_to_logical(position: winit::dpi::PhysicalPosition<f64>, scale: &DpiScale) -> Vec2 {
Vec2::new(
scale.to_logical(position.x) as f32,
scale.to_logical(position.y) as f32,
)
}
fn pointer_id_from_source(source: &winit::event::PointerSource) -> PointerId {
use winit::event::PointerSource;
match source {
PointerSource::Touch { finger_id, .. } => PointerId::new(finger_id.into_raw() as u32),
_ => PointerId::PRIMARY,
}
}
fn button_source_info(button: &winit::event::ButtonSource) -> (PointerId, Option<MouseButton>) {
use winit::event::{ButtonSource, TabletToolButton};
match button {
ButtonSource::Mouse(mouse) => (PointerId::PRIMARY, Some(convert_mouse_button(*mouse))),
ButtonSource::Touch { finger_id, .. } => (
PointerId::new(finger_id.into_raw() as u32),
Some(MouseButton::Left),
),
ButtonSource::TabletTool {
button: tool_button,
..
} => {
let mb = match tool_button {
TabletToolButton::Contact => Some(MouseButton::Left),
TabletToolButton::Barrel => Some(MouseButton::Right),
TabletToolButton::Other(raw) => match raw {
1 => Some(MouseButton::Middle),
3 => Some(MouseButton::Back),
4 => Some(MouseButton::Forward),
_ => Some(MouseButton::Other(*raw as u8)),
},
};
(PointerId::PRIMARY, mb)
}
ButtonSource::Unknown(_) => (PointerId::PRIMARY, None),
_ => (PointerId::PRIMARY, None),
}
}
#[cfg(test)]
mod tests {
use super::*;
use martensite_core::{ColdNode, HotNode, NodeFlags, Rect, WidgetArena};
use winit::dpi::PhysicalPosition;
use winit::event::{
ButtonSource, ElementState, FingerId, Force, Modifiers, MouseButton as WinitMouseButton,
PointerSource, WindowEvent,
};
use winit::keyboard::{ModifiersKeys, ModifiersState};
fn hot_node(x: f32, y: f32, w: f32, h: f32) -> HotNode {
HotNode {
bounds: Rect::new(x, y, w, h),
flags: NodeFlags::VISIBLE | NodeFlags::HIT_TEST_ENABLED,
..HotNode::default()
}
}
fn insert(arena: &mut WidgetArena, x: f32, y: f32, w: f32, h: f32) -> WidgetId {
arena.insert(hot_node(x, y, w, h), ColdNode::default())
}
#[test]
fn pointer_capture_starts_empty() {
let cap = PointerCapture::new();
assert!(cap.captured_primary().is_none());
assert_eq!(cap.active_captures(), 0);
}
#[test]
fn pointer_capture_routes_all_events_to_captured_widget() {
let mut arena = WidgetArena::new();
let a = insert(&mut arena, 0.0, 0.0, 100.0, 100.0);
let b = insert(&mut arena, 200.0, 0.0, 100.0, 100.0);
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
router.capture_pointer_primary(a);
let event = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(250.0, 50.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, a, win, &event),
EventDispatchOutcome::Handled(a)
);
assert_eq!(
router.route_pointer_event(&arena, b, win, &event),
EventDispatchOutcome::Handled(a)
);
}
#[test]
fn pointer_capture_release_resumes_hit_testing() {
let mut arena = WidgetArena::new();
let a = insert(&mut arena, 0.0, 0.0, 100.0, 100.0);
let b = insert(&mut arena, 200.0, 0.0, 100.0, 100.0);
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
router.capture_pointer_primary(a);
let event = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(250.0, 50.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, a, win, &event),
EventDispatchOutcome::Handled(a)
);
router.release_pointer_primary();
assert_eq!(
router.route_pointer_event(&arena, b, win, &event),
EventDispatchOutcome::Handled(b)
);
}
#[test]
fn pointer_capture_dead_widget_auto_releases() {
let mut arena = WidgetArena::new();
let a = insert(&mut arena, 0.0, 0.0, 100.0, 100.0);
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
router.capture_pointer_primary(a);
arena.remove(a);
let event = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(50.0, 50.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, a, win, &event),
EventDispatchOutcome::Unhandled
);
assert!(router.captured_widget_primary().is_none());
}
#[test]
fn pointer_capture_default_is_empty() {
let cap = PointerCapture::default();
assert!(cap.captured_primary().is_none());
assert_eq!(cap.active_captures(), 0);
}
#[test]
fn pointer_capture_multi_pointer_independent() {
let mut cap = PointerCapture::new();
let widget_a = WidgetId::from_parts(1, 1);
let widget_b = WidgetId::from_parts(2, 1);
let finger_1 = PointerId::new(1);
let finger_2 = PointerId::new(2);
cap.capture(finger_1, widget_a);
cap.capture(finger_2, widget_b);
assert_eq!(cap.active_captures(), 2);
assert_eq!(cap.captured(finger_1), Some(widget_a));
assert_eq!(cap.captured(finger_2), Some(widget_b));
cap.release(finger_1);
assert_eq!(cap.active_captures(), 1);
assert!(cap.captured(finger_1).is_none());
assert_eq!(cap.captured(finger_2), Some(widget_b));
cap.clear();
assert_eq!(cap.active_captures(), 0);
assert!(cap.captured(finger_2).is_none());
}
#[test]
fn mouse_tracker_position_tracking_per_window() {
let mut tracker = MouseTracker::new();
let w1 = WindowId::from_raw(1);
let w2 = WindowId::from_raw(2);
assert_eq!(tracker.position(w1), None);
tracker.update_position(w1, Vec2::new(10.0, 20.0));
tracker.update_position(w2, Vec2::new(30.0, 40.0));
assert_eq!(tracker.position(w1), Some(Vec2::new(10.0, 20.0)));
assert_eq!(tracker.position(w2), Some(Vec2::new(30.0, 40.0)));
tracker.update_position(w1, Vec2::new(99.0, 99.0));
assert_eq!(tracker.position(w1), Some(Vec2::new(99.0, 99.0)));
assert_eq!(tracker.position(w2), Some(Vec2::new(30.0, 40.0)));
assert_eq!(tracker.tracked_window_count(), 2);
}
#[test]
fn mouse_tracker_hover_state_tracking() {
let mut tracker = MouseTracker::new();
let win = WindowId::from_raw(1);
let widget = WidgetId::from_parts(1, 1);
assert_eq!(tracker.hovered_widget(win), None);
tracker.set_hovered(win, Some(widget));
assert_eq!(tracker.hovered_widget(win), Some(widget));
tracker.set_hovered(win, None);
assert_eq!(tracker.hovered_widget(win), None);
}
#[test]
fn mouse_tracker_hover_is_per_window() {
let mut tracker = MouseTracker::new();
let w1 = WindowId::from_raw(1);
let w2 = WindowId::from_raw(2);
let widget_a = WidgetId::from_parts(1, 1);
let widget_b = WidgetId::from_parts(2, 1);
tracker.set_hovered(w1, Some(widget_a));
tracker.set_hovered(w2, Some(widget_b));
assert_eq!(tracker.hovered_widget(w1), Some(widget_a));
assert_eq!(tracker.hovered_widget(w2), Some(widget_b));
}
#[test]
fn mouse_tracker_clear_window_removes_all_state() {
let mut tracker = MouseTracker::new();
let win = WindowId::from_raw(1);
let widget = WidgetId::from_parts(1, 1);
tracker.update_position(win, Vec2::new(5.0, 5.0));
tracker.set_hovered(win, Some(widget));
assert_eq!(tracker.tracked_window_count(), 1);
tracker.clear_window(win);
assert_eq!(tracker.position(win), None);
assert_eq!(tracker.hovered_widget(win), None);
assert_eq!(tracker.tracked_window_count(), 0);
}
#[test]
fn router_routes_to_correct_widget_by_position() {
let mut arena = WidgetArena::new();
let root = insert(&mut arena, 0.0, 0.0, 200.0, 200.0);
let child = insert(&mut arena, 50.0, 50.0, 100.0, 100.0);
arena.append_child(root, child).unwrap();
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
let over_child = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(75.0, 75.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, root, win, &over_child),
EventDispatchOutcome::Handled(child)
);
let over_root = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(10.0, 10.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, root, win, &over_root),
EventDispatchOutcome::Handled(root)
);
}
#[test]
fn router_captured_widget_takes_priority_over_hit_test() {
let mut arena = WidgetArena::new();
let root = insert(&mut arena, 0.0, 0.0, 200.0, 200.0);
let child = insert(&mut arena, 50.0, 50.0, 100.0, 100.0);
arena.append_child(root, child).unwrap();
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
router.capture_pointer_primary(root);
let event = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(75.0, 75.0),
state: PointerState::Pressed,
button: Some(MouseButton::Left),
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, root, win, &event),
EventDispatchOutcome::Handled(root)
);
}
#[test]
fn router_keyboard_event_routes_to_focused() {
let mut router = EventRouter::new();
let focused = WidgetId::from_parts(3, 1);
assert_eq!(
router.route_keyboard_event(Some(focused)),
EventDispatchOutcome::Handled(focused)
);
assert_eq!(
router.route_keyboard_event(None),
EventDispatchOutcome::Ignored
);
}
#[test]
fn router_scroll_routes_to_hovered_widget() {
let mut arena = WidgetArena::new();
let root = insert(&mut arena, 0.0, 0.0, 200.0, 200.0);
let child = insert(&mut arena, 50.0, 50.0, 100.0, 100.0);
arena.append_child(root, child).unwrap();
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
let over_child = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(75.0, 75.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
router.route_pointer_event(&arena, root, win, &over_child);
assert_eq!(
router.route_scroll_event(win, Vec2::new(0.0, 10.0)),
EventDispatchOutcome::Handled(child)
);
let over_root = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(10.0, 10.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
router.route_pointer_event(&arena, root, win, &over_root);
assert_eq!(
router.route_scroll_event(win, Vec2::new(0.0, 10.0)),
EventDispatchOutcome::Handled(root)
);
}
#[test]
fn router_scroll_unhandled_when_no_hover() {
let mut router = EventRouter::new();
let win = WindowId::from_raw(1);
assert_eq!(
router.route_scroll_event(win, Vec2::new(0.0, 1.0)),
EventDispatchOutcome::Unhandled
);
}
#[test]
fn modifier_keys_all_combinations() {
assert_eq!(ModifierKeys::empty(), ModifierKeys::empty());
assert!(ModifierKeys::SHIFT.contains(ModifierKeys::SHIFT));
assert!(!ModifierKeys::SHIFT.contains(ModifierKeys::CONTROL));
let all =
ModifierKeys::SHIFT | ModifierKeys::CONTROL | ModifierKeys::ALT | ModifierKeys::COMMAND;
assert!(all.contains(ModifierKeys::SHIFT));
assert!(all.contains(ModifierKeys::CONTROL));
assert!(all.contains(ModifierKeys::ALT));
assert!(all.contains(ModifierKeys::COMMAND));
let shift_ctrl = ModifierKeys::SHIFT | ModifierKeys::CONTROL;
assert_eq!(shift_ctrl, ModifierKeys::SHIFT | ModifierKeys::CONTROL);
assert_ne!(shift_ctrl, ModifierKeys::SHIFT | ModifierKeys::ALT);
let mut m = shift_ctrl;
m.remove(ModifierKeys::SHIFT);
assert_eq!(m, ModifierKeys::CONTROL);
}
#[test]
fn convert_modifiers_state_all_combinations() {
let empty = convert_modifiers_state(&ModifiersState::empty());
assert_eq!(empty, ModifierKeys::empty());
let shift = convert_modifiers_state(&ModifiersState::SHIFT);
assert_eq!(shift, ModifierKeys::SHIFT);
let ctrl = convert_modifiers_state(&ModifiersState::CONTROL);
assert_eq!(ctrl, ModifierKeys::CONTROL);
let alt = convert_modifiers_state(&ModifiersState::ALT);
assert_eq!(alt, ModifierKeys::ALT);
let meta = convert_modifiers_state(&ModifiersState::META);
assert_eq!(meta, ModifierKeys::COMMAND);
let combo = convert_modifiers_state(&(ModifiersState::SHIFT | ModifiersState::CONTROL));
assert_eq!(combo, ModifierKeys::SHIFT | ModifierKeys::CONTROL);
let all = convert_modifiers_state(
&(ModifiersState::SHIFT
| ModifiersState::CONTROL
| ModifiersState::ALT
| ModifiersState::META),
);
assert_eq!(
all,
ModifierKeys::SHIFT | ModifierKeys::CONTROL | ModifierKeys::ALT | ModifierKeys::COMMAND
);
}
#[test]
fn convert_modifiers_from_modifiers_value() {
let mods = Modifiers::new(
ModifiersState::SHIFT | ModifiersState::ALT,
ModifiersKeys::empty(),
);
assert_eq!(
convert_modifiers(&mods),
ModifierKeys::SHIFT | ModifierKeys::ALT
);
}
#[test]
fn convert_mouse_button_named() {
assert_eq!(
convert_mouse_button(WinitMouseButton::Left),
MouseButton::Left
);
assert_eq!(
convert_mouse_button(WinitMouseButton::Right),
MouseButton::Right
);
assert_eq!(
convert_mouse_button(WinitMouseButton::Middle),
MouseButton::Middle
);
assert_eq!(
convert_mouse_button(WinitMouseButton::Back),
MouseButton::Back
);
assert_eq!(
convert_mouse_button(WinitMouseButton::Forward),
MouseButton::Forward
);
}
#[test]
fn convert_mouse_button_extended() {
assert_eq!(
convert_mouse_button(WinitMouseButton::Button6),
MouseButton::Other(6)
);
assert_eq!(
convert_mouse_button(WinitMouseButton::Button32),
MouseButton::Other(32)
);
}
#[test]
fn convert_window_event_pointer_moved_mouse() {
let scale = DpiScale::new(2.0);
let event = WindowEvent::PointerMoved {
device_id: None,
position: PhysicalPosition::new(100.0, 200.0),
primary: true,
source: PointerSource::Mouse,
};
let pe = convert_window_event(&event, &scale).expect("pointer moved converts");
assert_eq!(pe.pointer_id, PointerId::PRIMARY);
assert_eq!(pe.state, PointerState::Moved);
assert_eq!(pe.button, None);
assert_eq!(pe.position, Vec2::new(50.0, 100.0));
assert_eq!(pe.modifiers, ModifierKeys::empty());
}
#[test]
fn convert_window_event_pointer_moved_touch() {
let scale = DpiScale::new(1.0);
let event = WindowEvent::PointerMoved {
device_id: None,
position: PhysicalPosition::new(10.0, 20.0),
primary: false,
source: PointerSource::Touch {
finger_id: FingerId::from_raw(3),
force: Some(Force::Normalized(0.5)),
},
};
let pe = convert_window_event(&event, &scale).expect("touch moved converts");
assert_eq!(pe.pointer_id, PointerId::new(3));
assert_eq!(pe.state, PointerState::Moved);
assert_eq!(pe.position, Vec2::new(10.0, 20.0));
}
#[test]
fn convert_window_event_pointer_button_pressed() {
let scale = DpiScale::new(1.0);
let event = WindowEvent::PointerButton {
device_id: None,
state: ElementState::Pressed,
position: PhysicalPosition::new(42.0, 17.0),
primary: true,
button: ButtonSource::Mouse(WinitMouseButton::Left),
is_macos_activation_click: false,
};
let pe = convert_window_event(&event, &scale).expect("pointer button converts");
assert_eq!(pe.state, PointerState::Pressed);
assert_eq!(pe.button, Some(MouseButton::Left));
assert_eq!(pe.position, Vec2::new(42.0, 17.0));
assert_eq!(pe.pointer_id, PointerId::PRIMARY);
}
#[test]
fn convert_window_event_pointer_button_released() {
let scale = DpiScale::new(1.0);
let event = WindowEvent::PointerButton {
device_id: None,
state: ElementState::Released,
position: PhysicalPosition::new(42.0, 17.0),
primary: true,
button: ButtonSource::Mouse(WinitMouseButton::Right),
is_macos_activation_click: false,
};
let pe = convert_window_event(&event, &scale).expect("pointer button converts");
assert_eq!(pe.state, PointerState::Released);
assert_eq!(pe.button, Some(MouseButton::Right));
}
#[test]
fn convert_window_event_pointer_button_touch() {
let scale = DpiScale::new(1.0);
let event = WindowEvent::PointerButton {
device_id: None,
state: ElementState::Pressed,
position: PhysicalPosition::new(5.0, 5.0),
primary: false,
button: ButtonSource::Touch {
finger_id: FingerId::from_raw(7),
force: None,
},
is_macos_activation_click: false,
};
let pe = convert_window_event(&event, &scale).expect("touch button converts");
assert_eq!(pe.pointer_id, PointerId::new(7));
assert_eq!(pe.button, Some(MouseButton::Left));
assert_eq!(pe.state, PointerState::Pressed);
}
#[test]
fn convert_window_event_non_pointer_returns_none() {
let scale = DpiScale::new(1.0);
assert!(convert_window_event(&WindowEvent::CloseRequested, &scale).is_none());
assert!(convert_window_event(&WindowEvent::Destroyed, &scale).is_none());
}
#[test]
fn convert_window_event_scales_coordinates() {
let scale = DpiScale::new(1.5);
let event = WindowEvent::PointerMoved {
device_id: None,
position: PhysicalPosition::new(150.0, 300.0),
primary: true,
source: PointerSource::Mouse,
};
let pe = convert_window_event(&event, &scale).expect("converts");
assert_eq!(pe.position, Vec2::new(100.0, 200.0));
}
#[test]
fn multi_window_events_routed_to_correct_window() {
let mut arena_a = WidgetArena::new();
let root_a = insert(&mut arena_a, 0.0, 0.0, 100.0, 100.0);
let mut arena_b = WidgetArena::new();
let root_b = insert(&mut arena_b, 0.0, 0.0, 100.0, 100.0);
let w1 = WindowId::from_raw(1);
let w2 = WindowId::from_raw(2);
let mut router = EventRouter::new();
let event = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(50.0, 50.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena_a, root_a, w1, &event),
EventDispatchOutcome::Handled(root_a)
);
assert_eq!(
router.route_pointer_event(&arena_b, root_b, w2, &event),
EventDispatchOutcome::Handled(root_b)
);
assert_eq!(
router.mouse_tracker().position(w1),
Some(Vec2::new(50.0, 50.0))
);
assert_eq!(
router.mouse_tracker().position(w2),
Some(Vec2::new(50.0, 50.0))
);
router
.mouse_tracker_mut()
.update_position(w1, Vec2::new(1.0, 2.0));
router
.mouse_tracker_mut()
.update_position(w2, Vec2::new(3.0, 4.0));
assert_eq!(
router.mouse_tracker().position(w1),
Some(Vec2::new(1.0, 2.0))
);
assert_eq!(
router.mouse_tracker().position(w2),
Some(Vec2::new(3.0, 4.0))
);
}
#[test]
fn empty_arena_dead_root_returns_unhandled() {
let arena = WidgetArena::new();
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
let dead = WidgetId::from_parts(999, 1);
let event = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(50.0, 50.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, dead, win, &event),
EventDispatchOutcome::Unhandled
);
}
#[test]
fn root_only_tree_hit_inside_and_outside() {
let mut arena = WidgetArena::new();
let root = insert(&mut arena, 0.0, 0.0, 100.0, 100.0);
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
let inside = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(50.0, 50.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, root, win, &inside),
EventDispatchOutcome::Handled(root)
);
let outside = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(500.0, 500.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, root, win, &outside),
EventDispatchOutcome::Unhandled
);
}
#[test]
fn point_outside_all_widgets_returns_unhandled() {
let mut arena = WidgetArena::new();
let root = insert(&mut arena, 0.0, 0.0, 100.0, 100.0);
let other = insert(&mut arena, 200.0, 200.0, 100.0, 100.0);
arena.append_child(root, other).unwrap();
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
let event = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(150.0, 150.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
assert_eq!(
router.route_pointer_event(&arena, root, win, &event),
EventDispatchOutcome::Unhandled
);
}
#[test]
fn route_pointer_event_updates_hovered_widget() {
let mut arena = WidgetArena::new();
let root = insert(&mut arena, 0.0, 0.0, 200.0, 200.0);
let child = insert(&mut arena, 50.0, 50.0, 100.0, 100.0);
arena.append_child(root, child).unwrap();
let win = WindowId::from_raw(1);
let mut router = EventRouter::new();
let over_child = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(75.0, 75.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
router.route_pointer_event(&arena, root, win, &over_child);
assert_eq!(router.mouse_tracker().hovered_widget(win), Some(child));
let miss = PointerEvent {
pointer_id: PointerId::PRIMARY,
position: Vec2::new(500.0, 500.0),
state: PointerState::Moved,
button: None,
modifiers: ModifierKeys::empty(),
};
router.route_pointer_event(&arena, root, win, &miss);
assert_eq!(router.mouse_tracker().hovered_widget(win), None);
}
#[test]
fn event_dispatch_outcome_variants_distinct() {
let w = WidgetId::from_parts(1, 1);
assert_ne!(
EventDispatchOutcome::Handled(w),
EventDispatchOutcome::Unhandled
);
assert_ne!(
EventDispatchOutcome::Unhandled,
EventDispatchOutcome::Ignored
);
assert_eq!(
EventDispatchOutcome::Handled(w),
EventDispatchOutcome::Handled(w)
);
}
#[test]
fn pointer_id_primary_is_zero() {
assert_eq!(PointerId::PRIMARY, PointerId::new(0));
assert_eq!(PointerId::PRIMARY.get(), 0);
}
}