UI: title-bar light/dark switch, File › Display theme, system appearance on first run (#194, #239)

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 <noreply@anthropic.com>
This commit is contained in:
vangel76
2026-10-11 12:43:47 +02:00
co-authored by Claude Opus 5.5
parent 9f896c1249
commit 838304accf
5 changed files with 68 additions and 2 deletions
+2
View File
@@ -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::<gridcraft_ui_egui::UiState>(&b)
+12
View File
@@ -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);
+25
View File
@@ -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()
+26
View File
@@ -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);
}
+3 -2
View File
@@ -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 |