aboutsummaryrefslogtreecommitdiff
path: root/src/layout/tile.rs
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2023-12-27 21:51:42 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2023-12-27 21:51:42 +0400
commitc21805bf705bd36a6eb7f79039b759e9af79dfcb (patch)
treee24213b023c8129320daa782c8b0830ea334d519 /src/layout/tile.rs
parentbfc24182670a0b3e17f79d66474fd291b7110732 (diff)
downloadniri-c21805bf705bd36a6eb7f79039b759e9af79dfcb.tar.gz
niri-c21805bf705bd36a6eb7f79039b759e9af79dfcb.tar.bz2
niri-c21805bf705bd36a6eb7f79039b759e9af79dfcb.zip
layout: Refactor to support window decorations, add border and fullscreen backdrop
Windows are now wrapped in Tiles, which keep track of window-specific decorations. Particularly, I implemented a black fullscreen backdrop, which finally brings fullscreened windows smaller than the screen in line with how the Wayland protocol says they should look—centered in a black rectangle. I also implemented window borders, which are similar to the focus ring, but always visible (and hence affect the layout and sizing).
Diffstat (limited to 'src/layout/tile.rs')
-rw-r--r--src/layout/tile.rs267
1 files changed, 267 insertions, 0 deletions
diff --git a/src/layout/tile.rs b/src/layout/tile.rs
new file mode 100644
index 00000000..2e8dcd90
--- /dev/null
+++ b/src/layout/tile.rs
@@ -0,0 +1,267 @@
+use std::cmp::max;
+use std::rc::Rc;
+use std::time::Duration;
+
+use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
+use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
+use smithay::backend::renderer::element::Kind;
+use smithay::backend::renderer::{ImportAll, Renderer};
+use smithay::utils::{Logical, Point, Scale, Size};
+
+use super::focus_ring::FocusRing;
+use super::workspace::WorkspaceRenderElement;
+use super::{LayoutElement, Options};
+
+/// Toplevel window with decorations.
+#[derive(Debug)]
+pub struct Tile<W: LayoutElement> {
+ /// The toplevel window itself.
+ window: W,
+
+ /// The border around the window.
+ border: FocusRing,
+
+ /// Whether this tile is fullscreen.
+ ///
+ /// This will update only when the `window` actually goes fullscreen, rather than right away,
+ /// to avoid black backdrop flicker before the window has had a chance to resize.
+ is_fullscreen: bool,
+
+ /// The black backdrop for fullscreen windows.
+ fullscreen_backdrop: SolidColorBuffer,
+
+ /// The size we were requested to fullscreen into.
+ fullscreen_size: Size<i32, Logical>,
+
+ /// Configurable properties of the layout.
+ options: Rc<Options>,
+}
+
+impl<W: LayoutElement> Tile<W> {
+ pub fn new(window: W, options: Rc<Options>) -> Self {
+ Self {
+ window,
+ border: FocusRing::new(options.border),
+ is_fullscreen: false, // FIXME: up-to-date fullscreen right away, but we need size.
+ fullscreen_backdrop: SolidColorBuffer::new((0, 0), [0., 0., 0., 1.]),
+ fullscreen_size: Default::default(),
+ options,
+ }
+ }
+
+ pub fn update_config(&mut self, options: Rc<Options>) {
+ self.border.update_config(options.border);
+ self.options = options;
+ }
+
+ pub fn advance_animations(&mut self, _current_time: Duration, is_active: bool) {
+ let width = self.border.width();
+ self.border.update(
+ (width, width).into(),
+ self.window.size(),
+ self.window.has_ssd(),
+ );
+ self.border.set_active(is_active);
+
+ // FIXME: remove when we can get a fullscreen size right away.
+ if self.fullscreen_size != Size::from((0, 0)) {
+ self.is_fullscreen = self.window.is_fullscreen();
+ }
+ }
+
+ pub fn window(&self) -> &W {
+ &self.window
+ }
+
+ pub fn into_window(self) -> W {
+ self.window
+ }
+
+ /// Returns `None` if the border is hidden and `Some(width)` if it should be shown.
+ fn effective_border_width(&self) -> Option<i32> {
+ if self.is_fullscreen {
+ return None;
+ }
+
+ if self.border.is_off() {
+ return None;
+ }
+
+ Some(self.border.width())
+ }
+
+ /// Returns the location of the window's visual geometry within this Tile.
+ fn window_loc(&self) -> Point<i32, Logical> {
+ let mut loc = Point::from((0, 0));
+
+ // In fullscreen, center the window in the given size.
+ if self.is_fullscreen {
+ let window_size = self.window.size();
+ let target_size = self.fullscreen_size;
+
+ // Windows aren't supposed to be larger than the fullscreen size, but in case we get
+ // one, leave it at the top-left as usual.
+ if window_size.w < target_size.w {
+ loc.x += (target_size.w - window_size.w) / 2;
+ }
+ if window_size.h < target_size.h {
+ loc.y += (target_size.h - window_size.h) / 2;
+ }
+ }
+
+ if let Some(width) = self.effective_border_width() {
+ loc += (width, width).into();
+ }
+
+ loc
+ }
+
+ pub fn tile_size(&self) -> Size<i32, Logical> {
+ let mut size = self.window.size();
+
+ if self.is_fullscreen {
+ // Normally we'd just return the fullscreen size here, but this makes things a bit
+ // nicer if a fullscreen window is bigger than the fullscreen size for some reason.
+ size.w = max(size.w, self.fullscreen_size.w);
+ size.h = max(size.h, self.fullscreen_size.h);
+ return size;
+ }
+
+ if let Some(width) = self.effective_border_width() {
+ size += (width * 2, width * 2).into();
+ }
+
+ size
+ }
+
+ pub fn window_size(&self) -> Size<i32, Logical> {
+ self.window.size()
+ }
+
+ pub fn buf_loc(&self) -> Point<i32, Logical> {
+ let mut loc = Point::from((0, 0));
+ loc += self.window_loc();
+ loc += self.window.buf_loc();
+ loc
+ }
+
+ pub fn is_in_input_region(&self, mut point: Point<f64, Logical>) -> bool {
+ point -= self.window_loc().to_f64();
+ self.window.is_in_input_region(point)
+ }
+
+ pub fn request_tile_size(&mut self, mut size: Size<i32, Logical>) {
+ // Can't go through effective_border_width() because we might be fullscreen.
+ if !self.border.is_off() {
+ let width = self.border.width();
+ size.w = max(1, size.w - width * 2);
+ size.h = max(1, size.h - width * 2);
+ }
+
+ self.window.request_size(size);
+ }
+
+ pub fn tile_width_for_window_width(&self, size: i32) -> i32 {
+ if self.border.is_off() {
+ size
+ } else {
+ size + self.border.width() * 2
+ }
+ }
+
+ pub fn tile_height_for_window_height(&self, size: i32) -> i32 {
+ if self.border.is_off() {
+ size
+ } else {
+ size + self.border.width() * 2
+ }
+ }
+
+ pub fn window_height_for_tile_height(&self, size: i32) -> i32 {
+ if self.border.is_off() {
+ size
+ } else {
+ size - self.border.width() * 2
+ }
+ }
+
+ pub fn request_fullscreen(&mut self, size: Size<i32, Logical>) {
+ self.fullscreen_backdrop.resize(size);
+ self.fullscreen_size = size;
+ self.window.request_fullscreen(size);
+ }
+
+ pub fn min_size(&self) -> Size<i32, Logical> {
+ let mut size = self.window.min_size();
+
+ if let Some(width) = self.effective_border_width() {
+ size.w = max(1, size.w);
+ size.h = max(1, size.h);
+ size += (width * 2, width * 2).into();
+ }
+
+ size
+ }
+
+ pub fn max_size(&self) -> Size<i32, Logical> {
+ let mut size = self.window.max_size();
+
+ if let Some(width) = self.effective_border_width() {
+ if size.w > 0 {
+ size.w += width * 2;
+ }
+ if size.h > 0 {
+ size.h += width * 2;
+ }
+ }
+
+ size
+ }
+
+ pub fn has_ssd(&self) -> bool {
+ self.effective_border_width().is_some() || self.window.has_ssd()
+ }
+
+ pub fn render<R: Renderer + ImportAll>(
+ &self,
+ renderer: &mut R,
+ location: Point<i32, Logical>,
+ scale: Scale<f64>,
+ ) -> Vec<WorkspaceRenderElement<R>>
+ where
+ <R as Renderer>::TextureId: 'static,
+ {
+ let mut rv = Vec::new();
+
+ let window_pos = location + self.window_loc();
+ rv.extend(self.window.render(renderer, window_pos, scale));
+
+ if self.effective_border_width().is_some() {
+ rv.extend(
+ self.border
+ .render(scale)
+ .map(|elem| {
+ RelocateRenderElement::from_element(
+ elem,
+ location.to_physical_precise_round(scale),
+ Relocate::Relative,
+ )
+ })
+ .map(Into::into),
+ );
+ }
+
+ if self.is_fullscreen {
+ let elem = SolidColorRenderElement::from_buffer(
+ &self.fullscreen_backdrop,
+ location.to_physical_precise_round(scale),
+ scale,
+ 1.,
+ Kind::Unspecified,
+ );
+ rv.push(elem.into());
+ }
+
+ rv
+ }
+}