diff options
| -rw-r--r-- | niri-config/src/layer_rule.rs | 6 | ||||
| -rw-r--r-- | niri-config/src/lib.rs | 2 | ||||
| -rw-r--r-- | src/handlers/layer_shell.rs | 5 | ||||
| -rw-r--r-- | src/layer/mapped.rs | 37 | ||||
| -rw-r--r-- | src/layer/mod.rs | 24 | ||||
| -rw-r--r-- | src/niri.rs | 7 | ||||
| -rw-r--r-- | wiki/Configuration:-Layer-Rules.md | 58 |
7 files changed, 133 insertions, 6 deletions
diff --git a/niri-config/src/layer_rule.rs b/niri-config/src/layer_rule.rs index dc6fbd8d..f97b2c0d 100644 --- a/niri-config/src/layer_rule.rs +++ b/niri-config/src/layer_rule.rs @@ -1,4 +1,4 @@ -use crate::{BlockOutFrom, RegexEq}; +use crate::{BlockOutFrom, CornerRadius, RegexEq, ShadowRule}; #[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)] pub struct LayerRule { @@ -11,6 +11,10 @@ pub struct LayerRule { pub opacity: Option<f32>, #[knuffel(child, unwrap(argument))] pub block_out_from: Option<BlockOutFrom>, + #[knuffel(child, default)] + pub shadow: ShadowRule, + #[knuffel(child)] + pub geometry_corner_radius: Option<CornerRadius>, } #[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)] diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index fae5129e..db301151 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -3778,6 +3778,8 @@ mod tests { excludes: vec![], opacity: None, block_out_from: Some(BlockOutFrom::Screencast), + shadow: ShadowRule::default(), + geometry_corner_radius: None, } ], workspaces: vec![ diff --git a/src/handlers/layer_shell.rs b/src/handlers/layer_shell.rs index 1888e11c..73a61e6e 100644 --- a/src/handlers/layer_shell.rs +++ b/src/handlers/layer_shell.rs @@ -132,10 +132,11 @@ impl State { // Resolve rules for newly mapped layer surfaces. if was_unmapped { - let rules = &self.niri.config.borrow().layer_rules; + let config = self.niri.config.borrow(); + let rules = &config.layer_rules; let rules = ResolvedLayerRules::compute(rules, layer, self.niri.is_at_startup); - let mapped = MappedLayer::new(layer.clone(), rules); + let mapped = MappedLayer::new(layer.clone(), rules, &config); let prev = self .niri .mapped_layer_surfaces diff --git a/src/layer/mapped.rs b/src/layer/mapped.rs index dfc09fe3..493bbc58 100644 --- a/src/layer/mapped.rs +++ b/src/layer/mapped.rs @@ -1,4 +1,5 @@ use niri_config::layer_rule::LayerRule; +use niri_config::Config; use smithay::backend::renderer::element::surface::{ render_elements_from_surface_tree, WaylandSurfaceRenderElement, }; @@ -7,8 +8,10 @@ use smithay::desktop::{LayerSurface, PopupManager}; use smithay::utils::{Logical, Point, Scale, Size}; use super::ResolvedLayerRules; +use crate::layout::shadow::Shadow; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; +use crate::render_helpers::shadow::ShadowRenderElement; use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement}; use crate::render_helpers::{RenderTarget, SplitElements}; @@ -22,29 +25,56 @@ pub struct MappedLayer { /// Buffer to draw instead of the surface when it should be blocked out. block_out_buffer: SolidColorBuffer, + + /// The shadow around the surface. + shadow: Shadow, } niri_render_elements! { LayerSurfaceRenderElement<R> => { Wayland = WaylandSurfaceRenderElement<R>, SolidColor = SolidColorRenderElement, + Shadow = ShadowRenderElement, } } impl MappedLayer { - pub fn new(surface: LayerSurface, rules: ResolvedLayerRules) -> Self { + pub fn new(surface: LayerSurface, rules: ResolvedLayerRules, config: &Config) -> Self { + let mut shadow_config = config.layout.shadow; + // Shadows for layer surfaces need to be explicitly enabled. + shadow_config.on = false; + let shadow_config = rules.shadow.resolve_against(shadow_config); + Self { surface, rules, block_out_buffer: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]), + shadow: Shadow::new(shadow_config), } } + pub fn update_config(&mut self, config: &Config) { + let mut shadow_config = config.layout.shadow; + // Shadows for layer surfaces need to be explicitly enabled. + shadow_config.on = false; + let shadow_config = self.rules.shadow.resolve_against(shadow_config); + self.shadow.update_config(shadow_config); + } + + pub fn update_shaders(&mut self) { + self.shadow.update_shaders(); + } + pub fn update_render_elements(&mut self, size: Size<f64, Logical>, scale: Scale<f64>) { // Round to physical pixels. let size = size.to_physical_precise_round(scale).to_logical(scale); self.block_out_buffer.resize(size); + + let radius = self.rules.geometry_corner_radius.unwrap_or_default(); + // FIXME: is_active based on keyboard focus? + self.shadow + .update_render_elements(size, true, radius, scale.x); } pub fn surface(&self) -> &LayerSurface { @@ -81,6 +111,7 @@ impl MappedLayer { // Round to physical pixels. let location = location.to_physical_precise_round(scale).to_logical(scale); + // FIXME: take geometry-corner-radius into account. let elem = SolidColorRenderElement::from_buffer( &self.block_out_buffer, location, @@ -117,6 +148,10 @@ impl MappedLayer { ); } + let location = location.to_physical_precise_round(scale).to_logical(scale); + rv.normal + .extend(self.shadow.render(renderer, location).map(Into::into)); + rv } } diff --git a/src/layer/mod.rs b/src/layer/mod.rs index 72e804d5..36e7ee67 100644 --- a/src/layer/mod.rs +++ b/src/layer/mod.rs @@ -1,5 +1,5 @@ use niri_config::layer_rule::{LayerRule, Match}; -use niri_config::BlockOutFrom; +use niri_config::{BlockOutFrom, CornerRadius, ShadowRule}; use smithay::desktop::LayerSurface; pub mod mapped; @@ -13,6 +13,12 @@ pub struct ResolvedLayerRules { /// Whether to block out this layer surface from certain render targets. pub block_out_from: Option<BlockOutFrom>, + + /// Shadow overrides. + pub shadow: ShadowRule, + + /// Corner radius to assume this layer surface has. + pub geometry_corner_radius: Option<CornerRadius>, } impl ResolvedLayerRules { @@ -20,6 +26,17 @@ impl ResolvedLayerRules { Self { opacity: None, block_out_from: None, + shadow: ShadowRule { + off: false, + on: false, + offset: None, + softness: None, + spread: None, + draw_behind_window: None, + color: None, + inactive_color: None, + }, + geometry_corner_radius: None, } } @@ -53,6 +70,11 @@ impl ResolvedLayerRules { if let Some(x) = rule.block_out_from { resolved.block_out_from = Some(x); } + if let Some(x) = rule.geometry_corner_radius { + resolved.geometry_corner_radius = Some(x); + } + + resolved.shadow.merge_with(&rule.shadow); } resolved diff --git a/src/niri.rs b/src/niri.rs index a8d6f004..a578ced3 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -1097,6 +1097,9 @@ impl State { } self.niri.layout.update_config(&config); + for mapped in self.niri.mapped_layer_surfaces.values_mut() { + mapped.update_config(&config); + } // Create new named workspaces. for ws_config in &config.workspaces { @@ -5101,11 +5104,13 @@ impl Niri { let mut changed = false; { - let rules = &self.config.borrow().layer_rules; + let config = self.config.borrow(); + let rules = &config.layer_rules; for mapped in self.mapped_layer_surfaces.values_mut() { if mapped.recompute_layer_rules(rules, self.is_at_startup) { changed = true; + mapped.update_config(&config); } } } diff --git a/wiki/Configuration:-Layer-Rules.md b/wiki/Configuration:-Layer-Rules.md index 297991e3..3bf262c9 100644 --- a/wiki/Configuration:-Layer-Rules.md +++ b/wiki/Configuration:-Layer-Rules.md @@ -19,6 +19,19 @@ layer-rule { opacity 0.5 block-out-from "screencast" // block-out-from "screen-capture" + + shadow { + on + // off + softness 40 + spread 5 + offset x=0 y=5 + draw-behind-window true + color "#00000064" + // inactive-color "#00000064" + } + + geometry-corner-radius 12 } ``` @@ -95,3 +108,48 @@ layer-rule { opacity 0.95 } ``` + +#### `shadow` + +<sup>Since: next release</sup> + +Override the shadow options for the surface. + +These rules have the same options as the normal shadow config in the [layout](./Configuration:-Layout.md) section, so check the documentation there. + +Unlike window shadows, layer surface shadows always need to be enabled with a layer rule. +That is, enabling shadows in the layout config section won't automatically enable them for layer surfaces. + +> [!NOTE] +> Layer surfaces have no way to tell niri about their *visual geometry*. +> For example, if a layer surface includes some invisible margins (like mako), niri has no way of knowing that, and will draw the shadow behind the entire surface, including the invisible margins. +> +> So to use niri shadows, you'll need to configure layer-shell clients to remove their own margins or shadows. + +```kdl +// Add a shadow for fuzzel. +layer-rule { + match namespace="^launcher$" + + shadow { + on + } + + // Fuzzel defaults to 10 px rounded corners. + geometry-corner-radius 10 +} +``` + +#### `geometry-corner-radius` + +<sup>Since: next release</sup> + +Set the corner radius of the surface. + +This setting will only affect the shadow—it will round its corners to match the geometry corner radius. + +```kdl +layer-rule { + geometry-corner-radius 12 +} +``` |
