aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2025-01-15 14:16:05 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2025-01-17 23:10:01 +0300
commitbd559a26602874f4104e342e2ce02317ae1ae605 (patch)
tree5ba6d9d511f3ca1342a5874afdc33ecb3f93953f /src
parentb4add625b2ffdad3e003b3e437891daacf53a12f (diff)
downloadniri-bd559a26602874f4104e342e2ce02317ae1ae605.tar.gz
niri-bd559a26602874f4104e342e2ce02317ae1ae605.tar.bz2
niri-bd559a26602874f4104e342e2ce02317ae1ae605.zip
Implement window shadows
Diffstat (limited to 'src')
-rw-r--r--src/layout/mod.rs19
-rw-r--r--src/layout/shadow.rs182
-rw-r--r--src/layout/tile.rs40
-rw-r--r--src/render_helpers/mod.rs1
-rw-r--r--src/render_helpers/shaders/mod.rs24
-rw-r--r--src/render_helpers/shaders/shadow.frag142
-rw-r--r--src/render_helpers/shadow.rs257
-rw-r--r--src/window/mod.rs16
8 files changed, 672 insertions, 9 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index f2baa11d..34f64993 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -77,6 +77,7 @@ pub mod insert_hint_element;
pub mod monitor;
pub mod opening_window;
pub mod scrolling;
+pub mod shadow;
pub mod tile;
pub mod workspace;
@@ -304,6 +305,7 @@ pub struct Options {
pub struts: Struts,
pub focus_ring: niri_config::FocusRing,
pub border: niri_config::Border,
+ pub shadow: niri_config::Shadow,
pub insert_hint: niri_config::InsertHint,
pub center_focused_column: CenterFocusedColumn,
pub always_center_single_column: bool,
@@ -327,6 +329,7 @@ impl Default for Options {
struts: Default::default(),
focus_ring: Default::default(),
border: Default::default(),
+ shadow: Default::default(),
insert_hint: Default::default(),
center_focused_column: Default::default(),
always_center_single_column: false,
@@ -509,6 +512,7 @@ impl Options {
struts: layout.struts,
focus_ring: layout.focus_ring,
border: layout.border,
+ shadow: layout.shadow,
insert_hint: layout.insert_hint,
center_focused_column: layout.center_focused_column,
always_center_single_column: layout.always_center_single_column,
@@ -7073,11 +7077,25 @@ mod tests {
}
prop_compose! {
+ fn arbitrary_shadow()(
+ on in any::<bool>(),
+ width in arbitrary_spacing(),
+ ) -> niri_config::Shadow {
+ niri_config::Shadow {
+ on,
+ softness: FloatOrInt(width),
+ ..Default::default()
+ }
+ }
+ }
+
+ prop_compose! {
fn arbitrary_options()(
gaps in arbitrary_spacing(),
struts in arbitrary_struts(),
focus_ring in arbitrary_focus_ring(),
border in arbitrary_border(),
+ shadow in arbitrary_shadow(),
center_focused_column in arbitrary_center_focused_column(),
always_center_single_column in any::<bool>(),
empty_workspace_above_first in any::<bool>(),
@@ -7090,6 +7108,7 @@ mod tests {
empty_workspace_above_first,
focus_ring,
border,
+ shadow,
..Default::default()
}
}
diff --git a/src/layout/shadow.rs b/src/layout/shadow.rs
new file mode 100644
index 00000000..1600e333
--- /dev/null
+++ b/src/layout/shadow.rs
@@ -0,0 +1,182 @@
+use std::iter::zip;
+
+use niri_config::CornerRadius;
+use smithay::utils::{Logical, Point, Rectangle, Size};
+
+use crate::render_helpers::renderer::NiriRenderer;
+use crate::render_helpers::shadow::ShadowRenderElement;
+
+#[derive(Debug)]
+pub struct Shadow {
+ shader_rects: Vec<Rectangle<f64, Logical>>,
+ shaders: Vec<ShadowRenderElement>,
+ config: niri_config::Shadow,
+}
+
+impl Shadow {
+ pub fn new(config: niri_config::Shadow) -> Self {
+ Self {
+ shader_rects: Vec::new(),
+ shaders: Vec::new(),
+ config,
+ }
+ }
+
+ pub fn update_config(&mut self, config: niri_config::Shadow) {
+ self.config = config;
+ }
+
+ pub fn update_shaders(&mut self) {
+ for elem in &mut self.shaders {
+ elem.damage_all();
+ }
+ }
+
+ pub fn update_render_elements(
+ &mut self,
+ win_size: Size<f64, Logical>,
+ is_active: bool,
+ radius: CornerRadius,
+ scale: f64,
+ ) {
+ let ceil = |logical: f64| (logical * scale).ceil() / scale;
+
+ // All of this stuff should end up aligned to physical pixels because:
+ // * Window size is rounded to physical pixels before being passed to this function.
+ // * We will ceil the corner radii below.
+ // * We do not divide anything, only add, subtract and multiply by integers.
+ // * At rendering time, tile positions are rounded to physical pixels.
+
+ let width = self.config.softness.0;
+ // Like in CSS box-shadow.
+ let sigma = width / 2.;
+ // Adjust width to draw all necessary pixels.
+ let width = ceil(sigma * 3.);
+
+ let offset = self.config.offset;
+ let offset = Point::from((ceil(offset.x.0), ceil(offset.y.0)));
+
+ let spread = ceil(self.config.spread.0);
+ let offset = offset - Point::from((spread, spread));
+
+ let win_radius = radius.fit_to(win_size.w as f32, win_size.h as f32);
+
+ let box_size = win_size + Size::from((spread, spread)).upscale(2.);
+ let radius = win_radius.expanded_by(spread as f32);
+
+ let shader_size = box_size + Size::from((width, width)).upscale(2.);
+
+ let color = if is_active {
+ self.config.color
+ } else {
+ // Default to slightly more transparent.
+ self.config
+ .inactive_color
+ .unwrap_or(self.config.color * 0.75)
+ };
+
+ let shader_geo = Rectangle::new(Point::from((-width, -width)), shader_size);
+
+ // This is actually offset relative to shader_geo, this is handled below.
+ let window_geo = Rectangle::new(Point::from((0., 0.)), win_size);
+
+ if !self.config.draw_behind_window {
+ let top_left = ceil(f64::from(win_radius.top_left));
+ let top_right = f64::min(win_size.w - top_left, ceil(f64::from(win_radius.top_right)));
+ let bottom_left = f64::min(
+ win_size.h - top_left,
+ ceil(f64::from(win_radius.bottom_left)),
+ );
+ let bottom_right = f64::min(
+ win_size.h - top_right,
+ f64::min(
+ win_size.w - bottom_left,
+ ceil(f64::from(win_radius.bottom_right)),
+ ),
+ );
+
+ let top_left = Rectangle::new(Point::from((0., 0.)), Size::from((top_left, top_left)));
+ let top_right = Rectangle::new(
+ Point::from((win_size.w - top_right, 0.)),
+ Size::from((top_right, top_right)),
+ );
+ let bottom_right = Rectangle::new(
+ Point::from((win_size.w - bottom_right, win_size.h - bottom_right)),
+ Size::from((bottom_right, bottom_right)),
+ );
+ let bottom_left = Rectangle::new(
+ Point::from((0., win_size.h - bottom_left)),
+ Size::from((bottom_left, bottom_left)),
+ );
+
+ let mut background =
+ window_geo.subtract_rects([top_left, top_right, bottom_right, bottom_left]);
+ for rect in &mut background {
+ rect.loc -= offset;
+ }
+
+ self.shader_rects = shader_geo.subtract_rects(background);
+ self.shaders
+ .resize_with(self.shader_rects.len(), Default::default);
+
+ for (shader, rect) in zip(&mut self.shaders, &mut self.shader_rects) {
+ shader.update(
+ rect.size,
+ Rectangle::new(rect.loc.upscale(-1.), box_size),
+ color,
+ sigma as f32,
+ radius,
+ scale as f32,
+ Rectangle::new(window_geo.loc - offset - rect.loc, window_geo.size),
+ win_radius,
+ );
+
+ rect.loc += offset;
+ }
+ } else {
+ self.shader_rects.resize_with(1, Default::default);
+ self.shader_rects[0] = shader_geo;
+
+ self.shaders.resize_with(1, Default::default);
+ self.shaders[0].update(
+ shader_geo.size,
+ Rectangle::new(shader_geo.loc.upscale(-1.), box_size),
+ color,
+ sigma as f32,
+ radius,
+ scale as f32,
+ Rectangle::zero(),
+ Default::default(),
+ );
+
+ self.shader_rects[0].loc += offset;
+ }
+ }
+
+ pub fn render(
+ &self,
+ renderer: &mut impl NiriRenderer,
+ location: Point<f64, Logical>,
+ ) -> impl Iterator<Item = ShadowRenderElement> {
+ let mut rv = Vec::new();
+
+ if !self.config.on {
+ return rv.into_iter();
+ }
+
+ let has_shadow_shader = ShadowRenderElement::has_shader(renderer);
+ if !has_shadow_shader {
+ return rv.into_iter();
+ }
+
+ let mut push = |shader: &ShadowRenderElement, location: Point<f64, Logical>| {
+ rv.push(shader.clone().with_location(location));
+ };
+
+ for (shader, rect) in zip(&self.shaders, &self.shader_rects) {
+ push(shader, location + rect.loc);
+ }
+
+ rv.into_iter()
+ }
+}
diff --git a/src/layout/tile.rs b/src/layout/tile.rs
index 9cc5237e..a9fd8e13 100644
--- a/src/layout/tile.rs
+++ b/src/layout/tile.rs
@@ -8,6 +8,7 @@ use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
use super::focus_ring::{FocusRing, FocusRingRenderElement};
use super::opening_window::{OpenAnimation, OpeningWindowRenderElement};
+use super::shadow::Shadow;
use super::{
LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot, Options, SizeFrac,
RESIZE_ANIMATION_THRESHOLD,
@@ -19,6 +20,7 @@ use crate::render_helpers::clipped_surface::{ClippedSurfaceRenderElement, Rounde
use crate::render_helpers::damage::ExtraDamage;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::resize::ResizeRenderElement;
+use crate::render_helpers::shadow::ShadowRenderElement;
use crate::render_helpers::snapshot::RenderSnapshot;
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
use crate::render_helpers::{render_to_encompassing_texture, RenderTarget};
@@ -36,6 +38,9 @@ pub struct Tile<W: LayoutElement> {
/// The focus ring around the window.
focus_ring: FocusRing,
+ /// The shadow around the window.
+ shadow: Shadow,
+
/// Whether this tile is fullscreen.
///
/// This will update only when the `window` actually goes fullscreen, rather than right away,
@@ -111,6 +116,7 @@ niri_render_elements! {
Opening = OpeningWindowRenderElement,
Resize = ResizeRenderElement,
Border = BorderRenderElement,
+ Shadow = ShadowRenderElement,
ClippedSurface = ClippedSurfaceRenderElement<R>,
ExtraDamage = ExtraDamage,
}
@@ -143,12 +149,14 @@ impl<W: LayoutElement> Tile<W> {
let rules = window.rules();
let border_config = rules.border.resolve_against(options.border);
let focus_ring_config = rules.focus_ring.resolve_against(options.focus_ring.into());
+ let shadow_config = rules.shadow.resolve_against(options.shadow);
let is_fullscreen = window.is_fullscreen();
Self {
window,
border: FocusRing::new(border_config.into()),
focus_ring: FocusRing::new(focus_ring_config.into()),
+ shadow: Shadow::new(shadow_config),
is_fullscreen,
fullscreen_backdrop: SolidColorBuffer::new(view_size, [0., 0., 0., 1.]),
unfullscreen_to_floating: false,
@@ -198,12 +206,16 @@ impl<W: LayoutElement> Tile<W> {
.resolve_against(self.options.focus_ring.into());
self.focus_ring.update_config(focus_ring_config.into());
+ let shadow_config = rules.shadow.resolve_against(self.options.shadow);
+ self.shadow.update_config(shadow_config);
+
self.fullscreen_backdrop.resize(view_size);
}
pub fn update_shaders(&mut self) {
self.border.update_shaders();
self.focus_ring.update_shaders();
+ self.shadow.update_shaders();
}
pub fn update_window(&mut self) {
@@ -254,6 +266,9 @@ impl<W: LayoutElement> Tile<W> {
.resolve_against(self.options.focus_ring.into());
self.focus_ring.update_config(focus_ring_config.into());
+ let shadow_config = rules.shadow.resolve_against(self.options.shadow);
+ self.shadow.update_config(shadow_config);
+
let window_size = self.window_size();
let radius = rules
.geometry_corner_radius
@@ -323,19 +338,26 @@ impl<W: LayoutElement> Tile<W> {
self.scale,
);
- let draw_focus_ring_with_background = if self.effective_border_width().is_some() {
- false
- } else {
- draw_border_with_background
- };
let radius = if self.is_fullscreen {
CornerRadius::default()
} else if self.effective_border_width().is_some() {
radius
} else {
rules.geometry_corner_radius.unwrap_or_default()
- }
- .expanded_by(self.focus_ring.width() as f32);
+ };
+ self.shadow.update_render_elements(
+ self.animated_tile_size(),
+ is_active,
+ radius,
+ self.scale,
+ );
+
+ let draw_focus_ring_with_background = if self.effective_border_width().is_some() {
+ false
+ } else {
+ draw_border_with_background
+ };
+ let radius = radius.expanded_by(self.focus_ring.width() as f32);
self.focus_ring.update_render_elements(
self.animated_tile_size(),
is_active,
@@ -899,7 +921,9 @@ impl<W: LayoutElement> Tile<W> {
let rv = rv.chain(elem.into_iter().flatten());
let elem = focus_ring.then(|| self.focus_ring.render(renderer, location).map(Into::into));
- rv.chain(elem.into_iter().flatten())
+ let rv = rv.chain(elem.into_iter().flatten());
+
+ rv.chain(self.shadow.render(renderer, location).map(Into::into))
}
pub fn render<R: NiriRenderer>(
diff --git a/src/render_helpers/mod.rs b/src/render_helpers/mod.rs
index 98ccf19e..45e6e527 100644
--- a/src/render_helpers/mod.rs
+++ b/src/render_helpers/mod.rs
@@ -31,6 +31,7 @@ pub mod resize;
pub mod resources;
pub mod shader_element;
pub mod shaders;
+pub mod shadow;
pub mod snapshot;
pub mod solid_color;
pub mod surface;
diff --git a/src/render_helpers/shaders/mod.rs b/src/render_helpers/shaders/mod.rs
index 91ba32d1..ddc58cdb 100644
--- a/src/render_helpers/shaders/mod.rs
+++ b/src/render_helpers/shaders/mod.rs
@@ -11,6 +11,7 @@ use super::shader_element::ShaderProgram;
pub struct Shaders {
pub border: Option<ShaderProgram>,
+ pub shadow: Option<ShaderProgram>,
pub clipped_surface: Option<GlesTexProgram>,
pub resize: Option<ShaderProgram>,
pub custom_resize: RefCell<Option<ShaderProgram>>,
@@ -21,6 +22,7 @@ pub struct Shaders {
#[derive(Debug, Clone, Copy)]
pub enum ProgramType {
Border,
+ Shadow,
Resize,
Close,
Open,
@@ -53,6 +55,26 @@ impl Shaders {
})
.ok();
+ let shadow = ShaderProgram::compile(
+ renderer,
+ include_str!("shadow.frag"),
+ &[
+ UniformName::new("shadow_color", UniformType::_4f),
+ UniformName::new("sigma", UniformType::_1f),
+ UniformName::new("input_to_geo", UniformType::Matrix3x3),
+ UniformName::new("geo_size", UniformType::_2f),
+ UniformName::new("corner_radius", UniformType::_4f),
+ UniformName::new("window_input_to_geo", UniformType::Matrix3x3),
+ UniformName::new("window_geo_size", UniformType::_2f),
+ UniformName::new("window_corner_radius", UniformType::_4f),
+ ],
+ &[],
+ )
+ .map_err(|err| {
+ warn!("error compiling shadow shader: {err:?}");
+ })
+ .ok();
+
let clipped_surface = renderer
.compile_custom_texture_shader(
include_str!("clipped_surface.frag"),
@@ -76,6 +98,7 @@ impl Shaders {
Self {
border,
+ shadow,
clipped_surface,
resize,
custom_resize: RefCell::new(None),
@@ -121,6 +144,7 @@ impl Shaders {
pub fn program(&self, program: ProgramType) -> Option<ShaderProgram> {
match program {
ProgramType::Border => self.border.clone(),
+ ProgramType::Shadow => self.shadow.clone(),
ProgramType::Resize => self
.custom_resize
.borrow()
diff --git a/src/render_helpers/shaders/shadow.frag b/src/render_helpers/shaders/shadow.frag
new file mode 100644
index 00000000..3912d71f
--- /dev/null
+++ b/src/render_helpers/shaders/shadow.frag
@@ -0,0 +1,142 @@
+precision highp float;
+
+#if defined(DEBUG_FLAGS)
+uniform float niri_tint;
+#endif
+
+uniform float niri_alpha;
+uniform float niri_scale;
+
+uniform vec2 niri_size;
+varying vec2 niri_v_coords;
+
+uniform vec4 shadow_color;
+uniform float sigma;
+
+uniform mat3 input_to_geo;
+uniform vec2 geo_size;
+uniform vec4 corner_radius;
+
+uniform mat3 window_input_to_geo;
+uniform vec2 window_geo_size;
+uniform vec4 window_corner_radius;
+
+// Based on: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/
+//
+// License: CC0 (http://creativecommons.org/publicdomain/zero/1.0/)
+
+// A standard gaussian function, used for weighting samples
+float gaussian(float x, float sigma) {
+ const float pi = 3.141592653589793;
+ return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * pi) * sigma);
+}
+
+// This approximates the error function, needed for the gaussian integral
+vec2 erf(vec2 x) {
+ vec2 s = sign(x), a = abs(x);
+ x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
+ x *= x;
+ return s - s / (x * x);
+}
+
+// Return the blurred mask along the x dimension
+float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize) {
+ float delta = min(halfSize.y - corner - abs(y), 0.0);
+ float curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
+ vec2 integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma));
+ return integral.y - integral.x;
+}
+
+// Return the mask for the shadow of a box from lower to upper
+float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float corner) {
+ // Center everything to make the math easier
+ vec2 center = (lower + upper) * 0.5;
+ vec2 halfSize = (upper - lower) * 0.5;
+ point -= center;
+
+ // The signal is only non-zero in a limited range, so don't waste samples
+ float low = point.y - halfSize.y;
+ float high = point.y + halfSize.y;
+ float start = clamp(-3.0 * sigma, low, high);
+ float end = clamp(3.0 * sigma, low, high);
+
+ // Accumulate samples (we can get away with surprisingly few samples)
+ float step = (end - start) / 4.0;
+ float y = start + step * 0.5;
+ float value = 0.0;
+ for (int i = 0; i < 4; i++) {
+ value += roundedBoxShadowX(point.x, point.y - y, sigma, corner, halfSize) * gaussian(y, sigma) * step;
+ y += step;
+ }
+
+ return value;
+}
+
+float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
+ vec2 center;
+ float radius;
+
+ if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
+ radius = corner_radius.x;
+ center = vec2(radius, radius);
+ } else if (size.x - corner_radius.y < coords.x && coords.y < corner_radius.y) {
+ radius = corner_radius.y;
+ center = vec2(size.x - radius, radius);
+ } else if (size.x - corner_radius.z < coords.x && size.y - corner_radius.z < coords.y) {
+ radius = corner_radius.z;
+ center = vec2(size.x - radius, size.y - radius);
+ } else if (coords.x < corner_radius.w && size.y - corner_radius.w < coords.y) {
+ radius = corner_radius.w;
+ center = vec2(radius, size.y - radius);
+ } else {
+ return 1.0;
+ }
+
+ float dist = distance(coords, center);
+ float half_px = 0.5 / niri_scale;
+ return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
+}
+
+void main() {
+ vec3 coords_geo = input_to_geo * vec3(niri_v_coords, 1.0);
+ vec3 coords_window_geo = window_input_to_geo * vec3(niri_v_coords, 1.0);
+
+ vec4 color = shadow_color;
+
+ float shadow_value;
+ if (sigma < 0.1) {
+ // With low enough sigma just draw a rounded rectangle.
+ shadow_value = rounding_alpha(coords_geo.xy, geo_size, corner_radius);
+ } else {
+ shadow_value = roundedBoxShadow(
+ vec2(0.0, 0.0),
+ geo_size,
+ coords_geo.xy,
+ sigma,
+ // FIXME: figure out how to blur with different corner radii.
+ //
+ // GTK seems to call blurring separately for the rect and for the 4 corners:
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-4-16/gsk/gpu/shaders/gskgpuboxshadow.glsl
+ corner_radius.x
+ );
+ }
+ color = color * shadow_value;
+
+ // Cut out the inside of the window geometry if requested.
+ if (window_geo_size != vec2(0.0, 0.0)) {
+ if (0.0 <= coords_window_geo.x && coords_window_geo.x <= window_geo_size.x
+ && 0.0 <= coords_window_geo.y && coords_window_geo.y <= window_geo_size.y) {
+ float alpha = rounding_alpha(coords_window_geo.xy, window_geo_size, window_corner_radius);
+ color = color * (1.0 - alpha);
+ }
+ }
+
+ color = color * niri_alpha;
+
+#if defined(DEBUG_FLAGS)
+ if (niri_tint == 1.0)
+ color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
+#endif
+
+ gl_FragColor = color;
+}
diff --git a/src/render_helpers/shadow.rs b/src/render_helpers/shadow.rs
new file mode 100644
index 00000000..b98202e4
--- /dev/null
+++ b/src/render_helpers/shadow.rs
@@ -0,0 +1,257 @@
+use std::collections::HashMap;
+
+use glam::{Mat3, Vec2};
+use niri_config::{Color, CornerRadius};
+use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
+use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, Uniform};
+use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions};
+use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
+
+use super::renderer::NiriRenderer;
+use super::shader_element::ShaderRenderElement;
+use super::shaders::{mat3_uniform, ProgramType, Shaders};
+use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
+
+/// Renders a rounded rectangle shadow.
+#[derive(Debug, Clone)]
+pub struct ShadowRenderElement {
+ inner: ShaderRenderElement,
+ params: Parameters,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+struct Parameters {
+ size: Size<f64, Logical>,
+ geometry: Rectangle<f64, Logical>,
+ color: Color,
+ sigma: f32,
+ corner_radius: CornerRadius,
+ // Should only be used for visual improvements, i.e. corner radius anti-aliasing.
+ scale: f32,
+
+ window_geometry: Rectangle<f64, Logical>,
+ window_corner_radius: CornerRadius,
+}
+
+impl ShadowRenderElement {
+ #[allow(clippy::too_many_arguments)]
+ pub fn new(
+ size: Size<f64, Logical>,
+ geometry: Rectangle<f64, Logical>,
+ color: Color,
+ sigma: f32,
+ corner_radius: CornerRadius,
+ scale: f32,
+ window_geometry: Rectangle<f64, Logical>,
+ window_corner_radius: CornerRadius,
+ ) -> Self {
+ let inner = ShaderRenderElement::empty(ProgramType::Shadow, Kind::Unspecified);
+ let mut rv = Self {
+ inner,
+ params: Parameters {
+ size,
+ geometry,
+ color,
+ sigma,
+ corner_radius,
+ scale,
+ window_geometry,
+ window_corner_radius,
+ },
+ };
+ rv.update_inner();
+ rv
+ }
+
+ pub fn empty() -> Self {
+ let inner = ShaderRenderElement::empty(ProgramType::Shadow, Kind::Unspecified);
+ Self {
+ inner,
+ params: Parameters {
+ size: Default::default(),
+ geometry: Default::default(),
+ color: Default::default(),
+ sigma: 0.,
+ corner_radius: Default::default(),
+ scale: 1.,
+ window_geometry: Default::default(),
+ window_corner_radius: Default::default(),
+ },
+ }
+ }
+
+ pub fn damage_all(&mut self) {
+ self.inner.damage_all();
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ pub fn update(
+ &mut self,
+ size: Size<f64, Logical>,
+ geometry: Rectangle<f64, Logical>,
+ color: Color,
+ sigma: f32,
+ corner_radius: CornerRadius,
+ scale: f32,
+ window_geometry: Rectangle<f64, Logical>,
+ window_corner_radius: CornerRadius,
+ ) {
+ let params = Parameters {
+ size,
+ geometry,
+ color,
+ sigma,
+ corner_radius,
+ scale,
+ window_geometry,
+ window_corner_radius,
+ };
+ if self.params == params {
+ return;
+ }
+
+ self.params = params;
+ self.update_inner();
+ }
+
+ fn update_inner(&mut self) {
+ let Parameters {
+ size,
+ geometry,
+ color,
+ sigma,
+ corner_radius,
+ scale,
+ window_geometry,
+ window_corner_radius,
+ } = self.params;
+
+ let area_size = Vec2::new(size.w as f32, size.h as f32);
+
+ let geo_loc = Vec2::new(geometry.loc.x as f32, geometry.loc.y as f32);
+ let geo_size = Vec2::new(geometry.size.w as f32, geometry.size.h as f32);
+
+ let input_to_geo =
+ Mat3::from_scale(area_size) * Mat3::from_translation(-geo_loc / area_size);
+
+ let window_geo_loc = Vec2::new(window_geometry.loc.x as f32, window_geometry.loc.y as f32);
+ let window_geo_size =
+ Vec2::new(window_geometry.size.w as f32, window_geometry.size.h as f32);
+
+ let window_input_to_geo =
+ Mat3::from_scale(area_size) * Mat3::from_translation(-window_geo_loc / area_size);
+
+ self.inner.update(
+ size,
+ None,
+ scale,
+ vec![
+ Uniform::new("shadow_color", color.to_array_premul()),
+ Uniform::new("sigma", sigma),
+ mat3_uniform("input_to_geo", input_to_geo),
+ Uniform::new("geo_size", geo_size.to_array()),
+ Uniform::new("corner_radius", <[f32; 4]>::from(corner_radius)),
+ mat3_uniform("window_input_to_geo", window_input_to_geo),
+ Uniform::new("window_geo_size", window_geo_size.to_array()),
+ Uniform::new(
+ "window_corner_radius",
+ <[f32; 4]>::from(window_corner_radius),
+ ),
+ ],
+ HashMap::new(),
+ );
+ }
+
+ pub fn with_location(mut self, location: Point<f64, Logical>) -> Self {
+ self.inner = self.inner.with_location(location);
+ self
+ }
+
+ pub fn has_shader(renderer: &mut impl NiriRenderer) -> bool {
+ Shaders::get(renderer)
+ .program(ProgramType::Shadow)
+ .is_some()
+ }
+}
+
+impl Default for ShadowRenderElement {
+ fn default() -> Self {
+ Self::empty()
+ }
+}
+
+impl Element for ShadowRenderElement {
+ fn id(&self) -> &Id {
+ self.inner.id()
+ }
+
+ fn current_commit(&self) -> CommitCounter {
+ self.inner.current_commit()
+ }
+
+ fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
+ self.inner.geometry(scale)
+ }
+
+ fn transform(&self) -> Transform {
+ self.inner.transform()
+ }
+
+ fn src(&self) -> Rectangle<f64, Buffer> {
+ self.inner.src()
+ }
+
+ fn damage_since(
+ &self,
+ scale: Scale<f64>,
+ commit: Option<CommitCounter>,
+ ) -> DamageSet<i32, Physical> {
+ self.inner.damage_since(scale, commit)
+ }
+
+ fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
+ self.inner.opaque_regions(scale)
+ }
+
+ fn alpha(&self) -> f32 {
+ self.inner.alpha()
+ }
+
+ fn kind(&self) -> Kind {
+ self.inner.kind()
+ }
+}
+
+impl RenderElement<GlesRenderer> for ShadowRenderElement {
+ fn draw(
+ &self,
+ frame: &mut GlesFrame<'_>,
+ src: Rectangle<f64, Buffer>,
+ dst: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ opaque_regions: &[Rectangle<i32, Physical>],
+ ) -> Result<(), GlesError> {
+ RenderElement::<GlesRenderer>::draw(&self.inner, frame, src, dst, damage, opaque_regions)
+ }
+
+ fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
+ self.inner.underlying_storage(renderer)
+ }
+}
+
+impl<'render> RenderElement<TtyRenderer<'render>> for ShadowRenderElement {
+ fn draw(
+ &self,
+ frame: &mut TtyFrame<'_, '_>,
+ src: Rectangle<f64, Buffer>,
+ dst: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ opaque_regions: &[Rectangle<i32, Physical>],
+ ) -> Result<(), TtyRendererError<'render>> {
+ RenderElement::<TtyRenderer<'_>>::draw(&self.inner, frame, src, dst, damage, opaque_regions)
+ }
+
+ fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option<UnderlyingStorage> {
+ self.inner.underlying_storage(renderer)
+ }
+}
diff --git a/src/window/mod.rs b/src/window/mod.rs
index 9c64fe82..189049b3 100644
--- a/src/window/mod.rs
+++ b/src/window/mod.rs
@@ -1,7 +1,8 @@
use std::cmp::{max, min};
use niri_config::{
- BlockOutFrom, BorderRule, CornerRadius, FloatingPosition, Match, PresetSize, WindowRule,
+ BlockOutFrom, BorderRule, CornerRadius, FloatingPosition, Match, PresetSize, ShadowRule,
+ WindowRule,
};
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::utils::{Logical, Size};
@@ -77,6 +78,8 @@ pub struct ResolvedWindowRules {
pub focus_ring: BorderRule,
/// Window border overrides.
pub border: BorderRule,
+ /// Shadow overrides.
+ pub shadow: ShadowRule,
/// Whether or not to draw the border with a solid background.
///
@@ -171,6 +174,16 @@ impl ResolvedWindowRules {
active_gradient: None,
inactive_gradient: None,
},
+ shadow: ShadowRule {
+ off: false,
+ on: false,
+ offset: None,
+ softness: None,
+ spread: None,
+ draw_behind_window: None,
+ color: None,
+ inactive_color: None,
+ },
draw_border_with_background: None,
opacity: None,
geometry_corner_radius: None,
@@ -268,6 +281,7 @@ impl ResolvedWindowRules {
resolved.focus_ring.merge_with(&rule.focus_ring);
resolved.border.merge_with(&rule.border);
+ resolved.shadow.merge_with(&rule.shadow);
if let Some(x) = rule.draw_border_with_background {
resolved.draw_border_with_background = Some(x);