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, Rectangle, 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 update_window(&mut self) { // 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 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); } 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. pub 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.w = size.w.saturating_add(width * 2); size.h = size.h.saturating_add(width * 2); } 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 is_in_activation_region(&self, point: Point) -> bool { let activation_region = Rectangle::from_loc_and_size((0, 0), self.tile_size()); activation_region.to_f64().contains(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.saturating_add(self.border.width() * 2) } } pub fn tile_height_for_window_height(&self, size: i32) -> i32 { if self.border.is_off() { size } else { size.saturating_add(self.border.width() * 2) } } pub fn window_height_for_tile_height(&self, size: i32) -> i32 { if self.border.is_off() { size } else { size.saturating_sub(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.w = size.w.saturating_add(width * 2); size.h = size.h.saturating_add(width * 2); } 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 = size.w.saturating_add(width * 2); } if size.h > 0 { size.h = size.h.saturating_add(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 } }