/
dgrigorev
/
veda
Обзор
Документация
Войти
/
dgrigorev
/
veda
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/ui/split.rs
222 строки
9 KB
Дмитрий Григорьев
Добавлен обратный вывод
03 июн 2026, 09:26
03 июн 2026, 09:26
fffae5e
Код
Авторство
О чём код?
// ============================================================================ // ui/split.rs — древовидное разбиение области окна на панели. // --------------------------------------------------------------------------- // Назначение модуля: // * описать бинарное дерево сплитов: листья — панели (терминалы), // внутренние узлы — разделители (горизонтальные/вертикальные); // * по размеру окна рассчитать пиксельные прямоугольники всех листьев // с учётом коэффициента деления и минимальных размеров. // // Деление «горизонтальное» = разделитель идёт горизонтально, панели одна над // другой (делим по высоте). «Вертикальное» = панели бок о бок (делим по ширине). // ============================================================================ /// Прямоугольник области панели в пикселях (левый верхний угол + размеры). #[derive(Debug, Clone, Copy)] pub struct Rect { pub x: f32, pub y: f32, pub w: f32, pub h: f32, } /// Направление разделителя. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SplitDir { /// Панели бок о бок (делим ширину). Vertical, /// Панели одна над другой (делим высоту). Horizontal, } /// Узел дерева сплитов. pub enum SplitNode { /// Лист: идентификатор панели (индекс терминала в WindowManager). Leaf(usize), /// Внутренний узел: направление, доля левой/верхней части [0..1], потомки. Node { dir: SplitDir, ratio: f32, first: Box<SplitNode>, second: Box<SplitNode>, }, } /// Минимальный размер панели в пикселях (чтобы сплиты не схлопывались). const MIN_PANE: f32 = 40.0; /// Толщина видимого разделителя между панелями. pub const SPLIT_GUTTER: f32 = 2.0; impl SplitNode { /// Рассчитать раскладку только для видимых листьев. Если одна сторона /// сплита скрыта, вторая занимает всю область узла. pub fn layout_visible(&self, area: Rect, hidden: &[usize], out: &mut Vec<(usize, Rect)>) { match self { SplitNode::Leaf(id) => { if !hidden.contains(id) { out.push((*id, area)); } } SplitNode::Node { dir, ratio, first, second, } => { let first_visible = first.has_visible_leaf(hidden); let second_visible = second.has_visible_leaf(hidden); match (first_visible, second_visible) { (true, true) => { let r = ratio.clamp(0.05, 0.95); let (a, b) = split_area(area, *dir, r); first.layout_visible(a, hidden, out); second.layout_visible(b, hidden, out); } (true, false) => first.layout_visible(area, hidden, out), (false, true) => second.layout_visible(area, hidden, out), (false, false) => {} } } } } pub fn has_visible_leaf(&self, hidden: &[usize]) -> bool { match self { SplitNode::Leaf(id) => !hidden.contains(id), SplitNode::Node { first, second, .. } => { first.has_visible_leaf(hidden) || second.has_visible_leaf(hidden) } } } pub fn first_visible_leaf(&self, hidden: &[usize]) -> Option<usize> { match self { SplitNode::Leaf(id) => (!hidden.contains(id)).then_some(*id), SplitNode::Node { first, second, .. } => first .first_visible_leaf(hidden) .or_else(|| second.first_visible_leaf(hidden)), } } pub fn contains_leaf(&self, target: usize) -> bool { match self { SplitNode::Leaf(id) => *id == target, SplitNode::Node { first, second, .. } => { first.contains_leaf(target) || second.contains_leaf(target) } } } /// Удалить лист из дерева. Если удаляется одна сторона сплита, вторая /// поднимается на её место. Возвращает true, если лист найден. pub fn remove_leaf(&mut self, target: usize) -> bool { match self { SplitNode::Leaf(_) => false, SplitNode::Node { first, second, .. } => { if matches!(first.as_ref(), SplitNode::Leaf(id) if *id == target) { let replacement = std::mem::replace(second, Box::new(SplitNode::Leaf(target))); *self = *replacement; return true; } if matches!(second.as_ref(), SplitNode::Leaf(id) if *id == target) { let replacement = std::mem::replace(first, Box::new(SplitNode::Leaf(target))); *self = *replacement; return true; } first.remove_leaf(target) || second.remove_leaf(target) } } } /// Разделить лист с заданным pane_id, добавив новую панель new_id. /// Возвращает true, если лист найден и разделён. pub fn split_leaf(&mut self, target: usize, new_id: usize, dir: SplitDir) -> bool { match self { SplitNode::Leaf(id) if *id == target => { let old = *id; *self = SplitNode::Node { dir, ratio: 0.5, first: Box::new(SplitNode::Leaf(old)), second: Box::new(SplitNode::Leaf(new_id)), }; true } SplitNode::Leaf(_) => false, SplitNode::Node { first, second, .. } => { first.split_leaf(target, new_id, dir) || second.split_leaf(target, new_id, dir) } } } /// Собрать идентификаторы всех листьев (панелей) в дереве. #[allow(dead_code)] pub fn collect_leaves(&self, out: &mut Vec<usize>) { match self { SplitNode::Leaf(id) => out.push(*id), SplitNode::Node { first, second, .. } => { first.collect_leaves(out); second.collect_leaves(out); } } } /// Пересчитать идентификаторы панелей в дереве согласно отображению old→new. /// Если id отсутствует в отображении, он остаётся без изменений. pub fn remap_ids(&mut self, mapping: &[Option<usize>]) { match self { SplitNode::Leaf(id) => { if let Some(Some(new_id)) = mapping.get(*id) { *id = *new_id; } } SplitNode::Node { first, second, .. } => { first.remap_ids(mapping); second.remap_ids(mapping); } } } } fn split_area(area: Rect, dir: SplitDir, ratio: f32) -> (Rect, Rect) { match dir { SplitDir::Vertical => { let usable = (area.w - SPLIT_GUTTER).max(MIN_PANE * 2.0); let w1 = ((usable * ratio).max(MIN_PANE)).min(usable - MIN_PANE); let w2 = usable - w1; ( Rect { x: area.x, y: area.y, w: w1, h: area.h, }, Rect { x: area.x + w1 + SPLIT_GUTTER, y: area.y, w: w2, h: area.h, }, ) } SplitDir::Horizontal => { let usable = (area.h - SPLIT_GUTTER).max(MIN_PANE * 2.0); let h1 = ((usable * ratio).max(MIN_PANE)).min(usable - MIN_PANE); let h2 = usable - h1; ( Rect { x: area.x, y: area.y, w: area.w, h: h1, }, Rect { x: area.x, y: area.y + h1 + SPLIT_GUTTER, w: area.w, h: h2, }, ) } } }