diff --git a/compositor/src/config.rs b/compositor/src/config.rs index e2a4c52..34a40c1 100644 --- a/compositor/src/config.rs +++ b/compositor/src/config.rs @@ -94,16 +94,13 @@ impl Config { pub(crate) fn resolved_theme_settings( theme: &ferese_config::theme::ResolvedTheme, - inactive_dim: InactiveDimSettings, ) -> Result { let config = Self { theme: serde_json::from_value(serde_json::to_value(&theme.tokens).unwrap()) .expect("resolved theme matches runtime schema"), ..Self::default() }; - let mut settings = config.theme_settings()?; - settings.inactive_dim = inactive_dim; - Ok(settings) + config.theme_settings() } pub(crate) fn runtime_config(&self) -> Result { @@ -126,6 +123,7 @@ impl Config { bindings, window_rules: self.window_rules()?, theme_settings: self.theme_settings()?, + inactive_dim: self.inactive_dim_settings()?, default_column_width: self.default_column_width()?, scrolling_focus_strategy: self.scrolling_focus_strategy(), column_width_presets: self.width_presets()?, @@ -622,11 +620,6 @@ mod tests { bar_background_color: RgbaColor([17.0 / 255.0, 24.0 / 255.0, 33.0 / 255.0, 1.0,]), text_primary_color: RgbaColor([244.0 / 255.0, 247.0 / 255.0, 251.0 / 255.0, 1.0]), shell_opacity: 0.78, - inactive_dim: InactiveDimSettings { - enabled: false, - amount: 0.15, - duration_ms: 150.0 - }, window_radius: 12.0, shadow_offset_y: -2.0, shadow_blur: 24.0, @@ -756,15 +749,14 @@ mod tests { #[test] fn inactive_dimming_is_opt_in_and_configurable() { - let defaults = parse("").theme_settings().unwrap().inactive_dim; + let defaults = parse("").inactive_dim_settings().unwrap(); assert!(!defaults.enabled); assert_eq!(defaults.amount, 0.15); assert_eq!(defaults.duration_ms, 150.0); let settings = parse("appearance {\n inactive-dim {\n enabled #true\n amount 0.25\n duration-ms 100\n }\n}\n") - .theme_settings() - .unwrap() - .inactive_dim; + .inactive_dim_settings() + .unwrap(); assert!(settings.enabled); assert_eq!(settings.amount, 0.25); assert_eq!(settings.duration_ms, 100.0); @@ -777,7 +769,7 @@ mod tests { ] { assert!( Config::parse_source(&format!("appearance {{ inactive-dim {{ {key} {value}; }} }}")) - .and_then(|config| config.theme_settings()) + .and_then(|config| config.runtime_config()) .is_err() ); } diff --git a/compositor/src/config/appearance.rs b/compositor/src/config/appearance.rs index 044e9c7..7e247e1 100644 --- a/compositor/src/config/appearance.rs +++ b/compositor/src/config/appearance.rs @@ -1,6 +1,17 @@ use super::*; impl Config { + pub(crate) fn inactive_dim_settings(&self) -> Result { + Ok(InactiveDimSettings { + enabled: self.appearance.inactive_dim.enabled, + amount: unit_theme_value(self.appearance.inactive_dim.amount, "appearance.inactive_dim.amount")?, + duration_ms: nonnegative_theme_value( + self.appearance.inactive_dim.duration_ms, + "appearance.inactive_dim.duration_ms", + )?, + }) + } + pub fn theme_settings(&self) -> Result { let border_width = nonnegative_theme_value(self.theme.geometry.border_width, "geometry.border_width")?; let focus_ring_width = @@ -39,14 +50,6 @@ impl Config { "surface.bar.background", )?, shell_opacity: unit_theme_value(self.theme.material.opacity, "material.opacity")?, - inactive_dim: InactiveDimSettings { - enabled: self.appearance.inactive_dim.enabled, - amount: unit_theme_value(self.appearance.inactive_dim.amount, "appearance.inactive_dim.amount")?, - duration_ms: nonnegative_theme_value( - self.appearance.inactive_dim.duration_ms, - "appearance.inactive_dim.duration_ms", - )?, - }, window_radius, shadow_offset_y, shadow_blur, @@ -210,7 +213,6 @@ pub struct ThemeSettings { pub bar_background_color: RgbaColor, pub text_primary_color: RgbaColor, pub shell_opacity: f64, - pub inactive_dim: InactiveDimSettings, pub window_radius: f64, pub shadow_offset_y: f64, pub shadow_blur: f64, diff --git a/compositor/src/reload.rs b/compositor/src/reload.rs index 98e369a..54f33ca 100644 --- a/compositor/src/reload.rs +++ b/compositor/src/reload.rs @@ -194,6 +194,62 @@ mod tests { } } + #[test] + fn live_reload_keeps_dimming_policy_independent_of_theme_transitions() { + if std::env::var_os("FERESE_DIM_RELOAD_TEST_CHILD").is_none() { + use std::os::unix::fs::PermissionsExt; + + let directory = Directory::new(); + std::fs::set_permissions(&directory.0, std::fs::Permissions::from_mode(0o700)).unwrap(); + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .args([ + "--exact", + "reload::tests::live_reload_keeps_dimming_policy_independent_of_theme_transitions", + "--nocapture", + ]) + .env("FERESE_DIM_RELOAD_TEST_CHILD", "1") + .env("XDG_RUNTIME_DIR", &directory.0) + .env("XDG_CONFIG_HOME", &directory.0) + .env_remove("FERESE_SOCKET") + .output() + .unwrap(); + assert!( + output.status.success(), + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + return; + } + let source = "appearance { inactive-dim { enabled #true; amount 0.25; }; }"; + let directory = Directory::new(); + let (_, runtime, candidate) = crate::theme::prepare(source, &directory.0).unwrap(); + let mut event_loop = smithay::reexports::calloop::EventLoop::try_new().unwrap(); + let display = smithay::reexports::wayland_server::Display::new().unwrap(); + let mut state = crate::Ferese::new(&mut event_loop, display, runtime).unwrap(); + state.config_source = Some(source.into()); + state.theme_engine.snapshot.theme = candidate.theme.clone(); + state.theme_engine.snapshot.presented = candidate.theme; + + for enabled in [false, true, false] { + let source = format!("appearance {{ inactive-dim {{ enabled #{enabled}; amount 0.25; }}; }}"); + state.reload_config_source(source).unwrap(); + assert_eq!(state.inactive_dim.enabled, enabled); + let opacity = crate::dimming::target( + state.inactive_dim, + Some(ferese_layout::WindowId(1)), + ferese_layout::WindowId(2), + false, + ); + assert_eq!(opacity, if enabled { 0.25 } else { 0.0 }); + } + state + .reload_config_source("appearance { inactive-dim { enabled #false; }; }; theme { mode \"light\"; }".into()) + .unwrap(); + state.poll_theme(); + assert!(!state.inactive_dim.enabled); + } + #[test] fn runtime_validation_rejects_bad_edits_before_application() { for source in [ diff --git a/compositor/src/state.rs b/compositor/src/state.rs index 3409a0b..b52acf8 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -372,6 +372,7 @@ pub struct Ferese { window_rules: Vec, window_rules_applied: HashSet, pub(crate) theme_settings: ThemeSettings, + pub(crate) inactive_dim: crate::config::InactiveDimSettings, animations_enabled: bool, animation_speed: f64, spring_config: SpringConfig, @@ -450,6 +451,7 @@ pub struct RuntimeConfig { pub workspace_auto_back_and_forth: bool, pub window_rules: Vec, pub theme_settings: ThemeSettings, + pub inactive_dim: crate::config::InactiveDimSettings, pub default_column_width: ColumnWidth, pub scrolling_focus_strategy: ViewportFocusStrategy, pub column_width_presets: Vec, @@ -602,6 +604,7 @@ impl Ferese { window_rules: config.window_rules, window_rules_applied: HashSet::new(), theme_settings: config.theme_settings, + inactive_dim: config.inactive_dim, animations_enabled: config.animations_enabled, animation_speed: config.animation_speed, spring_config: config.spring_config, @@ -739,6 +742,7 @@ impl Ferese { self.portal_shortcuts.reconcile(&self.bindings, &self.input_settings); let old_rules = std::mem::replace(&mut self.window_rules, config.window_rules); self.theme_settings = config.theme_settings; + self.inactive_dim = config.inactive_dim; self.column_width_presets = config.column_width_presets; self.animations_enabled = config.animations_enabled; if !self.animations_enabled { diff --git a/compositor/src/state/animation.rs b/compositor/src/state/animation.rs index fe283e9..461e9fd 100644 --- a/compositor/src/state/animation.rs +++ b/compositor/src/state/animation.rs @@ -69,7 +69,7 @@ impl Ferese { active }); - let dim_settings = self.theme_settings.inactive_dim; + let dim_settings = self.inactive_dim; let duration = if self.animations_enabled { dim_settings.duration_ms } else { diff --git a/compositor/src/theme.rs b/compositor/src/theme.rs index b9d809c..966c613 100644 --- a/compositor/src/theme.rs +++ b/compositor/src/theme.rs @@ -562,8 +562,7 @@ impl Ferese { } else { transition.from.transition(target, progress) }; - self.theme_settings = Config::resolved_theme_settings(&presented, self.theme_settings.inactive_dim) - .expect("validated resolved theme"); + self.theme_settings = Config::resolved_theme_settings(&presented).expect("validated resolved theme"); self.theme_engine.snapshot.presented = presented; self.theme_engine.publish(); self.backdrop_generation = self.backdrop_generation.wrapping_add(1); diff --git a/shell/ferese-shell/src/bar.rs b/shell/ferese-shell/src/bar.rs index 82e4b11..ac17361 100644 --- a/shell/ferese-shell/src/bar.rs +++ b/shell/ferese-shell/src/bar.rs @@ -300,7 +300,7 @@ pub(super) fn workspace_selector_style( background: if active { Some(Background::Color(color_with_opacity(shell_theme.accent, 0.16))) } else if occupied { - Some(Background::Color(color_with_opacity(shell_theme.text_primary, 0.04))) + Some(Background::Color(color_with_opacity(shell_theme.border, 0.3))) } else { None }, @@ -326,9 +326,9 @@ pub(super) fn bar_style(theme: ShellTheme, compositor_material: bool) -> contain pub(super) fn bar_group_style(theme: ShellTheme) -> container::Style { container::Style { - background: Some(Background::Color(color_with_opacity(theme.text_primary, 0.035))), + background: Some(Background::Color(color_with_opacity(theme.border, 0.25))), border: Border { - color: color_with_opacity(theme.text_muted, 0.12), + color: color(theme.border), width: 1.0, radius: theme.material_radius.min(16.0).into(), }, diff --git a/shell/ferese-shell/src/main.rs b/shell/ferese-shell/src/main.rs index e3f304a..ece77d8 100644 --- a/shell/ferese-shell/src/main.rs +++ b/shell/ferese-shell/src/main.rs @@ -1069,6 +1069,25 @@ mod tests { assert_eq!(configured_font(None), cosmic::font::default()); } + #[test] + fn bar_decoration_does_not_follow_transitional_text_contrast() { + let theme = config::ShellTheme::default(); + let contrasted = config::ShellTheme { + text_primary: [0, 0, 0, 255], + text_muted: [255, 255, 255, 255], + ..theme + }; + let before = bar_group_style(theme); + let after = bar_group_style(contrasted); + assert_eq!(before.border, after.border); + assert_eq!(before.background, after.background); + assert_eq!(before.border.color, color(theme.border)); + assert_eq!( + workspace_selector_style(false, false, true, theme).background, + workspace_selector_style(false, false, true, contrasted).background, + ); + } + #[test] fn workspace_selector_has_distinct_active_paint() { let theme = ShellTheme::default(); diff --git a/shell/ferese-shell/src/status_ui/mod.rs b/shell/ferese-shell/src/status_ui/mod.rs index 33a8846..05693bc 100644 --- a/shell/ferese-shell/src/status_ui/mod.rs +++ b/shell/ferese-shell/src/status_ui/mod.rs @@ -218,7 +218,7 @@ impl FereseShell { text_color: Some(primary), icon_color: Some(primary), border: Border { - color: color_with_opacity(theme.text_muted, 0.18 * p), + color: color_with_opacity(theme.border, p), width: 1.0, radius: theme.material_radius.into(), },