use std::cmp::max;
use std::iter::zip;
use std::rc::Rc;
use niri_ipc::SizeChange;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size};
use super::closing_window::{ClosingWindow, ClosingWindowRenderElement};
use super::scrolling::ColumnWidth;
use super::tile::{Tile, TileRenderElement, TileRenderSnapshot};
use super::workspace::InteractiveResize;
use super::{ConfigureIntent, InteractiveResizeData, LayoutElement, Options, RemovedTile};
use crate::animation::{Animation, Clock};
use crate::niri_render_elements;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::RenderTarget;
use crate::utils::transaction::TransactionBlocker;
use crate::utils::{ensure_min_max_size, ResizeEdge};
use crate::window::ResolvedWindowRules;
/// Space for floating windows.
#[derive(Debug)]
pub struct FloatingSpace<W: LayoutElement> {
/// Tiles in top-to-bottom order.
tiles: Vec<Tile<W>>,
/// Extra per-tile data.
data: Vec<Data>,
/// Id of the active window.
///
/// The active window is not necessarily the topmost window. Focus-follows-mouse should
/// activate a window, but not bring it to the top, because that's very annoying.
///
/// This is always set to `Some()` when `tiles` isn't empty.
active_window_id: Option<W::Id>,
/// Ongoing interactive resize.
interactive_resize: Option<InteractiveResize<W>>,
/// Windows in the closing animation.
closing_windows: Vec<ClosingWindow>,
/// Working area for this space.
working_area: Rectangle<f64, Logical>,
/// Scale of the output the space is on (and rounds its sizes to).
scale: f64,
/// Clock for driving animations.
clock: Clock,
/// Configurable properties of the layout.
options: Rc<Options>,
}
niri_render_elements! {
FloatingSpaceRenderElement<R> => {
Tile = TileRenderElement<R>,
ClosingWindow = ClosingWindowRenderElement,
}
}
/// Size-relative units.
struct SizeFrac;
/// Extra per-tile data.
#[derive(Debug, Clone, Copy, PartialEq)]
struct Data {
/// Position relative to the working area.
pos: Point<f64, SizeFrac>,
/// Cached position in logical coordinates.
///
/// Not rounded to physical pixels.
logical_pos: Point<f64, Logical>,
/// Cached actual size of the tile.
size: Size<f64, Logical>,
/// Working area used for conversions.
working_area: Rectangle<f64, Logical>,
}
impl Data {
pub fn new<W: LayoutElement>(
working_area: Rectangle<f64, Logical>,
tile: &Tile<W>,
logical_pos: Point<f64, Logical>,
) -> Self {
let mut rv = Self {
pos: Point::default(),
logical_pos: Point::default(),
size: Size::default(),
working_area,
};
rv.update(tile);
rv.set_logical_pos(logical_pos);
rv
}
fn recompute_logical_pos(&mut self) {
let mut logical_pos = Point::from((self.pos.x, self.pos.y));
logical_pos.x *= self.working_area.size.w;
logical_pos.y *= self.working_area.size.h;
logical_pos += self.working_area.loc;
self.logical_pos = logical_pos;
}
pub fn update_config(&mut self, working_area: Rectangle<f64, Logical>) {
if self.working_area == working_area {
return;
}
self.working_area = working_area;
self.recompute_logical_pos();
}
pub fn update<W: LayoutElement>(&mut self, tile: &Tile<W>) {
self.size = tile.tile_size();
}
pub fn set_logical_pos(&mut self, logical_pos: Point<f64, Logical>) {
let pos = logical_pos - self.working_area.loc;
let mut pos = Point::from((pos.x, pos.y));
pos.x /= f64::max(self.working_area.size.w, 1.0);
pos.y /= f64::max(self.working_area.size.h, 1.0);
self.pos = pos;
// This should get close to the same result as what we started with.
self.recompute_logical_pos();
}
#[cfg(test)]
fn verify_invariants(&self) {
let mut temp = *self;
temp.recompute_logical_pos();
assert_eq!(
self.logical_pos, temp.logical_pos,
"cached logical pos must be up to date"
);
}
}
impl<W: LayoutElement> FloatingSpace<W> {
pub fn new(
working_area: Rectangle<f64, Logical>,
scale: f64,
clock: Clock,
options: Rc<Options>,
) -> Self {
Self {
tiles: Vec