aboutsummaryrefslogtreecommitdiff
path: root/src
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
parentd86df5025cfd26ef4a3c48acd8ee80555265ee53 (diff)
downloadniri-42cef79c699c0f03b4bb99c4278169c48d9a5bd0.tar.gz
niri-42cef79c699c0f03b4bb99c4278169c48d9a5bd0.tar.bz2
niri-42cef79c699c0f03b4bb99c4278169c48d9a5bd0.zip
Implement rounded window corners
Diffstat (limited to 'src')
-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
17 files changed, 971 insertions, 280 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(
diff --git a/src/render_helpers/gradient.rs b/src/render_helpers/border.rs
index 958c2f9e..bf72a370 100644
--- a/src/render_helpers/gradient.rs
+++ b/src/render_helpers/border.rs
@@ -1,30 +1,40 @@
-use glam::Vec2;
+use std::collections::HashMap;
+
+use glam::{Mat3, Vec2};
+use niri_config::CornerRadius;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
-use smithay::backend::renderer::gles::element::PixelShaderElement;
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, Uniform};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform};
-use super::primary_gpu_pixel_shader::PrimaryGpuPixelShaderRenderElement;
use super::renderer::NiriRenderer;
-use super::shaders::Shaders;
+use super::shader_element::{ShaderProgram, ShaderRenderElement};
+use super::shaders::{mat3_uniform, Shaders};
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
-/// Renders a sub- or super-rect of an angled linear gradient like CSS linear-gradient(angle, a, b).
+/// Renders a wide variety of borders and border parts.
+///
+/// This includes:
+/// * sub- or super-rect of an angled linear gradient like CSS linear-gradient(angle, a, b).
+/// * corner rounding.
+/// * as a background rectangle and as parts of a border line.
#[derive(Debug)]
-pub struct GradientRenderElement(PrimaryGpuPixelShaderRenderElement);
+pub struct BorderRenderElement(ShaderRenderElement);
-impl GradientRenderElement {
+impl BorderRenderElement {
+ #[allow(clippy::too_many_arguments)]
pub fn new(
- renderer: &mut impl NiriRenderer,
+ shader: ShaderProgram,
scale: Scale<f64>,
area: Rectangle<i32, Logical>,
gradient_area: Rectangle<i32, Logical>,
color_from: [f32; 4],
color_to: [f32; 4],
angle: f32,
- ) -> Option<Self> {
- let shader = Shaders::get(renderer).gradient_border.clone()?;
+ geometry: Rectangle<i32, Logical>,
+ border_width: f32,
+ corner_radius: CornerRadius,
+ ) -> Self {
let grad_offset = (area.loc - gradient_area.loc).to_f64().to_physical(scale);
let grad_dir = Vec2::from_angle(angle);
@@ -42,9 +52,24 @@ impl GradientRenderElement {
grad_vec = -grad_vec;
}
- let elem = PixelShaderElement::new(
+ let area_physical = area.to_physical_precise_round(scale);
+ let area_loc = Vec2::new(area_physical.loc.x, area_physical.loc.y);
+ let area_size = Vec2::new(area_physical.size.w, area_physical.size.h);
+
+ let geo = geometry.to_physical_precise_round(scale);
+ let geo_loc = Vec2::new(geo.loc.x, geo.loc.y);
+ let geo_size = Vec2::new(geo.size.w, geo.size.h);
+
+ let input_to_geo =
+ Mat3::from_scale(area_size) * Mat3::from_translation((area_loc - geo_loc) / area_size);
+ let corner_radius = corner_radius.scaled_by(scale.x as f32);
+ let border_width = border_width * scale.x as f32;
+
+ let elem = ShaderRenderElement::new(
shader,
+ HashMap::new(),
area,
+ area.size.to_f64().to_buffer(scale, Transform::Normal),
None,
1.,
vec![
@@ -53,14 +78,22 @@ impl GradientRenderElement {
Uniform::new("grad_offset", (grad_offset.x as f32, grad_offset.y as f32)),
Uniform::new("grad_width", w),
Uniform::new("grad_vec", grad_vec.to_array()),
+ 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("border_width", border_width),
],
Kind::Unspecified,
);
- Some(Self(PrimaryGpuPixelShaderRenderElement(elem)))
+ Self(elem)
+ }
+
+ pub fn shader(renderer: &mut impl NiriRenderer) -> Option<&ShaderProgram> {
+ Shaders::get(renderer).border.as_ref()
}
}
-impl Element for GradientRenderElement {
+impl Element for BorderRenderElement {
fn id(&self) -> &Id {
self.0.id()
}
@@ -102,7 +135,7 @@ impl Element for GradientRenderElement {
}
}
-impl RenderElement<GlesRenderer> for GradientRenderElement {
+impl RenderElement<GlesRenderer> for BorderRenderElement {
fn draw(
&self,
frame: &mut GlesFrame<'_>,
@@ -118,7 +151,7 @@ impl RenderElement<GlesRenderer> for GradientRenderElement {
}
}
-impl<'render> RenderElement<TtyRenderer<'render>> for GradientRenderElement {
+impl<'render> RenderElement<TtyRenderer<'render>> for BorderRenderElement {
fn draw(
&self,
frame: &mut TtyFrame<'_, '_>,
diff --git a/src/render_helpers/clipped_surface.rs b/src/render_helpers/clipped_surface.rs
new file mode 100644
index 00000000..69c2a012
--- /dev/null
+++ b/src/render_helpers/clipped_surface.rs
@@ -0,0 +1,269 @@
+use glam::{Mat3, Vec2};
+use niri_config::CornerRadius;
+use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
+use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
+use smithay::backend::renderer::gles::{
+ GlesError, GlesFrame, GlesRenderer, GlesTexProgram, Uniform,
+};
+use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
+use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform};
+
+use super::renderer::{AsGlesFrame as _, NiriRenderer};
+use super::shaders::{mat3_uniform, Shaders};
+use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
+
+#[derive(Debug)]
+pub struct ClippedSurfaceRenderElement<R: NiriRenderer> {
+ inner: WaylandSurfaceRenderElement<R>,
+ program: GlesTexProgram,
+ corner_radius: CornerRadius,
+ geometry: Rectangle<i32, Logical>,
+ input_to_geo: Mat3,
+}
+
+impl<R: NiriRenderer> ClippedSurfaceRenderElement<R> {
+ pub fn new(
+ elem: WaylandSurfaceRenderElement<R>,
+ scale: Scale<f64>,
+ geometry: Rectangle<i32, Logical>,
+ program: GlesTexProgram,
+ corner_radius: CornerRadius,
+ ) -> Self {
+ let elem_geo = elem.geometry(scale);
+
+ let elem_geo_loc = Vec2::new(elem_geo.loc.x as f32, elem_geo.loc.y as f32);
+ let elem_geo_size = Vec2::new(elem_geo.size.w as f32, elem_geo.size.h as f32);
+
+ let geo = geometry.to_physical_precise_round(scale);
+ let geo_loc = Vec2::new(geo.loc.x, geo.loc.y);
+ let geo_size = Vec2::new(geo.size.w, geo.size.h);
+
+ let buf_size = elem.buffer_size().unwrap();
+ let buf_size = Vec2::new(buf_size.w as f32, buf_size.h as f32);
+
+ let view = elem.view().unwrap();
+ let src_loc = Vec2::new(view.src.loc.x as f32, view.src.loc.y as f32);
+ let src_size = Vec2::new(view.src.size.w as f32, view.src.size.h as f32);
+
+ let transform = elem.transform();
+ // HACK: ??? for some reason flipped ones are fine.
+ let transform = match transform {
+ Transform::_90 => Transform::_270,
+ Transform::_270 => Transform::_90,
+ x => x,
+ };
+ let transform_matrix = Mat3::from_translation(Vec2::new(0.5, 0.5))
+ * Mat3::from_cols_array(transform.matrix().as_ref())
+ * Mat3::from_translation(-Vec2::new(0.5, 0.5));
+
+ // FIXME: y_inverted
+ let input_to_geo = transform_matrix * Mat3::from_scale(elem_geo_size / geo_size)
+ * Mat3::from_translation((elem_geo_loc - geo_loc) / elem_geo_size)
+ // Apply viewporter src.
+ * Mat3::from_scale(buf_size / src_size)
+ * Mat3::from_translation(-src_loc / buf_size);
+
+ Self {
+ inner: elem,
+ program,
+ corner_radius,
+ geometry,
+ input_to_geo,
+ }
+ }
+
+ pub fn shader(renderer: &mut R) -> Option<&GlesTexProgram> {
+ Shaders::get(renderer).clipped_surface.as_ref()
+ }
+
+ pub fn will_clip(
+ elem: &WaylandSurfaceRenderElement<R>,
+ scale: Scale<f64>,
+ geometry: Rectangle<i32, Logical>,
+ corner_radius: CornerRadius,
+ ) -> bool {
+ let elem_geo = elem.geometry(scale);
+ let geo = geometry.to_physical_precise_round(scale);
+
+ if corner_radius == CornerRadius::default() {
+ !geo.contains_rect(elem_geo)
+ } else {
+ let corners = Self::rounded_corners(geometry.to_f64(), corner_radius);
+ let corners = corners
+ .into_iter()
+ .map(|rect| rect.to_physical_precise_round(scale));
+ let geo = Rectangle::subtract_rects_many([geo], corners);
+ !Rectangle::subtract_rects_many([elem_geo], geo).is_empty()
+ }
+ }
+
+ fn rounded_corners(
+ geo: Rectangle<f64, Logical>,
+ corner_radius: CornerRadius,
+ ) -> [Rectangle<f64, Logical>; 4] {
+ let top_left = corner_radius.top_left as f64;
+ let top_right = corner_radius.top_right as f64;
+ let bottom_right = corner_radius.bottom_right as f64;
+ let bottom_left = corner_radius.bottom_left as f64;
+
+ [
+ Rectangle::from_loc_and_size(geo.loc, (top_left, top_left)),
+ Rectangle::from_loc_and_size(
+ (geo.loc.x + geo.size.w - top_right, geo.loc.y),
+ (top_right, top_right),
+ ),
+ Rectangle::from_loc_and_size(
+ (
+ geo.loc.x + geo.size.w - bottom_right,
+ geo.loc.y + geo.size.h - bottom_right,
+ ),
+ (bottom_right, bottom_right),
+ ),
+ Rectangle::from_loc_and_size(
+ (geo.loc.x, geo.loc.y + geo.size.h - bottom_left),
+ (bottom_left, bottom_left),
+ ),
+ ]
+ }
+}
+
+impl<R: NiriRenderer> Element for ClippedSurfaceRenderElement<R> {
+ fn id(&self) -> &Id {
+ self.inner.id()
+ }
+
+ fn current_commit(&self) -> CommitCounter {
+ self.inner.current_commit()
+ }
+
+ fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
+ self.inner.geometry(scale)
+ }
+
+ fn src(&self) -> Rectangle<f64, Buffer> {
+ self.inner.src()
+ }
+
+ fn transform(&self) -> Transform {
+ self.inner.transform()
+ }
+
+ fn damage_since(
+ &self,
+ scale: Scale<f64>,
+ commit: Option<CommitCounter>,
+ ) -> DamageSet<i32, Physical> {
+ // FIXME: radius changes need to cause damage.
+ let damage = self.inner.damage_since(scale, commit);
+
+ // Intersect with geometry, since we're clipping by it.
+ let mut geo = self.geometry.to_physical_precise_round(scale);
+ geo.loc -= self.geometry(scale).loc;
+ damage
+ .into_iter()
+ .filter_map(|rect| rect.intersection(geo))
+ .collect()
+ }
+
+ fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
+ let regions = self.inner.opaque_regions(scale);
+
+ // Intersect with geometry, since we're clipping by it.
+ let mut geo = self.geometry.to_physical_precise_round(scale);
+ geo.loc -= self.geometry(scale).loc;
+ let regions = regions
+ .into_iter()
+ .filter_map(|rect| rect.intersection(geo));
+
+ // Subtract the rounded corners.
+ if self.corner_radius == CornerRadius::default() {
+ regions.collect()
+ } else {
+ let corners = Self::rounded_corners(self.geometry.to_f64(), self.corner_radius);
+
+ let elem_loc = self.geometry(scale).loc;
+ let corners = corners.into_iter().map(|rect| {
+ let mut rect = rect.to_physical_precise_round(scale);
+ rect.loc -= elem_loc;
+ rect
+ });
+
+ Rectangle::subtract_rects_many(regions, corners)
+ }
+ }
+
+ fn alpha(&self) -> f32 {
+ self.inner.alpha()
+ }
+
+ fn kind(&self) -> Kind {
+ self.inner.kind()
+ }
+}
+
+impl RenderElement<GlesRenderer> for ClippedSurfaceRenderElement<GlesRenderer> {
+ fn draw(
+ &self,
+ frame: &mut GlesFrame<'_>,
+ src: Rectangle<f64, Buffer>,
+ dst: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ ) -> Result<(), GlesError> {
+ frame.override_default_tex_program(
+ self.program.clone(),
+ vec![
+ Uniform::new(
+ "geo_size",
+ (self.geometry.size.w as f32, self.geometry.size.h as f32),
+ ),
+ Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
+ mat3_uniform("input_to_geo", self.input_to_geo),
+ ],
+ );
+ RenderElement::<GlesRenderer>::draw(&self.inner, frame, src, dst, damage)?;
+ frame.clear_tex_program_override();
+ Ok(())
+ }
+
+ fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
+ // If scanout for things other than Wayland buffers is implemented, this will need to take
+ // the target GPU into account.
+ None
+ }
+}
+
+impl<'render> RenderElement<TtyRenderer<'render>>
+ for ClippedSurfaceRenderElement<TtyRenderer<'render>>
+{
+ fn draw(
+ &self,
+ frame: &mut TtyFrame<'render, '_>,
+ src: Rectangle<f64, Buffer>,
+ dst: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ ) -> Result<(), TtyRendererError<'render>> {
+ frame.as_gles_frame().override_default_tex_program(
+ self.program.clone(),
+ vec![
+ Uniform::new(
+ "geo_size",
+ (self.geometry.size.w as f32, self.geometry.size.h as f32),
+ ),
+ Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
+ mat3_uniform("input_to_geo", self.input_to_geo),