teksilo_core/pointer/table.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! The live pointer table: one entry per pointer the tree currently knows
5//! about, and the two elected roles that replace the tree's old singular
6//! pointer state.
7//!
8//! # Three notions that used to be one
9//!
10//! Before this table the tree had a single `hovered`, a single
11//! `last_pointer_position` and a single `pointer_captured_by`, and "the
12//! pointer" meant all three at once. Multi-touch splits that into three
13//! genuinely different questions:
14//!
15//! 1. [`PointerInfo::primary`](crate::pointer::PointerInfo::primary) is the
16//! **W3C Pointer Events Level 3 per-kind flag**. Every mouse event is
17//! primary, and so is the first touch of a sequence. On a hybrid machine a
18//! mouse and a first touch are *both* primary. It is a property of the
19//! sample, decided by whoever produced it, and this table never rewrites it.
20//! 2. [`PointerTable::primary`] is **Teksilo's** single pointer — the one that
21//! backs the legacy singular position accessor
22//! ([`WidgetTree::last_pointer_position`](crate::WidgetTree::last_pointer_position)).
23//! Exactly one live pointer holds the role, a mouse always wins it, and it
24//! is a table-level election rather than anything carried on a sample.
25//! 3. [`PointerTable::hover_owner`] is the most recent **hovering-capable**
26//! pointer — a mouse, or a pen in proximity ([`PointerKind::hovers`]) — and
27//! the one [`WidgetTree::hovered`](crate::WidgetTree::hovered) reads.
28//! 3. [`PointerTable::hover_owner`] is the most recent **hovering-capable**
29//! pointer — a mouse, or a pen in proximity ([`PointerKind::hovers`]).
30//!
31//! **Hover follows the hover owner, never the primary.** Enter/leave, the
32//! cursor, tooltip dwell and every `on_hover` handler are hover-owner-only, and
33//! a touch contact is never a hover owner: a finger has no hover state to
34//! report, so a contact that wrote hover would make every hover affordance fire
35//! on tap and stick there after the lift. Affordances that today rely on hover
36//! get their own touch routes; they do not get them by pretending a finger
37//! hovers.
38//!
39//! # Why a `Vec`
40//!
41//! The live set is at most [`PointerTable::DEFAULT_CAP`] entries, so every
42//! operation is a linear scan of a handful of elements — cheaper than hashing
43//! a [`PointerId`], and it keeps iteration in a stable, oldest-first order.
44//!
45//! Reference: `docs/touch-and-pen.md`.
46
47use teksilo_canvas::Point;
48use teksilo_tokens::PointerKind;
49
50use super::{PointerId, PointerInfo, PointerPhase, PointerSample};
51use crate::widget_id::WidgetId;
52
53/// Everything the tree tracks about one live pointer.
54///
55/// One of these exists from the pointer's first sample until it lifts (a
56/// contact) or the window loses it (a hovering pointer never lifts, so a
57/// mouse's entry persists for the life of the tree once it has been seen).
58///
59/// `#[non_exhaustive]`: the velocity tracker lands here in a later package
60/// (the gesture-sequence handle already has), and it should not break a
61/// construction site.
62#[non_exhaustive]
63#[derive(Clone, Debug, PartialEq)]
64pub struct PointerEntry {
65 /// Who this pointer is, as of its most recent sample.
66 pub info: PointerInfo,
67 /// Where it is now, in window-logical coordinates.
68 pub position: Point,
69 /// Where its most recent press landed. Equal to
70 /// [`position`](Self::position) until the first press, so a slop test
71 /// against it is never nonsense.
72 pub down_position: Point,
73 /// The widget it is hovering, if it is a hovering-capable pointer that
74 /// currently holds the hover-owner role. Always `None` for a contact.
75 pub hovered: Option<WidgetId>,
76 /// The widget that captured *this* pointer. Capture is per pointer: two
77 /// contacts hold independent captures, and each is released only by its
78 /// own Up or Cancel.
79 pub captured_by: Option<WidgetId>,
80 /// Strict ancestors of [`hovered`](Self::hovered) whose `hover_within`
81 /// signal this pointer currently holds `true`.
82 pub hover_within: Vec<WidgetId>,
83 /// The arbitration in progress for this pointer's press: who is competing
84 /// for it, and who won. `None` between presses — a hovering mouse has an
85 /// entry but no sequence. See
86 /// [`PointerSequence`](crate::gesture::PointerSequence).
87 pub sequence: Option<crate::gesture::PointerSequence>,
88 /// The last widget that answered `Handled` to one of this pointer's
89 /// positional events.
90 ///
91 /// The fallback recipient of a
92 /// [`PointerCancel`](crate::event::WidgetEvent::PointerCancel) when the
93 /// pointer holds no capture: something was interacting with this pointer,
94 /// and it is the only thing the tree can name. Kept per pointer rather
95 /// than per tree, because two contacts on two widgets each have their own
96 /// answer.
97 pub last_accepted: Option<WidgetId>,
98}
99
100impl PointerEntry {
101 /// A fresh entry for `info` first seen at `position`.
102 fn new(info: PointerInfo, position: Point) -> Self {
103 Self {
104 info,
105 position,
106 down_position: position,
107 hovered: None,
108 captured_by: None,
109 hover_within: Vec::new(),
110 sequence: None,
111 last_accepted: None,
112 }
113 }
114
115 /// Whether this pointer can report a position without a button held, and
116 /// so can own hover. See [`PointerKind::hovers`].
117 pub fn hovers(&self) -> bool {
118 self.info.kind.hovers()
119 }
120
121 /// Whether this pointer is touching the surface: a contact is touching for
122 /// as long as it is live, a hovering-capable pointer only while a button
123 /// is held.
124 pub fn is_contacting(&self) -> bool {
125 !self.hovers() || !self.info.buttons.is_empty()
126 }
127}
128
129/// The live pointers, plus the primary and hover-owner elections.
130///
131/// See the [module docs](self) for what those two roles mean and why they are
132/// not the same thing as [`PointerInfo::primary`].
133#[derive(Clone, Debug)]
134pub struct PointerTable {
135 entries: Vec<PointerEntry>,
136 primary: Option<PointerId>,
137 hover_owner: Option<PointerId>,
138 cap: usize,
139}
140
141impl Default for PointerTable {
142 fn default() -> Self {
143 Self::new()
144 }
145}
146
147impl PointerTable {
148 /// How many pointers the table holds at once.
149 ///
150 /// Ten: the maximum simultaneous contacts a Windows digitiser and a
151 /// Wayland `wl_touch` seat both report. An eleventh arrival is refused at
152 /// [`begin`](Self::begin) rather than evicting one of the ten, because
153 /// evicting a contact mid-gesture is how a pinch turns into a fling.
154 pub const DEFAULT_CAP: usize = 10;
155
156 /// An empty table with the default cap.
157 pub fn new() -> Self {
158 Self {
159 entries: Vec::new(),
160 primary: None,
161 hover_owner: None,
162 cap: Self::DEFAULT_CAP,
163 }
164 }
165
166 /// An empty table that holds at most `cap` pointers. For tests that want
167 /// to reach the cap without simulating ten fingers.
168 pub fn with_cap(cap: usize) -> Self {
169 Self {
170 cap: cap.max(1),
171 ..Self::new()
172 }
173 }
174
175 /// The cap this table was built with.
176 pub fn cap(&self) -> usize {
177 self.cap
178 }
179
180 /// The entry for `id`, if that pointer is live.
181 pub fn get(&self, id: PointerId) -> Option<&PointerEntry> {
182 self.entries.iter().find(|e| e.info.id == id)
183 }
184
185 /// Mutable access to the entry for `id`, if that pointer is live.
186 pub fn get_mut(&mut self, id: PointerId) -> Option<&mut PointerEntry> {
187 self.entries.iter_mut().find(|e| e.info.id == id)
188 }
189
190 /// Admit `sample`'s pointer, creating its entry on first sight and
191 /// refreshing it afterwards.
192 ///
193 /// Returns `None` — and traces the refusal — when the sample must not be
194 /// dispatched at all:
195 ///
196 /// * the backend flagged the contact as a palm
197 /// ([`PointerInfo::palm`](crate::pointer::PointerInfo::palm)), or
198 /// * the table is full ([`DEFAULT_CAP`](Self::DEFAULT_CAP)).
199 ///
200 /// A pointer already in the table is always refreshed, cap or no cap: the
201 /// cap bounds how many pointers exist, never how many samples an admitted
202 /// pointer may send.
203 pub fn begin(&mut self, sample: &PointerSample) -> Option<PointerId> {
204 if !self.would_admit(&sample.pointer) {
205 return None;
206 }
207 self.admit(
208 sample.pointer,
209 sample.position,
210 sample.phase == PointerPhase::Down,
211 )
212 }
213
214 /// Whether [`begin`](Self::begin) would accept `info`, without touching the
215 /// table — and tracing the refusal, since a dropped sample that leaves no
216 /// evidence is the hardest input bug there is.
217 ///
218 /// The dispatcher asks this *before* it lowers a sample onto an event, so a
219 /// refused pointer produces no event at all rather than one that is later
220 /// discovered to have no entry behind it.
221 pub fn would_admit(&self, info: &PointerInfo) -> bool {
222 if self.contains(info.id) {
223 // The cap bounds how many pointers exist, never how many samples an
224 // admitted pointer may send.
225 return true;
226 }
227 if info.palm {
228 crate::trace_input!(
229 Samples,
230 "dropping {:?}: the backend classified it as a palm",
231 info.id
232 );
233 return false;
234 }
235 if self.entries.len() >= self.cap {
236 crate::trace_input!(
237 Samples,
238 "dropping {:?}: the pointer table already holds {} pointers",
239 info.id,
240 self.cap
241 );
242 return false;
243 }
244 true
245 }
246
247 /// The primitive behind [`begin`](Self::begin), for the legacy
248 /// [`WidgetEvent`](crate::event::WidgetEvent) path, which has no
249 /// [`PointerSample`] to hand.
250 ///
251 /// `is_down` records `position` as the entry's
252 /// [`down_position`](PointerEntry::down_position).
253 pub fn admit(
254 &mut self,
255 info: PointerInfo,
256 position: Point,
257 is_down: bool,
258 ) -> Option<PointerId> {
259 let id = info.id;
260 if let Some(entry) = self.get_mut(id) {
261 entry.info = info;
262 entry.position = position;
263 if is_down {
264 entry.down_position = position;
265 }
266 self.elect();
267 return Some(id);
268 }
269 if !self.would_admit(&info) {
270 return None;
271 }
272 let mut entry = PointerEntry::new(info, position);
273 if is_down {
274 entry.down_position = position;
275 }
276 self.entries.push(entry);
277 self.elect();
278 Some(id)
279 }
280
281 /// Forget `id`, returning its entry if it was live.
282 ///
283 /// Called for a contact's Up or Cancel. A hovering-capable pointer is
284 /// **not** ended by an Up — a mouse that releases a button is still there,
285 /// still hovering, and its entry is what every legacy singular accessor
286 /// reads.
287 pub fn end(&mut self, id: PointerId) -> Option<PointerEntry> {
288 let index = self.entries.iter().position(|e| e.info.id == id)?;
289 let entry = self.entries.remove(index);
290 self.elect();
291 Some(entry)
292 }
293
294 /// Teksilo's single pointer: the one backing the legacy singular
295 /// accessors. A mouse is preferred; failing that, the oldest live pointer.
296 ///
297 /// Not to be confused with
298 /// [`PointerInfo::primary`](crate::pointer::PointerInfo::primary) — see
299 /// the [module docs](self).
300 pub fn primary(&self) -> Option<&PointerEntry> {
301 self.primary.and_then(|id| self.get(id))
302 }
303
304 /// The most recent hovering-capable pointer, if any is live.
305 ///
306 /// Hover, enter/leave, the cursor and tooltip dwell all follow this
307 /// pointer. A touch contact is never it.
308 pub fn hover_owner(&self) -> Option<&PointerEntry> {
309 self.hover_owner.and_then(|id| self.get(id))
310 }
311
312 /// Mutable access to the hover owner's entry.
313 pub fn hover_owner_mut(&mut self) -> Option<&mut PointerEntry> {
314 let id = self.hover_owner?;
315 self.get_mut(id)
316 }
317
318 /// The hover owner's id, without borrowing its entry.
319 pub fn hover_owner_id(&self) -> Option<PointerId> {
320 self.hover_owner
321 }
322
323 /// The primary pointer's id, without borrowing its entry.
324 pub fn primary_id(&self) -> Option<PointerId> {
325 self.primary
326 }
327
328 /// Every live pointer, oldest first.
329 pub fn iter(&self) -> impl Iterator<Item = &PointerEntry> + '_ {
330 self.entries.iter()
331 }
332
333 /// Every live pointer, mutably, oldest first.
334 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut PointerEntry> + '_ {
335 self.entries.iter_mut()
336 }
337
338 /// How many live pointers there are, hovering or not.
339 pub fn len(&self) -> usize {
340 self.entries.len()
341 }
342
343 /// Whether no pointer is live.
344 pub fn is_empty(&self) -> bool {
345 self.entries.is_empty()
346 }
347
348 /// How many live pointers are **touching the surface** — every contact,
349 /// plus any hovering-capable pointer with a button held. This is the
350 /// number a multi-touch recognizer counts fingers with; it is `0` for a
351 /// resting mouse and `2` for a two-finger pinch.
352 pub fn contact_count(&self) -> usize {
353 self.entries.iter().filter(|e| e.is_contacting()).count()
354 }
355
356 /// Whether `id` names a live pointer.
357 pub fn contains(&self, id: PointerId) -> bool {
358 self.get(id).is_some()
359 }
360
361 /// Scrub widget references that no longer name an active node.
362 ///
363 /// A rebuild mints fresh ids, so a hover target, a capture or a
364 /// `hover_within` ancestor recorded before it can be dangling afterwards —
365 /// and a dangling capture swallows every later Move and Up, because
366 /// dispatch refuses an inactive target. Called from the tree's
367 /// post-layout revalidation *after* it has run the hover-owner's own
368 /// signal-firing recovery, so this is the sweep for every other pointer.
369 pub fn retain_active(&mut self, arena: &crate::arena::WidgetArena) {
370 for entry in &mut self.entries {
371 if entry.hovered.is_some_and(|id| !arena.is_active(id)) {
372 entry.hovered = None;
373 }
374 if entry.captured_by.is_some_and(|id| !arena.is_active(id)) {
375 entry.captured_by = None;
376 }
377 entry.hover_within.retain(|id| arena.is_active(*id));
378 if entry.last_accepted.is_some_and(|id| !arena.is_active(id)) {
379 entry.last_accepted = None;
380 }
381 }
382 }
383
384 /// Release every capture held on `widget` — it is going away, and a
385 /// capture that outlives its owner strands the pointer.
386 pub fn release_captures_of(&mut self, widget: WidgetId) {
387 for entry in &mut self.entries {
388 if entry.captured_by == Some(widget) {
389 entry.captured_by = None;
390 }
391 }
392 }
393
394 /// Re-run both elections after the live set or a pointer's kind changed.
395 ///
396 /// **Primary**: a mouse if one is live (there is at most one), else the
397 /// oldest pointer — ids are minted monotonically, so "oldest" is "lowest
398 /// id", and a stable choice is what keeps the legacy accessors from
399 /// flickering between two contacts.
400 ///
401 /// **Hover owner**: the *most recent* hovering-capable pointer, which for
402 /// a mouse-plus-pen machine means the one the user last used. Preserved
403 /// across an election when it is still live and still hovering-capable, so
404 /// a contact arriving alongside a hovering mouse cannot take the role.
405 fn elect(&mut self) {
406 self.primary = self
407 .entries
408 .iter()
409 .find(|e| e.info.kind == PointerKind::Mouse)
410 .or_else(|| self.entries.iter().min_by_key(|e| e.info.id))
411 .map(|e| e.info.id);
412
413 let owner_still_valid = self
414 .hover_owner
415 .and_then(|id| self.get(id))
416 .is_some_and(|e| e.hovers());
417 if !owner_still_valid {
418 self.hover_owner = self
419 .entries
420 .iter()
421 .filter(|e| e.hovers())
422 .max_by_key(|e| e.info.id)
423 .map(|e| e.info.id);
424 }
425 }
426
427 /// Hand the hover-owner role to `id`, reporting the pointer that lost it.
428 ///
429 /// Called when a hovering-capable pointer produces a sample: the later
430 /// sample wins, and the caller sends the displaced owner a
431 /// [`PointerLeave`](crate::event::WidgetEvent::PointerLeave). A contact is
432 /// refused outright — it can never own hover — and so is a pointer that is
433 /// not live.
434 pub fn claim_hover_owner(&mut self, id: PointerId) -> Option<PointerId> {
435 if !self.get(id).is_some_and(|e| e.hovers()) {
436 return None;
437 }
438 let previous = self.hover_owner;
439 if previous == Some(id) {
440 return None;
441 }
442 self.hover_owner = Some(id);
443 previous.filter(|prev| self.contains(*prev))
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use crate::pointer::{EventTime, PointerAxes};
451 use teksilo_tokens::PenKind;
452
453 fn id(raw: u64) -> PointerId {
454 // Mint through the allocator so ids stay monotonic and distinct from
455 // `PointerId::MOUSE`; the device key is per-test so nothing collides.
456 let alloc = crate::pointer::PointerIdAllocator::global();
457 let device = crate::pointer::BackendDeviceKey::new(0x7AB1E ^ raw);
458 let out = alloc.begin(device, raw);
459 alloc.end(device, raw);
460 out
461 }
462
463 fn touch(pid: PointerId) -> PointerInfo {
464 PointerInfo::touch(pid, EventTime::ZERO)
465 }
466
467 fn mouse() -> PointerInfo {
468 PointerInfo::mouse(EventTime::ZERO)
469 }
470
471 fn pen(pid: PointerId) -> PointerInfo {
472 let mut info = PointerInfo::touch(pid, EventTime::ZERO);
473 info.kind = PointerKind::Pen(PenKind::Pen);
474 info
475 }
476
477 fn sample(info: PointerInfo, phase: PointerPhase, at: Point) -> PointerSample {
478 PointerSample {
479 pointer: info,
480 phase,
481 position: at,
482 button: None,
483 modifiers: crate::event::Modifiers::NONE,
484 coalesced: Vec::new(),
485 }
486 }
487
488 #[test]
489 fn a_mouse_wins_the_primary_role_over_an_older_contact() {
490 let mut table = PointerTable::new();
491 let finger = id(1);
492 table.admit(touch(finger), Point::ZERO, true);
493 assert_eq!(table.primary_id(), Some(finger), "the only live pointer");
494
495 table.admit(mouse(), Point::new(5.0, 5.0), false);
496 assert_eq!(
497 table.primary_id(),
498 Some(PointerId::MOUSE),
499 "a mouse always wins the primary role"
500 );
501 }
502
503 #[test]
504 fn a_contact_is_never_the_hover_owner() {
505 let mut table = PointerTable::new();
506 let finger = id(2);
507 table.admit(touch(finger), Point::ZERO, true);
508 assert_eq!(table.hover_owner_id(), None, "a finger cannot hover");
509 assert_eq!(table.claim_hover_owner(finger), None);
510 assert_eq!(table.hover_owner_id(), None);
511 }
512
513 #[test]
514 fn a_pen_can_own_hover_and_displaces_the_mouse() {
515 let mut table = PointerTable::new();
516 table.admit(mouse(), Point::ZERO, false);
517 assert_eq!(table.hover_owner_id(), Some(PointerId::MOUSE));
518
519 let stylus = id(3);
520 table.admit(pen(stylus), Point::new(2.0, 2.0), false);
521 // The pen is admitted but does not seize the role until it produces a
522 // sample the tree routes — `claim_hover_owner` is that moment.
523 assert_eq!(table.hover_owner_id(), Some(PointerId::MOUSE));
524 assert_eq!(
525 table.claim_hover_owner(stylus),
526 Some(PointerId::MOUSE),
527 "the displaced owner is reported so it can be sent a leave"
528 );
529 assert_eq!(table.hover_owner_id(), Some(stylus));
530 }
531
532 #[test]
533 fn ending_the_hover_owner_falls_back_to_another_hovering_pointer() {
534 let mut table = PointerTable::new();
535 table.admit(mouse(), Point::ZERO, false);
536 let stylus = id(4);
537 table.admit(pen(stylus), Point::ZERO, false);
538 table.claim_hover_owner(stylus);
539
540 table.end(stylus);
541 assert_eq!(table.hover_owner_id(), Some(PointerId::MOUSE));
542 }
543
544 #[test]
545 fn captures_are_independent_per_pointer() {
546 let mut table = PointerTable::new();
547 let a = id(5);
548 let b = id(6);
549 table.admit(touch(a), Point::ZERO, true);
550 table.admit(touch(b), Point::new(50.0, 0.0), true);
551
552 let mut arena = crate::arena::WidgetArena::new();
553 let w1 = arena.insert(Box::new(crate::test_widgets::FillWidget::new()));
554 let w2 = arena.insert(Box::new(crate::test_widgets::FillWidget::new()));
555 table.get_mut(a).expect("a is live").captured_by = Some(w1);
556 table.get_mut(b).expect("b is live").captured_by = Some(w2);
557
558 table.end(a);
559 assert_eq!(
560 table.get(b).and_then(|e| e.captured_by),
561 Some(w2),
562 "ending one contact must leave the other's capture alone"
563 );
564 }
565
566 #[test]
567 fn the_eleventh_contact_is_refused() {
568 let mut table = PointerTable::new();
569 for n in 0..PointerTable::DEFAULT_CAP {
570 let pid = id(100 + n as u64);
571 assert_eq!(
572 table.begin(&sample(touch(pid), PointerPhase::Down, Point::ZERO)),
573 Some(pid),
574 "contact {} is within the cap",
575 n + 1
576 );
577 }
578 assert_eq!(table.len(), PointerTable::DEFAULT_CAP);
579
580 let overflow = id(200);
581 assert_eq!(
582 table.begin(&sample(touch(overflow), PointerPhase::Down, Point::ZERO)),
583 None,
584 "the eleventh contact must be refused, not evicted onto another"
585 );
586 assert_eq!(table.len(), PointerTable::DEFAULT_CAP);
587 }
588
589 #[test]
590 fn a_live_pointer_is_refreshed_even_at_the_cap() {
591 let mut table = PointerTable::with_cap(1);
592 let finger = id(7);
593 table.admit(touch(finger), Point::ZERO, true);
594 assert_eq!(
595 table.admit(touch(finger), Point::new(9.0, 9.0), false),
596 Some(finger),
597 "the cap bounds pointers, not samples"
598 );
599 assert_eq!(
600 table.get(finger).map(|e| e.position),
601 Some(Point::new(9.0, 9.0))
602 );
603 assert_eq!(
604 table.get(finger).map(|e| e.down_position),
605 Some(Point::ZERO),
606 "a move must not move the press origin"
607 );
608 }
609
610 #[test]
611 fn a_palm_is_refused() {
612 let mut table = PointerTable::new();
613 let mut info = touch(id(8));
614 info.palm = true;
615 assert_eq!(
616 table.begin(&sample(info, PointerPhase::Down, Point::ZERO)),
617 None
618 );
619 assert!(table.is_empty());
620 }
621
622 #[test]
623 fn contact_count_ignores_a_resting_mouse() {
624 let mut table = PointerTable::new();
625 table.admit(mouse(), Point::ZERO, false);
626 assert_eq!(
627 table.contact_count(),
628 0,
629 "a hovering mouse is not a contact"
630 );
631
632 let mut pressed = mouse();
633 pressed.buttons = crate::event::ButtonMask::PRIMARY;
634 table.admit(pressed, Point::ZERO, true);
635 assert_eq!(table.contact_count(), 1, "a held button is a contact");
636
637 table.admit(touch(id(9)), Point::ZERO, true);
638 assert_eq!(table.contact_count(), 2);
639 }
640
641 #[test]
642 fn retain_active_scrubs_dead_widget_references() {
643 let mut arena = crate::arena::WidgetArena::new();
644 let live = arena.insert(Box::new(crate::test_widgets::FillWidget::new()));
645 let dead = WidgetId::default(); // the slotmap null key: never active
646
647 let mut table = PointerTable::new();
648 let finger = id(10);
649 table.admit(touch(finger), Point::ZERO, true);
650 {
651 let entry = table.get_mut(finger).expect("finger is live");
652 entry.hovered = Some(dead);
653 entry.captured_by = Some(dead);
654 entry.hover_within = vec![live, dead];
655 }
656 table.retain_active(&arena);
657
658 let entry = table.get(finger).expect("finger is still live");
659 assert_eq!(entry.hovered, None);
660 assert_eq!(entry.captured_by, None);
661 assert_eq!(entry.hover_within, vec![live]);
662 }
663
664 /// The default axes are carried through unchanged — the table stores the
665 /// sample's `PointerInfo` verbatim rather than rebuilding it.
666 #[test]
667 fn the_entry_keeps_the_samples_own_info() {
668 let mut table = PointerTable::new();
669 let stylus = id(11);
670 let mut info = pen(stylus);
671 info.axes = PointerAxes {
672 pressure: Some(0.4),
673 ..PointerAxes::default()
674 };
675 table.admit(info, Point::new(1.0, 2.0), true);
676 assert_eq!(
677 table.get(stylus).map(|e| e.info.axes.pressure),
678 Some(Some(0.4))
679 );
680 }
681}