Refine shell status controls and glass rendering; defer cursor redraw safely

This commit is contained in:
Samuel Onoja committed 2026-09-26 05:05:50 +01:00
1 parent e85fc1e032
commit b3fa82b53b
53 files changed
+3329 -259

No files matched your search

+165 -45
View File
@@ -16,7 +16,10 @@ use smithay::{
};
use crate::{
Ferese, config::MaterialStyle, private_client::ClientCapabilities, state::ClientState,
Ferese,
config::{GlassQuality, GlassSettings, MaterialStyle},
private_client::ClientCapabilities,
state::ClientState,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -38,47 +41,72 @@ pub(crate) struct ResolvedMaterial {
pub saturation: f32,
pub brightness: f32,
pub noise: f32,
pub shadow: [f64; 3],
}
pub(crate) fn resolve_material(role: SemanticRole, style: MaterialStyle) -> ResolvedMaterial {
let (base_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),
pub(crate) fn resolve_material(
role: SemanticRole,
style: MaterialStyle,
settings: GlassSettings,
) -> ResolvedMaterial {
// Restrained shell shadows in logical pixels: the full-width bar needs
// less elevation than popovers, while dialogs retain a little more depth.
let (opacity, blur, saturation, shadow) = match role {
SemanticRole::Panel => (0.55, 24.0, 1.35, [1.0, 5.0, 0.04]),
SemanticRole::PanelElevated => (0.55, 24.0, 1.35, [2.0, 8.0, 0.07]),
SemanticRole::Popover => (0.55, 22.0, 1.35, [2.0, 8.0, 0.07]),
SemanticRole::Menu => (0.60, 20.0, 1.30, [2.0, 8.0, 0.07]),
SemanticRole::Hud => (0.65, 18.0, 1.25, [1.0, 4.0, 0.04]),
SemanticRole::Notification => (0.58, 24.0, 1.35, [3.0, 10.0, 0.09]),
SemanticRole::Modal => (0.62, 28.0, 1.40, [3.0, 10.0, 0.09]),
};
match style {
MaterialStyle::Glass => ResolvedMaterial {
style,
// Glass already contains an opaque copy of the backdrop. Its tint
// therefore needs less coverage than the no-capture translucent
// profile or the blur becomes visually indistinguishable from a
// flat alpha surface.
opacity: base_opacity * 0.72,
blur,
saturation,
brightness,
noise,
// Preserve the separately defined translucent family when selected
// directly. Glass degradation keeps the glass role's contrast floor.
let opacity = if style == MaterialStyle::Translucent {
match role {
SemanticRole::Panel => 0.78,
SemanticRole::Hud | SemanticRole::Modal => 0.88,
_ => 0.84,
}
} else {
opacity
};
let style = if style == MaterialStyle::Glass {
match settings.quality {
GlassQuality::Solid => MaterialStyle::Solid,
GlassQuality::Translucent => MaterialStyle::Translucent,
_ => style,
}
} else {
style
};
let glass = style == MaterialStyle::Glass;
ResolvedMaterial {
style,
opacity: if style == MaterialStyle::Solid {
1.0
} else {
opacity
},
MaterialStyle::Translucent => ResolvedMaterial {
style,
opacity: base_opacity,
blur: 0.0,
saturation: 1.0,
brightness: 1.0,
noise: 0.0,
blur: if !glass {
0.0
} else if settings.quality >= GlassQuality::ReducedBlur {
(blur * settings.blur_scale).min(12.0)
} else {
blur * settings.blur_scale
},
MaterialStyle::Solid => ResolvedMaterial {
style,
opacity: 1.0,
blur: 0.0,
saturation: 1.0,
brightness: 1.0,
noise: 0.0,
saturation: if glass && settings.quality < GlassQuality::NoSaturation {
saturation
} else {
1.0
},
brightness: 1.0,
noise: if glass && settings.grain && settings.quality < GlassQuality::NoGrain {
0.05
} else {
0.0
},
shadow,
}
}
@@ -297,13 +325,25 @@ mod tests {
#[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);
let glass = resolve_material(
SemanticRole::Popover,
MaterialStyle::Glass,
GlassSettings::default(),
);
let translucent = resolve_material(
SemanticRole::Popover,
MaterialStyle::Translucent,
GlassSettings::default(),
);
let solid = resolve_material(
SemanticRole::Popover,
MaterialStyle::Solid,
GlassSettings::default(),
);
assert!(glass.blur > 0.0);
assert!(glass.noise > 0.0);
assert!(glass.opacity < translucent.opacity);
assert!(glass.opacity <= translucent.opacity);
assert_eq!(translucent.blur, 0.0);
assert_eq!(translucent.noise, 0.0);
assert_eq!(solid.opacity, 1.0);
@@ -311,11 +351,91 @@ mod tests {
}
#[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);
fn glass_presets_use_role_specific_tints_and_restrained_shadows() {
for (role, blur, saturation, opacity, shadow) in [
(SemanticRole::Panel, 24.0, 1.35, 0.55, [1.0, 5.0, 0.04]),
(SemanticRole::Popover, 22.0, 1.35, 0.55, [2.0, 8.0, 0.07]),
(SemanticRole::Menu, 20.0, 1.30, 0.60, [2.0, 8.0, 0.07]),
(SemanticRole::Hud, 18.0, 1.25, 0.65, [1.0, 4.0, 0.04]),
(
SemanticRole::Notification,
24.0,
1.35,
0.58,
[3.0, 10.0, 0.09],
),
(SemanticRole::Modal, 28.0, 1.40, 0.62, [3.0, 10.0, 0.09]),
] {
let material = resolve_material(role, MaterialStyle::Glass, GlassSettings::default());
assert_eq!(
(
material.blur,
material.saturation,
material.opacity,
material.shadow
),
(blur, saturation, opacity, shadow)
);
}
}
assert!(popover.opacity > panel.opacity);
assert!(popover.blur > panel.blur);
#[test]
fn degradation_is_ordered_and_preserves_contrast_and_elevation() {
let reference = resolve_material(
SemanticRole::Modal,
MaterialStyle::Glass,
GlassSettings::default(),
);
for quality in [
GlassQuality::Full,
GlassQuality::NoGrain,
GlassQuality::NoSaturation,
GlassQuality::ReducedBlur,
GlassQuality::Translucent,
GlassQuality::Solid,
] {
let material = resolve_material(
SemanticRole::Modal,
MaterialStyle::Glass,
GlassSettings {
quality,
..GlassSettings::default()
},
);
assert!(material.opacity >= reference.opacity);
assert_eq!(material.shadow, reference.shadow);
if quality >= GlassQuality::NoGrain {
assert_eq!(material.noise, 0.0);
}
if quality >= GlassQuality::NoSaturation {
assert_eq!(material.saturation, 1.0);
}
if quality >= GlassQuality::ReducedBlur {
assert!(material.blur <= 12.0);
}
if quality >= GlassQuality::Translucent {
assert_eq!(material.blur, 0.0);
}
if quality == GlassQuality::Solid {
assert_eq!(material.opacity, 1.0);
}
}
}
#[test]
fn popover_uses_the_same_tint_with_a_smaller_blur_than_panel() {
let panel = resolve_material(
SemanticRole::Panel,
MaterialStyle::Glass,
GlassSettings::default(),
);
let popover = resolve_material(
SemanticRole::Popover,
MaterialStyle::Glass,
GlassSettings::default(),
);
assert_eq!(popover.opacity, panel.opacity);
assert!(popover.blur < panel.blur);
}
}