1use crate::pane::PaneId;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum SplitDirection {
8 Horizontal,
10 Vertical,
12}
13
14#[derive(Debug, Clone)]
16pub enum LayoutNode {
17 Leaf(PaneId),
18 Split {
19 direction: SplitDirection,
20 ratio: f32,
21 first: Box<LayoutNode>,
22 second: Box<LayoutNode>,
23 },
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct PanePosition {
29 pub col: usize,
30 pub row: usize,
31 pub cols: usize,
32 pub rows: usize,
33}
34
35impl LayoutNode {
36 pub fn pane_ids(&self) -> Vec<PaneId> {
38 match self {
39 LayoutNode::Leaf(id) => vec![*id],
40 LayoutNode::Split { first, second, .. } => {
41 let mut ids = first.pane_ids();
42 ids.extend(second.pane_ids());
43 ids
44 }
45 }
46 }
47
48 pub fn count(&self) -> usize {
50 match self {
51 LayoutNode::Leaf(_) => 1,
52 LayoutNode::Split { first, second, .. } => first.count() + second.count(),
53 }
54 }
55
56 fn split(&mut self, target: PaneId, new_pane: PaneId, direction: SplitDirection) -> bool {
58 match self {
59 LayoutNode::Leaf(id) if *id == target => {
60 let old = LayoutNode::Leaf(target);
61 let new = LayoutNode::Leaf(new_pane);
62 *self = LayoutNode::Split {
63 direction,
64 ratio: 0.5,
65 first: Box::new(old),
66 second: Box::new(new),
67 };
68 true
69 }
70 LayoutNode::Leaf(_) => false,
71 LayoutNode::Split { first, second, .. } => {
72 first.split(target, new_pane, direction)
73 || second.split(target, new_pane, direction)
74 }
75 }
76 }
77
78 fn remove(&mut self, target: PaneId) -> RemoveResult {
80 match self {
81 LayoutNode::Leaf(id) if *id == target => RemoveResult::Removed,
82 LayoutNode::Leaf(_) => RemoveResult::NotFound,
83 LayoutNode::Split { first, second, .. } => {
84 let first_result = first.remove(target);
85 match first_result {
86 RemoveResult::Removed => {
87 RemoveResult::Replaced(second.as_ref().clone())
89 }
90 RemoveResult::Replaced(replacement) => {
91 **first = replacement;
92 RemoveResult::NotFound }
94 RemoveResult::NotFound => {
95 let second_result = second.remove(target);
96 match second_result {
97 RemoveResult::Removed => RemoveResult::Replaced(first.as_ref().clone()),
98 RemoveResult::Replaced(replacement) => {
99 **second = replacement;
100 RemoveResult::NotFound }
102 RemoveResult::NotFound => RemoveResult::NotFound,
103 }
104 }
105 }
106 }
107 }
108 }
109
110 fn compute_positions_inner(
112 &self,
113 col: usize,
114 row: usize,
115 cols: usize,
116 rows: usize,
117 out: &mut Vec<(PaneId, PanePosition)>,
118 ) {
119 match self {
120 LayoutNode::Leaf(id) => {
121 out.push((
122 *id,
123 PanePosition {
124 col,
125 row,
126 cols,
127 rows,
128 },
129 ));
130 }
131 LayoutNode::Split {
132 direction,
133 ratio,
134 first,
135 second,
136 } => match direction {
137 SplitDirection::Horizontal => {
138 let first_rows = ((rows as f32) * ratio).round() as usize;
139 let first_rows = first_rows.max(1).min(rows.saturating_sub(1));
140 let second_rows = rows - first_rows;
141 first.compute_positions_inner(col, row, cols, first_rows, out);
142 second.compute_positions_inner(col, row + first_rows, cols, second_rows, out);
143 }
144 SplitDirection::Vertical => {
145 let first_cols = ((cols as f32) * ratio).round() as usize;
146 let first_cols = first_cols.max(1).min(cols.saturating_sub(1));
147 let second_cols = cols - first_cols;
148 first.compute_positions_inner(col, row, first_cols, rows, out);
149 second.compute_positions_inner(col + first_cols, row, second_cols, rows, out);
150 }
151 },
152 }
153 }
154
155 fn adjust_ratio(&mut self, target: PaneId, axis: SplitDirection, delta: f32) -> bool {
158 match self {
159 LayoutNode::Leaf(_) => false,
160 LayoutNode::Split {
161 direction,
162 ratio,
163 first,
164 second,
165 } => {
166 let in_first = first.pane_ids().contains(&target);
167 let in_second = second.pane_ids().contains(&target);
168 if !in_first && !in_second {
169 return false;
170 }
171 if *direction == axis {
173 if in_first {
174 let new_ratio = (*ratio + delta).clamp(0.1, 0.9);
176 *ratio = new_ratio;
177 return true;
178 } else {
179 let new_ratio = (*ratio - delta).clamp(0.1, 0.9);
181 *ratio = new_ratio;
182 return true;
183 }
184 }
185 if in_first {
187 first.adjust_ratio(target, axis, delta)
188 } else {
189 second.adjust_ratio(target, axis, delta)
190 }
191 }
192 }
193 }
194
195 pub fn swap_leaves(&mut self, a: PaneId, b: PaneId) -> bool {
197 let sentinel = u32::MAX;
199 if !self.replace_leaf(a, sentinel) {
200 return false;
201 }
202 if !self.replace_leaf(b, a) {
203 self.replace_leaf(sentinel, a);
205 return false;
206 }
207 self.replace_leaf(sentinel, b);
208 true
209 }
210
211 pub fn replace_leaf(&mut self, old_id: PaneId, new_id: PaneId) -> bool {
213 match self {
214 LayoutNode::Leaf(id) if *id == old_id => {
215 *id = new_id;
216 true
217 }
218 LayoutNode::Leaf(_) => false,
219 LayoutNode::Split { first, second, .. } => {
220 first.replace_leaf(old_id, new_id) || second.replace_leaf(old_id, new_id)
221 }
222 }
223 }
224
225 pub fn find_position(
227 &self,
228 target: PaneId,
229 col: usize,
230 row: usize,
231 cols: usize,
232 rows: usize,
233 ) -> Option<PanePosition> {
234 let mut positions = Vec::new();
235 self.compute_positions_inner(col, row, cols, rows, &mut positions);
236 positions
237 .into_iter()
238 .find(|(id, _)| *id == target)
239 .map(|(_, pos)| pos)
240 }
241}
242
243enum RemoveResult {
244 NotFound,
245 Removed,
246 Replaced(LayoutNode),
247}
248
249#[derive(Debug)]
251pub struct LayoutEngine {
252 root: Option<LayoutNode>,
253}
254
255impl LayoutEngine {
256 pub fn new() -> Self {
258 Self { root: None }
259 }
260
261 pub fn add_pane(&mut self, pane_id: PaneId) {
264 match &self.root {
265 None => {
266 self.root = Some(LayoutNode::Leaf(pane_id));
267 }
268 Some(_) => {
269 let ids = self.pane_ids();
271 if let Some(&last) = ids.last() {
272 self.split(last, pane_id, SplitDirection::Vertical);
273 }
274 }
275 }
276 }
277
278 pub fn split(
280 &mut self,
281 target_pane_id: PaneId,
282 new_pane_id: PaneId,
283 direction: SplitDirection,
284 ) -> bool {
285 if let Some(ref mut root) = self.root {
286 root.split(target_pane_id, new_pane_id, direction)
287 } else {
288 false
289 }
290 }
291
292 pub fn remove_pane(&mut self, pane_id: PaneId) -> bool {
294 if let Some(ref mut root) = self.root {
295 match root.remove(pane_id) {
296 RemoveResult::Removed => {
297 self.root = None;
298 true
299 }
300 RemoveResult::Replaced(new_root) => {
301 self.root = Some(new_root);
302 true
303 }
304 RemoveResult::NotFound => {
305 !self.pane_ids().contains(&pane_id)
308 }
309 }
310 } else {
311 false
312 }
313 }
314
315 pub fn pane_ids(&self) -> Vec<PaneId> {
317 match &self.root {
318 Some(root) => root.pane_ids(),
319 None => Vec::new(),
320 }
321 }
322
323 pub fn count(&self) -> usize {
325 match &self.root {
326 Some(root) => root.count(),
327 None => 0,
328 }
329 }
330
331 pub fn compute_positions(
333 &self,
334 total_cols: usize,
335 total_rows: usize,
336 ) -> Vec<(PaneId, PanePosition)> {
337 let mut out = Vec::new();
338 if let Some(ref root) = self.root {
339 root.compute_positions_inner(0, 0, total_cols, total_rows, &mut out);
340 }
341 out
342 }
343
344 pub fn adjust_ratio(&mut self, target: PaneId, axis: SplitDirection, delta: f32) -> bool {
346 if let Some(ref mut root) = self.root {
347 root.adjust_ratio(target, axis, delta)
348 } else {
349 false
350 }
351 }
352
353 pub fn swap_leaves(&mut self, a: PaneId, b: PaneId) -> bool {
355 if let Some(ref mut root) = self.root {
356 root.swap_leaves(a, b)
357 } else {
358 false
359 }
360 }
361
362 pub fn root(&self) -> Option<&LayoutNode> {
364 self.root.as_ref()
365 }
366
367 pub fn set_root(&mut self, root: LayoutNode) {
370 self.root = Some(root);
371 }
372
373 pub fn apply_template(&mut self, template: &LayoutNode, pane_ids: &[PaneId]) {
379 if pane_ids.is_empty() {
380 return;
381 }
382 let mut new_tree = template.clone();
383 let template_ids = new_tree.pane_ids();
384 for (i, &real_id) in pane_ids.iter().enumerate() {
386 if i < template_ids.len() {
387 new_tree.replace_leaf(template_ids[i], real_id);
388 }
389 }
390 if pane_ids.len() > template_ids.len() {
392 let mut last_id = pane_ids[template_ids.len() - 1];
393 for &extra_id in &pane_ids[template_ids.len()..] {
394 new_tree.split(last_id, extra_id, SplitDirection::Vertical);
395 last_id = extra_id;
396 }
397 }
398 if template_ids.len() > pane_ids.len() {
400 for &extra_template_id in &template_ids[pane_ids.len()..] {
401 new_tree.remove(extra_template_id);
402 }
403 }
404 self.root = Some(new_tree);
405 }
406}
407
408impl Default for LayoutEngine {
409 fn default() -> Self {
410 Self::new()
411 }
412}