Skip to content

Commit e9f6017

Browse files
committed
Implement window shadows
1 parent b4add62 commit e9f6017

File tree

12 files changed

+947
-9
lines changed

12 files changed

+947
-9
lines changed

niri-config/src/lib.rs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ extern crate tracing;
33

44
use std::collections::HashSet;
55
use std::ffi::OsStr;
6+
use std::ops::Mul;
67
use std::path::{Path, PathBuf};
78
use std::str::FromStr;
89
use std::time::Duration;
@@ -436,6 +437,8 @@ pub struct Layout {
436437
#[knuffel(child, default)]
437438
pub border: Border,
438439
#[knuffel(child, default)]
440+
pub shadow: Shadow,
441+
#[knuffel(child, default)]
439442
pub insert_hint: InsertHint,
440443
#[knuffel(child, unwrap(children), default)]
441444
pub preset_column_widths: Vec<PresetSize>,
@@ -460,6 +463,7 @@ impl Default for Layout {
460463
Self {
461464
focus_ring: Default::default(),
462465
border: Default::default(),
466+
shadow: Default::default(),
463467
insert_hint: Default::default(),
464468
preset_column_widths: Default::default(),
465469
default_column_width: Default::default(),
@@ -608,6 +612,49 @@ impl From<FocusRing> for Border {
608612
}
609613
}
610614

615+
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
616+
pub struct Shadow {
617+
#[knuffel(child)]
618+
pub on: bool,
619+
#[knuffel(child, default = Self::default().offset)]
620+
pub offset: ShadowOffset,
621+
#[knuffel(child, unwrap(argument), default = Self::default().softness)]
622+
pub softness: FloatOrInt<0, 1024>,
623+
#[knuffel(child, unwrap(argument), default = Self::default().spread)]
624+
pub spread: FloatOrInt<0, 1024>,
625+
#[knuffel(child, unwrap(argument), default = Self::default().draw_behind_window)]
626+
pub draw_behind_window: bool,
627+
#[knuffel(child, default = Self::default().color)]
628+
pub color: Color,
629+
#[knuffel(child)]
630+
pub inactive_color: Option<Color>,
631+
}
632+
633+
impl Default for Shadow {
634+
fn default() -> Self {
635+
Self {
636+
on: false,
637+
offset: ShadowOffset {
638+
x: FloatOrInt(0.),
639+
y: FloatOrInt(5.),
640+
},
641+
softness: FloatOrInt(30.),
642+
spread: FloatOrInt(5.),
643+
draw_behind_window: false,
644+
color: Color::from_rgba8_unpremul(0, 0, 0, 0x70),
645+
inactive_color: None,
646+
}
647+
}
648+
}
649+
650+
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
651+
pub struct ShadowOffset {
652+
#[knuffel(property, default)]
653+
pub x: FloatOrInt<-65535, 65535>,
654+
#[knuffel(property, default)]
655+
pub y: FloatOrInt<-65535, 65535>,
656+
}
657+
611658
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
612659
pub struct InsertHint {
613660
#[knuffel(child)]
@@ -679,6 +726,15 @@ impl Color {
679726
}
680727
}
681728

729+
impl Mul<f32> for Color {
730+
type Output = Self;
731+
732+
fn mul(mut self, rhs: f32) -> Self::Output {
733+
self.a *= rhs;
734+
self
735+
}
736+
}
737+
682738
#[derive(knuffel::Decode, Debug, PartialEq)]
683739
pub struct Cursor {
684740
#[knuffel(child, unwrap(argument), default = String::from("default"))]
@@ -1007,6 +1063,8 @@ pub struct WindowRule {
10071063
pub focus_ring: BorderRule,
10081064
#[knuffel(child, default)]
10091065
pub border: BorderRule,
1066+
#[knuffel(child, default)]
1067+
pub shadow: ShadowRule,
10101068
#[knuffel(child, unwrap(argument))]
10111069
pub draw_border_with_background: Option<bool>,
10121070
#[knuffel(child, unwrap(argument))]
@@ -1084,6 +1142,26 @@ pub struct BorderRule {
10841142
pub inactive_gradient: Option<Gradient>,
10851143
}
10861144

1145+
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
1146+
pub struct ShadowRule {
1147+
#[knuffel(child)]
1148+
pub off: bool,
1149+
#[knuffel(child)]
1150+
pub on: bool,
1151+
#[knuffel(child)]
1152+
pub offset: Option<ShadowOffset>,
1153+
#[knuffel(child, unwrap(argument))]
1154+
pub softness: Option<FloatOrInt<0, 1024>>,
1155+
#[knuffel(child, unwrap(argument))]
1156+
pub spread: Option<FloatOrInt<0, 1024>>,
1157+
#[knuffel(child, unwrap(argument))]
1158+
pub draw_behind_window: Option<bool>,
1159+
#[knuffel(child)]
1160+
pub color: Option<Color>,
1161+
#[knuffel(child)]
1162+
pub inactive_color: Option<Color>,
1163+
}
1164+
10871165
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
10881166
pub struct FloatingPosition {
10891167
#[knuffel(property)]
@@ -1803,6 +1881,67 @@ impl BorderRule {
18031881
}
18041882
}
18051883

1884+
impl ShadowRule {
1885+
pub fn merge_with(&mut self, other: &Self) {
1886+
if other.off {
1887+
self.off = true;
1888+
self.on = false;
1889+
}
1890+
1891+
if other.on {
1892+
self.off = false;
1893+
self.on = true;
1894+
}
1895+
1896+
if let Some(x) = other.offset {
1897+
self.offset = Some(x);
1898+
}
1899+
if let Some(x) = other.softness {
1900+
self.softness = Some(x);
1901+
}
1902+
if let Some(x) = other.spread {
1903+
self.spread = Some(x);
1904+
}
1905+
if let Some(x) = other.draw_behind_window {
1906+
self.draw_behind_window = Some(x);
1907+
}
1908+
if let Some(x) = other.color {
1909+
self.color = Some(x);
1910+
}
1911+
if let Some(x) = other.inactive_color {
1912+
self.inactive_color = Some(x);
1913+
}
1914+
}
1915+
1916+
pub fn resolve_against(&self, mut config: Shadow) -> Shadow {
1917+
config.on |= self.on;
1918+
if self.off {
1919+
config.on = false;
1920+
}
1921+
1922+
if let Some(x) = self.offset {
1923+
config.offset = x;
1924+
}
1925+
if let Some(x) = self.softness {
1926+
config.softness = x;
1927+
}
1928+
if let Some(x) = self.spread {
1929+
config.spread = x;
1930+
}
1931+
if let Some(x) = self.draw_behind_window {
1932+
config.draw_behind_window = x;
1933+
}
1934+
if let Some(x) = self.color {
1935+
config.color = x;
1936+
}
1937+
if let Some(x) = self.inactive_color {
1938+
config.inactive_color = Some(x);
1939+
}
1940+
1941+
config
1942+
}
1943+
}
1944+
18061945
impl CornerRadius {
18071946
pub fn fit_to(self, width: f32, height: f32) -> Self {
18081947
// Like in CSS: https://drafts.csswg.org/css-backgrounds/#corner-overlap
@@ -3221,6 +3360,10 @@ mod tests {
32213360
inactive-color "rgba(255, 200, 100, 0.0)"
32223361
}
32233362
3363+
shadow {
3364+
offset x=10 y=-20
3365+
}
3366+
32243367
preset-column-widths {
32253368
proportion 0.25
32263369
proportion 0.5
@@ -3460,6 +3603,13 @@ mod tests {
34603603
active_gradient: None,
34613604
inactive_gradient: None,
34623605
},
3606+
shadow: Shadow {
3607+
offset: ShadowOffset {
3608+
x: FloatOrInt(10.),
3609+
y: FloatOrInt(-20.),
3610+
},
3611+
..Default::default()
3612+
},
34633613
insert_hint: InsertHint {
34643614
off: false,
34653615
color: Color::from_rgba8_unpremul(255, 200, 127, 255),

resources/default-config.kdl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,43 @@ layout {
191191
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view"
192192
}
193193

194+
// You can enable drop shadows for windows.
195+
shadow {
196+
// Uncomment the next line to enable shadows.
197+
// on
198+
199+
// By default, the shadow draws only around its window, and not behind it.
200+
// Uncomment this setting to make the shadow draw behind its window.
201+
//
202+
// Note that niri has no way of knowing about the CSD window corner
203+
// radius. It has to assume that windows have square corners, leading to
204+
// shadow artifacts inside the CSD rounded corners. This setting fixes
205+
// those artifacts.
206+
//
207+
// However, instead you may want to set prefer-no-csd and/or
208+
// geometry-corner-radius. Then, niri will know the corner radius and
209+
// draw the shadow correctly, without having to draw it behind the
210+
// window. These will also remove client-side shadows if the window
211+
// draws any.
212+
//
213+
// draw-behind-window true
214+
215+
// You can change how shadows look. The values below are in logical
216+
// pixels and match the CSS box-shadow properties.
217+
218+
// Softness controls the shadow blur radius.
219+
softness 30
220+
221+
// Spread expands the shadow.
222+
spread 5
223+
224+
// Offset moves the shadow relative to the window.
225+
offset x=0 y=5
226+
227+
// You can also change the shadow color and opacity.
228+
color "#0007"
229+
}
230+
194231
// Struts shrink the area occupied by windows, similarly to layer-shell panels.
195232
// You can think of them as a kind of outer gaps. They are set in logical pixels.
196233
// Left and right struts will cause the next window to the side to always be visible.

src/layout/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pub mod insert_hint_element;
7777
pub mod monitor;
7878
pub mod opening_window;
7979
pub mod scrolling;
80+
pub mod shadow;
8081
pub mod tile;
8182
pub mod workspace;
8283

@@ -304,6 +305,7 @@ pub struct Options {
304305
pub struts: Struts,
305306
pub focus_ring: niri_config::FocusRing,
306307
pub border: niri_config::Border,
308+
pub shadow: niri_config::Shadow,
307309
pub insert_hint: niri_config::InsertHint,
308310
pub center_focused_column: CenterFocusedColumn,
309311
pub always_center_single_column: bool,
@@ -327,6 +329,7 @@ impl Default for Options {
327329
struts: Default::default(),
328330
focus_ring: Default::default(),
329331
border: Default::default(),
332+
shadow: Default::default(),
330333
insert_hint: Default::default(),
331334
center_focused_column: Default::default(),
332335
always_center_single_column: false,
@@ -509,6 +512,7 @@ impl Options {
509512
struts: layout.struts,
510513
focus_ring: layout.focus_ring,
511514
border: layout.border,
515+
shadow: layout.shadow,
512516
insert_hint: layout.insert_hint,
513517
center_focused_column: layout.center_focused_column,
514518
always_center_single_column: layout.always_center_single_column,
@@ -7072,12 +7076,26 @@ mod tests {
70727076
}
70737077
}
70747078

7079+
prop_compose! {
7080+
fn arbitrary_shadow()(
7081+
on in any::<bool>(),
7082+
width in arbitrary_spacing(),
7083+
) -> niri_config::Shadow {
7084+
niri_config::Shadow {
7085+
on,
7086+
softness: FloatOrInt(width),
7087+
..Default::default()
7088+
}
7089+
}
7090+
}
7091+
70757092
prop_compose! {
70767093
fn arbitrary_options()(
70777094
gaps in arbitrary_spacing(),
70787095
struts in arbitrary_struts(),
70797096
focus_ring in arbitrary_focus_ring(),
70807097
border in arbitrary_border(),
7098+
shadow in arbitrary_shadow(),
70817099
center_focused_column in arbitrary_center_focused_column(),
70827100
always_center_single_column in any::<bool>(),
70837101
empty_workspace_above_first in any::<bool>(),
@@ -7090,6 +7108,7 @@ mod tests {
70907108
empty_workspace_above_first,
70917109
focus_ring,
70927110
border,
7111+
shadow,
70937112
..Default::default()
70947113
}
70957114
}

0 commit comments

Comments
 (0)