diff --git a/apps/wordcraft/src/main.rs b/apps/wordcraft/src/main.rs index 2d54d04cc..94d05bb35 100644 --- a/apps/wordcraft/src/main.rs +++ b/apps/wordcraft/src/main.rs @@ -57,8 +57,7 @@ fn load_prefs(app: &mut WordApp) { && let Ok(bytes) = std::fs::read(&p) && let Ok(ui) = serde_json::from_slice::(&bytes) { - app.ui = ui; - app.ui.backstage = false; + app.apply_prefs(ui); } } @@ -70,7 +69,7 @@ fn save_prefs(app: &WordApp) { if let Some(dir) = p.parent() { let _ = std::fs::create_dir_all(dir); } - if let Ok(bytes) = serde_json::to_vec_pretty(&app.ui) { + if let Ok(bytes) = serde_json::to_vec_pretty(&app.prefs()) { let _ = std::fs::write(&p, bytes); } } diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index dff63a179..4b3d119b0 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -46,7 +46,7 @@ pub fn inspect(app: &mut WordApp, ctx: &egui::Context) -> Value { let r = ctx.content_rect(); let caret = crate::canvas::caret_screen(app).map(|(p, h)| json!({"x": p.x, "y": p.y, "h": h})); json!({ - "ui": app.ui, + "ui": app.prefs(), "view": app.session.view, "dialog": app.dialog.as_ref().map(|d| serde_json::to_value(d).unwrap_or_default()), "window": [r.width(), r.height()], diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index dbf71dd61..8d2995317 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -55,6 +55,8 @@ pub struct UiState { pub dark: bool, pub nav_tab: String, pub show_discord: bool, + /// User name (File › Options) for comments and tracked changes; empty keeps the default. + pub author: String, } impl Default for UiState { @@ -68,6 +70,7 @@ impl Default for UiState { dark: false, nav_tab: "headings".into(), show_discord: true, + author: String::new(), } } } @@ -130,6 +133,24 @@ impl WordApp { self } + /// Preferences to save between runs: the UI state plus the current user name. + pub fn prefs(&self) -> UiState { + let mut ui = self.ui.clone(); + ui.author = self.session.author.clone(); + ui + } + + /// Restore preferences saved by [`WordApp::prefs`]. + pub fn apply_prefs(&mut self, ui: UiState) { + self.ui = ui; + self.ui.backstage = false; + // The session owns the name from here on; `prefs` copies it back when saving. + let author = std::mem::take(&mut self.ui.author); + if !author.trim().is_empty() { + self.session.author = author; + } + } + /// Run a command; UI-level commands (`ui.*`) are handled here, the rest by the engine. pub fn run(&mut self, id: &str, params: Value) -> Result { if let Some(r) = self.ui_command(id, ¶ms) { @@ -513,4 +534,33 @@ mod tests { frame(Vec::new()); assert_eq!(ctx.zoom_factor(), 1.0); } + + fn app() -> WordApp { + WordApp::new(Session::new(wordcraft_doc::Document::new()), Services::default()) + } + + #[test] + fn user_name_survives_restart() { + let mut first = app(); + first.run("file.setAuthor", json!({"name": "Ada Lovelace"})).unwrap(); + let saved = serde_json::to_vec(&first.prefs()).unwrap(); + + let mut second = app(); + second.apply_prefs(serde_json::from_slice(&saved).unwrap()); + assert_eq!(second.session.author, "Ada Lovelace"); + + // A later rename is what gets saved next, not the name loaded at startup. + second.run("file.setAuthor", json!({"name": "Grace Hopper"})).unwrap(); + assert_eq!(second.prefs().author, "Grace Hopper"); + } + + #[test] + fn prefs_without_user_name_keep_default() { + let mut a = app(); + let default = a.session.author.clone(); + a.apply_prefs(serde_json::from_str(r#"{"dark": true, "backstage": true}"#).unwrap()); + assert_eq!(a.session.author, default); + assert!(a.ui.dark); + assert!(!a.ui.backstage); + } }