diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-05-01 19:06:08 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-05-02 14:27:53 +0400 |
| commit | 42cef79c699c0f03b4bb99c4278169c48d9a5bd0 (patch) | |
| tree | 630d18b49f3d93603a79bcfc5734dc773c6d0cb6 /src/layout | |
| parent | d86df5025cfd26ef4a3c48acd8ee80555265ee53 (diff) | |
| download | niri-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.rs | 154 | ||||
| -rw-r--r-- | src/layout/tile.rs | 132 |
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( |
