From d03012938e42d2c547ea82e6b903454b2bb155c1 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Thu, 1 Oct 2026 23:58:27 -0700 Subject: [PATCH] fix(shell): a press on the window in front never reaches a card window behind it A click on Mail, cascaded over News, opened the News story under the click (instrument scenario X1, bug B3). The desk offers a press to its tiles topmost first, and Mail's tile claims it (`handled`) where none of Mail's widgets took it. News's tile still handed the claimed press to its app and counted on the claim to keep the app's widgets off it. The claim does not do that: News's story rows are GestureViews, whose makepad hit test is `hits_with_capture_overload(.., true)`, and that captures a press another area has already claimed (touches it reads from the raw stream). On release the row tapped and opened its story. List drags and a View's `on_item_tap` co-capture the same way. A module tile now lets an already claimed press go by, as it lets one outside it go by. Something in front of the tile took it (a window over it, or a shell surface), so the app never sees it. A process tile has had this gate all along through `event.hits`. The closed face of a stopped app takes presses the same way and claims the one it takes, so a click on a window over its Restart no longer restarts it, and a click on the face no longer raises the window behind it. Tests (module_input_tests.rs): two cascaded module tiles, each press offered front to back as the desk does: a click and a touch with a probe app that takes presses as GestureView does, makepad's own GestureView's capture, and the closed face. Co-Authored-By: Claude Opus 5.5 --- crates/shell/src/lib.rs | 2 + crates/shell/src/module_input_tests.rs | 406 +++++++++++++++++++++++++ crates/shell/src/module_view.rs | 83 +++-- 3 files changed, 465 insertions(+), 26 deletions(-) create mode 100644 crates/shell/src/module_input_tests.rs diff --git a/crates/shell/src/lib.rs b/crates/shell/src/lib.rs index 6bb56cdf..de004007 100644 --- a/crates/shell/src/lib.rs +++ b/crates/shell/src/lib.rs @@ -52,6 +52,8 @@ pub mod module_host; #[cfg(test)] mod module_close_tests; #[cfg(test)] +mod module_input_tests; +#[cfg(test)] mod module_panic_tests; #[cfg(test)] mod module_peer_tests; diff --git a/crates/shell/src/module_input_tests.rs b/crates/shell/src/module_input_tests.rs new file mode 100644 index 00000000..f7b5b752 --- /dev/null +++ b/crates/shell/src/module_input_tests.rs @@ -0,0 +1,406 @@ +//! A press between overlapping module tiles. The desk offers pointer +//! events to its tiles topmost first (desk.rs `handle_event`), so a press +//! on the window in front reaches the tiles of the windows behind it +//! already claimed. A module tile behind lets such a press by: its app never +//! sees it. Before, the tile handed it to the app and counted on the claim, +//! which a GestureView ignores. News's story rows are GestureViews, so a +//! click on Mail's inbox footer, over News's third story, opened that story +//! (the instrument's B3). + +use crate::module_host::ModuleHost; +use crate::module_view::MpModuleView; +use crate::run_view::MpRunViewAction; +use makepad_ai_services::wire::{ServiceCall, ServiceManifest, ToolResult}; +use makepad_app_module::*; +use makepad_platform::event::{TouchPoint, TouchState, TouchUpdateEvent}; +use makepad_widgets::*; +use std::cell::Cell; + +script_mod! { + use mod.prelude.widgets_internal.* + use mod.widgets.* + + mod.widgets.TapProbeBase = #(TapProbe::register_widget(vm)) + mod.widgets.TapProbe = set_type_default() do mod.widgets.TapProbeBase { + width: Fill + height: Fill + } +} + +/// An app's root that counts the presses reaching it. A tappable one takes +/// them as makepad's GestureView does: a button press through +/// `hits_with_capture_overload`, which co-captures a press another area +/// already claimed, and a touch straight from the raw stream. A claim alone +/// never keeps a press from it. An inert one takes nothing, like the empty +/// part of a window (Mail's inbox footer). +#[derive(Script, ScriptHook, Widget)] +pub struct TapProbe { + #[uid] + uid: WidgetUid, + #[source] + source: ScriptObjectRef, + #[walk] + walk: Walk, + #[layout] + layout: Layout, + #[redraw] + #[live] + draw_bg: DrawColor, + #[rust] + tappable: bool, + /// Presses (a button going down, a new touch) that reached the app. + #[rust] + seen: usize, + /// Presses it took and saw released over it. + #[rust] + taps: usize, + /// The touch it follows. + #[rust] + touch: Option, +} + +impl Widget for TapProbe { + fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) { + match event { + Event::MouseDown(_) => self.seen += 1, + Event::TouchUpdate(update) => { + self.seen += update.touches.iter().filter(|t| t.state == TouchState::Start).count(); + } + _ => {} + } + if !self.tappable { + return; + } + let area = self.draw_bg.area(); + if let Event::TouchUpdate(update) = event { + let bounds = area.clipped_rect(cx); + for t in &update.touches { + match t.state { + TouchState::Start if self.touch.is_none() && bounds.contains(t.abs) => self.touch = Some(t.uid), + TouchState::Stop if self.touch == Some(t.uid) => { + self.touch = None; + if bounds.contains(t.abs) { + self.taps += 1; + } + } + _ => {} + } + } + return; + } + if let Hit::FingerUp(e) = event.hits_with_capture_overload(cx, area, true) { + if e.is_over { + self.taps += 1; + } + } + } + + fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawStep { + cx.begin_turtle(walk, self.layout); + let rect = cx.turtle().rect(); + self.draw_bg.draw_abs(cx, rect); + cx.end_turtle(); + DrawStep::done() + } +} + +#[derive(Clone, Copy)] +enum Root { + /// A TapProbe that takes presses. + Tappable, + /// A TapProbe that takes none. + Inert, + /// Makepad's own GestureView. + Gesture, +} + +struct InputProbe { + id: &'static str, + root: Root, +} + +impl AppModule for InputProbe { + fn id(&self) -> &'static str { + self.id + } + fn label(&self) -> &'static str { + "Input probe" + } + fn capabilities(&self) -> &'static [&'static str] { + &[] + } + fn open_schema(&self) -> OpenSchema { + OpenSchema::new(1) + } + fn register(&self, vm: &mut ScriptVm) { + self::script_mod(vm); + } + fn create(&self, vm: &mut ScriptVm, _open: ValidatedOpen, _handles: InstanceHandles) -> InstanceParts { + let root = match self.root { + // The tile draws its root filling it (`Walk::fill()`). + Root::Gesture => { + let value = script_eval!(vm, { use mod.widgets.* GestureView {} }); + WidgetRef::script_from_value(vm, value) + } + Root::Tappable | Root::Inert => { + let value = script_eval!(vm, { use mod.widgets.* TapProbe {} }); + let root = WidgetRef::script_from_value(vm, value); + root.borrow_mut::().expect("a probe root").tappable = matches!(self.root, Root::Tappable); + root + } + }; + InstanceParts { root, executor: Box::new(Idle), shutdown: Box::new(|_| {}) } + } +} + +struct Idle; +impl ServiceExecutor for Idle { + fn manifest(&self) -> ServiceManifest { + ServiceManifest::new("input-probe", "Input probe", "test") + } + fn execute(&mut self, _cx: &mut Cx, call: &ServiceCall) -> ExecOutcome { + ExecOutcome::Done(ToolResult::unavailable(&call.call_id, "nothing to do")) + } +} + +/// News: rows that take any press that reaches them. +static NEWS: InputProbe = InputProbe { id: "news-probe", root: Root::Tappable }; +/// Mail: nothing under the click. +static MAIL: InputProbe = InputProbe { id: "mail-probe", root: Root::Inert }; +/// News with makepad's GestureView as its row, as its bundle has it. +static GESTURE_NEWS: InputProbe = InputProbe { id: "gesture-probe", root: Root::Gesture }; + +fn setup() -> (Cx, ModuleHost) { + let mut cx = Cx::new(Box::new(|_, _| {})); + cx.with_vm(makepad_widgets::script_mod); + (cx, ModuleHost::default()) +} + +fn create(cx: &mut Cx, host: &mut ModuleHost, client: u64, module: &'static dyn AppModule) { + host.create(cx, client, module, module.open_schema().empty_open().unwrap(), dvec2(400.0, 500.0)).unwrap(); +} + +fn tile(cx: &mut Cx, host: &ModuleHost, client: u64) -> WidgetRef { + let tile = cx.with_vm(|vm| { + script_eval!(vm, { mod.wm_theme = { background: #1a1b26 } }); + crate::module_view::script_mod(vm); + let value = script_eval!(vm, { use mod.widgets.* MpModuleView {} }); + WidgetRef::script_from_value(vm, value) + }); + let instance = host.get(client).unwrap(); + let (vm_id, root) = (instance.vm_id, instance.root.clone()); + tile.borrow_mut::().unwrap().set_root(cx, client, vm_id, root); + tile +} + +/// A window's rect: 400 by 500 at (x, y). +fn at(x: f64, y: f64) -> Rect { + Rect { pos: dvec2(x, y), size: dvec2(400.0, 500.0) } +} + +/// The areas a tile hit-tests against live in the frame's draw list: kept +/// while the test offers events. +struct Frame { + _pass: DrawPass, + _list: DrawList2d, +} + +/// One frame with the tiles drawn back to front, each at its own rect, as +/// the desk draws cascaded windows. The root turtle ends as a window's does +/// (`end_pass_sized_turtle`), which applies the clips every hit test reads. +fn draw_stack(cx: &mut Cx, tiles: &[(&WidgetRef, Rect)]) -> Frame { + let pass = DrawPass::new(cx); + pass.set_size(cx, dvec2(900.0, 800.0)); + let mut list = DrawList2d::new(cx); + let event = DrawEvent::default(); + let mut draw = CxDraw::new(cx, &event); + let mut cx = Cx2d::new(&mut draw); + cx.begin_pass(&pass, Some(1.0)); + list.begin_always(&mut cx); + cx.begin_root_turtle(dvec2(900.0, 800.0), Layout::flow_overlay()); + for (tile, rect) in tiles { + tile.draw_walk_all(&mut cx, &mut Scope::empty(), Walk::abs_rect(*rect)); + } + cx.end_pass_sized_turtle(); + list.end(&mut cx); + cx.end_pass(&pass); + Frame { _pass: pass, _list: list } +} + +fn mouse_down(abs: Vec2d) -> Event { + Event::MouseDown(MouseDownEvent { + abs, + button: MouseButton::PRIMARY, + window_id: WindowId(0, 0), + modifiers: Default::default(), + handled: Cell::new(Area::Empty), + time: 1.0, + }) +} + +fn mouse_up(abs: Vec2d) -> Event { + Event::MouseUp(MouseUpEvent { abs, button: MouseButton::PRIMARY, window_id: WindowId(0, 0), modifiers: Default::default(), time: 1.1 }) +} + +fn touch(abs: Vec2d, state: TouchState) -> Event { + Event::TouchUpdate(TouchUpdateEvent { + time: 1.0, + window_id: WindowId(0, 0), + modifiers: Default::default(), + touches: vec![TouchPoint { state, abs, time: 1.0, uid: 1, rotation_angle: 0.0, force: 0.0, radius: dvec2(1.0, 1.0), handled: Default::default(), sweep_lock: Default::default() }], + }) +} + +/// What a tile asked the window manager for. +#[derive(Debug, PartialEq)] +enum Asked { + Raise(u64), + Restart(u64), +} + +/// Offer each event to the tiles front to back, as the desk does, and +/// collect what the tiles asked for. +fn offer(cx: &mut Cx, front_to_back: &[&WidgetRef], events: &[Event]) -> Vec { + let actions = cx.capture_actions(|cx| { + for event in events { + for tile in front_to_back { + tile.handle_event(cx, event, &mut Scope::empty()); + } + } + }); + actions + .iter() + .filter_map(|action| action.as_widget_action()) + .filter_map(|wa| match wa.cast::() { + MpRunViewAction::Clicked { client } => Some(Asked::Raise(client)), + MpRunViewAction::Restart { client } => Some(Asked::Restart(client)), + _ => None, + }) + .collect() +} + +fn click(cx: &mut Cx, front_to_back: &[&WidgetRef], abs: Vec2d) -> Vec { + offer(cx, front_to_back, &[mouse_down(abs), mouse_up(abs)]) +} + +fn tap(cx: &mut Cx, front_to_back: &[&WidgetRef], abs: Vec2d) -> Vec { + offer(cx, front_to_back, &[touch(abs, TouchState::Start), touch(abs, TouchState::Stop)]) +} + +/// (presses seen, taps) of a probe app. +fn counts(cx: &mut Cx, host: &mut ModuleHost, client: u64) -> (usize, usize) { + host.dispatch(cx, client, "a test read", |_, root| { + let probe = root.borrow::().unwrap(); + (probe.seen, probe.taps) + }) + .unwrap() +} + +/// News is opened, then Mail, which cascades over it. A press inside both +/// lands on Mail where Mail has nothing to take it, over a News row. +const NEWS_AT: (f64, f64) = (20.0, 40.0); +const MAIL_AT: (f64, f64) = (120.0, 140.0); +/// Inside Mail, over News. +const OVER_BOTH: (f64, f64) = (300.0, 450.0); +/// Inside News where Mail does not cover it. +const NEWS_ONLY: (f64, f64) = (60.0, 450.0); + +fn news_and_mail(news: &'static dyn AppModule) -> (Cx, ModuleHost, WidgetRef, WidgetRef, Frame) { + let (mut cx, mut host) = setup(); + create(&mut cx, &mut host, 1, news); + create(&mut cx, &mut host, 2, &MAIL); + let news = tile(&mut cx, &host, 1); + let mail = tile(&mut cx, &host, 2); + let frame = draw_stack(&mut cx, &[(&news, at(NEWS_AT.0, NEWS_AT.1)), (&mail, at(MAIL_AT.0, MAIL_AT.1))]); + assert!(at(MAIL_AT.0, MAIL_AT.1).contains(dvec2(OVER_BOTH.0, OVER_BOTH.1)) && at(NEWS_AT.0, NEWS_AT.1).contains(dvec2(OVER_BOTH.0, OVER_BOTH.1))); + (cx, host, news, mail, frame) +} + +fn close(mut cx: Cx, mut host: ModuleHost, tiles: Vec, frame: Frame, clients: &[u64]) { + drop(tiles); + drop(frame); + for client in clients { + assert!(host.teardown(&mut cx, *client)); + } +} + +#[test] +fn a_click_on_the_window_in_front_never_reaches_the_app_behind() { + let (mut cx, mut host, news, mail, frame) = news_and_mail(&NEWS); + let over_both = dvec2(OVER_BOTH.0, OVER_BOTH.1); + assert_eq!(click(&mut cx, &[&mail, &news], over_both), [Asked::Raise(2)], "Mail alone is raised"); + assert_eq!(counts(&mut cx, &mut host, 2), (1, 0), "Mail's app saw the press"); + assert_eq!(counts(&mut cx, &mut host, 1), (0, 0), "News's app never saw it, so no story opened"); + // Where News is in front of nothing, the same row takes the click. + assert_eq!(click(&mut cx, &[&mail, &news], dvec2(NEWS_ONLY.0, NEWS_ONLY.1)), [Asked::Raise(1)]); + assert_eq!(counts(&mut cx, &mut host, 1), (1, 1), "a click where News is in front opens its story"); + assert_eq!(counts(&mut cx, &mut host, 2), (1, 0)); + close(cx, host, vec![news, mail], frame, &[1, 2]); +} + +/// A finger: the rows follow new touches from the raw stream, as GestureView +/// does, so only the tile can keep one from them. +#[test] +fn a_touch_on_the_window_in_front_never_reaches_the_app_behind() { + let (mut cx, mut host, news, mail, frame) = news_and_mail(&NEWS); + assert_eq!(tap(&mut cx, &[&mail, &news], dvec2(OVER_BOTH.0, OVER_BOTH.1)), [Asked::Raise(2)]); + assert_eq!(counts(&mut cx, &mut host, 2), (1, 0)); + assert_eq!(counts(&mut cx, &mut host, 1), (0, 0), "News's app never saw the touch"); + assert_eq!(tap(&mut cx, &[&mail, &news], dvec2(NEWS_ONLY.0, NEWS_ONLY.1)), [Asked::Raise(1)]); + assert_eq!(counts(&mut cx, &mut host, 1), (1, 1)); + close(cx, host, vec![news, mail], frame, &[1, 2]); +} + +/// With makepad's own GestureView as the row: its tap fires on the release +/// of a press it captured, and it captured the one Mail's tile had claimed. +#[test] +fn a_gesture_view_behind_the_window_in_front_never_captures_its_press() { + let (mut cx, mut host, news, mail, frame) = news_and_mail(&GESTURE_NEWS); + let row = host.dispatch(&mut cx, 1, "a test read", |_, root| root.area()).unwrap(); + let over_both = dvec2(OVER_BOTH.0, OVER_BOTH.1); + assert_eq!(offer(&mut cx, &[&mail, &news], &[mouse_down(over_both)]), [Asked::Raise(2)]); + assert!(!cx.fingers.is_area_captured(row), "News's GestureView took a press made on Mail"); + offer(&mut cx, &[&mail, &news], &[mouse_up(over_both)]); + let news_only = dvec2(NEWS_ONLY.0, NEWS_ONLY.1); + assert_eq!(offer(&mut cx, &[&mail, &news], &[mouse_down(news_only)]), [Asked::Raise(1)]); + assert!(cx.fingers.is_area_captured(row), "where News is in front its row takes the press"); + offer(&mut cx, &[&mail, &news], &[mouse_up(news_only)]); + close(cx, host, vec![news, mail], frame, &[1, 2]); +} + +/// The closed face of an app that stopped after an error takes presses the +/// same way. A click on a window over its Restart does not restart it, and a +/// click on the face raises no window behind it. +#[test] +fn a_stopped_tile_takes_only_a_press_nothing_in_front_took() { + let (mut cx, mut host) = setup(); + create(&mut cx, &mut host, 1, &NEWS); + create(&mut cx, &mut host, 2, &MAIL); + create(&mut cx, &mut host, 3, &NEWS); + let (stopped, over, under) = (tile(&mut cx, &host, 1), tile(&mut cx, &host, 2), tile(&mut cx, &host, 3)); + stopped.borrow_mut::().unwrap().show_failed(&mut cx, "News"); + // The stopped app at (100, 100); Mail over its left part and its Restart; + // another app under it, reaching out right of Mail. + let (under_at, stopped_at, mail_at) = (at(150.0, 0.0), at(100.0, 100.0), at(60.0, 120.0)); + let frame = draw_stack(&mut cx, &[(&under, under_at), (&stopped, stopped_at), (&over, mail_at)]); + let restart = stopped.borrow::().unwrap().restart_button().expect("the closed face has its Restart"); + assert!(restart.is_inside_of(mail_at) && under_at.contains(restart.center()), "Mail covers the Restart: {restart:?}"); + let front_to_back = [&over, &stopped, &under]; + assert_eq!(click(&mut cx, &front_to_back, restart.center()), [Asked::Raise(2)], "a click on Mail is Mail's"); + assert_eq!(counts(&mut cx, &mut host, 3), (0, 0)); + // On the face, clear of Mail: the stopped tile is raised, and the app + // under it never sees the press. + let face = dvec2(480.0, 300.0); + assert!(stopped_at.contains(face) && !mail_at.contains(face) && under_at.contains(face)); + assert_eq!(click(&mut cx, &front_to_back, face), [Asked::Raise(1)]); + assert_eq!(counts(&mut cx, &mut host, 3), (0, 0)); + // Mail moved off it: its Restart restarts it. + drop(frame); + let mail_at = at(500.0, 300.0); + let frame = draw_stack(&mut cx, &[(&under, under_at), (&stopped, stopped_at), (&over, mail_at)]); + let restart = stopped.borrow::().unwrap().restart_button().unwrap(); + assert!(!mail_at.contains(restart.center())); + assert_eq!(click(&mut cx, &front_to_back, restart.center()), [Asked::Restart(1)]); + assert_eq!(counts(&mut cx, &mut host, 3), (0, 0)); + close(cx, host, vec![stopped, over, under], frame, &[1, 2, 3]); +} diff --git a/crates/shell/src/module_view.rs b/crates/shell/src/module_view.rs index 657453aa..759a4e47 100644 --- a/crates/shell/src/module_view.rs +++ b/crates/shell/src/module_view.rs @@ -202,8 +202,16 @@ impl MpModuleView { cx.end_turtle(); } - /// A press on the closed face: Restart, or just focus the tile. + /// A press on the closed face: Restart, or just focus the tile. The face + /// takes presses as the live tile does: only one inside it that nothing + /// in front claimed, and it claims what it takes, so a click on a window + /// over it never restarts the app and a click on it raises no window + /// behind it. fn handle_stopped_event(&mut self, cx: &mut Cx, event: &Event) { + let rect = self.area.is_valid(cx).then(|| self.area.rect(cx)); + if pointer_start(event, rect) != PointerStart::Inside { + return; + } let abs = match event { Event::MouseDown(e) => Some(e.abs), Event::TouchUpdate(update) => update.touches.iter() @@ -211,10 +219,14 @@ impl MpModuleView { .map(|point| point.abs), _ => None, }; - let (Some(abs), Some(client)) = (abs, self.client) else { return }; + let (Some(abs), Some(claim), Some(client)) = (abs, press_claim(event), self.client) else { return }; + if !claim.get().is_empty() { + return; + } + claim.set(self.area); if self.restart_rect.is_some_and(|r| r.contains(abs)) { cx.widget_action(self.uid, MpRunViewAction::Restart { client }); - } else if self.area.is_valid(cx) && self.area.rect(cx).contains(abs) { + } else { cx.widget_action(self.uid, MpRunViewAction::Clicked { client }); } } @@ -231,6 +243,12 @@ impl MpModuleView { pub fn root(&self) -> Option { self.root.clone() } + + /// Where the closed face drew its Restart (module_input_tests.rs). + #[cfg(test)] + pub(crate) fn restart_button(&self) -> Option { + self.restart_rect + } } impl TileHost for MpModuleView { @@ -321,17 +339,18 @@ fn pointer_start(event: &Event, rect: Option) -> PointerStart { } } -/// The claim cell of a press or new touch no tile in front has taken yet -/// (a wheel step has none: it raises nothing). -fn unclaimed_press(event: &Event) -> Option<&std::cell::Cell> { - let handled = match event { - Event::MouseDown(e) => &e.handled, - Event::TouchUpdate(update) => &update.touches.iter() - .find(|point| point.state == makepad_platform::event::TouchState::Start)? - .handled, - _ => return None, - }; - handled.get().is_empty().then_some(handled) +/// The cell that records who claimed a press or a new touch (a wheel step +/// has none: it raises nothing). Tiles see input topmost first (desk.rs), +/// after the shell's own surfaces over the desk, so a press that reaches a +/// tile already claimed was taken by something in front of it. +fn press_claim(event: &Event) -> Option<&std::cell::Cell> { + match event { + Event::MouseDown(e) => Some(&e.handled), + Event::TouchUpdate(update) => update.touches.iter() + .find(|point| point.state == makepad_platform::event::TouchState::Start) + .map(|point| &point.handled), + _ => None, + } } impl Widget for MpModuleView { @@ -396,11 +415,21 @@ impl Widget for MpModuleView { if start == PointerStart::Outside { return; } - // A press inside, with the cell that records who claimed it. Tiles - // see input topmost first (desk.rs), so a claimed press belongs to a - // tile in front: overlapping windows of one app (its extra windows) - // must not raise the one behind. - let press = if start == PointerStart::Inside { unclaimed_press(event) } else { None }; + // A press inside, with the cell that records who claimed it. One that + // is claimed already was taken in front of this tile (`press_claim`): + // by a window over it, this app's own extra windows included, or by + // a shell surface. Like a press outside, it is not this instance's: + // it raises nothing, and the root never sees it. The claim alone + // would not keep it from the app's widgets. Makepad lets a widget + // co-capture a press another area has claimed + // (`hits_with_capture_overload`: GestureView's taps, a list's drag, a + // View's `on_item_tap`), and GestureView follows new touches from + // the raw stream, so a click on Mail's inbox footer opened the News + // story under it. + let press = if start == PointerStart::Inside { press_claim(event) } else { None }; + if press.is_some_and(|claim| !claim.get().is_empty()) { + return; + } if press.is_some() { if let Some(client) = self.client { // The WM moves focus here (and back to us through @@ -432,7 +461,9 @@ impl Widget for MpModuleView { } } // A press inside this tile is this tile's, even where none of the - // app's widgets took it, so no window behind reacts to it. + // app's widgets took it. Claimed, it reaches no window behind: a + // module tile lets it by (above), a process tile's `event.hits` + // misses it. if let Some(handled) = press { if handled.get().is_empty() { handled.set(self.area); @@ -554,13 +585,13 @@ mod tests { assert_eq!(pointer_start(&Event::Startup, tile()), PointerStart::None); } - /// A new touch is a press this tile may claim; the rest of its stroke - /// and a frame are not. (A press a window in front already claimed has - /// a non-empty cell and raises nothing behind it.) + /// A new touch is a press with a claim; the rest of its stroke and a + /// frame are not. (A press a window in front already claimed has a + /// non-empty cell: the tile behind lets it by, module_input_tests.rs.) #[test] fn only_a_new_touch_or_button_is_a_press_to_claim() { - assert!(unclaimed_press(&touch(dvec2(200.0, 400.0), TouchState::Start)).is_some()); - assert!(unclaimed_press(&touch(dvec2(200.0, 400.0), TouchState::Move)).is_none(), "only a start is a press"); - assert!(unclaimed_press(&Event::Startup).is_none()); + assert!(press_claim(&touch(dvec2(200.0, 400.0), TouchState::Start)).is_some()); + assert!(press_claim(&touch(dvec2(200.0, 400.0), TouchState::Move)).is_none(), "only a start is a press"); + assert!(press_claim(&Event::Startup).is_none()); } }