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
@@ -34,8 +34,7 @@ decorations). Leaving fullscreen restores the previous maximized/tiled state.
|
|||||||
|
|
||||||
Set `FERESE_TRACE_PERFORMANCE=1` to emit five-second per-output summaries under
|
Set `FERESE_TRACE_PERFORMANCE=1` to emit five-second per-output summaries under
|
||||||
the `ferese::render` tracing target. Summaries include rendered frames, damaged
|
the `ferese::render` tracing target. Summaries include rendered frames, damaged
|
||||||
pixels, average and longest render time, DRM missed-deadline totals, blur-active
|
pixels, average and longest render time, and DRM missed-deadline totals.
|
||||||
frames, blur-region count, and sampled blur pixels.
|
|
||||||
|
|
||||||
The native-client soak runner and its nested/DRM procedure are documented in
|
The native-client soak runner and its nested/DRM procedure are documented in
|
||||||
[docs/native-soak-testing.md](docs/native-soak-testing.md).
|
[docs/native-soak-testing.md](docs/native-soak-testing.md).
|
||||||
@@ -48,7 +47,7 @@ cargo build -p ferese-effects-probe
|
|||||||
cargo run -p ferese -- --grant-effects -- target/debug/ferese-effects-probe
|
cargo run -p ferese -- --grant-effects -- target/debug/ferese-effects-probe
|
||||||
```
|
```
|
||||||
|
|
||||||
Add `--preview` to keep a glass popover visible in the nested compositor:
|
Add `--preview` to keep a solid popover visible in the nested compositor:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build -p ferese-effects-probe
|
cargo build -p ferese-effects-probe
|
||||||
@@ -57,8 +56,7 @@ cargo run -p ferese -- --backend nested --grant-effects -- \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Press `Super+Enter` in the Ferese window to open `foot`, then move or update the
|
Press `Super+Enter` in the Ferese window to open `foot`, then move or update the
|
||||||
terminal beneath the popover to see backdrop capture, blur, tint, and damage
|
terminal beneath the popover to inspect stacking, rounded clipping, and shadows. Stop the preview with `Ctrl+C` in the host terminal.
|
||||||
invalidation. Stop the preview with `Ctrl+C` in the host terminal.
|
|
||||||
|
|
||||||
To verify that the private global is absent from the public Wayland socket:
|
To verify that the private global is absent from the public Wayland socket:
|
||||||
|
|
||||||
@@ -108,29 +106,25 @@ blur = 18.0
|
|||||||
opacity = 0.20
|
opacity = 0.20
|
||||||
|
|
||||||
[theme.material]
|
[theme.material]
|
||||||
style = "glass" # glass, translucent, or solid
|
style = "solid" # solid or translucent
|
||||||
|
blur_radius = 12.0 # logical pixels; translucent only, 0 disables blur
|
||||||
```
|
```
|
||||||
|
|
||||||
Glass roles use live backdrop sampling, a dark tint, diagonal lighting, inset
|
Material roles use the configured surface color and role-specific shadows.
|
||||||
edges, static overlay grain, and role-specific shadows. Configure them with:
|
Solid is the default. Translucent adds backdrop blur behind the same surface
|
||||||
|
color, without grain, glossy lighting, saturation boosts, or inset highlights.
|
||||||
|
The radius is clamped to 32 logical pixels. A shader/allocation failure falls
|
||||||
|
back to a plain translucent fill. Shell popovers report rounded card regions;
|
||||||
|
their outer padding and gaps stay transparent. Each surface shares one capture
|
||||||
|
across its cards, so cards do not sample already blurred neighboring cards.
|
||||||
|
Use `--preview --regions` with the effects probe to inspect two cards over a
|
||||||
|
patterned background, including the sharp gap between them.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[appearance]
|
[appearance]
|
||||||
corner_radius = 14.0
|
corner_radius = 14.0
|
||||||
reduced_effects = false # true selects reduced_blur or a stricter quality tier
|
|
||||||
|
|
||||||
[appearance.glass]
|
|
||||||
tint_color = "#1C202E"
|
|
||||||
blur_scale = 1.0 # clamped to 0.5–1.5; non-finite values are rejected
|
|
||||||
grain = true
|
|
||||||
quality = "full"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Quality tiers are `full`, `no_grain`, `no_saturation`, `reduced_blur`
|
|
||||||
(at most 12 logical pixels with fewer samples), `translucent`, and `solid`.
|
|
||||||
Each tier retains earlier reductions without reducing tint opacity or changing
|
|
||||||
layout. Backdrop shader or texture allocation failure falls back to translucent.
|
|
||||||
The legacy `surface.panel_elevated` role uses the panel tint with popover elevation.
|
|
||||||
Shell shadows use restrained logical-pixel presets: panel `1/5/4%`, popover
|
Shell shadows use restrained logical-pixel presets: panel `1/5/4%`, popover
|
||||||
and menu `2/8/7%`, HUD `1/4/4%`, and notification/modal `3/10/9%`
|
and menu `2/8/7%`, HUD `1/4/4%`, and notification/modal `3/10/9%`
|
||||||
(vertical offset / blur / opacity). Private effects capabilities
|
(vertical offset / blur / opacity). Private effects capabilities
|
||||||
@@ -173,8 +167,7 @@ shadow. Window spacing uses the workspace outer gap; `top_bar_window_gap` under
|
|||||||
shell surfaces also omit outlines. The dark startup fallback matches the material palette.
|
shell surfaces also omit outlines. The dark startup fallback matches the material palette.
|
||||||
|
|
||||||
The shell decodes wallpaper in parallel with startup and prefers the lightweight
|
The shell decodes wallpaper in parallel with startup and prefers the lightweight
|
||||||
software renderer, with GPU rendering available as a fallback. Glass effects
|
software renderer, with GPU rendering available as a fallback. Set `ICED_BACKEND=wgpu` to explicitly use GPU
|
||||||
still run on the compositor GPU. Set `ICED_BACKEND=wgpu` to explicitly use GPU
|
|
||||||
rendering for shell content. Use release builds when evaluating startup and
|
rendering for shell content. Use release builds when evaluating startup and
|
||||||
animation performance; debug image processing is considerably slower.
|
animation performance; debug image processing is considerably slower.
|
||||||
|
|
||||||
|
|||||||
+105
-89
@@ -113,12 +113,13 @@ pub struct ThemeSettings {
|
|||||||
pub accent_color: RgbaColor,
|
pub accent_color: RgbaColor,
|
||||||
pub shadow_color: RgbaColor,
|
pub shadow_color: RgbaColor,
|
||||||
pub surface_base_color: RgbaColor,
|
pub surface_base_color: RgbaColor,
|
||||||
|
pub panel_opacity: f64,
|
||||||
pub window_radius: f64,
|
pub window_radius: f64,
|
||||||
pub shadow_offset_y: f64,
|
pub shadow_offset_y: f64,
|
||||||
pub shadow_blur: f64,
|
pub shadow_blur: f64,
|
||||||
pub shadow_opacity: f64,
|
pub shadow_opacity: f64,
|
||||||
pub material_style: MaterialStyle,
|
pub material_style: MaterialStyle,
|
||||||
pub glass: GlassSettings,
|
pub backdrop_blur: f64,
|
||||||
pub material_radius: f64,
|
pub material_radius: f64,
|
||||||
pub panel_radius: f64,
|
pub panel_radius: f64,
|
||||||
}
|
}
|
||||||
@@ -126,64 +127,20 @@ pub struct ThemeSettings {
|
|||||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum MaterialStyle {
|
pub enum MaterialStyle {
|
||||||
#[default]
|
|
||||||
Glass,
|
|
||||||
Translucent,
|
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]
|
#[default]
|
||||||
Full,
|
|
||||||
NoGrain,
|
|
||||||
NoSaturation,
|
|
||||||
ReducedBlur,
|
|
||||||
Translucent,
|
|
||||||
Solid,
|
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)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
struct AppearanceConfig {
|
struct AppearanceConfig {
|
||||||
corner_radius: Option<f64>,
|
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)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
struct ThemeConfig {
|
struct ThemeConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
surface: ThemeSurfaceConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
colors: ThemeColorsConfig,
|
colors: ThemeColorsConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -195,9 +152,35 @@ struct ThemeConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
|
struct ThemeSurfaceConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
bar: ThemeBarConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize)]
|
||||||
|
struct ThemeBarConfig {
|
||||||
|
opacity: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
struct ThemeMaterialConfig {
|
struct ThemeMaterialConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
style: MaterialStyle,
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -861,22 +844,6 @@ impl Config {
|
|||||||
let shadow_opacity =
|
let shadow_opacity =
|
||||||
unit_theme_value(self.theme.shadow.soft.opacity, "shadow.soft.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(
|
let material_radius = nonnegative_theme_value(
|
||||||
self.appearance.corner_radius.unwrap_or(14.0),
|
self.appearance.corner_radius.unwrap_or(14.0),
|
||||||
"appearance.corner_radius",
|
"appearance.corner_radius",
|
||||||
@@ -891,12 +858,20 @@ impl Config {
|
|||||||
&self.theme.colors.surface_base,
|
&self.theme.colors.surface_base,
|
||||||
"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,
|
window_radius,
|
||||||
shadow_offset_y,
|
shadow_offset_y,
|
||||||
shadow_blur,
|
shadow_blur,
|
||||||
shadow_opacity,
|
shadow_opacity,
|
||||||
material_style: self.theme.material.style,
|
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,
|
material_radius,
|
||||||
panel_radius: nonnegative_theme_value(
|
panel_radius: nonnegative_theme_value(
|
||||||
self.theme.geometry.top_bar_radius,
|
self.theme.geometry.top_bar_radius,
|
||||||
@@ -1665,30 +1640,6 @@ mod tests {
|
|||||||
assert!(invalid.gap_config().is_err());
|
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]
|
#[test]
|
||||||
fn parses_and_validates_theme_window_tokens() {
|
fn parses_and_validates_theme_window_tokens() {
|
||||||
let configured = parse(
|
let configured = parse(
|
||||||
@@ -1707,12 +1658,13 @@ mod tests {
|
|||||||
accent_color: RgbaColor([170.0 / 255.0, 187.0 / 255.0, 0.8, 1.0]),
|
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]),
|
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,]),
|
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,
|
window_radius: 12.0,
|
||||||
shadow_offset_y: -2.0,
|
shadow_offset_y: -2.0,
|
||||||
shadow_blur: 24.0,
|
shadow_blur: 24.0,
|
||||||
shadow_opacity: 0.4,
|
shadow_opacity: 0.4,
|
||||||
material_style: MaterialStyle::Translucent,
|
material_style: MaterialStyle::Translucent,
|
||||||
glass: GlassSettings::default(),
|
backdrop_blur: 12.0,
|
||||||
material_radius: 14.0,
|
material_radius: 14.0,
|
||||||
panel_radius: 0.0,
|
panel_radius: 0.0,
|
||||||
}
|
}
|
||||||
@@ -1723,6 +1675,70 @@ mod tests {
|
|||||||
assert!(toml::from_str::<Config>("[theme.material]\nstyle = \"mist\"").is_err());
|
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]
|
#[test]
|
||||||
fn parses_input_and_touchpad_settings() {
|
fn parses_input_and_touchpad_settings() {
|
||||||
let config = parse(
|
let config = parse(
|
||||||
|
|||||||
+107
-180
@@ -16,10 +16,7 @@ use smithay::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Ferese,
|
Ferese, config::MaterialStyle, private_client::ClientCapabilities, state::ClientState,
|
||||||
config::{GlassQuality, GlassSettings, MaterialStyle},
|
|
||||||
private_client::ClientCapabilities,
|
|
||||||
state::ClientState,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -37,75 +34,27 @@ pub(crate) enum SemanticRole {
|
|||||||
pub(crate) struct ResolvedMaterial {
|
pub(crate) struct ResolvedMaterial {
|
||||||
pub style: MaterialStyle,
|
pub style: MaterialStyle,
|
||||||
pub opacity: f32,
|
pub opacity: f32,
|
||||||
pub blur: f32,
|
|
||||||
pub saturation: f32,
|
|
||||||
pub brightness: f32,
|
|
||||||
pub noise: f32,
|
|
||||||
pub shadow: [f64; 3],
|
pub shadow: [f64; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn resolve_material(
|
pub(crate) fn resolve_material(role: SemanticRole, style: MaterialStyle) -> ResolvedMaterial {
|
||||||
role: SemanticRole,
|
let shadow = match role {
|
||||||
style: MaterialStyle,
|
SemanticRole::Panel => [1.0, 5.0, 0.04],
|
||||||
settings: GlassSettings,
|
SemanticRole::PanelElevated | SemanticRole::Popover | SemanticRole::Menu => {
|
||||||
) -> ResolvedMaterial {
|
[2.0, 8.0, 0.07]
|
||||||
// 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,
|
|
||||||
}
|
}
|
||||||
} else {
|
SemanticRole::Hud => [1.0, 4.0, 0.04],
|
||||||
opacity
|
SemanticRole::Notification | SemanticRole::Modal => [3.0, 10.0, 0.09],
|
||||||
};
|
};
|
||||||
let style = if style == MaterialStyle::Glass {
|
let opacity = match (style, role) {
|
||||||
match settings.quality {
|
(MaterialStyle::Solid, _) => 1.0,
|
||||||
GlassQuality::Solid => MaterialStyle::Solid,
|
(_, SemanticRole::Panel) => 0.78,
|
||||||
GlassQuality::Translucent => MaterialStyle::Translucent,
|
(_, SemanticRole::Hud | SemanticRole::Modal) => 0.88,
|
||||||
_ => style,
|
_ => 0.84,
|
||||||
}
|
|
||||||
} else {
|
|
||||||
style
|
|
||||||
};
|
};
|
||||||
let glass = style == MaterialStyle::Glass;
|
|
||||||
ResolvedMaterial {
|
ResolvedMaterial {
|
||||||
style,
|
style,
|
||||||
opacity: if style == MaterialStyle::Solid {
|
opacity,
|
||||||
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
|
|
||||||
},
|
|
||||||
shadow,
|
shadow,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +64,7 @@ struct SurfaceEffectsState {
|
|||||||
attached: AtomicBool,
|
attached: AtomicBool,
|
||||||
generation: AtomicU64,
|
generation: AtomicU64,
|
||||||
role: Mutex<Option<SemanticRole>>,
|
role: Mutex<Option<SemanticRole>>,
|
||||||
|
regions: Mutex<Option<Vec<[i32; 5]>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SurfaceEffectsState {
|
impl SurfaceEffectsState {
|
||||||
@@ -123,6 +73,7 @@ impl SurfaceEffectsState {
|
|||||||
attached: AtomicBool::new(false),
|
attached: AtomicBool::new(false),
|
||||||
generation: AtomicU64::new(0),
|
generation: AtomicU64::new(0),
|
||||||
role: Mutex::new(None),
|
role: Mutex::new(None),
|
||||||
|
regions: Mutex::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +103,39 @@ impl SurfaceEffectsUserData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn init_global(display: &DisplayHandle) {
|
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.
|
// 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,
|
WEnum::Unknown(_) | WEnum::Value(_) => return,
|
||||||
};
|
};
|
||||||
if set_surface_role(&surface, Some(role)) {
|
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);
|
crate::backends::direct::render_all(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ferese_surface_effects_v1::Request::ClearRole => {
|
ferese_surface_effects_v1::Request::ClearRole => {
|
||||||
if set_surface_role(&surface, None) {
|
if set_surface_role(&surface, None) {
|
||||||
state.invalidate_material_scene();
|
|
||||||
crate::backends::direct::render_all(state);
|
crate::backends::direct::render_all(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ferese_surface_effects_v1::Request::Destroy => {
|
ferese_surface_effects_v1::Request::Destroy => {
|
||||||
if detach(&surface) {
|
if detach(&surface) {
|
||||||
state.invalidate_material_scene();
|
|
||||||
crate::backends::direct::render_all(state);
|
crate::backends::direct::render_all(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,6 +310,31 @@ fn detach(surface: &WlSurface) -> bool {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn role_changes_advance_generation_only_when_material_changes() {
|
fn role_changes_advance_generation_only_when_material_changes() {
|
||||||
let effects = SurfaceEffectsState::new();
|
let effects = SurfaceEffectsState::new();
|
||||||
@@ -324,118 +349,20 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn styles_preserve_semantics_but_disable_unsupported_effects() {
|
fn solid_materials_are_opaque_and_keep_role_specific_elevation() {
|
||||||
let glass = resolve_material(
|
for role in [
|
||||||
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(
|
|
||||||
SemanticRole::Panel,
|
SemanticRole::Panel,
|
||||||
MaterialStyle::Glass,
|
|
||||||
GlassSettings::default(),
|
|
||||||
);
|
|
||||||
let popover = resolve_material(
|
|
||||||
SemanticRole::Popover,
|
SemanticRole::Popover,
|
||||||
MaterialStyle::Glass,
|
SemanticRole::Menu,
|
||||||
GlassSettings::default(),
|
SemanticRole::Hud,
|
||||||
);
|
SemanticRole::Notification,
|
||||||
|
SemanticRole::Modal,
|
||||||
assert_eq!(popover.opacity, panel.opacity);
|
] {
|
||||||
assert!(popover.blur < panel.blur);
|
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) {
|
fn commit(&mut self, surface: &WlSurface) {
|
||||||
|
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||||
on_commit_buffer_handler::<Self>(surface);
|
on_commit_buffer_handler::<Self>(surface);
|
||||||
self.invalidate_material_scene();
|
|
||||||
if !is_sync_subsurface(surface) {
|
if !is_sync_subsurface(surface) {
|
||||||
let mut root = surface.clone();
|
let mut root = surface.clone();
|
||||||
while let Some(parent) = get_parent(&root) {
|
while let Some(parent) = get_parent(&root) {
|
||||||
@@ -63,9 +63,7 @@ impl CompositorHandler for Ferese {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn destroyed(&mut self, surface: &WlSurface) {
|
fn destroyed(&mut self, surface: &WlSurface) {
|
||||||
// A removed popup changes the backdrop even without a final buffer
|
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||||
// commit. Invalidate expanded sampling regions as well as its bounds.
|
|
||||||
self.invalidate_material_scene();
|
|
||||||
crate::backends::direct::render_all(self);
|
crate::backends::direct::render_all(self);
|
||||||
if self.idle_inhibitors.remove(surface).is_some() {
|
if self.idle_inhibitors.remove(surface).is_some() {
|
||||||
self.idle_notifier_state
|
self.idle_notifier_state
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ use smithay::utils::{Physical, Rectangle};
|
|||||||
const REPORT_INTERVAL: Duration = Duration::from_secs(5);
|
const REPORT_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
pub(crate) struct FrameEffectMetrics {
|
pub(crate) struct FrameEffectMetrics;
|
||||||
pub(crate) blur_regions: u64,
|
|
||||||
pub(crate) blur_sample_pixels: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct RenderMetrics {
|
pub(crate) struct RenderMetrics {
|
||||||
@@ -19,9 +16,6 @@ pub(crate) struct RenderMetrics {
|
|||||||
damaged_pixels: u64,
|
damaged_pixels: u64,
|
||||||
total_render_time: Duration,
|
total_render_time: Duration,
|
||||||
longest_render_time: Duration,
|
longest_render_time: Duration,
|
||||||
blur_frames: u64,
|
|
||||||
blur_regions: u64,
|
|
||||||
blur_sample_pixels: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderMetrics {
|
impl RenderMetrics {
|
||||||
@@ -42,9 +36,6 @@ impl RenderMetrics {
|
|||||||
damaged_pixels: 0,
|
damaged_pixels: 0,
|
||||||
total_render_time: Duration::ZERO,
|
total_render_time: Duration::ZERO,
|
||||||
longest_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,
|
render_time: Duration,
|
||||||
damage: &[Rectangle<i32, Physical>],
|
damage: &[Rectangle<i32, Physical>],
|
||||||
missed_deadlines: u64,
|
missed_deadlines: u64,
|
||||||
effects: FrameEffectMetrics,
|
_effects: FrameEffectMetrics,
|
||||||
) {
|
) {
|
||||||
if !self.enabled {
|
if !self.enabled {
|
||||||
return;
|
return;
|
||||||
@@ -63,9 +54,6 @@ impl RenderMetrics {
|
|||||||
self.damaged_pixels += damage.iter().map(rectangle_area).sum::<u64>();
|
self.damaged_pixels += damage.iter().map(rectangle_area).sum::<u64>();
|
||||||
self.total_render_time += render_time;
|
self.total_render_time += render_time;
|
||||||
self.longest_render_time = self.longest_render_time.max(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 now = Instant::now();
|
||||||
let elapsed = now.saturating_duration_since(self.interval_started);
|
let elapsed = now.saturating_duration_since(self.interval_started);
|
||||||
@@ -87,9 +75,6 @@ impl RenderMetrics {
|
|||||||
average_render_us,
|
average_render_us,
|
||||||
longest_render_us = self.longest_render_time.as_micros(),
|
longest_render_us = self.longest_render_time.as_micros(),
|
||||||
missed_deadlines,
|
missed_deadlines,
|
||||||
blur_frames = self.blur_frames,
|
|
||||||
blur_regions = self.blur_regions,
|
|
||||||
blur_sample_pixels = self.blur_sample_pixels,
|
|
||||||
"render performance"
|
"render performance"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -98,9 +83,6 @@ impl RenderMetrics {
|
|||||||
self.damaged_pixels = 0;
|
self.damaged_pixels = 0;
|
||||||
self.total_render_time = Duration::ZERO;
|
self.total_render_time = Duration::ZERO;
|
||||||
self.longest_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()),
|
Rectangle::new((10, 10).into(), (20, 10).into()),
|
||||||
],
|
],
|
||||||
0,
|
0,
|
||||||
FrameEffectMetrics {
|
FrameEffectMetrics,
|
||||||
blur_regions: 2,
|
|
||||||
blur_sample_pixels: 4_096,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(metrics.rendered_frames, 1);
|
assert_eq!(metrics.rendered_frames, 1);
|
||||||
assert_eq!(metrics.damaged_pixels, 5_200);
|
assert_eq!(metrics.damaged_pixels, 5_200);
|
||||||
assert_eq!(metrics.total_render_time, Duration::from_millis(2));
|
assert_eq!(metrics.total_render_time, Duration::from_millis(2));
|
||||||
assert_eq!(metrics.longest_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) rounded_clip_programs: HashMap<ErasedContextId, crate::winit::RoundedClipPrograms>,
|
||||||
pub(crate) overview_scrims: HashMap<OutputId, crate::winit::OverviewScrim>,
|
pub(crate) overview_scrims: HashMap<OutputId, crate::winit::OverviewScrim>,
|
||||||
pub(crate) material_programs: HashMap<ErasedContextId, crate::winit::MaterialProgram>,
|
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>,
|
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>,
|
closing_windows: HashMap<WindowId, ClosingAnimation>,
|
||||||
viewport_animations: HashMap<WorkspaceId, AnimatedValue>,
|
viewport_animations: HashMap<WorkspaceId, AnimatedValue>,
|
||||||
scrolling_world_x: HashMap<WindowId, (WorkspaceId, AnimatedValue)>,
|
scrolling_world_x: HashMap<WindowId, (WorkspaceId, AnimatedValue)>,
|
||||||
@@ -457,9 +457,9 @@ impl Ferese {
|
|||||||
rounded_clip_programs: HashMap::new(),
|
rounded_clip_programs: HashMap::new(),
|
||||||
overview_scrims: HashMap::new(),
|
overview_scrims: HashMap::new(),
|
||||||
material_programs: HashMap::new(),
|
material_programs: HashMap::new(),
|
||||||
backdrop_programs: HashMap::new(),
|
|
||||||
material_buffers: HashMap::new(),
|
material_buffers: HashMap::new(),
|
||||||
material_scene_generation: 0,
|
blur_programs: HashMap::new(),
|
||||||
|
backdrop_generation: 0,
|
||||||
closing_windows: HashMap::new(),
|
closing_windows: HashMap::new(),
|
||||||
viewport_animations: HashMap::new(),
|
viewport_animations: HashMap::new(),
|
||||||
scrolling_world_x: HashMap::new(),
|
scrolling_world_x: HashMap::new(),
|
||||||
@@ -930,6 +930,7 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn relayout(&mut self) {
|
pub fn relayout(&mut self) {
|
||||||
|
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||||
self.arrange_layers();
|
self.arrange_layers();
|
||||||
let mut previous_scrolling_world_x = std::mem::take(&mut self.scrolling_world_x);
|
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);
|
let pending_column_width_cycles = std::mem::take(&mut self.pending_column_width_cycles);
|
||||||
@@ -1346,20 +1347,11 @@ impl Ferese {
|
|||||||
self.sync_window_stacking();
|
self.sync_window_stacking();
|
||||||
|
|
||||||
if active_animation {
|
if active_animation {
|
||||||
self.invalidate_material_scene();
|
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
active_animation
|
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 {
|
pub(crate) fn animations_enabled(&self) -> bool {
|
||||||
self.animations_enabled
|
self.animations_enabled
|
||||||
}
|
}
|
||||||
|
|||||||
+328
-593
File diff suppressed because it is too large.
Load diff
@@ -22,7 +22,7 @@
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
</copyright>
|
</copyright>
|
||||||
|
|
||||||
<interface name="ferese_effects_manager_v1" version="1">
|
<interface name="ferese_effects_manager_v1" version="2">
|
||||||
<description summary="assign semantic material roles to surfaces">
|
<description summary="assign semantic material roles to surfaces">
|
||||||
This private global is visible only to clients granted the Ferese effects
|
This private global is visible only to clients granted the Ferese effects
|
||||||
capability. Roles describe compositor-owned appearance policy and do not
|
capability. Roles describe compositor-owned appearance policy and do not
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
</enum>
|
</enum>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
<interface name="ferese_surface_effects_v1" version="1">
|
<interface name="ferese_surface_effects_v1" version="2">
|
||||||
<description summary="semantic material role for one surface">
|
<description summary="semantic material role for one surface">
|
||||||
The role affects only compositor rendering. It does not change surface
|
The role affects only compositor rendering. It does not change surface
|
||||||
geometry, input, focus, stacking, or the surface's Wayland role.
|
geometry, input, focus, stacking, or the surface's Wayland role.
|
||||||
@@ -78,5 +78,15 @@
|
|||||||
<entry name="hud" value="5" summary="heads-up display surface"/>
|
<entry name="hud" value="5" summary="heads-up display surface"/>
|
||||||
<entry name="modal" value="6" summary="modal surface"/>
|
<entry name="modal" value="6" summary="modal surface"/>
|
||||||
</enum>
|
</enum>
|
||||||
|
|
||||||
|
<request name="set_regions" since="2">
|
||||||
|
<description summary="restrict material to rounded surface-local rectangles">
|
||||||
|
An array of native-endian signed 32-bit tuples (x, y, width, height,
|
||||||
|
radius), measured in logical pixels. At most 32 rectangles are allowed.
|
||||||
|
An empty array disables material painting without clearing the role.
|
||||||
|
Surfaces which never send this request retain full-surface materials.
|
||||||
|
</description>
|
||||||
|
<arg name="regions" type="array"/>
|
||||||
|
</request>
|
||||||
</interface>
|
</interface>
|
||||||
</protocol>
|
</protocol>
|
||||||
@@ -86,7 +86,7 @@ impl Default for ShellTheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ShellTheme {
|
impl ShellTheme {
|
||||||
// The bar has its own foreground tokens, matched to its dark glass surface.
|
// The bar has its own foreground tokens, matched to its surface.
|
||||||
pub(crate) fn for_bar(mut self) -> Self {
|
pub(crate) fn for_bar(mut self) -> Self {
|
||||||
self.text_primary = self.bar_text_primary;
|
self.text_primary = self.bar_text_primary;
|
||||||
self.text_muted = self.bar_text_muted;
|
self.text_muted = self.bar_text_muted;
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ use cosmic::iced::platform_specific::{
|
|||||||
shell::commands::layer_surface::{Anchor, KeyboardInteractivity, Layer},
|
shell::commands::layer_surface::{Anchor, KeyboardInteractivity, Layer},
|
||||||
};
|
};
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
Background, Border, Color, ContentFit, Event, Length, Limits, Shadow, Subscription, Vector,
|
Background, Border, Color, ContentFit, Event, Length, Limits, Subscription, window,
|
||||||
window,
|
|
||||||
};
|
};
|
||||||
use cosmic::theme;
|
use cosmic::theme;
|
||||||
use cosmic::widget::{button, container, icon, image, row};
|
use cosmic::widget::{button, container, icon, image, row};
|
||||||
@@ -218,6 +217,9 @@ impl cosmic::Application for FereseShell {
|
|||||||
anchor: Anchor::TOP | Anchor::RIGHT | Anchor::BOTTOM | Anchor::LEFT,
|
anchor: Anchor::TOP | Anchor::RIGHT | Anchor::BOTTOM | Anchor::LEFT,
|
||||||
output: IcedOutput::Active,
|
output: IcedOutput::Active,
|
||||||
namespace: "ferese-shell-wallpaper".to_owned(),
|
namespace: "ferese-shell-wallpaper".to_owned(),
|
||||||
|
// Wallpaper covers the full output, including beneath the bar
|
||||||
|
// and its floating margins. Zero would use the remaining workspace.
|
||||||
|
exclusive_zone: -1,
|
||||||
size: Some((None, None)),
|
size: Some((None, None)),
|
||||||
size_limits: Limits::NONE,
|
size_limits: Limits::NONE,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -294,6 +296,14 @@ impl cosmic::Application for FereseShell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
|
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
|
||||||
|
if let Some(menu) = &self.menu
|
||||||
|
&& let Some(effects) = &menu.effects
|
||||||
|
{
|
||||||
|
let regions = menu.regions.lock().unwrap().clone();
|
||||||
|
if let Err(error) = effects.set_regions(®ions) {
|
||||||
|
eprintln!("ferese-shell: could not update card materials: {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
match message {
|
match message {
|
||||||
Message::WallpaperLoaded(result) => {
|
Message::WallpaperLoaded(result) => {
|
||||||
match result {
|
match result {
|
||||||
@@ -312,10 +322,7 @@ impl cosmic::Application for FereseShell {
|
|||||||
&& menu.id == id
|
&& menu.id == id
|
||||||
&& menu.effects.is_none()
|
&& menu.effects.is_none()
|
||||||
{
|
{
|
||||||
match EffectsBinding::attach_role(
|
match EffectsBinding::attach_role(&surface, menu.kind.material_role()) {
|
||||||
&surface,
|
|
||||||
Some(ferese_surface_effects_v1::Role::Popover),
|
|
||||||
) {
|
|
||||||
Ok(binding) => menu.effects = Some(binding),
|
Ok(binding) => menu.effects = Some(binding),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("ferese-shell: popover material unavailable: {error}")
|
eprintln!("ferese-shell: popover material unavailable: {error}")
|
||||||
@@ -467,7 +474,7 @@ impl FereseShell {
|
|||||||
if menu.effects.is_none() {
|
if menu.effects.is_none() {
|
||||||
match EffectsBinding::attach_role(
|
match EffectsBinding::attach_role(
|
||||||
&surface,
|
&surface,
|
||||||
Some(ferese_surface_effects_v1::Role::Popover),
|
menu.kind.material_role(),
|
||||||
) {
|
) {
|
||||||
Ok(binding) => menu.effects = Some(binding),
|
Ok(binding) => menu.effects = Some(binding),
|
||||||
Err(error) => eprintln!(
|
Err(error) => eprintln!(
|
||||||
@@ -481,7 +488,7 @@ impl FereseShell {
|
|||||||
if menu.effects.is_none() {
|
if menu.effects.is_none() {
|
||||||
match EffectsBinding::attach_role(
|
match EffectsBinding::attach_role(
|
||||||
&surface,
|
&surface,
|
||||||
Some(ferese_surface_effects_v1::Role::Popover),
|
menu.kind.material_role(),
|
||||||
) {
|
) {
|
||||||
Ok(binding) => menu.effects = Some(binding),
|
Ok(binding) => menu.effects = Some(binding),
|
||||||
Err(error) => eprintln!(
|
Err(error) => eprintln!(
|
||||||
@@ -513,10 +520,7 @@ impl FereseShell {
|
|||||||
))) if self.menu.as_ref().is_some_and(|menu| menu.id == frame_id) => {
|
))) if self.menu.as_ref().is_some_and(|menu| menu.id == frame_id) => {
|
||||||
if let Some(menu) = &mut self.menu {
|
if let Some(menu) = &mut self.menu {
|
||||||
if menu.effects.is_none() {
|
if menu.effects.is_none() {
|
||||||
match EffectsBinding::attach_role(
|
match EffectsBinding::attach_role(&surface, menu.kind.material_role()) {
|
||||||
&surface,
|
|
||||||
Some(ferese_surface_effects_v1::Role::Popover),
|
|
||||||
) {
|
|
||||||
Ok(binding) => menu.effects = Some(binding),
|
Ok(binding) => menu.effects = Some(binding),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("ferese-shell: popover material unavailable: {error}")
|
eprintln!("ferese-shell: popover material unavailable: {error}")
|
||||||
@@ -634,10 +638,13 @@ impl FereseShell {
|
|||||||
});
|
});
|
||||||
windows = windows.push(
|
windows = windows.push(
|
||||||
button::custom(
|
button::custom(
|
||||||
text(label)
|
container(
|
||||||
.size(bar.text_size)
|
text(label)
|
||||||
.wrapping(cosmic::iced::core::text::Wrapping::None)
|
.size(bar.text_size)
|
||||||
.class(theme::Text::Color(foreground)),
|
.wrapping(cosmic::iced::core::text::Wrapping::None)
|
||||||
|
.class(theme::Text::Color(foreground)),
|
||||||
|
)
|
||||||
|
.center_y(bar.control_height),
|
||||||
)
|
)
|
||||||
.padding([0, 8])
|
.padding([0, 8])
|
||||||
.height(bar.control_height)
|
.height(bar.control_height)
|
||||||
@@ -664,7 +671,7 @@ impl FereseShell {
|
|||||||
)
|
)
|
||||||
.padding([0, 6]);
|
.padding([0, 6]);
|
||||||
let right = row![self.view_status_bar(), clock]
|
let right = row![self.view_status_bar(), clock]
|
||||||
.spacing(10)
|
.spacing(6)
|
||||||
.align_y(cosmic::iced::Alignment::Center);
|
.align_y(cosmic::iced::Alignment::Center);
|
||||||
let content = row![
|
let content = row![
|
||||||
container(left).width(Length::Fill),
|
container(left).width(Length::Fill),
|
||||||
@@ -860,7 +867,23 @@ fn display_app_name(window: &control::WindowSnapshot) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn current_time() -> String {
|
fn current_time() -> String {
|
||||||
Zoned::now().strftime("%a · %H:%M").to_string()
|
format_bar_time(&Zoned::now())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_bar_time(now: &Zoned) -> String {
|
||||||
|
let months = [
|
||||||
|
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sept", "oct", "nov", "dec",
|
||||||
|
];
|
||||||
|
let hour = now.hour();
|
||||||
|
let hour12 = if hour % 12 == 0 { 12 } else { hour % 12 };
|
||||||
|
format!(
|
||||||
|
"{} {}, {}:{:02} {}",
|
||||||
|
now.day(),
|
||||||
|
months[(now.month() - 1) as usize],
|
||||||
|
hour12,
|
||||||
|
now.minute(),
|
||||||
|
if hour < 12 { "am" } else { "pm" }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compact_window_label(window: &crate::control::WindowSnapshot) -> String {
|
fn compact_window_label(window: &crate::control::WindowSnapshot) -> String {
|
||||||
@@ -963,6 +986,7 @@ struct EffectsBinding {
|
|||||||
_manager: FereseEffectsManagerV1,
|
_manager: FereseEffectsManagerV1,
|
||||||
surface: FereseSurfaceEffectsV1,
|
surface: FereseSurfaceEffectsV1,
|
||||||
_queue: EventQueue<EffectsState>,
|
_queue: EventQueue<EffectsState>,
|
||||||
|
regions: std::cell::RefCell<Option<Vec<[i32; 5]>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EffectsBinding {
|
impl EffectsBinding {
|
||||||
@@ -987,7 +1011,7 @@ impl EffectsBinding {
|
|||||||
let connection = Connection::from_backend(backend);
|
let connection = Connection::from_backend(backend);
|
||||||
let (globals, queue) = registry_queue_init::<EffectsState>(&connection)?;
|
let (globals, queue) = registry_queue_init::<EffectsState>(&connection)?;
|
||||||
let qh = queue.handle();
|
let qh = queue.handle();
|
||||||
let manager = globals.bind::<FereseEffectsManagerV1, _, _>(&qh, 1..=1, ())?;
|
let manager = globals.bind::<FereseEffectsManagerV1, _, _>(&qh, 1..=2, ())?;
|
||||||
let effects = manager.get_surface_effects(surface, &qh, ());
|
let effects = manager.get_surface_effects(surface, &qh, ());
|
||||||
|
|
||||||
if let Some(role) = role {
|
if let Some(role) = role {
|
||||||
@@ -1002,9 +1026,31 @@ impl EffectsBinding {
|
|||||||
_manager: manager,
|
_manager: manager,
|
||||||
surface: effects,
|
surface: effects,
|
||||||
_queue: queue,
|
_queue: queue,
|
||||||
|
regions: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_regions(&self, regions: &[[i32; 5]]) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if self.surface.version() < 2 || self.regions.borrow().as_deref() == Some(regions) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.surface.set_regions(
|
||||||
|
regions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|r| r.iter().flat_map(|v| v.to_ne_bytes()))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
if regions.is_empty() {
|
||||||
|
self.surface.clear_role();
|
||||||
|
} else {
|
||||||
|
self.surface
|
||||||
|
.set_role(ferese_surface_effects_v1::Role::Popover);
|
||||||
|
}
|
||||||
|
self.connection.flush()?;
|
||||||
|
*self.regions.borrow_mut() = Some(regions.to_vec());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn set_visible(&self, visible: bool) -> Result<(), Box<dyn std::error::Error>> {
|
fn set_visible(&self, visible: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if visible {
|
if visible {
|
||||||
self.surface
|
self.surface
|
||||||
@@ -1037,6 +1083,25 @@ delegate_noop!(EffectsState: ignore FereseSurfaceEffectsV1);
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn clock_uses_lowercase_date_and_twelve_hour_time() {
|
||||||
|
for (stamp, expected) in [
|
||||||
|
(
|
||||||
|
"2026-09-28T21:32:00+01:00[Africa/Lagos]",
|
||||||
|
"28 sept, 9:32 pm",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"2026-09-28T00:05:00+01:00[Africa/Lagos]",
|
||||||
|
"28 sept, 12:05 am",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"2026-09-28T12:00:00+01:00[Africa/Lagos]",
|
||||||
|
"28 sept, 12:00 pm",
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
assert_eq!(super::format_bar_time(&stamp.parse().unwrap()), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::control::{OutputSnapshot, WindowSnapshot};
|
use crate::control::{OutputSnapshot, WindowSnapshot};
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,23 @@ use cosmic::iced::advanced::{Clipboard, Layout, Shell, Widget, layout, mouse, re
|
|||||||
use cosmic::iced::{Event, Length, Rectangle, Size, Transformation, Vector};
|
use cosmic::iced::{Event, Length, Rectangle, Size, Transformation, Vector};
|
||||||
use cosmic::{Element, Theme};
|
use cosmic::{Element, Theme};
|
||||||
|
|
||||||
pub fn animated<'a, M: 'a>(content: Element<'a, M>, progress: f32) -> Element<'a, M> {
|
pub type Regions = std::sync::Arc<std::sync::Mutex<Vec<[i32; 5]>>>;
|
||||||
Element::new(Motion { content, progress })
|
|
||||||
|
pub fn animated<'a, M: 'a>(
|
||||||
|
content: Element<'a, M>,
|
||||||
|
progress: f32,
|
||||||
|
regions: Regions,
|
||||||
|
) -> Element<'a, M> {
|
||||||
|
Element::new(Motion {
|
||||||
|
content,
|
||||||
|
progress,
|
||||||
|
regions,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
struct Motion<'a, M> {
|
struct Motion<'a, M> {
|
||||||
content: Element<'a, M>,
|
content: Element<'a, M>,
|
||||||
progress: f32,
|
progress: f32,
|
||||||
|
regions: Regions,
|
||||||
}
|
}
|
||||||
impl<M> Motion<'_, M> {
|
impl<M> Motion<'_, M> {
|
||||||
fn translation(&self) -> Vector {
|
fn translation(&self) -> Vector {
|
||||||
@@ -56,6 +67,35 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Motion<'_, M> {
|
|||||||
shell: &mut Shell<'_, M>,
|
shell: &mut Shell<'_, M>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
|
let mut collector = CollectRegions(Vec::new());
|
||||||
|
self.content
|
||||||
|
.as_widget_mut()
|
||||||
|
.operate(tree, layout, renderer, &mut collector);
|
||||||
|
if collector.0.is_empty() {
|
||||||
|
// A single-content popover (e.g. battery) has one inner card, not
|
||||||
|
// a full-surface rectangle including the outer transparent padding.
|
||||||
|
if let Some(content) = layout.children().next() {
|
||||||
|
collector.0.push(content.bounds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let origin = layout.bounds().position();
|
||||||
|
let translation = self.translation();
|
||||||
|
let regions = collector
|
||||||
|
.0
|
||||||
|
.into_iter()
|
||||||
|
.take(32)
|
||||||
|
.filter(|r| r.width > 0.0 && r.height > 0.0)
|
||||||
|
.map(|r| {
|
||||||
|
[
|
||||||
|
(r.x - origin.x + translation.x).round() as i32,
|
||||||
|
(r.y - origin.y + translation.y).round() as i32,
|
||||||
|
r.width.round() as i32,
|
||||||
|
r.height.round() as i32,
|
||||||
|
11,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
*self.regions.lock().unwrap() = regions;
|
||||||
let cursor = self.cursor(cursor);
|
let cursor = self.cursor(cursor);
|
||||||
self.content.as_widget_mut().update(
|
self.content.as_widget_mut().update(
|
||||||
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
|
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||||
@@ -113,3 +153,52 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Motion<'_, M> {
|
|||||||
.operate(tree, layout, renderer, operation);
|
.operate(tree, layout, renderer, operation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CollectRegions(Vec<Rectangle>);
|
||||||
|
impl widget::Operation for CollectRegions {
|
||||||
|
fn traverse(&mut self, children: &mut dyn FnMut(&mut dyn widget::Operation)) {
|
||||||
|
children(self);
|
||||||
|
}
|
||||||
|
fn container(&mut self, id: Option<&widget::Id>, bounds: Rectangle) {
|
||||||
|
if id == Some(&widget::Id::new("ferese-blur-card")) {
|
||||||
|
let contains = |outer: &Rectangle, inner: &Rectangle| {
|
||||||
|
outer.x <= inner.x
|
||||||
|
&& outer.y <= inner.y
|
||||||
|
&& outer.x + outer.width >= inner.x + inner.width
|
||||||
|
&& outer.y + outer.height >= inner.y + inner.height
|
||||||
|
};
|
||||||
|
// Nested controls belong to their enclosing card's single blur pass.
|
||||||
|
if self.0.iter().any(|outer| contains(outer, &bounds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.0.retain(|inner| !contains(&bounds, inner));
|
||||||
|
self.0.push(bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn material_regions_include_cards_but_not_the_parent_or_gaps() {
|
||||||
|
let mut collector = CollectRegions(Vec::new());
|
||||||
|
let id = widget::Id::new("ferese-blur-card");
|
||||||
|
widget::Operation::container(
|
||||||
|
&mut collector,
|
||||||
|
None,
|
||||||
|
Rectangle::new((0.0, 0.0).into(), (320.0, 200.0).into()),
|
||||||
|
);
|
||||||
|
let first = Rectangle::new((12.0, 12.0).into(), (140.0, 80.0).into());
|
||||||
|
let second = Rectangle::new((160.0, 12.0).into(), (140.0, 80.0).into());
|
||||||
|
widget::Operation::container(&mut collector, Some(&id), first);
|
||||||
|
widget::Operation::container(&mut collector, Some(&id), second);
|
||||||
|
assert_eq!(collector.0, vec![first, second]);
|
||||||
|
widget::Operation::container(
|
||||||
|
&mut collector,
|
||||||
|
Some(&id),
|
||||||
|
Rectangle::new((20.0, 20.0).into(), (40.0, 40.0).into()),
|
||||||
|
);
|
||||||
|
assert_eq!(collector.0, vec![first, second]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,15 +17,9 @@ pub enum Menu {
|
|||||||
System,
|
System,
|
||||||
}
|
}
|
||||||
impl Menu {
|
impl Menu {
|
||||||
fn title(self) -> &'static str {
|
pub(super) fn material_role(self) -> Option<ferese_surface_effects_v1::Role> {
|
||||||
match self {
|
// Popovers contain independent cards, with no shared material backing.
|
||||||
Self::Network => "Wi-Fi",
|
None
|
||||||
Self::Bluetooth => "Bluetooth",
|
|
||||||
Self::Audio => "Volume",
|
|
||||||
Self::Battery => "Battery",
|
|
||||||
Self::Notifications => "Notifications",
|
|
||||||
Self::System => "Control Center",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn available(self, status: &Snapshot) -> bool {
|
fn available(self, status: &Snapshot) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@@ -45,6 +39,7 @@ pub struct OpenMenu {
|
|||||||
opened: Instant,
|
opened: Instant,
|
||||||
pub confirm: Option<Action>,
|
pub confirm: Option<Action>,
|
||||||
pub effects: Option<EffectsBinding>,
|
pub effects: Option<EffectsBinding>,
|
||||||
|
pub regions: super::motion::Regions,
|
||||||
}
|
}
|
||||||
impl OpenMenu {
|
impl OpenMenu {
|
||||||
pub fn progress(&self) -> f32 {
|
pub fn progress(&self) -> f32 {
|
||||||
@@ -77,6 +72,7 @@ impl FereseShell {
|
|||||||
opened: Instant::now(),
|
opened: Instant::now(),
|
||||||
confirm: None,
|
confirm: None,
|
||||||
effects: None,
|
effects: None,
|
||||||
|
regions: Default::default(),
|
||||||
});
|
});
|
||||||
super::EFFECT_FRAME_PENDING.store(true, std::sync::atomic::Ordering::Relaxed);
|
super::EFFECT_FRAME_PENDING.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
self.status_error = None;
|
self.status_error = None;
|
||||||
@@ -150,7 +146,7 @@ impl FereseShell {
|
|||||||
pub fn view_status_bar(&self) -> Element<'_, cosmic::Action<Message>> {
|
pub fn view_status_bar(&self) -> Element<'_, cosmic::Action<Message>> {
|
||||||
let theme = self.config.theme.for_bar();
|
let theme = self.config.theme.for_bar();
|
||||||
let metrics = BarMetrics::from(theme);
|
let metrics = BarMetrics::from(theme);
|
||||||
let mut controls = row::with_capacity(6).spacing(3).align_y(Alignment::Center);
|
let mut controls = row::with_capacity(6).spacing(1).align_y(Alignment::Center);
|
||||||
for kind in [
|
for kind in [
|
||||||
Menu::System,
|
Menu::System,
|
||||||
Menu::Network,
|
Menu::Network,
|
||||||
@@ -210,7 +206,7 @@ impl FereseShell {
|
|||||||
.name(status_label(kind, &self.status))
|
.name(status_label(kind, &self.status))
|
||||||
.padding([
|
.padding([
|
||||||
0.0,
|
0.0,
|
||||||
((metrics.height - f32::from(metrics.icon_size)) * 0.5).max(7.0),
|
((metrics.height - f32::from(metrics.icon_size)) * 0.5).max(4.0),
|
||||||
])
|
])
|
||||||
.height(metrics.height)
|
.height(metrics.height)
|
||||||
.class(button_style(foreground, selected, 1.0))
|
.class(button_style(foreground, selected, 1.0))
|
||||||
@@ -238,15 +234,7 @@ impl FereseShell {
|
|||||||
let p = menu.progress();
|
let p = menu.progress();
|
||||||
let primary = color_with_opacity(theme.text_primary, p);
|
let primary = color_with_opacity(theme.text_primary, p);
|
||||||
let muted = color_with_opacity(theme.text_muted, p);
|
let muted = color_with_opacity(theme.text_muted, p);
|
||||||
// The bar has already established whether this connection supports
|
|
||||||
// Ferese materials. Keep a glass popup's client buffer transparent
|
|
||||||
// from its FIRST frame, not only after its own focus/effects event.
|
|
||||||
// Otherwise the initial opaque fallback can be captured beneath glass
|
|
||||||
// while popup registration and client repaint happen asynchronously.
|
|
||||||
let compositor_material = self.effects.is_some();
|
|
||||||
let mut rows = column::with_capacity(12).spacing(8).width(Length::Fill);
|
let mut rows = column::with_capacity(12).spacing(8).width(Length::Fill);
|
||||||
let mut heading =
|
|
||||||
row![text(menu.kind.title()).size(16).width(Length::Fill)].align_y(Alignment::Center);
|
|
||||||
let radio = match menu.kind {
|
let radio = match menu.kind {
|
||||||
Menu::Network => self
|
Menu::Network => self
|
||||||
.status
|
.status
|
||||||
@@ -261,14 +249,13 @@ impl FereseShell {
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some((enabled, action)) = radio {
|
if let Some((enabled, action)) = radio {
|
||||||
heading = heading.push(menu_button(
|
rows = rows.push(menu_button(
|
||||||
if enabled { "Turn off" } else { "Turn on" },
|
if enabled { "Turn off" } else { "Turn on" },
|
||||||
Message::Control(action),
|
Message::Control(action),
|
||||||
primary,
|
primary,
|
||||||
p,
|
p,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
rows = rows.push(heading);
|
|
||||||
if let Some(action) = &menu.confirm {
|
if let Some(action) = &menu.confirm {
|
||||||
let title = match action {
|
let title = match action {
|
||||||
Action::Poweroff => "Power off this computer?",
|
Action::Poweroff => "Power off this computer?",
|
||||||
@@ -471,10 +458,8 @@ impl FereseShell {
|
|||||||
}
|
}
|
||||||
if combined || menu.kind == Menu::Notifications {
|
if combined || menu.kind == Menu::Notifications {
|
||||||
if let Some(n) = &self.status.notifications {
|
if let Some(n) = &self.status.notifications {
|
||||||
rows = rows.push(toggle_row(
|
rows = rows.push(control_card(
|
||||||
"Do Not Disturb",
|
toggle_row("Do Not Disturb", n.dnd, Action::Dnd(!n.dnd), primary, p),
|
||||||
n.dnd,
|
|
||||||
Action::Dnd(!n.dnd),
|
|
||||||
primary,
|
primary,
|
||||||
p,
|
p,
|
||||||
));
|
));
|
||||||
@@ -581,7 +566,7 @@ impl FereseShell {
|
|||||||
p,
|
p,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
rows = rows.push(actions.wrap());
|
rows = rows.push(control_card(actions.wrap().into(), primary, p));
|
||||||
}
|
}
|
||||||
if !menu.kind.available(&self.status) {
|
if !menu.kind.available(&self.status) {
|
||||||
rows = rows.push(
|
rows = rows.push(
|
||||||
@@ -601,39 +586,18 @@ impl FereseShell {
|
|||||||
.width(WIDTH)
|
.width(WIDTH)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.class(theme::Container::custom(move |_| container::Style {
|
.class(theme::Container::custom(move |_| container::Style {
|
||||||
background: if compositor_material {
|
background: None,
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Background::Color(color_with_opacity(
|
|
||||||
theme.surface_popover,
|
|
||||||
p,
|
|
||||||
)))
|
|
||||||
},
|
|
||||||
text_color: Some(primary),
|
text_color: Some(primary),
|
||||||
icon_color: Some(primary),
|
icon_color: Some(primary),
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: theme.material_radius.into(),
|
radius: theme.material_radius.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Iced's shadow fills the silhouette behind the quad. With
|
|
||||||
// transparent client content that fill remains visible over
|
|
||||||
// the compositor's glass, creating a second dark rectangle.
|
|
||||||
shadow: if compositor_material {
|
|
||||||
Shadow::default()
|
|
||||||
} else {
|
|
||||||
Shadow {
|
|
||||||
color: color_with_opacity(theme.shadow, p * theme.shadow_opacity.min(0.18)),
|
|
||||||
offset: Vector::new(0.0, 2.0),
|
|
||||||
blur_radius: 6.0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
// Keep the client panel and compositor material on the same surface
|
// Keep the outer surface transparent; only inner cards paint a background.
|
||||||
// bounds. A second outer padding box exposes a glass ring around the
|
|
||||||
// fallback fill and makes the first committed frame visibly jump.
|
|
||||||
cosmic::widget::autosize::autosize(
|
cosmic::widget::autosize::autosize(
|
||||||
super::motion::animated(panel.into(), p),
|
super::motion::animated(panel.into(), p, menu.regions.clone()),
|
||||||
cosmic::iced::advanced::widget::Id::new("ferese-status-menu"),
|
cosmic::iced::advanced::widget::Id::new("ferese-status-menu"),
|
||||||
)
|
)
|
||||||
.limits(
|
.limits(
|
||||||
@@ -684,6 +648,7 @@ fn status_summary<'a>(
|
|||||||
.spacing(12)
|
.spacing(12)
|
||||||
.align_y(Alignment::Center),
|
.align_y(Alignment::Center),
|
||||||
)
|
)
|
||||||
|
.id("ferese-blur-card")
|
||||||
.padding([12, 0])
|
.padding([12, 0])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
@@ -730,6 +695,7 @@ fn control_card<'a>(
|
|||||||
opacity: f32,
|
opacity: f32,
|
||||||
) -> Element<'a, cosmic::Action<Message>> {
|
) -> Element<'a, cosmic::Action<Message>> {
|
||||||
container(content)
|
container(content)
|
||||||
|
.id("ferese-blur-card")
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.class(theme::Container::custom(move |_| container::Style {
|
.class(theme::Container::custom(move |_| container::Style {
|
||||||
@@ -798,16 +764,21 @@ fn connection_control<'a>(
|
|||||||
// giving it a misleading clickable/disabled-button appearance.
|
// giving it a misleading clickable/disabled-button appearance.
|
||||||
let Some(action) = action else {
|
let Some(action) = action else {
|
||||||
return container(content)
|
return container(content)
|
||||||
|
.id("ferese-blur-card")
|
||||||
.width(Length::FillPortion(1))
|
.width(Length::FillPortion(1))
|
||||||
.padding(4)
|
.padding(4)
|
||||||
.into();
|
.into();
|
||||||
};
|
};
|
||||||
button::custom(content)
|
container(
|
||||||
.width(Length::FillPortion(1))
|
button::custom(content)
|
||||||
.padding(4)
|
.width(Length::FillPortion(1))
|
||||||
.class(button_style(foreground, false, opacity))
|
.padding(4)
|
||||||
.on_press(cosmic::Action::App(Message::Control(action)))
|
.class(button_style(foreground, false, opacity))
|
||||||
.into()
|
.on_press(cosmic::Action::App(Message::Control(action))),
|
||||||
|
)
|
||||||
|
.id("ferese-blur-card")
|
||||||
|
.width(Length::FillPortion(1))
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn menu_button<'a>(
|
fn menu_button<'a>(
|
||||||
@@ -1056,6 +1027,7 @@ mod tests {
|
|||||||
opened: past,
|
opened: past,
|
||||||
confirm: None,
|
confirm: None,
|
||||||
effects: None,
|
effects: None,
|
||||||
|
regions: Default::default(),
|
||||||
};
|
};
|
||||||
assert_eq!(menu.progress(), 1.0);
|
assert_eq!(menu.progress(), 1.0);
|
||||||
assert!(!menu.animating());
|
assert!(!menu.animating());
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use ferese_protocols::effects::v1::client::{
|
|||||||
ferese_surface_effects_v1::{self, FereseSurfaceEffectsV1},
|
ferese_surface_effects_v1::{self, FereseSurfaceEffectsV1},
|
||||||
};
|
};
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
Connection, Dispatch, QueueHandle, delegate_noop,
|
Connection, Dispatch, Proxy, QueueHandle, delegate_noop,
|
||||||
protocol::{wl_buffer, wl_compositor, wl_registry, wl_shm, wl_shm_pool, wl_surface},
|
protocol::{wl_buffer, wl_compositor, wl_registry, wl_shm, wl_shm_pool, wl_surface},
|
||||||
};
|
};
|
||||||
use wayland_protocols_wlr::layer_shell::v1::client::{
|
use wayland_protocols_wlr::layer_shell::v1::client::{
|
||||||
@@ -65,7 +65,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
effects.set_role(role);
|
effects.set_role(role);
|
||||||
queue.roundtrip(&mut state)?;
|
queue.roundtrip(&mut state)?;
|
||||||
// Exercise each role in an actual presented frame, including changes
|
// Exercise each role in an actual presented frame, including changes
|
||||||
// to the cached backdrop allocation and shadow bounds.
|
// to the material geometry and shadow bounds.
|
||||||
std::thread::sleep(Duration::from_millis(50));
|
std::thread::sleep(Duration::from_millis(50));
|
||||||
if state.configure_count != initial_configures {
|
if state.configure_count != initial_configures {
|
||||||
return Err(format!("semantic role {role:?} changed layer geometry").into());
|
return Err(format!("semantic role {role:?} changed layer geometry").into());
|
||||||
@@ -73,9 +73,23 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if preview {
|
if preview {
|
||||||
effects.set_role(ferese_surface_effects_v1::Role::Panel);
|
if std::env::args().any(|arg| arg == "--regions") {
|
||||||
|
if effects.version() < 2 {
|
||||||
|
return Err("rounded material regions require effects v2".into());
|
||||||
|
}
|
||||||
|
let regions = [[20_i32, 20, 200, 180, 14], [240, 20, 200, 180, 14]];
|
||||||
|
effects.set_regions(
|
||||||
|
regions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|r| r.iter().flat_map(|v| v.to_ne_bytes()))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
effects.set_role(ferese_surface_effects_v1::Role::Popover);
|
||||||
|
} else {
|
||||||
|
effects.set_role(ferese_surface_effects_v1::Role::Panel);
|
||||||
|
}
|
||||||
queue.roundtrip(&mut state)?;
|
queue.roundtrip(&mut state)?;
|
||||||
println!("PREVIEW glass panel is mapped over the patterned card; press Ctrl+C to stop");
|
println!("PREVIEW material is mapped over the patterned card; press Ctrl+C to stop");
|
||||||
|
|
||||||
while !state.closed {
|
while !state.closed {
|
||||||
queue.blocking_dispatch(&mut state)?;
|
queue.blocking_dispatch(&mut state)?;
|
||||||
@@ -84,7 +98,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the final glass role mapped long enough for the compositor to run
|
// Keep the final solid role mapped long enough for the compositor to run
|
||||||
// at least one presentation. This turns shader and capture failures into
|
// at least one presentation. This turns shader and capture failures into
|
||||||
// probe failures instead of disconnecting before the first rendered frame.
|
// probe failures instead of disconnecting before the first rendered frame.
|
||||||
std::thread::sleep(Duration::from_millis(100));
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
@@ -112,9 +126,9 @@ struct ProbeState {
|
|||||||
layer_surface: Option<ZwlrLayerSurfaceV1>,
|
layer_surface: Option<ZwlrLayerSurfaceV1>,
|
||||||
effects: Option<FereseSurfaceEffectsV1>,
|
effects: Option<FereseSurfaceEffectsV1>,
|
||||||
buffer: Option<wl_buffer::WlBuffer>,
|
buffer: Option<wl_buffer::WlBuffer>,
|
||||||
preview_backdrop_surface: Option<wl_surface::WlSurface>,
|
preview_background_surface: Option<wl_surface::WlSurface>,
|
||||||
preview_backdrop_layer: Option<ZwlrLayerSurfaceV1>,
|
preview_background_layer: Option<ZwlrLayerSurfaceV1>,
|
||||||
preview_backdrop_buffer: Option<wl_buffer::WlBuffer>,
|
preview_background_buffer: Option<wl_buffer::WlBuffer>,
|
||||||
preview: bool,
|
preview: bool,
|
||||||
configured: bool,
|
configured: bool,
|
||||||
closed: bool,
|
closed: bool,
|
||||||
@@ -174,25 +188,25 @@ impl ProbeState {
|
|||||||
surface.commit();
|
surface.commit();
|
||||||
|
|
||||||
if self.preview {
|
if self.preview {
|
||||||
let backdrop_surface = compositor.create_surface(qh, ());
|
let background_surface = compositor.create_surface(qh, ());
|
||||||
let backdrop_layer = layer_shell.get_layer_surface(
|
let background_layer = layer_shell.get_layer_surface(
|
||||||
&backdrop_surface,
|
&background_surface,
|
||||||
None,
|
None,
|
||||||
zwlr_layer_shell_v1::Layer::Top,
|
zwlr_layer_shell_v1::Layer::Top,
|
||||||
"ferese-effects-preview-backdrop".to_owned(),
|
"ferese-effects-preview-background".to_owned(),
|
||||||
qh,
|
qh,
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
backdrop_layer.set_anchor(
|
background_layer.set_anchor(
|
||||||
zwlr_layer_surface_v1::Anchor::Top | zwlr_layer_surface_v1::Anchor::Right,
|
zwlr_layer_surface_v1::Anchor::Top | zwlr_layer_surface_v1::Anchor::Right,
|
||||||
);
|
);
|
||||||
backdrop_layer.set_margin(12, 12, 0, 0);
|
background_layer.set_margin(12, 12, 0, 0);
|
||||||
backdrop_layer.set_size(520, 260);
|
background_layer.set_size(520, 260);
|
||||||
backdrop_layer.set_exclusive_zone(0);
|
background_layer.set_exclusive_zone(0);
|
||||||
backdrop_surface.commit();
|
background_surface.commit();
|
||||||
|
|
||||||
self.preview_backdrop_surface = Some(backdrop_surface);
|
self.preview_background_surface = Some(background_surface);
|
||||||
self.preview_backdrop_layer = Some(backdrop_layer);
|
self.preview_background_layer = Some(background_layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.surface = Some(surface);
|
self.surface = Some(surface);
|
||||||
@@ -237,7 +251,7 @@ fn create_buffer(
|
|||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_preview_backdrop_buffer(
|
fn create_preview_background_buffer(
|
||||||
shm: &wl_shm::WlShm,
|
shm: &wl_shm::WlShm,
|
||||||
qh: &QueueHandle<ProbeState>,
|
qh: &QueueHandle<ProbeState>,
|
||||||
width: u32,
|
width: u32,
|
||||||
@@ -304,7 +318,7 @@ impl Dispatch<wl_registry::WlRegistry, ()> for ProbeState {
|
|||||||
state.layer_shell = Some(registry.bind(name, version.min(4), qh, ()))
|
state.layer_shell = Some(registry.bind(name, version.min(4), qh, ()))
|
||||||
}
|
}
|
||||||
"ferese_effects_manager_v1" => {
|
"ferese_effects_manager_v1" => {
|
||||||
state.effects_manager = Some(registry.bind(name, 1, qh, ()))
|
state.effects_manager = Some(registry.bind(name, version.min(2), qh, ()))
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -328,20 +342,20 @@ impl Dispatch<ZwlrLayerSurfaceV1, ()> for ProbeState {
|
|||||||
} => {
|
} => {
|
||||||
layer_surface.ack_configure(serial);
|
layer_surface.ack_configure(serial);
|
||||||
|
|
||||||
if state.preview_backdrop_layer.as_ref() == Some(layer_surface) {
|
if state.preview_background_layer.as_ref() == Some(layer_surface) {
|
||||||
let width = if width == 0 { 520 } else { width };
|
let width = if width == 0 { 520 } else { width };
|
||||||
let height = if height == 0 { 260 } else { height };
|
let height = if height == 0 { 260 } else { height };
|
||||||
let surface = state
|
let surface = state
|
||||||
.preview_backdrop_surface
|
.preview_background_surface
|
||||||
.clone()
|
.clone()
|
||||||
.expect("preview backdrop surface initialized");
|
.expect("preview background surface initialized");
|
||||||
let shm = state.shm.as_ref().expect("shm initialized");
|
let shm = state.shm.as_ref().expect("shm initialized");
|
||||||
let buffer = create_preview_backdrop_buffer(shm, qh, width, height)
|
let buffer = create_preview_background_buffer(shm, qh, width, height)
|
||||||
.expect("create preview backdrop buffer");
|
.expect("create preview background buffer");
|
||||||
surface.attach(Some(&buffer), 0, 0);
|
surface.attach(Some(&buffer), 0, 0);
|
||||||
surface.damage_buffer(0, 0, width as i32, height as i32);
|
surface.damage_buffer(0, 0, width as i32, height as i32);
|
||||||
surface.commit();
|
surface.commit();
|
||||||
state.preview_backdrop_buffer = Some(buffer);
|
state.preview_background_buffer = Some(buffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user