mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-25 10:57:14 -06:00
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
117 lines
6.1 KiB
Rust
117 lines
6.1 KiB
Rust
//! 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}))
|
||
}
|