use core::ops::Deref;
use std::sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
use bitflags::bitflags;
use unlit_ecs::World;
use crate::behaviour::behaviour;
#[cfg(feature = "winit")]
pub mod winit;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub enum Key {
Escape,
Space,
Enter,
Tab,
Backspace,
Delete,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
Home,
End,
PageUp,
PageDown,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Num0,
Num1,
Num2,
Num3,
Num4,
Num5,
Num6,
Num7,
Num8,
Num9,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
F25,
F26,
F27,
F28,
F29,
F30,
F31,
F32,
F33,
F34,
F35,
Other(u32),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub enum MouseButton {
Primary,
Secondary,
Middle,
Back,
Forward,
Other(u16),
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MouseButtons: u8 {
const PRIMARY = 1 << 0;
const SECONDARY = 1 << 1;
const MIDDLE = 1 << 2;
const BACK = 1 << 3;
const FORWARD = 1 << 4;
}
}
impl MouseButtons {
#[must_use]
pub const fn bit_of(button: MouseButton) -> Option<Self> {
match button {
MouseButton::Primary => Some(Self::PRIMARY),
MouseButton::Secondary => Some(Self::SECONDARY),
MouseButton::Middle => Some(Self::MIDDLE),
MouseButton::Back => Some(Self::BACK),
MouseButton::Forward => Some(Self::FORWARD),
MouseButton::Other(_) => None,
}
}
const TRACKED: [Self; 5] = [
Self::PRIMARY,
Self::SECONDARY,
Self::MIDDLE,
Self::BACK,
Self::FORWARD,
];
pub fn held(self) -> impl Iterator<Item = Self> {
Self::TRACKED
.into_iter()
.filter(move |bit| self.contains(*bit))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub struct Modifiers {
pub alt: bool,
pub ctrl: bool,
pub shift: bool,
pub mac_cmd: bool,
pub command: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub enum TouchPhase {
Started,
Moved,
Ended,
Cancelled,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub enum WheelUnit {
Line,
Pixel,
Page,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub enum ImeKind {
Preedit {
text: String,
active_range: Option<core::ops::Range<usize>>,
},
Commit(String),
DeleteSurrounding {
before: usize,
after: usize,
},
Enabled,
Disabled,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub struct KeyEvent {
pub key: Key,
#[cfg_attr(feature = "reflect", facet(default = true))]
pub pressed: bool,
#[cfg_attr(feature = "reflect", facet(default))]
pub repeat: bool,
#[cfg_attr(feature = "reflect", facet(default))]
pub modifiers: Modifiers,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub enum PointerKind {
Mouse,
Touch,
Trackpad,
Pen,
Other,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub struct PointerEvent {
pub kind: PointerKind,
pub id: u64,
pub action: PointerAction,
pub position: Option<[f32; 2]>,
pub modifiers: Modifiers,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub struct PointerContact {
pub kind: PointerKind,
pub id: u64,
pub position: [f32; 2],
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub enum PointerAction {
Moved,
Pressed,
Released {
cancelled: bool,
},
Left,
Zoom(f32),
Rotate(f32),
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub enum MouseEvent {
Moved {
position: [f32; 2],
},
Button {
position: [f32; 2],
button: MouseButton,
#[cfg_attr(feature = "reflect", facet(default = true))]
pressed: bool,
#[cfg_attr(feature = "reflect", facet(default))]
modifiers: Modifiers,
},
Left,
Wheel {
delta: [f32; 2],
#[cfg_attr(feature = "reflect", facet(default = WheelUnit::Pixel))]
unit: WheelUnit,
#[cfg_attr(feature = "reflect", facet(default = TouchPhase::Moved))]
phase: TouchPhase,
#[cfg_attr(feature = "reflect", facet(default))]
modifiers: Modifiers,
},
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub struct TouchEvent {
pub id: u64,
pub phase: TouchPhase,
pub position: [f32; 2],
pub force: Option<f32>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub struct ImeEvent {
pub kind: ImeKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
#[cfg_attr(feature = "reflect", facet(transparent))]
pub struct TextEvent(pub String);
impl Deref for TextEvent {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug, PartialEq)]
#[repr(u8)]
#[cfg_attr(feature = "reflect", derive(facet::Facet))]
pub enum InputEvent {
Key(KeyEvent),
Mouse(MouseEvent),
Pointer(PointerEvent),
Touch(TouchEvent),
Text(TextEvent),
Ime(ImeEvent),
ModifiersChanged(Modifiers),
FocusChanged(bool),
}
#[derive(Clone, Debug, PartialEq)]
pub struct InputState {
events: Vec<InputEvent>,
pub modifiers: Modifiers,
pub pointers: Vec<PointerContact>,
pub pointer: Option<[f32; 2]>,
pub pointer_down: bool,
pub cursor: Option<[f32; 2]>,
pub buttons: MouseButtons,
pub touches: Vec<(u64, [f32; 2])>,
pub focused: bool,
pub size_px: (u32, u32),
pub scale_factor: f32,
}
impl Default for InputState {
fn default() -> Self {
Self {
events: Vec::new(),
modifiers: Modifiers::default(),
pointers: Vec::new(),
pointer: None,
pointer_down: false,
cursor: None,
buttons: MouseButtons::empty(),
touches: Vec::new(),
focused: true,
size_px: (0, 0),
scale_factor: 1.0,
}
}
}
impl InputState {
#[must_use]
pub fn events(&self) -> &[InputEvent] {
&self.events
}
pub fn push(&mut self, event: InputEvent) {
match event {
InputEvent::Key(event) => self.modifiers = event.modifiers,
InputEvent::Mouse(event) => self.apply_mouse(event),
InputEvent::Pointer(event) => self.apply_pointer(event),
InputEvent::Touch(event) => self.apply_touch(&event),
InputEvent::Ime(_) | InputEvent::Text(_) => {}
InputEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers,
InputEvent::FocusChanged(focused) => self.focused = focused,
}
self.events.push(event);
}
pub fn clear_events(&mut self) {
self.events.clear();
}
pub fn set_size_px(&mut self, width: u32, height: u32) {
self.size_px = (width, height);
}
pub fn set_scale_factor(&mut self, scale_factor: f32) {
self.scale_factor = scale_factor;
}
pub fn set_pointer(&mut self, position: Option<[f32; 2]>) {
self.pointer = position;
}
fn apply_mouse(&mut self, event: MouseEvent) {
match event {
MouseEvent::Moved { position } => self.cursor = Some(position),
MouseEvent::Button {
position,
button,
pressed,
..
} => {
self.cursor = Some(position);
if let Some(bit) = MouseButtons::bit_of(button) {
self.buttons.set(bit, pressed);
}
}
MouseEvent::Left => self.cursor = None,
MouseEvent::Wheel { .. } => {}
}
}
fn apply_pointer(&mut self, event: PointerEvent) {
if let Some(position) = event.position {
self.pointer = Some(position);
}
let position = event.position.or(self.pointer);
match event.action {
PointerAction::Pressed => {
match self.contact_mut(event.kind, event.id) {
Some(contact) => contact.position = position.unwrap_or(contact.position),
None => {
if let Some(position) = position {
self.pointers.push(PointerContact {
kind: event.kind,
id: event.id,
position,
});
}
}
}
}
PointerAction::Released { .. } => {
self.pointers
.retain(|contact| (contact.kind, contact.id) != (event.kind, event.id));
}
PointerAction::Left => self.pointer = None,
PointerAction::Moved => {
if let (Some(contact), Some(position)) =
(self.contact_mut(event.kind, event.id), position)
{
contact.position = position;
}
}
PointerAction::Zoom(_) | PointerAction::Rotate(_) => {}
}
self.pointer_down = !self.pointers.is_empty();
}
fn contact_mut(&mut self, kind: PointerKind, id: u64) -> Option<&mut PointerContact> {
self.pointers
.iter_mut()
.find(|contact| (contact.kind, contact.id) == (kind, id))
}
fn apply_touch(&mut self, event: &TouchEvent) {
match event.phase {
TouchPhase::Started | TouchPhase::Moved => {
match self.touches.iter_mut().find(|(id, _)| *id == event.id) {
Some((_, position)) => *position = event.position,
None => self.touches.push((event.id, event.position)),
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
self.touches.retain(|(id, _)| *id != event.id);
}
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "reflect",
derive(facet::Facet),
facet(opaque, proxy = crate::reflect::InputStateProxy)
)]
pub struct InputHandle(Arc<RwLock<InputState>>);
impl InputHandle {
pub fn new() -> Self {
Self::from_state(InputState::default())
}
pub fn from_state(state: InputState) -> Self {
Self(Arc::new(RwLock::new(state)))
}
pub fn read(&self) -> RwLockReadGuard<'_, InputState> {
self.0.read().unwrap_or_else(PoisonError::into_inner)
}
pub fn write(&self) -> RwLockWriteGuard<'_, InputState> {
self.0.write().unwrap_or_else(PoisonError::into_inner)
}
}
impl Default for InputHandle {
fn default() -> Self {
Self::new()
}
}
pub fn distribute_input(world: &World) -> bool {
let Some(handle) = world
.query::<&InputHandle>()
.next()
.map(|(_, handle)| InputHandle::clone(&handle))
else {
return false;
};
distribute_into(world, &handle)
}
fn distribute_into(world: &World, handle: &InputHandle) -> bool {
let mut changed = false;
for (entity, child) in world.query::<&World>() {
let has = child.query::<&InputHandle>().next().is_some();
drop(child);
let nested = world.with_mut::<World, _>(entity, |child| {
if !has {
child.spawn((handle.clone(),));
}
distribute_into(child, handle)
});
if let Some(nested) = nested {
changed |= !has || nested;
}
}
changed
}
behaviour!(
OnInput,
InputEvent,
"Runs for every input event, whatever its category.\n\n\
This is the catch-all: a behaviour that wants the whole stream — an input\n\
log, a replay recorder, a handler that cares about ordering across\n\
categories — mounts this one instead of the per-category components."
);
behaviour!(OnKey, KeyEvent, "Runs for every [`KeyEvent`].");
behaviour!(OnMouse, MouseEvent, "Runs for every [`MouseEvent`].");
behaviour!(
OnPointer,
PointerEvent,
"Runs for every [`PointerEvent`], whatever device produced it.\n\n\
This is the device-agnostic pointing behaviour: a drag written against it\n\
works with a mouse, a finger and a stylus alike. A consumer that needs the\n\
device's own detail — mouse buttons and wheel steps, or a touch's id and\n\
pressure — mounts [`OnMouse`] or [`OnTouch`] beside it."
);
behaviour!(OnTouch, TouchEvent, "Runs for every [`TouchEvent`].");
behaviour!(OnText, TextEvent, "Runs for every [`TextEvent`].");
behaviour!(OnIme, ImeEvent, "Runs for every [`ImeEvent`].");
pub fn dispatch_input(world: &World) -> bool {
let Some(events) = world
.query::<&InputHandle>()
.next()
.map(|(_, handle)| handle.read().events().to_vec())
else {
return false;
};
let delivered = !events.is_empty();
for event in &events {
match event {
InputEvent::Key(event) => {
for (entity, mut behaviour) in world.query::<&mut OnKey>() {
behaviour.run(world, entity, event);
}
}
InputEvent::Mouse(event) => {
for (entity, mut behaviour) in world.query::<&mut OnMouse>() {
behaviour.run(world, entity, event);
}
}
InputEvent::Pointer(event) => {
for (entity, mut behaviour) in world.query::<&mut OnPointer>() {
behaviour.run(world, entity, event);
}
}
InputEvent::Touch(event) => {
for (entity, mut behaviour) in world.query::<&mut OnTouch>() {
behaviour.run(world, entity, event);
}
}
InputEvent::Text(event) => {
for (entity, mut behaviour) in world.query::<&mut OnText>() {
behaviour.run(world, entity, event);
}
}
InputEvent::Ime(event) => {
for (entity, mut behaviour) in world.query::<&mut OnIme>() {
behaviour.run(world, entity, event);
}
}
InputEvent::ModifiersChanged(_) | InputEvent::FocusChanged(_) => {}
}
for (entity, mut behaviour) in world.query::<&mut OnInput>() {
behaviour.run(world, entity, event);
}
}
delivered
}
#[cfg(test)]
mod tests {
use core::cell::Cell;
use std::rc::Rc;
use unlit_ecs::{Entity, With};
use super::*;
const CURSOR: [f32; 2] = [3.0, 4.0];
fn key_event(key: Key) -> InputEvent {
InputEvent::Key(KeyEvent {
key,
pressed: true,
repeat: false,
modifiers: Modifiers::default(),
})
}
fn mouse_event() -> InputEvent {
InputEvent::Mouse(MouseEvent::Moved { position: CURSOR })
}
fn pointer_event() -> InputEvent {
InputEvent::Pointer(PointerEvent {
kind: PointerKind::Mouse,
id: 0,
action: PointerAction::Moved,
position: Some(CURSOR),
modifiers: Modifiers::default(),
})
}
fn touch_event() -> InputEvent {
InputEvent::Touch(TouchEvent {
id: 1,
phase: TouchPhase::Started,
position: CURSOR,
force: None,
})
}
fn text_event() -> InputEvent {
InputEvent::Text(TextEvent("hi".to_owned()))
}
fn ime_event() -> InputEvent {
InputEvent::Ime(ImeEvent {
kind: ImeKind::Commit("hi".to_owned()),
})
}
fn counter() -> (Rc<Cell<usize>>, Rc<Cell<usize>>) {
let shared = Rc::new(Cell::new(0));
(shared.clone(), shared)
}
#[test]
fn each_event_reaches_its_category_and_every_on_input() {
let mut world = World::new();
let (seen_input, input) = counter();
let (seen_key, key) = counter();
let (seen_mouse, mouse) = counter();
let (seen_pointer, pointer) = counter();
let (seen_touch, touch) = counter();
let (seen_text, text) = counter();
let (seen_ime, ime) = counter();
world.spawn((OnInput::new(move |_, _, _| {
seen_input.set(seen_input.get() + 1);
}),));
world.spawn((OnKey::new(move |_, _, _| {
seen_key.set(seen_key.get() + 1);
}),));
world.spawn((OnMouse::new(move |_, _, _| {
seen_mouse.set(seen_mouse.get() + 1);
}),));
world.spawn((OnPointer::new(move |_, _, _| {
seen_pointer.set(seen_pointer.get() + 1);
}),));
world.spawn((OnTouch::new(move |_, _, _| {
seen_touch.set(seen_touch.get() + 1);
}),));
world.spawn((OnText::new(move |_, _, _| {
seen_text.set(seen_text.get() + 1);
}),));
world.spawn((OnIme::new(move |_, _, _| {
seen_ime.set(seen_ime.get() + 1);
}),));
let input_handle = InputHandle::new();
world.spawn((input_handle.clone(),));
let events = [
key_event(Key::W),
mouse_event(),
pointer_event(),
touch_event(),
text_event(),
ime_event(),
InputEvent::ModifiersChanged(Modifiers {
ctrl: true,
..Modifiers::default()
}),
InputEvent::FocusChanged(false),
];
let event_count = events.len();
{
let mut state = input_handle.write();
for event in events {
state.push(event);
}
}
assert!(dispatch_input(&world), "events were delivered");
assert_eq!(input.get(), event_count, "OnInput hears every event");
assert_eq!(key.get(), 1, "OnKey hears only the key event");
assert_eq!(mouse.get(), 1, "OnMouse hears only the mouse event");
assert_eq!(pointer.get(), 1);
assert_eq!(touch.get(), 1);
assert_eq!(text.get(), 1);
assert_eq!(ime.get(), 1);
}
#[test]
fn one_entity_carries_on_key_and_on_input_together() {
let mut world = World::new();
let (keys, key_count) = counter();
let (all, all_count) = counter();
world.spawn((
OnKey::new(move |_, _, _| keys.set(keys.get() + 1)),
OnInput::new(move |_, _, _| all.set(all.get() + 1)),
));
let input = InputHandle::new();
world.spawn((input.clone(),));
{
let mut state = input.write();
state.push(key_event(Key::A));
state.push(mouse_event());
}
dispatch_input(&world);
assert_eq!(key_count.get(), 1);
assert_eq!(all_count.get(), 2);
}
#[test]
fn one_behaviour_serves_every_entity_carrying_it() {
let mut world = World::new();
let (count, seen) = counter();
for _ in 0..3 {
let count = count.clone();
world.spawn((OnKey::new(move |_, _, _| count.set(count.get() + 1)),));
}
let input = InputHandle::new();
world.spawn((input.clone(),));
input.write().push(key_event(Key::A));
dispatch_input(&world);
assert_eq!(seen.get(), 3, "one key event per behaviour");
}
#[test]
fn a_callback_sees_the_entity_it_sits_on() {
let mut world = World::new();
let (matches, seen) = counter();
world.spawn((
7u32,
OnKey::new(move |world, entity, _| {
let marker = *world.get::<u32>(entity).unwrap();
matches.set(marker as usize);
}),
));
world.spawn((9u32, OnKey::new(|_, _, _| {})));
let input = InputHandle::new();
world.spawn((input.clone(),));
input.write().push(key_event(Key::A));
dispatch_input(&world);
assert_eq!(seen.get(), 7);
}
#[test]
fn a_callback_may_read_and_write_the_input_state() {
let mut world = World::new();
let input = InputHandle::new();
world.spawn((input.clone(),));
let callback_input = input.clone();
world.spawn((OnKey::new(move |_, _, _| {
let held = callback_input.read().modifiers.ctrl;
callback_input.write().focused = held;
}),));
input.write().push(InputEvent::Key(KeyEvent {
key: Key::C,
pressed: true,
repeat: false,
modifiers: Modifiers {
ctrl: true,
..Modifiers::default()
},
}));
dispatch_input(&world);
assert!(input.read().focused);
}
#[test]
fn a_callback_may_filter_its_siblings_without_disturbing_the_dispatch() {
let mut world = World::new();
let (count, seen) = counter();
world.spawn((OnKey::new(move |world, _, _| {
let siblings = world.query_filtered::<Entity, With<OnKey>>().count();
count.set(siblings);
}),));
world.spawn((OnKey::new(|_, _, _| {}),));
let input = InputHandle::new();
world.spawn((input.clone(),));
input.write().push(key_event(Key::A));
dispatch_input(&world);
assert_eq!(seen.get(), 2);
}
#[test]
fn a_queued_spawn_lands_after_apply() {
let mut world = World::new();
world.spawn((OnKey::new(|world, _, _| {
world.queue().spawn((42u32,));
}),));
let input = InputHandle::new();
world.spawn((input.clone(),));
input.write().push(key_event(Key::A));
assert!(dispatch_input(&world));
assert_eq!(world.len(), 2, "queued, not applied");
world.apply();
assert_eq!(world.len(), 3);
}
#[test]
fn a_callback_may_queue_its_own_despawn() {
let mut world = World::new();
let doomed = world.spawn((OnKey::new(|world, entity, _| {
world.queue().despawn(entity);
}),));
let input = InputHandle::new();
world.spawn((input.clone(),));
{
let mut state = input.write();
state.push(key_event(Key::A));
state.push(key_event(Key::B));
}
dispatch_input(&world);
world.apply();
assert!(!world.has::<OnKey>(doomed));
}
#[test]
fn an_empty_event_list_delivers_nothing() {
let mut world = World::new();
let (count, seen) = counter();
world.spawn((OnInput::new(move |_, _, _| count.set(count.get() + 1)),));
world.spawn((InputHandle::new(),));
assert!(!dispatch_input(&world));
assert_eq!(seen.get(), 0);
}
#[test]
fn no_input_state_resource_is_not_an_error() {
let mut world = World::new();
let (count, seen) = counter();
world.spawn((OnInput::new(move |_, _, _| count.set(count.get() + 1)),));
assert!(!dispatch_input(&world));
assert_eq!(seen.get(), 0);
}
#[test]
fn events_without_behaviours_are_delivered_to_nobody() {
let mut world = World::new();
let input = InputHandle::new();
world.spawn((input.clone(),));
input.write().push(key_event(Key::A));
assert!(dispatch_input(&world));
}
#[test]
fn push_updates_the_state_and_keeps_the_event() {
let mut state = InputState::default();
state.push(InputEvent::ModifiersChanged(Modifiers {
shift: true,
..Modifiers::default()
}));
state.push(InputEvent::FocusChanged(false));
state.push(InputEvent::Mouse(MouseEvent::Moved { position: CURSOR }));
state.push(InputEvent::Mouse(MouseEvent::Button {
position: CURSOR,
button: MouseButton::Primary,
pressed: true,
modifiers: Modifiers::default(),
}));
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Mouse,
id: 0,
action: PointerAction::Pressed,
position: Some(CURSOR),
modifiers: Modifiers::default(),
}));
state.push(InputEvent::Touch(TouchEvent {
id: 7,
phase: TouchPhase::Started,
position: CURSOR,
force: Some(0.5),
}));
assert_eq!(state.events().len(), 6);
assert!(state.modifiers.shift);
assert!(!state.focused);
assert_eq!(state.cursor, Some(CURSOR));
assert!(state.buttons.contains(MouseButtons::PRIMARY));
assert_eq!(state.pointer, Some(CURSOR));
assert!(state.pointer_down);
assert_eq!(state.touches, [(7, CURSOR)]);
state.push(InputEvent::Mouse(MouseEvent::Button {
position: CURSOR,
button: MouseButton::Other(9),
pressed: true,
modifiers: Modifiers::default(),
}));
assert_eq!(state.buttons.held().count(), 1);
state.push(InputEvent::Mouse(MouseEvent::Button {
position: CURSOR,
button: MouseButton::Primary,
pressed: false,
modifiers: Modifiers::default(),
}));
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Mouse,
id: 0,
action: PointerAction::Released { cancelled: false },
position: Some(CURSOR),
modifiers: Modifiers::default(),
}));
state.push(InputEvent::Mouse(MouseEvent::Left));
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Mouse,
id: 0,
action: PointerAction::Left,
position: None,
modifiers: Modifiers::default(),
}));
state.push(InputEvent::Touch(TouchEvent {
id: 7,
phase: TouchPhase::Moved,
position: [1.0, 2.0],
force: None,
}));
assert!(state.buttons.is_empty());
assert!(!state.pointer_down);
assert_eq!(state.cursor, None);
assert_eq!(state.pointer, None);
assert_eq!(state.touches, [(7, [1.0, 2.0])], "moved, not duplicated");
state.push(InputEvent::Touch(TouchEvent {
id: 7,
phase: TouchPhase::Ended,
position: [1.0, 2.0],
force: None,
}));
assert!(state.touches.is_empty());
assert_eq!(state.buttons.held().count(), 0);
}
#[test]
fn a_pointer_down_state_is_device_agnostic() {
let mut state = InputState::default();
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Touch,
id: 1,
action: PointerAction::Pressed,
position: Some(CURSOR),
modifiers: Modifiers::default(),
}));
assert!(state.pointer_down);
assert_eq!(state.pointer, Some(CURSOR));
assert!(
state.buttons.is_empty(),
"a touch is not a mouse button being held"
);
assert_eq!(state.cursor, None, "a touch does not move the mouse");
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Touch,
id: 1,
action: PointerAction::Released { cancelled: true },
position: None,
modifiers: Modifiers::default(),
}));
assert!(!state.pointer_down, "a cancelled pointer is no longer down");
}
#[test]
fn two_fingers_are_two_pointers() {
let mut state = InputState::default();
let press = |id, position| {
InputEvent::Pointer(PointerEvent {
kind: PointerKind::Touch,
id,
action: PointerAction::Pressed,
position: Some(position),
modifiers: Modifiers::default(),
})
};
state.push(press(1, [10.0, 10.0]));
state.push(press(2, [40.0, 10.0]));
assert_eq!(
state.pointers,
[
PointerContact {
kind: PointerKind::Touch,
id: 1,
position: [10.0, 10.0],
},
PointerContact {
kind: PointerKind::Touch,
id: 2,
position: [40.0, 10.0],
},
],
"both contacts are tracked, in the order they went down"
);
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Touch,
id: 2,
action: PointerAction::Moved,
position: Some([50.0, 10.0]),
modifiers: Modifiers::default(),
}));
assert_eq!(state.pointers[0].position, [10.0, 10.0], "the other stays");
assert_eq!(state.pointers[1].position, [50.0, 10.0]);
assert_eq!(state.pointer, Some([50.0, 10.0]), "the latest move is it");
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Touch,
id: 1,
action: PointerAction::Released { cancelled: false },
position: Some([10.0, 10.0]),
modifiers: Modifiers::default(),
}));
assert!(state.pointer_down, "one finger is still on the screen");
assert_eq!(state.pointers.len(), 1);
assert_eq!(state.pointers[0].id, 2);
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Touch,
id: 2,
action: PointerAction::Released { cancelled: false },
position: Some([50.0, 10.0]),
modifiers: Modifiers::default(),
}));
assert!(!state.pointer_down, "the screen is clear again");
assert!(state.pointers.is_empty());
}
#[test]
fn a_mouse_and_a_finger_can_be_down_at_once() {
let mut state = InputState::default();
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Mouse,
id: 0,
action: PointerAction::Pressed,
position: Some([1.0, 1.0]),
modifiers: Modifiers::default(),
}));
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Touch,
id: 0,
action: PointerAction::Pressed,
position: Some([2.0, 2.0]),
modifiers: Modifiers::default(),
}));
assert_eq!(state.pointers.len(), 2, "the same id on two kinds differ");
assert!(state.pointer_down);
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Mouse,
id: 0,
action: PointerAction::Released { cancelled: false },
position: Some([1.0, 1.0]),
modifiers: Modifiers::default(),
}));
assert_eq!(state.pointers.len(), 1);
assert_eq!(state.pointers[0].kind, PointerKind::Touch);
assert!(state.pointer_down);
}
#[test]
fn a_hover_does_not_press_a_pointer() {
let mut state = InputState::default();
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Mouse,
id: 0,
action: PointerAction::Moved,
position: Some(CURSOR),
modifiers: Modifiers::default(),
}));
assert_eq!(state.pointer, Some(CURSOR));
assert!(!state.pointer_down);
assert!(state.pointers.is_empty());
}
#[test]
fn a_cancelled_pointer_keeps_the_position_it_was_last_seen_at() {
let mut state = InputState::default();
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Touch,
id: 1,
action: PointerAction::Moved,
position: Some(CURSOR),
modifiers: Modifiers::default(),
}));
state.push(InputEvent::Pointer(PointerEvent {
kind: PointerKind::Touch,
id: 1,
action: PointerAction::Released { cancelled: true },
position: None,
modifiers: Modifiers::default(),
}));
assert_eq!(state.pointer, Some(CURSOR));
assert!(!state.pointer_down);
}
#[test]
fn clear_events_keeps_the_state() {
let mut state = InputState::default();
state.set_size_px(1280, 720);
state.set_scale_factor(2.0);
state.push(InputEvent::ModifiersChanged(Modifiers {
alt: true,
..Modifiers::default()
}));
state.push(InputEvent::FocusChanged(false));
state.push(InputEvent::Mouse(MouseEvent::Moved { position: CURSOR }));
state.push(InputEvent::Mouse(MouseEvent::Button {
position: CURSOR,
button: MouseButton::Secondary,
pressed: true,
modifiers: Modifiers::default(),
}));
state.clear_events();
assert!(state.events().is_empty());
assert!(state.modifiers.alt);
assert_eq!(state.cursor, Some(CURSOR));
assert_eq!(state.buttons, MouseButtons::SECONDARY);
assert!(!state.focused);
assert_eq!(state.size_px, (1280, 720));
assert_eq!(state.scale_factor, 2.0);
}
#[test]
fn a_seeded_state_survives_a_dispatch_and_a_clear() {
let mut world = World::new();
let input = InputHandle::new();
world.spawn((input.clone(),));
{
let mut state = input.write();
state.set_size_px(800, 600);
state.set_scale_factor(1.5);
state.push(InputEvent::FocusChanged(false));
state.push(key_event(Key::S));
}
assert!(dispatch_input(&world));
world.apply();
input.write().clear_events();
let state = input.read();
assert!(state.events().is_empty());
assert_eq!(state.size_px, (800, 600));
assert_eq!(state.scale_factor, 1.5);
assert!(!state.focused);
}
#[test]
#[should_panic(expected = "already borrowed")]
fn a_behaviour_cannot_reborrow_its_own_component() {
let mut world = World::new();
world.spawn((OnKey::new(|world, entity, _| {
let _ = world.get::<OnKey>(entity);
}),));
let input = InputHandle::new();
world.spawn((input.clone(),));
input.write().push(key_event(Key::A));
dispatch_input(&world);
}
}