pub mod focus;
pub mod gesture;
pub mod hit_test;
pub mod input;
pub mod multi_tap;
pub mod scroll;
pub mod sim;
pub mod widget_input;
use crate::ecs::{Entity, World};
use crate::types::Fixed;
use crate::widget::{Parent, UserState};
use focus::key_dispatch;
use gesture::{GestureEvent, GestureSystem};
use hit_test::hit_test;
use input::InputEvent;
use scroll::{ScrollDragState, scroll_system};
#[derive(Clone, Copy, Default)]
pub struct PointerCursor {
pub x: Fixed,
pub y: Fixed,
pub down: bool,
pub event_seq: u32,
}
pub fn dispatch_input(
world: &mut World,
root: Entity,
event: &InputEvent,
now_ms: u32,
lw: u16,
lh: u16,
) {
match event {
InputEvent::PointerDown { x, y, .. } => {
let mut next = world
.resource::<PointerCursor>()
.copied()
.unwrap_or_default();
next.x = *x;
next.y = *y;
next.down = true;
next.event_seq = next.event_seq.wrapping_add(1);
world.insert_resource(next);
}
InputEvent::PointerMove { x, y, .. } => {
let mut next = world
.resource::<PointerCursor>()
.copied()
.unwrap_or_default();
next.x = *x;
next.y = *y;
world.insert_resource(next);
}
InputEvent::PointerUp { x, y, .. } => {
let mut next = world
.resource::<PointerCursor>()
.copied()
.unwrap_or_default();
next.x = *x;
next.y = *y;
next.down = false;
next.event_seq = next.event_seq.wrapping_add(1);
world.insert_resource(next);
}
_ => {}
}
if let InputEvent::PointerDown { x, y, .. } = event {
if let Some(target) = hit_test(world, root, *x, *y, lw, lh) {
if entity_or_ancestor_disabled(world, target) {
key_dispatch(world, event);
return;
}
}
}
scroll_system(world, root, event, lw, lh);
let hit = match event {
InputEvent::PointerDown { x, y, .. } => hit_test(world, root, *x, *y, lw, lh),
_ => None,
};
let scroll_claimed = world
.resource::<ScrollDragState>()
.is_some_and(|s| s.active && s.resolved);
if let Some(gs) = world.resource_mut::<GestureSystem>() {
gs.recognizer.scroll_claimed = scroll_claimed;
gs.recognizer.update(event, now_ms, hit, &mut gs.events);
}
key_dispatch(world, event);
}
pub fn entity_or_ancestor_disabled(world: &World, entity: Entity) -> bool {
let mut cur = Some(entity);
while let Some(e) = cur {
if matches!(world.get::<UserState>(e), Some(UserState::Disabled)) {
return true;
}
cur = world.get::<Parent>(e).map(|p| p.0);
}
false
}
pub struct GestureHandler {
pub on_gesture: fn(&mut World, Entity, &GestureEvent) -> bool,
}
pub struct HandlerCtx<'a, E> {
pub world: &'a mut World,
pub entity: Entity,
pub event: &'a E,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BubbleControl {
Prevent,
Allow,
}
pub trait HandlerReturn {
fn into_consumed(self) -> bool;
}
impl HandlerReturn for () {
fn into_consumed(self) -> bool {
true
}
}
impl HandlerReturn for bool {
fn into_consumed(self) -> bool {
self
}
}
impl HandlerReturn for BubbleControl {
fn into_consumed(self) -> bool {
matches!(self, BubbleControl::Prevent)
}
}
pub fn bubble_dispatch(world: &mut World, event: &GestureEvent) {
bubble_dispatch_at(world, event, 0);
}
pub fn bubble_dispatch_at(world: &mut World, event: &GestureEvent, now_ms: u32) {
multi_tap::observe_gesture(world, event, now_ms);
let mut current = event.target();
loop {
let internals = collect_internal_handlers(world, current);
for f in internals {
if f(world, current, event) {
return;
}
}
let handler_fn = world.get::<GestureHandler>(current).map(|h| h.on_gesture);
if let Some(f) = handler_fn {
if f(world, current, event) {
return;
}
}
match world.get::<Parent>(current) {
Some(p) => current = p.0,
None => return,
}
}
}
fn collect_internal_handlers(
world: &World,
entity: Entity,
) -> alloc::vec::Vec<crate::widget::view::ViewInternalGesture> {
let Some(registry) = world.resource::<crate::widget::view::ViewRegistry>() else {
return alloc::vec::Vec::new();
};
registry
.iter()
.filter_map(|v| {
let f = v.internal_gesture()?;
match v.component_filter() {
Some(type_id) if world.has_type(entity, type_id) => Some(f),
Some(_) => None,
None => Some(f),
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ancestor_disabled_propagates() {
let mut world = World::new();
let parent = world.spawn();
let child = world.spawn();
world.insert(child, Parent(parent));
world.insert(parent, UserState::Disabled);
assert!(entity_or_ancestor_disabled(&world, child));
}
#[test]
fn entity_self_disabled() {
let mut world = World::new();
let e = world.spawn();
world.insert(e, UserState::Disabled);
assert!(entity_or_ancestor_disabled(&world, e));
}
#[test]
fn unrelated_entity_not_disabled() {
let mut world = World::new();
let a = world.spawn();
let b = world.spawn();
world.insert(a, UserState::Disabled);
assert!(!entity_or_ancestor_disabled(&world, b));
}
#[test]
fn errored_does_not_propagate_via_disabled_walk() {
let mut world = World::new();
let e = world.spawn();
world.insert(e, UserState::Errored);
assert!(!entity_or_ancestor_disabled(&world, e));
}
mod dual_channel {
use super::*;
use crate::types::Fixed;
use crate::widget::view::{View, ViewRegistry};
use core::sync::atomic::{AtomicU8, Ordering};
use std::sync::Mutex;
struct ChannelMarker;
static INTERNAL_FIRES: AtomicU8 = AtomicU8::new(0);
static USER_FIRES: AtomicU8 = AtomicU8::new(0);
static SERIAL: Mutex<()> = Mutex::new(());
fn reset() {
INTERNAL_FIRES.store(0, Ordering::SeqCst);
USER_FIRES.store(0, Ordering::SeqCst);
}
fn internal_consume(_w: &mut World, _e: Entity, _ev: &GestureEvent) -> bool {
INTERNAL_FIRES.fetch_add(1, Ordering::SeqCst);
true
}
fn internal_passthrough(_w: &mut World, _e: Entity, _ev: &GestureEvent) -> bool {
INTERNAL_FIRES.fetch_add(1, Ordering::SeqCst);
false
}
fn user_handler(_w: &mut World, _e: Entity, _ev: &GestureEvent) -> bool {
USER_FIRES.fetch_add(1, Ordering::SeqCst);
true
}
fn registry_with(internal: fn(&mut World, Entity, &GestureEvent) -> bool) -> ViewRegistry {
fn dummy_render(
_r: &mut dyn crate::draw::renderer::Renderer,
_w: &World,
_e: Entity,
_rect: &crate::types::Rect,
_ctx: &mut crate::widget::view::ViewCtx,
) {
}
let mut reg = ViewRegistry::default();
reg.insert(
View::new("ChannelMarker", 60, dummy_render)
.with_filter::<ChannelMarker>()
.with_internal_gesture(internal),
);
reg
}
fn tap_event(target: Entity) -> GestureEvent {
GestureEvent::Tap {
x: Fixed::ZERO,
y: Fixed::ZERO,
target,
}
}
#[test]
fn user_only_runs_when_no_internal() {
let _g = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
reset();
let mut world = World::new();
world.insert_resource(ViewRegistry::default());
let e = world.spawn();
world.insert(
e,
GestureHandler {
on_gesture: user_handler,
},
);
bubble_dispatch_at(&mut world, &tap_event(e), 0);
assert_eq!(INTERNAL_FIRES.load(Ordering::SeqCst), 0);
assert_eq!(USER_FIRES.load(Ordering::SeqCst), 1);
}
#[test]
fn internal_only_runs_when_no_user() {
let _g = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
reset();
let mut world = World::new();
world.insert_resource(registry_with(internal_consume));
let e = world.spawn();
world.insert(e, ChannelMarker);
bubble_dispatch_at(&mut world, &tap_event(e), 0);
assert_eq!(INTERNAL_FIRES.load(Ordering::SeqCst), 1);
assert_eq!(USER_FIRES.load(Ordering::SeqCst), 0);
}
#[test]
fn internal_consumes_blocks_user() {
let _g = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
reset();
let mut world = World::new();
world.insert_resource(registry_with(internal_consume));
let e = world.spawn();
world.insert(e, ChannelMarker);
world.insert(
e,
GestureHandler {
on_gesture: user_handler,
},
);
bubble_dispatch_at(&mut world, &tap_event(e), 0);
assert_eq!(INTERNAL_FIRES.load(Ordering::SeqCst), 1);
assert_eq!(
USER_FIRES.load(Ordering::SeqCst),
0,
"internal returned true, user must not fire",
);
}
#[test]
fn internal_passthrough_lets_user_run() {
let _g = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
reset();
let mut world = World::new();
world.insert_resource(registry_with(internal_passthrough));
let e = world.spawn();
world.insert(e, ChannelMarker);
world.insert(
e,
GestureHandler {
on_gesture: user_handler,
},
);
bubble_dispatch_at(&mut world, &tap_event(e), 0);
assert_eq!(INTERNAL_FIRES.load(Ordering::SeqCst), 1);
assert_eq!(
USER_FIRES.load(Ordering::SeqCst),
1,
"internal returned false, user must fire on the same entity",
);
}
}
}