diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2023-12-27 21:51:42 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2023-12-27 21:51:42 +0400 |
| commit | c21805bf705bd36a6eb7f79039b759e9af79dfcb (patch) | |
| tree | e24213b023c8129320daa782c8b0830ea334d519 /src/layout/tile.rs | |
| parent | bfc24182670a0b3e17f79d66474fd291b7110732 (diff) | |
| download | niri-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.rs | 267 |
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 + } +} |
