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)
This commit is contained in:
aiongg
2026-10-25 23:29:51 -04:00
committed by GitHub
parent 15090b9d81
commit 6dca584bbd
9 changed files with 154 additions and 23 deletions
Generated
+19 -6
View File
@@ -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",
+3 -3
View File
@@ -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" }
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+2 -2
View File
@@ -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 }
+5
View File
@@ -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<String, panes::SavedPane>,
/// 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<String>,
/// Headings whose sub-headings the Navigation pane hides (#1997); per session, not saved.
#[serde(skip)]
pub nav_collapsed: std::collections::HashSet<wordcraft_doc::Path>,
@@ -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(),
+121 -8
View File
@@ -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> {
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<Pane> {
let mut order: Vec<Pane> = 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<Pane>) {
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<Vec<String>, D::Error> {
let v = <Value as serde::Deserialize>::deserialize(d)?;
let mut keys: Vec<String> = 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<Pane> {
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<Pane> {
]
.into_iter()
.filter_map(|(pane, shown)| shown.then_some(pane))
.collect()
.collect::<Vec<_>>();
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<Pane>) {
}
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<Value, String> {
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<Value, String
_ => 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]
+1 -1
View File
@@ -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 |
+1 -1
View File
@@ -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.