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