From c21805bf705bd36a6eb7f79039b759e9af79dfcb Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Wed, 27 Dec 2023 21:51:42 +0400 Subject: layout: Refactor to support window decorations, add border and fullscreen backdrop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- src/layout/tile.rs | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 src/layout/tile.rs (limited to 'src/layout/tile.rs') 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 { + /// 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, + + /// Configurable properties of the layout. + options: Rc, +} + +impl Tile { + pub fn new(window: W, options: Rc) -> 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) { + 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 { + 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 { + 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 { + 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 { + self.window.size() + } + + pub fn buf_loc(&self) -> Point { + 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) -> bool { + point -= self.window_loc().to_f64(); + self.window.is_in_input_region(point) + } + + pub fn request_tile_size(&mut self, mut size: Size) { + // 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) { + self.fullscreen_backdrop.resize(size); + self.fullscreen_size = size; + self.window.request_fullscreen(size); + } + + pub fn min_size(&self) -> Size { + 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 { + 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( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + ) -> Vec> + where + ::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 + } +} -- cgit