mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Implement rounded window corners
This commit is contained in:
Generated
+2
-2
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+124
-30
@@ -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(
|
||||
|
||||
+117
-15
@@ -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(
|
||||
|
||||
@@ -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<'_, '_>,
|
||||
@@ -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),
|
||||
],
|
||||
);
|
||||
RenderElement::draw(&self.inner, frame, src, dst, damage)?;
|
||||
frame.as_gles_frame().clear_tex_program_override();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn underlying_storage(
|
||||
&self,
|
||||
_renderer: &mut TtyRenderer<'render>,
|
||||
) -> Option<UnderlyingStorage> {
|
||||
// If scanout for things other than Wayland buffers is implemented, this will need to take
|
||||
// the target GPU into account.
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,15 @@ use smithay::wayland::shm;
|
||||
|
||||
use self::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
|
||||
pub mod gradient;
|
||||
pub mod border;
|
||||
pub mod clipped_surface;
|
||||
pub mod offscreen;
|
||||
pub mod primary_gpu_pixel_shader;
|
||||
pub mod primary_gpu_pixel_shader_with_textures;
|
||||
pub mod primary_gpu_texture;
|
||||
pub mod render_elements;
|
||||
pub mod renderer;
|
||||
pub mod resize;
|
||||
pub mod resources;
|
||||
pub mod shader_element;
|
||||
pub mod shaders;
|
||||
pub mod snapshot;
|
||||
pub mod surface;
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
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};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
|
||||
use smithay::utils::{Buffer, Physical, Rectangle, Scale, Transform};
|
||||
|
||||
use super::renderer::AsGlesFrame;
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
|
||||
/// Wrapper for a pixel shader from the primary GPU for rendering with the primary GPU.
|
||||
#[derive(Debug)]
|
||||
pub struct PrimaryGpuPixelShaderRenderElement(pub PixelShaderElement);
|
||||
|
||||
impl Element for PrimaryGpuPixelShaderRenderElement {
|
||||
fn id(&self) -> &Id {
|
||||
self.0.id()
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.0.current_commit()
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.0.geometry(scale)
|
||||
}
|
||||
|
||||
fn transform(&self) -> Transform {
|
||||
self.0.transform()
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
self.0.src()
|
||||
}
|
||||
|
||||
fn damage_since(
|
||||
&self,
|
||||
scale: Scale<f64>,
|
||||
commit: Option<CommitCounter>,
|
||||
) -> DamageSet<i32, Physical> {
|
||||
self.0.damage_since(scale, commit)
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
|
||||
self.0.opaque_regions(scale)
|
||||
}
|
||||
|
||||
fn alpha(&self) -> f32 {
|
||||
self.0.alpha()
|
||||
}
|
||||
|
||||
fn kind(&self) -> Kind {
|
||||
self.0.kind()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderRenderElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut GlesFrame<'_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
|
||||
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 PrimaryGpuPixelShaderRenderElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut TtyFrame<'_, '_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn underlying_storage(
|
||||
&self,
|
||||
_renderer: &mut TtyRenderer<'render>,
|
||||
) -> Option<UnderlyingStorage> {
|
||||
// If scanout for things other than Wayland buffers is implemented, this will need to take
|
||||
// the target GPU into account.
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,24 @@
|
||||
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::{GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
|
||||
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size, Transform};
|
||||
|
||||
use super::primary_gpu_pixel_shader_with_textures::{
|
||||
PixelWithTexturesProgram, PrimaryGpuPixelShaderWithTexturesRenderElement,
|
||||
};
|
||||
use super::renderer::{AsGlesFrame, NiriRenderer};
|
||||
use super::shader_element::{ShaderProgram, ShaderRenderElement};
|
||||
use super::shaders::{mat3_uniform, Shaders};
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ResizeRenderElement(PrimaryGpuPixelShaderWithTexturesRenderElement);
|
||||
pub struct ResizeRenderElement(ShaderRenderElement);
|
||||
|
||||
impl ResizeRenderElement {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
shader: PixelWithTexturesProgram,
|
||||
shader: ShaderProgram,
|
||||
area: Rectangle<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
texture_prev: (GlesTexture, Rectangle<i32, Physical>),
|
||||
@@ -28,6 +27,8 @@ impl ResizeRenderElement {
|
||||
size_next: Size<i32, Logical>,
|
||||
progress: f32,
|
||||
clamped_progress: f32,
|
||||
corner_radius: CornerRadius,
|
||||
clip_to_geometry: bool,
|
||||
result_alpha: f32,
|
||||
) -> Self {
|
||||
let curr_geo = area;
|
||||
@@ -84,9 +85,13 @@ impl ResizeRenderElement {
|
||||
* Mat3::from_scale(size_next / tex_next_geo_size * scale);
|
||||
|
||||
let curr_geo_size = curr_geo_size * scale;
|
||||
let corner_radius = corner_radius
|
||||
.scaled_by(scale.x)
|
||||
.fit_to(curr_geo_size.x, curr_geo_size.y);
|
||||
let clip_to_geometry = if clip_to_geometry { 1. } else { 0. };
|
||||
|
||||
// Create the shader.
|
||||
Self(PrimaryGpuPixelShaderWithTexturesRenderElement::new(
|
||||
Self(ShaderRenderElement::new(
|
||||
shader,
|
||||
HashMap::from([
|
||||
(String::from("niri_tex_prev"), texture_prev),
|
||||
@@ -105,12 +110,14 @@ impl ResizeRenderElement {
|
||||
mat3_uniform("niri_geo_to_tex_next", geo_to_tex_next),
|
||||
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_clip_to_geometry", clip_to_geometry),
|
||||
],
|
||||
Kind::Unspecified,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn shader(renderer: &mut impl NiriRenderer) -> Option<PixelWithTexturesProgram> {
|
||||
pub fn shader(renderer: &mut impl NiriRenderer) -> Option<ShaderProgram> {
|
||||
Shaders::get(renderer).resize()
|
||||
}
|
||||
}
|
||||
|
||||
+94
-24
@@ -9,18 +9,17 @@ use smithay::backend::renderer::gles::{
|
||||
UniformDesc, UniformName,
|
||||
};
|
||||
use smithay::backend::renderer::utils::CommitCounter;
|
||||
use smithay::backend::renderer::DebugFlags;
|
||||
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size};
|
||||
|
||||
use super::renderer::AsGlesFrame;
|
||||
use super::resources::Resources;
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
|
||||
/// Wrapper for a pixel shader from the primary GPU for rendering with the primary GPU.
|
||||
///
|
||||
/// The shader accepts textures as input.
|
||||
/// Renders a shader with optional texture input, on the primary GPU.
|
||||
#[derive(Debug)]
|
||||
pub struct PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
shader: PixelWithTexturesProgram,
|
||||
pub struct ShaderRenderElement {
|
||||
shader: ShaderProgram,
|
||||
textures: HashMap<String, GlesTexture>,
|
||||
id: Id,
|
||||
commit_counter: CommitCounter,
|
||||
@@ -33,10 +32,17 @@ pub struct PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PixelWithTexturesProgram(Rc<PixelWithTexturesProgramInner>);
|
||||
pub struct ShaderProgram(Rc<ShaderProgramInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PixelWithTexturesProgramInner {
|
||||
struct ShaderProgramInner {
|
||||
normal: ShaderProgramInternal,
|
||||
debug: ShaderProgramInternal,
|
||||
uniform_tint: ffi::types::GLint,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ShaderProgramInternal {
|
||||
program: ffi::types::GLuint,
|
||||
uniform_tex_matrix: ffi::types::GLint,
|
||||
uniform_matrix: ffi::types::GLint,
|
||||
@@ -54,10 +60,12 @@ unsafe fn compile_program(
|
||||
additional_uniforms: &[UniformName<'_>],
|
||||
texture_uniforms: &[&str],
|
||||
// destruction_callback_sender: Sender<CleanupResource>,
|
||||
) -> Result<PixelWithTexturesProgram, GlesError> {
|
||||
let shader = src;
|
||||
|
||||
let program = unsafe { link_program(gl, include_str!("shaders/texture.vert"), shader)? };
|
||||
) -> Result<ShaderProgram, GlesError> {
|
||||
let shader = format!("#version 100\n{}", src);
|
||||
let program = unsafe { link_program(gl, include_str!("shaders/texture.vert"), &shader)? };
|
||||
let debug_shader = format!("#version 100\n#define DEBUG_FLAGS\n{}", src);
|
||||
let debug_program =
|
||||
unsafe { link_program(gl, include_str!("shaders/texture.vert"), &debug_shader)? };
|
||||
|
||||
let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
|
||||
let vert_position = CStr::from_bytes_with_nul(b"vert_position\0").expect("NULL terminated");
|
||||
@@ -65,9 +73,10 @@ unsafe fn compile_program(
|
||||
let tex_matrix = CStr::from_bytes_with_nul(b"tex_matrix\0").expect("NULL terminated");
|
||||
let size = CStr::from_bytes_with_nul(b"niri_size\0").expect("NULL terminated");
|
||||
let alpha = CStr::from_bytes_with_nul(b"niri_alpha\0").expect("NULL terminated");
|
||||
let tint = CStr::from_bytes_with_nul(b"niri_tint\0").expect("NULL terminated");
|
||||
|
||||
Ok(PixelWithTexturesProgram(Rc::new(
|
||||
PixelWithTexturesProgramInner {
|
||||
Ok(ShaderProgram(Rc::new(ShaderProgramInner {
|
||||
normal: ShaderProgramInternal {
|
||||
program,
|
||||
uniform_matrix: gl
|
||||
.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
@@ -106,10 +115,60 @@ unsafe fn compile_program(
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
)))
|
||||
debug: ShaderProgramInternal {
|
||||
program: debug_program,
|
||||
uniform_matrix: gl
|
||||
.GetUniformLocation(debug_program, matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_tex_matrix: gl.GetUniformLocation(
|
||||
debug_program,
|
||||
tex_matrix.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
uniform_size: gl
|
||||
.GetUniformLocation(debug_program, size.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_alpha: gl
|
||||
.GetUniformLocation(debug_program, alpha.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_vert: gl
|
||||
.GetAttribLocation(debug_program, vert.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_vert_position: gl.GetAttribLocation(
|
||||
debug_program,
|
||||
vert_position.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
additional_uniforms: additional_uniforms
|
||||
.iter()
|
||||
.map(|uniform| {
|
||||
let name =
|
||||
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
|
||||
let location = gl.GetUniformLocation(
|
||||
debug_program,
|
||||
name.as_ptr() as *const ffi::types::GLchar,
|
||||
);
|
||||
(
|
||||
uniform.name.clone().into_owned(),
|
||||
UniformDesc {
|
||||
location,
|
||||
type_: uniform.type_,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
texture_uniforms: texture_uniforms
|
||||
.iter()
|
||||
.map(|name_| {
|
||||
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
|
||||
let location = gl.GetUniformLocation(
|
||||
debug_program,
|
||||
name.as_ptr() as *const ffi::types::GLchar,
|
||||
);
|
||||
(name_.to_string(), location)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
uniform_tint: gl
|
||||
.GetUniformLocation(debug_program, tint.as_ptr() as *const ffi::types::GLchar),
|
||||
})))
|
||||
}
|
||||
|
||||
impl PixelWithTexturesProgram {
|
||||
impl ShaderProgram {
|
||||
pub fn compile(
|
||||
renderer: &mut GlesRenderer,
|
||||
src: &str,
|
||||
@@ -123,15 +182,16 @@ impl PixelWithTexturesProgram {
|
||||
|
||||
pub fn destroy(self, renderer: &mut GlesRenderer) -> Result<(), GlesError> {
|
||||
renderer.with_context(move |gl| unsafe {
|
||||
gl.DeleteProgram(self.0.program);
|
||||
gl.DeleteProgram(self.0.normal.program);
|
||||
gl.DeleteProgram(self.0.debug.program);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
impl ShaderRenderElement {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
shader: PixelWithTexturesProgram,
|
||||
shader: ShaderProgram,
|
||||
textures: HashMap<String, GlesTexture>,
|
||||
area: Rectangle<i32, Logical>,
|
||||
size: Size<f64, Buffer>,
|
||||
@@ -158,7 +218,7 @@ impl PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
impl Element for ShaderRenderElement {
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
@@ -191,7 +251,7 @@ impl Element for PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElement {
|
||||
impl RenderElement<GlesRenderer> for ShaderRenderElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut GlesFrame<'_>,
|
||||
@@ -274,7 +334,14 @@ impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElem
|
||||
//apply output transformation
|
||||
matrix = Mat3::from_cols_array(frame.projection()) * matrix;
|
||||
|
||||
let program = &self.shader.0;
|
||||
let has_debug = !frame.debug_flags().is_empty();
|
||||
let has_tint = frame.debug_flags().contains(DebugFlags::TINT);
|
||||
|
||||
let program = if has_debug {
|
||||
&self.shader.0.debug
|
||||
} else {
|
||||
&self.shader.0.normal
|
||||
};
|
||||
|
||||
// render
|
||||
frame.with_context(move |gl| -> Result<(), GlesError> {
|
||||
@@ -317,6 +384,11 @@ impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElem
|
||||
gl.Uniform2f(program.uniform_size, dest.size.w as f32, dest.size.h as f32);
|
||||
gl.Uniform1f(program.uniform_alpha, self.alpha);
|
||||
|
||||
let tint = if has_tint { 1.0f32 } else { 0.0f32 };
|
||||
if has_debug {
|
||||
gl.Uniform1f(self.shader.0.uniform_tint, tint);
|
||||
}
|
||||
|
||||
for uniform in &self.additional_uniforms {
|
||||
let desc =
|
||||
program
|
||||
@@ -408,9 +480,7 @@ impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElem
|
||||
}
|
||||
}
|
||||
|
||||
impl<'render> RenderElement<TtyRenderer<'render>>
|
||||
for PrimaryGpuPixelShaderWithTexturesRenderElement
|
||||
{
|
||||
impl<'render> RenderElement<TtyRenderer<'render>> for ShaderRenderElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut TtyFrame<'_, '_>,
|
||||
@@ -0,0 +1,87 @@
|
||||
precision mediump float;
|
||||
|
||||
#if defined(DEBUG_FLAGS)
|
||||
uniform float niri_tint;
|
||||
#endif
|
||||
|
||||
uniform float niri_alpha;
|
||||
|
||||
uniform vec2 niri_size;
|
||||
varying vec2 niri_v_coords;
|
||||
|
||||
uniform vec4 color_from;
|
||||
uniform vec4 color_to;
|
||||
uniform vec2 grad_offset;
|
||||
uniform float grad_width;
|
||||
uniform vec2 grad_vec;
|
||||
|
||||
uniform mat3 input_to_geo;
|
||||
uniform vec2 geo_size;
|
||||
uniform vec4 outer_radius;
|
||||
uniform float border_width;
|
||||
|
||||
vec4 gradient_color(vec2 coords) {
|
||||
coords = coords + grad_offset;
|
||||
|
||||
if ((grad_vec.x < 0.0 && 0.0 <= grad_vec.y) || (0.0 <= grad_vec.x && grad_vec.y < 0.0))
|
||||
coords.x -= grad_width;
|
||||
|
||||
float frac = dot(coords, grad_vec) / dot(grad_vec, grad_vec);
|
||||
|
||||
if (grad_vec.y < 0.0)
|
||||
frac += 1.0;
|
||||
|
||||
frac = clamp(frac, 0.0, 1.0);
|
||||
return mix(color_from, color_to, frac);
|
||||
}
|
||||
|
||||
float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
|
||||
vec2 center;
|
||||
float radius;
|
||||
|
||||
if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
|
||||
radius = corner_radius.x;
|
||||
center = vec2(radius, radius);
|
||||
} else if (size.x - corner_radius.y < coords.x && coords.y < corner_radius.y) {
|
||||
radius = corner_radius.y;
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (size.x - corner_radius.z < coords.x && size.y - corner_radius.z < coords.y) {
|
||||
radius = corner_radius.z;
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else if (coords.x < corner_radius.w && size.y - corner_radius.w < coords.y) {
|
||||
radius = corner_radius.w;
|
||||
center = vec2(radius, size.y - radius);
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 color = gradient_color(niri_v_coords * niri_size);
|
||||
|
||||
vec3 coords_geo = input_to_geo * vec3(niri_v_coords, 1.0);
|
||||
color = color * rounding_alpha(coords_geo.xy, geo_size, outer_radius);
|
||||
|
||||
if (border_width > 0.0) {
|
||||
coords_geo -= vec3(border_width);
|
||||
vec2 inner_geo_size = geo_size - vec2(border_width * 2.0);
|
||||
if (0.0 <= coords_geo.x && coords_geo.x <= inner_geo_size.x
|
||||
&& 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 - rounding_alpha(coords_geo.xy, inner_geo_size, inner_radius));
|
||||
}
|
||||
}
|
||||
|
||||
color = color * niri_alpha;
|
||||
|
||||
#if defined(DEBUG_FLAGS)
|
||||
if (niri_tint == 1.0)
|
||||
color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
|
||||
#endif
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#version 100
|
||||
|
||||
//_DEFINES_
|
||||
|
||||
#if defined(EXTERNAL)
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
#endif
|
||||
|
||||
precision mediump float;
|
||||
#if defined(EXTERNAL)
|
||||
uniform samplerExternalOES tex;
|
||||
#else
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
uniform float alpha;
|
||||
varying vec2 v_coords;
|
||||
|
||||
#if defined(DEBUG_FLAGS)
|
||||
uniform float tint;
|
||||
#endif
|
||||
|
||||
uniform vec2 geo_size;
|
||||
uniform vec4 corner_radius;
|
||||
uniform mat3 input_to_geo;
|
||||
|
||||
float rounding_alpha(vec2 coords, vec2 size) {
|
||||
vec2 center;
|
||||
float radius;
|
||||
|
||||
if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
|
||||
radius = corner_radius.x;
|
||||
center = vec2(radius, radius);
|
||||
} else if (size.x - corner_radius.y < coords.x && coords.y < corner_radius.y) {
|
||||
radius = corner_radius.y;
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (size.x - corner_radius.z < coords.x && size.y - corner_radius.z < coords.y) {
|
||||
radius = corner_radius.z;
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else if (coords.x < corner_radius.w && size.y - corner_radius.w < coords.y) {
|
||||
radius = corner_radius.w;
|
||||
center = vec2(radius, size.y - radius);
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0);
|
||||
|
||||
// Sample the texture.
|
||||
vec4 color = texture2D(tex, v_coords);
|
||||
#if defined(NO_ALPHA)
|
||||
color = vec4(color.rgb, 1.0);
|
||||
#endif
|
||||
|
||||
if (coords_geo.x < 0.0 || 1.0 < coords_geo.x || coords_geo.y < 0.0 || 1.0 < coords_geo.y) {
|
||||
// Clip outside geometry.
|
||||
color = vec4(0.0);
|
||||
} else {
|
||||
// Apply corner rounding inside geometry.
|
||||
color = color * rounding_alpha(coords_geo.xy * geo_size, geo_size);
|
||||
}
|
||||
|
||||
// Apply final alpha and tint.
|
||||
color = color * alpha;
|
||||
|
||||
#if defined(DEBUG_FLAGS)
|
||||
if (tint == 1.0)
|
||||
color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
|
||||
#endif
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
precision mediump float;
|
||||
uniform float alpha;
|
||||
#if defined(DEBUG_FLAGS)
|
||||
uniform float tint;
|
||||
#endif
|
||||
uniform vec2 size;
|
||||
varying vec2 v_coords;
|
||||
|
||||
uniform vec4 color_from;
|
||||
uniform vec4 color_to;
|
||||
uniform vec2 grad_offset;
|
||||
uniform float grad_width;
|
||||
uniform vec2 grad_vec;
|
||||
|
||||
void main() {
|
||||
vec2 coords = v_coords * size + grad_offset;
|
||||
|
||||
if ((grad_vec.x < 0.0 && 0.0 <= grad_vec.y) || (0.0 <= grad_vec.x && grad_vec.y < 0.0))
|
||||
coords.x -= grad_width;
|
||||
|
||||
float frac = dot(coords, grad_vec) / dot(grad_vec, grad_vec);
|
||||
|
||||
if (grad_vec.y < 0.0)
|
||||
frac += 1.0;
|
||||
|
||||
frac = clamp(frac, 0.0, 1.0);
|
||||
vec4 out_color = mix(color_from, color_to, frac);
|
||||
|
||||
#if defined(DEBUG_FLAGS)
|
||||
if (tint == 1.0)
|
||||
out_color = vec4(0.0, 0.3, 0.0, 0.2) + out_color * 0.8;
|
||||
#endif
|
||||
|
||||
gl_FragColor = out_color;
|
||||
}
|
||||
@@ -2,35 +2,55 @@ use std::cell::RefCell;
|
||||
|
||||
use glam::Mat3;
|
||||
use smithay::backend::renderer::gles::{
|
||||
GlesError, GlesPixelProgram, GlesRenderer, Uniform, UniformName, UniformType, UniformValue,
|
||||
GlesError, GlesRenderer, GlesTexProgram, Uniform, UniformName, UniformType, UniformValue,
|
||||
};
|
||||
|
||||
use super::primary_gpu_pixel_shader_with_textures::PixelWithTexturesProgram;
|
||||
use super::renderer::NiriRenderer;
|
||||
use super::shader_element::ShaderProgram;
|
||||
|
||||
pub struct Shaders {
|
||||
pub gradient_border: Option<GlesPixelProgram>,
|
||||
pub resize: Option<PixelWithTexturesProgram>,
|
||||
pub custom_resize: RefCell<Option<PixelWithTexturesProgram>>,
|
||||
pub border: Option<ShaderProgram>,
|
||||
pub clipped_surface: Option<GlesTexProgram>,
|
||||
pub resize: Option<ShaderProgram>,
|
||||
pub custom_resize: RefCell<Option<ShaderProgram>>,
|
||||
}
|
||||
|
||||
impl Shaders {
|
||||
fn compile(renderer: &mut GlesRenderer) -> Self {
|
||||
let _span = tracy_client::span!("Shaders::compile");
|
||||
|
||||
let gradient_border = renderer
|
||||
.compile_custom_pixel_shader(
|
||||
include_str!("gradient_border.frag"),
|
||||
let border = ShaderProgram::compile(
|
||||
renderer,
|
||||
include_str!("border.frag"),
|
||||
&[
|
||||
UniformName::new("color_from", UniformType::_4f),
|
||||
UniformName::new("color_to", UniformType::_4f),
|
||||
UniformName::new("grad_offset", UniformType::_2f),
|
||||
UniformName::new("grad_width", UniformType::_1f),
|
||||
UniformName::new("grad_vec", UniformType::_2f),
|
||||
UniformName::new("input_to_geo", UniformType::Matrix3x3),
|
||||
UniformName::new("geo_size", UniformType::_2f),
|
||||
UniformName::new("outer_radius", UniformType::_4f),
|
||||
UniformName::new("border_width", UniformType::_1f),
|
||||
],
|
||||
&[],
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("error compiling border shader: {err:?}");
|
||||
})
|
||||
.ok();
|
||||
|
||||
let clipped_surface = renderer
|
||||
.compile_custom_texture_shader(
|
||||
include_str!("clipped_surface.frag"),
|
||||
&[
|
||||
UniformName::new("color_from", UniformType::_4f),
|
||||
UniformName::new("color_to", UniformType::_4f),
|
||||
UniformName::new("grad_offset", UniformType::_2f),
|
||||
UniformName::new("grad_width", UniformType::_1f),
|
||||
UniformName::new("grad_vec", UniformType::_2f),
|
||||
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 gradient border shader: {err:?}");
|
||||
warn!("error compiling clipped surface shader: {err:?}");
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -41,7 +61,8 @@ impl Shaders {
|
||||
.ok();
|
||||
|
||||
Self {
|
||||
gradient_border,
|
||||
border,
|
||||
clipped_surface,
|
||||
resize,
|
||||
custom_resize: RefCell::new(None),
|
||||
}
|
||||
@@ -56,12 +77,12 @@ impl Shaders {
|
||||
|
||||
pub fn replace_custom_resize_program(
|
||||
&self,
|
||||
program: Option<PixelWithTexturesProgram>,
|
||||
) -> Option<PixelWithTexturesProgram> {
|
||||
program: Option<ShaderProgram>,
|
||||
) -> Option<ShaderProgram> {
|
||||
self.custom_resize.replace(program)
|
||||
}
|
||||
|
||||
pub fn resize(&self) -> Option<PixelWithTexturesProgram> {
|
||||
pub fn resize(&self) -> Option<ShaderProgram> {
|
||||
self.custom_resize
|
||||
.borrow()
|
||||
.clone()
|
||||
@@ -80,12 +101,12 @@ pub fn init(renderer: &mut GlesRenderer) {
|
||||
fn compile_resize_program(
|
||||
renderer: &mut GlesRenderer,
|
||||
src: &str,
|
||||
) -> Result<PixelWithTexturesProgram, GlesError> {
|
||||
let mut program = include_str!("resize-prelude.frag").to_string();
|
||||
) -> Result<ShaderProgram, GlesError> {
|
||||
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!("resize_epilogue.frag"));
|
||||
|
||||
PixelWithTexturesProgram::compile(
|
||||
ShaderProgram::compile(
|
||||
renderer,
|
||||
&program,
|
||||
&[
|
||||
@@ -97,6 +118,8 @@ fn compile_resize_program(
|
||||
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"],
|
||||
)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
void main() {
|
||||
vec3 coords_curr_geo = niri_input_to_curr_geo * vec3(niri_v_coords, 1.0);
|
||||
vec3 size_curr_geo = vec3(niri_curr_geo_size, 1.0);
|
||||
|
||||
vec4 color = resize_color(coords_curr_geo, size_curr_geo);
|
||||
|
||||
gl_FragColor = color * niri_alpha;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
|
||||
varying vec2 niri_v_coords;
|
||||
uniform vec2 niri_size;
|
||||
|
||||
uniform mat3 niri_input_to_curr_geo;
|
||||
uniform mat3 niri_curr_geo_to_prev_geo;
|
||||
uniform mat3 niri_curr_geo_to_next_geo;
|
||||
uniform vec2 niri_curr_geo_size;
|
||||
|
||||
uniform sampler2D niri_tex_prev;
|
||||
uniform mat3 niri_geo_to_tex_prev;
|
||||
|
||||
uniform sampler2D niri_tex_next;
|
||||
uniform mat3 niri_geo_to_tex_next;
|
||||
|
||||
uniform float niri_progress;
|
||||
uniform float niri_clamped_progress;
|
||||
|
||||
uniform float niri_alpha;
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
void main() {
|
||||
vec3 coords_curr_geo = niri_input_to_curr_geo * vec3(niri_v_coords, 1.0);
|
||||
vec3 size_curr_geo = vec3(niri_curr_geo_size, 1.0);
|
||||
|
||||
vec4 color = resize_color(coords_curr_geo, size_curr_geo);
|
||||
|
||||
if (niri_clip_to_geometry == 1.0) {
|
||||
if (coords_curr_geo.x < 0.0 || 1.0 < coords_curr_geo.x
|
||||
|| coords_curr_geo.y < 0.0 || 1.0 < coords_curr_geo.y) {
|
||||
// Clip outside geometry.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
color = color * niri_alpha;
|
||||
|
||||
#if defined(DEBUG_FLAGS)
|
||||
if (niri_tint == 1.0)
|
||||
color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
|
||||
#endif
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
precision mediump float;
|
||||
|
||||
#if defined(DEBUG_FLAGS)
|
||||
uniform float niri_tint;
|
||||
#endif
|
||||
|
||||
varying vec2 niri_v_coords;
|
||||
uniform vec2 niri_size;
|
||||
|
||||
uniform mat3 niri_input_to_curr_geo;
|
||||
uniform mat3 niri_curr_geo_to_prev_geo;
|
||||
uniform mat3 niri_curr_geo_to_next_geo;
|
||||
uniform vec2 niri_curr_geo_size;
|
||||
|
||||
uniform sampler2D niri_tex_prev;
|
||||
uniform mat3 niri_geo_to_tex_prev;
|
||||
|
||||
uniform sampler2D niri_tex_next;
|
||||
uniform mat3 niri_geo_to_tex_next;
|
||||
|
||||
uniform float niri_progress;
|
||||
uniform float niri_clamped_progress;
|
||||
|
||||
uniform vec4 niri_corner_radius;
|
||||
uniform float niri_clip_to_geometry;
|
||||
|
||||
uniform float niri_alpha;
|
||||
|
||||
float niri_rounding_alpha(vec2 coords, vec2 size) {
|
||||
vec2 center;
|
||||
float radius;
|
||||
|
||||
if (coords.x < niri_corner_radius.x && coords.y < niri_corner_radius.x) {
|
||||
radius = niri_corner_radius.x;
|
||||
center = vec2(radius, radius);
|
||||
} else if (size.x - niri_corner_radius.y < coords.x && coords.y < niri_corner_radius.y) {
|
||||
radius = niri_corner_radius.y;
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (size.x - niri_corner_radius.z < coords.x && size.y - niri_corner_radius.z < coords.y) {
|
||||
radius = niri_corner_radius.z;
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else if (coords.x < niri_corner_radius.w && size.y - niri_corner_radius.w < coords.y) {
|
||||
radius = niri_corner_radius.w;
|
||||
center = vec2(radius, size.y - radius);
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
|
||||
}
|
||||
+15
-1
@@ -1,4 +1,4 @@
|
||||
use niri_config::{BlockOutFrom, BorderRule, Match, WindowRule};
|
||||
use niri_config::{BlockOutFrom, BorderRule, CornerRadius, Match, WindowRule};
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use smithay::wayland::shell::xdg::{
|
||||
@@ -61,6 +61,12 @@ pub struct ResolvedWindowRules {
|
||||
/// Extra opacity to draw this window with.
|
||||
pub opacity: Option<f32>,
|
||||
|
||||
/// Corner radius to assume this window has.
|
||||
pub geometry_corner_radius: Option<CornerRadius>,
|
||||
|
||||
/// Whether to clip this window to its geometry, including the corner radius.
|
||||
pub clip_to_geometry: Option<bool>,
|
||||
|
||||
/// Whether to block out this window from certain render targets.
|
||||
pub block_out_from: Option<BlockOutFrom>,
|
||||
}
|
||||
@@ -112,6 +118,8 @@ impl ResolvedWindowRules {
|
||||
},
|
||||
draw_border_with_background: None,
|
||||
opacity: None,
|
||||
geometry_corner_radius: None,
|
||||
clip_to_geometry: None,
|
||||
block_out_from: None,
|
||||
}
|
||||
}
|
||||
@@ -190,6 +198,12 @@ impl ResolvedWindowRules {
|
||||
if let Some(x) = rule.opacity {
|
||||
resolved.opacity = Some(x);
|
||||
}
|
||||
if let Some(x) = rule.geometry_corner_radius {
|
||||
resolved.geometry_corner_radius = Some(x);
|
||||
}
|
||||
if let Some(x) = rule.clip_to_geometry {
|
||||
resolved.clip_to_geometry = Some(x);
|
||||
}
|
||||
if let Some(x) = rule.block_out_from {
|
||||
resolved.block_out_from = Some(x);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,9 @@ window-rule {
|
||||
// Same as focus-ring.
|
||||
}
|
||||
|
||||
geometry-corner-radius 12
|
||||
clip-to-geometry true
|
||||
|
||||
min-width 100
|
||||
max-width 200
|
||||
min-height 300
|
||||
@@ -378,6 +381,77 @@ window-rule {
|
||||
}
|
||||
```
|
||||
|
||||
#### `geometry-corner-radius`
|
||||
|
||||
<sup>Since: 0.1.6</sup>
|
||||
|
||||
Set the corner radius of the window.
|
||||
|
||||
On its own, this setting will only affect the border and the focus ring—they will round their corners to match the geometry corner radius.
|
||||
If you'd like to force-round the corners of the window itself, set `clip-to-geometry true` in addition to this setting.
|
||||
|
||||
```
|
||||
window-rule {
|
||||
geometry-corner-radius 12
|
||||
}
|
||||
```
|
||||
|
||||
The radius is set in logical pixels, and controls the radius of the window itself, that is, the inner radius of the border:
|
||||
|
||||

|
||||
|
||||
Instead of one radius, you can set four, for each corner.
|
||||
The order is the same as in CSS: top-left, top-right, bottom-right, bottom-left.
|
||||
|
||||
```
|
||||
window-rule {
|
||||
geometry-corner-radius 8 8 0 0
|
||||
}
|
||||
```
|
||||
|
||||
This way, you can match GTK 3 applications which have square bottom corners:
|
||||
|
||||

|
||||
|
||||
#### `clip-to-geometry`
|
||||
|
||||
<sup>Since: 0.1.6</sup>
|
||||
|
||||
Clips the window to its visual geometry.
|
||||
|
||||
This will cut out any client-side window shadows, and also round window corners according to `geometry-corner-radius`.
|
||||
|
||||

|
||||
|
||||
```
|
||||
window-rule {
|
||||
clip-to-geometry true
|
||||
}
|
||||
```
|
||||
|
||||
Enable border, set `geometry-corner-radius` and `clip-to-geometry`, and you've got a classic setup:
|
||||
|
||||

|
||||
|
||||
```
|
||||
prefer-no-csd
|
||||
|
||||
layout {
|
||||
focus-ring {
|
||||
off
|
||||
}
|
||||
|
||||
border {
|
||||
width 2
|
||||
}
|
||||
}
|
||||
|
||||
window-rule {
|
||||
geometry-corner-radius 12
|
||||
clip-to-geometry true
|
||||
}
|
||||
```
|
||||
|
||||
#### Size Overrides
|
||||
|
||||
You can amend the window's minimum and maximum size in logical pixels.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6246413f95e5aa2d63db3fc8bc7726e45af3d20a95ea8f66614f4d62fa7a6624
|
||||
size 34239
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:88ca1517153755ee64b3fae4e67eced1f83873026ad1ad35c44b85f31fa8171d
|
||||
size 33591
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e420523fff7891335ef9af5410d946abdd468f1e1dad88e0c189a2b21c9bc7f3
|
||||
size 11099
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3ed3d553cae8948726ab03db3bf4ad9ef2dc9cf4593e24a96fad9d8820af949d
|
||||
size 25777
|
||||
Reference in New Issue
Block a user