diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 289edf811d..428f71c186 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -1604,9 +1604,9 @@ fn draw_pages( cancel_on_escape(ui.ctx(), view, &ptr); view.router.settle(ptr.any_down, !ptr.edges.is_empty()); // A new press is no longer the last one's, wherever it lands (the page it is on gives it - // its owner; in the gap between pages it has none). + // its owner; in the gap between pages it is no page's). if ptr.pressed().is_some() { - view.router.cancel(); + view.router.begin(); // The page widget is not focusable. Explicitly return keyboard ownership from Pages // when the user presses the document, without stealing focus through a modal. if let Some(id) = view.pages_focus_id { diff --git a/crates/ui-egui/src/canvas/page_input.rs b/crates/ui-egui/src/canvas/page_input.rs index 5d6dcb8054..00edfe54cc 100644 --- a/crates/ui-egui/src/canvas/page_input.rs +++ b/crates/ui-egui/src/canvas/page_input.rs @@ -7,7 +7,7 @@ use pdfcraft_render::{DocInfo, PageText}; use super::{DocView, PageEnv, PageFrame, PageSlot, PageXform, Selection, Span, cached_edit_images, click_unit, signature_badges}; use crate::page_cursor::{Layer, Subject}; use crate::page_pointer::PagePointer; -use crate::page_router::{Owner, top}; +use crate::page_router::{Owner, PagePress, top}; use crate::{QuickTool, comments, icons}; /// What the page's tools did with the pointer this frame. @@ -98,6 +98,8 @@ pub(super) fn page_tool_input(ui: &mut egui::Ui, view: &mut DocView, frame: &mut let at = hits.at(view, o); view.router.press(i, at, tool); } + // What this page has to do with the press: its owner's, the active tool's, or another page's. + let press = view.router.press_for(i); let context = pointer.filter(|p| resp.secondary_clicked() && xf.rect.contains(*p)).and_then(|p| right_click(&hits, view, p)); if let QuickTool::Measure(measure_tool) = tool { crate::measure_ui::page_input(ui, resp, doc, view, &pcx, measure_tool, units); @@ -111,7 +113,7 @@ pub(super) fn page_tool_input(ui: &mut egui::Ui, view: &mut DocView, frame: &mut { view.cursor.claim(Layer::ToolArea, egui::CursorIcon::Crosshair); // The tool's own click: nothing drawn over the page here holds the press. - if resp.clicked() && view.router.owner(i).is_none() { + if resp.clicked() && press == PagePress::Free { // Centred on the click, upright as the page is shown. let (vx, vy) = xf.screen_to_view(p); let (w, h) = kind.size(); @@ -136,7 +138,7 @@ pub(super) fn page_tool_input(ui: &mut egui::Ui, view: &mut DocView, frame: &mut { view.cursor.claim(Layer::ToolArea, egui::CursorIcon::Crosshair); // The tool's own click: nothing drawn over the page here holds the press. - if resp.clicked() && view.router.owner(i).is_none() { + if resp.clicked() && press == PagePress::Free { // Centred on the click at its natural size (the engine sizes it). let (vx, vy) = xf.screen_to_view(p); let u = info.pages[i].view_to_user(vx, vy); @@ -273,7 +275,7 @@ pub(super) fn page_tool_input(ui: &mut egui::Ui, view: &mut DocView, frame: &mut crate::content_ui::sync_image_selection(view, i, &images, added); } } - let (link_owner, link_context) = (link_of(view.router.owner(i)), link_of(context)); + let (link_owner, link_context) = (link_of(press.owner()), link_of(context)); let on_link = tool == QuickTool::Link && can_modify && crate::link_ui::page_input(cx, link_owner, link_context, view); if tool == QuickTool::Select && can_modify diff --git a/crates/ui-egui/src/canvas/page_pointer/tests.rs b/crates/ui-egui/src/canvas/page_pointer/tests.rs index f9fc72bbcd..2e79b7481e 100644 --- a/crates/ui-egui/src/canvas/page_pointer/tests.rs +++ b/crates/ui-egui/src/canvas/page_pointer/tests.rs @@ -384,7 +384,7 @@ fn page_modules_claim_the_cursor_and_never_set_it() { } /// What decides, keeps or drops the press the pages hold. -const ROUTER_WRITES: [&str; 3] = ["router.press(", "router.settle(", "router.cancel("]; +const ROUTER_WRITES: [&str; 4] = ["router.begin(", "router.press(", "router.settle(", "router.cancel("]; /// The functions that decide, keep or drop the press the pages hold: (file, function, why). const ROUTER_WRITERS: [(&str, &str, &str); 3] = [ diff --git a/crates/ui-egui/src/canvas/page_router.rs b/crates/ui-egui/src/canvas/page_router.rs index a9f7500189..f1aa199c67 100644 --- a/crates/ui-egui/src/canvas/page_router.rs +++ b/crates/ui-egui/src/canvas/page_router.rs @@ -6,7 +6,8 @@ //! press point decide who captures it ([`PageRouter::press`]), by the same table: the highest layer //! the active tool reaches. Until the button is up only the owner acts on the press, its drag and //! its release, so what the cursor promised is what the press does, and nothing takes a gesture -//! back halfway. +//! back halfway. Each page is told what it has to do with the press ([`PagePress`]): its owner's, +//! the active tool's, or none of its business. use egui::CursorIcon; @@ -106,6 +107,28 @@ pub(crate) fn top(hits: impl IntoIterator, tool: QuickTool) -> Optio }) } +/// What one page has to do with the press the pages hold. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum PagePress { + /// The press began on this page, and the owner's hit won it: only its module acts on it. + Mine(Owner), + /// No button is down, or the press began on this page and nobody's hit won it: the active + /// tool's. + Free, + /// The press began on another page, or between the pages. + Elsewhere, +} + +impl PagePress { + /// Who holds the press on this page, if anyone. + pub(crate) fn owner(self) -> Option { + match self { + PagePress::Mine(owner) => Some(owner), + PagePress::Free | PagePress::Elsewhere => None, + } + } +} + /// A press the pages hold, and who it belongs to. #[derive(Clone, Debug, PartialEq)] pub(crate) struct Capture { @@ -118,13 +141,33 @@ pub(crate) struct Capture { #[derive(Debug, Default)] pub(crate) struct PageRouter { capture: Option, + /// A press is held, and the page it began on (`None`: between the pages). + began: Option>, } impl PageRouter { + /// A new press on the pages: the last one's owner goes, and until a page claims it the press is + /// between the pages. + pub(crate) fn begin(&mut self) { + self.capture = None; + self.began = Some(None); + } + /// A press on `page`: `hits` at the press point decide who it belongs to. A press nobody's /// hit claims is held by nobody (the page's text, or a tool that acts anywhere on the page). pub(crate) fn press(&mut self, page: usize, hits: impl IntoIterator, tool: QuickTool) { self.capture = top(hits, tool).map(|h| Capture { page, owner: h.owner, declines_drag: h.declines_drag }); + self.began = Some(Some(page)); + } + + /// What `page` has to do with the press the pages hold. + pub(crate) fn press_for(&self, page: usize) -> PagePress { + match (self.began, &self.capture) { + (None, _) => PagePress::Free, + (Some(Some(p)), Some(c)) if p == page => PagePress::Mine(c.owner), + (Some(Some(p)), None) if p == page => PagePress::Free, + _ => PagePress::Elsewhere, + } } /// Who holds the press on `page`. @@ -142,12 +185,14 @@ impl PageRouter { pub(crate) fn settle(&mut self, any_down: bool, edges: bool) { if !any_down && !edges { self.capture = None; + self.began = None; } } /// Drops the press (Escape, a dialog, automation's cancel). pub(crate) fn cancel(&mut self) { self.capture = None; + self.began = None; } } @@ -185,6 +230,27 @@ mod tests { assert!(!r.keeps_drag()); } + /// A page is told whether the press is its owner's, the active tool's, or another page's. + #[test] + fn each_page_is_told_whether_the_press_is_its_own() { + let mut r = PageRouter::default(); + assert_eq!(r.press_for(3), PagePress::Free, "no button down: every page is the tool's"); + r.begin(); + assert_eq!(r.press_for(0), PagePress::Elsewhere, "a press between the pages is no page's"); + r.press(2, [], QuickTool::Select); + assert_eq!((r.press_for(2), r.press_for(1)), (PagePress::Free, PagePress::Elsewhere), "nobody's, on page 2"); + r.begin(); + r.press(1, [hit(Layer::Comment, 4)], QuickTool::Select); + assert_eq!((r.press_for(1), r.press_for(2)), (PagePress::Mine(hit(Layer::Comment, 4).owner), PagePress::Elsewhere)); + r.settle(false, true); + assert_eq!(r.press_for(2), PagePress::Elsewhere, "until its release has been seen"); + r.settle(false, false); + assert_eq!((r.press_for(1), r.press_for(2)), (PagePress::Free, PagePress::Free)); + r.begin(); + r.cancel(); + assert_eq!(r.press_for(0), PagePress::Free, "a cancelled press is nobody's"); + } + #[test] fn a_highlight_lets_its_drag_go_to_the_text() { let mut r = PageRouter::default();