From 6dca584bbdb196107f0eaaecf1557ec544a74fcd Mon Sep 17 00:00:00 2001 From: aiongg <67543387+aiongg@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:29:51 -0400 Subject: [PATCH] Task panes: drop a pane outside or inside a docked pane to order a side (#4386) * Task panes: a pane dropped over a docked pane's outer or inner third docks outside or inside it, and the order is kept (craft-libs#357) * Pin craft-libs at b74f1b6 (pane order on an edge, craft-libs#357) * Pin craft-libs at b926b5c (craft-libs main) --- Cargo.lock | 25 +++++-- Cargo.toml | 6 +- NOTICE | 2 +- apps/wordcraft/Cargo.toml | 2 +- crates/ui-egui/Cargo.toml | 4 +- crates/ui-egui/src/lib.rs | 5 ++ crates/ui-egui/src/panes.rs | 129 +++++++++++++++++++++++++++++++++--- docs/control-protocol.md | 2 +- docs/shared-pdf.md | 2 +- 9 files changed, 154 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c9052a8c7..244e0f453 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1011,10 +1011,15 @@ dependencies = [ "libc", ] +[[package]] +name = "craft-fonts-build" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" + [[package]] name = "craft-i18n" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" dependencies = [ "fixed_decimal", "icu_decimal", @@ -1037,7 +1042,7 @@ dependencies = [ [[package]] name = "craft-pdf" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" dependencies = [ "krilla", "thiserror 2.0.21", @@ -1046,7 +1051,7 @@ dependencies = [ [[package]] name = "craft-print" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" dependencies = [ "craft-winprint-gdi", "miniz_oxide 0.9.1", @@ -1059,19 +1064,27 @@ dependencies = [ [[package]] name = "craft-ui" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" dependencies = [ + "craft-fonts-build", + "craft-i18n", "egui", + "epaint", + "js-sys", "log", "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", + "skrifa 0.44.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] name = "craft-update" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" dependencies = [ "chrono", "dunce", @@ -2830,7 +2843,7 @@ dependencies = [ [[package]] name = "krilla" version = "0.8.2" -source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +source = "git+https://github.com/storytold/craft-libs?rev=b926b5c03e256c86ca10934f58d03f628810b5b7#b926b5c03e256c86ca10934f58d03f628810b5b7" dependencies = [ "base64 0.22.1", "bumpalo", diff --git a/Cargo.toml b/Cargo.toml index b183f3a1f..fe7d7c9c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,8 +58,8 @@ wuff = "0.2.9" brotli-decompressor = "5" vello_cpu = "0.2" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif", "bmp", "tiff"] } -craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", version = "=0.1.0", default-features = false, features = ["write"] } -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", features = ["egui"] } +craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", version = "=0.1.0", default-features = false, features = ["write"] } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7", features = ["egui"] } quick-xml = "0.41" zip = { version = "3", default-features = false, features = ["deflate"] } # zlib streams (compressed metafile pictures in `.doc` drawings); already in the lock as a dependency of other crates. @@ -120,7 +120,7 @@ debug = 0 # Required by craft-pdf: the backend rejects writers that failed during unwinding. [patch.crates-io] -krilla = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" } +krilla = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } diff --git a/NOTICE b/NOTICE index a7462a69c..82e79a0a9 100644 --- a/NOTICE +++ b/NOTICE @@ -101,7 +101,7 @@ list (author, source, license) is in ATTRIBUTION.md. Rust dependencies (egui, eframe, vello_cpu, skrifa, harfrust, krilla, quick-xml, zip, image, …) are permissively licensed (MIT/Apache-2.0/BSD/ISC/Zlib) and fetched through crates.io or pinned Git sources. The craft-pdf writer and Krilla 0.8.2 backend are pinned to storytold/craft-libs revision -1e6f127844ec61b3b8afdcc2bb76e7cfe470153f. The backend retains its MIT OR Apache-2.0 licensing +b926b5c03e256c86ca10934f58d03f628810b5b7. The backend retains its MIT OR Apache-2.0 licensing and upstream bundled material; see that revision's licenses/pdf-writer/ and ATTRIBUTION.md. The backend patch prevents finalization during an unwind and rejects a writer after failed cleanup. WordCraft enables only writing; PDF import and its image decoders are not enabled. diff --git a/apps/wordcraft/Cargo.toml b/apps/wordcraft/Cargo.toml index 8e129d1aa..85227c169 100644 --- a/apps/wordcraft/Cargo.toml +++ b/apps/wordcraft/Cargo.toml @@ -73,7 +73,7 @@ windows = { version = "0.62", features = ["UI_ViewManagement", "System_UserProfi clipboard-win = { version = "5.4", features = ["std"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -craft-print = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" } +craft-print = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } rfd = { workspace = true } # Waits for an `rfd::AsyncFileDialog` on a background thread (the dialogs don't block the UI, #94); # rfd itself already depends on it. diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index 599fa540e..8add30555 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -9,12 +9,12 @@ description = "WordCraft egui front end (Word-style ribbon UI); swappable: nothi workspace = true [dependencies] -craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" } +craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } # Interface translations: lookup, fallback, plurals and system-locale matching over the craft-i18n # catalogs that `build.rs` embeds from `CRAFT_I18N_DIR` (storytold/craft-i18n, pinned in # `craft-i18n.rev`). Same craft-libs rev as craft-ui; it brings lazy per-locale parsing # (storytold/craft-libs#247), ICU4X plural rules and bidi isolates around inserted values. -craft-i18n = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" } +craft-i18n = { git = "https://github.com/storytold/craft-libs", rev = "b926b5c03e256c86ca10934f58d03f628810b5b7" } wordcraft-geom = { workspace = true, features = ["image"] } wordcraft-doc = { workspace = true } wordcraft-fonts = { workspace = true } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 61cf2448c..32949f317 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -304,6 +304,10 @@ pub struct UiState { /// Where each of the other task panes sits, by [`panes::Pane::key`] (#1756). #[serde(deserialize_with = "panes::lenient_places")] pub pane_places: std::collections::BTreeMap, + /// The task panes in the order the user put them, by [`panes::Pane::key`]: each edge shows its + /// panes in this order, the first outermost. Empty: the default order. + #[serde(deserialize_with = "panes::lenient_order", skip_serializing_if = "Vec::is_empty")] + pub pane_order: Vec, /// Headings whose sub-headings the Navigation pane hides (#1997); per session, not saved. #[serde(skip)] pub nav_collapsed: std::collections::HashSet, @@ -425,6 +429,7 @@ impl Default for UiState { styles_pane_float: None, styles_pane_width: None, pane_places: Default::default(), + pane_order: Vec::new(), nav_collapsed: Default::default(), show_discord: true, author: String::new(), diff --git a/crates/ui-egui/src/panes.rs b/crates/ui-egui/src/panes.rs index bf96e3887..5125dbb86 100644 --- a/crates/ui-egui/src/panes.rs +++ b/crates/ui-egui/src/panes.rs @@ -17,7 +17,7 @@ use wordcraft_engine::StylesPaneDock; use crate::WordApp; use crate::theme::{Tokens, regular, semibold}; -/// The task panes, in the order they open: each edge's first pane is outermost. +/// The task panes, in their default order: each edge's first pane is outermost. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Pane { /// SmartArt Design › Text Pane, while a SmartArt graphic WordCraft can edit is selected. @@ -94,6 +94,10 @@ impl Pane { Self::Comments, ]; + fn from_key(key: &str) -> Option { + Self::ALL.into_iter().find(|pane| pane.key() == key) + } + /// The command that closes the pane; Restrict Editing has none and closes in [`close`]. fn close_command(self) -> Option<&'static str> { match self { @@ -111,11 +115,45 @@ impl Pane { } } -/// The panes open now, from the session's view flags. +/// Every pane in the order the user left them (`paneOrder` in `ui.json`), then the panes that +/// list lacks in their default order. Each edge shows its panes in this order, the first outermost. +fn order(app: &WordApp) -> Vec { + let mut order: Vec = app.ui.pane_order.iter().filter_map(|key| Pane::from_key(key)).collect(); + for pane in Pane::ALL { + if !order.contains(&pane) { + order.push(pane); + } + } + order +} + +/// Put `pane` directly before `before` in the order, outside it on their edge (`None`: after +/// every pane). +fn move_before(app: &mut WordApp, pane: Pane, before: Option) { + let mut order = order(app); + order.retain(|p| *p != pane); + let at = before.and_then(|b| order.iter().position(|p| *p == b)).unwrap_or(order.len()); + order.insert(at, pane); + app.ui.pane_order = order.iter().map(|p| p.key().to_string()).collect(); +} + +/// `paneOrder` as saved: unknown and repeated keys are dropped, not the whole file. +pub(crate) fn lenient_order<'de, D: serde::Deserializer<'de>>(d: D) -> Result, D::Error> { + let v = ::deserialize(d)?; + let mut keys: Vec = Vec::new(); + for key in v.as_array().map(Vec::as_slice).unwrap_or_default().iter().take(Pane::ALL.len() * 4).filter_map(Value::as_str) { + if Pane::from_key(key).is_some() && !keys.iter().any(|k| k == key) { + keys.push(key.to_string()); + } + } + Ok(keys) +} + +/// The panes open now, from the session's view flags, in the user's order. pub fn open(app: &WordApp) -> Vec { let v = &app.session.view; let smart_art = v.smart_art_pane && wordcraft_engine::cmd::smart_art::selected_smart_art(&app.session).is_some(); - [ + let mut open = [ (Pane::SmartArtText, smart_art), (Pane::Navigation, v.nav_pane), (Pane::Clipboard, v.clipboard_pane), @@ -129,7 +167,10 @@ pub fn open(app: &WordApp) -> Vec { ] .into_iter() .filter_map(|(pane, shown)| shown.then_some(pane)) - .collect() + .collect::>(); + let order = order(app); + open.sort_by_key(|pane| order.iter().position(|p| p == pane).unwrap_or(usize::MAX)); + open } /// Where a task pane other than Styles sits, kept in `ui.json` (#1756). Styles keeps its place in @@ -345,16 +386,21 @@ pub(crate) fn apply(app: &mut WordApp, request: PaneRequest) { } PaneRequest::Resize(pane, width) => app.ui.pane_places.entry(pane.key().into()).or_default().width = Some(width), PaneRequest::FloatRect(pane, rect) => app.ui.pane_places.entry(pane.key().into()).or_default().float = Some(rect), + PaneRequest::Before(pane, before) => { + let _ = app.run("ui.paneDock", json!({"pane": pane.key(), "before": before.map(Pane::key)})); + } PaneRequest::Focus(_) => {} } } -/// `ui.paneDock {"pane": key, "dock"?: "left" | "right" | "float"}` (#1756): docks or floats a -/// task pane, as its menu's Dock Left, Dock Right and Float do; without `dock` it reads the -/// pane's place. `pane` is the pane's `panePlaces` key; Styles goes through `view.stylesPaneDock`. +/// `ui.paneDock {"pane": key, "dock"?: "left" | "right" | "float", "before"?: key | null}` (#1756): +/// docks or floats a task pane, as its menu's Dock Left, Dock Right and Float do; without `dock` +/// it reads the pane's place. `pane` is the pane's `panePlaces` key; Styles goes through +/// `view.stylesPaneDock`. `before` puts the pane directly before that pane in the order, outside +/// it on their edge, as a drop over a docked pane does; `null` puts it after every pane. pub(crate) fn dock_command(app: &mut WordApp, p: &Value) -> Result { let key = p.get("pane").and_then(Value::as_str).ok_or("`pane` must be a task pane's key, such as \"navigation\"")?; - let pane = Pane::ALL.into_iter().find(|pane| pane.key() == key).ok_or_else(|| format!("unknown task pane `{key}`"))?; + let pane = Pane::from_key(key).ok_or_else(|| format!("unknown task pane `{key}`"))?; let dock = match p.get("dock") { None => None, Some(v) => Some(match v.as_str() { @@ -364,6 +410,17 @@ pub(crate) fn dock_command(app: &mut WordApp, p: &Value) -> Result return Err("`dock` must be left, right or float".into()), }), }; + let before = match p.get("before") { + None => None, + Some(Value::Null) => Some(None), + Some(v) => match v.as_str().and_then(Pane::from_key) { + Some(before) if before != pane => Some(Some(before)), + _ => return Err("`before` must be another task pane's key or null".into()), + }, + }; + if let Some(before) = before { + move_before(app, pane, before); + } if pane == Pane::Styles { let params = p.get("dock").map_or_else(|| json!({}), |d| json!({"dock": d})); let result = app.run("view.stylesPaneDock", params)?; @@ -1336,6 +1393,62 @@ mod tests { assert_eq!(super::pane_state(h.state(), Pane::Navigation).place, docked, "the drag was cancelled"); } + /// With Navigation docked left, Comments dropped over Navigation's outer third docks left of + /// it, where the preview showed, and the order survives a restart; over Navigation's inner + /// third it goes back inside. + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn comments_dropped_over_navigations_outer_third_docks_left_of_it() { + use egui_kittest::kittest::Queryable; + + let mut app = crate::WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::from_text("")), crate::Services::default()); + app.run("view.navigationPane", serde_json::json!({"value": true})).unwrap(); + app.run("view.commentsPane", serde_json::json!({"value": true})).unwrap(); + let mut h = pane_harness(app); + // A docked pane's title bar starts at its panel's left edge. + let panel = |h: &egui_kittest::Harness<'static, crate::WordApp>, pane: Pane| h.get_by_label(&format!("Move Pane: {}", pane.title())).rect(); + let button = + |pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE }; + let drag = |h: &mut egui_kittest::Harness<'static, crate::WordApp>, to: egui::Pos2| { + let from = h.get_by_label("Move Pane: Comments").rect().center(); + h.input_mut().events.extend([egui::Event::PointerMoved(from), button(from, true)]); + h.run_steps(1); + h.input_mut().events.push(egui::Event::PointerMoved(from + egui::vec2(-8.0, 6.0))); + h.run_steps(1); + h.input_mut().events.push(egui::Event::PointerMoved(to)); + h.run_steps(1); + h.input_mut().events.push(button(to, false)); + h.run_steps(3); + }; + let nav = panel(&h, Pane::Navigation); + drag(&mut h, egui::pos2(nav.left() + 60.0, nav.center().y)); + let (comments, nav_after) = (panel(&h, Pane::Comments), panel(&h, Pane::Navigation)); + assert_eq!(comments.left(), nav.left(), "Comments is outermost"); + assert!(nav_after.left() > comments.right(), "Navigation sits inside it"); + assert_eq!(super::open(h.state()), vec![Pane::Comments, Pane::Navigation]); + + let saved = serde_json::to_vec(&h.state().prefs()).unwrap(); + let mut next = crate::WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), crate::Services::default()); + next.apply_prefs(serde_json::from_slice(&saved).unwrap()); + next.session.view.comments_pane = true; + assert_eq!(super::open(&next), vec![Pane::Comments, Pane::Navigation], "the order is kept"); + + let nav = panel(&h, Pane::Navigation); + let width = Pane::Navigation.default_state().size.unwrap(); + drag(&mut h, egui::pos2(nav.left() + width * 0.85, nav.center().y)); + assert_eq!(super::open(h.state()), vec![Pane::Navigation, Pane::Comments]); + assert!(panel(&h, Pane::Comments).left() > panel(&h, Pane::Navigation).right(), "back inside Navigation"); + + let mut junk = crate::WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::new()), crate::Services::default()); + junk.apply_prefs(serde_json::from_str(r#"{"paneOrder": ["comments", 3, "nope", "comments", "navigation"], "navPane": true}"#).unwrap()); + assert_eq!(junk.ui.pane_order, vec!["comments", "navigation"], "unknown and repeated keys are dropped"); + for bad in [serde_json::json!({"pane": "comments", "before": "comments"}), serde_json::json!({"pane": "comments", "before": 7})] { + assert!(junk.run("ui.paneDock", bad.clone()).is_err(), "{bad}"); + } + junk.run("ui.paneDock", serde_json::json!({"pane": "navigation", "before": null})).unwrap(); + assert_eq!(junk.ui.pane_order.last().map(String::as_str), Some("navigation")); + } + /// An F6 the user bound runs its command; plain F6 still moves to the panes when only /// Shift+F6 is bound. #[test] diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 513ef0e81..014966bf9 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -54,7 +54,7 @@ and set `WORDCRAFT_CONTROL_KEY` to that app's key. | Method | Params | Result | |---|---|---| -| `engine.execute` | `command`, `params` | the command's result (any id from `engine.commands`, including UI commands `ui.tab`, `ui.dialog` (`{"name": dialog, "part"?: table style part}`; `part` opens New / Modify Table Style (`newTableStyle` / `modifyTableStyle`) on that part: `wholeTable`, `headerRow`, `firstColumn`, `bandedRows`, `nwCell`… as `table.modifyStyle` names them), `ui.backstage`, `ui.zotero.*` (see `docs/zotero.md`), `ui.language` (`{"value": "auto"\|"en"\|"zh-hans"\|"zh-hant"\|"ja"\|"pt-br"\|"es"\|"uk"\|"sr"\|"sr-latn"\|"et"\|"tr"\|"ru"\|"pl"\|"de"\|"it"\|"cs"\|"hu"\|"fr"}`), `ui.theme` (`{"value": "system"\|"light"\|"dark"}`; `system` follows the OS appearance, light when it reports none), `ui.interfaceSize` (`{"value": "auto"}` or `{"percent": 50–300}`, clamped; no value reads it; `auto` keeps the system display scale, a percentage is absolute, so 100 is one screen pixel per point (#822); the result's `percent` is the size drawn at and `auto` whether it follows the system; document zoom stays `view.zoom`), `ui.animations` (`{"value": "system"\|"on"\|"off"}`; no value reads it; `system` follows the system's animation setting, which the desktop app reads on Windows, macOS, Linux and the BSDs and the web app from `prefers-reduced-motion`, and means on where the system doesn't say; the result's `enabled` is what the setting comes to and `system` what the system reports, `null` when it doesn't say), `ui.fileBrowser` (desktop: `{"purpose": "document"\|"picture"\|"recipients"\|"save"}` opens WordCraft's own file browser, for when the system file dialog doesn't show up, #576), `ui.saveAsTemplate` (File › Save As › Save as Word Template: the Save dialog for a `.dotx`, starting in the user templates folder; the result's `dir` is that folder, `null` on the web, #2852), `ui.caretAnimation` (`{"value": true\|false}`; no value reads it; off, the caret jumps and blinks on and off instead of gliding and fading), `ui.startScreen` (`{"value": true\|false}`; no value reads it; on, the default, the desktop and web apps start on File › Home when launched with nothing to open, #766), `ui.alignmentGuides` (`{"value": true\|false}`; no value reads it; on, the default, moving a picture or shape snaps it to the page, the margins and other objects, with guide lines while dragging; Alt moves freely), `ui.quickAccessToolbar` (`{"value": true\|false}`; no value toggles it; false takes the Quick Access Toolbar out of the title bar, as its menu's Hide Quick Access Toolbar does, and true brings it back, #1945), `ui.focusRibbon` (`{"value": true\|false}`; no value toggles it; in Focus, true drops the ribbon down over the page, as the bar's ⋯ does, and false puts it away, as a click on the page does; outside Focus it stays put away, #1678), `ui.contextMenu` (opens the page's context menu below the caret, as Shift+F10 does), `ui.paneDock` (`{"pane": "navigation"\|"clipboard"\|"styles"\|"styleInspector"\|"accessibility"\|"altText"\|"formatShape"\|"restrictEditing"\|"comments"\|"smartArtText", "dock"?: "left"\|"right"\|"float"}`; no `dock` reads it; docks or floats a task pane as its menu does, kept between runs; Styles is `view.stylesPaneDock`, #1756)…) | +| `engine.execute` | `command`, `params` | the command's result (any id from `engine.commands`, including UI commands `ui.tab`, `ui.dialog` (`{"name": dialog, "part"?: table style part}`; `part` opens New / Modify Table Style (`newTableStyle` / `modifyTableStyle`) on that part: `wholeTable`, `headerRow`, `firstColumn`, `bandedRows`, `nwCell`… as `table.modifyStyle` names them), `ui.backstage`, `ui.zotero.*` (see `docs/zotero.md`), `ui.language` (`{"value": "auto"\|"en"\|"zh-hans"\|"zh-hant"\|"ja"\|"pt-br"\|"es"\|"uk"\|"sr"\|"sr-latn"\|"et"\|"tr"\|"ru"\|"pl"\|"de"\|"it"\|"cs"\|"hu"\|"fr"}`), `ui.theme` (`{"value": "system"\|"light"\|"dark"}`; `system` follows the OS appearance, light when it reports none), `ui.interfaceSize` (`{"value": "auto"}` or `{"percent": 50–300}`, clamped; no value reads it; `auto` keeps the system display scale, a percentage is absolute, so 100 is one screen pixel per point (#822); the result's `percent` is the size drawn at and `auto` whether it follows the system; document zoom stays `view.zoom`), `ui.animations` (`{"value": "system"\|"on"\|"off"}`; no value reads it; `system` follows the system's animation setting, which the desktop app reads on Windows, macOS, Linux and the BSDs and the web app from `prefers-reduced-motion`, and means on where the system doesn't say; the result's `enabled` is what the setting comes to and `system` what the system reports, `null` when it doesn't say), `ui.fileBrowser` (desktop: `{"purpose": "document"\|"picture"\|"recipients"\|"save"}` opens WordCraft's own file browser, for when the system file dialog doesn't show up, #576), `ui.saveAsTemplate` (File › Save As › Save as Word Template: the Save dialog for a `.dotx`, starting in the user templates folder; the result's `dir` is that folder, `null` on the web, #2852), `ui.caretAnimation` (`{"value": true\|false}`; no value reads it; off, the caret jumps and blinks on and off instead of gliding and fading), `ui.startScreen` (`{"value": true\|false}`; no value reads it; on, the default, the desktop and web apps start on File › Home when launched with nothing to open, #766), `ui.alignmentGuides` (`{"value": true\|false}`; no value reads it; on, the default, moving a picture or shape snaps it to the page, the margins and other objects, with guide lines while dragging; Alt moves freely), `ui.quickAccessToolbar` (`{"value": true\|false}`; no value toggles it; false takes the Quick Access Toolbar out of the title bar, as its menu's Hide Quick Access Toolbar does, and true brings it back, #1945), `ui.focusRibbon` (`{"value": true\|false}`; no value toggles it; in Focus, true drops the ribbon down over the page, as the bar's ⋯ does, and false puts it away, as a click on the page does; outside Focus it stays put away, #1678), `ui.contextMenu` (opens the page's context menu below the caret, as Shift+F10 does), `ui.paneDock` (`{"pane": "navigation"\|"clipboard"\|"styles"\|"styleInspector"\|"accessibility"\|"altText"\|"formatShape"\|"restrictEditing"\|"comments"\|"smartArtText", "dock"?: "left"\|"right"\|"float", "before"?: pane\|null}`; no `dock` reads it; docks or floats a task pane as its menu does, kept between runs; Styles is `view.stylesPaneDock`, #1756; `before` puts the pane directly before that pane in the order, outside it on their edge, as a drop over a docked pane's outer third does, and `null` after every pane; the order is kept between runs)…) | | `engine.commands` | — | every command: id, label, location, shortcut, params, enabled | | `document.inspect` | `text?` | blocks (text, style, runs, lists, tables), parts, sections, selection, pages | | `ui.inspect` | — | UI state, view, dialog, window size, page rects on screen, caret, comment balloons (`balloons`: id + screen rect; `balloon`: the selected one), perf | diff --git a/docs/shared-pdf.md b/docs/shared-pdf.md index a3216940d..92596b59a 100644 --- a/docs/shared-pdf.md +++ b/docs/shared-pdf.md @@ -2,7 +2,7 @@ WordCraft uses `craft-pdf` with default features disabled and `write` enabled. Both that dependency and the required root Krilla patch use the immutable `storytold/craft-libs` revision -`1e6f127844ec61b3b8afdcc2bb76e7cfe470153f`. Rust 1.95 or newer is required. The revision must be +`b926b5c03e256c86ca10934f58d03f628810b5b7`. Rust 1.95 or newer is required. The revision must be available from the remote before a fresh checkout can resolve it. The shared writer checks a backend marker at compile time so an unpatched Krilla cannot replace the required backend.