diff --git a/crates/engine/src/autosave.rs b/crates/engine/src/autosave.rs index 0da3f3aa..f5e8e809 100644 --- a/crates/engine/src/autosave.rs +++ b/crates/engine/src/autosave.rs @@ -392,6 +392,7 @@ impl Preferences { self.playback.postroll_seconds = secs(self.playback.postroll_seconds); self.trim.large_trim_offset = self.trim.large_trim_offset.clamp(1, 1000); self.labels.sanitize_labels(); + self.appearance.sync_color_theme(); self.version = settings::PREFS_VERSION; } @@ -454,12 +455,13 @@ impl Preferences { let _: Preferences = serde_json::from_value(v.clone()).map_err(|e| format!("`{key}`: {e}"))?; settings::sanitize(&mut v, &Self::default().to_value()); let mut p: Preferences = serde_json::from_value(v).map_err(|e| format!("`{key}`: {e}"))?; - p.clamp(); - // Old clients that set the single Color Theme still pick the theme they name. + // Old clients that set the single Color Theme still pick the theme they name (before + // `clamp`, which brings `colorTheme` back in line with the mode and theme choices). if key == "appearance.colorTheme" { let theme = p.appearance.color_theme.clone(); p.appearance.select_theme(&theme); } + p.clamp(); *self = p; Ok(()) } diff --git a/crates/engine/src/settings.rs b/crates/engine/src/settings.rs index e72f7865..9f8adb1d 100644 --- a/crates/engine/src/settings.rs +++ b/crates/engine/src/settings.rs @@ -89,7 +89,10 @@ impl Default for GeneralPrefs { #[serde(rename_all = "camelCase", default)] pub struct AppearancePrefs { /// Legacy single "Color Theme": `darkest` | `dark` | `light`. Setting it (old settings files, - /// automation, View ▸ Appearance) selects that theme and fixes the mode to its family. + /// automation, View ▸ Appearance) selects that theme and fixes the mode to its family. It is + /// kept in line with the fields below ([`AppearancePrefs::sync_color_theme`]): the light or + /// dark theme of a fixed mode, and the dark theme in Auto (whose shown theme depends on the + /// system; read `appearanceMode` for that). pub color_theme: String, /// "Appearance Mode": `auto` (follow the operating system) | `dark` (default) | `light`. pub appearance_mode: String, @@ -140,6 +143,13 @@ impl AppearancePrefs { } } + /// Bring the legacy `color_theme` in line with the mode and theme choices, so clients that + /// still read it get the theme a fixed mode shows (the dark theme in Auto). + pub fn sync_color_theme(&mut self) { + let theme = self.shown_theme(None).to_string(); + self.color_theme = theme; + } + /// The mode after this one in the header button's cycle: Auto, Light, Dark, Auto… pub fn next_mode(&self) -> &'static str { match self.appearance_mode.as_str() { diff --git a/crates/engine/src/settings_tests.rs b/crates/engine/src/settings_tests.rs index 67d124de..6744a9c3 100644 --- a/crates/engine/src/settings_tests.rs +++ b/crates/engine/src/settings_tests.rs @@ -598,6 +598,17 @@ fn appearance_choices_validate_and_legacy_theme_still_selects() { assert_eq!(s.prefs.appearance.dark_theme, "dark", "the dark choice is kept"); set(&mut s, "appearance.colorTheme", json!("darkest")); assert_eq!((s.prefs.appearance.appearance_mode.as_str(), s.prefs.appearance.dark_theme.as_str()), ("dark", "darkest")); + // the legacy Color Theme follows the mode and theme choices, so clients reading it aren't stale + assert_eq!(s.prefs.appearance.color_theme, "darkest"); + set(&mut s, "appearance.appearanceMode", json!("light")); + assert_eq!(s.prefs.appearance.color_theme, "light"); + set(&mut s, "appearance.appearanceMode", json!("dark")); + set(&mut s, "appearance.darkTheme", json!("dark")); + assert_eq!(s.prefs.appearance.color_theme, "dark"); + set(&mut s, "appearance.appearanceMode", json!("auto")); + assert_eq!(s.prefs.appearance.color_theme, "dark", "Auto: the dark theme"); + set(&mut s, "appearance.colorTheme", json!("light")); + assert_eq!((s.prefs.appearance.appearance_mode.as_str(), s.prefs.appearance.color_theme.as_str()), ("light", "light")); // the header button's cycle let mut a = settings::AppearancePrefs::default(); let mut seen = Vec::new(); diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index cfa1e04e..cbd1c9b7 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -3435,8 +3435,13 @@ Username ユーザー名 Vertical Type Tool 縦書き文字ツール -# System appearance mode menu command. +# System appearance modes and theme cards. + Active 使用中 + Appearance Mode 外観モード + Dark Theme ダークテーマ + Light Theme ライトテーマ Next Appearance Mode 次の外観モード + Sync with system システムと同期 Go to previous keyframe 前のキーフレームへ移動 Go to next keyframe 次のキーフレームへ移動 Add keyframe キーフレームを追加 diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 2e4fd527..6fa303f4 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -216,6 +216,7 @@ New Створити New Layer Новий шар New Project Panel Нова панель проєкту + Next Appearance Mode Наступний режим вигляду Next in Sequence Наступний у секвенції Next in Track Наступний на доріжці Normalize Mix Track… Нормалізувати доріжку міксу… diff --git a/crates/ui-egui/tests/settings_ui.rs b/crates/ui-egui/tests/settings_ui.rs index e4b9eff3..3d19aaaf 100644 --- a/crates/ui-egui/tests/settings_ui.rs +++ b/crates/ui-egui/tests/settings_ui.rs @@ -251,9 +251,12 @@ fn view_menu_and_settings_name_each_color_theme_alike() { let menu = d.ok("ui.menu.list", json!({})); let menu_label = |id: &str| menu.as_array().unwrap().iter().find(|m| m["id"] == id).unwrap()["label"].as_str().unwrap().to_string(); d.menu("app.settings.appearance"); - d.click("settings.appearance.colorTheme"); - for (menu_id, pref) in [("view.theme.dark", "darkest"), ("view.theme.medium", "dark"), ("view.theme.light", "light")] { - let id = format!("settings.appearance.colorTheme.{pref}"); + // the theme cards' choices (the single Color Theme is no longer drawn) + for (menu_id, id) in [ + ("view.theme.dark", "settings.appearance.darkTheme.darkest"), + ("view.theme.medium", "settings.appearance.darkTheme.dark"), + ("view.theme.light", "settings.appearance.lightTheme.light"), + ] { let found = d.ok("ui.elements", json!({"prefix": id})); let choice = found.as_array().unwrap().iter().find(|e| e["id"] == json!(id)).unwrap(); assert_eq!(menu_label(menu_id), choice["label"].as_str().unwrap(), "{menu_id} and {id} name the same theme"); diff --git a/docs/project-files.md b/docs/project-files.md index c8827150..5c1b08fd 100644 --- a/docs/project-files.md +++ b/docs/project-files.md @@ -215,7 +215,7 @@ opens the dialog on a page. | Category | Takes effect | |---|---| | General | At Startup (Show Home = demo project, Open Most Recent, empty project; recent projects are remembered on open/save), Show Tool Tips | -| Appearance | Color Theme (Darkest / Dark / Light; View ▸ Appearance writes it too), highlight colour, accessible contrast | +| Appearance | Appearance Mode `appearanceMode` (`auto` = Sync with system / `dark` (default) / `light`; the header button and View ▸ Appearance ▸ Next Appearance Mode cycle it), Dark Theme `darkTheme` (`darkest` (default) / `dark`), Light Theme `lightTheme` (`light`), highlight colour, accessible contrast. `colorTheme` (Darkest / Dark / Light) is legacy input: setting it (older files and clients, View ▸ Appearance ▸ a theme) selects that theme and fixes the mode to its family; a file without `appearanceMode` migrates to its theme's fixed mode. It is kept in line with the choices: the theme a fixed mode shows, the dark theme in Auto | | Audio | Automatch Time, Large Volume Adjustment, automation keyframe thinning (linear, minimum time) | | Audio Hardware | device class (cpal host), output device, I/O buffer size, sample rate, force document rate, Output Mapping (programme L/R → device channels) | | Auto Save | the auto-save ring and the crash-recovery journal |