From 532c6d19ccabbaf65d1e39be2567f484781aac4c Mon Sep 17 00:00:00 2001
From: HigherOrderLogic <73709188+HigherOrderLogic@users.noreply.github.com>
Date: Sat, 4 Apr 2026 00:22:45 +1100
Subject: [PATCH] config: add rounded corner exponent option
---
docs/wiki/Configuration:-Layer-Rules.md | 19 ++
docs/wiki/Configuration:-Layout.md | 10 +
docs/wiki/Configuration:-Window-Rules.md | 19 ++
niri-config/src/appearance.rs | 11 +
niri-config/src/layer_rule.rs | 3 +
niri-config/src/lib.rs | 6 +
niri-config/src/window_rule.rs | 9 +
niri-visual-tests/src/cases/gradient_angle.rs | 3 +-
niri-visual-tests/src/cases/gradient_area.rs | 4 +-
niri-visual-tests/src/cases/gradient_oklab.rs | 2 +
.../src/cases/gradient_oklab_alpha.rs | 5 +-
.../src/cases/gradient_oklch_alpha.rs | 2 +
.../src/cases/gradient_oklch_decreasing.rs | 2 +
.../src/cases/gradient_oklch_increasing.rs | 2 +
.../src/cases/gradient_oklch_longer.rs | 2 +
.../src/cases/gradient_oklch_shorter.rs | 2 +
niri-visual-tests/src/cases/gradient_srgb.rs | 2 +
.../src/cases/gradient_srgb_alpha.rs | 5 +-
.../src/cases/gradient_srgblinear.rs | 2 +
.../src/cases/gradient_srgblinear_alpha.rs | 5 +-
src/layer/mapped.rs | 20 +-
src/layer/mod.rs | 5 +
src/layout/focus_ring.rs | 3 +
src/layout/insert_hint_element.rs | 15 +-
src/layout/mod.rs | 1 +
src/layout/shadow.rs | 5 +
src/layout/tab_indicator.rs | 19 +-
src/layout/tile.rs | 33 ++-
src/layout/workspace.rs | 2 +
src/render_helpers/background_effect.rs | 28 +-
src/render_helpers/border.rs | 9 +
src/render_helpers/clipped_surface.rs | 14 +
src/render_helpers/framebuffer_effect.rs | 15 +-
src/render_helpers/resize.rs | 2 +
src/render_helpers/shaders/border.frag | 8 +-
.../shaders/clipped_surface.frag | 5 +-
src/render_helpers/shaders/mod.rs | 245 +++++++++++++-----
.../shaders/resize_epilogue.frag | 2 +-
.../shaders/resize_prelude.frag | 3 +-
.../shaders/rounding_alpha.frag | 11 +-
.../shaders/rounding_alpha_superellipse.frag | 22 ++
src/render_helpers/shaders/shadow.frag | 24 +-
src/render_helpers/shadow.rs | 18 +-
src/render_helpers/xray.rs | 16 +-
src/ui/mru.rs | 21 +-
src/window/mapped.rs | 12 +-
src/window/mod.rs | 5 +
47 files changed, 550 insertions(+), 128 deletions(-)
create mode 100644 src/render_helpers/shaders/rounding_alpha_superellipse.frag
diff --git a/docs/wiki/Configuration:-Layer-Rules.md b/docs/wiki/Configuration:-Layer-Rules.md
index 0b2ccf34de..f223d56817 100644
--- a/docs/wiki/Configuration:-Layer-Rules.md
+++ b/docs/wiki/Configuration:-Layer-Rules.md
@@ -193,6 +193,25 @@ layer-rule {
}
```
+#### `geometry-corner-radius-exponent`
+
+Since: next release
+
+Sets the exponent used for corner rounding, using the superellipse equation `x^n + y^n = 1`.
+
+- 2 is the default circular rounding.
+- Values greater than 2 produce "squircle" corners.
+- Values closer to 1 produce chamfer-like corners.
+
+```kdl
+layer-rule {
+ match namespace="^launcher$"
+
+ geometry-corner-radius 12
+ geometry-corner-radius-exponent 4
+}
+```
+
#### `place-within-backdrop`
Since: 25.05
diff --git a/docs/wiki/Configuration:-Layout.md b/docs/wiki/Configuration:-Layout.md
index 5e5e2a5c6c..c239d715ae 100644
--- a/docs/wiki/Configuration:-Layout.md
+++ b/docs/wiki/Configuration:-Layout.md
@@ -73,6 +73,7 @@ layout {
position "right"
gaps-between-tabs 2
corner-radius 8
+ corner-radius-exponent 4
active-color "red"
inactive-color "gray"
urgent-color "blue"
@@ -459,6 +460,15 @@ It can be `left`, `right`, `top`, or `bottom`.
`corner-radius` sets the rounded corner radius for tabs in the indicator in logical pixels.
When `gaps-between-tabs` is zero, only the first and the last tabs have rounded corners, otherwise all tabs do.
+`corner-radius-exponent` Since: next release sets the shape of the rounded corners.
+The default is `2` for normal circular rounding, values greater than `2` make "squircle" corners, and `1` make the corners "chamfered".
+
+Tab corner exponents are picked in this order:
+
+1. `corner-radius-exponent` from the `tab-indicator` window rule, if set.
+1. `geometry-corner-radius-exponent` from the window rule, if set.
+1. `corner-radius-exponent` from the `tab-indicator` layout options.
+
`active-color`, `inactive-color`, `urgent-color`, `active-gradient`, `inactive-gradient`, `urgent-gradient` let you override the colors for the tabs.
They have the same semantics as the border and focus ring colors and gradients.
diff --git a/docs/wiki/Configuration:-Window-Rules.md b/docs/wiki/Configuration:-Window-Rules.md
index f8fa215ec3..0cca336fb3 100644
--- a/docs/wiki/Configuration:-Window-Rules.md
+++ b/docs/wiki/Configuration:-Window-Rules.md
@@ -811,6 +811,7 @@ window-rule {
tab-indicator {
inactive-color "darkred"
+ corner-radius-exponent 4
}
}
```
@@ -847,6 +848,23 @@ This way, you can match GTK 3 applications which have square bottom corners:

+#### `geometry-corner-radius-exponent`
+
+Since: next release
+
+Sets the exponent used for corner rounding, using the superellipse equation `x^n + y^n = 1`.
+
+- 2 is the default circular rounding.
+- Values greater than 2 produce "squircle" corners.
+- Values closer to 1 produce chamfer-like corners.
+
+```kdl
+window-rule {
+ geometry-corner-radius 12
+ geometry-corner-radius-exponent 4
+}
+```
+
#### `clip-to-geometry`
Since: 0.1.6
@@ -994,6 +1012,7 @@ window-rule {
popups {
// Matches the default libadwaita pop-up corner radius.
geometry-corner-radius 15
+ geometry-corner-radius-exponent 2
// Note: it'll look better to set background opacity
// through your GTK theme CSS and not here.
diff --git a/niri-config/src/appearance.rs b/niri-config/src/appearance.rs
index a1c8327149..94bf53fa81 100644
--- a/niri-config/src/appearance.rs
+++ b/niri-config/src/appearance.rs
@@ -148,6 +148,8 @@ pub struct CornerRadius {
pub bottom_left: f32,
}
+pub const DEFAULT_CORNER_RADIUS_EXPONENT: f32 = 2.;
+
impl From for [f32; 4] {
fn from(value: CornerRadius) -> Self {
[
@@ -466,6 +468,7 @@ pub struct TabIndicator {
pub position: TabIndicatorPosition,
pub gaps_between_tabs: f64,
pub corner_radius: f64,
+ pub corner_radius_exponent: f64,
pub active_color: Option,
pub inactive_color: Option,
pub urgent_color: Option,
@@ -488,6 +491,7 @@ impl Default for TabIndicator {
position: TabIndicatorPosition::Left,
gaps_between_tabs: 0.,
corner_radius: 0.,
+ corner_radius_exponent: DEFAULT_CORNER_RADIUS_EXPONENT as f64,
active_color: None,
inactive_color: None,
urgent_color: None,
@@ -513,6 +517,7 @@ impl MergeWith for TabIndicator {
width,
gaps_between_tabs,
corner_radius,
+ corner_radius_exponent,
);
merge_clone!((self, part), length, position);
@@ -548,6 +553,8 @@ pub struct TabIndicatorPart {
pub gaps_between_tabs: Option>,
#[knuffel(child, unwrap(argument))]
pub corner_radius: Option>,
+ #[knuffel(child, unwrap(argument))]
+ pub corner_radius_exponent: Option>,
#[knuffel(child)]
pub active_color: Option,
#[knuffel(child)]
@@ -666,6 +673,8 @@ pub struct ShadowRule {
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct TabIndicatorRule {
+ #[knuffel(child, unwrap(argument))]
+ pub corner_radius_exponent: Option>,
#[knuffel(child)]
pub active_color: Option,
#[knuffel(child)]
@@ -713,6 +722,8 @@ impl MergeWith for ShadowRule {
impl MergeWith for TabIndicatorRule {
fn merge_with(&mut self, part: &Self) {
+ merge_clone_opt!((self, part), corner_radius_exponent);
+
merge_color_gradient_opt!(
(self, part),
(active_color, active_gradient),
diff --git a/niri-config/src/layer_rule.rs b/niri-config/src/layer_rule.rs
index cf099b3a6d..b8acd8cb88 100644
--- a/niri-config/src/layer_rule.rs
+++ b/niri-config/src/layer_rule.rs
@@ -1,6 +1,7 @@
use crate::appearance::{BackgroundEffectRule, BlockOutFrom, CornerRadius, ShadowRule};
use crate::utils::RegexEq;
use crate::window_rule::PopupsRule;
+use crate::FloatOrInt;
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
pub struct LayerRule {
@@ -18,6 +19,8 @@ pub struct LayerRule {
#[knuffel(child)]
pub geometry_corner_radius: Option,
#[knuffel(child, unwrap(argument))]
+ pub geometry_corner_radius_exponent: Option>,
+ #[knuffel(child, unwrap(argument))]
pub place_within_backdrop: Option,
#[knuffel(child, unwrap(argument))]
pub baba_is_float: Option,
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index 909aeb80a5..29a4e5d910 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -1377,6 +1377,7 @@ mod tests {
position: Top,
gaps_between_tabs: 0.0,
corner_radius: 0.0,
+ corner_radius_exponent: 2.0,
active_color: None,
inactive_color: None,
urgent_color: None,
@@ -1837,6 +1838,7 @@ mod tests {
inactive_color: None,
},
tab_indicator: TabIndicatorRule {
+ corner_radius_exponent: None,
active_color: Some(
Color {
r: 1.0,
@@ -1854,6 +1856,7 @@ mod tests {
draw_border_with_background: None,
opacity: None,
geometry_corner_radius: None,
+ geometry_corner_radius_exponent: None,
clip_to_geometry: None,
baba_is_float: None,
block_out_from: None,
@@ -1883,6 +1886,7 @@ mod tests {
popups: PopupsRule {
opacity: None,
geometry_corner_radius: None,
+ geometry_corner_radius_exponent: None,
background_effect: BackgroundEffectRule {
xray: None,
blur: None,
@@ -1923,6 +1927,7 @@ mod tests {
inactive_color: None,
},
geometry_corner_radius: None,
+ geometry_corner_radius_exponent: None,
place_within_backdrop: None,
baba_is_float: None,
background_effect: BackgroundEffectRule {
@@ -1934,6 +1939,7 @@ mod tests {
popups: PopupsRule {
opacity: None,
geometry_corner_radius: None,
+ geometry_corner_radius_exponent: None,
background_effect: BackgroundEffectRule {
xray: None,
blur: None,
diff --git a/niri-config/src/window_rule.rs b/niri-config/src/window_rule.rs
index f2bc2ad157..803f7a0759 100644
--- a/niri-config/src/window_rule.rs
+++ b/niri-config/src/window_rule.rs
@@ -60,6 +60,8 @@ pub struct WindowRule {
#[knuffel(child)]
pub geometry_corner_radius: Option,
#[knuffel(child, unwrap(argument))]
+ pub geometry_corner_radius_exponent: Option>,
+ #[knuffel(child, unwrap(argument))]
pub clip_to_geometry: Option,
#[knuffel(child, unwrap(argument))]
pub baba_is_float: Option,
@@ -88,6 +90,8 @@ pub struct PopupsRule {
pub opacity: Option,
#[knuffel(child)]
pub geometry_corner_radius: Option,
+ #[knuffel(child, unwrap(argument))]
+ pub geometry_corner_radius_exponent: Option>,
#[knuffel(child, default)]
pub background_effect: BackgroundEffectRule,
}
@@ -100,6 +104,8 @@ pub struct ResolvedPopupsRules {
/// Corner radius to assume the popups have.
pub geometry_corner_radius: Option,
+ /// Exponent to use for popup corner rounding.
+ pub geometry_corner_radius_exponent: Option,
/// Background effect configuration for popups.
pub background_effect: BackgroundEffect,
@@ -113,6 +119,9 @@ impl MergeWith for ResolvedPopupsRules {
if let Some(x) = part.geometry_corner_radius {
self.geometry_corner_radius = Some(x);
}
+ if let Some(x) = part.geometry_corner_radius_exponent {
+ self.geometry_corner_radius_exponent = Some(x.0 as f32);
+ }
self.background_effect.merge_with(&part.background_effect);
}
}
diff --git a/niri-visual-tests/src/cases/gradient_angle.rs b/niri-visual-tests/src/cases/gradient_angle.rs
index 6cf666c141..401b6187f3 100644
--- a/niri-visual-tests/src/cases/gradient_angle.rs
+++ b/niri-visual-tests/src/cases/gradient_angle.rs
@@ -2,7 +2,7 @@ use std::f32::consts::{FRAC_PI_2, PI};
use std::time::Duration;
use niri::render_helpers::border::BorderRenderElement;
-use niri_config::{Color, CornerRadius, GradientInterpolation};
+use niri_config::{Color, CornerRadius, GradientInterpolation, DEFAULT_CORNER_RADIUS_EXPONENT};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Physical, Point, Rectangle, Size};
@@ -62,6 +62,7 @@ impl TestCase for GradientAngle {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_area.rs b/niri-visual-tests/src/cases/gradient_area.rs
index f92f850420..2e17c3892e 100644
--- a/niri-visual-tests/src/cases/gradient_area.rs
+++ b/niri-visual-tests/src/cases/gradient_area.rs
@@ -3,7 +3,7 @@ use std::time::Duration;
use niri::layout::focus_ring::FocusRing;
use niri::render_helpers::border::BorderRenderElement;
-use niri_config::{Color, CornerRadius, GradientInterpolation};
+use niri_config::{Color, CornerRadius, GradientInterpolation, DEFAULT_CORNER_RADIUS_EXPONENT};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Physical, Point, Rectangle, Size};
@@ -86,6 +86,7 @@ impl TestCase for GradientArea {
false,
Rectangle::default(),
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
);
@@ -103,6 +104,7 @@ impl TestCase for GradientArea {
Rectangle::from_size(rect_size).to_f64(),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_oklab.rs b/niri-visual-tests/src/cases/gradient_oklab.rs
index c933d278aa..dac523fec6 100644
--- a/niri-visual-tests/src/cases/gradient_oklab.rs
+++ b/niri-visual-tests/src/cases/gradient_oklab.rs
@@ -1,6 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -43,6 +44,7 @@ impl TestCase for GradientOklab {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_oklab_alpha.rs b/niri-visual-tests/src/cases/gradient_oklab_alpha.rs
index a0d65f9293..158eb32f5f 100644
--- a/niri-visual-tests/src/cases/gradient_oklab_alpha.rs
+++ b/niri-visual-tests/src/cases/gradient_oklab_alpha.rs
@@ -1,5 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
-use niri_config::{Color, CornerRadius, GradientColorSpace, GradientInterpolation};
+use niri_config::{
+ Color, CornerRadius, GradientColorSpace, GradientInterpolation, DEFAULT_CORNER_RADIUS_EXPONENT,
+};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Physical, Point, Rectangle, Size};
@@ -41,6 +43,7 @@ impl TestCase for GradientOklabAlpha {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_oklch_alpha.rs b/niri-visual-tests/src/cases/gradient_oklch_alpha.rs
index ebcfb09a98..2dd0a5dae9 100644
--- a/niri-visual-tests/src/cases/gradient_oklch_alpha.rs
+++ b/niri-visual-tests/src/cases/gradient_oklch_alpha.rs
@@ -1,6 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -43,6 +44,7 @@ impl TestCase for GradientOklchAlpha {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_oklch_decreasing.rs b/niri-visual-tests/src/cases/gradient_oklch_decreasing.rs
index fb604fae69..4dc3e678d2 100644
--- a/niri-visual-tests/src/cases/gradient_oklch_decreasing.rs
+++ b/niri-visual-tests/src/cases/gradient_oklch_decreasing.rs
@@ -1,6 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -43,6 +44,7 @@ impl TestCase for GradientOklchDecreasing {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_oklch_increasing.rs b/niri-visual-tests/src/cases/gradient_oklch_increasing.rs
index 776b0d51dc..08c7e8ac90 100644
--- a/niri-visual-tests/src/cases/gradient_oklch_increasing.rs
+++ b/niri-visual-tests/src/cases/gradient_oklch_increasing.rs
@@ -1,6 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -43,6 +44,7 @@ impl TestCase for GradientOklchIncreasing {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_oklch_longer.rs b/niri-visual-tests/src/cases/gradient_oklch_longer.rs
index 870b117561..4d7fa83ac5 100644
--- a/niri-visual-tests/src/cases/gradient_oklch_longer.rs
+++ b/niri-visual-tests/src/cases/gradient_oklch_longer.rs
@@ -1,6 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -43,6 +44,7 @@ impl TestCase for GradientOklchLonger {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_oklch_shorter.rs b/niri-visual-tests/src/cases/gradient_oklch_shorter.rs
index 5622ac35a6..c58d2a533d 100644
--- a/niri-visual-tests/src/cases/gradient_oklch_shorter.rs
+++ b/niri-visual-tests/src/cases/gradient_oklch_shorter.rs
@@ -1,6 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -43,6 +44,7 @@ impl TestCase for GradientOklchShorter {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_srgb.rs b/niri-visual-tests/src/cases/gradient_srgb.rs
index 4afc69b6c8..4f772435df 100644
--- a/niri-visual-tests/src/cases/gradient_srgb.rs
+++ b/niri-visual-tests/src/cases/gradient_srgb.rs
@@ -1,6 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -43,6 +44,7 @@ impl TestCase for GradientSrgb {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_srgb_alpha.rs b/niri-visual-tests/src/cases/gradient_srgb_alpha.rs
index 1cb602fafd..b349dcaf41 100644
--- a/niri-visual-tests/src/cases/gradient_srgb_alpha.rs
+++ b/niri-visual-tests/src/cases/gradient_srgb_alpha.rs
@@ -1,5 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
-use niri_config::{Color, CornerRadius, GradientColorSpace, GradientInterpolation};
+use niri_config::{
+ Color, CornerRadius, GradientColorSpace, GradientInterpolation, DEFAULT_CORNER_RADIUS_EXPONENT,
+};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Physical, Point, Rectangle, Size};
@@ -41,6 +43,7 @@ impl TestCase for GradientSrgbAlpha {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_srgblinear.rs b/niri-visual-tests/src/cases/gradient_srgblinear.rs
index 5c24275f98..b7337e8737 100644
--- a/niri-visual-tests/src/cases/gradient_srgblinear.rs
+++ b/niri-visual-tests/src/cases/gradient_srgblinear.rs
@@ -1,6 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -43,6 +44,7 @@ impl TestCase for GradientSrgbLinear {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/niri-visual-tests/src/cases/gradient_srgblinear_alpha.rs b/niri-visual-tests/src/cases/gradient_srgblinear_alpha.rs
index 59b8fbce36..259bb6e0c3 100644
--- a/niri-visual-tests/src/cases/gradient_srgblinear_alpha.rs
+++ b/niri-visual-tests/src/cases/gradient_srgblinear_alpha.rs
@@ -1,5 +1,7 @@
use niri::render_helpers::border::BorderRenderElement;
-use niri_config::{Color, CornerRadius, GradientColorSpace, GradientInterpolation};
+use niri_config::{
+ Color, CornerRadius, GradientColorSpace, GradientInterpolation, DEFAULT_CORNER_RADIUS_EXPONENT,
+};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Physical, Point, Rectangle, Size};
@@ -41,6 +43,7 @@ impl TestCase for GradientSrgbLinearAlpha {
Rectangle::from_size(area.size),
0.,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
1.,
1.,
)
diff --git a/src/layer/mapped.rs b/src/layer/mapped.rs
index fbaa6fc61f..c5077e97d9 100644
--- a/src/layer/mapped.rs
+++ b/src/layer/mapped.rs
@@ -1,5 +1,5 @@
use niri_config::utils::MergeWith as _;
-use niri_config::{Config, LayerRule};
+use niri_config::{Config, LayerRule, DEFAULT_CORNER_RADIUS_EXPONENT};
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::element::Kind;
use smithay::desktop::{LayerSurface, PopupKind, PopupManager};
@@ -122,8 +122,16 @@ impl MappedLayer {
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, self.scale, 1.);
+ self.shadow.update_render_elements(
+ size,
+ true,
+ radius,
+ self.rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
+ self.scale,
+ 1.,
+ );
}
pub fn are_animations_ongoing(&self) -> bool {
@@ -248,6 +256,9 @@ impl MappedLayer {
surface_anim_scale,
self.blur_config,
radius,
+ self.rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
self.rules.background_effect,
should_block_out,
xray_pos,
@@ -317,6 +328,9 @@ impl MappedLayer {
surface_anim_scale,
self.blur_config,
popup_rules.geometry_corner_radius.unwrap_or_default(),
+ popup_rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
effect,
false,
xray_pos,
diff --git a/src/layer/mod.rs b/src/layer/mod.rs
index 767a6b0ec1..4e3a6580c7 100644
--- a/src/layer/mod.rs
+++ b/src/layer/mod.rs
@@ -21,6 +21,8 @@ pub struct ResolvedLayerRules {
/// Corner radius to assume this layer surface has.
pub geometry_corner_radius: Option,
+ /// Exponent to use for corner rounding.
+ pub geometry_corner_radius_exponent: Option,
/// Whether to place this layer surface within the overview backdrop.
pub place_within_backdrop: bool,
@@ -69,6 +71,9 @@ impl ResolvedLayerRules {
if let Some(x) = rule.geometry_corner_radius {
resolved.geometry_corner_radius = Some(x);
}
+ if let Some(x) = rule.geometry_corner_radius_exponent {
+ resolved.geometry_corner_radius_exponent = Some(x.0 as f32);
+ }
if let Some(x) = rule.place_within_backdrop {
resolved.place_within_backdrop = x;
}
diff --git a/src/layout/focus_ring.rs b/src/layout/focus_ring.rs
index b53a5df8b9..49a7bc77cf 100644
--- a/src/layout/focus_ring.rs
+++ b/src/layout/focus_ring.rs
@@ -63,6 +63,7 @@ impl FocusRing {
is_urgent: bool,
view_rect: Rectangle,
radius: CornerRadius,
+ exponent: f32,
scale: f64,
alpha: f32,
) {
@@ -190,6 +191,7 @@ impl FocusRing {
Rectangle::new(full_rect.loc - loc, full_rect.size),
rounded_corner_border_width,
radius,
+ exponent,
scale as f32,
alpha,
);
@@ -209,6 +211,7 @@ impl FocusRing {
Rectangle::new(full_rect.loc - self.locations[0], full_rect.size),
rounded_corner_border_width,
radius,
+ exponent,
scale as f32,
alpha,
);
diff --git a/src/layout/insert_hint_element.rs b/src/layout/insert_hint_element.rs
index 97bbf2d5cb..c6931f05f6 100644
--- a/src/layout/insert_hint_element.rs
+++ b/src/layout/insert_hint_element.rs
@@ -1,4 +1,4 @@
-use niri_config::CornerRadius;
+use niri_config::{CornerRadius, DEFAULT_CORNER_RADIUS_EXPONENT};
use smithay::utils::{Logical, Point, Rectangle, Size};
use super::focus_ring::{FocusRing, FocusRingRenderElement};
@@ -51,8 +51,17 @@ impl InsertHintElement {
radius: CornerRadius,
scale: f64,
) {
- self.inner
- .update_render_elements(size, true, false, false, view_rect, radius, scale, 1.);
+ self.inner.update_render_elements(
+ size,
+ true,
+ false,
+ false,
+ view_rect,
+ radius,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
+ scale,
+ 1.,
+ );
}
pub fn render(
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 5c4dd639e4..a2d8818901 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -208,6 +208,7 @@ pub trait LayoutElement {
_clip_to_geometry: bool,
_surface_anim_scale: Scale,
_radius: CornerRadius,
+ _exponent: f32,
_xray_pos: XrayPos,
_push: &mut dyn FnMut(BackgroundEffectElement),
) {
diff --git a/src/layout/shadow.rs b/src/layout/shadow.rs
index 509ad728cc..8efa6f2a4e 100644
--- a/src/layout/shadow.rs
+++ b/src/layout/shadow.rs
@@ -37,6 +37,7 @@ impl Shadow {
win_size: Size,
is_active: bool,
radius: CornerRadius,
+ exponent: f32,
scale: f64,
alpha: f32,
) {
@@ -133,9 +134,11 @@ impl Shadow {
color,
sigma as f32,
radius,
+ exponent,
scale as f32,
Rectangle::new(window_geo.loc - offset - rect.loc, window_geo.size),
win_radius,
+ exponent,
alpha,
);
@@ -152,9 +155,11 @@ impl Shadow {
color,
sigma as f32,
radius,
+ exponent,
scale as f32,
Rectangle::zero(),
Default::default(),
+ exponent,
alpha,
);
diff --git a/src/layout/tab_indicator.rs b/src/layout/tab_indicator.rs
index e7d9a3df1a..95696c8168 100644
--- a/src/layout/tab_indicator.rs
+++ b/src/layout/tab_indicator.rs
@@ -28,6 +28,8 @@ pub struct TabInfo {
pub gradient: Gradient,
/// Tab geometry in the same coordinate system as the area.
pub geometry: Rectangle,
+ /// Corner exponent for this tab.
+ pub corner_exponent: f32,
}
niri_render_elements! {
@@ -263,6 +265,7 @@ impl TabIndicator {
Rectangle::from_size(rect.size),
0.,
radius,
+ tab.corner_exponent,
scale as f32,
1.,
);
@@ -406,7 +409,19 @@ impl TabInfo {
.unwrap_or_else(gradient_from_border);
let geometry = Rectangle::new(position, tile.animated_tile_size());
-
- TabInfo { gradient, geometry }
+ let corner_exponent = rule.corner_radius_exponent.map_or_else(
+ || {
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(config.corner_radius_exponent as f32)
+ },
+ |x| x.0 as f32,
+ );
+
+ TabInfo {
+ gradient,
+ geometry,
+ corner_exponent,
+ }
}
}
diff --git a/src/layout/tile.rs b/src/layout/tile.rs
index 86c7ebadb1..7d9432d4e5 100644
--- a/src/layout/tile.rs
+++ b/src/layout/tile.rs
@@ -2,7 +2,7 @@ use core::f64;
use std::rc::Rc;
use niri_config::utils::MergeWith as _;
-use niri_config::{Color, CornerRadius, GradientInterpolation};
+use niri_config::{Color, CornerRadius, GradientInterpolation, DEFAULT_CORNER_RADIUS_EXPONENT};
use niri_ipc::WindowLayout;
use smithay::backend::renderer::element::{Element, Kind};
use smithay::backend::renderer::gles::GlesRenderer;
@@ -408,6 +408,11 @@ impl Tile {
.geometry_corner_radius()
.fit_to(window_size.w as f32, window_size.h as f32);
self.rounded_corner_damage.set_corner_radius(radius);
+ self.rounded_corner_damage.set_corner_exponent(
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
+ );
}
pub fn advance_animations(&mut self) {
@@ -460,7 +465,6 @@ impl Tile {
let rules = self.window.rules();
let animated_tile_size = self.animated_tile_size();
let expanded_progress = self.expanded_progress();
-
let draw_border_with_background = rules
.draw_border_with_background
.unwrap_or_else(|| !self.window.has_ssd());
@@ -500,6 +504,9 @@ impl Tile {
view_rect.size,
),
radius,
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
self.scale,
1. - expanded_progress as f32,
);
@@ -515,6 +522,9 @@ impl Tile {
animated_tile_size,
is_active,
radius,
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
self.scale,
1. - expanded_progress as f32,
);
@@ -532,6 +542,9 @@ impl Tile {
self.window.is_urgent(),
view_rect,
radius,
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
self.scale,
1. - expanded_progress as f32,
);
@@ -1073,7 +1086,6 @@ impl Tile {
.window
.geometry_corner_radius()
.scaled_by(1. - expanded_progress as f32);
-
// Popups go on top, whether it's resize or not.
self.window.render_popups(
ctx.r(),
@@ -1134,6 +1146,9 @@ impl Tile {
resize.anim.value() as f32,
resize.anim.clamped_value().clamp(0., 1.) as f32,
radius,
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
clip_to_geometry,
win_alpha,
);
@@ -1182,6 +1197,9 @@ impl Tile {
geo,
shader.clone(),
radius,
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
)
.into();
}
@@ -1209,6 +1227,9 @@ impl Tile {
Rectangle::from_size(geo.size),
0.,
radius,
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
scale.x as f32,
1.,
)
@@ -1263,6 +1284,9 @@ impl Tile {
Rectangle::from_size(size),
0.,
radius,
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
scale.x as f32,
alpha,
)
@@ -1309,6 +1333,9 @@ impl Tile {
clip_to_geometry,
surface_anim_scale,
radius,
+ rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
xray_pos,
&mut |elem| push(elem.into()),
);
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index 7448779722..a19959408c 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -5,6 +5,7 @@ use std::time::Duration;
use niri_config::utils::MergeWith as _;
use niri_config::{
CenterFocusedColumn, CornerRadius, OutputName, PresetSize, Workspace as WorkspaceConfig,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use niri_ipc::{ColumnDisplay, PositionChange, SizeChange, WindowLayout};
use smithay::backend::renderer::element::Kind;
@@ -387,6 +388,7 @@ impl Workspace {
self.view_size,
true,
CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
self.scale.fractional_scale(),
1.,
);
diff --git a/src/render_helpers/background_effect.rs b/src/render_helpers/background_effect.rs
index d148b6c150..5df859fa08 100644
--- a/src/render_helpers/background_effect.rs
+++ b/src/render_helpers/background_effect.rs
@@ -1,6 +1,6 @@
use std::sync::{Arc, Mutex};
-use niri_config::CornerRadius;
+use niri_config::{CornerRadius, DEFAULT_CORNER_RADIUS_EXPONENT};
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Point, Rectangle, Scale};
use smithay::wayland::compositor::{with_states, SurfaceData};
@@ -26,6 +26,8 @@ pub struct BackgroundEffect {
/// Stored here in addition to `RenderParams` to damage when it changes.
// FIXME: would be good to remove this duplication of radius.
corner_radius: CornerRadius,
+ /// Corner exponent for clipping.
+ corner_exponent: f32,
blur_config: niri_config::Blur,
options: Options,
}
@@ -56,15 +58,15 @@ pub struct RenderParams {
///
/// `subregion.iter()` should return `geometry`-relative rectangles.
pub subregion: Option,
- /// Geometry and radius for clipping in the same coordinate space as `geometry`.
- pub clip: Option<(Rectangle, CornerRadius)>,
+ /// Geometry, radius and exponent for clipping in the same coordinate space as `geometry`.
+ pub clip: Option<(Rectangle, CornerRadius, f32)>,
/// Scale to use for rounding to physical pixels.
pub scale: f64,
}
impl RenderParams {
fn fit_clip_radius(&mut self) {
- if let Some((geo, radius)) = &mut self.clip {
+ if let Some((geo, radius, _)) = &mut self.clip {
// HACK: increase radius to avoid slight bleed on rounded corners.
*radius = radius.expanded_by(1.);
@@ -87,6 +89,7 @@ impl BackgroundEffect {
nonxray: FramebufferEffect::new(),
damage: ExtraDamage::new(),
corner_radius: CornerRadius::default(),
+ corner_exponent: DEFAULT_CORNER_RADIUS_EXPONENT,
blur_config: niri_config::Blur::default(),
options: Options::default(),
}
@@ -111,6 +114,7 @@ impl BackgroundEffect {
pub fn update_render_elements(
&mut self,
corner_radius: CornerRadius,
+ corner_exponent: f32,
effect: niri_config::BackgroundEffect,
has_blur_region: bool,
) {
@@ -134,12 +138,16 @@ impl BackgroundEffect {
options.xray = true;
}
- if self.options == options && self.corner_radius == corner_radius {
+ if self.options == options
+ && self.corner_radius == corner_radius
+ && self.corner_exponent == corner_exponent
+ {
return;
}
self.options = options;
self.corner_radius = corner_radius;
+ self.corner_exponent = corner_exponent;
self.damage.damage_all();
self.nonxray.damage();
}
@@ -162,6 +170,7 @@ impl BackgroundEffect {
if let Some(clip) = &mut params.clip {
clip.1 = self.corner_radius;
+ clip.2 = self.corner_exponent;
}
params.fit_clip_radius();
@@ -251,7 +260,11 @@ fn render_params_for_tile(
}
// This corner radius is reset to self.corner_radius in render().
- let clip = clip.then_some((geometry, CornerRadius::default()));
+ let clip = clip.then_some((
+ geometry,
+ CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
+ ));
Some(RenderParams {
geometry: effect_geometry,
@@ -292,6 +305,7 @@ pub fn render_for_tile(
surface_anim_scale: Scale,
blur_config: niri_config::Blur,
radius: CornerRadius,
+ corner_exponent: f32,
effect: niri_config::BackgroundEffect,
should_block_out: bool,
xray_pos: XrayPos,
@@ -305,7 +319,7 @@ pub fn render_for_tile(
let has_blur_region = blur_region.as_ref().is_some_and(|r| !r.is_empty());
background_effect.update_config(blur_config);
- background_effect.update_render_elements(radius, effect, has_blur_region);
+ background_effect.update_render_elements(radius, corner_exponent, effect, has_blur_region);
if !background_effect.is_visible() {
return;
diff --git a/src/render_helpers/border.rs b/src/render_helpers/border.rs
index 923066efed..fa78f71000 100644
--- a/src/render_helpers/border.rs
+++ b/src/render_helpers/border.rs
@@ -4,6 +4,7 @@ use std::rc::Rc;
use glam::{Mat3, Vec2};
use niri_config::{
Color, CornerRadius, GradientColorSpace, GradientInterpolation, HueInterpolation,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
};
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, Uniform};
@@ -41,6 +42,7 @@ struct Parameters {
geometry: Rectangle,
border_width: f32,
corner_radius: CornerRadius,
+ corner_exponent: f32,
// Should only be used for visual improvements, i.e. corner radius anti-aliasing.
scale: f32,
alpha: f32,
@@ -58,6 +60,7 @@ impl BorderRenderElement {
geometry: Rectangle,
border_width: f32,
corner_radius: CornerRadius,
+ corner_exponent: f32,
scale: f32,
alpha: f32,
) -> Self {
@@ -74,6 +77,7 @@ impl BorderRenderElement {
geometry,
border_width,
corner_radius,
+ corner_exponent,
scale,
alpha,
},
@@ -96,6 +100,7 @@ impl BorderRenderElement {
geometry: Default::default(),
border_width: 0.,
corner_radius: Default::default(),
+ corner_exponent: DEFAULT_CORNER_RADIUS_EXPONENT,
scale: 1.,
alpha: 1.,
},
@@ -118,6 +123,7 @@ impl BorderRenderElement {
geometry: Rectangle,
border_width: f32,
corner_radius: CornerRadius,
+ corner_exponent: f32,
scale: f32,
alpha: f32,
) {
@@ -131,6 +137,7 @@ impl BorderRenderElement {
geometry,
border_width,
corner_radius,
+ corner_exponent,
scale,
alpha,
};
@@ -153,6 +160,7 @@ impl BorderRenderElement {
geometry,
border_width,
corner_radius,
+ corner_exponent,
scale,
alpha,
} = self.params;
@@ -212,6 +220,7 @@ impl BorderRenderElement {
mat3_uniform("input_to_geo", input_to_geo),
Uniform::new("geo_size", geo_size.to_array()),
Uniform::new("outer_radius", <[f32; 4]>::from(corner_radius)),
+ Uniform::new("corner_exponent", corner_exponent),
Uniform::new("border_width", border_width),
]),
HashMap::new(),
diff --git a/src/render_helpers/clipped_surface.rs b/src/render_helpers/clipped_surface.rs
index e357895e66..e24dc90793 100644
--- a/src/render_helpers/clipped_surface.rs
+++ b/src/render_helpers/clipped_surface.rs
@@ -20,6 +20,7 @@ pub struct ClippedSurfaceRenderElement {
inner: WaylandSurfaceRenderElement,
program: GlesTexProgram,
corner_radius: CornerRadius,
+ corner_exponent: f32,
geometry: Rectangle,
scale: f32,
}
@@ -28,6 +29,7 @@ pub struct ClippedSurfaceRenderElement {
pub struct RoundedCornerDamage {
damage: ExtraDamage,
corner_radius: CornerRadius,
+ corner_exponent: f32,
}
impl ClippedSurfaceRenderElement {
@@ -37,11 +39,13 @@ impl ClippedSurfaceRenderElement {
geometry: Rectangle,
program: GlesTexProgram,
corner_radius: CornerRadius,
+ corner_exponent: f32,
) -> Self {
Self {
inner: elem,
program,
corner_radius,
+ corner_exponent,
geometry,
scale: scale.x as f32,
}
@@ -95,6 +99,7 @@ impl ClippedSurfaceRenderElement {
Uniform::new("niri_scale", self.scale),
Uniform::new("geo_size", geo_size),
Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
+ Uniform::new("corner_exponent", self.corner_exponent),
mat3_uniform("input_to_geo", input_to_geo),
]
}
@@ -300,6 +305,15 @@ impl RoundedCornerDamage {
self.damage.damage_all();
}
+ pub fn set_corner_exponent(&mut self, corner_exponent: f32) {
+ if self.corner_exponent == corner_exponent {
+ return;
+ }
+
+ self.corner_exponent = corner_exponent;
+ self.damage.damage_all();
+ }
+
pub fn render(&self, geometry: Rectangle) -> ExtraDamage {
self.damage.render(geometry)
}
diff --git a/src/render_helpers/framebuffer_effect.rs b/src/render_helpers/framebuffer_effect.rs
index 4dc529dbb4..9958335242 100644
--- a/src/render_helpers/framebuffer_effect.rs
+++ b/src/render_helpers/framebuffer_effect.rs
@@ -1,7 +1,7 @@
use std::cell::RefCell;
use glam::{Mat3, Vec2};
-use niri_config::CornerRadius;
+use niri_config::{CornerRadius, DEFAULT_CORNER_RADIUS_EXPONENT};
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::{Element, Id, RenderElement};
use smithay::backend::renderer::gles::{
@@ -33,6 +33,7 @@ pub struct FramebufferEffectElement {
geometry: Rectangle,
clip_geo: Rectangle,
corner_radius: CornerRadius,
+ corner_exponent: f32,
subregion: Option,
scale: f32,
blur_options: Option,
@@ -69,9 +70,11 @@ impl FramebufferEffect {
noise: f32,
saturation: f32,
) -> FramebufferEffectElement {
- let (clip_geo, corner_radius) = params
- .clip
- .unwrap_or((params.geometry, CornerRadius::default()));
+ let (clip_geo, corner_radius, corner_exponent) = params.clip.unwrap_or((
+ params.geometry,
+ CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
+ ));
let mut id = self.id.clone();
if let Some(ns) = ns {
@@ -84,6 +87,7 @@ impl FramebufferEffect {
geometry: params.geometry,
clip_geo,
corner_radius,
+ corner_exponent,
subregion: params.subregion,
scale: params.scale as f32,
blur_options,
@@ -98,7 +102,7 @@ impl FramebufferEffectElement {
&self,
crop: Rectangle,
transform: Transform,
- ) -> [Uniform<'static>; 7] {
+ ) -> [Uniform<'static>; 8] {
let offset = crop.loc - (self.clip_geo.loc - self.geometry.loc);
let offset = Vec2::new(offset.x as f32, offset.y as f32);
let crop_size = Vec2::new(crop.size.w as f32, crop.size.h as f32);
@@ -120,6 +124,7 @@ impl FramebufferEffectElement {
Uniform::new("niri_scale", self.scale),
Uniform::new("geo_size", clip_geo_size),
Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
+ Uniform::new("corner_exponent", self.corner_exponent),
mat3_uniform("input_to_geo", input_to_clip_geo),
Uniform::new("noise", self.noise),
Uniform::new("saturation", self.saturation),
diff --git a/src/render_helpers/resize.rs b/src/render_helpers/resize.rs
index 79b26dbf34..ff39e5095c 100644
--- a/src/render_helpers/resize.rs
+++ b/src/render_helpers/resize.rs
@@ -31,6 +31,7 @@ impl ResizeRenderElement {
progress: f32,
clamped_progress: f32,
corner_radius: CornerRadius,
+ corner_exponent: f32,
clip_to_geometry: bool,
result_alpha: f32,
) -> Self {
@@ -103,6 +104,7 @@ impl ResizeRenderElement {
Uniform::new("niri_progress", progress),
Uniform::new("niri_clamped_progress", clamped_progress),
Uniform::new("niri_corner_radius", <[f32; 4]>::from(corner_radius)),
+ Uniform::new("niri_corner_exponent", corner_exponent),
Uniform::new("niri_clip_to_geometry", clip_to_geometry),
]),
HashMap::from([
diff --git a/src/render_helpers/shaders/border.frag b/src/render_helpers/shaders/border.frag
index ed0e08877f..73ad307d27 100644
--- a/src/render_helpers/shaders/border.frag
+++ b/src/render_helpers/shaders/border.frag
@@ -21,6 +21,7 @@ uniform vec2 grad_vec;
uniform mat3 input_to_geo;
uniform vec2 geo_size;
uniform vec4 outer_radius;
+uniform float corner_exponent;
uniform float border_width;
vec4 premul_rect(vec4 color) {
@@ -208,12 +209,13 @@ vec4 gradient_color(vec2 coords) {
return color_mix(color_from, color_to, frac);
}
-float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius);
+float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius, float corner_exponent);
+
void main() {
vec3 coords_geo = input_to_geo * vec3(niri_v_coords, 1.0);
vec4 color = gradient_color(coords_geo.xy);
- color = color * niri_rounding_alpha(coords_geo.xy, geo_size, outer_radius);
+ color = color * niri_rounding_alpha(coords_geo.xy, geo_size, outer_radius, corner_exponent);
if (border_width > 0.0) {
coords_geo -= vec3(border_width);
@@ -222,7 +224,7 @@ void main() {
&& 0.0 <= coords_geo.y && coords_geo.y <= inner_geo_size.y)
{
vec4 inner_radius = max(outer_radius - vec4(border_width), 0.0);
- color = color * (1.0 - niri_rounding_alpha(coords_geo.xy, inner_geo_size, inner_radius));
+ color = color * (1.0 - niri_rounding_alpha(coords_geo.xy, inner_geo_size, inner_radius, corner_exponent));
}
}
diff --git a/src/render_helpers/shaders/clipped_surface.frag b/src/render_helpers/shaders/clipped_surface.frag
index e97f18c07b..34186b5e51 100644
--- a/src/render_helpers/shaders/clipped_surface.frag
+++ b/src/render_helpers/shaders/clipped_surface.frag
@@ -24,9 +24,10 @@ uniform float niri_scale;
uniform vec2 geo_size;
uniform vec4 corner_radius;
+uniform float corner_exponent;
uniform mat3 input_to_geo;
-float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius);
+float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius, float corner_exponent);
vec4 postprocess(vec4 color);
void main() {
@@ -45,7 +46,7 @@ void main() {
color = vec4(0.0);
} else {
// Apply corner rounding inside geometry.
- color = color * niri_rounding_alpha(coords_geo.xy * geo_size, geo_size, corner_radius);
+ color = color * niri_rounding_alpha(coords_geo.xy * geo_size, geo_size, corner_radius, corner_exponent);
}
// Apply final alpha and tint.
diff --git a/src/render_helpers/shaders/mod.rs b/src/render_helpers/shaders/mod.rs
index 7be9b159fd..686b9ba1f7 100644
--- a/src/render_helpers/shaders/mod.rs
+++ b/src/render_helpers/shaders/mod.rs
@@ -10,6 +10,27 @@ use super::renderer::NiriRenderer;
use super::shader_element::ShaderProgram;
use crate::render_helpers::blur::BlurProgram;
+const ROUNDING_ALPHA_IMPL_PLACEHOLDER: &str = "@ROUNDING_ALPHA_IMPL@";
+const EXPONENT_SHADOW_X_IMPL_PLACEHOLDER: &str = "@EXPONENT_SHADOW_X_IMPL@";
+
+const ROUNDING_ALPHA_IMPL: &str = include_str!("rounding_alpha_superellipse.frag");
+const EXPONENT_SHADOW_X_IMPL: &str = "
+float normalized = (-delta) / max(corner, 0.0001);
+float remaining = max(0.0, 1.0 - pow(normalized, exponent));
+curved = halfSize.x - corner + corner * pow(remaining, 1.0 / exponent);";
+
+const ROUNDING_ALPHA_IMPL_FALLBACK: &str = "
+float niri_rounding_alpha_impl(vec2 coords, vec2 center, float radius, float corner_exponent) {
+ float dist = distance(coords, center);
+
+ // Manual smoothstep() between radius - half_px and radius + half_px
+ // to avoid a division in clamp().
+ float t = clamp((dist - radius) * niri_scale + 0.5, 0.0, 1.0);
+ return 1.0 - t * t * (3.0 - 2.0 * t);
+}";
+const EXPONENT_SHADOW_X_IMPL_FALLBACK: &str =
+ "curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));";
+
pub struct Shaders {
pub border: Option,
pub shadow: Option,
@@ -32,11 +53,82 @@ pub enum ProgramType {
Open,
}
+fn try_compile_shader(
+ shader_name: &str,
+ renderer: &mut GlesRenderer,
+ src: &str,
+ additional_uniforms: &[UniformName<'_>],
+ texture_uniforms: &[&str],
+) -> Option {
+ let replaced_src = src
+ .replace(ROUNDING_ALPHA_IMPL_PLACEHOLDER, ROUNDING_ALPHA_IMPL)
+ .replace(EXPONENT_SHADOW_X_IMPL_PLACEHOLDER, EXPONENT_SHADOW_X_IMPL);
+ match ShaderProgram::compile(
+ renderer,
+ &replaced_src,
+ additional_uniforms,
+ texture_uniforms,
+ ) {
+ Ok(p) => Some(p),
+ Err(err) => {
+ warn!("error compiling {shader_name} shader with support for corner radius exponent: {err:?}");
+ let fallback_src = src
+ .replace(
+ ROUNDING_ALPHA_IMPL_PLACEHOLDER,
+ ROUNDING_ALPHA_IMPL_FALLBACK,
+ )
+ .replace(
+ EXPONENT_SHADOW_X_IMPL_PLACEHOLDER,
+ EXPONENT_SHADOW_X_IMPL_FALLBACK,
+ );
+ ShaderProgram::compile(
+ renderer,
+ &fallback_src,
+ additional_uniforms,
+ texture_uniforms,
+ )
+ .inspect_err(|e| warn!("error compiling fallback {shader_name} shader: {e:?}"))
+ .ok()
+ }
+ }
+}
+
+fn try_compile_custom_texture_shader(
+ shader_name: &str,
+ renderer: &mut GlesRenderer,
+ src: &str,
+ additional_uniforms: &[UniformName<'_>],
+) -> Option {
+ let replaced_src = src
+ .replace(ROUNDING_ALPHA_IMPL_PLACEHOLDER, ROUNDING_ALPHA_IMPL)
+ .replace(EXPONENT_SHADOW_X_IMPL_PLACEHOLDER, EXPONENT_SHADOW_X_IMPL);
+ match renderer.compile_custom_texture_shader(replaced_src, additional_uniforms) {
+ Ok(s) => Some(s),
+ Err(err) => {
+ warn!("error compiling {shader_name} shader with support for corner radius exponent: {err:?}");
+ let fallback_src = src
+ .replace(
+ ROUNDING_ALPHA_IMPL_PLACEHOLDER,
+ ROUNDING_ALPHA_IMPL_FALLBACK,
+ )
+ .replace(
+ EXPONENT_SHADOW_X_IMPL_PLACEHOLDER,
+ EXPONENT_SHADOW_X_IMPL_FALLBACK,
+ );
+ renderer
+ .compile_custom_texture_shader(fallback_src, additional_uniforms)
+ .inspect_err(|e| warn!("error compiling fallback {shader_name} shader: {e:?}"))
+ .ok()
+ }
+ }
+}
+
impl Shaders {
fn compile(renderer: &mut GlesRenderer) -> Self {
let _span = tracy_client::span!("Shaders::compile");
- let border = ShaderProgram::compile(
+ let border = try_compile_shader(
+ "border",
renderer,
concat!(
include_str!("border.frag"),
@@ -53,16 +145,14 @@ impl Shaders {
UniformName::new("input_to_geo", UniformType::Matrix3x3),
UniformName::new("geo_size", UniformType::_2f),
UniformName::new("outer_radius", UniformType::_4f),
+ UniformName::new("corner_exponent", UniformType::_1f),
UniformName::new("border_width", UniformType::_1f),
],
&[],
- )
- .map_err(|err| {
- warn!("error compiling border shader: {err:?}");
- })
- .ok();
+ );
- let shadow = ShaderProgram::compile(
+ let shadow = try_compile_shader(
+ "shadow",
renderer,
concat!(
include_str!("shadow.frag"),
@@ -74,57 +164,51 @@ impl Shaders {
UniformName::new("input_to_geo", UniformType::Matrix3x3),
UniformName::new("geo_size", UniformType::_2f),
UniformName::new("corner_radius", UniformType::_4f),
+ UniformName::new("corner_exponent", UniformType::_1f),
UniformName::new("window_input_to_geo", UniformType::Matrix3x3),
UniformName::new("window_geo_size", UniformType::_2f),
UniformName::new("window_corner_radius", UniformType::_4f),
+ UniformName::new("window_corner_exponent", UniformType::_1f),
],
&[],
- )
- .map_err(|err| {
- warn!("error compiling shadow shader: {err:?}");
- })
- .ok();
+ );
- let clipped_surface = renderer
- .compile_custom_texture_shader(
- concat!(
- include_str!("clipped_surface.frag"),
- include_str!("rounding_alpha.frag"),
- "\nvec4 postprocess(vec4 color) { return color; }",
- ),
- &[
- UniformName::new("niri_scale", UniformType::_1f),
- UniformName::new("geo_size", UniformType::_2f),
- UniformName::new("corner_radius", UniformType::_4f),
- UniformName::new("input_to_geo", UniformType::Matrix3x3),
- ],
- )
- .map_err(|err| {
- warn!("error compiling clipped surface shader: {err:?}");
- })
- .ok();
+ let clipped_surface = try_compile_custom_texture_shader(
+ "clipped surface",
+ renderer,
+ concat!(
+ include_str!("clipped_surface.frag"),
+ include_str!("rounding_alpha.frag"),
+ "\nvec4 postprocess(vec4 color) { return color; }",
+ ),
+ &[
+ UniformName::new("niri_scale", UniformType::_1f),
+ UniformName::new("geo_size", UniformType::_2f),
+ UniformName::new("corner_radius", UniformType::_4f),
+ UniformName::new("corner_exponent", UniformType::_1f),
+ UniformName::new("input_to_geo", UniformType::Matrix3x3),
+ ],
+ );
- let postprocess_and_clip = renderer
- .compile_custom_texture_shader(
- concat!(
- include_str!("clipped_surface.frag"),
- include_str!("rounding_alpha.frag"),
- include_str!("postprocess.frag"),
- ),
- &[
- UniformName::new("niri_scale", UniformType::_1f),
- UniformName::new("geo_size", UniformType::_2f),
- UniformName::new("corner_radius", UniformType::_4f),
- UniformName::new("input_to_geo", UniformType::Matrix3x3),
- UniformName::new("noise", UniformType::_1f),
- UniformName::new("saturation", UniformType::_1f),
- UniformName::new("bg_color", UniformType::_4f),
- ],
- )
- .map_err(|err| {
- warn!("error compiling postprocess_and_clip shader: {err:?}");
- })
- .ok();
+ let postprocess_and_clip = try_compile_custom_texture_shader(
+ "postprocess_and_clip",
+ renderer,
+ concat!(
+ include_str!("clipped_surface.frag"),
+ include_str!("rounding_alpha.frag"),
+ include_str!("postprocess.frag"),
+ ),
+ &[
+ UniformName::new("niri_scale", UniformType::_1f),
+ UniformName::new("geo_size", UniformType::_2f),
+ UniformName::new("corner_radius", UniformType::_4f),
+ UniformName::new("corner_exponent", UniformType::_1f),
+ UniformName::new("input_to_geo", UniformType::Matrix3x3),
+ UniformName::new("noise", UniformType::_1f),
+ UniformName::new("saturation", UniformType::_1f),
+ UniformName::new("bg_color", UniformType::_4f),
+ ],
+ );
let resize = compile_resize_program(renderer, include_str!("resize.frag"))
.map_err(|err| {
@@ -223,28 +307,53 @@ fn compile_resize_program(
renderer: &mut GlesRenderer,
src: &str,
) -> Result {
+ let additional_uniforms = &[
+ UniformName::new("niri_input_to_curr_geo", UniformType::Matrix3x3),
+ UniformName::new("niri_curr_geo_to_prev_geo", UniformType::Matrix3x3),
+ UniformName::new("niri_curr_geo_to_next_geo", UniformType::Matrix3x3),
+ UniformName::new("niri_curr_geo_size", UniformType::_2f),
+ UniformName::new("niri_geo_to_tex_prev", UniformType::Matrix3x3),
+ UniformName::new("niri_geo_to_tex_next", UniformType::Matrix3x3),
+ UniformName::new("niri_progress", UniformType::_1f),
+ UniformName::new("niri_clamped_progress", UniformType::_1f),
+ UniformName::new("niri_corner_radius", UniformType::_4f),
+ UniformName::new("niri_corner_exponent", UniformType::_1f),
+ UniformName::new("niri_clip_to_geometry", UniformType::_1f),
+ ];
+ let texture_uniforms = &["niri_tex_prev", "niri_tex_next"];
+
let mut program = include_str!("resize_prelude.frag").to_string();
program.push_str(src);
program.push_str(include_str!("resize_epilogue.frag"));
- program.push_str(include_str!("rounding_alpha.frag"));
- ShaderProgram::compile(
+ let replaced_program = program.clone()
+ + "\n".into()
+ + &include_str!("rounding_alpha.frag")
+ .replace(ROUNDING_ALPHA_IMPL_PLACEHOLDER, ROUNDING_ALPHA_IMPL);
+
+ match ShaderProgram::compile(
renderer,
- &program,
- &[
- UniformName::new("niri_input_to_curr_geo", UniformType::Matrix3x3),
- UniformName::new("niri_curr_geo_to_prev_geo", UniformType::Matrix3x3),
- UniformName::new("niri_curr_geo_to_next_geo", UniformType::Matrix3x3),
- UniformName::new("niri_curr_geo_size", UniformType::_2f),
- UniformName::new("niri_geo_to_tex_prev", UniformType::Matrix3x3),
- UniformName::new("niri_geo_to_tex_next", UniformType::Matrix3x3),
- UniformName::new("niri_progress", UniformType::_1f),
- UniformName::new("niri_clamped_progress", UniformType::_1f),
- UniformName::new("niri_corner_radius", UniformType::_4f),
- UniformName::new("niri_clip_to_geometry", UniformType::_1f),
- ],
- &["niri_tex_prev", "niri_tex_next"],
- )
+ &replaced_program,
+ additional_uniforms,
+ texture_uniforms,
+ ) {
+ Ok(p) => Ok(p),
+ Err(err) => {
+ warn!("error compiling resize shader with support for corner radius exponent: {err:?}");
+ let fallback_program = program
+ + "\n".into()
+ + &include_str!("rounding_alpha.frag").replace(
+ ROUNDING_ALPHA_IMPL_PLACEHOLDER,
+ ROUNDING_ALPHA_IMPL_FALLBACK,
+ );
+ ShaderProgram::compile(
+ renderer,
+ &fallback_program,
+ additional_uniforms,
+ texture_uniforms,
+ )
+ }
+ }
}
pub fn set_custom_resize_program(renderer: &mut GlesRenderer, src: Option<&str>) {
diff --git a/src/render_helpers/shaders/resize_epilogue.frag b/src/render_helpers/shaders/resize_epilogue.frag
index 82c20d67d6..b97b7cac36 100644
--- a/src/render_helpers/shaders/resize_epilogue.frag
+++ b/src/render_helpers/shaders/resize_epilogue.frag
@@ -12,7 +12,7 @@ void main() {
color = vec4(0.0);
} else {
// Apply corner rounding inside geometry.
- color = color * niri_rounding_alpha(coords_curr_geo.xy * size_curr_geo.xy, size_curr_geo.xy, niri_corner_radius);
+ color = color * niri_rounding_alpha(coords_curr_geo.xy * size_curr_geo.xy, size_curr_geo.xy, niri_corner_radius, niri_corner_exponent);
}
}
diff --git a/src/render_helpers/shaders/resize_prelude.frag b/src/render_helpers/shaders/resize_prelude.frag
index 6e672f423f..84247edd35 100644
--- a/src/render_helpers/shaders/resize_prelude.frag
+++ b/src/render_helpers/shaders/resize_prelude.frag
@@ -22,9 +22,10 @@ uniform float niri_progress;
uniform float niri_clamped_progress;
uniform vec4 niri_corner_radius;
+uniform float niri_corner_exponent;
uniform float niri_clip_to_geometry;
uniform float niri_alpha;
uniform float niri_scale;
-float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius);
+float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius, float corner_exponent);
diff --git a/src/render_helpers/shaders/rounding_alpha.frag b/src/render_helpers/shaders/rounding_alpha.frag
index e1c9527e2e..563f08b201 100644
--- a/src/render_helpers/shaders/rounding_alpha.frag
+++ b/src/render_helpers/shaders/rounding_alpha.frag
@@ -1,4 +1,6 @@
-float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
+@ROUNDING_ALPHA_IMPL@
+
+float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius, float corner_exponent) {
vec2 center;
float radius;
@@ -18,10 +20,5 @@ float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
return 1.0;
}
- float dist = distance(coords, center);
-
- // Manual smoothstep() between radius - half_px and radius + half_px
- // to avoid a division in clamp().
- float t = clamp((dist - radius) * niri_scale + 0.5, 0.0, 1.0);
- return 1.0 - t * t * (3.0 - 2.0 * t);
+ return niri_rounding_alpha_impl(coords, center, radius, corner_exponent);
}
diff --git a/src/render_helpers/shaders/rounding_alpha_superellipse.frag b/src/render_helpers/shaders/rounding_alpha_superellipse.frag
new file mode 100644
index 0000000000..d2a4038d85
--- /dev/null
+++ b/src/render_helpers/shaders/rounding_alpha_superellipse.frag
@@ -0,0 +1,22 @@
+float niri_superellipse_dist(vec2 coords, vec2 center, float radius, float exponent) {
+ vec2 delta = abs(coords - center);
+
+ if (abs(exponent - 2.0) < 0.001) {
+ return length(delta) - radius;
+ }
+
+ if (abs(exponent - 1.0) < 0.001) {
+ return delta.x + delta.y - radius;
+ }
+
+ vec2 normalized = delta / max(radius, 0.0001);
+ float lp_norm = pow(pow(normalized.x, exponent) + pow(normalized.y, exponent), 1.0 / exponent);
+ return (lp_norm - 1.0) * radius;
+}
+
+float niri_rounding_alpha_impl(vec2 coords, vec2 center, float radius, float corner_exponent) {
+ float exponent = max(corner_exponent, 0.01);
+ float dist = niri_superellipse_dist(coords, center, radius, exponent);
+ float half_px = 0.5 / niri_scale;
+ return 1.0 - smoothstep(-half_px, half_px, dist);
+}
diff --git a/src/render_helpers/shaders/shadow.frag b/src/render_helpers/shaders/shadow.frag
index 98d5fd60f6..0f35cdd412 100644
--- a/src/render_helpers/shaders/shadow.frag
+++ b/src/render_helpers/shaders/shadow.frag
@@ -16,10 +16,12 @@ uniform float sigma;
uniform mat3 input_to_geo;
uniform vec2 geo_size;
uniform vec4 corner_radius;
+uniform float corner_exponent;
uniform mat3 window_input_to_geo;
uniform vec2 window_geo_size;
uniform vec4 window_corner_radius;
+uniform float window_corner_exponent;
// Based on: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/
//
@@ -40,15 +42,20 @@ vec2 erf(vec2 x) {
}
// Return the blurred mask along the x dimension
-float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize) {
+float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize, float exponent) {
float delta = min(halfSize.y - corner - abs(y), 0.0);
- float curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
+ float curved;
+ if (abs(exponent - 2.0) < 0.001) {
+ curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
+ } else {
+ @EXPONENT_SHADOW_X_IMPL@
+ }
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) {
+float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float corner, float exponent) {
// Center everything to make the math easier
vec2 center = (lower + upper) * 0.5;
vec2 halfSize = (upper - lower) * 0.5;
@@ -65,14 +72,14 @@ float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float co
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;
+ value += roundedBoxShadowX(point.x, point.y - y, sigma, corner, halfSize, exponent) * gaussian(y, sigma) * step;
y += step;
}
return value;
}
-float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius);
+float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius, float corner_exponent);
void main() {
vec3 coords_geo = input_to_geo * vec3(niri_v_coords, 1.0);
@@ -83,7 +90,7 @@ void main() {
float shadow_value;
if (sigma < 0.1) {
// With low enough sigma just draw a rounded rectangle.
- shadow_value = niri_rounding_alpha(coords_geo.xy, geo_size, corner_radius);
+ shadow_value = niri_rounding_alpha(coords_geo.xy, geo_size, corner_radius, corner_exponent);
} else {
shadow_value = roundedBoxShadow(
vec2(0.0, 0.0),
@@ -94,7 +101,8 @@ void main() {
//
// 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
+ corner_radius.x,
+ corner_exponent
);
}
color = color * shadow_value;
@@ -103,7 +111,7 @@ void main() {
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 = niri_rounding_alpha(coords_window_geo.xy, window_geo_size, window_corner_radius);
+ float alpha = niri_rounding_alpha(coords_window_geo.xy, window_geo_size, window_corner_radius, window_corner_exponent);
color = color * (1.0 - alpha);
}
}
diff --git a/src/render_helpers/shadow.rs b/src/render_helpers/shadow.rs
index 0d26201c81..619dfb8073 100644
--- a/src/render_helpers/shadow.rs
+++ b/src/render_helpers/shadow.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::rc::Rc;
use glam::{Mat3, Vec2};
-use niri_config::{Color, CornerRadius};
+use niri_config::{Color, CornerRadius, DEFAULT_CORNER_RADIUS_EXPONENT};
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};
@@ -30,12 +30,14 @@ struct Parameters {
color: Color,
sigma: f32,
corner_radius: CornerRadius,
+ corner_exponent: f32,
// Should only be used for visual improvements, i.e. corner radius anti-aliasing.
scale: f32,
alpha: f32,
window_geometry: Rectangle,
window_corner_radius: CornerRadius,
+ window_corner_exponent: f32,
}
impl ShadowRenderElement {
@@ -46,9 +48,11 @@ impl ShadowRenderElement {
color: Color,
sigma: f32,
corner_radius: CornerRadius,
+ corner_exponent: f32,
scale: f32,
window_geometry: Rectangle,
window_corner_radius: CornerRadius,
+ window_corner_exponent: f32,
alpha: f32,
) -> Self {
let inner = ShaderRenderElement::empty(ProgramType::Shadow, Kind::Unspecified);
@@ -60,10 +64,12 @@ impl ShadowRenderElement {
color,
sigma,
corner_radius,
+ corner_exponent,
scale,
alpha,
window_geometry,
window_corner_radius,
+ window_corner_exponent,
},
};
rv.update_inner();
@@ -80,10 +86,12 @@ impl ShadowRenderElement {
color: Default::default(),
sigma: 0.,
corner_radius: Default::default(),
+ corner_exponent: DEFAULT_CORNER_RADIUS_EXPONENT,
scale: 1.,
alpha: 1.,
window_geometry: Default::default(),
window_corner_radius: Default::default(),
+ window_corner_exponent: DEFAULT_CORNER_RADIUS_EXPONENT,
},
}
}
@@ -100,9 +108,11 @@ impl ShadowRenderElement {
color: Color,
sigma: f32,
corner_radius: CornerRadius,
+ corner_exponent: f32,
scale: f32,
window_geometry: Rectangle,
window_corner_radius: CornerRadius,
+ window_corner_exponent: f32,
alpha: f32,
) {
let params = Parameters {
@@ -112,9 +122,11 @@ impl ShadowRenderElement {
sigma,
alpha,
corner_radius,
+ corner_exponent,
scale,
window_geometry,
window_corner_radius,
+ window_corner_exponent,
};
if self.params == params {
return;
@@ -132,9 +144,11 @@ impl ShadowRenderElement {
sigma,
alpha,
corner_radius,
+ corner_exponent,
scale,
window_geometry,
window_corner_radius,
+ window_corner_exponent,
} = self.params;
let area_size = Vec2::new(size.w as f32, size.h as f32);
@@ -163,12 +177,14 @@ impl ShadowRenderElement {
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)),
+ Uniform::new("corner_exponent", corner_exponent),
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),
),
+ Uniform::new("window_corner_exponent", window_corner_exponent),
]),
HashMap::new(),
);
diff --git a/src/render_helpers/xray.rs b/src/render_helpers/xray.rs
index f4b4f861e0..7c1bbce9c7 100644
--- a/src/render_helpers/xray.rs
+++ b/src/render_helpers/xray.rs
@@ -3,7 +3,7 @@ use std::cell::RefCell;
use std::rc::Rc;
use glam::{Mat3, Vec2};
-use niri_config::CornerRadius;
+use niri_config::{CornerRadius, DEFAULT_CORNER_RADIUS_EXPONENT};
use smithay::backend::renderer::element::{Element, Id, RenderElement};
use smithay::backend::renderer::gles::{
GlesError, GlesFrame, GlesRenderer, GlesTexProgram, Uniform,
@@ -75,6 +75,7 @@ pub struct XrayElement {
input_to_clip_geo: Mat3,
clip_geo_size: Vec2,
corner_radius: CornerRadius,
+ corner_exponent: f32,
scale: f32,
blur: bool,
noise: f32,
@@ -109,9 +110,11 @@ impl Xray {
let zoom = xray_pos.zoom;
let pos_in_backdrop = xray_pos.pos_in_backdrop.upscale(zoom);
- let (clip_geo, corner_radius) = params
- .clip
- .unwrap_or((params.geometry, CornerRadius::default()));
+ let (clip_geo, corner_radius, corner_exponent) = params.clip.unwrap_or((
+ params.geometry,
+ CornerRadius::default(),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
+ ));
let clip_offset = clip_geo.loc - params.geometry.loc;
let clip_pos_in_backdrop = pos_in_backdrop + clip_offset.upscale(zoom);
@@ -196,6 +199,7 @@ impl Xray {
input_to_clip_geo,
clip_geo_size,
corner_radius,
+ corner_exponent,
scale: params.scale as f32,
blur,
noise,
@@ -246,6 +250,7 @@ impl Xray {
input_to_clip_geo,
clip_geo_size,
corner_radius: corner_radius.scaled_by(zoom as f32),
+ corner_exponent,
scale: params.scale as f32,
blur,
noise,
@@ -259,11 +264,12 @@ impl Xray {
}
impl XrayElement {
- fn compute_uniforms(&self) -> [Uniform<'static>; 7] {
+ fn compute_uniforms(&self) -> [Uniform<'static>; 8] {
[
Uniform::new("niri_scale", self.scale),
Uniform::new("geo_size", <[f32; 2]>::from(self.clip_geo_size)),
Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
+ Uniform::new("corner_exponent", self.corner_exponent),
mat3_uniform("input_to_geo", self.input_to_clip_geo),
Uniform::new("noise", self.noise),
Uniform::new("saturation", self.saturation),
diff --git a/src/ui/mru.rs b/src/ui/mru.rs
index d8ec6de16e..a5c8b049d2 100644
--- a/src/ui/mru.rs
+++ b/src/ui/mru.rs
@@ -8,7 +8,7 @@ use std::time::Duration;
use anyhow::ensure;
use niri_config::{
Action, Bind, Color, Config, CornerRadius, GradientInterpolation, Key, Modifiers, MruDirection,
- MruFilter, MruScope, Trigger,
+ MruFilter, MruScope, Trigger, DEFAULT_CORNER_RADIUS_EXPONENT,
};
use pango::FontDescription;
use pangocairo::cairo::{self, ImageSurface};
@@ -383,8 +383,17 @@ impl Thumbnail {
LayoutElementRenderElement::Wayland(elem) => {
if let Some(shader) = clip_shader.clone() {
if ClippedSurfaceRenderElement::will_clip(&elem, s, geo, radius) {
- let elem =
- ClippedSurfaceRenderElement::new(elem, s, geo, shader.clone(), radius);
+ let elem = ClippedSurfaceRenderElement::new(
+ elem,
+ s,
+ geo,
+ shader.clone(),
+ radius,
+ mapped
+ .rules()
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
+ );
return ThumbnailRenderElement::ClippedSurface(elem);
}
}
@@ -411,6 +420,10 @@ impl Thumbnail {
Rectangle::from_size(geo.size),
0.,
radius,
+ mapped
+ .rules()
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
scale as f32,
1.,
)
@@ -536,6 +549,7 @@ impl Thumbnail {
false,
Rectangle::default(),
radius,
+ DEFAULT_CORNER_RADIUS_EXPONENT,
scale,
0.5,
);
@@ -557,6 +571,7 @@ impl Thumbnail {
false,
Rectangle::default(),
radius.expanded_by(config.width as f32),
+ DEFAULT_CORNER_RADIUS_EXPONENT,
scale,
1.,
);
diff --git a/src/window/mapped.rs b/src/window/mapped.rs
index 3db1f66cad..291015f15b 100644
--- a/src/window/mapped.rs
+++ b/src/window/mapped.rs
@@ -1,7 +1,9 @@
use std::cell::{Cell, Ref, RefCell};
use std::time::Duration;
-use niri_config::{Color, Config, CornerRadius, GradientInterpolation, WindowRule};
+use niri_config::{
+ Color, Config, CornerRadius, GradientInterpolation, WindowRule, DEFAULT_CORNER_RADIUS_EXPONENT,
+};
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::element::Kind;
use smithay::backend::renderer::gles::GlesRenderer;
@@ -532,6 +534,9 @@ impl Mapped {
Rectangle::from_size(geo.size),
0.,
radius,
+ self.rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
scale.x as f32,
1.,
)
@@ -729,6 +734,9 @@ impl LayoutElement for Mapped {
surface_anim_scale,
self.blur_config,
popup_rules.geometry_corner_radius.unwrap_or_default(),
+ popup_rules
+ .geometry_corner_radius_exponent
+ .unwrap_or(DEFAULT_CORNER_RADIUS_EXPONENT),
effect,
false,
xray_pos,
@@ -745,6 +753,7 @@ impl LayoutElement for Mapped {
clip_to_geometry: bool,
surface_anim_scale: Scale,
radius: CornerRadius,
+ exponent: f32,
xray_pos: XrayPos,
push: &mut dyn FnMut(BackgroundEffectElement),
) {
@@ -760,6 +769,7 @@ impl LayoutElement for Mapped {
surface_anim_scale,
self.blur_config,
radius,
+ exponent,
self.rules.background_effect,
should_block_out,
xray_pos,
diff --git a/src/window/mod.rs b/src/window/mod.rs
index 14527cd242..5d6d50abd7 100644
--- a/src/window/mod.rs
+++ b/src/window/mod.rs
@@ -101,6 +101,8 @@ pub struct ResolvedWindowRules {
/// Corner radius to assume this window has.
pub geometry_corner_radius: Option,
+ /// Exponent to use for corner rounding.
+ pub geometry_corner_radius_exponent: Option,
/// Whether to clip this window to its geometry, including the corner radius.
pub clip_to_geometry: Option,
@@ -284,6 +286,9 @@ impl ResolvedWindowRules {
if let Some(x) = rule.geometry_corner_radius {
resolved.geometry_corner_radius = Some(x);
}
+ if let Some(x) = rule.geometry_corner_radius_exponent {
+ resolved.geometry_corner_radius_exponent = Some(x.0 as f32);
+ }
if let Some(x) = rule.clip_to_geometry {
resolved.clip_to_geometry = Some(x);
}