From 838304accf0dd05a585669c7cef731ecb8682d5c Mon Sep 17 00:00:00 2001 From: vangel76 Date: Sun, 11 Oct 2026 12:43:47 +0200 Subject: [PATCH] =?UTF-8?q?UI:=20title-bar=20light/dark=20switch,=20File?= =?UTF-8?q?=20=E2=80=BA=20Display=20theme,=20system=20appearance=20on=20fi?= =?UTF-8?q?rst=20run=20(#194,=20#239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The theme could only be chosen in the View tab's Display theme box, and the app always started light. Now: - a title-bar button switches light/dark in one click (view.darkMode); right-click offers System / Light / Dark; - File › Display theme offers the same three (Excel keeps it under File › Account › Office Theme); - the desktop app follows the system appearance until a theme is chosen and saved in ui.json (UiState's default stays light for tests). Co-Authored-By: Claude Opus 5.5 --- apps/gridcraft/src/main.rs | 2 ++ crates/ui-egui/src/icons.rs | 12 ++++++++++++ crates/ui-egui/src/ribbon.rs | 25 +++++++++++++++++++++++++ crates/ui-egui/tests/system_theme.rs | 26 ++++++++++++++++++++++++++ docs/ui-parity.md | 5 +++-- 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/apps/gridcraft/src/main.rs b/apps/gridcraft/src/main.rs index 3c73a9541..db252b518 100644 --- a/apps/gridcraft/src/main.rs +++ b/apps/gridcraft/src/main.rs @@ -84,6 +84,8 @@ fn load_prefs(app: &mut SheetApp) { } // First run (or a ui.json without a usable language): follow the desktop's language. app.ui.language = gridcraft_ui_egui::i18n::Language::system(); + // ...and its light or dark appearance (#239), until a theme is chosen and saved in ui.json. + app.ui.system_theme = true; let Some(dir) = config_dir() else { return }; if let Ok(b) = std::fs::read(dir.join("ui.json")) && let Ok(ui) = serde_json::from_slice::(&b) diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs index b2079c821..87bde2d52 100644 --- a/crates/ui-egui/src/icons.rs +++ b/crates/ui-egui/src/icons.rs @@ -108,6 +108,8 @@ pub enum Icon { Share, Search, Settings, + /// Light/dark appearance: a circle, its right half filled. + Appearance, Sheet, Book, Folder, @@ -596,6 +598,16 @@ pub fn paint(p: &Painter, rect: Rect, icon: Icon, c: Color32) { g.circle(8.0, 8.0, 5.0, None, Some(c)); g.circle(8.0, 8.0, 2.0, Some(c), None); } + Icon::Appearance => { + g.circle(8.0, 8.0, 5.5, None, Some(c)); + let half: Vec<(f32, f32)> = (0..=12) + .map(|i| { + let a = std::f32::consts::PI * (i as f32 / 12.0 - 0.5); + (8.0 + 5.5 * a.cos(), 8.0 + 5.5 * a.sin()) + }) + .collect(); + g.poly(&half, c); + } Icon::Book => { g.rect(2.0, 2.0, 12.0, 12.0, Some(GREEN), None); g.rect(4.0, 4.0, 8.0, 8.0, Some(Color32::WHITE), None); diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index 264479a8c..148fd8ee8 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -113,6 +113,16 @@ pub fn title_bar(app: &mut SheetApp, ui: &mut Ui) { if icon_button(ui, Icon::Comment, t.text_dim, "Comments", vec2(28.0, 26.0)).clicked() { app.open_dialog("comments", json!({})); } + // Quick light/dark switch (#194); right-click offers System as well. The same + // choice lives in File › Display theme and the View tab. + let lang = app.ui.language; + let theme = icon_button(ui, Icon::Appearance, t.text_dim, "Dark Mode", vec2(28.0, 26.0)); + let dark = ui.ctx().theme() == egui::Theme::Dark; + theme.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, true, dark, lang.tr("Dark Mode"))); + if theme.clicked() { + act(app, "view.darkMode", json!({})); + } + theme.context_menu(|ui| theme_choices(app, ui)); }); }); }); @@ -230,9 +240,24 @@ fn file_menu(app: &mut SheetApp, ui: &mut Ui) { ui.close(); } } + // Excel keeps the theme under File › Account › Office Theme (#194). + ui.separator(); + ui.menu_button(app.ui.language.tr("Display theme"), |ui| theme_choices(app, ui)); }); } +/// System / Light / Dark, as in the View tab's Display theme box (`view.theme`). +fn theme_choices(app: &mut SheetApp, ui: &mut Ui) { + let lang = app.ui.language; + let mode = app.ui.theme_mode(); + for (value, label) in [("system", "System"), ("light", "Light"), ("dark", "Dark")] { + if ui.radio(mode == value, lang.tr(label)).clicked() { + act(app, "view.theme", json!({"mode": value})); + ui.close(); + } + } +} + fn contextual_tabs(app: &SheetApp) -> Vec<&'static str> { let mut v = Vec::new(); if let Some(d) = app.session.active() diff --git a/crates/ui-egui/tests/system_theme.rs b/crates/ui-egui/tests/system_theme.rs index 0d2cdea32..a0a6b007d 100644 --- a/crates/ui-egui/tests/system_theme.rs +++ b/crates/ui-egui/tests/system_theme.rs @@ -162,3 +162,29 @@ fn invalid_mode_preserves_preferences_and_legacy_overrides_leave_system_mode() { system_theme(&mut h, Some(Theme::Dark)); assert_appearance(&h, false); } + +#[test] +fn title_bar_switch_and_file_menu_choose_the_theme() { + // #194: one click in the title bar switches light/dark; File › Display theme offers all three. + let mut h = harness(UiState::default()); + system_theme(&mut h, Some(Theme::Light)); + assert_appearance(&h, false); + h.get_by_role_and_label(egui::accesskit::Role::CheckBox, "Dark Mode").click(); + h.run_steps(2); + assert_eq!(preferences(&h)["dark"], true); + assert_eq!(preferences(&h)["systemTheme"], false); + assert_appearance(&h, true); + h.get_by_role_and_label(egui::accesskit::Role::CheckBox, "Dark Mode").click(); + h.run_steps(2); + assert_appearance(&h, false); + + h.get_by_label("File").click(); + h.run_steps(3); + h.get_by_label("Display theme ⏵").click(); + h.run_steps(3); + h.get_by_label("System").click(); + h.run_steps(3); + assert_eq!(preferences(&h)["systemTheme"], true); + system_theme(&mut h, Some(Theme::Dark)); + assert_appearance(&h, true); +} diff --git a/docs/ui-parity.md b/docs/ui-parity.md index e85bca103..e592cb77f 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (created from a source audit and the issue tracker) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** trivial (theme switch, #194) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 Interaction, controls and feel, against Excel. Part of [target-app-parity.md](target-app-parity.md). **UI/UX fidelity: ~55% ready, 50–80 h** (estimated). Ribbon command coverage is measured @@ -23,7 +23,7 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes | Status bar: Sum/Count/Average and more, view buttons, zoom | yes | yes | customizable statistics partial | | Task panes (Format Chart, Comments, Watch, Selection, PivotTable fields) | yes | yes | element-level Format pane for shapes and pictures missing | | Backstage (File) view: Info, New with templates, Open recent, Export, Options | yes | partial | Options dialog and template gallery missing | -| Dark mode, system appearance | yes | yes | done (10-09) | +| Dark mode, system appearance | yes | yes | done (10-09); follows the system on first run, title-bar switch and File › Display theme (#194, #239) | | Page Layout view (paper view with rulers and header editing) | yes | **no** (Page Break Preview only) | 8–12 h | | Multiple windows per workbook | yes | no (#162) | see [hardware-parity.md](hardware-parity.md) | @@ -91,5 +91,6 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12. | Date | Change | Summary | |---|---|---| +| 2026-10-11 | trivial | Theme: title-bar light/dark switch, File › Display theme, system appearance on first run (#194, #239) | | 2026-10-10 | minor | Chart resize bug (#190) | | 2026-10-10 | major | Created: chrome, grid, keyboard, dialogs, accessibility; key tips, Page Layout view and accessibility tree flagged |