feat: resolve semantic material styles

This commit is contained in:
Samuel Onoja committed 2026-09-25 05:10:31 +01:00
1 parent 87bc68fe8a
commit ecefae6f81
3 files changed
+102 -2

No files matched your search

+22 -1
View File
@@ -114,6 +114,16 @@ pub struct ThemeSettings {
pub shadow_offset_y: f64,
pub shadow_blur: f64,
pub shadow_opacity: f64,
pub material_style: MaterialStyle,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum MaterialStyle {
#[default]
Glass,
Translucent,
Solid,
}
#[derive(Debug, Default, Deserialize)]
@@ -124,6 +134,14 @@ struct ThemeConfig {
geometry: ThemeGeometryConfig,
#[serde(default)]
shadow: ThemeShadowConfig,
#[serde(default)]
material: ThemeMaterialConfig,
}
#[derive(Debug, Default, Deserialize)]
struct ThemeMaterialConfig {
#[serde(default)]
style: MaterialStyle,
}
#[derive(Debug, Deserialize)]
@@ -788,6 +806,7 @@ impl Config {
shadow_offset_y,
shadow_blur,
shadow_opacity,
material_style: self.theme.material.style,
})
}
@@ -1535,7 +1554,7 @@ mod tests {
#[test]
fn parses_and_validates_theme_window_tokens() {
let configured = parse(
"[theme.colors]\nborder = \"#11223344\"\naccent = \"#AABBCC\"\nshadow = \"#01020380\"\n\n[theme.geometry]\nborder_width = 1.5\nfocus_ring_width = 3.0\nwindow_radius = 12.0\n\n[theme.shadow.soft]\noffset_y = -2.0\nblur = 24.0\nopacity = 0.4",
"[theme.colors]\nborder = \"#11223344\"\naccent = \"#AABBCC\"\nshadow = \"#01020380\"\n\n[theme.geometry]\nborder_width = 1.5\nfocus_ring_width = 3.0\nwindow_radius = 12.0\n\n[theme.shadow.soft]\noffset_y = -2.0\nblur = 24.0\nopacity = 0.4\n\n[theme.material]\nstyle = \"translucent\"",
);
let invalid_color = parse("[theme.colors]\naccent = \"blue\"");
let invalid_radius = parse("[theme.geometry]\nwindow_radius = -1.0");
@@ -1553,11 +1572,13 @@ mod tests {
shadow_offset_y: -2.0,
shadow_blur: 24.0,
shadow_opacity: 0.4,
material_style: MaterialStyle::Translucent,
}
);
assert!(invalid_color.theme_settings().is_err());
assert!(invalid_radius.theme_settings().is_err());
assert!(invalid_opacity.theme_settings().is_err());
assert!(toml::from_str::<Config>("[theme.material]\nstyle = \"mist\"").is_err());
}
#[test]
+77 -1
View File
@@ -15,7 +15,9 @@ use smithay::{
wayland::compositor::with_states,
};
use crate::{Ferese, private_client::ClientCapabilities, state::ClientState};
use crate::{
Ferese, config::MaterialStyle, private_client::ClientCapabilities, state::ClientState,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SemanticRole {
@@ -28,6 +30,56 @@ pub(crate) enum SemanticRole {
Modal,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(dead_code)] // Used by the material renderer in the next M7 slice.
pub(crate) struct ResolvedMaterial {
pub style: MaterialStyle,
pub opacity: f32,
pub blur: f32,
pub saturation: f32,
pub brightness: f32,
pub noise: f32,
}
#[allow(dead_code)] // Used by the material renderer in the next M7 slice.
pub(crate) fn resolve_material(role: SemanticRole, style: MaterialStyle) -> ResolvedMaterial {
let (opacity, blur, saturation, brightness, noise) = match role {
SemanticRole::Panel => (0.78, 24.0, 1.08, 1.02, 0.012),
SemanticRole::PanelElevated
| SemanticRole::Popover
| SemanticRole::Menu
| SemanticRole::Notification => (0.84, 28.0, 1.10, 1.03, 0.014),
SemanticRole::Hud | SemanticRole::Modal => (0.88, 30.0, 1.06, 1.04, 0.010),
};
match style {
MaterialStyle::Glass => ResolvedMaterial {
style,
opacity,
blur,
saturation,
brightness,
noise,
},
MaterialStyle::Translucent => ResolvedMaterial {
style,
opacity,
blur: 0.0,
saturation: 1.0,
brightness: 1.0,
noise: 0.0,
},
MaterialStyle::Solid => ResolvedMaterial {
style,
opacity: 1.0,
blur: 0.0,
saturation: 1.0,
brightness: 1.0,
noise: 0.0,
},
}
}
#[derive(Debug)]
struct SurfaceEffectsState {
attached: AtomicBool,
@@ -221,4 +273,28 @@ mod tests {
effects.set_role(Some(SemanticRole::Popover));
assert_eq!(effects.generation.load(Ordering::Acquire), 2);
}
#[test]
fn styles_preserve_semantics_but_disable_unsupported_effects() {
let glass = resolve_material(SemanticRole::Popover, MaterialStyle::Glass);
let translucent = resolve_material(SemanticRole::Popover, MaterialStyle::Translucent);
let solid = resolve_material(SemanticRole::Popover, MaterialStyle::Solid);
assert!(glass.blur > 0.0);
assert!(glass.noise > 0.0);
assert_eq!(translucent.opacity, glass.opacity);
assert_eq!(translucent.blur, 0.0);
assert_eq!(translucent.noise, 0.0);
assert_eq!(solid.opacity, 1.0);
assert_eq!(solid.blur, 0.0);
}
#[test]
fn elevated_roles_resolve_more_strongly_than_the_panel() {
let panel = resolve_material(SemanticRole::Panel, MaterialStyle::Glass);
let popover = resolve_material(SemanticRole::Popover, MaterialStyle::Glass);
assert!(popover.opacity > panel.opacity);
assert!(popover.blur > panel.blur);
}
}