aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-05-01 19:06:08 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-05-02 14:27:53 +0400
commit42cef79c699c0f03b4bb99c4278169c48d9a5bd0 (patch)
tree630d18b49f3d93603a79bcfc5734dc773c6d0cb6
parentd86df5025cfd26ef4a3c48acd8ee80555265ee53 (diff)
downloadniri-42cef79c699c0f03b4bb99c4278169c48d9a5bd0.tar.gz
niri-42cef79c699c0f03b4bb99c4278169c48d9a5bd0.tar.bz2
niri-42cef79c699c0f03b4bb99c4278169c48d9a5bd0.zip
Implement rounded window corners
-rw-r--r--Cargo.lock4
-rw-r--r--niri-config/src/lib.rs189
-rw-r--r--niri-visual-tests/src/cases/gradient_angle.rs33
-rw-r--r--niri-visual-tests/src/cases/gradient_area.rs34
-rw-r--r--src/layout/focus_ring.rs154
-rw-r--r--src/layout/tile.rs132
-rw-r--r--src/render_helpers/border.rs (renamed from src/render_helpers/gradient.rs)63
-rw-r--r--src/render_helpers/clipped_surface.rs269
-rw-r--r--src/render_helpers/mod.rs6
-rw-r--r--src/render_helpers/primary_gpu_pixel_shader.rs97
-rw-r--r--src/render_helpers/resize.rs21
-rw-r--r--src/render_helpers/shader_element.rs (renamed from src/render_helpers/primary_gpu_pixel_shader_with_textures.rs)118
-rw-r--r--src/render_helpers/shaders/border.frag87
-rw-r--r--src/render_helpers/shaders/clipped_surface.frag77
-rw-r--r--src/render_helpers/shaders/gradient_border.frag35
-rw-r--r--src/render_helpers/shaders/mod.rs67
-rw-r--r--src/render_helpers/shaders/resize-epilogue.frag9
-rw-r--r--src/render_helpers/shaders/resize-prelude.frag22
-rw-r--r--src/render_helpers/shaders/resize_epilogue.frag27
-rw-r--r--src/render_helpers/shaders/resize_prelude.frag51
-rw-r--r--src/window/mod.rs16
-rw-r--r--wiki/Configuration:-Window-Rules.md74
-rw-r--r--wiki/examples/resize_custom_shader.frag (renamed from wiki/examples/resize-custom-shader.frag)0
-rw-r--r--wiki/img/border-radius-clip.png3
-rw-r--r--wiki/img/clip-to-geometry.png3
-rw-r--r--wiki/img/different-corner-radius.png3
-rw-r--r--wiki/img/geometry-corner-radius.png3
27 files changed, 1288 insertions, 309 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a211f438..4e0d3015 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3170,7 +3170,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "smithay"
version = "0.3.0"
-source = "git+https://github.com/Smithay/smithay.git#7544c4d4b9ef89e2945bb626dd47518fedc5f660"
+source = "git+https://github.com/Smithay/smithay.git#219b783609dab41114b8202e8be8e46523c7f4b5"
dependencies = [
"appendlist",
"bitflags 2.5.0",
@@ -3242,7 +3242,7 @@ dependencies = [
[[package]]
name = "smithay-drm-extras"
version = "0.1.0"
-source = "git+https://github.com/Smithay/smithay.git#7544c4d4b9ef89e2945bb626dd47518fedc5f660"
+source = "git+https://github.com/Smithay/smithay.git#219b783609dab41114b8202e8be8e46523c7f4b5"
dependencies = [
"drm",
"edid-rs",
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index ca789528..f4728e61 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -722,6 +722,10 @@ pub struct WindowRule {
pub draw_border_with_background: Option<bool>,
#[knuffel(child, unwrap(argument))]
pub opacity: Option<f32>,
+ #[knuffel(child)]
+ pub geometry_corner_radius: Option<CornerRadius>,
+ #[knuffel(child, unwrap(argument))]
+ pub clip_to_geometry: Option<bool>,
#[knuffel(child, unwrap(argument))]
pub block_out_from: Option<BlockOutFrom>,
}
@@ -748,6 +752,25 @@ impl PartialEq for Match {
}
}
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
+pub struct CornerRadius {
+ pub top_left: f32,
+ pub top_right: f32,
+ pub bottom_right: f32,
+ pub bottom_left: f32,
+}
+
+impl From<CornerRadius> for [f32; 4] {
+ fn from(value: CornerRadius) -> Self {
+ [
+ value.top_left,
+ value.top_right,
+ value.bottom_right,
+ value.bottom_left,
+ ]
+ }
+}
+
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockOutFrom {
Screencast,
@@ -1079,6 +1102,53 @@ impl BorderRule {
}
}
+impl CornerRadius {
+ pub fn fit_to(self, width: f32, height: f32) -> Self {
+ // Like in CSS: https://drafts.csswg.org/css-backgrounds/#corner-overlap
+ let reduction = f32::min(
+ f32::min(
+ width / (self.top_left + self.top_right),
+ width / (self.bottom_left + self.bottom_right),
+ ),
+ f32::min(
+ height / (self.top_left + self.bottom_left),
+ height / (self.top_right + self.bottom_right),
+ ),
+ );
+ let reduction = f32::min(1., reduction);
+
+ Self {
+ top_left: self.top_left * reduction,
+ top_right: self.top_right * reduction,
+ bottom_right: self.bottom_right * reduction,
+ bottom_left: self.bottom_left * reduction,
+ }
+ }
+
+ pub fn expanded_by(self, width: f32) -> Self {
+ // Preserve zero radius.
+ if self == Self::default() {
+ return self;
+ }
+
+ Self {
+ top_left: self.top_left + width,
+ top_right: self.top_right + width,
+ bottom_right: self.bottom_right + width,
+ bottom_left: self.bottom_left + width,
+ }
+ }
+
+ pub fn scaled_by(self, scale: f32) -> Self {
+ Self {
+ top_left: self.top_left * scale,
+ top_right: self.top_right * scale,
+ bottom_right: self.bottom_right * scale,
+ bottom_left: self.bottom_left * scale,
+ }
+ }
+}
+
impl FromStr for Color {
type Err = miette::Error;
@@ -1606,6 +1676,125 @@ where
}
}
+impl<S> knuffel::Decode<S> for CornerRadius
+where
+ S: knuffel::traits::ErrorSpan,
+{
+ fn decode_node(
+ node: &knuffel::ast::SpannedNode<S>,
+ ctx: &mut knuffel::decode::Context<S>,
+ ) -> Result<Self, DecodeError<S>> {
+ // Check for unexpected type name.
+ if let Some(type_name) = &node.type_name {
+ ctx.emit_error(DecodeError::unexpected(
+ type_name,
+ "type name",
+ "no type name expected for this node",
+ ));
+ }
+
+ let decode_radius = |ctx: &mut knuffel::decode::Context<S>,
+ val: &knuffel::ast::Value<S>| {
+ // Check for unexpected type name.
+ if let Some(typ) = &val.type_name {
+ ctx.emit_error(DecodeError::TypeName {
+ span: typ.span().clone(),
+ found: Some((**typ).clone()),
+ expected: knuffel::errors::ExpectedType::no_type(),
+ rust_type: "str",
+ });
+ }
+
+ // Decode both integers and floats.
+ let radius = match *val.literal {
+ knuffel::ast::Literal::Int(ref x) => f32::from(match x.try_into() {
+ Ok(x) => x,
+ Err(err) => {
+ ctx.emit_error(DecodeError::conversion(&val.literal, err));
+ 0i16
+ }
+ }),
+ knuffel::ast::Literal::Decimal(ref x) => match x.try_into() {
+ Ok(x) => x,
+ Err(err) => {
+ ctx.emit_error(DecodeError::conversion(&val.literal, err));
+ 0.
+ }
+ },
+ _ => {
+ ctx.emit_error(DecodeError::scalar_kind(
+ knuffel::decode::Kind::Int,
+ &val.literal,
+ ));
+ 0.
+ }
+ };
+
+ if radius < 0. {
+ ctx.emit_error(DecodeError::conversion(&val.literal, "radius must be >= 0"));
+ }
+
+ radius
+ };
+
+ // Get the first argument.
+ let mut iter_args = node.arguments.iter();
+ let val = iter_args
+ .next()
+ .ok_or_else(|| DecodeError::missing(node, "additional argument is required"))?;
+
+ let top_left = decode_radius(ctx, val);
+
+ let mut rv = CornerRadius {
+ top_left,
+ top_right: top_left,
+ bottom_right: top_left,
+ bottom_left: top_left,
+ };
+
+ if let Some(val) = iter_args.next() {
+ rv.top_right = decode_radius(ctx, val);
+
+ let val = iter_args.next().ok_or_else(|| {
+ DecodeError::missing(node, "either 1 or 4 arguments are required")
+ })?;
+ rv.bottom_right = decode_radius(ctx, val);
+
+ let val = iter_args.next().ok_or_else(|| {
+ DecodeError::missing(node, "either 1 or 4 arguments are required")
+ })?;
+ rv.bottom_left = decode_radius(ctx, val);
+
+ // Check for unexpected following arguments.
+ if let Some(val) = iter_args.next() {
+ ctx.emit_error(DecodeError::unexpected(
+ &val.literal,
+ "argument",
+ "unexpected argument",
+ ));
+ }
+ }
+
+ // Check for unexpected properties and children.
+ for name in node.properties.keys() {
+ ctx.emit_error(DecodeError::unexpected(
+ name,
+ "property",
+ format!("unexpected property `{}`", name.escape_default()),
+ ));
+ }
+ for child in node.children.as_ref().map(|lst| &lst[..]).unwrap_or(&[]) {
+ ctx.emit_error(DecodeError::unexpected(
+ child,
+ "node",
+ format!("unexpected node `{}`", child.node_name.escape_default()),
+ ));
+ }
+
+ Ok(rv)
+ }
+}
+
impl<S> knuffel::Decode<S> for Binds
where
S: knuffel::traits::ErrorSpan,
diff --git a/niri-visual-tests/src/cases/gradient_angle.rs b/niri-visual-tests/src/cases/gradient_angle.rs
index adbedff4..aada9862 100644
--- a/niri-visual-tests/src/cases/gradient_angle.rs
+++ b/niri-visual-tests/src/cases/gradient_angle.rs
@@ -3,7 +3,8 @@ use std::sync::atomic::Ordering;
use std::time::Duration;
use niri::animation::ANIMATION_SLOWDOWN;
-use niri::render_helpers::gradient::GradientRenderElement;
+use niri::render_helpers::border::BorderRenderElement;
+use niri_config::CornerRadius;
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Scale, Size};
@@ -60,17 +61,23 @@ impl TestCase for GradientAngle {
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size);
- GradientRenderElement::new(
- renderer,
- Scale::from(1.),
- area,
- area,
- [1., 0., 0., 1.],
- [0., 1., 0., 1.],
- self.angle - FRAC_PI_2,
- )
- .into_iter()
- .map(|elem| Box::new(elem) as _)
- .collect()
+ BorderRenderElement::shader(renderer)
+ .map(|shader| {
+ BorderRenderElement::new(
+ shader.clone(),
+ Scale::from(1.),
+ area,
+ area,
+ [1., 0., 0., 1.],
+ [0., 1., 0., 1.],
+ self.angle - FRAC_PI_2,
+ area,
+ 0.,
+ CornerRadius::default(),
+ )
+ })
+ .into_iter()
+ .map(|elem| Box::new(elem) as _)
+ .collect()
}
}
diff --git a/niri-visual-tests/src/cases/gradient_area.rs b/niri-visual-tests/src/cases/gradient_area.rs
index d47fb2ce..22aac0f0 100644
--- a/niri-visual-tests/src/cases/gradient_area.rs
+++ b/niri-visual-tests/src/cases/gradient_area.rs
@@ -4,8 +4,8 @@ use std::time::Duration;
use niri::animation::ANIMATION_SLOWDOWN;
use niri::layout::focus_ring::FocusRing;
-use niri::render_helpers::gradient::GradientRenderElement;
-use niri_config::Color;
+use niri::render_helpers::border::BorderRenderElement;
+use niri_config::{Color, CornerRadius};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size};
@@ -85,7 +85,7 @@ impl TestCase for GradientArea {
let g_loc = ((size.w - g_size.w) / 2, (size.h - g_size.h) / 2);
let g_area = Rectangle::from_loc_and_size(g_loc, g_size);
- self.border.update(g_size, true);
+ self.border.update(g_size, true, CornerRadius::default());
rv.extend(
self.border
.render(
@@ -98,17 +98,23 @@ impl TestCase for GradientArea {
);
rv.extend(
- GradientRenderElement::new(
- renderer,
- Scale::from(1.),
- area,
- g_area,
- [1., 0., 0., 1.],
- [0., 1., 0., 1.],
- FRAC_PI_4,
- )
- .into_iter()
- .map(|elem| Box::new(elem) as _),
+ BorderRenderElement::shader(renderer)
+ .map(|shader| {
+ BorderRenderElement::new(
+ shader.clone(),
+ Scale::from(1.),
+ area,
+ g_area,
+ [1., 0., 0., 1.],
+ [0., 1., 0., 1.],
+ FRAC_PI_4,
+ area,
+ 0.,
+ CornerRadius::default(),
+ )
+ })
+ .into_iter()
+ .map(|elem| Box::new(elem) as _),
);
rv
diff --git a/src/layout/focus_ring.rs b/src/layout/focus_ring.rs
index f8a70d93..d52bb08e 100644
--- a/src/layout/focus_ring.rs
+++ b/src/layout/focus_ring.rs
@@ -1,30 +1,32 @@
+use std::cmp::{max, min};
use std::iter::zip;
use arrayvec::ArrayVec;
-use niri_config::GradientRelativeTo;
+use niri_config::{CornerRadius, GradientRelativeTo};
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
use smithay::backend::renderer::element::Kind;
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
use crate::niri_render_elements;
-use crate::render_helpers::gradient::GradientRenderElement;
+use crate::render_helpers::border::BorderRenderElement;
use crate::render_helpers::renderer::NiriRenderer;
#[derive(Debug)]
pub struct FocusRing {
- buffers: [SolidColorBuffer; 4],
- locations: [Point<i32, Logical>; 4],
- sizes: [Size<i32, Logical>; 4],
+ buffers: [SolidColorBuffer; 8],
+ locations: [Point<i32, Logical>; 8],
+ sizes: [Size<i32, Logical>; 8],
full_size: Size<i32, Logical>,
is_active: bool,
is_border: bool,
+ radius: CornerRadius,
config: niri_config::FocusRing,
}
niri_render_elements! {
FocusRingRenderElement => {
SolidColor = SolidColorRenderElement,
- Gradient = GradientRenderElement,
+ Gradient = BorderRenderElement,
}
}
@@ -37,6 +39,7 @@ impl FocusRing {
full_size: Default::default(),
is_active: false,
is_border: false,
+ radius: Default::default(),
config,
}
}
@@ -45,24 +48,69 @@ impl FocusRing {
self.config = config;
}
- pub fn update(&mut self, win_size: Size<i32, Logical>, is_border: bool) {
+ pub fn update(&mut self, win_size: Size<i32, Logical>, is_border: bool, radius: CornerRadius) {
let width = i32::from(self.config.width);
self.full_size = win_size + Size::from((width * 2, width * 2));
+ let radius = radius.fit_to(self.full_size.w as f32, self.full_size.h as f32);
+
if is_border {
- self.sizes[0] = Size::from((win_size.w + width * 2, width));
- self.sizes[1] = Size::from((win_size.w + width * 2, width));
- self.sizes[2] = Size::from((width, win_size.h));
- self.sizes[3] = Size::from((width, win_size.h));
+ let top_left = max(width, radius.top_left.ceil() as i32);
+ let top_right = min(
+ self.full_size.w - top_left,
+ max(width, radius.top_right.ceil() as i32),
+ );
+ let bottom_left = min(
+ self.full_size.h - top_left,
+ max(width, radius.bottom_left.ceil() as i32),
+ );
+ let bottom_right = min(
+ self.full_size.h - top_right,
+ min(
+ self.full_size.w - bottom_left,
+ max(width, radius.bottom_right.ceil() as i32),
+ ),
+ );
+
+ // Top edge.
+ self.sizes[0] = Size::from((win_size.w + width * 2 - top_left - top_right, width));
+ self.locations[0] = Point::from((-width + top_left, -width));
+
+ // Bottom edge.
+ self.sizes[1] =
+ Size::from((win_size.w + width * 2 - bottom_left - bottom_right, width));
+ self.locations[1] = Point::from((-width + bottom_left, win_size.h));
+
+ // Left edge.
+ self.sizes[2] = Size::from((width, win_size.h + width * 2 - top_left - bottom_left));
+ self.locations[2] = Point::from((-width, -width + top_left));
+
+ // Right edge.
+ self.sizes[3] = Size::from((width, win_size.h + width * 2 - top_right - bottom_right));
+ self.locations[3] = Point::from((win_size.w, -width + top_right));
+
+ // Top-left corner.
+ self.sizes[4] = Size::from((top_left, top_left));
+ self.locations[4] = Point::from((-width, -width));
+
+ // Top-right corner.
+ self.sizes[5] = Size::from((top_right, top_right));
+ self.locations[5] = Point::from((win_size.w + width - top_right, -width));
+
+ // Bottom-right corner.
+ self.sizes[6] = Size::from((bottom_right, bottom_right));
+ self.locations[6] = Point::from((
+ win_size.w + width - bottom_right,
+ win_size.h + width - bottom_right,
+ ));
+
+ // Bottom-left corner.
+ self.sizes[7] = Size::from((bottom_left, bottom_left));
+ self.locations[7] = Point::from((-width, win_size.h + width - bottom_left));
for (buf, size) in zip(&mut self.buffers, self.sizes) {
buf.resize(size);
}
-
- self.locations[0] = Point::from((-width, -width));
- self.locations[1] = Point::from((-width, win_size.h));
- self.locations[2] = Point::from((-width, 0));
- self.locations[3] = Point::from((win_size.w, 0));
} else {
self.sizes[0] = self.full_size;
self.buffers[0].resize(self.sizes[0]);
@@ -70,6 +118,7 @@ impl FocusRing {
}
self.is_border = is_border;
+ self.radius = radius;
}
pub fn set_active(&mut self, is_active: bool) {
@@ -93,38 +142,83 @@ impl FocusRing {
scale: Scale<f64>,
view_size: Size<i32, Logical>,
) -> impl Iterator<Item = FocusRingRenderElement> {
- let mut rv = ArrayVec::<_, 4>::new();
+ let mut rv = ArrayVec::<_, 8>::new();
if self.config.off {
return rv.into_iter();
}
+ let border_width = -self.locations[0].y;
+
+ // If drawing as a border with width = 0, then there's nothing to draw.
+ if self.is_border && border_width == 0 {
+ return rv.into_iter();
+ }
+
let gradient = if self.is_active {
self.config.active_gradient
} else {
self.config.inactive_gradient
};
+ let color = if self.is_active {
+ self.config.active_color
+ } else {
+ self.config.inactive_color
+ };
- let full_rect = Rectangle::from_loc_and_size(location + self.locations[0], self.full_size);
+ let offset = Point::from((border_width, border_width));
+ let full_rect = Rectangle::from_loc_and_size(location - offset, self.full_size);
let view_rect = Rectangle::from_loc_and_size((0, 0), view_size);
+ let border_width = if self.is_border {
+ // HACK: increase the border width used for the inner rounded corners a tiny bit to
+ // reduce background bleed.
+ border_width as f32 + 0.5 / scale.x as f32
+ } else {
+ 0.
+ };
+ let shader = BorderRenderElement::shader(renderer);
+
let mut push = |buffer, location: Point<i32, Logical>, size: Size<i32, Logical>| {
- let elem = gradient.and_then(|gradient| {
+ let elem = if let Some(gradient) = gradient {
let gradient_area = match gradient.relative_to {
GradientRelativeTo::Window => full_rect,
GradientRelativeTo::WorkspaceView => view_rect,
};
- GradientRenderElement::new(
- renderer,
- scale,
- Rectangle::from_loc_and_size(location, size),
- gradient_area,
- gradient.from.into(),
- gradient.to.into(),
- ((gradient.angle as f32) - 90.).to_radians(),
- )
- .map(Into::into)
- });
+ shader.cloned().map(|shader| {
+ BorderRenderElement::new(
+ shader,
+ scale,
+ Rectangle::from_loc_and_size(location, size),
+ gradient_area,
+ gradient.from.into(),
+ gradient.to.into(),
+ ((gradient.angle as f32) - 90.).to_radians(),
+ full_rect,
+ border_width,
+ self.radius,
+ )
+ .into()
+ })
+ } else if self.radius != CornerRadius::default() {
+ shader.cloned().map(|shader| {
+ BorderRenderElement::new(
+ shader,
+ scale,
+ Rectangle::from_loc_and_size(location, size),
+ full_rect,
+ color.into(),
+ color.into(),
+ 0.,
+ full_rect,
+ border_width,
+ self.radius,
+ )
+ .into()
+ })
+ } else {
+ None
+ };
let elem = elem.unwrap_or_else(|| {
SolidColorRenderElement::from_buffer(
diff --git a/src/layout/tile.rs b/src/layout/tile.rs
index b5b1a674..f0e770f4 100644
--- a/src/layout/tile.rs
+++ b/src/layout/tile.rs
@@ -3,6 +3,7 @@ use std::cmp::max;
use std::rc::Rc;
use std::time::Duration;
+use niri_config::CornerRadius;
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
use smithay::backend::renderer::element::utils::RescaleRenderElement;
@@ -17,6 +18,8 @@ use super::{
};
use crate::animation::Animation;
use crate::niri_render_elements;
+use crate::render_helpers::border::BorderRenderElement;
+use crate::render_helpers::clipped_surface::ClippedSurfaceRenderElement;
use crate::render_helpers::offscreen::OffscreenRenderElement;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::resize::ResizeRenderElement;
@@ -76,6 +79,8 @@ niri_render_elements! {
SolidColor = SolidColorRenderElement,
Offscreen = RescaleRenderElement<OffscreenRenderElement>,
Resize = ResizeRenderElement,
+ Border = BorderRenderElement,
+ ClippedSurface = ClippedSurfaceRenderElement<R>,
}
}
@@ -205,13 +210,26 @@ impl<W: LayoutElement> Tile<W> {
}
}
- let draw_border_with_background = self
- .window
- .rules()
+ let rules = self.window.rules();
+
+ let draw_border_with_background = rules
.draw_border_with_background
.unwrap_or_else(|| !self.window.has_ssd());
- self.border
- .update(self.animated_window_size(), !draw_border_with_background);
+ let border_width = self.effective_border_width().unwrap_or(0);
+ let radius = if self.is_fullscreen {
+ CornerRadius::default()
+ } else {
+ rules
+ .geometry_corner_radius
+ .map_or(CornerRadius::default(), |radius| {
+ radius.expanded_by(border_width as f32)
+ })
+ };
+ self.border.update(
+ self.animated_window_size(),
+ !draw_border_with_background,
+ radius,
+ );
self.border.set_active(is_active);
let draw_focus_ring_with_background = if self.effective_border_width().is_some() {
@@ -219,8 +237,19 @@ impl<W: LayoutElement> Tile<W> {
} else {
draw_border_with_background
};
- self.focus_ring
- .update(self.animated_tile_size(), !draw_focus_ring_with_background);
+ let radius = if self.is_fullscreen {
+ CornerRadius::default()
+ } else if self.effective_border_width().is_some() {
+ radius
+ } else {
+ rules.geometry_corner_radius.unwrap_or_default()
+ }
+ .expanded_by(self.focus_ring.width() as f32);
+ self.focus_ring.update(
+ self.animated_tile_size(),
+ !draw_focus_ring_with_background,
+ radius,
+ );
self.focus_ring.set_active(is_active);
}
@@ -573,6 +602,11 @@ impl<W: LayoutElement> Tile<W> {
.map_err(|err| warn!("error rendering window to texture: {err:?}"))
.ok();
+ let rules = self.window.rules();
+ let clip_to_geometry =
+ !self.is_fullscreen && rules.clip_to_geometry == Some(true);
+ let corner_radius = rules.geometry_corner_radius.unwrap_or_default();
+
if let Some((texture_current, _sync_point, texture_current_geo)) = current {
let elem = ResizeRenderElement::new(
shader,
@@ -584,8 +618,12 @@ impl<W: LayoutElement> Tile<W> {
window_size,
resize.anim.value() as f32,
resize.anim.clamped_value().clamp(0., 1.) as f32,
+ corner_radius,
+ clip_to_geometry,
alpha,
);
+ // FIXME: with split popups, this will use the resize element ID for
+ // popups, but we want the real IDs.
self.window
.set_offscreen_element_id(Some(elem.id().clone()));
resize_shader = Some(elem.into());
@@ -610,14 +648,77 @@ impl<W: LayoutElement> Tile<W> {
}
// If we're not resizing, render the window itself.
- let mut window = None;
+ let mut window_surface = None;
+ let mut window_popups = None;
if resize_shader.is_none() && resize_fallback.is_none() {
- window = Some(
- self.window
- .render(renderer, window_render_loc, scale, alpha, target)
- .into_iter()
- .map(Into::into),
- );
+ let window = self
+ .window
+ .render(renderer, window_render_loc, scale, alpha, target);
+
+ let geo = Rectangle::from_loc_and_size(window_render_loc, window_size);
+
+ let rules = self.window.rules();
+ let clip_to_geometry = !self.is_fullscreen && rules.clip_to_geometry == Some(true);
+ let radius = rules
+ .geometry_corner_radius
+ .unwrap_or_default()
+ .fit_to(window_size.w as f32, window_size.h as f32);
+
+ let clip_shader = ClippedSurfaceRenderElement::shader(renderer).cloned();
+ let border_shader = BorderRenderElement::shader(renderer).cloned();
+
+ window_surface = Some(window.normal.into_iter().map(move |elem| match elem {
+ LayoutElementRenderElement::Wayland(elem) => {
+ // If we should clip to geometry, render a clipped window.
+ if clip_to_geometry {
+ if let Some(shader) = clip_shader.clone() {
+ if ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radius) {
+ return ClippedSurfaceRenderElement::new(
+ elem,
+ scale,
+ geo,
+ shader.clone(),
+ radius,
+ )
+ .into();
+ }
+ }
+ }
+
+ // Otherwise, render it normally.
+ LayoutElementRenderElement::Wayland(elem).into()
+ }
+ LayoutElementRenderElement::SolidColor(elem) => {
+ // In this branch we're rendering a blocked-out window with a solid
+ // color. We need to render it with a rounded corner shader even if
+ // clip_to_geometry is false, because in this case we're assuming that
+ // the unclipped window CSD already has corners rounded to the
+ // user-provided radius, so our blocked-out rendering should match that
+ // radius.
+ if radius != CornerRadius::default() {
+ if let Some(shader) = border_shader.clone() {
+ return BorderRenderElement::new(
+ shader,
+ scale,
+ elem.geometry(Scale::from(1.)).to_logical(1),
+ Rectangle::from_loc_and_size(Point::from((0, 0)), geo.size),
+ elem.color(),
+ elem.color(),
+ 0.,
+ elem.geometry(Scale::from(1.)).to_logical(1),
+ 0.,
+ radius,
+ )
+ .into();
+ }
+ }
+
+