mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
266 lines
9.0 KiB
Rust
266 lines
9.0 KiB
Rust
use std::iter::zip;
|
|
|
|
use arrayvec::ArrayVec;
|
|
use niri_config::{CornerRadius, Gradient, GradientInterpolation, GradientRelativeTo};
|
|
use smithay::backend::renderer::element::Kind;
|
|
use smithay::utils::{Logical, Point, Rectangle, Size};
|
|
|
|
use crate::niri_render_elements;
|
|
use crate::render_helpers::border::BorderRenderElement;
|
|
use crate::render_helpers::renderer::NiriRenderer;
|
|
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
|
|
|
#[derive(Debug)]
|
|
pub struct FocusRing {
|
|
buffers: [SolidColorBuffer; 8],
|
|
locations: [Point<f64, Logical>; 8],
|
|
sizes: [Size<f64, Logical>; 8],
|
|
borders: [BorderRenderElement; 8],
|
|
full_size: Size<f64, Logical>,
|
|
is_border: bool,
|
|
use_border_shader: bool,
|
|
config: niri_config::FocusRing,
|
|
}
|
|
|
|
niri_render_elements! {
|
|
FocusRingRenderElement => {
|
|
SolidColor = SolidColorRenderElement,
|
|
Gradient = BorderRenderElement,
|
|
}
|
|
}
|
|
|
|
impl FocusRing {
|
|
pub fn new(config: niri_config::FocusRing) -> Self {
|
|
Self {
|
|
buffers: Default::default(),
|
|
locations: Default::default(),
|
|
sizes: Default::default(),
|
|
borders: Default::default(),
|
|
full_size: Default::default(),
|
|
is_border: false,
|
|
use_border_shader: false,
|
|
config,
|
|
}
|
|
}
|
|
|
|
pub fn update_config(&mut self, config: niri_config::FocusRing) {
|
|
self.config = config;
|
|
}
|
|
|
|
pub fn update_shaders(&mut self) {
|
|
for elem in &mut self.borders {
|
|
elem.damage_all();
|
|
}
|
|
}
|
|
|
|
pub fn update_render_elements(
|
|
&mut self,
|
|
win_size: Size<f64, Logical>,
|
|
is_active: bool,
|
|
is_border: bool,
|
|
view_rect: Rectangle<f64, Logical>,
|
|
radius: CornerRadius,
|
|
scale: f64,
|
|
) {
|
|
let width = self.config.width.0;
|
|
self.full_size = win_size + Size::from((width, width)).upscale(2.);
|
|
|
|
let color = if is_active {
|
|
self.config.active_color
|
|
} else {
|
|
self.config.inactive_color
|
|
};
|
|
|
|
for buf in &mut self.buffers {
|
|
buf.set_color(color.to_array_premul());
|
|
}
|
|
|
|
let radius = radius.fit_to(self.full_size.w as f32, self.full_size.h as f32);
|
|
|
|
let gradient = if is_active {
|
|
self.config.active_gradient
|
|
} else {
|
|
self.config.inactive_gradient
|
|
};
|
|
|
|
self.use_border_shader = radius != CornerRadius::default() || gradient.is_some();
|
|
|
|
// Set the defaults for solid color + rounded corners.
|
|
let gradient = gradient.unwrap_or(Gradient {
|
|
from: color,
|
|
to: color,
|
|
angle: 0,
|
|
relative_to: GradientRelativeTo::Window,
|
|
in_: GradientInterpolation::default(),
|
|
});
|
|
|
|
let full_rect = Rectangle::new(Point::from((-width, -width)), self.full_size);
|
|
let gradient_area = match gradient.relative_to {
|
|
GradientRelativeTo::Window => full_rect,
|
|
GradientRelativeTo::WorkspaceView => view_rect,
|
|
};
|
|
|
|
let rounded_corner_border_width = if self.is_border {
|
|
// HACK: increase the border width used for the inner rounded corners a tiny bit to
|
|
// reduce background bleed.
|
|
width as f32 + 0.5
|
|
} else {
|
|
0.
|
|
};
|
|
|
|
let ceil = |logical: f64| (logical * scale).ceil() / scale;
|
|
|
|
// All of this stuff should end up aligned to physical pixels because:
|
|
// * Window size and border width are rounded to physical pixels before being passed to this
|
|
// function.
|
|
// * We will ceil the corner radii below.
|
|
// * We do not divide anything, only add, subtract and multiply by integers.
|
|
// * At rendering time, tile positions are rounded to physical pixels.
|
|
|
|
if is_border {
|
|
let top_left = f64::max(width, ceil(f64::from(radius.top_left)));
|
|
let top_right = f64::min(
|
|
self.full_size.w - top_left,
|
|
f64::max(width, ceil(f64::from(radius.top_right))),
|
|
);
|
|
let bottom_left = f64::min(
|
|
self.full_size.h - top_left,
|
|
f64::max(width, ceil(f64::from(radius.bottom_left))),
|
|
);
|
|
let bottom_right = f64::min(
|
|
self.full_size.h - top_right,
|
|
f64::min(
|
|
self.full_size.w - bottom_left,
|
|
f64::max(width, ceil(f64::from(radius.bottom_right))),
|
|
),
|
|
);
|
|
|
|
// 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);
|
|
}
|
|
|
|
for (border, (loc, size)) in zip(&mut self.borders, zip(self.locations, self.sizes)) {
|
|
border.update(
|
|
size,
|
|
Rectangle::new(gradient_area.loc - loc, gradient_area.size),
|
|
gradient.in_,
|
|
gradient.from,
|
|
gradient.to,
|
|
((gradient.angle as f32) - 90.).to_radians(),
|
|
Rectangle::new(full_rect.loc - loc, full_rect.size),
|
|
rounded_corner_border_width,
|
|
radius,
|
|
scale as f32,
|
|
);
|
|
}
|
|
} else {
|
|
self.sizes[0] = self.full_size;
|
|
self.buffers[0].resize(self.sizes[0]);
|
|
self.locations[0] = Point::from((-width, -width));
|
|
|
|
self.borders[0].update(
|
|
self.sizes[0],
|
|
Rectangle::new(gradient_area.loc - self.locations[0], gradient_area.size),
|
|
gradient.in_,
|
|
gradient.from,
|
|
gradient.to,
|
|
((gradient.angle as f32) - 90.).to_radians(),
|
|
Rectangle::new(full_rect.loc - self.locations[0], full_rect.size),
|
|
rounded_corner_border_width,
|
|
radius,
|
|
scale as f32,
|
|
);
|
|
}
|
|
|
|
self.is_border = is_border;
|
|
}
|
|
|
|
pub fn render(
|
|
&self,
|
|
renderer: &mut impl NiriRenderer,
|
|
location: Point<f64, Logical>,
|
|
) -> impl Iterator<Item = FocusRingRenderElement> {
|
|
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 has_border_shader = BorderRenderElement::has_shader(renderer);
|
|
|
|
let mut push = |buffer, border: &BorderRenderElement, location: Point<f64, Logical>| {
|
|
let elem = if self.use_border_shader && has_border_shader {
|
|
border.clone().with_location(location).into()
|
|
} else {
|
|
SolidColorRenderElement::from_buffer(buffer, location, 1., Kind::Unspecified).into()
|
|
};
|
|
rv.push(elem);
|
|
};
|
|
|
|
if self.is_border {
|
|
for ((buf, border), loc) in zip(zip(&self.buffers, &self.borders), self.locations) {
|
|
push(buf, border, location + loc);
|
|
}
|
|
} else {
|
|
push(
|
|
&self.buffers[0],
|
|
&self.borders[0],
|
|
location + self.locations[0],
|
|
);
|
|
}
|
|
|
|
rv.into_iter()
|
|
}
|
|
|
|
pub fn width(&self) -> f64 {
|
|
self.config.width.0
|
|
}
|
|
|
|
pub fn is_off(&self) -> bool {
|
|
self.config.off
|
|
}
|
|
}
|