Commit Graph
1165 Commits
Author SHA1 Message Date
KenM76andClaude Opus 5.5 6cb3dbe8cc Merge feat/outline-io and origin/main into feat/bookmark-clipboard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 07:11:34 -05:00
FredApps 3683150ee0 Merge pull request #5803 from alehel/refactor/page-press-dispatch
refactor(ui): tell page modules whether a press is theirs (router PR 4a)
2026-11-04 11:56:35 +01:00
KenM76andClaude Opus 5.5 bfa6db7bd3 M4: a cut too deep to copy leaves the bookmarks in place (#3994)
A cut copies the subtree, then deletes it. When the copy is truncated
(bookmarks nested past the listing depth), paste cannot bring back what was
left out, so the cut now keeps the copy, deletes nothing and says why.

Test: cutting_a_subtree_too_deep_to_copy_keeps_it (40-level synthetic
outline); fails when the truncated copy is allowed to delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 05:53:41 -05:00
KenM76andClaude Opus 5.5 c16c6b5716 M4: cut, copy and paste bookmark subtrees (#3994)
The Bookmarks panel's item menu gets Cut, Copy and Paste, and Bookmark
options gets Paste bookmarks (to the end of the outline, which is how you
paste into a document without one). The clipboard lives in the app
(`PdfCraftApp::bookmark_clipboard`), like the Organize Pages clipboard, so
it works across tabs and on the web build.

- Copy reads the subtree with `outline_tree`; a copy cut at the listing
  depth says so.
- Cut copies first and deletes only if the copy succeeded: one undo step.
- Paste is one `ImportBookmarks` edit after the bookmark it was asked on,
  at its level. A bookmark whose page the document lacks keeps its place
  without a destination, and a notice counts them (plural forms through
  `trn`).

Parity: new `organize.bookmark-clipboard` entry with what is missing
(shortcuts, system clipboard, multi-select, page-ordered insertion).

Test: cut_copy_and_paste_a_bookmark_subtree_within_and_between_documents.
Reverting copy, cut, paste, the paste's parent, the one-step undo or the
notice each fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 05:04:49 -05:00
FredApps 69fda9959e Merge storytold/main into fix/scroll-regression-1230 2026-11-04 08:56:42 +01:00
FredApps 933a92fad0 Merge storytold/main into fix/continuous-scroll-prefetch 2026-11-04 07:42:58 +01:00
9ce6d89f46 i18n: interface translations from craft-i18n (#6055)
* build(i18n): embed the craft-i18n catalogs from a pinned checkout

- `craft-i18n.rev` pins storytold/craft-i18n at e45ddf7 (the PdfCraft cutover sync of
  pdfcraft@ecdd1e6); `.gitattributes` keeps it LF.
- The `craft-i18n` crate (craft-libs 787a7a0, PdfCraft's existing pin) is a direct dependency
  of the UI crate; it was already in the tree through craft-ui.
- `crates/ui-egui/build.rs` writes `CRAFT_I18N` from `CRAFT_I18N_DIR`: one (locale, common.tsv,
  pdfcraft.tsv) entry per locale with a `pdfcraft.tsv`, paths written with `{:?}` and never
  canonicalized. Unset it is empty (English only); `CRAFT_I18N_REQUIRED=1` makes a missing or
  invalid checkout an error.
- Both flatpak manifests name the craft-i18n commit and `CRAFT_I18N_DIR`; a test keeps both
  commits equal to the trimmed pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(i18n): look strings up through craft-i18n behind a thin Lang wrapper

`i18n.rs` keeps PdfCraft's API (tr, tr_ctx, tr_id, trn, key_name, fmt,
Lang, LANGUAGES, detection) and hands lookups to the craft-i18n crate:
`get` for key names, `trn_plain` for plurals (no isolates, no digit
grouping), PdfCraft's own `fmt` (no FSI isolates). `@id` rows and the
"Undo {action}" template are read only where PdfCraft had them, so menus
and history labels read exactly as before.

Detection is unchanged (LC_ALL/LC_MESSAGES/LANG, macOS defaults,
sys_locale); the crate's match_tag would drop nb for no/nn, so PdfCraft's
own tag mapping stays, with one parity test per path.

Completeness tests become reports; tests that need catalogs skip with a
note when the build has none. Font coverage tests read the embedded
catalogs (shared and PdfCraft rows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(i18n): remove the in-repo catalogs; craft-i18n holds them

The 34 `crates/ui-egui/src/i18n/*.tsv` catalogs were migrated into
craft-i18n at pdfcraft@ecdd1e6 and are now embedded from the pinned
checkout. ATTRIBUTION.toml drops their 34 entries for one craft-i18n
`[[build_input]]` (craft-i18n's ATTRIBUTION.md credits the translators);
AGENTS.md and docs/localization.md describe the new system and how to
add a language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(i18n): skip catalog tests without craft-i18n; drop the catalog hash exemption

Tests that switch to a non-English language skip with a note when the
build embeds no catalogs (CRAFT_I18N_DIR unset), so the English-only
build tests green. `skip_without_catalogs` moves to the integration
test support module.

With no catalogs left in the repository, `cargo xtask assets` no longer
exempts `crates/ui-egui/src/i18n/*.tsv` from hashing: every `[[asset]]`
carries a sha256 again, and a `.tsv` added back would need one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build(i18n): watch the locales directory before reading it

A CRAFT_I18N_DIR that doesn't exist yet now reruns the build script once it appears (review).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build(i18n): pin craft-i18n dfbe955, the merge of storytold/craft-i18n#129

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(i18n): skip an @id row that changes the label's placeholders or ellipsis

craft-i18n checks placeholders and the trailing ellipsis on every row whose
source is English text, but not on @id rows, whose source is the command id.
The in-repo catalog test used to fail on such a row; since the cutover it only
prints it, while tr_id and menu_label still showed it. Both now use PdfCraft's
own @id row only when it keeps the label's shape (same_shape, the rule
craft-i18n applies elsewhere), and otherwise show the label's translation, as
bundled_catalogs_are_consistent says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(i18n): reject an @id row with an open brace; list unused @id rows in the gaps report

same_shape skipped a `{` with no `}`, so an @id row such as "Öffnen {name…"
for "Open…" was still shown. It now counts as a changed shape.

bundled_catalogs_are_consistent printed the @id rows PdfCraft doesn't use, but
CI captures test output, so nobody saw them. translation_gaps_report, which
`cargo xtask i18n-report` runs with --nocapture, now lists them
(unused_id_rows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(i18n): reject an @id row with a `}` that closes no placeholder

same_shape counted only the `}` that close a `{`, so "Öffnen name}…" for
"Open…" or "{name}} öffnen" for "Open {name}" still passed. A row whose `}`
count differs from its placeholders now counts as a changed shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(i18n): no braces in the assert messages of the brace test

assert! reads its message as a format string, so "a `}` without its `{`"
did not compile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-04 00:47:17 +01:00
dotCooCoo a262171999 fix(ui): continuous view renders the pages ahead of the scroll
In continuous view the only demand off screen was the pages near the
window. A fast wheel spin crosses them faster than a heavy page renders,
so pages came into view blank.

The view now keeps the direction it last moved and also asks for the
next two rows that way (four pages in two-up). A zoom or a layout change
clears the direction. Scrolling the other way drops those pages from the
queue, which cancels their renders. Pages on screen stay at the head of
the queue in their usual order, and the byte and count limits bound the
prefetch. The web build renders on the UI thread and does not prefetch.

A raster that arrives over the allowance now evicts the cached pages the
frame does not ask for first, then those behind the scroll, so it no
longer evicts a prefetched page that the next frame asks for again.

A prefetched page that is tiled at the current zoom keeps a cached
whole-page raster sharper than its backdrop, as a tiled page on screen
does. This also applies to the prefetch of single-page and two-page view.
2026-11-03 13:53:52 -08:00
dotCooCoo 292f9c75ff Scroll: smooth wheel bursts and preserve precise movement 2026-11-03 12:12:11 -08:00
AdrianTM 0d3f57e120 M5: keep Fill & Sign editing on Enter and preserve line breaks (#5850) (#5954)
* M5: keep Fill & Sign editing on Enter and preserve line breaks (#5850)

* M5: preserve typewriter whitespace through readers and edits (#5850)

* M5: retain direct-subtype comment listing for Fill & Sign fix (#5850)
2026-11-03 10:24:45 -05:00
alehelandClaude Opus 5.5 e69d1221ff fix(ui): a right-click between the pages deselects the comment; Paste keeps to the picture's page
Two of CodeRabbit's findings on #5803. A right-click off the pages
cleared only the menu's anchor and kept the selected comment, so the
menu that opened was that comment's, not the current page's. A
right-click on a page's empty part deselects it, so one that no page
takes (`PageFrame.right_clicked`) now does the same. A selected Prepare
field keeps its menu off the pages, as it does on a page's empty part;
the comment there says so.

Where pages touch, the first page takes a right-click on the row they
share and anchors the menu there, but a picture on the second page under
it still opens its menu. Paste from that menu centred the image on page
1's point, which on page 2 is at its foot. The image menu now uses the
anchor only on the picture's own page, and otherwise pastes as Ctrl+V
does.

Tests, each seen failing with its fix reverted:
`a_right_click_between_the_pages_opens_the_page_menu_not_the_selected_comments`,
`paste_from_a_picture_menu_where_two_pages_touch_keeps_off_the_first_pages_point`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 14:41:30 +01:00
alehel d4c8b6639a Merge remote-tracking branch 'origin/main' into refactor/page-press-dispatch
# Conflicts:
#	crates/ui-egui/src/tools/edit_text_ui.rs
2026-11-03 14:10:49 +01:00
ea353f7a59 fix(ui): do not open right-click context menu on image in crop mode (#5835) (#5900)
* fix(ui): do not open right-click context menu on image in crop mode (#5835)

* M7: preserve active crop context routing (#5835)

Prioritize active crops over overlapping images and share measured action-pill geometry with painting. Cover overlapping images, handle margins, page edges, and all view rotations with regression tests.

* fix(ui): test selected cropped image by quad in right-click loop (#5835)

* fix(ui): trim crop right-click routing to the crop box and handles (#5835)

The Done/Cancel/Remove Crop pills are widgets and already take their own
right-clicks, and the canvas context-menu guard already closes the image
menu, so drop the pill hit-test, the crop_pills layout extraction and the
extra secondary-click block. Move the session note to the Progress log.

---------

Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-03 14:00:16 +01:00
Harsh VermaandFredApps 01ba3ac9d1 Feat/unify hotbar quick tools (Fixes #5890) (#5909)
* feat(ui): unify quick bar and hotbar and refresh settings dialog (#5890)

* style(ui): compact hotbar tools customization menu

* fix(ui): keep hotbar settings visible when docked to sides and address review findings

- Add placed bool to Hotbar to distinguish explicitly placed [0.0, 0.0] from default floating margin offset
- Check !a.locked before applying quick bar color edits to annotations
- In non-full-side docked vertical layout, scroll tools within available height so settings button remains visible in compact windows
- Remove max_width(cell) on vertical full-side scroll area to avoid clipping wider controls
- Restore outer vertical scrolling with max_height on Hotbar tools picker window
- Add test verifying docked hotbar in compact windows keeps settings button visible, and assert controls do not overlap

* feat(ui): close hotbar settings menu when clicking outside or pressing Escape

- Detect pointer clicks outside the settings menu window bounds and dismiss the menu automatically
- Keep settings button click behavior to allow opening and keeping the menu open
- Support Escape key to dismiss the settings menu
- Add integration test verifying clicking outside on canvas or on another tool dismisses the menu

* style(ui): restore hotbar settings dialog design to match concept mockup

* fix(ui): cap hotbar tools picker width to viewport on narrow screens

* fix(ui): anchor hotbar tools picker to rendered bar edges to avoid overlap

* fix(ui): keep upgraded hotbars in the margin and label the settings controls (#5890)

Preferences written before this change have no `placed` key, so every
existing user's untouched floating, horizontal, top-centre hotbar would
have jumped out of the margin. Such a bar now takes the new default; any
spot the user chose, including the top-left corner, is kept.

The side picker reuses the Preferences segmented pill control, so its
options are themed, reachable as radio buttons by label and visible to the
translation scan. The layout switch now carries its label and enabled state.

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-03 13:06:31 +01:00
5cb65ff0ee feat(ui-egui): Preferences can invert the middle-button drag (#5790) (#5817)
Add a saved preference, invert_middle_drag (off by default), that
reverses the direction of the middle-button pan and of the latched
auto-scroll. It changes only the direction, not which of the two a
platform uses, so it composes with the Windows auto-scroll work.

Reachable as Preferences > Invert middle-button drag and headlessly
through ui.set invert_middle_drag. Parity "Automatic scrolling" notes
updated; status unchanged.

Tests: a unit test of both paths with the switch on and off (dead zone
kept), a settings round-trip test, and an end-to-end control-channel
drag. Each fails with its fix reverted.


Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9

Co-authored-by: KenM76 <KenM76>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:33:18 -05:00
Dylan M. Taylor 661ded72bb Merge pull request #5748 from Combiler01/fix/single-page-zoom-scroll-lock-5713
fix(ui): prevent wheel lockup at edge in non-continuous view (#5713)
2026-11-02 22:59:04 -05:00
alehelandClaude Opus 5.5 bdd2ff0ee6 fix(ui): a camera click is located where it came up
Found in review (CodeRabbit): the last fix located a camera click by the pointer, which can have
moved on by the end of the release's frame. A click pressed on page 2 and released on page 1,
with the pointer back on page 2 by then, moved the quadrilateral to page 2. `PagePointer` keeps
each release's position, so `PagePointer::released` gives where the click came up, and the click
is located there.

Test: `a_camera_click_released_off_its_page_moves_nothing`, seen failing without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:04:19 +01:00
alehelandClaude Opus 5.5 b77903012d fix(ui): a camera click on another page moves the quadrilateral there
Found in review (CodeRabbit): with the camera tool, a press nobody holds on a page without the
quadrilateral moves it there, but a click was located by its press's origin, which egui has
cleared by the release frame. So only a drag moved it, and a click did nothing. This was so on
`main` too. A click is now located where it came up; `PagePress::Free` already says its press
began on this page.

Test: `a_camera_click_on_another_page_moves_the_quadrilateral_there`, seen failing without the
fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:51:52 +01:00
alehelandClaude Opus 5.5 568ea94f48 fix(ui): a right-click between the pages anchors no page's menu; the guard finds router readers
Found in review:
- **A right-click off the pages.** Between the pages or beside one, no page took the
  right-click, but the page menu still opened over the page view, on the last anchor: Delete
  page, Paste and Copy Image acted on a page right-clicked long before. This was so on `main`
  too. The anchor is now cleared on every right-click before the pages set it, so off the pages
  the menu acts on the current page.
- **The guard** took a file as a page module only by marks a router read doesn't need. A file
  that reads `.router.` is one too, so its router reads are checked.
- **Docs.** egui ends a press's drag when any button is released, not when a second goes down.
  `link_ui::hit` takes the tip from the link it found rather than looking it up again.

Tests: a right-click between the pages clears the anchor, seen failing without the fix. A
planted router read in an unlisted file fails the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:56 +01:00
alehelandClaude Opus 5.5 6752b36807 fix(ui): a mark being moved keeps Fill & Sign's presses; the guard sees calls by path
Found in review:
- **Fill & Sign during a mark's move.** `main` placed no Fill & Sign mark while a comment's move,
  resize or reshape was live on the page, whoever held the press. That guard was lost: with a
  move left over (its release never seen), the next click placed a new mark. A live gesture on
  the page now counts as over a mark again (`comments::moving_on`, which the comments' own
  dispatch uses too).
- **The guard** missed the router's and the page view's hits called by path
  (`PageRouter::owner(&view.router, i)`), and its doc said the `.at(` it matches was confined to
  `page_input.rs`.
- `measure_ui::Clicks` derived traits nothing uses.

Tests: a Fill & Sign click while a mark's move is live places nothing, seen failing without the
fix. A planted router read by path fails the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:51 +01:00
alehelandClaude Opus 5.5 6756ab0ed4 fix(ui): a right-click where two pages touch is one page's; the guard reads split calls
Found in review:
- **A right-click where two pages touch.** With the page gaps hidden, a right-click on the row
  two pages share ran on both: the first page selected its comment, the second unselected it,
  and the second page's anchor for the menu stood. A right-click is now the first page's, as a
  press has been since the last fix. This was so on `main` too.
- **The guard** matched one line at a time, so a call rustfmt splits across lines
  (`view` / `.router` / `.owner(i)`) got past it. A continued line is now read with the one it
  continues. It also refuses a hit test imported under a name that isn't a hit test's
  (`use …::hit as under`), which the checks by name would miss.
- **Docs.** Links still take a right-click while the primary button is held, as before, and when
  Escape comes in a press's own frame every page is `Elsewhere`, not `Free`.

Tests: a stamp clicked where two pages touch lands on the first, a click pressed on one page and
released on the next places nothing, a right-click where two pages touch is the first page's,
Prepare keeps its selection on a click that began off the page, and the Note tool starts no note
on added text with the Edit panel open. Each was seen failing with its fix, or the rule it pins,
reverted. A planted split router read and a planted aliased hit test fail the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:41 +01:00
alehelandClaude Opus 5.5 c1d34fc9ea fix(ui): commenting tools leave a frame another tool took; Escape keeps the menu's anchor true
Found in review:
- **A double-click another tool took finished a polygon.** Round 5 kept commenting tools off a
  press another tool had taken, but a polygon, polyline or cloud finishes on a double-click or
  Enter whoever has the press. With Prepare open, a double-click on a field opened its properties
  and committed the polygon being clicked out. Added text opened for editing did the same with
  the Edit panel open. The commenting tools now skip such a frame entirely, as `main`'s consumed
  chain did. The comments' own presses and a click's unselecting still run.
- **The menu's anchor after Escape.** While a press Escape cancelled is held, the page's tools
  are skipped, and a right-click left the page menu on the page right-clicked before. It now
  anchors where it is, as every other right-click does.
- **The guard.** It now lists who asks the page view's hits at a point (`PageHits::at` and
  `context_at`), so a new place in `page_input.rs` can't test a point again. It takes a file
  that uses the comments' page context (`PageCx`) for a page module. Its doc of the point tests
  it doesn't list is now true.
- **Docs and visibility.** True docs for `PagePress::Free`, where a press is off the pages, and
  the camera's hit. `link_ui::screen_rect` is private.

Tests: a polygon left unfinished by a double-click on a Prepare field, and a right-click after
Escape anchoring the menu. Each was seen failing without its fix. A planted ask of the page
view's hits, and a planted unlisted file using `PageCx`, each fail the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:35 +01:00
alehelandClaude Opus 5.5 f0caaf9259 fix(ui): a press Prepare or added content took draws no comment; menus open where right-clicked
Found in review:
- **Comments acted on presses another tool had taken.** Comments run every frame now, and a
  commenting tool acted on any press nobody held, so with Prepare open a Rectangle drag drew a
  rectangle as well as Prepare's selection rectangle. Added content, with the Edit panel open,
  could likewise open a note composer under the Note tool. `main` skipped comments when another
  module took the frame. They are now told (`taken`), and their drawing and placing tools leave
  such a press alone. A click there still unselects.
- **The page menu's anchor went stale.** A right-click while the primary button was held skipped
  the call that also anchors the page menu, so Delete page, Paste and Copy Image acted on the page
  right-clicked before. Every right-click on a page now anchors the menu there, whoever takes it,
  and only the selections wait for the release. That includes the right-clicks Add text, Edit
  text, the Link tool and a field tool take, which used to keep the previous anchor.
- **Edit text's right-clicks.** Edit text took right-clicks on added content too. It now takes
  those on its paragraphs, pictures and the objects selected together, and comments see a
  right-click on added text, as before.
- **Camera corners.** A camera corner at the page's edge showed its grab cursor past the edge,
  where a press is no page's. Its hit now stops at the page, as a link's does.
- One source for a comment's layer, dead code in Add text's right-click, and true docs for
  `PagePress::Elsewhere` and the "Page presses" rule.

Tests: a shape-tool drag with Prepare open, a right-click with the button held on another page,
a right-click on added text with Edit text, the menu anchored by an Edit text right-click, and a
camera corner at the page's edge. Each was seen failing with its fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:32 +01:00
alehelandClaude Opus 5.5 aa415811d7 fix(ui): right-clicks leave a held press's selections alone; guard finds more page modules
Found in review:
- **Right-clicks during a press.** A comment being moved kept its selection only while its
  gesture state was live, so a gesture left over (a release the pages never saw) blocked every
  later right-click. Comments and Prepare now leave their selection alone while the primary
  button is held. egui ends the press's own interaction (and its drag) when another button goes
  down, so the button is what tells. A touch's long press, which egui reports as a right-click
  with the finger still down, still opens its menu.
- **A click that began beside the page** unselected the selected comment, though the Link tool and
  Prepare now leave theirs alone for such a click. Comments do the same.
- **The guard** took a file as a page module only if it named `PagePointer`; a file using
  `PageToolContext`, `PagePress` or the router's hits or owners is one too. Its message about
  tooltips and release checks now points at the page view's own hits, and its doc of the router's
  readers names link following.

Tests: a right-click with a gesture left over, a click that began beside the page, and a long
press on a comment. Each was seen failing with its fix
reverted. A planted page-router name in a module the guard didn't list fails the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:29 +01:00
alehelandClaude Opus 5.5 47ece03a54 fix(ui): the Link tool's click told too; a moved comment keeps its selection
Found in review:
- **The Link tool's click** acted on any press: with Prepare open, a click on a field also
  unselected the selected link, and a click that began off the page did too. It now acts only on
  a press on a link, or one nobody holds.
- **A right-click while a comment is moved** selected the comment under it mid-drag, though the
  release still moved the first. Comments now keep their selection during a move, resize or
  reshape, as Prepare does.
- The hits under the pointer before the tools act are taken only on the pointer's page, and the
  Link tool's right-click reuses them.
- The router has one source for the press's page (`Capture` no longer carries its own).
- The guard lists which functions may ask which comment lies under a point (`comment_at`); the
  guard finds hit tests by name, and its docs say so.
- Docs made true: what `PagePress::Mine` promises, what `tool_input` gates, and that a dialog does
  not cancel the pages' press (only Escape and automation's cancel do).

Tests: the two fixes, and ruling 1 for added text (a click on it unselects the comment), each seen
failing with its fix reverted; the drawing-tool right-click test now checks the comment's menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:25 +01:00
alehelandClaude Opus 5.5 ae62b66304 fix(ui): right-clicks with the Link tool and the camera as before; true docs
Routing right-clicks changed a few more cases beyond the rulings, found in review:
- **The Link tool.** A right-click on a link also unselected the selected comment and moved the
  page menu's anchor; the Link tool used to take it whole. On a document it can't change, its
  right-click on a note over a link opened the plain page menu instead of the note's. Now the Link
  tool's right-click is what its press there would take, on a document it can change, and on a
  link it leaves comments alone.
- **The camera.** A corner, on the handle layer, took right-clicks from a comment under it,
  though it has no menu. Corners are no longer right-click targets.
- **The Link tool's hand** showed in the 2 px margin past the page's edge, where a press is no
  page's. `link_ui::hit` now finds nothing off the page.

Docs made true: what a comment's press is (`comments::page_input`), what reaches the free-press
gate (`page_tool_input`), and when every page is `PagePress::Free`.

Tests: those three, and two that pin right-clicks kept as before: Edit text's on a paragraph
under a comment, and a Prepare field tool's. Each seen failing with its fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:22 +01:00
alehelandClaude Opus 5.5 ce90c81ffb fix(ui): a drag seen past its page's edge still draws on that page
egui reports a drag once the pointer has moved a few points from the press. A press near a page's
bottom edge, flicked onto the next page in one frame, is seen as a drag only with the pointer on
the next page. Redact (and the area Highlight), the Link tool and Prepare returned before their
drag start unless the pointer was on their own page, and the next page is `Elsewhere`, so the
press drew nothing anywhere. (On main the next page took it, with the box's start on the page
before.)

They now start the gesture from where the press began on their own page, as zoom, snapshot, crop
and Sign area already did, so the box, link rectangle, marquee, field drawn or field moved belongs
to the page the press began on. Tests: one per tool, each seen failing with the fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:16 +01:00
alehelandClaude Opus 5.5 b12d091496 fix(ui): hover painting beside the open palette, and one hit test under the pointer
The page's hover (the top hit under the pointer) was only worked out when nothing was drawn over
the pages, so with the palette open a field beside it lost its tooltip and its hover outline,
and comments their outline. The palette keeps the page's cursor and tips away, which is what
that gate is for; hover now ignores it, as the outlines and the field tooltip did before.

The hits under the pointer were also asked up to four times a page each frame, each pass running
every module's hit test: for a click's release, for Fill & Sign's preview, for a right-click, and
for the cursor. A click's release, Fill & Sign's preview and a right-click's tool checks now share
one pass, taken before the tools act and only on frames that need it. A right-click's own pass
(comments by `context_hit`) stays, and the cursor and hover keep theirs, after the tools act.

A unit test pins that cancelling the pages' gestures lets go of a camera corner. The kittest for
Escape mid corner drag passes on `main` too, because Escape leaves the camera tool first.

Tests: `a_fields_tooltip_shows_beside_the_open_palette`,
`cancelling_pointer_gestures_lets_go_of_a_camera_corner`; each seen failing with its fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:13 +01:00
alehelandClaude Opus 5.5 b1a0657c42 fix(ui): right-clicks with Prepare, the Link tool and Add text as before
Routing right-clicks by what lies on top under the pointer changed three cases beyond the
rulings:
- **Prepare.** A right-click while a field was dragged selected the field under the pointer, and
  the release then moved that one. Prepare used to ignore right-clicks during a drag; it does
  again (`prepare::context` returns while a grab is held).
- **The Link tool.** A note over a link took the right-click, though the Link tool's press there
  is the link's. The Link tool's right-click on a link is the link's again, whatever lies over it.
- **Add text.** Comments took right-clicks away from paragraphs and added items. Add text used to
  take every right-click on a page it can change; it does again.

Tests: those three, and two that pin ruling 6's right-click half with the Select tool: no link
menu on a link with no action, and none on a form field over a link. Each was seen failing with
its fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:10 +01:00
alehelandClaude Opus 5.5 ed8a10e83e refactor(ui): tell forms and Prepare whether the press is theirs
Forms and Prepare read the router themselves, and forms tested the pointer twice more: for a
field's tooltip, and for whether a click came up over the widget it was pressed on. Following a
link tested the pointer again too.

Both are now told the page's `PagePress`. Prepare's Select tool draws a selection rectangle, or
unselects, only on a press nobody holds, and a field tool draws only on such a press. Whether a
click came up over what its press began on is answered once, from the hits under the pointer
(`released_over`), for fields and for following links. The field tooltip (`forms_ui::tooltip`)
and the field hover outline come from the page's hover, the top hit under the pointer.

`PageHits::hits` is now the only function that calls a hit test, and the press-handler check no
longer lists forms or Prepare.

Behaviour changes: a field's tooltip shows only where a press would take the field, not under
a comment drawn over it (the user's ruling, 2026-10-31); a field's hover outline follows the
same rule, so it shows only with the Select tool, which fills fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:04 +01:00
alehelandClaude Opus 5.5 951ef7e362 refactor(ui): one gate for the presses nobody holds
The tools that act wherever on the page each decided for themselves whether a press was
theirs: Sign area, zoom and snapshot, crop, redact and the stamps asked the router, and Measure,
Fill & Sign, the camera and the Link tool's rectangle acted on any press. Stamp placement lived
inline in the page view.

`tool_input` is now the one gate. Measure, the Stamp tool and custom stamps, Fill & Sign, Sign
area, zoom and snapshot, the camera and crop are each told whether the press is the tool's (it
began on their page and no hit won it), and act only then; their cursors and gestures run as
before. Redact's boxes and the Link tool's rectangle are told the same where they are called.
Stamp placement moves to `stamps_ui::place` and `place_custom`, beside custom stamps. Measure
is told its clicks (`measure_ui::Clicks`), Sign area takes a `PageToolContext`.

The camera's corners are hits (`camera_ui::hit`, `Owner::CameraCorner` on the handle layer), so
a corner's drag is an owned press like any other: it starts on the corner's own press, ends on
`drag_stopped() || !primary_down`, and `cancel_pointer_gestures` drops it. Until now only the
order of two key handlers kept Escape mid-drag from leaving a corner following the pointer:
Escape leaves the camera tool before the pages see it. The kittest for Escape mid-drag can't
fail with that order; a unit test of the cancel comes later in this series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:38:56 +01:00
alehelandClaude Opus 5.5 84fe5e54b3 refactor(ui): tell comments and Fill & Sign whether the press is theirs
Comments ran last in the page view's chain of `||`, so only when no earlier module had taken the
frame, and asked the router themselves (`grabs`, `fill_grabs`, `draw_grabs`), with a hit test of
their own for hover. A link, a field or added text taking the frame first hid a comment's
gesture end, its Escape and the click that unselects it.

Comments now run on every frame and are told the page's `PagePress`:
- a press on a comment (its body, a handle or a point) goes to `select_input` with the part,
  with the Select tool, a shape tool (the selected shape's handles) or Fill & Sign (its marks);
- the drawing tools act only on a press nobody holds (`Gestures::free`);
- a click on anything but a comment leaves comments unselected.

`grabs`, `fill_grabs`, `draw_grabs` and `cancelled_drag` (with the raw button read in `keys` it
needed) are gone; `grabs_points` is `reaches_points`. Fill & Sign leaves the press to a mark
when the press is someone's, or, with no button down, when a mark is the top hit under the
pointer. The page's hover is that top hit (`PageInput.hover`), and the comments' hover outline
follows it, so it no longer shows under a field or added text that would take a press.

Behaviour change (the user's ruling, 2026-10-31): a click on a form field, or on added text,
leaves the selected comment unselected, as a click on a link or the page already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:38:52 +01:00
alehelandClaude Opus 5.5 bab615fed1 refactor(ui): route right-clicks by what lies on top under the pointer
Each right-click handler tested the pointer itself, and the chain's order decided which ran:
comments only when no earlier module took the frame, Prepare by its own widget scan.

A right-click now goes to what lies on top under the pointer (`PageHits::context_at`), by the
layer ranking a press uses, with two differences. Comments count with every tool, as a
comment's menu opens whatever tool is active (`comments::context_hit`). A highlight over a link
is the highlight's: a left click there follows the link, a right-click opens the highlight's
menu. On every right-click the page's menu is anchored at the pointer, and the comment it is
on is selected, or none (`comments::context`); a Prepare field is selected (`prepare::context`);
links take theirs from the same answer. Edit text's own paragraphs and pictures, and a Prepare
field tool, keep their right-clicks as before.

Behaviour change (the user's ruling, 2026-10-31): a right-click on a highlight over a link opens
the highlight's menu, not the link's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:38:40 +01:00
alehelandClaude Opus 5.5 2b256ab0c7 refactor(ui): one link test for the Select tool, the Link tool and right-clicks
Three link tests disagreed. The Select tool's hand and click (`link_hit`) took the topmost link
the renderer lists, with no margin, by its place in that list. The Link tool's selection and
Select's right-click took the bottom one of every link the engine lists, 2 px around each, by
`/Annots` index. Comments had a fourth (`on_link`).

Now `link_ui::hit` is the one test, and `Owner::Link` carries the `/Annots` index:
- the Link tool finds every link the engine lists, 2 px around each, and takes the topmost; a
  drag from a link still draws a new one (the hit declines the drag);
- every other tool finds the links the renderer can follow, with no margin (so Select shows no
  hand over a link with no action, as before), and takes the topmost;
- following a link looks its target up by that index;
- `comments::hit` is told whether a link is under the point.

The Link tool is told the link its press is on, and the one a right-click is on, instead of
testing the pointer itself. A right-click goes to the top hit under the pointer, so with the
Select tool a right-click on a note over a link selects the note and opens its menu, and one
just beside a link no longer opens the link's.

Behaviour changes (the user's rulings, 2026-10-31): the Link tool takes the topmost of stacked
links; Select's right-click has no margin; a right-click on a note over a link is the note's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:38:37 +01:00
FredApps 8f1441a3f2 fix(ui): keep pane resize edges right of the border, off the page scrollbar (#5730)
The centred 4 pt strip still covered the right 3 pt of the page's
scrollbar beside a right-docked pane. Register the host's resize edge
again from the border rightwards, on the host's own layer, and leave an
inert edge inert. The test now hovers and drags both scrollbars beside a
right-docked Tools pane (CodeRabbit) and checks the width is unchanged.
2026-11-02 16:59:26 +01:00
FredApps 1997b8ed94 Merge remote-tracking branch 'origin/main' into pr5740 2026-11-02 16:29:14 +01:00
FredApps 1303684851 Merge pull request #5639 from RiceDee/fix/rail-page-zoom-fields
fix(ui): the rail's page box fits, and the zoom field looks like a field (#5636)
2026-11-02 14:46:11 +01:00
arnav-hiwarkarandClaude Opus 5.5 08af1dc39f fix(ui): end a notch's turn when the next notch only pans (#5713)
A wheel notch sets `turned`, and only a trackpad touch or a smooth burst
cleared it. With a wheel that macOS reports in points, a notch that
turned the page left `gesture_turned` set, so read_wheel swallowed every
later notch on that page. The same happened at the last page's bottom
edge.

A notch that cannot turn now clears `turned`. The new tests use Point
notches. The Line-unit test in b31b4035 passes without either fix,
because egui's own ScrollArea still applies line scrolling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:33:38 +05:30
Combiler01 b31b40356d fix(ui): prevent wheel lockup at edge in non-continuous view (#5713) 2026-11-02 17:54:15 +05:30
RiceDee 89efc79ecd fix(ui): the size field stays within its room in the row in every language (#5634)
It reserves room for a translated "Mixed", and in Czech or French that pushed the alignment
button up to 54 pt past the Tools pane. The field is now no wider than SIZE_FIELD, and a longer
translation scrolls in it. The language test checks the size row too.
2026-11-02 23:23:20 +11:00
Harsh Verma ec2f59305b Merge branch 'storytold:main' into fix/panel-resize-scrollbar-5730 2026-11-02 12:23:11 +00:00
RiceDee 808ba71287 Merge main into fix/rail-page-zoom-fields
Conflict with main's rail docking, resolved by keeping both: the rail follows the side chosen
for it (main) at 49 pt with 1 pt padding, and main's dock button stays above the page display
button, while zoom out and in stay side by side under the zoom field.
2026-11-02 23:11:59 +11:00
RiceDee f711b163c6 Merge main into fix/format-text-fields-fit-pane
Conflicts with main's font menu, size field and accessible names, resolved by keeping both:
the font list keeps main's searchable installed-fonts menu inside its outline; the line spacing
list and number fields keep their names. The size field now reserves room for "Mixed", so
MIN_ROW counts it (62 pt, measured for English), and the spacing columns take their width from
the font list so a longer translation of "Mixed" can't widen them.
2026-11-02 22:43:31 +11:00
Combiler01 8baae14c34 fix(ui): prevent panel resize handle collision with scrollbar (#5730) 2026-11-02 16:36:17 +05:30
Combiler01 b5e847d3b9 ui: scroll to matches in segmented pill options, toggle pills, and theme cards 2026-11-02 14:18:21 +05:30
Combiler01 80ffea6df4 ui: remove misleading cancel button from preferences dialog 2026-11-02 12:28:19 +05:30
Combiler01 0c822af3b1 ui: implement browser-style in-situ text search and highlight in preferences 2026-11-02 12:00:53 +05:30
Harsh Verma fa818ccc08 Merge branch 'storytold:main' into feat/preferences-cards-refresh 2026-11-02 06:00:41 +00:00
Combiler01 6aa88171d3 ui: register theme card focus upfront and assert TwoUp continuous preservation 2026-11-02 11:24:53 +05:30
Combiler01 ec533af1bb ui: fix two-page continuous scrolling and add keyboard focus indicators 2026-11-02 10:43:27 +05:30