Two defects Iris reported from her phone on 2026-09-08, one root cause each, both in how a gesture ends. A widget that takes pointer capture cuts every other widget off from the press completely -- no PressEnd, no Drop -- so anything else tracking it was left with an open gesture at a stale origin, and the *next* touch anywhere was measured from that origin. That is the transcript jumping on a tap after a code fence was panned sideways. CursorSense::Cancel is the missing state: delivered once to each loser of a capture race, the way Android sends ACTION_CANCEL and the web sends pointercancel. And registered click_or_drag|unclick, which never matches a Drop, so a Scroll that had captured never saw its own gesture end and stayed panning from where the finger left. That is the horizontal snap back. CursorSense::drag_senses() states the rule once for every widget driving a DragGesture instead of per call site. The pointer's own state (who holds capture, who is tracking the press) no longer lives in a Mutex on UiRenderState. It is Event::Global for the cursor senses -- owned by the event manager that runs the dispatch, reached by &mut, with a per-dispatch PointerRequests slot for handlers -- per Iris: never reach for locks first, and input-wide state belongs to the general input handler. What had forced the lock was a Data: Send bound on task_on that nothing needed; the spawned future never sees the event's data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 lines
1.8 KiB
Rust
59 lines
1.8 KiB
Rust
mod ctx;
|
|
mod manager;
|
|
mod rsc;
|
|
|
|
pub use ctx::*;
|
|
pub use manager::*;
|
|
pub use rsc::*;
|
|
|
|
pub trait Event: Sized + 'static + Clone {
|
|
type Data<'a>: Clone = ();
|
|
type State: Default = ();
|
|
/// State this event owns that belongs to no single widget -- what the
|
|
/// thing dispatching the event knows about the *input*, rather than
|
|
/// about a listener. `()` for almost every event; the cursor's is
|
|
/// `iris::sense::PointerInput` (which widget holds pointer capture,
|
|
/// and who is tracking the press in flight).
|
|
///
|
|
/// It lives here so that such state has one owner, reached by `&mut`
|
|
/// through the event manager, instead of being parked on whatever
|
|
/// structure a handler happens to be able to reach and guarded with a
|
|
/// lock. Iris asked for that on 2026-09-08, of the pointer capture
|
|
/// that used to sit in a `Mutex` on `UiRenderState`: "everything
|
|
/// global should be stored in the general input handler, not in
|
|
/// specific senses with locking stuff."
|
|
type Global: Default = ();
|
|
#[allow(unused_variables)]
|
|
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
|
Some(data.clone())
|
|
}
|
|
}
|
|
|
|
pub trait EventLike {
|
|
type Event: Event;
|
|
fn into_event(self) -> Self::Event;
|
|
}
|
|
|
|
impl<E: Event> EventLike for E {
|
|
type Event = Self;
|
|
|
|
fn into_event(self) -> Self::Event {
|
|
self
|
|
}
|
|
}
|
|
|
|
pub trait EventFn<Rsc: HasEvents, Data>: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static {}
|
|
impl<Rsc: HasEvents, F: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static, Data> EventFn<Rsc, Data>
|
|
for F
|
|
{
|
|
}
|
|
|
|
pub trait WidgetEventFn<Rsc: HasEvents, Data, W: ?Sized>:
|
|
Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static
|
|
{
|
|
}
|
|
impl<Rsc: HasEvents, F: Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static, Data, W: ?Sized>
|
|
WidgetEventFn<Rsc, Data, W> for F
|
|
{
|
|
}
|