aboutsummaryrefslogtreecommitdiff
path: root/src/layout
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 /src/layout
parentd86df5025cfd26ef4a3c48acd8ee80555265ee53 (diff)
downloadniri-42cef79c699c0f03b4bb99c4278169c48d9a5bd0.tar.gz
niri-42cef79c699c0f03b4bb99c4278169c48d9a5bd0.tar.bz2
niri-42cef79c699c0f03b4bb99c4278169c48d9a5bd0.zip
Implement rounded window corners
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/focus_ring.rs154
-rw-r--r--src/layout/tile.rs132
2 files changed, 241 insertions, 45 deletions
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();
+ }
+ }
+
+ // Otherwise, render the solid color as is.
+ LayoutElementRenderElement::SolidColor(elem).into()
+ }
+ }));
+
+ window_popups = Some(window.popups.into_iter().map(Into::into));
}
let rv = resize_popups
@@ -625,7 +726,8 @@ impl<W: LayoutElement> Tile<W> {
.flatten()
.chain(resize_shader)
.chain(resize_fallback)
- .chain(window.into_iter().flatten());
+ .chain(window_popups.into_iter().flatten())
+ .chain(window_surface.into_iter().flatten());
let elem = self.is_fullscreen.then(|| {
SolidColorRenderElement::from_buffer(