use ratatui::layout::Rect;
use super::{Server, Tab};
use crate::layout::{Node, PaneId};
pub(super) struct Tiling {
root: Node,
area: Rect,
frames: bool,
tiles: Vec<(PaneId, Rect, bool)>,
}
impl Server {
fn main_area(&self, cols: u16, rows: u16, sidebar: bool) -> Rect {
let top = 1;
let height = rows.saturating_sub(top + self.bottom_rows()).max(1);
let left = if sidebar { self.sidebar_width(cols) } else { 0 };
let left = if left > 0 { left + 1 } else { 0 };
Rect::new(left, top, cols.saturating_sub(left).max(1), height)
}
pub fn bottom_rows(&self) -> u16 {
u16::from(self.bottom_bar)
}
pub fn screen_above_bar(&self, cols: u16, rows: u16) -> Rect {
Rect::new(0, 0, cols, rows.saturating_sub(self.bottom_rows()))
}
pub(super) fn client_screen(&self, id: super::ClientId) -> Rect {
let (cols, rows) = self.clients.get(&id).map_or((80, 24), |c| (c.cols, c.rows));
self.screen_above_bar(cols, rows)
}
pub fn sidebar_width(&self, cols: u16) -> u16 {
let w = self.config.sidebar.width;
if cols < w * 3 { 0 } else { w }
}
pub fn sidebar_shown(&self, c: &super::Client) -> bool {
c.sidebar.unwrap_or_else(|| self.config.sidebar.visible && self.sidebar_useful())
}
pub(super) fn sidebar_useful(&self) -> bool {
self.workspaces.len() > 1
|| !self.def_files.is_empty()
|| !self.tunnels.is_empty()
|| !self.ports.is_empty()
|| self.panes.values().any(|p| p.agent.kind.is_some())
|| self.remote_agents().next().is_some()
}
pub fn tab_rects(&self, tab: &Tab, area: Rect) -> Vec<(PaneId, Rect, bool)> {
let mut out = Vec::new();
if let Some(z) = tab.zoom.filter(|z| !tab.is_floating(*z)) {
out.push((z, self.tile(area), false));
} else if let Some(root) = &tab.root {
out.extend(
self.tiled_rects(root, area).into_iter().filter(|(.., folded)| !folded).map(|(p, r, _)| (p, r, false)),
);
}
if tab.show_floating {
for f in &tab.floating {
out.push((f.pane, f.rect.to_rect(area), true));
}
}
out
}
pub fn stack_rows(&self, tab: &Tab, area: Rect) -> Vec<(PaneId, Rect)> {
match &tab.root {
Some(root) if tab.zoom.is_none_or(|z| tab.is_floating(z)) => self
.tiled_rects(root, area)
.into_iter()
.filter(|(.., folded)| *folded)
.map(|(p, r, _)| (p, r))
.collect(),
_ => Vec::new(),
}
}
fn tiled_rects(&self, root: &Node, area: Rect) -> Vec<(PaneId, Rect, bool)> {
let frames = self.config.pane_frames;
let mut known = self.tilings.borrow_mut();
if let Some(i) = known.iter().position(|t| t.area == area && t.frames == frames && t.root == *root) {
let hit = known.remove(i);
let tiles = hit.tiles.clone();
known.push(hit);
return tiles;
}
let tiles = root.tiles(area, frames);
if known.len() >= 8 {
known.remove(0);
}
known.push(Tiling { root: root.clone(), area, frames, tiles: tiles.clone() });
tiles
}
fn tile(&self, outer: Rect) -> Rect {
if self.config.pane_frames { crate::layout::framed(outer, self.layout_area) } else { outer }
}
pub fn inner(&self, frame: Rect) -> Rect {
if self.config.pane_frames && frame.width >= 4 && frame.height >= 3 {
Rect::new(frame.x + 1, frame.y + 1, frame.width - 2, frame.height - 2)
} else {
frame
}
}
pub fn placed(&self, frame: Rect, screen: Rect) -> (Rect, Rect) {
let frame = frame.intersection(screen);
(frame, self.inner(frame))
}
pub fn relayout(&mut self) {
if let Some(c) = self.primary.and_then(|p| self.clients.get(&p)) {
self.layout_area = self.main_area(c.cols, c.rows, self.sidebar_shown(c));
}
let area = self.layout_area;
let mut sizes = Vec::new();
for ws in &self.workspaces {
for tab in &ws.tabs {
let mut tiled = std::collections::HashMap::new();
if let Some(root) = &tab.root {
let frames: std::collections::HashMap<PaneId, Rect> = self
.tiled_rects(root, area)
.into_iter()
.filter(|(.., folded)| !folded)
.map(|(p, r, _)| (p, r))
.collect();
for id in root.leaves() {
if let Some(r) = frames.get(&root.holder(id)) {
tiled.insert(id, *r);
}
}
}
if let Some(z) = tab.zoom {
tiled.insert(z, self.tile(area));
}
for (id, r) in tiled {
sizes.push((id, self.inner(r)));
}
for f in &tab.floating {
sizes.push((f.pane, self.inner(f.rect.to_rect(area))));
}
}
}
for (id, r) in sizes {
if let Some(p) = self.panes.get_mut(&id) {
p.resize(r.width, r.height);
}
}
for c in self.clients.values_mut() {
c.dirty = true;
}
self.schedule_render();
}
pub fn ws_index(&self, id: u32) -> Option<usize> {
self.workspaces.iter().position(|w| w.id == id)
}
pub fn workspace_of(&self, pane: PaneId) -> Option<(usize, usize)> {
for (wi, ws) in self.workspaces.iter().enumerate() {
for (ti, tab) in ws.tabs.iter().enumerate() {
if tab.contains(pane) {
return Some((wi, ti));
}
}
}
None
}
}