diff --git a/crates/ui-egui/src/backstage.rs b/crates/ui-egui/src/backstage.rs index 922baabc5..9a61d5b73 100644 --- a/crates/ui-egui/src/backstage.rs +++ b/crates/ui-egui/src/backstage.rs @@ -969,9 +969,9 @@ fn options(app: &mut SheetApp, ui: &mut Ui) { } ui.add_space(18.0); section(ui, lang, "Options|Save"); - let mut autosave = app.grid.autosave(); + let mut autosave = app.ui.autosave; if ui.checkbox(&mut autosave, lang.tr("Save .xlsx workbooks automatically after every change")).changed() { - app.grid.toggle_autosave(); + app.toggle_autosave(); } let Some(calc) = app.session.active().map(|d| d.wb.calc.clone()) else { return }; ui.add_space(18.0); diff --git a/crates/ui-egui/src/grid.rs b/crates/ui-egui/src/grid.rs index 106bc6684..f10d83430 100644 --- a/crates/ui-egui/src/grid.rs +++ b/crates/ui-egui/src/grid.rs @@ -119,7 +119,6 @@ pub struct GridState { pub glide: crate::selection_glide::SelectionGlide, pub last_click: Option<(CellRef, f64)>, pub hover_cell: Option, - pub autosave: bool, pub last_autosave: f64, pub last_border: String, pub last_fill: String, @@ -179,15 +178,6 @@ impl WheelZoom { } } -impl GridState { - pub fn autosave(&self) -> bool { - self.autosave - } - pub fn toggle_autosave(&mut self) { - self.autosave = !self.autosave; - } -} - /// Geometry of the grid for one frame (handles zoom and frozen panes). #[derive(Clone)] pub struct Geo { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index f549aec2b..977e4b26b 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -60,6 +60,9 @@ pub struct UiState { /// Enable AutoComplete for cell values (File ▸ Options ▸ Editing): typed text is completed /// from the one matching entry in the same column. pub autocomplete: bool, + /// AutoSave (the title-bar switch): save saved-to-file .xlsx workbooks shortly after every + /// change. A preference like the rest of this struct — it stays on across restarts (#1901). + pub autosave: bool, pub recent: Vec, /// One-shot keytip request for a ribbon control: set when a keytip action resolves to /// [`keytips::Action::Control`], consumed (and cleared) by the control's render site. @@ -103,6 +106,7 @@ impl Default for UiState { status_bar: true, animations: true, autocomplete: true, + autosave: false, recent: vec![], keytip_invoke: None, language: i18n::Language::En, @@ -1128,6 +1132,12 @@ impl SheetApp { } } + /// Flip AutoSave (title-bar switch / Options ▸ Save). Persisted with the other UI + /// preferences, so it stays as the user left it across restarts (#1901). + pub fn toggle_autosave(&mut self) { + self.ui.autosave = !self.ui.autosave; + } + /// Lays out the whole window. pub fn ui(&mut self, ui: &mut egui::Ui) { let ctx = ui.ctx().clone(); @@ -1190,7 +1200,7 @@ impl SheetApp { } self.perf.last = now; // AutoSave: save workbooks that have a file shortly after they change. - if self.grid.autosave + if self.ui.autosave && self.editor.is_none() && self.text_box_editor.is_none() && now - self.grid.last_autosave > 2000.0 diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index a0ab5a339..ebe8d66a1 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -53,14 +53,14 @@ pub fn title_bar(app: &mut SheetApp, ui: &mut Ui) { ui.add_space(TITLE_LEFT_PAD); // AutoSave toggle (saves after each change when the workbook has a file). ui.label(egui::RichText::new("AutoSave").font(theme::ui_font(12.5)).color(t.text_dim)); - let on = app.grid.autosave(); + let on = app.ui.autosave; let (r, resp) = ui.allocate_exact_size(vec2(30.0, 16.0), Sense::click()); ui.painter().rect_filled(r, 8.0, if on { t.accent } else { Color32::TRANSPARENT }); ui.painter().rect_stroke(r, 8.0, Stroke::new(1.0, if on { t.accent } else { t.text_dim }), StrokeKind::Inside); let knob = if on { pos2(r.right() - 8.0, r.center().y) } else { pos2(r.left() + 8.0, r.center().y) }; ui.painter().circle_filled(knob, 5.0, if on { Color32::WHITE } else { t.text_dim }); if resp.on_hover_text("AutoSave: save after every change (workbooks saved to a file)").clicked() { - app.grid.toggle_autosave(); + app.toggle_autosave(); } ui.add_space(6.0); let __k = icon_button(ui, Icon::Home, t.text_dim, "Home", vec2(26.0, ch)); diff --git a/crates/ui-egui/tests/autosave_persist.rs b/crates/ui-egui/tests/autosave_persist.rs new file mode 100644 index 000000000..50f3d3b05 --- /dev/null +++ b/crates/ui-egui/tests/autosave_persist.rs @@ -0,0 +1,26 @@ +//! #1901: AutoSave is a preference — it must survive a restart like the other UI settings. + +use gridcraft_engine::Session; +use gridcraft_ui_egui::SheetApp; + +#[test] +fn autosave_survives_a_restart() { + let mut app = SheetApp::new(Session::new(), Default::default()); + assert!(!app.ui.autosave, "off by default"); + app.toggle_autosave(); + assert!(app.ui.autosave, "the toggle flips the persisted flag"); + // What the desktop app writes to ui.json, and reads back on the next launch. + let saved = serde_json::to_string(&app.ui).unwrap(); + let restored: gridcraft_ui_egui::UiState = serde_json::from_str(&saved).unwrap(); + assert!(restored.autosave, "a saved ui.json restores AutoSave on (#1901)"); + app.toggle_autosave(); + assert!(!app.ui.autosave, "toggling back persists off too"); +} + +#[test] +fn an_old_ui_json_without_autosave_still_loads() { + // Files saved by earlier versions have no autosave key; serde(default) reads them as off. + let restored: gridcraft_ui_egui::UiState = serde_json::from_str(r#"{"dark": true}"#).unwrap(); + assert!(!restored.autosave); + assert!(restored.dark, "the rest of the old file still applies"); +}