Replace glass effects with plain backdrop blur and refine shell layout
This commit is contained in:
1 parent
af32c29a49
commit
64c95f0b97
13 files changed
+823
-1036
No files matched your search
+105
-89
@@ -113,12 +113,13 @@ pub struct ThemeSettings {
|
||||
pub accent_color: RgbaColor,
|
||||
pub shadow_color: RgbaColor,
|
||||
pub surface_base_color: RgbaColor,
|
||||
pub panel_opacity: f64,
|
||||
pub window_radius: f64,
|
||||
pub shadow_offset_y: f64,
|
||||
pub shadow_blur: f64,
|
||||
pub shadow_opacity: f64,
|
||||
pub material_style: MaterialStyle,
|
||||
pub glass: GlassSettings,
|
||||
pub backdrop_blur: f64,
|
||||
pub material_radius: f64,
|
||||
pub panel_radius: f64,
|
||||
}
|
||||
@@ -126,64 +127,20 @@ pub struct ThemeSettings {
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MaterialStyle {
|
||||
#[default]
|
||||
Glass,
|
||||
Translucent,
|
||||
Solid,
|
||||
}
|
||||
|
||||
/// Ordered quality tiers; each step retains the preceding reductions.
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GlassQuality {
|
||||
#[default]
|
||||
Full,
|
||||
NoGrain,
|
||||
NoSaturation,
|
||||
ReducedBlur,
|
||||
Translucent,
|
||||
Solid,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct GlassSettings {
|
||||
pub tint: RgbaColor,
|
||||
pub blur_scale: f32,
|
||||
pub grain: bool,
|
||||
pub quality: GlassQuality,
|
||||
}
|
||||
|
||||
impl Default for GlassSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tint: RgbaColor([28.0 / 255.0, 32.0 / 255.0, 46.0 / 255.0, 1.0]),
|
||||
blur_scale: 1.0,
|
||||
grain: true,
|
||||
quality: GlassQuality::Full,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct AppearanceConfig {
|
||||
corner_radius: Option<f64>,
|
||||
#[serde(default)]
|
||||
reduced_effects: bool,
|
||||
#[serde(default)]
|
||||
glass: GlassConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct GlassConfig {
|
||||
tint_color: Option<String>,
|
||||
blur_scale: Option<f64>,
|
||||
grain: Option<bool>,
|
||||
#[serde(default)]
|
||||
quality: GlassQuality,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct ThemeConfig {
|
||||
#[serde(default)]
|
||||
surface: ThemeSurfaceConfig,
|
||||
#[serde(default)]
|
||||
colors: ThemeColorsConfig,
|
||||
#[serde(default)]
|
||||
@@ -195,9 +152,35 @@ struct ThemeConfig {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct ThemeSurfaceConfig {
|
||||
#[serde(default)]
|
||||
bar: ThemeBarConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct ThemeBarConfig {
|
||||
opacity: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ThemeMaterialConfig {
|
||||
#[serde(default)]
|
||||
style: MaterialStyle,
|
||||
#[serde(default = "default_backdrop_blur")]
|
||||
blur_radius: f64,
|
||||
}
|
||||
|
||||
impl Default for ThemeMaterialConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
style: MaterialStyle::Solid,
|
||||
blur_radius: default_backdrop_blur(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_backdrop_blur() -> f64 {
|
||||
12.0
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -861,22 +844,6 @@ impl Config {
|
||||
let shadow_opacity =
|
||||
unit_theme_value(self.theme.shadow.soft.opacity, "shadow.soft.opacity")?;
|
||||
|
||||
let mut glass = GlassSettings::default();
|
||||
if let Some(tint) = &self.appearance.glass.tint_color {
|
||||
glass.tint = parse_color(tint, "appearance.glass.tint_color")?;
|
||||
// Opacity belongs to the semantic role, never the color token.
|
||||
glass.tint.0[3] = 1.0;
|
||||
}
|
||||
glass.blur_scale = finite_theme_value(
|
||||
self.appearance.glass.blur_scale.unwrap_or(1.0),
|
||||
"appearance.glass.blur_scale",
|
||||
)?
|
||||
.clamp(0.5, 1.5) as f32;
|
||||
glass.grain = self.appearance.glass.grain.unwrap_or(true);
|
||||
glass.quality = self.appearance.glass.quality;
|
||||
if self.appearance.reduced_effects {
|
||||
glass.quality = glass.quality.max(GlassQuality::ReducedBlur);
|
||||
}
|
||||
let material_radius = nonnegative_theme_value(
|
||||
self.appearance.corner_radius.unwrap_or(14.0),
|
||||
"appearance.corner_radius",
|
||||
@@ -891,12 +858,20 @@ impl Config {
|
||||
&self.theme.colors.surface_base,
|
||||
"colors.surface_base",
|
||||
)?,
|
||||
panel_opacity: unit_theme_value(
|
||||
self.theme.surface.bar.opacity.unwrap_or(0.78),
|
||||
"surface.bar.opacity",
|
||||
)?,
|
||||
window_radius,
|
||||
shadow_offset_y,
|
||||
shadow_blur,
|
||||
shadow_opacity,
|
||||
material_style: self.theme.material.style,
|
||||
glass,
|
||||
backdrop_blur: nonnegative_theme_value(
|
||||
self.theme.material.blur_radius,
|
||||
"material.blur_radius",
|
||||
)?
|
||||
.min(32.0),
|
||||
material_radius,
|
||||
panel_radius: nonnegative_theme_value(
|
||||
self.theme.geometry.top_bar_radius,
|
||||
@@ -1665,30 +1640,6 @@ mod tests {
|
||||
assert!(invalid.gap_config().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glass_configuration_clamps_cost_and_rejects_nonfinite_values() {
|
||||
for (value, expected) in [(0.1, 0.5), (1.2, 1.2), (100.0, 1.5)] {
|
||||
let config = parse(&format!("[appearance.glass]\nblur_scale = {value}"));
|
||||
assert_eq!(config.theme_settings().unwrap().glass.blur_scale, expected);
|
||||
}
|
||||
for value in ["nan", "inf", "-inf"] {
|
||||
assert!(
|
||||
parse(&format!("[appearance.glass]\nblur_scale = {value}"))
|
||||
.theme_settings()
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
let settings = parse("[appearance]\nreduced_effects = true\ncorner_radius = 17.0\n[appearance.glass]\ntint_color = \"#123456\"\ngrain = false")
|
||||
.theme_settings().unwrap();
|
||||
assert_eq!(settings.glass.quality, GlassQuality::ReducedBlur);
|
||||
assert!(!settings.glass.grain);
|
||||
assert_eq!(settings.material_radius, 17.0);
|
||||
assert_eq!(
|
||||
settings.glass.tint,
|
||||
RgbaColor([18.0 / 255.0, 52.0 / 255.0, 86.0 / 255.0, 1.0])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_and_validates_theme_window_tokens() {
|
||||
let configured = parse(
|
||||
@@ -1707,12 +1658,13 @@ mod tests {
|
||||
accent_color: RgbaColor([170.0 / 255.0, 187.0 / 255.0, 0.8, 1.0]),
|
||||
shadow_color: RgbaColor([1.0 / 255.0, 2.0 / 255.0, 3.0 / 255.0, 128.0 / 255.0]),
|
||||
surface_base_color: RgbaColor([17.0 / 255.0, 24.0 / 255.0, 33.0 / 255.0, 1.0,]),
|
||||
panel_opacity: 0.78,
|
||||
window_radius: 12.0,
|
||||
shadow_offset_y: -2.0,
|
||||
shadow_blur: 24.0,
|
||||
shadow_opacity: 0.4,
|
||||
material_style: MaterialStyle::Translucent,
|
||||
glass: GlassSettings::default(),
|
||||
backdrop_blur: 12.0,
|
||||
material_radius: 14.0,
|
||||
panel_radius: 0.0,
|
||||
}
|
||||
@@ -1723,6 +1675,70 @@ mod tests {
|
||||
assert!(toml::from_str::<Config>("[theme.material]\nstyle = \"mist\"").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn materials_default_to_solid_and_reject_removed_style() {
|
||||
assert_eq!(
|
||||
parse("").theme_settings().unwrap().material_style,
|
||||
MaterialStyle::Solid
|
||||
);
|
||||
assert!(toml::from_str::<Config>("[theme.material]\nstyle = \"glass\"").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn material_color_comes_from_surface_base() {
|
||||
let theme = parse("[theme.colors]\nsurface_base = \"#000000\"\n[theme.surface.bar]\nbackground = \"#FFFFFF\"")
|
||||
.theme_settings()
|
||||
.unwrap();
|
||||
assert_eq!(theme.surface_base_color, RgbaColor([0.0, 0.0, 0.0, 1.0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bar_opacity_uses_config_and_rejects_invalid_values() {
|
||||
assert_eq!(parse("").theme_settings().unwrap().panel_opacity, 0.78);
|
||||
for opacity in [0.0, 0.65, 1.0] {
|
||||
assert_eq!(
|
||||
parse(&format!("[theme.surface.bar]\nopacity = {opacity}"))
|
||||
.theme_settings()
|
||||
.unwrap()
|
||||
.panel_opacity,
|
||||
opacity
|
||||
);
|
||||
}
|
||||
for opacity in ["-0.1", "1.1", "nan", "inf"] {
|
||||
assert!(
|
||||
parse(&format!("[theme.surface.bar]\nopacity = {opacity}"))
|
||||
.theme_settings()
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backdrop_blur_is_configurable_without_glass_settings() {
|
||||
assert_eq!(parse("").theme_settings().unwrap().backdrop_blur, 12.0);
|
||||
assert_eq!(
|
||||
parse("[theme.material]\nstyle = \"translucent\"\nblur_radius = 100")
|
||||
.theme_settings()
|
||||
.unwrap()
|
||||
.backdrop_blur,
|
||||
32.0
|
||||
);
|
||||
assert_eq!(
|
||||
parse("[theme.material]\nblur_radius = 0")
|
||||
.theme_settings()
|
||||
.unwrap()
|
||||
.backdrop_blur,
|
||||
0.0
|
||||
);
|
||||
for value in ["-1", "nan", "inf"] {
|
||||
assert!(
|
||||
parse(&format!("[theme.material]\nblur_radius = {value}"))
|
||||
.theme_settings()
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_input_and_touchpad_settings() {
|
||||
let config = parse(
|
||||
|
||||
+107
-180
@@ -16,10 +16,7 @@ use smithay::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Ferese,
|
||||
config::{GlassQuality, GlassSettings, MaterialStyle},
|
||||
private_client::ClientCapabilities,
|
||||
state::ClientState,
|
||||
Ferese, config::MaterialStyle, private_client::ClientCapabilities, state::ClientState,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -37,75 +34,27 @@ pub(crate) enum SemanticRole {
|
||||
pub(crate) struct ResolvedMaterial {
|
||||
pub style: MaterialStyle,
|
||||
pub opacity: f32,
|
||||
pub blur: f32,
|
||||
pub saturation: f32,
|
||||
pub brightness: f32,
|
||||
pub noise: f32,
|
||||
pub shadow: [f64; 3],
|
||||
}
|
||||
|
||||
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]),
|
||||
};
|
||||
// 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,
|
||||
pub(crate) fn resolve_material(role: SemanticRole, style: MaterialStyle) -> ResolvedMaterial {
|
||||
let shadow = match role {
|
||||
SemanticRole::Panel => [1.0, 5.0, 0.04],
|
||||
SemanticRole::PanelElevated | SemanticRole::Popover | SemanticRole::Menu => {
|
||||
[2.0, 8.0, 0.07]
|
||||
}
|
||||
} else {
|
||||
opacity
|
||||
SemanticRole::Hud => [1.0, 4.0, 0.04],
|
||||
SemanticRole::Notification | SemanticRole::Modal => [3.0, 10.0, 0.09],
|
||||
};
|
||||
let style = if style == MaterialStyle::Glass {
|
||||
match settings.quality {
|
||||
GlassQuality::Solid => MaterialStyle::Solid,
|
||||
GlassQuality::Translucent => MaterialStyle::Translucent,
|
||||
_ => style,
|
||||
}
|
||||
} else {
|
||||
style
|
||||
let opacity = match (style, role) {
|
||||
(MaterialStyle::Solid, _) => 1.0,
|
||||
(_, SemanticRole::Panel) => 0.78,
|
||||
(_, SemanticRole::Hud | SemanticRole::Modal) => 0.88,
|
||||
_ => 0.84,
|
||||
};
|
||||
let glass = style == MaterialStyle::Glass;
|
||||
ResolvedMaterial {
|
||||
style,
|
||||
opacity: if style == MaterialStyle::Solid {
|
||||
1.0
|
||||
} else {
|
||||
opacity
|
||||
},
|
||||
blur: if !glass {
|
||||
0.0
|
||||
} else if settings.quality >= GlassQuality::ReducedBlur {
|
||||
(blur * settings.blur_scale).min(12.0)
|
||||
} else {
|
||||
blur * settings.blur_scale
|
||||
},
|
||||
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
|
||||
},
|
||||
opacity,
|
||||
shadow,
|
||||
}
|
||||
}
|
||||
@@ -115,6 +64,7 @@ struct SurfaceEffectsState {
|
||||
attached: AtomicBool,
|
||||
generation: AtomicU64,
|
||||
role: Mutex<Option<SemanticRole>>,
|
||||
regions: Mutex<Option<Vec<[i32; 5]>>>,
|
||||
}
|
||||
|
||||
impl SurfaceEffectsState {
|
||||
@@ -123,6 +73,7 @@ impl SurfaceEffectsState {
|
||||
attached: AtomicBool::new(false),
|
||||
generation: AtomicU64::new(0),
|
||||
role: Mutex::new(None),
|
||||
regions: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +103,39 @@ impl SurfaceEffectsUserData {
|
||||
}
|
||||
|
||||
pub(crate) fn init_global(display: &DisplayHandle) {
|
||||
display.create_global::<Ferese, FereseEffectsManagerV1, _>(1, ());
|
||||
display.create_global::<Ferese, FereseEffectsManagerV1, _>(2, ());
|
||||
}
|
||||
|
||||
pub(crate) fn surface_regions(surface: &WlSurface) -> Option<Vec<[i32; 5]>> {
|
||||
with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<SurfaceEffectsState>()?
|
||||
.regions
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn decode_regions(bytes: &[u8]) -> Option<Vec<[i32; 5]>> {
|
||||
if bytes.len() % 20 != 0 || bytes.len() > 32 * 20 {
|
||||
return None;
|
||||
}
|
||||
bytes
|
||||
.chunks_exact(20)
|
||||
.map(|tuple| {
|
||||
let values: Vec<_> = tuple
|
||||
.chunks_exact(4)
|
||||
.map(|v| i32::from_ne_bytes(v.try_into().unwrap()))
|
||||
.collect();
|
||||
(values[2] > 0
|
||||
&& values[3] > 0
|
||||
&& values[4] >= 0
|
||||
&& values.iter().all(|v| v.abs_diff(0) <= 32768))
|
||||
.then(|| [values[0], values[1], values[2], values[3], values[4]])
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Consumed by the material renderer in the next M7 slice.
|
||||
@@ -254,19 +237,36 @@ impl Dispatch<FereseSurfaceEffectsV1, SurfaceEffectsUserData> for Ferese {
|
||||
WEnum::Unknown(_) | WEnum::Value(_) => return,
|
||||
};
|
||||
if set_surface_role(&surface, Some(role)) {
|
||||
state.invalidate_material_scene();
|
||||
crate::backends::direct::render_all(state);
|
||||
}
|
||||
}
|
||||
ferese_surface_effects_v1::Request::SetRegions { regions } => {
|
||||
let Some(regions) = decode_regions(®ions) else {
|
||||
return;
|
||||
};
|
||||
let changed = with_states(&surface, |states| {
|
||||
let Some(effects) = states.data_map.get::<SurfaceEffectsState>() else {
|
||||
return false;
|
||||
};
|
||||
let mut old = effects.regions.lock().unwrap();
|
||||
if old.as_ref() == Some(®ions) {
|
||||
return false;
|
||||
}
|
||||
*old = Some(regions);
|
||||
effects.generation.fetch_add(1, Ordering::Release);
|
||||
true
|
||||
});
|
||||
if changed {
|
||||
crate::backends::direct::render_all(state);
|
||||
}
|
||||
}
|
||||
ferese_surface_effects_v1::Request::ClearRole => {
|
||||
if set_surface_role(&surface, None) {
|
||||
state.invalidate_material_scene();
|
||||
crate::backends::direct::render_all(state);
|
||||
}
|
||||
}
|
||||
ferese_surface_effects_v1::Request::Destroy => {
|
||||
if detach(&surface) {
|
||||
state.invalidate_material_scene();
|
||||
crate::backends::direct::render_all(state);
|
||||
}
|
||||
}
|
||||
@@ -310,6 +310,31 @@ fn detach(surface: &WlSurface) -> bool {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rounded_regions_are_bounded_and_validated() {
|
||||
let region = [12_i32, 8, 120, 48, 11];
|
||||
let bytes: Vec<_> = region.into_iter().flat_map(i32::to_ne_bytes).collect();
|
||||
assert_eq!(decode_regions(&bytes), Some(vec![region]));
|
||||
assert_eq!(decode_regions(&[]), Some(vec![]));
|
||||
assert!(decode_regions(&bytes[..19]).is_none());
|
||||
assert!(decode_regions(&bytes.repeat(33)).is_none());
|
||||
for invalid in [
|
||||
[0_i32, 0, 0, 48, 11],
|
||||
[0, 0, 120, 48, -1],
|
||||
[i32::MIN, 0, 120, 48, 11],
|
||||
] {
|
||||
assert!(
|
||||
decode_regions(
|
||||
&invalid
|
||||
.into_iter()
|
||||
.flat_map(i32::to_ne_bytes)
|
||||
.collect::<Vec<_>>()
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn role_changes_advance_generation_only_when_material_changes() {
|
||||
let effects = SurfaceEffectsState::new();
|
||||
@@ -324,118 +349,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn styles_preserve_semantics_but_disable_unsupported_effects() {
|
||||
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_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 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[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(
|
||||
fn solid_materials_are_opaque_and_keep_role_specific_elevation() {
|
||||
for role in [
|
||||
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);
|
||||
SemanticRole::Menu,
|
||||
SemanticRole::Hud,
|
||||
SemanticRole::Notification,
|
||||
SemanticRole::Modal,
|
||||
] {
|
||||
let solid = resolve_material(role, MaterialStyle::Solid);
|
||||
let translucent = resolve_material(role, MaterialStyle::Translucent);
|
||||
assert_eq!(solid.opacity, 1.0);
|
||||
assert!(translucent.opacity < 1.0);
|
||||
assert_eq!(solid.shadow, translucent.shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,8 @@ impl CompositorHandler for Ferese {
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||
on_commit_buffer_handler::<Self>(surface);
|
||||
self.invalidate_material_scene();
|
||||
if !is_sync_subsurface(surface) {
|
||||
let mut root = surface.clone();
|
||||
while let Some(parent) = get_parent(&root) {
|
||||
@@ -63,9 +63,7 @@ impl CompositorHandler for Ferese {
|
||||
}
|
||||
|
||||
fn destroyed(&mut self, surface: &WlSurface) {
|
||||
// A removed popup changes the backdrop even without a final buffer
|
||||
// commit. Invalidate expanded sampling regions as well as its bounds.
|
||||
self.invalidate_material_scene();
|
||||
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||
crate::backends::direct::render_all(self);
|
||||
if self.idle_inhibitors.remove(surface).is_some() {
|
||||
self.idle_notifier_state
|
||||
|
||||
@@ -5,10 +5,7 @@ use smithay::utils::{Physical, Rectangle};
|
||||
const REPORT_INTERVAL: Duration = Duration::from_secs(5);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub(crate) struct FrameEffectMetrics {
|
||||
pub(crate) blur_regions: u64,
|
||||
pub(crate) blur_sample_pixels: u64,
|
||||
}
|
||||
pub(crate) struct FrameEffectMetrics;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RenderMetrics {
|
||||
@@ -19,9 +16,6 @@ pub(crate) struct RenderMetrics {
|
||||
damaged_pixels: u64,
|
||||
total_render_time: Duration,
|
||||
longest_render_time: Duration,
|
||||
blur_frames: u64,
|
||||
blur_regions: u64,
|
||||
blur_sample_pixels: u64,
|
||||
}
|
||||
|
||||
impl RenderMetrics {
|
||||
@@ -42,9 +36,6 @@ impl RenderMetrics {
|
||||
damaged_pixels: 0,
|
||||
total_render_time: Duration::ZERO,
|
||||
longest_render_time: Duration::ZERO,
|
||||
blur_frames: 0,
|
||||
blur_regions: 0,
|
||||
blur_sample_pixels: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +44,7 @@ impl RenderMetrics {
|
||||
render_time: Duration,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
missed_deadlines: u64,
|
||||
effects: FrameEffectMetrics,
|
||||
_effects: FrameEffectMetrics,
|
||||
) {
|
||||
if !self.enabled {
|
||||
return;
|
||||
@@ -63,9 +54,6 @@ impl RenderMetrics {
|
||||
self.damaged_pixels += damage.iter().map(rectangle_area).sum::<u64>();
|
||||
self.total_render_time += render_time;
|
||||
self.longest_render_time = self.longest_render_time.max(render_time);
|
||||
self.blur_frames += u64::from(effects.blur_regions > 0);
|
||||
self.blur_regions += effects.blur_regions;
|
||||
self.blur_sample_pixels += effects.blur_sample_pixels;
|
||||
|
||||
let now = Instant::now();
|
||||
let elapsed = now.saturating_duration_since(self.interval_started);
|
||||
@@ -87,9 +75,6 @@ impl RenderMetrics {
|
||||
average_render_us,
|
||||
longest_render_us = self.longest_render_time.as_micros(),
|
||||
missed_deadlines,
|
||||
blur_frames = self.blur_frames,
|
||||
blur_regions = self.blur_regions,
|
||||
blur_sample_pixels = self.blur_sample_pixels,
|
||||
"render performance"
|
||||
);
|
||||
|
||||
@@ -98,9 +83,6 @@ impl RenderMetrics {
|
||||
self.damaged_pixels = 0;
|
||||
self.total_render_time = Duration::ZERO;
|
||||
self.longest_render_time = Duration::ZERO;
|
||||
self.blur_frames = 0;
|
||||
self.blur_regions = 0;
|
||||
self.blur_sample_pixels = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,18 +121,12 @@ mod tests {
|
||||
Rectangle::new((10, 10).into(), (20, 10).into()),
|
||||
],
|
||||
0,
|
||||
FrameEffectMetrics {
|
||||
blur_regions: 2,
|
||||
blur_sample_pixels: 4_096,
|
||||
},
|
||||
FrameEffectMetrics,
|
||||
);
|
||||
|
||||
assert_eq!(metrics.rendered_frames, 1);
|
||||
assert_eq!(metrics.damaged_pixels, 5_200);
|
||||
assert_eq!(metrics.total_render_time, Duration::from_millis(2));
|
||||
assert_eq!(metrics.longest_render_time, Duration::from_millis(2));
|
||||
assert_eq!(metrics.blur_frames, 1);
|
||||
assert_eq!(metrics.blur_regions, 2);
|
||||
assert_eq!(metrics.blur_sample_pixels, 4_096);
|
||||
}
|
||||
}
|
||||
+6
-14
@@ -125,9 +125,9 @@ pub struct Ferese {
|
||||
pub(crate) rounded_clip_programs: HashMap<ErasedContextId, crate::winit::RoundedClipPrograms>,
|
||||
pub(crate) overview_scrims: HashMap<OutputId, crate::winit::OverviewScrim>,
|
||||
pub(crate) material_programs: HashMap<ErasedContextId, crate::winit::MaterialProgram>,
|
||||
pub(crate) backdrop_programs: HashMap<ErasedContextId, crate::winit::BackdropProgram>,
|
||||
pub(crate) material_buffers: HashMap<WlSurface, crate::winit::MaterialBuffers>,
|
||||
material_scene_generation: u64,
|
||||
pub(crate) blur_programs: HashMap<ErasedContextId, crate::winit::BlurProgram>,
|
||||
pub(crate) backdrop_generation: u64,
|
||||
closing_windows: HashMap<WindowId, ClosingAnimation>,
|
||||
viewport_animations: HashMap<WorkspaceId, AnimatedValue>,
|
||||
scrolling_world_x: HashMap<WindowId, (WorkspaceId, AnimatedValue)>,
|
||||
@@ -457,9 +457,9 @@ impl Ferese {
|
||||
rounded_clip_programs: HashMap::new(),
|
||||
overview_scrims: HashMap::new(),
|
||||
material_programs: HashMap::new(),
|
||||
backdrop_programs: HashMap::new(),
|
||||
material_buffers: HashMap::new(),
|
||||
material_scene_generation: 0,
|
||||
blur_programs: HashMap::new(),
|
||||
backdrop_generation: 0,
|
||||
closing_windows: HashMap::new(),
|
||||
viewport_animations: HashMap::new(),
|
||||
scrolling_world_x: HashMap::new(),
|
||||
@@ -930,6 +930,7 @@ impl Ferese {
|
||||
}
|
||||
|
||||
pub fn relayout(&mut self) {
|
||||
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||
self.arrange_layers();
|
||||
let mut previous_scrolling_world_x = std::mem::take(&mut self.scrolling_world_x);
|
||||
let pending_column_width_cycles = std::mem::take(&mut self.pending_column_width_cycles);
|
||||
@@ -1346,20 +1347,11 @@ impl Ferese {
|
||||
self.sync_window_stacking();
|
||||
|
||||
if active_animation {
|
||||
self.invalidate_material_scene();
|
||||
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||
}
|
||||
|
||||
active_animation
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_material_scene(&mut self) {
|
||||
self.material_scene_generation = self.material_scene_generation.wrapping_add(1);
|
||||
}
|
||||
|
||||
pub(crate) fn material_scene_generation(&self) -> u64 {
|
||||
self.material_scene_generation
|
||||
}
|
||||
|
||||
pub(crate) fn animations_enabled(&self) -> bool {
|
||||
self.animations_enabled
|
||||
}
|
||||
|
||||
+328
-593
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user