Task panes: Dock Left, Dock Right and Float run ui.paneDock for every pane but Styles

This commit is contained in:
aiongg
2026-10-24 23:43:01 +00:00
parent 4f071a5b88
commit f501f7cb5f
3 changed files with 66 additions and 5 deletions
+1
View File
@@ -1660,6 +1660,7 @@ impl WordApp {
self.canvas.want_focus = true;
json!({"pending": "contextMenu"})
}
"ui.paneDock" => return Some(panes::dock_command(self, p)),
"ui.collapseRibbon" => {
self.ui.ribbon_collapsed = !self.ui.ribbon_collapsed;
json!({"collapsed": self.ui.ribbon_collapsed})
+64 -4
View File
@@ -336,12 +336,12 @@ pub(crate) fn apply(app: &mut WordApp, request: PaneRequest<Pane>) {
PaneRequest::FloatRect(Pane::Styles, rect) => app.ui.styles_pane_float = Some(rect),
PaneRequest::Place(pane, place) => {
let dock = match place {
Place::Docked(Edge::Start) => SavedDock::Left,
Place::Docked(Edge::End) => SavedDock::Right,
Place::Floating => SavedDock::Float,
Place::Docked(Edge::Start) => "left",
Place::Docked(Edge::End) => "right",
Place::Floating => "float",
Place::Docked(Edge::Bottom) => return,
};
app.ui.pane_places.entry(pane.key().into()).or_default().dock = Some(dock);
let _ = app.run("ui.paneDock", json!({"pane": pane.key(), "dock": dock}));
}
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),
@@ -349,6 +349,37 @@ pub(crate) fn apply(app: &mut WordApp, request: PaneRequest<Pane>) {
}
}
/// `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`.
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 dock = match p.get("dock") {
None => None,
Some(v) => Some(match v.as_str() {
Some("left") => SavedDock::Left,
Some("right") => SavedDock::Right,
Some("float") => SavedDock::Float,
_ => return Err("`dock` must be left, right or float".into()),
}),
};
if pane == Pane::Styles {
let params = p.get("dock").map_or_else(|| json!({}), |d| json!({"dock": d}));
let result = app.run("view.stylesPaneDock", params)?;
return Ok(json!({"pane": key, "dock": result.get("dock")}));
}
if let Some(dock) = dock {
app.ui.pane_places.entry(key.into()).or_default().dock = Some(dock);
}
let name = match pane_state(app, pane).place {
Place::Docked(Edge::Start) => "left",
Place::Floating => "float",
Place::Docked(_) => "right",
};
Ok(json!({"pane": key, "dock": name}))
}
fn close(app: &mut WordApp, pane: Pane) {
match pane.close_command() {
Some(id) => {
@@ -1402,6 +1433,35 @@ mod tests {
assert!(odd.ui.pane_places.is_empty() && odd.session.view.nav_pane);
}
/// `ui.paneDock` docks or floats any pane and reads its place, as the pane's menu does
/// (#1756); Styles goes through `view.stylesPaneDock`; junk is refused and changes nothing.
#[test]
fn pane_dock_command_docks_floats_and_reads_each_pane() {
use craft_ui::panes::{Edge, Place};
use serde_json::json;
let mut app = crate::WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::from_text("")), crate::Services::default());
assert_eq!(app.run("ui.paneDock", json!({"pane": "navigation"})).unwrap(), json!({"pane": "navigation", "dock": "left"}));
assert_eq!(app.run("ui.paneDock", json!({"pane": "comments"})).unwrap(), json!({"pane": "comments", "dock": "right"}));
assert_eq!(app.run("ui.paneDock", json!({"pane": "navigation", "dock": "float"})).unwrap()["dock"], "float");
assert_eq!(super::pane_state(&app, Pane::Navigation).place, Place::Floating);
app.run("ui.paneDock", json!({"pane": "comments", "dock": "left"})).unwrap();
assert_eq!(super::pane_state(&app, Pane::Comments).place, Place::Docked(Edge::Start));
assert_eq!(app.run("ui.paneDock", json!({"pane": "styles", "dock": "float"})).unwrap(), json!({"pane": "styles", "dock": "float"}));
assert_eq!(app.session.view.styles_pane_dock, wordcraft_engine::StylesPaneDock::Floating);
assert!(!app.ui.pane_places.contains_key("styles"), "Styles keeps its place in the view state");
for junk in [
json!({}),
json!({"pane": "nope", "dock": "left"}),
json!({"pane": 3}),
json!({"pane": "navigation", "dock": "top"}),
json!({"pane": "navigation", "dock": 1}),
json!({"pane": "navigation", "dock": null}),
] {
assert!(app.run("ui.paneDock", junk.clone()).is_err(), "{junk}");
}
assert_eq!(super::pane_state(&app, Pane::Navigation).place, Place::Floating, "junk changed nothing");
}
/// #3032: Delete is greyed out for built-in styles in the Styles pane (the engine refuses
/// them — `crates/engine/src/cmd/para.rs`), while custom styles keep it enabled.
#[test]
+1 -1
View File
@@ -52,7 +52,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)…) |
| `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.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 |