Files
wordcraft/crates/engine/src/cmd/view.rs
T
2026-10-12 00:34:30 -04:00

117 lines
6.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! View tab and status bar: views, zoom, show/hide, panes.
use serde_json::{Value, json};
use wordcraft_layout::ViewMode;
use super::sel_result;
use crate::{CmdError, CmdResult, CommandSpec, Session, p};
fn toggle(s: &mut Session, v: &Value, f: fn(&mut crate::ViewState) -> &mut bool) -> CmdResult {
let cur = *f(&mut s.view);
*f(&mut s.view) = p::bool(v, "value").unwrap_or(!cur);
Ok(json!({"value": *f(&mut s.view)}))
}
pub fn specs() -> Vec<CommandSpec> {
vec![
CommandSpec::new("view.marks", "Show/Hide ¶", "Home › Paragraph", |s, v| {
let r = toggle(s, v, |x| &mut x.marks);
s.relayout();
r
})
.key("Mod+Shift+8 / Mod+8")
.pure(),
CommandSpec::new("view.printLayout", "Print Layout", "View › Views", |s, _| mode(s, ViewMode::Print, false)).key("Mod+Alt+P").pure(),
CommandSpec::new("view.webLayout", "Web Layout", "View › Views", |s, _| mode(s, ViewMode::Web, false)).pure(),
CommandSpec::new("view.draft", "Draft", "View › Views", |s, _| mode(s, ViewMode::Draft, false)).key("Mod+Alt+N").pure(),
// The body as an outline of its headings, with the Outlining tab (`outline.*`).
CommandSpec::new("view.outline", "Outline", "View › Views", |s, _| mode(s, ViewMode::Outline, false)).key("Mod+Alt+O").pure(),
CommandSpec::new("view.readMode", "Read Mode", "View › Views", |s, _| mode(s, ViewMode::Print, true)).pure(),
// Print Layout: hide the white space (margins, headers, footers) between pages. Screen only.
CommandSpec::new("view.hideWhiteSpace", "Hide White Space", "View › Show", |s, v| toggle(s, v, |x| &mut x.hide_white_space))
.params(r#"{"value"?: bool}"#)
.pure(),
CommandSpec::new("view.focus", "Focus", "View › Immersive", |s, v| toggle(s, v, |x| &mut x.focus_mode)).pure(),
CommandSpec::new("view.ruler", "Ruler", "View › Show", |s, v| toggle(s, v, |x| &mut x.ruler)).pure(),
// The drawing grid over the page (screen only; never printed or exported).
CommandSpec::new("view.gridlines", "Gridlines", "View › Show", |s, v| toggle(s, v, |x| &mut x.gridlines))
.params(r#"{"value"?: bool}"#)
.pure(),
// Outlines of table cells (screen only; never printed or exported).
CommandSpec::new("table.viewGridlines", "View Gridlines", "Table Layout › Table", |s, v| toggle(s, v, |x| &mut x.table_gridlines))
.params(r#"{"value"?: bool}"#)
.pure(),
CommandSpec::new("view.navigationPane", "Navigation Pane", "View › Show", |s, v| toggle(s, v, |x| &mut x.nav_pane)).pure(),
CommandSpec::new("view.zoom", "Zoom", "View › Zoom", zoom)
.params(r#"{"value": percent (10-500) | "pageWidth" | "onePage" | "multiplePages"}"#)
.pure(),
CommandSpec::new("view.zoom100", "100%", "View › Zoom", |s, _| zoom(s, &json!({"value": 100}))).pure(),
CommandSpec::new("view.zoomIn", "Zoom In", "View › Zoom", |s, _| {
let z = ((s.view.zoom * 10.0).round() / 10.0 + 0.1).min(5.0);
zoom(s, &json!({"value": z * 100.0}))
})
.pure(),
CommandSpec::new("view.zoomOut", "Zoom Out", "View › Zoom", |s, _| {
let z = ((s.view.zoom * 10.0).round() / 10.0 - 0.1).max(0.1);
zoom(s, &json!({"value": z * 100.0}))
})
.pure(),
CommandSpec::new("view.onePage", "One Page", "View › Zoom", |s, _| zoom(s, &json!({"value": "onePage"}))).pure(),
CommandSpec::new("view.multiplePages", "Multiple Pages", "View › Zoom", |s, _| zoom(s, &json!({"value": "multiplePages"}))).pure(),
CommandSpec::new("view.pageWidth", "Page Width", "View › Zoom", |s, _| zoom(s, &json!({"value": "pageWidth"}))).pure(),
CommandSpec::new("view.darkMode", "Switch Modes", "View › Dark Mode", |s, v| toggle(s, v, |x| &mut x.dark_mode)).pure(),
CommandSpec::new("view.stylesPane", "Styles Pane", "Home › Styles", |s, v| toggle(s, v, |x| &mut x.styles_pane)).pure(),
CommandSpec::new("view.commentsPane", "Comments Pane", "Review › Comments", |s, v| toggle(s, v, |x| &mut x.comments_pane)).pure(),
CommandSpec::new("view.accessibilityPane", "Accessibility Pane", "Review › Accessibility", |s, v| {
toggle(s, v, |x| &mut x.accessibility_pane)
})
.params(r#"{"value"?: bool}"#)
.pure(),
CommandSpec::new("view.altTextPane", "Alt Text Pane", "Review › Accessibility", |s, v| toggle(s, v, |x| &mut x.alt_text_pane))
.params(r#"{"value"?: bool}"#)
.pure(),
CommandSpec::new("view.newWindow", "New Window", "View › Window", |s, _| {
s.ui_requests.push(json!({"open": "newWindow"}));
sel_result(s)
})
.pure(),
CommandSpec::new("view.split", "Split", "View › Window", |s, _| {
s.ui_requests.push(json!({"toggle": "split"}));
sel_result(s)
})
.key("Mod+Alt+S")
.pure(),
CommandSpec::new("view.state", "View State", "View", |s, _| serde_json::to_value(&s.view).map_err(|e| CmdError::Failed(e.to_string())))
.pure(),
]
}
fn mode(s: &mut Session, m: ViewMode, read: bool) -> CmdResult {
s.view.mode = m;
s.view.read_mode = read;
s.relayout();
Ok(json!({"mode": m, "readMode": read}))
}
fn zoom(s: &mut Session, v: &Value) -> CmdResult {
match v.get("value") {
Some(Value::String(f)) if ["pageWidth", "onePage", "multiplePages", "textWidth"].contains(&f.as_str()) => {
s.view.fit = f.clone();
s.view.multi_page = f == "multiplePages";
}
Some(x) => {
let pct = x.as_f64().ok_or_else(|| CmdError::Params("zoom percent or fit mode".into()))? as f32;
if !(10.0..=500.0).contains(&pct) {
return Err(CmdError::Params("zoom must be 10–500%".into()));
}
s.view.zoom = pct / 100.0;
s.view.fit.clear();
s.view.multi_page = false;
}
None => {
s.ui_requests.push(json!({"open": "zoom"}));
}
}
Ok(json!({"zoom": s.view.zoom, "fit": s.view.fit}))
}