mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-29 12:37:08 -06:00
Appearance: Sync with System keeps following macOS appearance changes after startup (#642)
apply_visuals pinned egui's theme preference to Light or Dark on every apply. egui syncs that to the native window (ViewportCommand::SetTheme), winit then gives the NSWindow an explicit Aqua/DarkAqua appearance, and a window with its own appearance no longer receives the system's ThemeChanged events, so Sync with System froze on the first frame's appearance. Our visuals now go into both of egui's styles (light and dark), and the preference is System while the appearance mode is Sync with System, so the window keeps following the OS; fixed Light / Dark modes still pin it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
This commit is contained in:
@@ -427,6 +427,7 @@ impl EffectcraftApp {
|
||||
self.ui.theme = theme::ThemeKind::from_name(p.appearance.resolved_theme(system)).unwrap_or_default();
|
||||
self.tokens = Tokens::from_prefs(p, self.ui.theme);
|
||||
theme::apply_visuals(ctx, &self.tokens);
|
||||
ctx.set_theme(theme::theme_preference(&p.appearance.appearance_mode, self.ui.theme.is_light()));
|
||||
let tips = p.general.show_tool_tips;
|
||||
ctx.all_styles_mut(|s| s.interaction.tooltip_delay = if tips { 0.5 } else { f32::INFINITY });
|
||||
// Settings ▸ Appearance ▸ UI Scale (#284). egui's own Ctrl+= / Ctrl+- / Ctrl+0 zoom stays
|
||||
@@ -1891,6 +1892,35 @@ mod appearance_tests {
|
||||
assert_eq!(app.ui.theme, ThemeKind::Dark);
|
||||
}
|
||||
|
||||
/// Sync with System leaves egui's theme preference at System, so egui never gives the native
|
||||
/// window an appearance of its own (on macOS that stops system appearance changes reaching
|
||||
/// the app, #642); egui's light and dark styles both carry the resolved theme. Fixed modes
|
||||
/// pin the window to their family.
|
||||
#[test]
|
||||
fn auto_mode_leaves_the_native_window_following_the_system() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = app();
|
||||
app.hooks.system_theme = Some(Box::new(|_| Some(egui::Theme::Light)));
|
||||
app.set_pref("appearance.appearanceMode", json!("auto")).unwrap();
|
||||
app.apply_prefs(&ctx);
|
||||
assert_eq!(app.ui.theme, ThemeKind::Light);
|
||||
assert_eq!(ctx.options(|o| o.theme_preference), egui::ThemePreference::System);
|
||||
for theme in [egui::Theme::Light, egui::Theme::Dark] {
|
||||
assert_eq!(ctx.style_of(theme).visuals.panel_fill, app.tokens.panel_bg, "{theme:?}");
|
||||
}
|
||||
// egui asks the window to follow the system, never to take Light or Dark.
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |_| {});
|
||||
out.textures_delta.clear();
|
||||
let cmds = out.viewport_output.values().flat_map(|v| v.commands.iter()).collect::<Vec<_>>();
|
||||
assert!(!cmds.iter().any(|c| matches!(c, egui::ViewportCommand::SetTheme(t) if t != &egui::SystemTheme::SystemDefault)), "{cmds:?}");
|
||||
for (mode, pref) in [("light", egui::ThemePreference::Light), ("dark", egui::ThemePreference::Dark)] {
|
||||
app.set_pref("appearance.appearanceMode", json!(mode)).unwrap();
|
||||
app.apply_prefs(&ctx);
|
||||
assert_eq!(ctx.options(|o| o.theme_preference), pref, "{mode}");
|
||||
assert_eq!(ctx.global_style().visuals.panel_fill, app.tokens.panel_bg, "{mode}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Picking a theme (`view.theme.*`, `ui.set {theme}`) fixes the mode to its family.
|
||||
#[test]
|
||||
fn picking_a_theme_fixes_its_mode() {
|
||||
|
||||
@@ -405,11 +405,20 @@ fn is_chinese(language: &str) -> bool {
|
||||
matches!(language, "zh-hans" | "zh-hant")
|
||||
}
|
||||
|
||||
/// egui's theme preference for an appearance mode showing a `light` (or dark) theme. Sync with
|
||||
/// System leaves it at System: a concrete preference makes egui set the native window's
|
||||
/// appearance, and on macOS a window with its own appearance no longer hears when the system's
|
||||
/// changes (#642). A fixed Light or Dark mode pins the window to match.
|
||||
pub fn theme_preference(appearance_mode: &str, light: bool) -> egui::ThemePreference {
|
||||
match appearance_mode {
|
||||
"auto" => egui::ThemePreference::System,
|
||||
_ if light => egui::ThemePreference::Light,
|
||||
_ => egui::ThemePreference::Dark,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_visuals(ctx: &egui::Context, t: &Tokens) {
|
||||
let light = t.kind.is_light();
|
||||
// egui keeps one style per light/dark theme and by default picks it by the system appearance;
|
||||
// pin it to ours so a fixed Dark or Light mode isn't swapped for egui's defaults.
|
||||
ctx.set_theme(if light { egui::ThemePreference::Light } else { egui::ThemePreference::Dark });
|
||||
let mut v = if light { Visuals::light() } else { Visuals::dark() };
|
||||
v.panel_fill = t.panel_bg;
|
||||
v.window_fill = t.panel_bg;
|
||||
@@ -444,8 +453,10 @@ pub fn apply_visuals(ctx: &egui::Context, t: &Tokens) {
|
||||
v.widgets.active.fg_stroke = Stroke::new(1.0, t.tab_text_active);
|
||||
v.widgets.open.bg_fill = t.hover;
|
||||
v.widgets.open.weak_bg_fill = t.hover;
|
||||
ctx.set_visuals(v);
|
||||
ctx.global_style_mut(|s| {
|
||||
// egui keeps one style per light/dark theme and picks it by its theme preference (see
|
||||
// [`theme_preference`]); both get ours, so whichever it picks, egui's defaults never show.
|
||||
ctx.all_styles_mut(|s| {
|
||||
s.visuals = v.clone();
|
||||
s.spacing.item_spacing = egui::vec2(6.0, 4.0);
|
||||
s.spacing.button_padding = egui::vec2(8.0, 3.0);
|
||||
s.spacing.interact_size.y = 22.0;
|
||||
|
||||
Reference in New Issue
Block a user