mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-15 04:46:42 +00:00
refactor(ui): tell each page whether the press is its own
Each module asked the router `owner(page).is_none()` to learn whether a press was the active tool's. That reads a press held on another page, or begun between the pages, as the tool's too. The router now remembers the page a press began on (`begin` on every new press, then `press` on the page it lands on), and `press_for(page)` answers what a page has to do with it: - `Mine(owner)`: the press began on this page and the owner's hit won it; - `Free`: no button is down, or the press began on this page and no hit won it, so it is the active tool's; - `Elsewhere`: the press began on another page, or between the pages. The page view asks once per page, and the stamps and the Link tool use the answer. The other modules move to it in the next commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bab615fed1
commit
1780f33350
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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] = [
|
||||
|
||||
@@ -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<Item = Hit>, 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<Owner> {
|
||||
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<Capture>,
|
||||
/// A press is held, and the page it began on (`None`: between the pages).
|
||||
began: Option<Option<usize>>,
|
||||
}
|
||||
|
||||
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<Item = Hit>, 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();
|
||||
|
||||
Reference in New Issue
Block a user