mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
M3: rename Smooth scrolling to Animation effects; it decides every animation
The preference also governs Ctrl-wheel zooming, so "Smooth scrolling" described only part of it. As "Animation effects" (Use system setting, On, Off) it now decides every animation in the window, the way the Windows setting of the same name does: - the wheel's scrolling and zooming, and scrolling to a place (as before); - egui's own animations, the progress card's sweep, and the chrome's hover and press washes (new on main, with durations of their own), which used to follow the system alone or not at all. Saved and set on the control channel as animation_effects (system, on, off); the unreleased smooth_scrolling name is gone. The three labels are retranslated in all 20 catalogs. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d6a9901bf3
commit
486970a4d4
32 files changed
+254
-207
No files matched your search
+20
-20
@@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "657bc9a96e997acb2e896d22abd366a897e3382f27fc7c410a1ef5bdc69104d0"
|
||||
sha256 = "0c12b624e677e2ec61afa20333aa7b2e3e2303ab596b305269f4fcfb1183cdaa"
|
||||
|
||||
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
|
||||
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
|
||||
@@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "8488be5cb07151c4c310b766b8bb76672f92971e3e54219accc1f2f8bed35c83"
|
||||
sha256 = "9ce8e70f139336bae217f6b96408d93b66fb3b7a2cae2c4df3e7473ba0f40d5e"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
|
||||
@@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "0370dee8a3b4c24097747edf01ca091fb2914166f0151e1e23d105d73dfaf161"
|
||||
sha256 = "d16f126aa4082aa7d22f539d0a0256c87f486615e9088f812f76c750b64b7c49"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/cs.tsv"
|
||||
@@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "41c2138cdd04c39d6d661e6db70774e0223746130c6536e3556c8f06fa76f731"
|
||||
sha256 = "60cfcc7003f50d5466a7e376ae5e4710e05833d7f30a5b82d7a866e802c479e6"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pt-br.tsv"
|
||||
@@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "676bd5fe27d2f8a1b1aac4ca2153207fae4a0a4f279f66beb3a648973af3d02a"
|
||||
sha256 = "20f5e4d96efd67556c90d3b20cb8f224f3fcca7ccca679f79b28e8a785db4519"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/es.tsv"
|
||||
@@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "935ad1e5a12d67f52c8007022faf54d61928e70bac1b184ec623f0641af3d2eb"
|
||||
sha256 = "4073d6909780954808c27ed510f41b5f34d9037d435e8ecb9baa03b3dae34900"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/fr.tsv"
|
||||
@@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "2b88514e089f08bf302411e418bc31e3445fd7772caf6c9cbdd22d830c16886e"
|
||||
sha256 = "fb7b933a43439715ac7a702bee483f007b2dd7c34a72805acb89786bc0c26d04"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/de.tsv"
|
||||
@@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "e7b755aae3c0f45e9c936f37220af290e133bd0bb6d1e106fd349b3517cd0247"
|
||||
sha256 = "82759859786d66d3c2e10cd57911d66834dd73e93c6bdbdf173956cb6c7c00a9"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/te.tsv"
|
||||
@@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "b789db385ccede5774382beef49d2fc6a75a76082d21276855eb3a0a6c6837ed"
|
||||
sha256 = "a0157cc0dc8a129f08a22f194b5bd57215f88b4c983af6725dd56d4a375b1f1b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/it.tsv"
|
||||
@@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "05b10895c062df8a2f6e42cab801fcaaa1f0e793d7b62d9b5ea9eed8baf8e914"
|
||||
sha256 = "e3ce7cac5de5a4561e1b93072bce74a14651d7b3d49e13d2df8b125d6cb5a424"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pl.tsv"
|
||||
@@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "478e0b058359f1f213721657157a2c6f6c9759e98a1bd30f0852f06571a83e8d"
|
||||
sha256 = "15328644bb17127f0df378ab7ece03edffcda6eb42438c0613e032dd20641aa0"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ru.tsv"
|
||||
@@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "fb815a01750c0d870a953cd9d450664b223266e3d2bdd056b2ffe03d3b0d2917"
|
||||
sha256 = "f1df5b6a526dcf80e9d9917892508eb5eb229955dc5c704fd69cec3536241564"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hu.tsv"
|
||||
@@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "0a46c705ed0a00731b2834b1e5669eba9be8944d1db5f341d1efc6a6ff7efee1"
|
||||
sha256 = "b1d367feb23ccd7d2bd59691fa6a82ae7392545e7bfc00b0f00b3f430f0cf8d8"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/bg.tsv"
|
||||
@@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "ba5bffa22a11ab087db9777b7710895ec75213d2efd284f73b8e14d277d209ab"
|
||||
sha256 = "48fefa761b28fafe7a0f07184d8280aae3eed6956d4a5291895f4158011894e3"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/kk.tsv"
|
||||
@@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "2b6d916eb54c26aa439864e718eeea4cf2eab29f44d1ba5aab2ed3c92d34d1d3"
|
||||
sha256 = "0bc1061796a4bee05e817cd023d677231dba3ba0472d41d9584b2a06d4cdb34c"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ja.tsv"
|
||||
@@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "6d6a974b3ed8ebfbf5db9967bd26964aa3a02ad34f2e16be7facffbb2129cd34"
|
||||
sha256 = "e34088eef6d6fe517da21dc4a32c4ffd72decf642f12f4c6dc50b794cccfb742"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/uk.tsv"
|
||||
@@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "013f483cc43314ab8aa6aac5a2f922b6d62f05b79d386162b9e2179783a12914"
|
||||
sha256 = "a7eb1c513ec11a04d16bed1b3e165125ae5dd10fcec2b3caa8871eee21a56daf"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ar.tsv"
|
||||
@@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "5c7a877cc61db1f706d8c83fb0269e99a9d243dc8a8648fd7407c4fae4ffde99"
|
||||
sha256 = "1939aa38b6ce305c0cf1b84d247e6a49f948826163712ff789a3c339f3f7b488"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/tr.tsv"
|
||||
@@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "26309a1de18df3873662b8d7c5d63b6b1f43cfa4a3c43a2fe0d01637debe5a6a"
|
||||
sha256 = "a8f52da804453baf6546941442dfc860a0ea3bb25e274037c3dab607887a2f29"
|
||||
|
||||
[[asset]]
|
||||
path = "assets/icons/accessibility.svg"
|
||||
@@ -3339,4 +3339,4 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "eddbea7c050c06c628ec1bb64eb564013f378e798ebfe62c331b0604402f256e"
|
||||
sha256 = "1e1d5662e8674698f2c526b12cea3eb280e6168512f77db32676e79e80a4c43b"
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
**Stage: alpha** · next: beta, ~26% (ready for real work 49% → 75%) and ~400–700 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (M2 short same-line redraws split before placement; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (M3: Windows' Animation effects setting is followed, and Preferences has Smooth scrolling: system, on or off); earlier: M10, #1028: OCR reads pictures on pages that have text, on request; earlier: M14 #307 Plan 3 uses one marker traversal on open and full refresh, and the combined scanner borrows metadata instead of cloning scan-only containers; includes a behavior-preserving `organize` binding for the workspace Clippy gate · earlier: minor (localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (M3: Windows' Animation effects setting is followed, and Preferences has Animation effects: system, on or off); earlier: M10, #1028: OCR reads pictures on pages that have text, on request; earlier: M14 #307 Plan 3 uses one marker traversal on open and full refresh, and the combined scanner borrows metadata instead of cloning scan-only containers; includes a behavior-preserving `organize` binding for the workspace Clippy gate · earlier: minor (localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (#594 paragraph drags validated in the Linux test environment; localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; earlier: second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (architectural paper controls from #599; localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; earlier: second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (closed issues synced: #774, #756 and the #799–#821 batch; earlier: localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; earlier: second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
@@ -156,7 +156,7 @@ Newest first: the date, what moved, and the new overall estimate.
|
||||
|
||||
- **2026-10-11 (M5.7, scanned signatures, #1110):** Fill & Sign ▸ Add signature has a Scan tab: a phone photo of a signature on paper is framed automatically (all words; table, sheet edge and dust ignored; a sideways signature turned upright), the frame can be moved, resized or redrawn, the photo turned, and an ink-sensitivity slider picks up faint ink. A morphological paper estimate removes shadows and uneven light, and the trimmed ink is saved and reused like an image signature. Photos and images can also be dropped on the Image and Scan tabs. Photos keep the image-signature limits (#1124: a 4 MiB file, up to 32 megapixels decoded, scaled down to 4 megapixels), so a scan never processes more than 4 megapixels. `fill_sign_add` takes `scan` and `sensitivity`. Synthetic engine, UI and automation tests; checked by hand on real phone photos. Milestone and effort estimates unchanged.
|
||||
- **2026-10-12 (M10.5, hosted CI):** Integrated current main, including its date-table formatting and static-storage fixes. The Arabic synthetic drawing regression now checks the original serialized-output golden with and without the optional font face: the face-dependent FontName and FontFamily are checked separately and normalized only in the fixture. The hosted FreeBSD fingerprint is reproduced by those two metadata fields alone. Drawing bytes and all other fields retain the original golden; no printing behavior or readiness estimate changes.
|
||||
- **2026-10-11 (M3, smooth scrolling and Windows' Animation effects setting):** PdfCraft follows Windows' Settings ▸ Accessibility ▸ Visual effects ▸ Animation effects, and Preferences has Smooth scrolling: Use system setting (the default), On or Off. With Animation effects off, a wheel notch scrolls its whole distance, and Ctrl with the wheel zooms its whole step, in the frame it arrives. Both were eased over about a quarter of a second whatever the setting: egui spreads a coarse wheel step over later frames and has no switch for it, so `crates/ui-egui/src/motion.rs` reads each frame's wheel input the way egui does, minus the easing, and hands that to the scroll areas and the views (a test sums what egui eases and compares it with what is read at once, and fails if egui stops easing). egui's own animations (menus, sections, scrolling to a row) take no time, and the progress card's bar steps without its sweep. The desktop app asks WinRT's `UISettings.AnimationsEnabled` at launch and every two seconds of use, so a change applies without a restart. How far a notch scrolls or zooms is unchanged. Smooth scrolling set to On or Off decides the wheel's scrolling and zooming, and scrolling to a place, whatever the system says (menus, sections and the progress card always follow the system); it is saved, set as `smooth_scrolling` (`system`, `on`, `off`) on the control channel, and translated in all 20 catalogs. Not done: macOS (Reduce motion), Linux (GNOME's animations switch) and the browser (`prefers-reduced-motion`) aren't asked yet, so Use system setting eases there as before; Off works everywhere. Estimates unchanged.
|
||||
- **2026-10-11 (M3, animation effects):** PdfCraft follows Windows' Settings ▸ Accessibility ▸ Visual effects ▸ Animation effects, and Preferences has its own Animation effects: Use system setting (the default), On or Off. With animation effects off, a wheel notch scrolls its whole distance, and Ctrl with the wheel zooms its whole step, in the frame it arrives. Both were eased over about a quarter of a second whatever the setting: egui spreads a coarse wheel step over later frames and has no switch for it, so `crates/ui-egui/src/motion.rs` reads each frame's wheel input the way egui does, minus the easing, and hands that to the scroll areas and the views (a test sums what egui eases and compares it with what is read at once, and fails if egui stops easing). egui's own animations (menus, sections, scrolling to a row) take no time, the chrome's hover and press washes are simply there, and the progress card's bar steps without its sweep. The desktop app asks WinRT's `UISettings.AnimationsEnabled` at launch and every two seconds of use, so a change applies without a restart. How far a notch scrolls or zooms is unchanged. The preference set to On or Off decides all of this whatever the system says; it is saved, set as `animation_effects` (`system`, `on`, `off`) on the control channel, and translated in all 20 catalogs. Not done: macOS (Reduce motion), Linux (GNOME's animations switch) and the browser (`prefers-reduced-motion`) aren't asked yet, so Use system setting animates there as before; Off works everywhere. Estimates unchanged.
|
||||
- **2026-10-11 (M10.5):** Extracted printer discovery, options and PDF submission into craft-libs/craft-print, shared with DesignCraft. Windows preserves composed sheet size and orientation without fitting into default margins; duplex binding edges are corrected. PdfCraft retains permission checks and imposition. Shared transport and local composition, engine, automation and UI coverage validate the boundary; native Windows CI passes, including paused-queue submission and cleanup; physical printer fidelity remains a hardware check. Rust 1.95 is required by the shared crate. No readiness percentage change.
|
||||
- **2026-10-11 (M10, #1028):** Recognize Text can read pages that already have text: "Also recognize pages that already have text" in the dialog (remembered with the other choices, and used by Recognize text in multiple files) and `skip_text_pages: false` on `ocr_recognize` and, newly, `ocr_recognize_files`. A picture on such a page becomes searchable, and a recognised word at least half covered by the page's own text (an earlier OCR layer included) is left out, so nothing is found twice; `ocr_recognize` reports how many were left out as `already_on_page`. Before, the option existed only for agents and added the page's text a second time. The default still skips pages with text. Synthetic tests (a picture of a sentence plus real text) cover the engine, the tool and the dialog, and fail with the filter turned off. Still missing: reading only the picture areas (the whole page is read, as for a scan). Separately, `recognize_text_makes_a_scanned_page_searchable` fails on main wherever the models are installed (the model reads "quick" as "auick"). Estimates unchanged.
|
||||
- **2026-10-11 (M2, page-label prefixes):** The page-label readers bound the total size of decoded prefixes. Each range decodes its own prefix, and ranges may share one prefix object, so a small file could ask for a copy per range: within the 200,000-entry tree limit, about 200 MiB of copies of one 1,024-byte prefix. Inspection and Organize now refuse a tree whose decoded prefixes pass 4 MiB in total (`MAX_PREFIX_TOTAL_BYTES`), before any label is made, as the other tree limits do; relabelling such a tree changes nothing. Synthetic tests share one 1,000-byte prefix across 5,000 ranges and keep exactly 4 MiB of prefixes readable. Overall estimate unchanged.
|
||||
@@ -379,7 +379,7 @@ Newest first: the date, what moved, and the new overall estimate.
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | M10: borderless inferred tables and positioned invoice watermarks (#773); estimates unchanged. |
|
||||
| 2026-10-11 | minor | M3: Windows' Animation effects setting is followed (wheel scrolling and zooming without easing, egui's animations off), and Preferences has Smooth scrolling (Use system setting, On, Off). Estimates unchanged. |
|
||||
| 2026-10-11 | minor | M3: Windows' Animation effects setting is followed (wheel scrolling and zooming without easing, no other animations), and Preferences has Animation effects (Use system setting, On, Off). Estimates unchanged. |
|
||||
| 2026-10-11 | minor | M7: #594 paragraph-drag candidate and source regressions prepared; subsequent Linux validation passed all eight CI gates. Estimates unchanged. |
|
||||
| 2026-10-10 | minor | Recorded the remaining System appearance repair after #881; estimates unchanged |
|
||||
| 2026-10-11 | minor | Contributor fork: document-font selection and subset completion, CFF, style ranges, contextual/bidi save/reopen and all-page Chinese keyboard acceptance; curve/hole hit testing (shared clips stay fixed, as on main). Scope remains bounded; percentages unchanged |
|
||||
|
||||
@@ -314,8 +314,8 @@ fn app_creator<'a>(
|
||||
}
|
||||
app.integrated_titlebar = integrated;
|
||||
app.system_text_scale = text_scale::system();
|
||||
// Windows' Animation effects setting: with it off nothing animates, and the wheel doesn't
|
||||
// ease unless Preferences ▸ Smooth scrolling says so.
|
||||
// Windows' Animation effects setting, which Preferences ▸ Animation effects follows unless
|
||||
// it is set to On or Off.
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
app.motion.source = Some(motion::source());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The system's "no animations" setting, which `pdfcraft_ui_egui::motion` follows (and Preferences ▸
|
||||
//! Smooth scrolling, unless it is set to On or Off).
|
||||
//! The system's "no animations" setting, which Preferences ▸ Animation effects follows unless it is
|
||||
//! set to On or Off (`pdfcraft_ui_egui::motion`).
|
||||
//!
|
||||
//! Windows: Settings ▸ Accessibility ▸ Visual effects ▸ Animation effects (in the Control Panel,
|
||||
//! "Show animations in Windows"), read through WinRT's `UISettings.AnimationsEnabled`. Other
|
||||
|
||||
@@ -2277,9 +2277,9 @@ key Space مسافة
|
||||
scroll speed Slow بطيئة
|
||||
scroll speed Normal عادية
|
||||
scroll speed Fast سريعة
|
||||
Smooth scrolling التمرير السلس
|
||||
smooth scrolling On مفعّل
|
||||
smooth scrolling Off معطّل
|
||||
Animation effects تأثيرات الحركة
|
||||
animation effects On مفعّلة
|
||||
animation effects Off معطّلة
|
||||
Enhance camera image تحسين صورة الكاميرا
|
||||
Drag the corners onto the document's edges, then choose Enhance page. اسحب الزوايا إلى حواف المستند، ثم اختر تحسين الصفحة.
|
||||
Enhance page تحسين الصفحة
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2219,9 +2219,9 @@ key Space Интервал
|
||||
scroll speed Slow Ниска
|
||||
scroll speed Normal Нормална
|
||||
scroll speed Fast Висока
|
||||
Smooth scrolling Плавно превъртане
|
||||
smooth scrolling On Включено
|
||||
smooth scrolling Off Изключено
|
||||
Animation effects Анимационни ефекти
|
||||
animation effects On Включени
|
||||
animation effects Off Изключени
|
||||
Enhance camera image Подобри снимка от камера
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Плъзнете ъглите до краищата на документа, след това изберете „Подобри страницата“.
|
||||
Enhance page Подобри страницата
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -2307,9 +2307,9 @@ certificate Subject Subjekt
|
||||
scroll speed Slow Pomalá
|
||||
scroll speed Normal Normální
|
||||
scroll speed Fast Rychlá
|
||||
Smooth scrolling Hladké posouvání
|
||||
smooth scrolling On Zapnuto
|
||||
smooth scrolling Off Vypnuto
|
||||
Animation effects Animační efekty
|
||||
animation effects On Zapnuto
|
||||
animation effects Off Vypnuto
|
||||
|
||||
# Counts that take plural forms: optimized images, discarded items, bytes, pixels and deleted fields.
|
||||
@plural {n} image optimized|{n} images optimized {n} obrázek optimalizován|{n} obrázky optimalizovány|{n} obrázků optimalizováno
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2257,9 +2257,9 @@ key Space Leertaste
|
||||
scroll speed Slow Langsam
|
||||
scroll speed Normal Normal
|
||||
scroll speed Fast Schnell
|
||||
Smooth scrolling Sanftes Scrollen
|
||||
smooth scrolling On Ein
|
||||
smooth scrolling Off Aus
|
||||
Animation effects Animationseffekte
|
||||
animation effects On Ein
|
||||
animation effects Off Aus
|
||||
Enhance camera image Kamerafoto verbessern
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Ziehen Sie die Ecken auf die Kanten des Dokuments und wählen Sie dann Seite verbessern.
|
||||
Enhance page Seite verbessern
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
@@ -2269,9 +2269,9 @@ comment menu Edit Επεξεργασία
|
||||
scroll speed Slow Αργή
|
||||
scroll speed Normal Κανονική
|
||||
scroll speed Fast Γρήγορη
|
||||
Smooth scrolling Ομαλή κύλιση
|
||||
smooth scrolling On Ενεργή
|
||||
smooth scrolling Off Ανενεργή
|
||||
Animation effects Εφέ κίνησης
|
||||
animation effects On Ενεργά
|
||||
animation effects Off Ανενεργά
|
||||
Enhance camera image Βελτίωση εικόνας κάμερας
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Σύρετε τις γωνίες στις άκρες του εγγράφου και επιλέξτε Βελτίωση σελίδας.
|
||||
Enhance page Βελτίωση σελίδας
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 479 and column 12.
|
@@ -2386,9 +2386,9 @@ key Space Espacio
|
||||
scroll speed Slow Lenta
|
||||
scroll speed Normal Normal
|
||||
scroll speed Fast Rápida
|
||||
Smooth scrolling Desplazamiento suave
|
||||
smooth scrolling On Activado
|
||||
smooth scrolling Off Desactivado
|
||||
Animation effects Efectos de animación
|
||||
animation effects On Activados
|
||||
animation effects Off Desactivados
|
||||
Enhance camera image Mejorar foto de cámara
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Arrastra las esquinas hasta los bordes del documento y elige Mejorar página.
|
||||
Enhance page Mejorar página
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
@@ -2256,9 +2256,9 @@ key Space Espace
|
||||
scroll speed Slow Lente
|
||||
scroll speed Normal Normale
|
||||
scroll speed Fast Rapide
|
||||
Smooth scrolling Défilement fluide
|
||||
smooth scrolling On Activé
|
||||
smooth scrolling Off Désactivé
|
||||
Animation effects Effets d'animation
|
||||
animation effects On Activés
|
||||
animation effects Off Désactivés
|
||||
Enhance camera image Améliorer la photo d'un document
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Faites glisser les coins sur les bords du document, puis choisissez Améliorer la page.
|
||||
Enhance page Améliorer la page
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
@@ -2255,9 +2255,9 @@ key Space Szóköz
|
||||
scroll speed Slow Lassú
|
||||
scroll speed Normal Normál
|
||||
scroll speed Fast Gyors
|
||||
Smooth scrolling Finom görgetés
|
||||
smooth scrolling On Be
|
||||
smooth scrolling Off Ki
|
||||
Animation effects Animációs effektusok
|
||||
animation effects On Be
|
||||
animation effects Off Ki
|
||||
Enhance camera image Kamerakép javítása
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Húzza a sarkokat a dokumentum széleire, majd válassza az Oldal javítása lehetőséget.
|
||||
Enhance page Oldal javítása
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
@@ -2265,9 +2265,9 @@ key Space Spazio
|
||||
scroll speed Slow Lenta
|
||||
scroll speed Normal Normale
|
||||
scroll speed Fast Veloce
|
||||
Smooth scrolling Scorrimento fluido
|
||||
smooth scrolling On Attivato
|
||||
smooth scrolling Off Disattivato
|
||||
Animation effects Effetti di animazione
|
||||
animation effects On Attivati
|
||||
animation effects Off Disattivati
|
||||
Enhance camera image Migliora foto della fotocamera
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Trascina gli angoli sui bordi del documento, poi scegli Migliora la pagina.
|
||||
Enhance page Migliora la pagina
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2256,9 +2256,9 @@ key Space スペース
|
||||
scroll speed Slow 遅い
|
||||
scroll speed Normal 標準
|
||||
scroll speed Fast 速い
|
||||
Smooth scrolling スムーズスクロール
|
||||
smooth scrolling On オン
|
||||
smooth scrolling Off オフ
|
||||
Animation effects アニメーション効果
|
||||
animation effects On オン
|
||||
animation effects Off オフ
|
||||
Enhance camera image カメラ画像を補正
|
||||
Drag the corners onto the document's edges, then choose Enhance page. 四隅を文書の端までドラッグしてから、「ページを補正」を選択します。
|
||||
Enhance page ページを補正
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2251,9 +2251,9 @@ key Space Бос орын
|
||||
scroll speed Slow Баяу
|
||||
scroll speed Normal Қалыпты
|
||||
scroll speed Fast Жылдам
|
||||
Smooth scrolling Тегіс айналдыру
|
||||
smooth scrolling On Қосулы
|
||||
smooth scrolling Off Өшірулі
|
||||
Animation effects Анимация әсерлері
|
||||
animation effects On Қосулы
|
||||
animation effects Off Өшірулі
|
||||
Enhance camera image Камера суретін жақсарту
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Бұрыштарды құжаттың жиектеріне сүйреп, содан кейін «Бетті жақсарту» таңдаңыз.
|
||||
Enhance page Бетті жақсарту
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 819 and column 12.
|
@@ -2238,9 +2238,9 @@ key Space Spacja
|
||||
scroll speed Slow Wolna
|
||||
scroll speed Normal Normalna
|
||||
scroll speed Fast Szybka
|
||||
Smooth scrolling Płynne przewijanie
|
||||
smooth scrolling On Włączone
|
||||
smooth scrolling Off Wyłączone
|
||||
Animation effects Efekty animacji
|
||||
animation effects On Włączone
|
||||
animation effects Off Wyłączone
|
||||
Enhance camera image Popraw zdjęcie z aparatu
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Przeciągnij narożniki na krawędzie dokumentu, a następnie wybierz Popraw stronę.
|
||||
Enhance page Popraw stronę
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2270,9 +2270,9 @@ calendar date December dezembro
|
||||
scroll speed Slow Lenta
|
||||
scroll speed Normal Normal
|
||||
scroll speed Fast Rápida
|
||||
Smooth scrolling Rolagem suave
|
||||
smooth scrolling On Ativada
|
||||
smooth scrolling Off Desativada
|
||||
Animation effects Efeitos de animação
|
||||
animation effects On Ativados
|
||||
animation effects Off Desativados
|
||||
Enhance camera image Aprimorar foto da câmera
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Arraste os cantos até as bordas do documento e escolha Aprimorar página.
|
||||
Enhance page Aprimorar página
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2169,9 +2169,9 @@ key Space Пробел
|
||||
scroll speed Slow Низкая
|
||||
scroll speed Normal Обычная
|
||||
scroll speed Fast Высокая
|
||||
Smooth scrolling Плавная прокрутка
|
||||
smooth scrolling On Вкл
|
||||
smooth scrolling Off Выкл
|
||||
Animation effects Эффекты анимации
|
||||
animation effects On Вкл
|
||||
animation effects Off Выкл
|
||||
Enhance camera image Улучшить снимок с камеры
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Перетащите углы к краям документа, затем выберите «Улучшить страницу».
|
||||
Enhance page Улучшить страницу
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2258,9 +2258,9 @@ key Space Preslednica
|
||||
scroll speed Slow Počasi
|
||||
scroll speed Normal Običajno
|
||||
scroll speed Fast Hitro
|
||||
Smooth scrolling Gladko drsenje
|
||||
smooth scrolling On Vklopljeno
|
||||
smooth scrolling Off Izklopljeno
|
||||
Animation effects Učinki animacije
|
||||
animation effects On Vklopljeni
|
||||
animation effects Off Izklopljeni
|
||||
Enhance camera image Izboljšaj fotografijo s kamere
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Povlecite vogale na robove dokumenta, nato izberite Izboljšaj stran.
|
||||
Enhance page Izboljšaj stran
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2172,9 +2172,9 @@ key Space స్పేస్
|
||||
scroll speed Slow నెమ్మదిగా
|
||||
scroll speed Normal సాధారణం
|
||||
scroll speed Fast వేగంగా
|
||||
Smooth scrolling సున్నితమైన స్క్రోలింగ్
|
||||
smooth scrolling On ఆన్
|
||||
smooth scrolling Off ఆఫ్
|
||||
Animation effects యానిమేషన్ ప్రభావాలు
|
||||
animation effects On ఆన్
|
||||
animation effects Off ఆఫ్
|
||||
Enhance camera image కెమెరా చిత్రాన్ని మెరుగుపరచు
|
||||
Drag the corners onto the document's edges, then choose Enhance page. మూలలను పత్రం అంచుల వరకు లాగి, ఆపై పేజీని మెరుగుపరచు ఎంచుకోండి.
|
||||
Enhance page పేజీని మెరుగుపరచు
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 13 and column 16.
|
@@ -2249,9 +2249,9 @@ key Space Boşluk
|
||||
scroll speed Slow Yavaş
|
||||
scroll speed Normal Orta
|
||||
scroll speed Fast Hızlı
|
||||
Smooth scrolling Yumuşak kaydırma
|
||||
smooth scrolling On Açık
|
||||
smooth scrolling Off Kapalı
|
||||
Animation effects Animasyon efektleri
|
||||
animation effects On Açık
|
||||
animation effects Off Kapalı
|
||||
Enhance camera image Kamera görüntüsünü iyileştir
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Köşeleri belgenin kenarlarına sürükleyin, ardından Sayfayı iyileştir'i seçin.
|
||||
Enhance page Sayfayı iyileştir
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 818 and column 12.
|
@@ -2278,9 +2278,9 @@ key Space Пробіл
|
||||
scroll speed Slow Повільна
|
||||
scroll speed Normal Звичайна
|
||||
scroll speed Fast Швидка
|
||||
Smooth scrolling Плавне прокручування
|
||||
smooth scrolling On Увімкнено
|
||||
smooth scrolling Off Вимкнено
|
||||
Animation effects Ефекти анімації
|
||||
animation effects On Увімкнено
|
||||
animation effects Off Вимкнено
|
||||
Enhance camera image Поліпшити знімок з камери
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Перетягніть кути до країв документа, потім виберіть «Поліпшити сторінку».
|
||||
Enhance page Поліпшити сторінку
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2220,9 +2220,9 @@ key Space 空格
|
||||
scroll speed Slow 慢
|
||||
scroll speed Normal 正常
|
||||
scroll speed Fast 快
|
||||
Smooth scrolling 平滑滚动
|
||||
smooth scrolling On 开
|
||||
smooth scrolling Off 关
|
||||
Animation effects 动画效果
|
||||
animation effects On 开
|
||||
animation effects Off 关
|
||||
Enhance camera image 增强相机图像
|
||||
Drag the corners onto the document's edges, then choose Enhance page. 将四个角拖到文档边缘,然后选择“增强页面”。
|
||||
Enhance page 增强页面
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2131,9 +2131,9 @@ key Space 空格
|
||||
scroll speed Slow 慢
|
||||
scroll speed Normal 正常
|
||||
scroll speed Fast 快
|
||||
Smooth scrolling 平滑捲動
|
||||
smooth scrolling On 開
|
||||
smooth scrolling Off 關
|
||||
Animation effects 動畫效果
|
||||
animation effects On 開
|
||||
animation effects Off 關
|
||||
Table rows belong directly in a table or its head, body or foot. Fix the table's tags. 表格列應直接位於表格或其表頭、主體、表尾內。請修正表格標記。
|
||||
Enhance camera image 增強相機影像
|
||||
Drag the corners onto the document's edges, then choose Enhance page. 將四個角拖曳到文件邊緣,然後選擇「增強頁面」。
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -357,11 +357,12 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
||||
widgets::radio_value(ui, &mut app.scroll_speed, speed, tl_ctx!("scroll speed", speed.label()));
|
||||
}
|
||||
});
|
||||
// Whether the wheel eases; left alone it follows the system's animation setting.
|
||||
// Whether anything animates, the wheel's scrolling and zooming included; left alone it
|
||||
// follows the system's animation setting.
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Smooth scrolling"));
|
||||
for choice in crate::motion::SmoothScrolling::ALL {
|
||||
widgets::radio_value(ui, &mut app.smooth_scrolling, choice, tl_ctx!("smooth scrolling", choice.label()));
|
||||
ui.label(tl!("Animation effects"));
|
||||
for choice in crate::motion::AnimationEffects::ALL {
|
||||
widgets::radio_value(ui, &mut app.animation_effects, choice, tl_ctx!("animation effects", choice.label()));
|
||||
}
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
|
||||
+10
-10
@@ -468,8 +468,8 @@ pub struct PdfCraftApp {
|
||||
desktop_theme_enabled: bool,
|
||||
/// How far a mouse-wheel notch scrolls.
|
||||
pub scroll_speed: scroll_speed::ScrollSpeed,
|
||||
/// Preferences ▸ Smooth scrolling: eased, at once, or as the system's animation setting says.
|
||||
pub smooth_scrolling: motion::SmoothScrolling,
|
||||
/// Preferences ▸ Animation effects: on, off, or as the system's animation setting says.
|
||||
pub animation_effects: motion::AnimationEffects,
|
||||
/// Whether the system asked for no animations, and how the desktop app asks it.
|
||||
pub motion: motion::Motion,
|
||||
/// Interface language preference: `auto` (follow the system) or a code from [`i18n::LANGUAGES`].
|
||||
@@ -797,7 +797,7 @@ impl PdfCraftApp {
|
||||
#[cfg(target_os = "linux")]
|
||||
desktop_theme_enabled: false,
|
||||
scroll_speed: scroll_speed::ScrollSpeed::default(),
|
||||
smooth_scrolling: motion::SmoothScrolling::default(),
|
||||
animation_effects: motion::AnimationEffects::default(),
|
||||
motion: motion::Motion::default(),
|
||||
language: i18n::AUTO.to_string(),
|
||||
ui_scale: None,
|
||||
@@ -1636,7 +1636,7 @@ impl PdfCraftApp {
|
||||
"pinned_folders": self.pinned.folders,
|
||||
"theme": self.theme_preference,
|
||||
"scroll_speed": self.scroll_speed,
|
||||
"smooth_scrolling": self.smooth_scrolling,
|
||||
"animation_effects": self.animation_effects,
|
||||
"default_mode": self.default_mode,
|
||||
"default_layout": canvas::display_token(self.view_defaults.layout, self.view_defaults.continuous),
|
||||
"default_zoom": self.view_defaults.zoom_name(),
|
||||
@@ -1722,8 +1722,8 @@ impl PdfCraftApp {
|
||||
if let Ok(speed) = serde_json::from_value::<scroll_speed::ScrollSpeed>(v["scroll_speed"].clone()) {
|
||||
self.scroll_speed = speed;
|
||||
}
|
||||
if let Ok(smooth) = serde_json::from_value::<motion::SmoothScrolling>(v["smooth_scrolling"].clone()) {
|
||||
self.smooth_scrolling = smooth;
|
||||
if let Ok(effects) = serde_json::from_value::<motion::AnimationEffects>(v["animation_effects"].clone()) {
|
||||
self.animation_effects = effects;
|
||||
}
|
||||
if let Ok(mode) = serde_json::from_value::<Mode>(v["default_mode"].clone()) {
|
||||
self.default_mode = mode;
|
||||
@@ -1851,8 +1851,8 @@ impl PdfCraftApp {
|
||||
("scroll_speed", _) => {
|
||||
self.scroll_speed = scroll_speed::ScrollSpeed::parse(value).ok_or("scroll_speed must be slow, normal, or fast")?;
|
||||
}
|
||||
("smooth_scrolling", _) => {
|
||||
self.smooth_scrolling = motion::SmoothScrolling::parse(value).ok_or("smooth_scrolling must be system, on, or off")?;
|
||||
("animation_effects", _) => {
|
||||
self.animation_effects = motion::AnimationEffects::parse(value).ok_or("animation_effects must be system, on, or off")?;
|
||||
}
|
||||
("panel", _) => {
|
||||
self.right = match value {
|
||||
@@ -2285,8 +2285,8 @@ impl eframe::App for PdfCraftApp {
|
||||
self.startup_superseded |= !self.views.is_empty() || self.combine_showing() || self.dialog.is_some() || self.password_prompt.is_some();
|
||||
// Also done in `raw_input_hook`, before input; here for hosts that don't call the hook.
|
||||
self.scroll_speed.set_line_speed(ctx);
|
||||
// Before anything reads this frame's wheel input, and with the line speed just set.
|
||||
self.motion.begin_pass(ctx, self.smooth_scrolling);
|
||||
// Before anything reads this frame's wheel input or draws, and with the line speed just set.
|
||||
self.motion.begin_pass(ctx, self.animation_effects);
|
||||
// Before taking this frame's drop: the grid must be drawn once with the pointer where
|
||||
// the files were let go before the gap is read.
|
||||
self.finish_grid_drop(ctx);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
//! Motion the system asked to do without.
|
||||
//! Animation effects, and the system's request to do without them.
|
||||
//!
|
||||
//! Windows' Settings ▸ Accessibility ▸ Visual effects ▸ Animation effects, turned off, asks
|
||||
//! apps not to animate (other systems have a Reduce motion switch for it). The desktop app
|
||||
//! reads the setting ([`MotionSource`]); while motion is reduced, nothing in the window eases:
|
||||
//! reads the setting ([`MotionSource`]), and Preferences ▸ Animation effects
|
||||
//! ([`AnimationEffects`]) follows it unless the user chose On or Off there. With animation
|
||||
//! effects off, nothing in the window eases:
|
||||
//!
|
||||
//! - A wheel notch scrolls its whole distance in the frame it arrives. egui spreads a notch
|
||||
//! over about a quarter of a second and has no switch for that, so each frame's wheel input
|
||||
@@ -11,29 +13,27 @@
|
||||
//! - ⌘/Ctrl-wheel zooms by a notch's whole step at once: egui makes its zoom factor from the
|
||||
//! same eased wheel motion. Views read [`zoom_delta`] instead of egui's.
|
||||
//! - Scrolling to a place (a row revealed by the keys, the Hand tool's drag) takes no time.
|
||||
//! - egui's other animations (menus fading in, sections opening) take no time.
|
||||
//!
|
||||
//! Preferences ▸ Smooth scrolling ([`SmoothScrolling`]) decides the first three by itself when
|
||||
//! it is On or Off, whatever the system says; the last always follows the system.
|
||||
//! - egui's other animations (menus fading in, sections opening) take no time: its style's
|
||||
//! animation time is zero, which the app's own fades and sweeps ask about too ([`animated`]).
|
||||
//!
|
||||
//! How far anything scrolls or zooms doesn't change, only how long it takes.
|
||||
|
||||
use egui::style::ScrollAnimation;
|
||||
use egui::{Event, Modifiers, MouseWheelUnit, TouchPhase, Vec2, vec2};
|
||||
|
||||
/// Preferences ▸ Smooth scrolling: whether the wheel's scrolling and zooming, and scrolling to
|
||||
/// a place, are eased.
|
||||
/// Preferences ▸ Animation effects: whether anything in the window is animated, the wheel's
|
||||
/// scrolling and zooming included.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SmoothScrolling {
|
||||
/// Eased unless the system asks for no animations ([`Motion::reduced`]).
|
||||
pub enum AnimationEffects {
|
||||
/// Animated unless the system asks for no animations ([`Motion::reduced`]).
|
||||
#[default]
|
||||
System,
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
|
||||
impl SmoothScrolling {
|
||||
impl AnimationEffects {
|
||||
pub const ALL: [Self; 3] = [Self::System, Self::On, Self::Off];
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
@@ -57,8 +57,8 @@ impl SmoothScrolling {
|
||||
Self::ALL.into_iter().find(|choice| choice.name().eq_ignore_ascii_case(value))
|
||||
}
|
||||
|
||||
/// Whether scrolling is eased, given whether the system asks for no animations.
|
||||
pub fn eases(self, system_reduced: bool) -> bool {
|
||||
/// Whether the window animates, given whether the system asks for no animations.
|
||||
pub fn animates(self, system_reduced: bool) -> bool {
|
||||
match self {
|
||||
Self::System => !system_reduced,
|
||||
Self::On => true,
|
||||
@@ -76,7 +76,7 @@ pub type MotionSource = Box<dyn Fn() -> Option<bool>>;
|
||||
/// this away from the window, so the first frame back already follows it.
|
||||
const ASK_EVERY: f64 = 2.0;
|
||||
|
||||
/// Whether the window animates, and what it takes to stop egui from doing so.
|
||||
/// What the system says about animations, and what it takes to stop egui's.
|
||||
pub struct Motion {
|
||||
/// The system asks for no animations. The desktop app's [`Self::source`] sets it; false
|
||||
/// elsewhere, so tests and screenshots don't depend on the machine.
|
||||
@@ -97,8 +97,8 @@ impl Default for Motion {
|
||||
}
|
||||
|
||||
impl Motion {
|
||||
/// Call once at the start of every pass, before any ui reads the wheel.
|
||||
pub(crate) fn begin_pass(&mut self, ctx: &egui::Context, smooth: SmoothScrolling) {
|
||||
/// Call once at the start of every pass, before any ui reads the wheel or draws.
|
||||
pub(crate) fn begin_pass(&mut self, ctx: &egui::Context, effects: AnimationEffects) {
|
||||
let now = ctx.input(|i| i.time);
|
||||
// `>=` is false for a NaN time, which then never asks.
|
||||
if now - self.asked_at >= ASK_EVERY
|
||||
@@ -109,9 +109,9 @@ impl Motion {
|
||||
self.reduced = reduced;
|
||||
}
|
||||
}
|
||||
let eases = smooth.eases(self.reduced);
|
||||
self.sync_style(ctx, eases);
|
||||
if eases {
|
||||
let animate = effects.animates(self.reduced);
|
||||
self.sync_style(ctx, animate);
|
||||
if animate {
|
||||
// A gesture begun while easing is egui's to finish.
|
||||
self.wheel = WheelReader::default();
|
||||
return;
|
||||
@@ -123,32 +123,37 @@ impl Motion {
|
||||
ctx.data_mut(|d| d.insert_temp(zoom_id(), (pass, motion.zoom)));
|
||||
}
|
||||
|
||||
/// egui's animations take no time while the system asks for none, and scrolling to a place
|
||||
/// takes none unless scrolling `eases`; otherwise both keep the times egui had.
|
||||
fn sync_style(&mut self, ctx: &egui::Context, eases: bool) {
|
||||
let unchanged = !self.reduced && eases;
|
||||
if unchanged && self.egui_times.is_none() {
|
||||
/// egui's animations, and its scrolling to a place, take no time unless the window is to
|
||||
/// `animate`; then both get back the times egui had.
|
||||
fn sync_style(&mut self, ctx: &egui::Context, animate: bool) {
|
||||
if animate && self.egui_times.is_none() {
|
||||
return;
|
||||
}
|
||||
// Checked every pass, so a style replaced since can't bring the animations back.
|
||||
let style = ctx.global_style();
|
||||
let (time, scroll) = *self.egui_times.get_or_insert((style.animation_time, style.scroll_animation));
|
||||
let wanted = (if self.reduced { 0.0 } else { time }, if eases { scroll } else { ScrollAnimation::none() });
|
||||
let egui_times = *self.egui_times.get_or_insert((style.animation_time, style.scroll_animation));
|
||||
let wanted = if animate { egui_times } else { (0.0, ScrollAnimation::none()) };
|
||||
if (style.animation_time, style.scroll_animation) != wanted {
|
||||
ctx.all_styles_mut(|s| (s.animation_time, s.scroll_animation) = wanted);
|
||||
}
|
||||
if unchanged {
|
||||
if animate {
|
||||
self.egui_times = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the window animates this pass. The app's own fades and sweeps, which have their own
|
||||
/// durations, ask this; egui's animations stop by themselves ([`Motion::begin_pass`]).
|
||||
pub(crate) fn animated(ctx: &egui::Context) -> bool {
|
||||
ctx.global_style().animation_time > 0.0
|
||||
}
|
||||
|
||||
fn zoom_id() -> egui::Id {
|
||||
egui::Id::new("pdfcraft-motion-wheel-zoom")
|
||||
}
|
||||
|
||||
/// This pass's zoom factor from a pinch or ⌘/Ctrl with the wheel (1.0 for none): egui's
|
||||
/// `zoom_delta`, without the easing unless scrolling is eased.
|
||||
/// `zoom_delta`, without the easing while animation effects are off.
|
||||
pub(crate) fn zoom_delta(ctx: &egui::Context) -> f32 {
|
||||
let read: Option<(u64, f32)> = ctx.data(|d| d.get_temp(zoom_id()));
|
||||
let pass = ctx.cumulative_pass_nr();
|
||||
@@ -374,7 +379,7 @@ mod tests {
|
||||
let mut events = vec![notch(-1.0, Modifiers::NONE)];
|
||||
for _ in 0..30 {
|
||||
pass(&ctx, &mut time, std::mem::take(&mut events), |ctx| {
|
||||
motion.begin_pass(ctx, SmoothScrolling::System);
|
||||
motion.begin_pass(ctx, AnimationEffects::System);
|
||||
seen.push(ctx.input(|i| i.smooth_scroll_delta));
|
||||
});
|
||||
}
|
||||
@@ -392,7 +397,7 @@ mod tests {
|
||||
let mut events = vec![notch(1.0, Modifiers::COMMAND)];
|
||||
for _ in 0..30 {
|
||||
pass(&ctx, &mut time, std::mem::take(&mut events), |ctx| {
|
||||
motion.begin_pass(ctx, SmoothScrolling::System);
|
||||
motion.begin_pass(ctx, AnimationEffects::System);
|
||||
zooms.push(zoom_delta(ctx));
|
||||
});
|
||||
}
|
||||
@@ -405,7 +410,7 @@ mod tests {
|
||||
let mut events = vec![notch(1.0, Modifiers::COMMAND)];
|
||||
for _ in 0..3 {
|
||||
pass(&ctx, &mut time, std::mem::take(&mut events), |ctx| {
|
||||
motion.begin_pass(ctx, SmoothScrolling::System);
|
||||
motion.begin_pass(ctx, AnimationEffects::System);
|
||||
eased.push((zoom_delta(ctx), ctx.input(|i| i.zoom_delta())));
|
||||
});
|
||||
}
|
||||
@@ -416,66 +421,99 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_preference_decides_scrolling_and_the_system_the_other_animations() {
|
||||
use SmoothScrolling::{Off, On, System};
|
||||
assert_eq!(SmoothScrolling::default(), System, "the system's setting is followed unless the user chooses");
|
||||
for choice in SmoothScrolling::ALL {
|
||||
assert_eq!(SmoothScrolling::parse(choice.name()), Some(choice));
|
||||
fn the_preference_overrides_the_system_for_every_animation() {
|
||||
use AnimationEffects::{Off, On, System};
|
||||
assert_eq!(AnimationEffects::default(), System, "the system's setting is followed unless the user chooses");
|
||||
for choice in AnimationEffects::ALL {
|
||||
assert_eq!(AnimationEffects::parse(choice.name()), Some(choice));
|
||||
}
|
||||
assert_eq!((SmoothScrolling::parse("OFF"), SmoothScrolling::parse("smooth")), (Some(Off), None));
|
||||
assert_eq!((AnimationEffects::parse("OFF"), AnimationEffects::parse("smooth")), (Some(Off), None));
|
||||
let (line, egui_default) = (egui::InputOptions::default().line_scroll_speed, egui::Style::default());
|
||||
// The system's answer, the choice, and whether a notch then lands whole in its pass.
|
||||
for (reduced, smooth, at_once) in
|
||||
[(false, System, false), (true, System, true), (false, On, false), (true, On, false), (false, Off, true), (true, Off, true)]
|
||||
let egui_times = (egui_default.animation_time, egui_default.scroll_animation);
|
||||
// The system's answer, the choice, and whether the window then animates.
|
||||
for (reduced, effects, animates) in
|
||||
[(false, System, true), (true, System, false), (false, On, true), (true, On, true), (false, Off, false), (true, Off, false)]
|
||||
{
|
||||
assert_eq!(smooth.eases(reduced), !at_once, "{smooth:?}, system reduced: {reduced}");
|
||||
assert_eq!(effects.animates(reduced), animates, "{effects:?}, system reduced: {reduced}");
|
||||
let ctx = egui::Context::default();
|
||||
let mut motion = Motion { reduced, ..Default::default() };
|
||||
let (mut time, mut first) = (0.0, None);
|
||||
pass(&ctx, &mut time, vec![notch(-1.0, Modifiers::NONE)], |ctx| {
|
||||
motion.begin_pass(ctx, smooth);
|
||||
first = Some(ctx.input(|i| i.smooth_scroll_delta));
|
||||
motion.begin_pass(ctx, effects);
|
||||
first = Some((ctx.input(|i| i.smooth_scroll_delta), animated(ctx)));
|
||||
});
|
||||
assert_eq!(first == Some(vec2(0.0, -line)), at_once, "{smooth:?}, system reduced: {reduced}: {first:?}");
|
||||
// The wheel, egui's animations and the app's own fades all go the same way.
|
||||
let whole_notch = first.map(|(scroll, _)| scroll) == Some(vec2(0.0, -line));
|
||||
assert_eq!(
|
||||
(whole_notch, first.map(|(_, animated)| animated)),
|
||||
(!animates, Some(animates)),
|
||||
"{effects:?}, system reduced: {reduced}: {first:?}"
|
||||
);
|
||||
let style = ctx.global_style();
|
||||
// Scrolling to a place goes with the wheel; menus and sections follow the system alone.
|
||||
let scroll_to = if at_once { ScrollAnimation::none() } else { egui_default.scroll_animation };
|
||||
let other = if reduced { 0.0 } else { egui_default.animation_time };
|
||||
assert_eq!((style.scroll_animation, style.animation_time), (scroll_to, other), "{smooth:?}, system reduced: {reduced}");
|
||||
let times = if animates { egui_times } else { (0.0, ScrollAnimation::none()) };
|
||||
assert_eq!((style.animation_time, style.scroll_animation), times, "{effects:?}, system reduced: {reduced}");
|
||||
}
|
||||
// A choice made while running applies from the next pass, and choosing back undoes it.
|
||||
let ctx = egui::Context::default();
|
||||
let mut motion = Motion::default();
|
||||
let mut time = 0.0;
|
||||
for (smooth, scroll_to) in [(Off, ScrollAnimation::none()), (System, egui_default.scroll_animation), (Off, ScrollAnimation::none())] {
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, smooth));
|
||||
for (effects, times) in [(Off, (0.0, ScrollAnimation::none())), (System, egui_times), (Off, (0.0, ScrollAnimation::none())), (On, egui_times)]
|
||||
{
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, effects));
|
||||
let style = ctx.global_style();
|
||||
assert_eq!((style.scroll_animation, style.animation_time), (scroll_to, egui_default.animation_time), "{smooth:?}");
|
||||
assert_eq!((style.animation_time, style.scroll_animation), times, "{effects:?}");
|
||||
}
|
||||
// The row's labels are translated: the heading, and On and Off as they suit it.
|
||||
let de = crate::i18n::Lang::from_code("de").expect("German ships");
|
||||
assert_eq!(crate::i18n::tr(de, "Smooth scrolling"), "Sanftes Scrollen");
|
||||
let labels: Vec<&str> = SmoothScrolling::ALL.into_iter().map(|choice| crate::i18n::tr_ctx(de, "smooth scrolling", choice.label())).collect();
|
||||
assert_eq!(crate::i18n::tr(de, "Animation effects"), "Animationseffekte");
|
||||
let labels: Vec<&str> =
|
||||
AnimationEffects::ALL.into_iter().map(|choice| crate::i18n::tr_ctx(de, "animation effects", choice.label())).collect();
|
||||
assert_eq!(labels, ["Systemeinstellung verwenden", "Ein", "Aus"]);
|
||||
for lang in crate::i18n::Lang::all() {
|
||||
for english in ["Smooth scrolling", "On", "Off"] {
|
||||
let shown = if english == "Smooth scrolling" {
|
||||
for english in ["Animation effects", "On", "Off"] {
|
||||
let shown = if english == "Animation effects" {
|
||||
crate::i18n::tr(lang, english)
|
||||
} else {
|
||||
crate::i18n::tr_ctx(lang, "smooth scrolling", english)
|
||||
crate::i18n::tr_ctx(lang, "animation effects", english)
|
||||
};
|
||||
assert!(lang.code() == "en" || shown != english, "{}: {english} is not translated", lang.code());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The chrome's hover wash has a duration of its own (`theme::Press`): it eases in with
|
||||
/// animation effects on and is simply there with them off.
|
||||
#[test]
|
||||
fn the_hover_wash_eases_only_with_animation_effects_on() {
|
||||
for (effects, eased) in [(AnimationEffects::On, true), (AnimationEffects::Off, false)] {
|
||||
let ctx = egui::Context::default();
|
||||
let mut motion = Motion::default();
|
||||
let (mut time, mut hover) = (0.0, Vec::new());
|
||||
for _ in 0..12 {
|
||||
time += 1.0 / 60.0;
|
||||
let raw = egui::RawInput { time: Some(time), events: vec![Event::PointerMoved(egui::pos2(20.0, 20.0))], ..Default::default() };
|
||||
ctx.run_ui(raw, |ui| {
|
||||
motion.begin_pass(ui.ctx(), effects);
|
||||
let (_, response) = ui.allocate_exact_size(vec2(100.0, 40.0), egui::Sense::click());
|
||||
hover.push(crate::theme::Press::track(ui, &response).hover);
|
||||
})
|
||||
.textures_delta
|
||||
.clear();
|
||||
}
|
||||
// The first frame the pointer is found over the control.
|
||||
let first = hover.iter().copied().find(|amount| *amount > 0.0).expect("the pointer is over the control");
|
||||
assert_eq!(first < 1.0, eased, "{effects:?}: {hover:?}");
|
||||
assert_eq!(hover.last(), Some(&1.0), "{effects:?}: the wash is whole in the end either way: {hover:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_zoom_read_in_an_earlier_pass_is_never_used_again() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut motion = Motion { reduced: true, ..Default::default() };
|
||||
let mut time = 0.0;
|
||||
pass(&ctx, &mut time, vec![notch(1.0, Modifiers::COMMAND)], |ctx| {
|
||||
motion.begin_pass(ctx, SmoothScrolling::System);
|
||||
motion.begin_pass(ctx, AnimationEffects::System);
|
||||
assert!(zoom_delta(ctx) > 1.0);
|
||||
});
|
||||
// A pass that didn't begin here (a host that draws without `logic`): egui's own answer.
|
||||
@@ -499,21 +537,21 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
let mut time = 0.0;
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, SmoothScrolling::System));
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, AnimationEffects::System));
|
||||
assert!(motion.reduced, "asked in the first pass");
|
||||
answer.set(Some(false));
|
||||
for _ in 0..30 {
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, SmoothScrolling::System));
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, AnimationEffects::System));
|
||||
}
|
||||
assert!(motion.reduced && asked.get() == 1, "not asked again within half a second ({} times)", asked.get());
|
||||
// Back at the window later: the first pass asks.
|
||||
time += ASK_EVERY;
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, SmoothScrolling::System));
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, AnimationEffects::System));
|
||||
assert!(!motion.reduced);
|
||||
answer.set(None);
|
||||
motion.reduced = true;
|
||||
time += ASK_EVERY;
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, SmoothScrolling::System));
|
||||
pass(&ctx, &mut time, Vec::new(), |ctx| motion.begin_pass(ctx, AnimationEffects::System));
|
||||
assert!(motion.reduced && asked.get() == 3, "no answer leaves the last one standing");
|
||||
}
|
||||
}
|
||||
@@ -141,9 +141,11 @@ impl Press {
|
||||
pub fn track(ui: &egui::Ui, resp: &Response) -> Self {
|
||||
let hot = resp.hovered() || resp.is_pointer_button_down_on();
|
||||
let ease = egui::emath::easing::quadratic_out;
|
||||
// With animation effects off the wash and the press are simply there.
|
||||
let (hover, down) = if crate::motion::animated(ui.ctx()) { (0.12, 0.06) } else { (0.0, 0.0) };
|
||||
Self {
|
||||
hover: ui.ctx().animate_bool_with_time_and_easing(resp.id.with("chrome-hover"), hot, 0.12, ease),
|
||||
down: ui.ctx().animate_bool_with_time_and_easing(resp.id.with("chrome-down"), resp.is_pointer_button_down_on(), 0.06, ease),
|
||||
hover: ui.ctx().animate_bool_with_time_and_easing(resp.id.with("chrome-hover"), hot, hover, ease),
|
||||
down: ui.ctx().animate_bool_with_time_and_easing(resp.id.with("chrome-down"), resp.is_pointer_button_down_on(), down, ease),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -430,7 +430,7 @@ const PROGRESS_HEIGHT: &str = "progress-notice-height";
|
||||
|
||||
/// Draw [`PdfCraftApp::progress_notice`]: the label, a bar that eases towards the fraction with
|
||||
/// a light sweeping across it, the percentage and Cancel. Returns true when Cancel was clicked.
|
||||
/// With motion reduced ([`crate::motion`]) the bar steps to the fraction and has no sweep.
|
||||
/// With animation effects off ([`crate::motion`]) the bar steps to the fraction and has no sweep.
|
||||
pub fn progress_notice(app: &PdfCraftApp, ctx: &egui::Context) -> bool {
|
||||
let Some(p) = &app.progress_notice else { return false };
|
||||
let t = Tokens::get(ctx);
|
||||
@@ -442,7 +442,7 @@ pub fn progress_notice(app: &PdfCraftApp, ctx: &egui::Context) -> bool {
|
||||
(Color32::from_rgb(0x2A, 0x2A, 0x2F), Color32::WHITE)
|
||||
};
|
||||
let fraction = if p.fraction.is_finite() { p.fraction.clamp(0.0, 1.0) } else { 0.0 };
|
||||
let animated = !app.motion.reduced;
|
||||
let animated = crate::motion::animated(ctx);
|
||||
let shown = ctx.animate_value_with_time(egui::Id::new("progress-notice-bar"), fraction, if animated { 0.3 } else { 0.0 });
|
||||
let now = ctx.input(|i| i.time);
|
||||
let mut cancel = false;
|
||||
|
||||
+20
-17
@@ -856,37 +856,40 @@ fn preferences_selects_default_workspace_for_next_open() {
|
||||
assert_eq!(h.state().mode, Mode::Read);
|
||||
}
|
||||
|
||||
/// Preferences ▸ Smooth scrolling: the system's animation setting until the user chooses On or
|
||||
/// Preferences ▸ Animation effects: the system's animation setting until the user chooses On or
|
||||
/// Off. The choice is saved, and settings holding anything else leave it alone.
|
||||
#[test]
|
||||
fn preferences_choose_smooth_scrolling_and_the_choice_is_saved() {
|
||||
fn preferences_choose_animation_effects_and_the_choice_is_saved() {
|
||||
use egui::accesskit::Role;
|
||||
use pdfcraft_ui_egui::motion::SmoothScrolling;
|
||||
use pdfcraft_ui_egui::motion::AnimationEffects;
|
||||
let mut h = harness(|app| app.set_option("dialog", "preferences").unwrap());
|
||||
assert_eq!(h.state().smooth_scrolling, SmoothScrolling::System);
|
||||
h.get_by_label("Smooth scrolling");
|
||||
for (label, choice) in
|
||||
[("Off", SmoothScrolling::Off), ("On", SmoothScrolling::On), ("Use system setting", SmoothScrolling::System), ("Off", SmoothScrolling::Off)]
|
||||
{
|
||||
assert_eq!(h.state().animation_effects, AnimationEffects::System);
|
||||
h.get_by_label("Animation effects");
|
||||
for (label, choice) in [
|
||||
("Off", AnimationEffects::Off),
|
||||
("On", AnimationEffects::On),
|
||||
("Use system setting", AnimationEffects::System),
|
||||
("Off", AnimationEffects::Off),
|
||||
] {
|
||||
h.get_by_role_and_label(Role::RadioButton, label).scroll_to_me();
|
||||
h.run_steps(4);
|
||||
h.get_by_role_and_label(Role::RadioButton, label).click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().smooth_scrolling, choice, "{label}");
|
||||
assert_eq!(h.state().animation_effects, choice, "{label}");
|
||||
}
|
||||
let saved = h.state().persist();
|
||||
assert!(saved.contains(r#""smooth_scrolling":"off""#), "{saved}");
|
||||
assert!(saved.contains(r#""animation_effects":"off""#), "{saved}");
|
||||
let mut restored = PdfCraftApp::new();
|
||||
restored.restore(&saved);
|
||||
assert_eq!(restored.smooth_scrolling, SmoothScrolling::Off);
|
||||
assert_eq!(restored.animation_effects, AnimationEffects::Off);
|
||||
let mut fresh = PdfCraftApp::new();
|
||||
fresh.restore(&saved.replace(r#""smooth_scrolling":"off""#, r#""smooth_scrolling":"sideways""#));
|
||||
assert_eq!(fresh.smooth_scrolling, SmoothScrolling::System);
|
||||
fresh.restore(&saved.replace(r#""animation_effects":"off""#, r#""animation_effects":"sideways""#));
|
||||
assert_eq!(fresh.animation_effects, AnimationEffects::System);
|
||||
// The control channel and the command line take the same three names.
|
||||
fresh.set_option("smooth_scrolling", "ON").unwrap();
|
||||
assert_eq!(fresh.smooth_scrolling, SmoothScrolling::On);
|
||||
assert!(fresh.set_option("smooth_scrolling", "sideways").unwrap_err().contains("system, on, or off"));
|
||||
assert_eq!(fresh.smooth_scrolling, SmoothScrolling::On);
|
||||
fresh.set_option("animation_effects", "ON").unwrap();
|
||||
assert_eq!(fresh.animation_effects, AnimationEffects::On);
|
||||
assert!(fresh.set_option("animation_effects", "sideways").unwrap_err().contains("system, on, or off"));
|
||||
assert_eq!(fresh.animation_effects, AnimationEffects::On);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -257,12 +257,12 @@ fn precise_touchpad_wheel_scrolls_one_to_one() {
|
||||
}
|
||||
|
||||
/// The 40 frames from one wheel notch on, with the system asking for no animations (Windows'
|
||||
/// Animation effects turned off) or not, and Preferences ▸ Smooth scrolling as given: how far
|
||||
/// Animation effects turned off) or not, and Preferences ▸ Animation effects as given: how far
|
||||
/// page 1 has moved up in each frame, or with ⌘/Ctrl held, the view's zoom.
|
||||
fn frames_after_a_notch(system_reduced: bool, smooth_scrolling: &str, zooming: bool) -> Vec<f32> {
|
||||
fn frames_after_a_notch(system_reduced: bool, animation_effects: &str, zooming: bool) -> Vec<f32> {
|
||||
let mut h = harness_stepping(1.0 / 60.0, &[("layout", "continuous"), ("zoom", "100")]);
|
||||
h.state_mut().motion.reduced = system_reduced;
|
||||
h.state_mut().set_option("smooth_scrolling", smooth_scrolling).unwrap();
|
||||
h.state_mut().set_option("animation_effects", animation_effects).unwrap();
|
||||
let at = h.state().views[0].viewport_rect().center();
|
||||
h.hover_at(at);
|
||||
h.run_steps(2);
|
||||
@@ -277,52 +277,55 @@ fn frames_after_a_notch(system_reduced: bool, smooth_scrolling: &str, zooming: b
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The system's answer and the Smooth scrolling choice, for each way to scroll at once and each
|
||||
/// way to ease.
|
||||
/// The system's answer and the Animation effects choice, for each way to have them off and
|
||||
/// each way to have them on.
|
||||
const AT_ONCE: [(bool, &str); 3] = [(true, "system"), (false, "off"), (true, "off")];
|
||||
const EASED: [(bool, &str); 3] = [(false, "system"), (false, "on"), (true, "on")];
|
||||
|
||||
#[test]
|
||||
fn a_wheel_notch_scrolls_at_once_unless_scrolling_is_smooth() {
|
||||
fn a_wheel_notch_scrolls_at_once_with_animation_effects_off() {
|
||||
// Eased, egui spreads a notch over about a quarter of a second. At once, the page moves the
|
||||
// notch's whole distance in one frame.
|
||||
let notch = pdfcraft_ui_egui::scroll_speed::ScrollSpeed::default().line_points();
|
||||
for (system_reduced, smooth) in AT_ONCE {
|
||||
let moved = frames_after_a_notch(system_reduced, smooth, false);
|
||||
for (system_reduced, effects) in AT_ONCE {
|
||||
let moved = frames_after_a_notch(system_reduced, effects, false);
|
||||
// The frame the notch arrives in scrolls; the next one draws the page where it stays.
|
||||
assert!(
|
||||
moved[2..].iter().all(|y| (y - notch).abs() < 0.5),
|
||||
"{smooth}, system reduced {system_reduced}: one notch is {notch} points, all at once: {moved:?}"
|
||||
"{effects}, system reduced {system_reduced}: one notch is {notch} points, all at once: {moved:?}"
|
||||
);
|
||||
}
|
||||
for (system_reduced, smooth) in EASED {
|
||||
let moved = frames_after_a_notch(system_reduced, smooth, false);
|
||||
assert!(moved[2] < notch * 0.75, "{smooth}, system reduced {system_reduced}: egui eases the notch: {moved:?}");
|
||||
for (system_reduced, effects) in EASED {
|
||||
let moved = frames_after_a_notch(system_reduced, effects, false);
|
||||
assert!(moved[2] < notch * 0.75, "{effects}, system reduced {system_reduced}: egui eases the notch: {moved:?}");
|
||||
assert!(
|
||||
(moved[39] - notch).abs() < 1.0,
|
||||
"{smooth}, system reduced {system_reduced}: easing changes how long a notch takes, not how far it goes: {moved:?}"
|
||||
"{effects}, system reduced {system_reduced}: easing changes how long a notch takes, not how far it goes: {moved:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_wheel_zooms_a_whole_step_at_once_unless_scrolling_is_smooth() {
|
||||
fn the_wheel_zooms_a_whole_step_at_once_with_animation_effects_off() {
|
||||
// ⌘/Ctrl with the wheel: egui eases the zoom as it eases scrolling.
|
||||
let step = frames_after_a_notch(true, "system", true)[0];
|
||||
assert!(step > 1.1, "the notch zoomed in: {step}");
|
||||
for (system_reduced, smooth) in AT_ONCE {
|
||||
let zooms = frames_after_a_notch(system_reduced, smooth, true);
|
||||
for (system_reduced, effects) in AT_ONCE {
|
||||
let zooms = frames_after_a_notch(system_reduced, effects, true);
|
||||
assert!(
|
||||
zooms.iter().all(|zoom| *zoom == step),
|
||||
"{smooth}, system reduced {system_reduced}: the whole step in the frame the notch arrives, and no more after it: {zooms:?}"
|
||||
"{effects}, system reduced {system_reduced}: the whole step in the frame the notch arrives, and no more after it: {zooms:?}"
|
||||
);
|
||||
}
|
||||
for (system_reduced, smooth) in EASED {
|
||||
let zooms = frames_after_a_notch(system_reduced, smooth, true);
|
||||
assert!(zooms[0] > 1.0 && zooms[0] < 1.0 + (step - 1.0) * 0.75, "{smooth}, system reduced {system_reduced}: egui eases the notch: {zooms:?}");
|
||||
for (system_reduced, effects) in EASED {
|
||||
let zooms = frames_after_a_notch(system_reduced, effects, true);
|
||||
assert!(
|
||||
zooms[0] > 1.0 && zooms[0] < 1.0 + (step - 1.0) * 0.75,
|
||||
"{effects}, system reduced {system_reduced}: egui eases the notch: {zooms:?}"
|
||||
);
|
||||
assert!(
|
||||
(zooms[39] / step - 1.0).abs() < 0.01,
|
||||
"{smooth}, system reduced {system_reduced}: easing changes how long a zoom step takes, not how far it goes: {zooms:?}"
|
||||
"{effects}, system reduced {system_reduced}: easing changes how long a zoom step takes, not how far it goes: {zooms:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
# PdfCraft roadmap: milestones and what's next
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (M2: short same-line redraws split before placement; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (M3: Windows' Animation effects setting is followed, and Preferences has Smooth scrolling; earlier: M10, #1028: OCR reads pictures on pages that have text, on request; earlier: direct Organize overview and page actions; localized shortcut labels, #496; alpha gate added; convert-and-share re-judged with the cross-app rule → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (M3: Windows' Animation effects setting is followed, and Preferences has Animation effects; earlier: M10, #1028: OCR reads pictures on pages that have text, on request; earlier: direct Organize overview and page actions; localized shortcut labels, #496; alpha gate added; convert-and-share re-judged with the cross-app rule → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-11 · **Change:** minor (M13/#971: bound per-page search match allocation; text cache byte budget and extraction warnings remain open) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (#594 paragraph drags validated in the Linux test environment; localized shortcut labels, #496; alpha gate added; convert-and-share re-judged with the cross-app rule → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (architectural paper controls from #599; M6: respect existing form-font encodings; retain source fonts on appearance fallback; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
@@ -79,7 +79,7 @@ total matches the parity estimate.
|
||||
| M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings |
|
||||
| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel<br>Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). |
|
||||
| M4 | Engine, history, save, organize | 100–180 | 95% | 8–15 | Done:<br>- command registry (menus, shortcuts and palette all use it);<br>- undo/redo; incremental, atomic and encrypted saves;<br>- autosave and crash recovery;<br>- organize, combine, extract, split and insert-from-file, with identical fonts and images stored once;<br>- CLI `edit/combine/extract/split`.<br>Done since: Organize opens directly into the page overview, with checkbox selection and per-page rotate/delete controls; Combine files in its own tab (sortable, resizable columns; folders; early warnings; unlocking protected files), bookmark editing, page labels (Number pages), CLI `run`, Set Page Boxes, Crop tool, Duplicate pages, Ctrl+A / Cmd+A to select all pages in Organize, Replace pages, bookmarks from tagged headings. Missing: page transitions, recovery on the web |
|
||||
| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel<br>Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). Renderer-creation errors return to the existing OpenGL startup fallback; a real-GPU regression covers errors and device reuse. The underlying x86 DX12 shader compiler failure remains unresolved; see `windows-renderer-startup.md`. With Windows' Animation effects turned off, the wheel scrolls and zooms without easing and egui's animations take no time (`motion.rs`; macOS, Linux and the browser aren't asked yet); Preferences ▸ Smooth scrolling (Use system setting, On, Off) decides the wheel's easing on every platform. |
|
||||
| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel<br>Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). Renderer-creation errors return to the existing OpenGL startup fallback; a real-GPU regression covers errors and device reuse. The underlying x86 DX12 shader compiler failure remains unresolved; see `windows-renderer-startup.md`. With Windows' Animation effects turned off, the wheel scrolls and zooms without easing and egui's animations take no time (`motion.rs`; macOS, Linux and the browser aren't asked yet); Preferences ▸ Animation effects (Use system setting, On, Off) decides it on every platform. |
|
||||
| M4 | Engine, history, save, organize | 100–180 | 95% | 8–15 | Done:<br>- command registry (menus, shortcuts and palette all use it);<br>- undo/redo; incremental, atomic and encrypted saves;<br>- autosave and crash recovery;<br>- organize, combine, extract, split and insert-from-file, with identical fonts and images stored once;<br>- CLI `edit/combine/extract/split`.<br>Done since: Combine files in its own tab (sortable, resizable columns; folders; early warnings; unlocking protected files), bookmark editing, page labels (Number pages), CLI `run`, Set Page Boxes, Crop tool, Duplicate pages, Ctrl+A / Cmd+A to select all pages in Organize, Replace pages, bookmarks from tagged headings. Missing: page transitions, recovery on the web |
|
||||
| M5 | Comments (all annotation types, XFDF) | 120–200 | 88% | 12–25 | Done: notes, highlight/underline/strikeout/squiggly, text boxes, ink, lines, arrows, rectangles, ovals, stamps (dynamic, Sign Here, Standard Business), with appearance streams; replies, status, checkmarks, locking, move/resize/restyle/delete; properties dialog; panel filter and sort; hide all; summaries (comments only); XFDF/FDF import and export (partial imports preserve existing reply and review-status links); flatten; Fill & Sign; agent tools.<br>Done since: polygons, connected lines, clouds, callouts, inserted text, colour and checkmark filters; image signature pointer previews, selection handles and live image movement/resizing with proportional corners and independent edge handles. Missing: custom stamps, replace-text proposals, summary layouts with the page |
|
||||
| M6 | Forms + JavaScript | 160–320 | 50% | 40–75 | Done: filling all field types with regenerated appearances; Prepare a form (every field type, multi-select/shared properties, move/resize/delete, Field Properties General/Appearance/Position/Options/Format/Validate/Calculate); Acrobat's AF format/keystroke/validate/calculate functions and simplified field notation run natively in Acrobat's event order; tab order; push-button actions; flatten; agent tools. Form data exchange (FDF, XFDF, XML, CSV, text) done.<br>Done since: JavaScript engine (boa, sandboxed) running custom keystroke/validate/calculate/format and button scripts, document JavaScripts, console. Missing: Actions tab, wider object model (annotations, layers, dialogs), XFA |
|
||||
@@ -142,7 +142,7 @@ M2 (renderer), M7 (content editing), M10 (OCR and Office), M11 (Preflight and st
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | M10: Word invoice export regressions (#773); estimates unchanged. |
|
||||
| 2026-10-12 | minor | M2: short same-line redraws split before placement; estimates unchanged. |
|
||||
| 2026-10-11 | minor | M3: Windows' Animation effects setting is followed: wheel scrolling and zooming without easing, egui's animations off. Preferences ▸ Smooth scrolling (Use system setting, On, Off) overrides it for the wheel. macOS, Linux and the browser aren't asked yet. Estimates unchanged. |
|
||||
| 2026-10-11 | minor | M3: Windows' Animation effects setting is followed: wheel scrolling and zooming without easing, no other animations. Preferences ▸ Animation effects (Use system setting, On, Off) overrides it. macOS, Linux and the browser aren't asked yet. Estimates unchanged. |
|
||||
| 2026-10-11 | minor | M13/#971: cap materialized text-search matches to the requested limit plus one continuation match. |
|
||||
| 2026-10-11 | minor | M7: #594 ordinary paragraph drags validated in the Linux test environment with source-loss baseline failures, UI/engine regressions, native save/reopen and all eight CI gates. Estimates unchanged. |
|
||||
| 2026-10-11 | minor | M4: direct Organize overview, checkbox selection and individual page actions. Estimates unchanged. |
|
||||
|
||||
Reference in new issue
Block a user