use core::ops::Deref;
use std::sync::{Arc, PoisonError, RwLock};
use crate::relay::{Stream, StreamReader};
use bitflags::bitflags;
use unlit_ecs::{Bundle, DynamicBundle, Entity, World, bundle};
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),
Resized {
size_px: (u32, u32),
},
ScaleFactorChanged {
scale_factor: f32,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct InputState {
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 {
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,
}
}
}
#[cfg(test)]
impl InputState {
fn push(&mut self, event: InputEvent) {
self.record(&event);
}
}
impl InputState {
fn record(&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,
InputEvent::Resized { size_px } => self.size_px = *size_px,
InputEvent::ScaleFactorChanged { scale_factor } => self.scale_factor = *scale_factor,
}
}
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::InputEventsProxy)
)]
pub struct InputEvents {
events: StreamReader<InputEvent>,
state: Arc<RwLock<InputState>>,
}
impl InputEvents {
pub fn with_state<R>(&self, f: impl FnOnce(&InputState) -> R) -> R {
let state = self.state.read().unwrap_or_else(PoisonError::into_inner);
f(&state)
}
pub fn with_batch<R>(&self, f: impl FnOnce(&[InputEvent]) -> R) -> R {
self.events.with_batch(f)
}
pub fn copy_events_into(&self, out: &mut Vec<InputEvent>) {
self.events.copy_into(out);
}
pub fn refresh(&self) {
self.events.pump();
let mut state = self.state.write().unwrap_or_else(PoisonError::into_inner);
self.events.with_batch(|batch| {
for event in batch {
state.record(event);
}
});
}
}
#[derive(Clone, Debug, Default)]
pub struct InputWriter(Stream<InputEvent>);
impl InputWriter {
pub fn new() -> Self {
Self::default()
}
pub fn push(&self, event: InputEvent) {
self.push_all([event]);
}
pub fn push_all(&self, events: impl IntoIterator<Item = InputEvent>) {
self.0.publish_all(events);
}
pub fn subscribe(&self) -> InputEvents {
InputEvents {
events: self.0.subscribe(),
state: Arc::new(RwLock::new(InputState::default())),
}
}
}
pub struct InputFeed(pub InputWriter);
impl Bundle for InputFeed {
fn into_bundle(self) -> DynamicBundle {
bundle![self.0.clone(), self.0.subscribe()]
}
}
pub fn refresh_input(world: &World) -> bool {
let entities: Vec<_> = world
.root_scope()
.query::<(&InputEvents, Entity)>()
.map(|(_input_events, entity)| entity)
.collect();
let present = !entities.is_empty();
for entity in entities {
if let Ok(buffer) = world.root_scope().query_one::<&InputEvents>(entity) {
buffer.refresh();
}
}
present
}
pub fn subscribe_input(world: &mut World) -> bool {
if world.root_scope().query::<&InputEvents>().next().is_some() {
return false;
}
let Some(events) = world
.root_scope()
.query::<&InputWriter>()
.next()
.map(|input_writer| input_writer.subscribe())
else {
return false;
};
let shared = world.shared();
world
.scope_mut(shared)
.expect("the shared scope exists")
.spawn(bundle![events]);
true
}
pub fn distribute_input(world: &World) -> bool {
let Some(writer) = world
.root_scope()
.query::<&InputWriter>()
.next()
.map(|input_writer| InputWriter::clone(&input_writer))
else {
return false;
};
distribute_into(world, &writer)
}
fn distribute_into(world: &World, writer: &InputWriter) -> bool {
let mut changed = false;
for (child, entity) in world.root_scope().query::<(&World, Entity)>() {
let has = child.root_scope().query::<&InputWriter>().next().is_some();
drop(child);
let nested = world
.root_scope()
.query_one::<&mut World>(entity)
.map(|mut child| {
if !has {
child.root_scope_mut().spawn(bundle![writer.clone()]);
}
let subscribed = subscribe_input(&mut child);
(subscribed, distribute_into(&child, writer))
});
if let Ok((subscribed, nested)) = nested {
changed |= !has || subscribed || 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 {
if !refresh_input(world) {
return false;
}
let Some(events) = world
.root_scope()
.query::<&InputEvents>()
.next()
.map(|input_events| input_events.with_batch(|batch| batch.to_vec()))
else {
return false;
};
let delivered = !events.is_empty();
for event in &events {
match event {
InputEvent::Key(event) => {
for (mut on_key, entity) in world.root_scope().query::<(&mut OnKey, Entity)>() {
on_key.run(world, entity, event);
}
}
InputEvent::Mouse(event) => {
for (mut on_mouse, entity) in world.root_scope().query::<(&mut OnMouse, Entity)>() {
on_mouse.run(world, entity, event);
}
}
InputEvent::Pointer(event) => {
for (mut on_pointer, entity) in
world.root_scope().query::<(&mut OnPointer, Entity)>()
{
on_pointer.run(world, entity, event);
}
}
InputEvent::Touch(event) => {
for (mut on_touch, entity) in world.root_scope().query::<(&mut OnTouch, Entity)>() {
on_touch.run(world, entity, event);
}
}
InputEvent::Text(event) => {
for (mut on_text, entity) in world.root_scope().query::<(&mut OnText, Entity)>() {
on_text.run(world, entity, event);
}
}
InputEvent::Ime(event) => {
for (mut on_ime, entity) in world.root_scope().query::<(&mut OnIme, Entity)>() {
on_ime.run(world, entity, event);
}
}
InputEvent::ModifiersChanged(_)
| InputEvent::FocusChanged(_)
| InputEvent::Resized { .. }
| InputEvent::ScaleFactorChanged { .. } => {}
}
for (mut on_input, entity) in world.root_scope().query::<(&mut OnInput, Entity)>() {
on_input.run(world, entity, event);
}
}
delivered
}
#[cfg(test)]
mod tests {
use core::cell::Cell;
use std::rc::Rc;
use unlit_ecs::{Archetype, Has};
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
.root_scope_mut()
.spawn(bundle![OnInput::new(move |_, _, _| {
seen_input.set(seen_input.get() + 1);
}),]);
world
.root_scope_mut()
.spawn(bundle![OnKey::new(move |_, _, _| {
seen_key.set(seen_key.get() + 1);
}),]);
world
.root_scope_mut()
.spawn(bundle![OnMouse::new(move |_, _, _| {
seen_mouse.set(seen_mouse.get() + 1);
}),]);
world
.root_scope_mut()
.spawn(bundle![OnPointer::new(move |_, _, _| {
seen_pointer.set(seen_pointer.get() + 1);
}),]);
world
.root_scope_mut()
.spawn(bundle![OnTouch::new(move |_, _, _| {
seen_touch.set(seen_touch.get() + 1);
}),]);
world
.root_scope_mut()
.spawn(bundle![OnText::new(move |_, _, _| {
seen_text.set(seen_text.get() + 1);
}),]);
world
.root_scope_mut()
.spawn(bundle![OnIme::new(move |_, _, _| {
seen_ime.set(seen_ime.get() + 1);
}),]);
let input_handle = InputWriter::new();
world
.root_scope_mut()
.spawn(InputFeed(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();
for event in events {
input_handle.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.root_scope_mut().spawn(bundle![
OnKey::new(move |_, _, _| keys.set(keys.get() + 1)),
OnInput::new(move |_, _, _| all.set(all.get() + 1)),
]);
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.push(key_event(Key::A));
input.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.root_scope_mut().spawn(bundle![OnKey::new(
move |_, _, _| count.set(count.get() + 1)
),]);
}
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.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.root_scope_mut().spawn(bundle![
7u32,
OnKey::new(move |world, entity, _| {
let marker = *world.root_scope().query_one::<&u32>(entity).unwrap();
matches.set(marker as usize);
}),
]);
world
.root_scope_mut()
.spawn(bundle![9u32, OnKey::new(|_, _, _| {})]);
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.push(key_event(Key::A));
dispatch_input(&world);
assert_eq!(seen.get(), 7);
}
#[test]
fn a_callback_may_read_the_input_state() {
let mut world = World::new();
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
let (held, seen) = counter();
world
.root_scope_mut()
.spawn(bundle![OnKey::new(move |world, _, _| {
let ctrl = world
.root_scope()
.query::<&InputEvents>()
.next()
.is_some_and(|input_events| {
input_events.with_state(|state| state.modifiers.ctrl)
});
if ctrl {
held.set(held.get() + 1);
}
}),]);
input.push(InputEvent::Key(KeyEvent {
key: Key::C,
pressed: true,
repeat: false,
modifiers: Modifiers {
ctrl: true,
..Modifiers::default()
},
}));
dispatch_input(&world);
assert_eq!(seen.get(), 1, "the callback read the state the event built");
}
#[test]
fn a_callback_may_filter_its_siblings_without_disturbing_the_dispatch() {
let mut world = World::new();
let (count, seen) = counter();
world
.root_scope_mut()
.spawn(bundle![OnKey::new(move |world, _, _| {
let siblings = world
.root_scope()
.query::<Entity>()
.by(|archetype: &Archetype| archetype.has::<OnKey>())
.count();
count.set(siblings);
}),]);
world
.root_scope_mut()
.spawn(bundle![OnKey::new(|_, _, _| {})]);
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.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
.root_scope_mut()
.spawn(bundle![OnKey::new(|world, _, _| {
world.root_scope().queue().spawn(bundle![42u32]);
}),]);
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.push(key_event(Key::A));
assert!(dispatch_input(&world));
assert_eq!(world.root_scope().len(), 2, "queued, not applied");
world.apply().unwrap();
assert_eq!(world.root_scope().len(), 3);
}
#[test]
fn a_callback_may_queue_its_own_despawn() {
let mut world = World::new();
let doomed = world
.root_scope_mut()
.spawn(bundle![OnKey::new(|world, entity, _| {
world.root_scope().queue().despawn(entity);
}),]);
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.push(key_event(Key::A));
dispatch_input(&world);
world.apply().unwrap();
assert!(
!world
.root_scope()
.query_one::<Has<OnKey>>(doomed)
.unwrap_or(false)
);
}
#[test]
fn an_empty_event_list_delivers_nothing() {
let mut world = World::new();
let (count, seen) = counter();
world.root_scope_mut().spawn(bundle![OnInput::new(
move |_, _, _| count.set(count.get() + 1)
),]);
world.root_scope_mut().spawn(bundle![InputWriter::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.root_scope_mut().spawn(bundle![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 = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.push(key_event(Key::A));
assert!(dispatch_input(&world));
}
#[test]
fn push_updates_the_state_it_leaves_behind() {
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!(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 a_dispatch_leaves_the_state_the_events_built() {
let mut world = World::new();
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.push(InputEvent::Resized {
size_px: (800, 600),
});
input.push(InputEvent::ScaleFactorChanged { scale_factor: 1.5 });
input.push(InputEvent::FocusChanged(false));
input.push(key_event(Key::S));
assert!(dispatch_input(&world));
world.apply().unwrap();
let input_events = world
.root_scope()
.query::<&InputEvents>()
.next()
.expect("the world has a buffer");
input_events.with_state(|state| {
assert_eq!(state.size_px, (800, 600));
assert_eq!(state.scale_factor, 1.5);
assert!(!state.focused);
});
}
#[test]
fn a_slow_consumer_reads_every_event() {
let mut world = World::new();
let (count, seen) = counter();
world.root_scope_mut().spawn(bundle![OnKey::new(
move |_, _, _| count.set(count.get() + 1)
),]);
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.push(key_event(Key::A));
input.push(key_event(Key::B));
input.push(key_event(Key::C));
assert!(dispatch_input(&world));
assert_eq!(seen.get(), 3, "every event waited for this dispatch");
assert!(!dispatch_input(&world));
assert_eq!(seen.get(), 3);
}
#[test]
fn every_reader_of_a_world_sees_the_same_batch() {
let input = InputWriter::new();
let buffer = input.subscribe();
input.push(key_event(Key::A));
buffer.refresh();
assert_eq!(
buffer.with_batch(|batch| batch.to_vec()),
[key_event(Key::A)]
);
assert_eq!(
buffer.with_batch(|batch| batch.to_vec()),
[key_event(Key::A)],
"reading leaves the batch for the next reader"
);
buffer.refresh();
assert!(buffer.with_batch(|batch| batch.to_vec()).is_empty());
}
#[test]
fn each_world_builds_its_own_state() {
let input = InputWriter::new();
let first = input.subscribe();
let second = input.subscribe();
input.push(InputEvent::ModifiersChanged(Modifiers {
shift: true,
..Modifiers::default()
}));
first.refresh();
assert!(first.with_state(|state| state.modifiers.shift));
assert!(
!second.with_state(|state| state.modifiers.shift),
"a world that has not ticked is still in its previous state"
);
second.refresh();
assert!(second.with_state(|state| state.modifiers.shift));
}
#[test]
fn a_slow_world_builds_the_state_its_events_describe() {
let input = InputWriter::new();
let buffer = input.subscribe();
input.push(InputEvent::ModifiersChanged(Modifiers {
ctrl: true,
..Modifiers::default()
}));
input.push(InputEvent::ModifiersChanged(Modifiers::default()));
buffer.refresh();
assert!(
!buffer.with_state(|state| state.modifiers.ctrl),
"the later event is what the state ends up as"
);
assert_eq!(
buffer.with_batch(|batch| batch.to_vec()).len(),
2,
"both events are still delivered"
);
}
#[test]
#[should_panic(expected = "already borrowed")]
fn a_behaviour_cannot_reborrow_its_own_component() {
let mut world = World::new();
world
.root_scope_mut()
.spawn(bundle![OnKey::new(|world, entity, _| {
let _ = world.root_scope().query_one::<&OnKey>(entity);
}),]);
let input = InputWriter::new();
world.root_scope_mut().spawn(InputFeed(input.clone()));
input.push(key_event(Key::A));
dispatch_input(&world);
}
}