Skip to content

Commit 8cc8756

Browse files
committed
Add geometry-corner-radius and background-effect popup rules
1 parent 30cc4e9 commit 8cc8756

File tree

10 files changed

+210
-82
lines changed

10 files changed

+210
-82
lines changed

docs/wiki/Configuration:-Layer-Rules.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ layer-rule {
4242
noise 0.05
4343
saturation 3
4444
}
45+
46+
popups {
47+
opacity 0.5
48+
geometry-corner-radius 6
49+
50+
background-effect {
51+
xray true
52+
blur true
53+
noise 0.05
54+
saturation 3
55+
}
56+
}
4557
}
4658
```
4759

@@ -241,3 +253,35 @@ layer-rule {
241253
}
242254
}
243255
```
256+
257+
#### `popups`
258+
259+
<sup>Since: next release</sup>
260+
261+
Override properties for this layer surface's pop-ups (e.g. a menu opened by clicking an item in Waybar).
262+
263+
The properties work the same way as the corresponding layer-rule properties, except that they apply to the layer surface's pop-ups rather than to the layer surface itself.
264+
265+
`opacity` is applied *on top* of the layer surface's own opacity rule, so setting both will make pop-ups more transparent than the surface.
266+
Other properties apply independently.
267+
268+
> [!NOTE]
269+
> This block does not affect input-method pop-ups, such as Fcitx.
270+
> Only the app's own pop-ups.
271+
272+
```kdl
273+
// Blur the background behind Waybar popup menus.
274+
layer-rule {
275+
match namespace="^waybar$"
276+
277+
popups {
278+
// Match the default GTK 3 popup corner radius.
279+
geometry-corner-radius 6
280+
opacity 0.85
281+
282+
background-effect {
283+
blur true
284+
}
285+
}
286+
}
287+
```

docs/wiki/Configuration:-Window-Rules.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ window-rule {
107107
saturation 3
108108
}
109109
110+
popups {
111+
opacity 0.5
112+
geometry-corner-radius 15
113+
114+
background-effect {
115+
xray true
116+
blur true
117+
noise 0.05
118+
saturation 3
119+
}
120+
}
121+
110122
min-width 100
111123
max-width 200
112124
min-height 300
@@ -941,6 +953,45 @@ window-rule {
941953
}
942954
```
943955

956+
#### `popups`
957+
958+
<sup>Since: next release</sup>
959+
960+
Override properties for this window's pop-ups (menus and tooltips).
961+
962+
The properties work the same way as the corresponding window-rule properties, except that they apply to the window's pop-ups rather than to the window itself.
963+
964+
`opacity` is applied *on top* of the layer surface's own opacity rule, so setting both will make pop-ups more transparent than the surface.
965+
Other properties apply independently.
966+
967+
> [!NOTE]
968+
> This block does not affect input-method pop-ups, such as Fcitx.
969+
> Only the app's own pop-ups.
970+
971+
```kdl
972+
// Blur the background behind pop-up menus in Nautilus.
973+
window-rule {
974+
match app-id="Nautilus"
975+
976+
popups {
977+
// Matches the default libadwaita pop-up corner radius.
978+
geometry-corner-radius 15
979+
980+
// Note: it'll look better to set background opacity
981+
// through your GTK theme CSS and not here.
982+
// This is just an example that makes it look obvious.
983+
opacity 0.5
984+
985+
background-effect {
986+
blur true
987+
}
988+
}
989+
}
990+
```
991+
992+
Keep in mind that the background effect will look right only if the pop-up is shaped like a (rounded) rectangle, and the window correctly sets its Wayland geometry to exclude any shadows.
993+
For example, GTK 4 pop-ups with pointing arrows (`has-arrow=true` property) are *not* rounded rectangles—the arrow sticks out—so if you enable blur, it will also stick out of the pop-up.
994+
944995
#### Size Overrides
945996

946997
You can amend the window's minimum and maximum size in logical pixels.

niri-config/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,13 @@ mod tests {
18641864
},
18651865
popups: PopupsRule {
18661866
opacity: None,
1867+
geometry_corner_radius: None,
1868+
background_effect: BackgroundEffectRule {
1869+
xray: None,
1870+
blur: None,
1871+
noise: None,
1872+
saturation: None,
1873+
},
18671874
},
18681875
},
18691876
],
@@ -1908,6 +1915,13 @@ mod tests {
19081915
},
19091916
popups: PopupsRule {
19101917
opacity: None,
1918+
geometry_corner_radius: None,
1919+
background_effect: BackgroundEffectRule {
1920+
xray: None,
1921+
blur: None,
1922+
noise: None,
1923+
saturation: None,
1924+
},
19111925
},
19121926
},
19131927
],

niri-config/src/window_rule.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use niri_ipc::ColumnDisplay;
22

33
use crate::appearance::{
4-
BackgroundEffectRule, BlockOutFrom, BorderRule, CornerRadius, ShadowRule, TabIndicatorRule,
4+
BackgroundEffect, BackgroundEffectRule, BlockOutFrom, BorderRule, CornerRadius, ShadowRule,
5+
TabIndicatorRule,
56
};
67
use crate::layout::DefaultPresetSize;
78
use crate::utils::{MergeWith, RegexEq};
@@ -85,20 +86,34 @@ pub struct WindowRule {
8586
pub struct PopupsRule {
8687
#[knuffel(child, unwrap(argument))]
8788
pub opacity: Option<f32>,
89+
#[knuffel(child)]
90+
pub geometry_corner_radius: Option<CornerRadius>,
91+
#[knuffel(child, default)]
92+
pub background_effect: BackgroundEffectRule,
8893
}
8994

9095
/// Resolved popup-specific rules.
9196
#[derive(Debug, Default, Clone, Copy, PartialEq)]
9297
pub struct ResolvedPopupsRules {
9398
/// Extra opacity to draw popups with.
9499
pub opacity: Option<f32>,
100+
101+
/// Corner radius to assume the popups have.
102+
pub geometry_corner_radius: Option<CornerRadius>,
103+
104+
/// Background effect configuration for popups.
105+
pub background_effect: BackgroundEffect,
95106
}
96107

97108
impl MergeWith<PopupsRule> for ResolvedPopupsRules {
98109
fn merge_with(&mut self, part: &PopupsRule) {
99110
if let Some(x) = part.opacity {
100111
self.opacity = Some(x);
101112
}
113+
if let Some(x) = part.geometry_corner_radius {
114+
self.geometry_corner_radius = Some(x);
115+
}
116+
self.background_effect.merge_with(&part.background_effect);
102117
}
103118
}
104119

src/layer/mapped.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ impl MappedLayer {
258258
mut ctx: RenderCtx<R>,
259259
ns: Option<usize>,
260260
location: Point<f64, Logical>,
261+
xray_pos: XrayPos,
261262
push: &mut dyn FnMut(LayerSurfaceRenderElement<R>),
262263
) {
263264
if ctx.target.should_block_out(self.rules.block_out_from) {
@@ -278,7 +279,8 @@ impl MappedLayer {
278279
let alpha = alpha * popup_rules.opacity.unwrap_or(1.).clamp(0., 1.);
279280

280281
let surface = popup.wl_surface();
281-
let surface_loc = location + (offset - popup.geometry().loc).to_f64();
282+
let popup_geo = popup.geometry();
283+
let surface_loc = location + (offset - popup_geo.loc).to_f64();
282284

283285
push_elements_from_surface_tree(
284286
ctx.renderer,
@@ -290,13 +292,29 @@ impl MappedLayer {
290292
&mut |elem| push(elem.into()),
291293
);
292294

293-
background_effect::render_for_surface(
294-
surface,
295+
let geometry = Rectangle::new(location + offset.to_f64(), popup_geo.size.to_f64());
296+
let surface_off = popup_geo.loc.upscale(-1).to_f64();
297+
let surface_anim_scale = Scale::from(1.);
298+
let mut effect = popup_rules.background_effect;
299+
// Default xray to false for pop-ups since they're always on top of something.
300+
if effect.xray.is_none() {
301+
effect.xray = Some(false);
302+
}
303+
let xray_pos = xray_pos.offset(offset.to_f64());
304+
background_effect::render_for_tile(
295305
ctx.as_gles(),
296306
ns,
307+
geometry,
308+
self.scale,
309+
false,
310+
surface,
311+
surface_off,
312+
surface_anim_scale,
297313
self.blur_config,
298-
surface_loc,
299-
scale,
314+
popup_rules.geometry_corner_radius.unwrap_or_default(),
315+
effect,
316+
false,
317+
xray_pos,
300318
&mut |elem| push(elem.into()),
301319
);
302320
}

src/layout/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,10 @@ pub trait LayoutElement {
166166
location: Point<f64, Logical>,
167167
scale: Scale<f64>,
168168
alpha: f32,
169+
xray_pos: XrayPos,
169170
push: &mut dyn FnMut(LayoutElementRenderElement<R>),
170171
) {
171-
self.render_popups(ctx.r(), location, scale, alpha, push);
172+
self.render_popups(ctx.r(), location, scale, alpha, xray_pos, push);
172173
self.render_normal(ctx.r(), location, scale, alpha, push);
173174
}
174175

@@ -191,9 +192,10 @@ pub trait LayoutElement {
191192
location: Point<f64, Logical>,
192193
scale: Scale<f64>,
193194
alpha: f32,
195+
xray_pos: XrayPos,
194196
push: &mut dyn FnMut(LayoutElementRenderElement<R>),
195197
) {
196-
let _ = (ctx, location, scale, alpha, push);
198+
let _ = (ctx, location, scale, alpha, xray_pos, push);
197199
}
198200

199201
/// Renders the background effect behind the main surface of the element.

src/layout/tile.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,10 +1065,14 @@ impl<W: LayoutElement> Tile<W> {
10651065
.scaled_by(1. - expanded_progress as f32);
10661066

10671067
// Popups go on top, whether it's resize or not.
1068-
self.window
1069-
.render_popups(ctx.r(), window_render_loc, scale, win_alpha, &mut |elem| {
1070-
push(elem.into())
1071-
});
1068+
self.window.render_popups(
1069+
ctx.r(),
1070+
window_render_loc,
1071+
scale,
1072+
win_alpha,
1073+
xray_pos,
1074+
&mut |elem| push(elem.into()),
1075+
);
10721076

10731077
// If we're resizing, try to render a shader, or a fallback.
10741078
let mut pushed_resize = false;

src/niri.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4275,17 +4275,29 @@ impl Niri {
42754275
// We use macros instead of closures to avoid borrowing issues (renderer and push() go
42764276
// into different functions).
42774277
macro_rules! push_popups_from_layer {
4278-
($layer:expr, $ns:expr, $backdrop:expr, $push:expr) => {{
4279-
self.render_layer_popups(ctx.r(), $ns, &layer_map, $layer, $backdrop, $push);
4278+
($layer:expr, $ns:expr, $xray_pos:expr, $backdrop:expr, $push:expr) => {{
4279+
self.render_layer_popups(
4280+
ctx.r(),
4281+
$ns,
4282+
&layer_map,
4283+
$layer,
4284+
$xray_pos,
4285+
$backdrop,
4286+
$push,
4287+
);
42804288
}};
42814289
($layer:expr, true) => {{
4282-
push_popups_from_layer!($layer, None, true, &mut |elem| push(elem.into()));
4290+
push_popups_from_layer!($layer, None, XrayPos::default(), true, &mut |elem| push(
4291+
elem.into()
4292+
));
42834293
}};
4284-
($layer:expr, $ns:expr, $push:expr) => {{
4285-
push_popups_from_layer!($layer, $ns, false, $push);
4294+
($layer:expr, $ns:expr, $xray_pos:expr, $push:expr) => {{
4295+
push_popups_from_layer!($layer, $ns, $xray_pos, false, $push);
42864296
}};
42874297
($layer:expr) => {{
4288-
push_popups_from_layer!($layer, None, false, &mut |elem| push(elem.into()));
4298+
push_popups_from_layer!($layer, None, XrayPos::default(), false, &mut |elem| push(
4299+
elem.into()
4300+
));
42894301
}};
42904302
}
42914303
macro_rules! push_normal_from_layer {
@@ -4363,8 +4375,9 @@ impl Niri {
43634375

43644376
for (ws, geo) in mon.workspaces_with_render_geo() {
43654377
let ns = Some(ws.id().get() as usize);
4366-
push_popups_from_layer!(Layer::Bottom, ns, process!(geo));
4367-
push_popups_from_layer!(Layer::Background, ns, process!(geo));
4378+
let xray_pos = XrayPos::new(geo.loc, zoom);
4379+
push_popups_from_layer!(Layer::Bottom, ns, xray_pos, process!(geo));
4380+
push_popups_from_layer!(Layer::Background, ns, xray_pos, process!(geo));
43684381
}
43694382

43704383
mon.render_workspaces(ctx.r(), focus_ring, &mut |elem| push(elem.into()));
@@ -4519,17 +4532,21 @@ impl Niri {
45194532
}
45204533
}
45214534

4535+
#[allow(clippy::too_many_arguments)]
45224536
fn render_layer_popups<R: NiriRenderer>(
45234537
&self,
45244538
mut ctx: RenderCtx<R>,
45254539
ns: Option<usize>,
45264540
layer_map: &LayerMap,
45274541
layer: Layer,
4542+
xray_pos: XrayPos,
45284543
for_backdrop: bool,
45294544
push: &mut dyn FnMut(LayerSurfaceRenderElement<R>),
45304545
) {
45314546
for (mapped, geo) in self.layers_in_render_order(layer_map, layer, for_backdrop) {
4532-
mapped.render_popups(ctx.r(), ns, geo.loc.to_f64(), push);
4547+
let loc = geo.loc.to_f64();
4548+
let xray_pos = xray_pos.offset(loc);
4549+
mapped.render_popups(ctx.r(), ns, loc, xray_pos, push);
45334550
}
45344551
}
45354552

@@ -5571,6 +5588,7 @@ impl Niri {
55715588
mapped.window.geometry().loc.to_f64(),
55725589
scale,
55735590
alpha,
5591+
XrayPos::default(),
55745592
&mut |elem| elements.push(elem.into()),
55755593
);
55765594

0 commit comments

Comments
 (0)