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::{
center_preferring_top_left_in_area, clamp_preferring_top_left_in_area, 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;
// Make sure the window doesn't go too much off-screen. Numbers taken from Mutter.
let min_on_screen_hor = f64::clamp(self.size.w / 4., 10., 75.);
let min_on_screen_ver = f64::clamp(self.size.h / 4., 10., 75.);
let max_off_screen_hor = f64::max(0., self.size.w - min_on_screen_hor);
let max_off_screen_ver = f64::max(0., self.size.h - min_on_screen_ver);
logical_pos.x = f64::max(logical_pos.x, -max_off_screen_hor);
logical_pos.y = f64::max(logical_pos.y, -max_off_screen_ver);
logical_pos.x = f64::min(
logical_pos.x,
self.working_area.size.w - self.size.w + max_off_screen_hor,
);
logical_pos.y = f64::min(
logical_pos.y,
self.working_area.size.h - self.size.