1use std::cmp::Ordering;
9use std::convert::TryInto;
10use std::marker::PhantomData;
11use std::ops::Not;
12
13use serde::{Deserialize, Serialize};
14
15use ratatui::{
16 buffer::Buffer,
17 layout::Rect,
18 style::Style,
19 widgets::{Block, BorderType, Borders, StatefulWidget, Widget},
20};
21
22use crate::util::{rect_down, rect_right, rect_zero_height, rect_zero_width};
23use crate::{TermOffset, TerminalCursor, Window, WindowOps};
24
25use super::size::{ResizeInfo, ResizeInfoTrail, MIN_WIN_LEN};
26use super::slot::WindowSlot;
27use super::tree::{AxisTreeIterMut, SubtreeOps, TreeOps};
28use super::{
29 winnr_cmp,
30 AxisT,
31 AxisTree,
32 AxisTreeNode,
33 HorizontalT,
34 TreeInfo,
35 Value,
36 VerticalT,
37 WindowActions,
38 WindowInfo,
39};
40
41use modalkit::actions::{Jumpable, WindowAction, WindowContainer, WindowCount};
42use modalkit::errors::{EditError, EditResult, UIError, UIResult};
43use modalkit::prelude::Axis::{Horizontal, Vertical};
44use modalkit::prelude::MoveDir2D::{Down, Left, Right, Up};
45use modalkit::prelude::*;
46use modalkit::ui::idx_offset;
47
48use modalkit::editing::{
49 application::ApplicationInfo,
50 context::{EditContext, Resolve},
51 store::Store,
52};
53
54fn windex(count: &Count, ctx: &EditContext) -> usize {
55 ctx.resolve(count).saturating_sub(1)
56}
57
58fn slopped_length(base: u16, count: u16, slop: &mut u16) -> u16 {
59 let slopped = count.min(*slop);
60 let length = base * count + slopped;
61
62 *slop -= slopped;
63
64 return length;
65}
66
67fn set_area_lens<W, X, Y>(node: &mut AxisTreeNode<W, X, Y>, area: Rect, info: &ResizeInfo)
68where
69 X: AxisT,
70 Y: AxisT,
71{
72 let Some(ref lengths) = info.lengths else {
73 return;
74 };
75 assert_eq!(lengths.len(), node.weight());
76
77 fn winlen(sd: &super::size::SizeDescription, winrem: u16, lenrem: u16) -> u16 {
78 if winrem == 0 {
79 return lenrem;
81 }
82
83 lenrem.saturating_sub(winrem * MIN_WIN_LEN).min(sd.length)
85 }
86
87 match X::axis() {
88 Axis::Horizontal => {
89 let mut carea = rect_zero_height(area);
90 let mut iter = lengths.iter();
91 let mut rem = area.height;
92
93 let mut f = |value: &mut Value<W, X, Y>| {
94 let size = iter.next().unwrap();
95 let height = winlen(size, iter.as_slice().len() as u16, rem);
96
97 carea = rect_down(carea, height);
98 rem -= height;
99
100 value.set_area(carea, info);
101 };
102
103 node.for_each_value(&mut f);
104 },
105 Axis::Vertical => {
106 let mut carea = rect_zero_width(area);
107 let mut iter = lengths.iter();
108 let mut rem = area.width;
109
110 let mut f = |value: &mut Value<W, X, Y>| {
111 let size = iter.next().unwrap();
112 let width = winlen(size, iter.as_slice().len() as u16, rem);
113
114 carea = rect_right(carea, width);
115 rem -= width;
116
117 value.set_area(carea, info);
118 };
119
120 node.for_each_value(&mut f);
121 },
122 }
123}
124
125fn set_area_equal<W, X, Y>(node: &mut AxisTreeNode<W, X, Y>, area: Rect, info: &ResizeInfo)
126where
127 X: AxisT,
128 Y: AxisT,
129{
130 let (lw, lh) = node.dimensions();
131 let (lw, lh) = (lw as u16, lh as u16);
132
133 match X::axis() {
134 Axis::Horizontal => {
135 let height = area.height / lh;
136
137 let mut slop = area.height % lh;
138 let mut carea = rect_zero_height(area);
139
140 let mut f = |value: &mut Value<W, X, Y>| {
141 let height = slopped_length(height, value.height() as u16, &mut slop);
142
143 carea = rect_down(carea, height);
144
145 value.set_area(carea, info);
146 };
147
148 node.for_each_value(&mut f);
149 },
150 Axis::Vertical => {
151 let width = area.width / lw;
152
153 let mut slop = area.width % lw;
154 let mut carea = rect_zero_width(area);
155
156 let mut f = |value: &mut Value<W, X, Y>| {
157 let width = slopped_length(width, value.width() as u16, &mut slop);
158
159 carea = rect_right(carea, width);
160
161 value.set_area(carea, info);
162 };
163
164 node.for_each_value(&mut f);
165 },
166 }
167}
168
169enum WrappedIterMut<'a, W> {
170 Horizontal(AxisTreeIterMut<'a, W, HorizontalT, VerticalT>),
171 Vertical(AxisTreeIterMut<'a, W, VerticalT, HorizontalT>),
172}
173
174struct SlotIter<'a, W> {
175 stack: Vec<WrappedIterMut<'a, WindowSlot<W>>>,
176}
177
178impl<'a, W> Iterator for SlotIter<'a, W> {
179 type Item = &'a mut WindowSlot<W>;
180
181 fn next(&mut self) -> Option<Self::Item> {
182 loop {
183 let last = self.stack.last_mut()?;
184
185 match last {
186 WrappedIterMut::Horizontal(node) => {
187 match node.next() {
188 None => {
189 let _ = self.stack.pop();
190 },
191 Some(Value::Window(w, _)) => {
192 return Some(w);
193 },
194 Some(Value::Tree(t, _)) => {
195 let iter = WrappedIterMut::Vertical(t.iter_mut());
196 self.stack.push(iter);
197 continue;
198 },
199 }
200 },
201 WrappedIterMut::Vertical(node) => {
202 match node.next() {
203 None => {
204 let _ = self.stack.pop();
205 },
206 Some(Value::Window(w, _)) => {
207 return Some(w);
208 },
209 Some(Value::Tree(t, _)) => {
210 let iter = WrappedIterMut::Horizontal(t.iter_mut());
211 self.stack.push(iter);
212 continue;
213 },
214 }
215 },
216 }
217 }
218 }
219}
220
221pub(super) trait LayoutOps<W, X, Y>
226where
227 X: AxisT,
228 Y: AxisT,
229{
230 fn size(&self) -> usize;
232
233 fn dimensions(&self) -> (usize, usize);
235
236 fn width(&self) -> usize {
238 self.dimensions().0
239 }
240
241 fn height(&self) -> usize {
243 self.dimensions().1
244 }
245
246 fn close(&mut self, at: usize, trail: Box<ResizeInfoTrail<'_, X, Y>>) -> Option<W>;
248
249 fn collapse(self, at: usize) -> Option<W>;
251
252 fn get_area(&self, at: usize) -> Option<(&W, Rect)>;
254
255 fn get(&self, at: usize) -> Option<&W> {
257 self.get_area(at).map(|(w, _)| w)
258 }
259
260 fn get_mut(&mut self, at: usize) -> Option<&mut W>;
262
263 fn swap(&mut self, a: usize, b: usize);
265
266 fn open(
269 &mut self,
270 at: usize,
271 open: W,
272 length: Option<u16>,
273 rel: MoveDir1D,
274 split_axis: Axis,
275 trail: Box<ResizeInfoTrail<'_, X, Y>>,
276 ) -> usize;
277
278 fn clear_sizes(&mut self);
280
281 fn freeze(&mut self, axis: Axis);
283
284 fn resize(
286 &mut self,
287 at: usize,
288 axis: Axis,
289 change: SizeChange<u16>,
290 trail: Box<ResizeInfoTrail<'_, X, Y>>,
291 );
292
293 fn set_area(&mut self, area: Rect, info: &ResizeInfo);
295
296 fn _neighbor_walk(
297 &self,
298 base: usize,
299 current: (usize, usize),
300 c: TermOffset,
301 dir: MoveDir2D,
302 ) -> (usize, usize);
303
304 fn _neighbor_of(
305 &self,
306 base: usize,
307 at: usize,
308 c: TermOffset,
309 dir: MoveDir2D,
310 count: usize,
311 ) -> Option<(usize, usize)>;
312
313 fn neighbor(&self, at: usize, dir: MoveDir2D, count: usize) -> Option<usize>
316 where
317 W: TerminalCursor;
318}
319
320impl<W, X, Y> LayoutOps<W, X, Y> for Value<W, X, Y>
321where
322 X: AxisT,
323 Y: AxisT,
324{
325 fn close(&mut self, idx: usize, trail: Box<ResizeInfoTrail<'_, X, Y>>) -> Option<W> {
326 match self {
327 Value::Window(_, _) => panic!("cannot remove element from non-tree"),
328 Value::Tree(tree, ref mut info) => {
329 let trail = ResizeInfoTrail::new(idx, &mut info.resized, Some(trail));
330
331 tree.close(idx, trail)
332 },
333 }
334 }
335
336 fn collapse(self, at: usize) -> Option<W> {
337 match self {
338 Value::Window(w, _) => Some(w),
339 Value::Tree(tree, _) => tree.collapse(at),
340 }
341 }
342
343 fn get_area(&self, at: usize) -> Option<(&W, Rect)> {
344 match self {
345 Value::Window(ref window, info) => {
346 if at == 0 {
347 Some((window, info.area))
348 } else {
349 None
350 }
351 },
352 Value::Tree(ref tree, _) => {
353 return tree.get_area(at);
354 },
355 }
356 }
357
358 fn get_mut(&mut self, at: usize) -> Option<&mut W> {
359 match self {
360 Value::Window(ref mut window, _) => {
361 if at == 0 {
362 Some(window)
363 } else {
364 None
365 }
366 },
367 Value::Tree(ref mut tree, _) => {
368 return tree.get_mut(at);
369 },
370 }
371 }
372
373 fn dimensions(&self) -> (usize, usize) {
374 match self {
375 Value::Window(_, _) => (1, 1),
376 Value::Tree(tree, _) => tree.dimensions(),
377 }
378 }
379
380 fn size(&self) -> usize {
381 match self {
382 Value::Window(_, _) => 1,
383 Value::Tree(tree, _) => tree.size(),
384 }
385 }
386
387 fn swap(&mut self, a: usize, b: usize) {
388 match self {
389 Value::Window(_, _) => (),
390 Value::Tree(tree, _) => tree.swap(a, b),
391 }
392 }
393
394 fn clear_sizes(&mut self) {
395 match self {
396 Value::Window(_, _) => {
397 return;
398 },
399 Value::Tree(tree, ref mut info) => {
400 info.resized.lengths = None;
401
402 tree.clear_sizes();
403 },
404 }
405 }
406
407 fn freeze(&mut self, axis: Axis) {
408 match self {
409 Value::Window(_, _) => {
410 return;
411 },
412 Value::Tree(tree, info) => {
413 if axis == Y::axis() {
414 info.resized.lengths = Some(tree.get_lengths());
415 }
416
417 tree.freeze(axis);
418 },
419 }
420 }
421
422 fn resize(
423 &mut self,
424 at: usize,
425 axis: Axis,
426 change: SizeChange<u16>,
427 mut trail: Box<ResizeInfoTrail<'_, X, Y>>,
428 ) {
429 match self {
430 Value::Window(_, info) => {
431 let len = match change {
432 SizeChange::Equal => {
433 trail.clear(axis);
434 return;
435 },
436 SizeChange::Exact(amt) => amt.max(MIN_WIN_LEN),
437 SizeChange::Decrease(amt) => info.get_length(axis).saturating_sub(amt),
438 SizeChange::Increase(amt) => info.get_length(axis).saturating_add(amt),
439 };
440
441 trail.set_size(axis, len);
442 },
443 Value::Tree(tree, ref mut info) => {
444 info.resized.lengths = Some(tree.get_lengths());
445
446 let trail = ResizeInfoTrail::new(at, &mut info.resized, Some(trail));
447
448 tree.resize(at, axis, change, trail);
449 },
450 }
451 }
452
453 fn set_area(&mut self, area: Rect, _: &ResizeInfo) {
454 match self {
455 Value::Window(_, ref mut info) => {
456 info.area = area;
457 },
458 Value::Tree(tree, ref mut info) => {
459 info.area = area;
460 tree.set_area(area, &info.resized);
461 },
462 }
463 }
464
465 fn open(
466 &mut self,
467 at: usize,
468 open: W,
469 length: Option<u16>,
470 rel: MoveDir1D,
471 split_axis: Axis,
472 trail: Box<ResizeInfoTrail<'_, X, Y>>,
473 ) -> usize {
474 match self {
475 Value::Window(_, info) => {
476 let mut info = info.to_tree(length.map(|_| split_axis));
478 let mut trail = ResizeInfoTrail::new(at, &mut info.resized, Some(trail));
479 trail.split_or_clear(rel, split_axis, length);
480
481 let tree = AxisTreeNode::singleton(open);
483 let mut node = Value::Tree(Box::new(tree), info);
484 std::mem::swap(self, &mut node);
485
486 match (self, node, rel) {
487 (Value::Tree(t, _), Value::Window(w, _), MoveDir1D::Previous) => {
488 t.insert_max(w);
489 0
490 },
491 (Value::Tree(t, _), Value::Window(w, _), MoveDir1D::Next) => {
492 t.insert_min(w);
493 1
494 },
495 (_, _, _) => {
496 panic!("split: invalid state");
497 },
498 }
499 },
500 Value::Tree(tree, ref mut info) => {
501 let trail = ResizeInfoTrail::new(at, &mut info.resized, Some(trail));
502
503 tree.open(at, open, length, rel, split_axis, trail)
504 },
505 }
506 }
507
508 fn _neighbor_walk(
509 &self,
510 base: usize,
511 current: (usize, usize),
512 c: TermOffset,
513 dir: MoveDir2D,
514 ) -> (usize, usize) {
515 if current.1 == 0 {
516 return current;
517 }
518
519 match self {
520 Value::Window(_, _) => (base, current.1 - 1),
521 Value::Tree(tree, _) => tree._neighbor_walk(base, current, c, dir),
522 }
523 }
524
525 fn _neighbor_of(
526 &self,
527 base: usize,
528 at: usize,
529 c: TermOffset,
530 dir: MoveDir2D,
531 count: usize,
532 ) -> Option<(usize, usize)> {
533 match self {
534 Value::Window(_, _) => Some((base, count)),
535 Value::Tree(tree, _) => tree._neighbor_of(base, at, c, dir, count),
536 }
537 }
538
539 fn neighbor(&self, _: usize, _: MoveDir2D, _: usize) -> Option<usize>
540 where
541 W: TerminalCursor,
542 {
543 unreachable!();
544 }
545}
546
547type HorizontalTree<W> = AxisTree<W, HorizontalT, VerticalT>;
548
549impl<W, X, Y> LayoutOps<W, X, Y> for AxisTreeNode<W, X, Y>
550where
551 X: AxisT,
552 Y: AxisT,
553{
554 fn size(&self) -> usize {
555 self.info.size
556 }
557
558 fn dimensions(&self) -> (usize, usize) {
559 self.info.dimensions
560 }
561
562 fn close(&mut self, at: usize, mut trail: Box<ResizeInfoTrail<'_, X, Y>>) -> Option<W> {
563 let lsize = self.left.size();
564 let vsize = self.value.size();
565
566 match winnr_cmp(at, lsize, vsize) {
567 (Ordering::Less, idx) => {
568 let ret = self.left.close(idx, trail);
569 self.balance_left();
570 return ret;
571 },
572 (Ordering::Equal, idx) => {
573 if vsize == 1 {
574 trail.clear(X::axis());
575
576 return self.remove_root().collapse(0);
577 } else {
578 let ret = self.value.close(idx, trail);
579 self._update_info();
580 return ret;
581 }
582 },
583 (Ordering::Greater, idx) => {
584 let ret = self.right.close(idx, trail);
585 self.balance_right();
586 return ret;
587 },
588 }
589 }
590
591 fn collapse(self, at: usize) -> Option<W> {
592 let lsize = self.left.size();
593 let vsize = self.value.size();
594
595 match winnr_cmp(at, lsize, vsize) {
596 (Ordering::Less, idx) => self.left.collapse(idx),
597 (Ordering::Equal, idx) => self.value.collapse(idx),
598 (Ordering::Greater, idx) => self.right.collapse(idx),
599 }
600 }
601
602 fn get_area(&self, at: usize) -> Option<(&W, Rect)> {
603 let lsize = self.left.size();
604 let vsize = self.value.size();
605
606 match winnr_cmp(at, lsize, vsize) {
607 (Ordering::Less, idx) => self.left.get_area(idx),
608 (Ordering::Equal, idx) => self.value.get_area(idx),
609 (Ordering::Greater, idx) => self.right.get_area(idx),
610 }
611 }
612
613 fn get_mut(&mut self, at: usize) -> Option<&mut W> {
614 let lsize = self.left.size();
615 let vsize = self.value.size();
616
617 match winnr_cmp(at, lsize, vsize) {
618 (Ordering::Less, idx) => self.left.get_mut(idx),
619 (Ordering::Equal, idx) => self.value.get_mut(idx),
620 (Ordering::Greater, idx) => self.right.get_mut(idx),
621 }
622 }
623
624 fn open(
625 &mut self,
626 at: usize,
627 open: W,
628 length: Option<u16>,
629 rel: MoveDir1D,
630 split_axis: Axis,
631 mut trail: Box<ResizeInfoTrail<'_, X, Y>>,
632 ) -> usize {
633 let lsize = self.left.size();
634 let vsize = self.value.size();
635
636 match winnr_cmp(at, lsize, vsize) {
637 (Ordering::Less, idx) => {
638 let winnr = self.left.open(idx, open, length, rel, split_axis, trail);
639 self.balance_right();
640 winnr
641 },
642 (Ordering::Equal, idx) => {
643 let split_here = vsize == 1 && split_axis == X::axis();
644
645 if split_here {
646 trail.split_or_clear(rel, split_axis, length);
647
648 match rel {
649 MoveDir1D::Previous => {
650 let winnr = self.left.insert_max(open);
651 self.balance_right();
652 winnr
653 },
654 MoveDir1D::Next => {
655 let winnr = self.right.insert_min(open);
656 let winnr = lsize + vsize + winnr;
657 self.balance_left();
658 winnr
659 },
660 }
661 } else {
662 let winnr = self.value.open(idx, open, length, rel, split_axis, trail);
663 let winnr = lsize + winnr;
664 self._update_info();
665 winnr
666 }
667 },
668 (Ordering::Greater, idx) => {
669 let winnr = self.right.open(idx, open, length, rel, split_axis, trail);
670 let winnr = lsize + vsize + winnr;
671 self.balance_left();
672 winnr
673 },
674 }
675 }
676
677 fn swap(&mut self, a: usize, b: usize) {
683 if a == b {
684 return;
688 }
689
690 let lsize = self.left.size();
691 let vsize = self.value.size();
692
693 let ap = winnr_cmp(a, lsize, vsize);
694 let bp = winnr_cmp(b, lsize, vsize);
695
696 let (amut, bmut) = match (ap, bp) {
697 ((Ordering::Less, aidx), (Ordering::Less, bidx)) => {
698 self.left.swap(aidx, bidx);
699 return;
700 },
701 ((Ordering::Less, aidx), (Ordering::Equal, bidx)) => {
702 (self.left.get_mut(aidx), self.value.get_mut(bidx))
703 },
704 ((Ordering::Less, aidx), (Ordering::Greater, bidx)) => {
705 (self.left.get_mut(aidx), self.right.get_mut(bidx))
706 },
707 ((Ordering::Equal, aidx), (Ordering::Less, bidx)) => {
708 (self.value.get_mut(aidx), self.left.get_mut(bidx))
709 },
710 ((Ordering::Equal, aidx), (Ordering::Equal, bidx)) => {
711 self.value.swap(aidx, bidx);
712 return;
713 },
714 ((Ordering::Equal, aidx), (Ordering::Greater, bidx)) => {
715 (self.value.get_mut(aidx), self.right.get_mut(bidx))
716 },
717 ((Ordering::Greater, aidx), (Ordering::Less, bidx)) => {
718 (self.right.get_mut(aidx), self.left.get_mut(bidx))
719 },
720 ((Ordering::Greater, aidx), (Ordering::Equal, bidx)) => {
721 (self.right.get_mut(aidx), self.value.get_mut(bidx))
722 },
723 ((Ordering::Greater, aidx), (Ordering::Greater, bidx)) => {
724 self.right.swap(aidx, bidx);
725 return;
726 },
727 };
728
729 if let (Some(awin), Some(bwin)) = (amut, bmut) {
730 std::mem::swap(awin, bwin);
734 }
735 }
736
737 fn clear_sizes(&mut self) {
738 self.left.clear_sizes();
739 self.value.clear_sizes();
740 self.right.clear_sizes();
741 }
742
743 fn freeze(&mut self, axis: Axis) {
744 self.left.freeze(axis);
745 self.value.freeze(axis);
746 self.right.freeze(axis);
747 }
748
749 fn resize(
750 &mut self,
751 at: usize,
752 axis: Axis,
753 change: SizeChange<u16>,
754 trail: Box<ResizeInfoTrail<'_, X, Y>>,
755 ) {
756 let lsize = self.left.size();
757 let vsize = self.value.size();
758
759 match winnr_cmp(at, lsize, vsize) {
760 (Ordering::Less, idx) => {
761 self.left.resize(idx, axis, change, trail);
762 },
763 (Ordering::Equal, idx) => {
764 self.value.resize(idx, axis, change, trail);
765 },
766 (Ordering::Greater, idx) => {
767 self.right.resize(idx, axis, change, trail);
768 },
769 }
770 }
771
772 fn set_area(&mut self, area: Rect, info: &ResizeInfo) {
773 if info.lengths.is_some() {
774 set_area_lens(self, area, info);
775 } else {
776 set_area_equal(self, area, info);
777 }
778 }
779
780 fn _neighbor_walk(
781 &self,
782 base: usize,
783 current: (usize, usize),
784 c: TermOffset,
785 dir: MoveDir2D,
786 ) -> (usize, usize) {
787 if current.1 == 0 {
788 return current;
792 }
793
794 let lsize = self.left.size();
795 let vsize = self.value.size();
796
797 let lbase = base;
798 let vbase = lbase + lsize;
799 let rbase = vbase + vsize;
800
801 match (X::axis(), dir) {
802 (Vertical, Left) | (Horizontal, Up) => {
803 let current = self.right._neighbor_walk(rbase, current, c, dir);
804 let current = self.value._neighbor_walk(vbase, current, c, dir);
805 return self.left._neighbor_walk(lbase, current, c, dir);
806 },
807 (Vertical, Right) | (Horizontal, Down) => {
808 let current = self.left._neighbor_walk(lbase, current, c, dir);
809 let current = self.value._neighbor_walk(vbase, current, c, dir);
810 return self.right._neighbor_walk(rbase, current, c, dir);
811 },
812 (Vertical, Up | Down) => {
813 let mut off = base;
818 let col = c.0;
819
820 for value in self.iter() {
821 let area = value.area();
822 let lc = area.left();
823 let rc = area.right();
824
825 if col >= lc && col < rc {
826 return value._neighbor_walk(off, current, c, dir);
827 }
828
829 off += value.size();
830 }
831
832 self.value._neighbor_walk(base, current, c, dir)
834 },
835 (Horizontal, Left | Right) => {
836 let mut off = base;
842 let row = c.1;
843
844 for value in self.iter() {
845 let area = value.area();
846 let tr = area.top();
847 let br = area.bottom();
848
849 if row >= tr && row < br {
850 return value._neighbor_walk(off, current, c, dir);
851 }
852
853 off += value.size();
854 }
855
856 self.value._neighbor_walk(base, current, c, dir)
858 },
859 }
860 }
861
862 fn _neighbor_of(
863 &self,
864 base: usize,
865 at: usize,
866 c: TermOffset,
867 dir: MoveDir2D,
868 count: usize,
869 ) -> Option<(usize, usize)> {
870 let lsize = self.left.size();
871 let vsize = self.value.size();
872
873 let branch = winnr_cmp(at, lsize, vsize);
874
875 let lbase = base;
876 let vbase = lbase + lsize;
877 let rbase = vbase + vsize;
878
879 match (X::axis(), dir) {
880 (Vertical, Left) | (Horizontal, Up) => {
881 match branch {
882 (Ordering::Less, idx) => {
883 return self.left._neighbor_of(lbase, idx, c, dir, count);
884 },
885 (Ordering::Equal, idx) => {
886 let current = self.value._neighbor_of(vbase, idx, c, dir, count)?;
887 let current = self.left._neighbor_walk(lbase, current, c, dir);
888
889 return Some(current);
890 },
891 (Ordering::Greater, idx) => {
892 let current = self.right._neighbor_of(rbase, idx, c, dir, count)?;
893 let current = self.value._neighbor_walk(vbase, current, c, dir);
894 let current = self.left._neighbor_walk(lbase, current, c, dir);
895
896 return Some(current);
897 },
898 }
899 },
900 (Vertical, Right) | (Horizontal, Down) => {
901 match branch {
902 (Ordering::Less, idx) => {
903 let current = self.left._neighbor_of(lbase, idx, c, dir, count)?;
904 let current = self.value._neighbor_walk(vbase, current, c, dir);
905 let current = self.right._neighbor_walk(rbase, current, c, dir);
906
907 return Some(current);
908 },
909 (Ordering::Equal, idx) => {
910 let current = self.value._neighbor_of(vbase, idx, c, dir, count)?;
911 let current = self.right._neighbor_walk(rbase, current, c, dir);
912
913 return Some(current);
914 },
915 (Ordering::Greater, idx) => {
916 return self.right._neighbor_of(rbase, idx, c, dir, count);
917 },
918 }
919 },
920 (Vertical, Up | Down) | (Horizontal, Left | Right) => {
921 match branch {
922 (Ordering::Less, idx) => {
923 return self.left._neighbor_of(lbase, idx, c, dir, count);
924 },
925 (Ordering::Equal, idx) => {
926 return self.value._neighbor_of(vbase, idx, c, dir, count);
927 },
928 (Ordering::Greater, idx) => {
929 return self.right._neighbor_of(rbase, idx, c, dir, count);
930 },
931 }
932 },
933 }
934 }
935
936 fn neighbor(&self, at: usize, dir: MoveDir2D, count: usize) -> Option<usize>
937 where
938 W: TerminalCursor,
939 {
940 let (w, r) = self.get_area(at)?;
941 let c = w.get_term_cursor().unwrap_or((r.x, r.y));
942 self._neighbor_of(0, at, c, dir, count).map(|current| current.0)
943 }
944}
945
946impl<W, X, Y> LayoutOps<W, X, Y> for AxisTree<W, X, Y>
947where
948 X: AxisT,
949 Y: AxisT,
950{
951 fn size(&self) -> usize {
952 match self {
953 None => 0,
954 Some(node) => node.size(),
955 }
956 }
957
958 fn dimensions(&self) -> (usize, usize) {
959 match self {
960 None => (0, 0),
961 Some(node) => node.dimensions(),
962 }
963 }
964
965 fn close(&mut self, at: usize, mut trail: Box<ResizeInfoTrail<'_, X, Y>>) -> Option<W> {
966 if let Some(node) = self {
967 let lsize = node.left.size();
968 let vsize = node.value.size();
969
970 match winnr_cmp(at, lsize, vsize) {
971 (Ordering::Less, idx) => {
972 let ret = node.left.close(idx, trail);
973 node.balance_left();
974 return ret;
975 },
976 (Ordering::Equal, idx) => {
977 if vsize == 1 {
978 trail.clear(X::axis());
979
980 return self.remove_root().collapse(0);
981 } else {
982 let ret = node.value.close(idx, trail);
983 node._update_info();
984 return ret;
985 }
986 },
987 (Ordering::Greater, idx) => {
988 let ret = node.right.close(idx, trail);
989 node.balance_right();
990 return ret;
991 },
992 }
993 } else {
994 return None;
995 }
996 }
997
998 fn collapse(self, at: usize) -> Option<W> {
999 self.and_then(|node| node.collapse(at))
1000 }
1001
1002 fn get_area(&self, at: usize) -> Option<(&W, Rect)> {
1003 self.as_ref().and_then(|node| node.get_area(at))
1004 }
1005
1006 fn get_mut(&mut self, at: usize) -> Option<&mut W> {
1007 self.as_mut().and_then(|node| node.get_mut(at))
1008 }
1009
1010 fn swap(&mut self, a: usize, b: usize) {
1011 if let Some(node) = self {
1012 node.swap(a, b);
1013 }
1014 }
1015
1016 fn open(
1017 &mut self,
1018 at: usize,
1019 open: W,
1020 length: Option<u16>,
1021 rel: MoveDir1D,
1022 split_axis: Axis,
1023 trail: Box<ResizeInfoTrail<'_, X, Y>>,
1024 ) -> usize {
1025 match self {
1026 None => {
1027 *self = AxisTree::from(if split_axis == X::axis() {
1028 Value::from(open)
1029 } else {
1030 Value::from(AxisTreeNode::singleton(open))
1031 });
1032
1033 return 0;
1034 },
1035 Some(node) => {
1036 return node.open(at, open, length, rel, split_axis, trail);
1037 },
1038 }
1039 }
1040
1041 fn _neighbor_walk(
1042 &self,
1043 base: usize,
1044 current: (usize, usize),
1045 c: TermOffset,
1046 dir: MoveDir2D,
1047 ) -> (usize, usize) {
1048 match self {
1049 None => current,
1050 Some(node) => node._neighbor_walk(base, current, c, dir),
1051 }
1052 }
1053
1054 fn _neighbor_of(
1055 &self,
1056 base: usize,
1057 at: usize,
1058 c: TermOffset,
1059 dir: MoveDir2D,
1060 count: usize,
1061 ) -> Option<(usize, usize)> {
1062 self.as_ref().and_then(|node| node._neighbor_of(base, at, c, dir, count))
1063 }
1064
1065 fn clear_sizes(&mut self) {
1066 if let Some(node) = self {
1067 node.clear_sizes();
1068 }
1069 }
1070
1071 fn freeze(&mut self, axis: Axis) {
1072 if let Some(node) = self {
1073 node.freeze(axis);
1074 }
1075 }
1076
1077 fn resize(
1078 &mut self,
1079 at: usize,
1080 axis: Axis,
1081 change: SizeChange<u16>,
1082 trail: Box<ResizeInfoTrail<'_, X, Y>>,
1083 ) {
1084 if let Some(node) = self {
1085 node.resize(at, axis, change, trail);
1086 }
1087 }
1088
1089 fn set_area(&mut self, area: Rect, info: &ResizeInfo) {
1090 if let Some(node) = self {
1091 node.set_area(area, info);
1092 }
1093 }
1094
1095 fn neighbor(&self, at: usize, dir: MoveDir2D, count: usize) -> Option<usize>
1096 where
1097 W: TerminalCursor,
1098 {
1099 self.as_ref().and_then(|tree| tree.neighbor(at, dir, count))
1100 }
1101}
1102
1103#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1105#[serde(bound(deserialize = "I::WindowId: Deserialize<'de>"))]
1106#[serde(bound(serialize = "I::WindowId: Serialize"))]
1107#[serde(rename_all = "lowercase")]
1108pub struct WindowLayoutRoot<I: ApplicationInfo> {
1109 layout: WindowLayoutDescription<I>,
1110 focused: usize,
1111 zoomed: bool,
1112}
1113
1114impl<I> WindowLayoutRoot<I>
1115where
1116 I: ApplicationInfo,
1117{
1118 pub fn to_layout<W: Window<I>>(
1120 self,
1121 area: Option<Rect>,
1122 store: &mut Store<I>,
1123 ) -> UIResult<WindowLayoutState<W, I>, I> {
1124 let mut layout = self.layout.to_layout(area, store)?;
1125 layout._focus(self.focused);
1126 layout.zoom = self.zoomed;
1127 Ok(layout)
1128 }
1129}
1130
1131#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1133#[serde(bound(deserialize = "I::WindowId: Deserialize<'de>"))]
1134#[serde(bound(serialize = "I::WindowId: Serialize"))]
1135#[serde(rename_all = "lowercase", tag = "type")]
1136pub enum WindowLayoutDescription<I: ApplicationInfo> {
1137 Window {
1139 window: I::WindowId,
1141
1142 length: Option<u16>,
1144 },
1145
1146 Split {
1148 children: Vec<Self>,
1150
1151 length: Option<u16>,
1153 },
1154}
1155
1156impl<I, W, X, Y> From<&Value<W, X, Y>> for WindowLayoutDescription<I>
1157where
1158 I: ApplicationInfo,
1159 W: Window<I>,
1160 X: AxisT,
1161 Y: AxisT,
1162{
1163 fn from(node: &Value<W, X, Y>) -> Self {
1164 match node {
1165 Value::Window(window, info) => {
1166 let length = if X::axis() == Axis::Horizontal {
1167 info.area.height.into()
1168 } else {
1169 info.area.width.into()
1170 };
1171
1172 WindowLayoutDescription::Window { window: window.id(), length }
1173 },
1174 Value::Tree(tree, info) => {
1175 let length = if X::axis() == Axis::Horizontal {
1176 info.area.height.into()
1177 } else {
1178 info.area.width.into()
1179 };
1180
1181 WindowLayoutDescription::Split {
1182 children: tree.iter().map(Self::from).collect(),
1183 length,
1184 }
1185 },
1186 }
1187 }
1188}
1189
1190impl<I> WindowLayoutDescription<I>
1191where
1192 I: ApplicationInfo,
1193{
1194 fn into_value<W, X, Y>(self, store: &mut Store<I>) -> UIResult<Value<W, X, Y>, I>
1195 where
1196 W: Window<I>,
1197 X: AxisT,
1198 Y: AxisT,
1199 {
1200 match self {
1201 WindowLayoutDescription::Window { window, .. } => {
1202 let w = W::open(window, store)?;
1203 let v = Value::Window(w, WindowInfo::default());
1204
1205 Ok(v)
1206 },
1207 WindowLayoutDescription::Split { children, .. } => {
1208 let mut layout: AxisTree<W, Y, X> = None;
1209 let sizes = children
1210 .iter()
1211 .map(|v| {
1212 match v {
1213 Self::Window { length, .. } | Self::Split { length, .. } => *length,
1214 }
1215 })
1216 .collect::<Vec<_>>();
1217
1218 for child in children {
1219 let value = child.into_value(store)?;
1220 layout.insert_max_value(value);
1221 }
1222
1223 let Some(layout) = layout else {
1224 let msg = "Cannot open empty split";
1225 let err = UIError::Failure(msg.to_string());
1226
1227 return Err(err);
1228 };
1229
1230 let lengths = if sizes.iter().any(Option::is_some) {
1231 let mut l = layout.get_lengths();
1232
1233 for (sd, length) in l.iter_mut().zip(sizes.into_iter()) {
1234 if let Some(length) = length {
1235 sd.length = length;
1236 }
1237 }
1238
1239 Some(l)
1240 } else {
1241 None
1242 };
1243
1244 let resized = ResizeInfo { lengths };
1245 let info = TreeInfo { area: Rect::default(), resized };
1246 Ok(Value::Tree(layout, info))
1247 },
1248 }
1249 }
1250
1251 pub fn to_layout<W: Window<I>>(
1253 self,
1254 area: Option<Rect>,
1255 store: &mut Store<I>,
1256 ) -> UIResult<WindowLayoutState<W, I>, I> {
1257 let mut layout = WindowLayoutState::empty();
1258
1259 match self.into_value(store)? {
1260 Value::Window(w, _) => {
1261 layout.root.insert_min(w);
1262 },
1263 Value::Tree(tree, info) => {
1264 layout.root = Some(tree);
1265 layout.info = info;
1266 },
1267 }
1268
1269 if let Some(area) = area {
1270 layout.root.set_area(area, &layout.info.resized);
1271 layout.info.area = area;
1272 }
1273
1274 return Ok(layout);
1275 }
1276}
1277
1278pub struct WindowLayoutState<W: Window<I>, I: ApplicationInfo> {
1280 root: HorizontalTree<WindowSlot<W>>,
1281 info: TreeInfo,
1282 zoom: bool,
1283 focused: usize,
1284 focused_last: usize,
1285 _p: PhantomData<I>,
1286}
1287
1288impl<W, I> WindowLayoutState<W, I>
1289where
1290 W: Window<I>,
1291 I: ApplicationInfo,
1292{
1293 pub fn new(window: W) -> Self {
1295 WindowLayoutState::from_slot(window.into())
1296 }
1297
1298 pub fn empty() -> Self {
1300 WindowLayoutState {
1301 root: None,
1302 info: TreeInfo::default(),
1303 zoom: false,
1304 focused: 0,
1305 focused_last: 0,
1306 _p: PhantomData,
1307 }
1308 }
1309
1310 pub fn as_description(&self) -> WindowLayoutRoot<I> {
1312 let mut children = vec![];
1313 let focused = self.focused;
1314 let zoomed = self.zoom;
1315
1316 let Some(root) = &self.root else {
1317 return WindowLayoutRoot {
1318 layout: WindowLayoutDescription::Split { children, length: None },
1319 focused,
1320 zoomed,
1321 };
1322 };
1323
1324 for w in root.iter() {
1325 children.push(w.into());
1326 }
1327
1328 return WindowLayoutRoot {
1329 layout: WindowLayoutDescription::Split { children, length: None },
1330 focused,
1331 zoomed,
1332 };
1333 }
1334
1335 pub fn from_target(
1337 &self,
1338 target: &OpenTarget<I::WindowId>,
1339 ctx: &EditContext,
1340 store: &mut Store<I>,
1341 ) -> UIResult<Self, I> {
1342 let w = self._open(target, ctx, store)?;
1343 let layout = WindowLayoutState::new(w);
1344
1345 Ok(layout)
1346 }
1347
1348 fn from_slot(slot: WindowSlot<W>) -> Self {
1349 WindowLayoutState {
1350 root: AxisTree::singleton(slot),
1351 info: TreeInfo::default(),
1352 zoom: false,
1353 focused: 0,
1354 focused_last: 0,
1355 _p: PhantomData,
1356 }
1357 }
1358
1359 fn slots(&mut self) -> SlotIter<'_, W> {
1361 let iter = SubtreeOps::iter_mut(&mut self.root);
1362 let stack = vec![WrappedIterMut::Horizontal(iter)];
1363
1364 SlotIter { stack }
1365 }
1366
1367 pub fn get(&self) -> Option<&W> {
1369 self.get_slot().map(WindowSlot::get)
1370 }
1371
1372 pub fn get_mut(&mut self) -> Option<&mut W> {
1374 self.get_slot_mut().map(WindowSlot::get_mut)
1375 }
1376
1377 fn get_slot(&self) -> Option<&WindowSlot<W>> {
1379 self.root.get(self.focused)
1380 }
1381
1382 fn get_slot_mut(&mut self) -> Option<&mut WindowSlot<W>> {
1384 self.root.get_mut(self.focused)
1385 }
1386
1387 pub fn extract(&mut self) -> Self {
1389 let at = self.focused;
1390 let trail = ResizeInfoTrail::new(at, &mut self.info.resized, None);
1391
1392 if let Some(slot) = self.root.close(at, trail) {
1393 self._clamp_focus();
1394
1395 return WindowLayoutState::from_slot(slot);
1396 } else {
1397 return WindowLayoutState::empty();
1398 }
1399 }
1400
1401 fn move_side(&mut self, at: usize, dir: MoveDir2D) {
1402 let trail = ResizeInfoTrail::new(at, &mut self.info.resized, None);
1403
1404 if let Some(window) = self.root.close(at, trail) {
1405 let nr = self.root.insert_side(window, dir);
1406 self._focus(nr);
1407 }
1408 }
1409
1410 fn close(&mut self, at: usize, _flags: CloseFlags) -> bool {
1411 let trail = ResizeInfoTrail::new(at, &mut self.info.resized, None);
1412
1413 self.root.close(at, trail);
1414 self._clamp_focus();
1415
1416 return self.root.size() == 0;
1417 }
1418
1419 fn only(&mut self, at: usize, _flags: CloseFlags) -> bool {
1420 let target = if at < self.root.size() {
1421 at
1422 } else {
1423 self.focused
1424 };
1425
1426 self.root = std::mem::take(&mut self.root)
1427 .collapse(target)
1428 .and_then(AxisTree::singleton);
1429 self._clamp_focus();
1430 self.clear_sizes();
1431
1432 return self.root.size() == 0;
1433 }
1434
1435 fn open(&mut self, w: W, length: Option<u16>, axis: Axis, rel: MoveDir1D) {
1436 let windex = self.focused;
1437
1438 if length.is_some() {
1439 self.freeze(axis);
1440 }
1441
1442 let trail = ResizeInfoTrail::new(windex, &mut self.info.resized, None);
1443
1444 let nr = self.root.open(windex, w.into(), length, rel, axis, trail);
1445 self.root.set_area(self.info.area, &self.info.resized);
1446
1447 self._focus(nr);
1448 }
1449
1450 fn clear_sizes(&mut self) {
1451 self.info.resized.clear_sizes();
1452 self.root.clear_sizes();
1453 self.root.set_area(self.info.area, &self.info.resized);
1454 }
1455
1456 fn freeze(&mut self, axis: Axis) {
1457 if axis == Axis::Horizontal {
1458 self.info.resized.lengths = Some(self.root.get_lengths());
1459 }
1460
1461 self.root.freeze(axis);
1462 }
1463
1464 fn resize(&mut self, windex: usize, axis: Axis, change: SizeChange<u16>) {
1465 self.freeze(axis);
1466
1467 let trail = ResizeInfoTrail::new(windex, &mut self.info.resized, None);
1468
1469 self.root.resize(windex, axis, change, trail);
1470 self.root.set_area(self.info.area, &self.info.resized);
1471 }
1472
1473 fn _focus(&mut self, focus: usize) {
1474 let max = self.root.size().saturating_sub(1);
1475
1476 self.focused_last = self.focused;
1477 self.focused = focus.min(max);
1478 }
1479
1480 fn _clamp_focus(&mut self) {
1481 let nwins = self.root.size();
1482 let lastw = nwins.saturating_sub(1);
1483
1484 self.focused_last = self.focused_last.min(lastw);
1485 self.focused = self.focused.min(lastw);
1486 }
1487
1488 fn _max_idx(&self) -> usize {
1489 self.root.size().saturating_sub(1)
1490 }
1491
1492 fn _open(
1493 &self,
1494 target: &OpenTarget<I::WindowId>,
1495 ctx: &EditContext,
1496 store: &mut Store<I>,
1497 ) -> UIResult<W, I> {
1498 match target {
1499 OpenTarget::Alternate => {
1500 let slot = self.get_slot().ok_or(UIError::NoWindow)?;
1501
1502 match slot.get_alt() {
1503 Some(alt) => Ok(alt.dup(store)),
1504 None => Err(UIError::Failure("No alternate window".into())),
1505 }
1506 },
1507 OpenTarget::Application(id) => W::open(id.clone(), store),
1508 OpenTarget::Current => {
1509 let slot = self.get_slot().ok_or(UIError::NoWindow)?;
1510
1511 Ok(slot.get().dup(store))
1512 },
1513 OpenTarget::Cursor(style) => {
1514 let slot = self.get_slot().ok_or(UIError::NoWindow)?;
1515
1516 if let Some(text) = slot.get().get_cursor_word(style) {
1517 W::find(text, store)
1518 } else {
1519 let msg = "No word under cursor".to_string();
1520 let err = UIError::Failure(msg);
1521
1522 Err(err)
1523 }
1524 },
1525 OpenTarget::List(count) => W::posn(ctx.resolve(count), store),
1526 OpenTarget::Name(name) => W::find(name.clone(), store),
1527 OpenTarget::Offset(dir, count) => {
1528 let slot = self.get_slot().ok_or(UIError::NoWindow)?;
1529 let count = ctx.resolve(count);
1530
1531 match slot.get_off(*dir, count) {
1532 Some(w) => Ok(w.dup(store)),
1533 None => Err(UIError::Failure("Not a valid window offset".into())),
1534 }
1535 },
1536 OpenTarget::Selection => {
1537 let slot = self.get_slot().ok_or(UIError::NoWindow)?;
1538
1539 if let Some(text) = slot.get().get_selected_word() {
1540 W::find(text, store)
1541 } else {
1542 let msg = "No text currently selected".to_string();
1543 let err = UIError::Failure(msg);
1544
1545 Err(err)
1546 }
1547 },
1548 OpenTarget::Unnamed => W::unnamed(store),
1549 }
1550 }
1551
1552 fn _target(&self, change: &FocusChange, ctx: &EditContext) -> Option<usize> {
1553 match change {
1554 FocusChange::Current => {
1555 return Some(self.focused);
1556 },
1557 FocusChange::Offset(count, false) => {
1558 let winnr = windex(count, ctx);
1559
1560 if winnr >= self.root.size() {
1561 return None;
1563 }
1564
1565 return Some(winnr);
1566 },
1567 FocusChange::Offset(count, true) => {
1568 let target = windex(count, ctx).min(self._max_idx());
1569
1570 return Some(target);
1571 },
1572 FocusChange::Direction1D(dir, count, wrap) => {
1573 let nwins = self.root.size();
1574 let count = ctx.resolve(count);
1575
1576 return idx_offset(self.focused, count, dir, nwins, *wrap);
1577 },
1578 FocusChange::Direction2D(dir, count) => {
1579 let count = ctx.resolve(count);
1580
1581 return self.root.neighbor(self.focused, *dir, count);
1582 },
1583 FocusChange::Position(MovePosition::Beginning) => {
1584 return Some(0);
1585 },
1586 FocusChange::Position(MovePosition::Middle) => {
1587 return Some(self.root.size() / 2);
1588 },
1589 FocusChange::Position(MovePosition::End) => {
1590 return Some(self.root.size().saturating_sub(1));
1591 },
1592 FocusChange::PreviouslyFocused => {
1593 return Some(self.focused_last);
1594 },
1595 }
1596 }
1597}
1598
1599impl<W, I> WindowActions<EditContext, I> for WindowLayoutState<W, I>
1600where
1601 W: Window<I>,
1602 I: ApplicationInfo,
1603{
1604 fn window_focus(
1605 &mut self,
1606 change: &FocusChange,
1607 ctx: &EditContext,
1608 _: &mut Store<I>,
1609 ) -> EditResult<EditInfo, I> {
1610 if let Some(target) = self._target(change, ctx) {
1611 self.zoom = false;
1612 self._focus(target);
1613 }
1614
1615 return Ok(None);
1616 }
1617
1618 fn window_exchange(
1619 &mut self,
1620 change: &FocusChange,
1621 ctx: &EditContext,
1622 _: &mut Store<I>,
1623 ) -> EditResult<EditInfo, I> {
1624 if let Some(target) = self._target(change, ctx) {
1625 self.zoom = false;
1626 self.root.swap(self.focused, target);
1627 }
1628
1629 return Ok(None);
1630 }
1631
1632 fn window_move_side(
1633 &mut self,
1634 dir: MoveDir2D,
1635 _: &EditContext,
1636 _: &mut Store<I>,
1637 ) -> EditResult<EditInfo, I> {
1638 self.zoom = false;
1639 self.move_side(self.focused, dir);
1640
1641 return Ok(None);
1642 }
1643
1644 fn window_rotate(
1645 &mut self,
1646 _dir: MoveDir1D,
1647 _ctx: &EditContext,
1648 _: &mut Store<I>,
1649 ) -> EditResult<EditInfo, I> {
1650 let msg = "Window rotation is not currently implemented";
1652 let err = EditError::Unimplemented(msg.into());
1653
1654 return Err(err);
1655 }
1656
1657 fn window_split(
1658 &mut self,
1659 target: &OpenTarget<I::WindowId>,
1660 axis: Axis,
1661 rel: MoveDir1D,
1662 count: &Count,
1663 ctx: &EditContext,
1664 store: &mut Store<I>,
1665 ) -> UIResult<EditInfo, I> {
1666 let count = ctx.resolve(count);
1667 let w = self._open(target, ctx, store)?;
1668
1669 self.zoom = false;
1670
1671 for _ in 0..count {
1672 self.open(w.dup(store), None, axis, rel);
1673 }
1674
1675 return Ok(None);
1676 }
1677
1678 fn window_clear_sizes(&mut self, _: &EditContext, _: &mut Store<I>) -> EditResult<EditInfo, I> {
1679 self.clear_sizes();
1680
1681 return Ok(None);
1682 }
1683
1684 fn window_resize(
1685 &mut self,
1686 change: &FocusChange,
1687 axis: Axis,
1688 size: &SizeChange<Count>,
1689 ctx: &EditContext,
1690 _: &mut Store<I>,
1691 ) -> EditResult<EditInfo, I> {
1692 let Some(target) = self._target(change, ctx) else {
1693 return Ok(None);
1694 };
1695
1696 let change: SizeChange<u16> = match &size {
1697 SizeChange::Equal => SizeChange::Equal,
1698 SizeChange::Exact(count) => SizeChange::Exact(ctx.resolve(count).try_into()?),
1699 SizeChange::Increase(count) => SizeChange::Increase(ctx.resolve(count).try_into()?),
1700 SizeChange::Decrease(count) => SizeChange::Decrease(ctx.resolve(count).try_into()?),
1701 };
1702
1703 self.zoom = false;
1704 self.resize(target, axis, change);
1705
1706 return Ok(None);
1707 }
1708
1709 fn window_close(
1710 &mut self,
1711 target: &WindowTarget,
1712 flags: CloseFlags,
1713 ctx: &EditContext,
1714 _: &mut Store<I>,
1715 ) -> EditResult<EditInfo, I> {
1716 let nwins = self.root.size();
1717
1718 if nwins == 1 && !flags.contains(CloseFlags::QUIT) {
1719 return Ok(None);
1723 }
1724
1725 self.zoom = false;
1726
1727 match target {
1728 WindowTarget::Single(focus) => {
1729 if let Some(target) = self._target(focus, ctx) {
1730 self.close(target, flags);
1731 }
1732
1733 return Ok(None);
1734 },
1735 WindowTarget::AllBut(focus) => {
1736 if let Some(target) = self._target(focus, ctx) {
1737 self.only(target, flags);
1738 }
1739
1740 return Ok(None);
1741 },
1742 WindowTarget::All => {
1743 self.root = None;
1744
1745 return Ok(None);
1746 },
1747 }
1748 }
1749
1750 fn window_open(
1751 &mut self,
1752 target: &OpenTarget<I::WindowId>,
1753 axis: Axis,
1754 rel: MoveDir1D,
1755 count: &Count,
1756 ctx: &EditContext,
1757 store: &mut Store<I>,
1758 ) -> UIResult<EditInfo, I> {
1759 let count: u16 = ctx.resolve(count).try_into().map_err(EditError::from)?;
1760 let w = self._open(target, ctx, store)?;
1761
1762 self.open(w, Some(count), axis, rel);
1763
1764 Ok(None)
1765 }
1766
1767 fn window_switch(
1768 &mut self,
1769 target: &OpenTarget<I::WindowId>,
1770 ctx: &EditContext,
1771 store: &mut Store<I>,
1772 ) -> UIResult<EditInfo, I> {
1773 let slot = self.get_slot_mut().ok_or(UIError::NoWindow)?;
1774
1775 let w = match target {
1776 OpenTarget::Alternate => {
1777 slot.alternate();
1778
1779 return Ok(None);
1780 },
1781 OpenTarget::Application(id) => W::open(id.clone(), store)?,
1782 OpenTarget::Current => slot.get().dup(store),
1783 OpenTarget::Cursor(style) => {
1784 if let Some(text) = slot.get().get_cursor_word(style) {
1785 W::find(text, store)?
1786 } else {
1787 let msg = "No word under cursor".to_string();
1788 let err = UIError::Failure(msg);
1789
1790 return Err(err);
1791 }
1792 },
1793 OpenTarget::List(count) => W::posn(ctx.resolve(count), store)?,
1794 OpenTarget::Name(name) => W::find(name.clone(), store)?,
1795 OpenTarget::Offset(dir, count) => {
1796 slot.offset(*dir, ctx.resolve(count));
1797
1798 return Ok(None);
1799 },
1800 OpenTarget::Selection => {
1801 if let Some(text) = slot.get().get_selected_word() {
1802 W::find(text, store)?
1803 } else {
1804 let msg = "No text currently selected".to_string();
1805 let err = UIError::Failure(msg);
1806
1807 return Err(err);
1808 }
1809 },
1810 OpenTarget::Unnamed => W::unnamed(store)?,
1811 };
1812
1813 slot.open(w);
1814
1815 Ok(None)
1816 }
1817
1818 fn window_write(
1819 &mut self,
1820 target: &WindowTarget,
1821 path: Option<&str>,
1822 flags: WriteFlags,
1823 ctx: &EditContext,
1824 store: &mut Store<I>,
1825 ) -> UIResult<EditInfo, I> {
1826 match target {
1827 WindowTarget::Single(focus) => {
1828 if let Some(target) = self._target(focus, ctx) {
1829 if let Some(w) = self.root.get_mut(target) {
1830 return w.write(path, flags, store);
1831 }
1832 }
1833
1834 return Ok(None);
1835 },
1836 WindowTarget::AllBut(focus) => {
1837 let target = self._target(focus, ctx);
1838
1839 for (i, slot) in self.slots().enumerate() {
1840 if matches!(target, Some(idx) if idx == i) {
1841 continue;
1842 }
1843
1844 let _ = slot.write(path, flags, store)?;
1845 }
1846
1847 return Ok(None);
1848 },
1849 WindowTarget::All => {
1850 for slot in self.slots() {
1851 let _ = slot.write(path, flags, store)?;
1852 }
1853
1854 return Ok(None);
1855 },
1856 }
1857 }
1858
1859 fn window_zoom_toggle(&mut self, _: &EditContext, _: &mut Store<I>) -> EditResult<EditInfo, I> {
1860 self.zoom = self.zoom.not();
1861
1862 Ok(None)
1863 }
1864}
1865
1866impl<W, I> WindowCount for WindowLayoutState<W, I>
1867where
1868 W: Window<I>,
1869 I: ApplicationInfo,
1870{
1871 fn windows(&self) -> usize {
1872 self.root.size()
1873 }
1874}
1875
1876impl<W, C, I> Jumpable<C, I> for WindowLayoutState<W, I>
1877where
1878 W: Window<I> + Jumpable<C, I>,
1879 I: ApplicationInfo,
1880{
1881 fn jump(
1882 &mut self,
1883 list: PositionList,
1884 dir: MoveDir1D,
1885 count: usize,
1886 ctx: &C,
1887 ) -> UIResult<usize, I> {
1888 self.get_slot_mut().ok_or(UIError::NoWindow)?.jump(list, dir, count, ctx)
1889 }
1890}
1891
1892impl<W, I> WindowContainer<EditContext, Store<I>, I> for WindowLayoutState<W, I>
1893where
1894 W: Window<I>,
1895 I: ApplicationInfo,
1896{
1897 fn window_command(
1898 &mut self,
1899 action: &WindowAction<I>,
1900 ctx: &EditContext,
1901 store: &mut Store<I>,
1902 ) -> UIResult<EditInfo, I> {
1903 let info = match action {
1904 WindowAction::ClearSizes => self.window_clear_sizes(ctx, store)?,
1905 WindowAction::Close(target, flags) => self.window_close(target, *flags, ctx, store)?,
1906 WindowAction::Exchange(target) => self.window_exchange(target, ctx, store)?,
1907 WindowAction::Focus(target) => self.window_focus(target, ctx, store)?,
1908 WindowAction::MoveSide(dir) => self.window_move_side(*dir, ctx, store)?,
1909 WindowAction::Open(target, axis, rel, count) => {
1910 self.window_open(target, *axis, *rel, count, ctx, store)?
1911 },
1912 WindowAction::Resize(target, axis, size) => {
1913 self.window_resize(target, *axis, size, ctx, store)?
1914 },
1915 WindowAction::Rotate(dir) => self.window_rotate(*dir, ctx, store)?,
1916 WindowAction::Split(target, axis, rel, count) => {
1917 self.window_split(target, *axis, *rel, count, ctx, store)?
1918 },
1919 WindowAction::Switch(target) => self.window_switch(target, ctx, store)?,
1920 WindowAction::Write(target, path, flags) => {
1921 let path = path.as_ref().map(String::as_str);
1922
1923 self.window_write(target, path, *flags, ctx, store)?
1924 },
1925 WindowAction::ZoomToggle => self.window_zoom_toggle(ctx, store)?,
1926 act => {
1927 let msg = format!("unknown window action: {act:?}");
1928 return Err(UIError::Unimplemented(msg));
1929 },
1930 };
1931
1932 return Ok(info);
1933 }
1934}
1935
1936pub struct WindowLayout<'a, W: Window<I>, I: ApplicationInfo> {
1938 store: &'a mut Store<I>,
1939 focused: bool,
1940
1941 borders: bool,
1942 border_style: Style,
1943 border_style_focused: Style,
1944 border_type: BorderType,
1945
1946 _pw: PhantomData<(W, I)>,
1947}
1948
1949impl<'a, W, I> WindowLayout<'a, W, I>
1950where
1951 W: Window<I>,
1952 I: ApplicationInfo,
1953{
1954 pub fn new(store: &'a mut Store<I>) -> Self {
1956 WindowLayout {
1957 store,
1958 focused: false,
1959 borders: false,
1960 border_style: Style::default(),
1961 border_style_focused: Style::default(),
1962 border_type: BorderType::Plain,
1963 _pw: PhantomData,
1964 }
1965 }
1966
1967 pub fn border_style(mut self, style: Style) -> Self {
1969 self.border_style = style;
1970 self
1971 }
1972
1973 pub fn border_style_focused(mut self, style: Style) -> Self {
1975 self.border_style_focused = style;
1976 self
1977 }
1978
1979 pub fn border_type(mut self, border_type: BorderType) -> Self {
1981 self.border_type = border_type;
1982 self
1983 }
1984
1985 pub fn borders(mut self, borders: bool) -> Self {
1987 self.borders = borders;
1988 self
1989 }
1990
1991 pub fn focus(mut self, focused: bool) -> Self {
1993 self.focused = focused;
1994 self
1995 }
1996
1997 fn _draw<X, Y>(
1998 &mut self,
1999 node: &mut AxisTreeNode<WindowSlot<W>, X, Y>,
2000 focus: Option<usize>,
2001 _outer: Rect,
2002 buf: &mut Buffer,
2003 ) where
2004 X: AxisT,
2005 Y: AxisT,
2006 {
2007 let mut base = 0;
2008 let mut f = |value: &mut Value<WindowSlot<W>, X, Y>| {
2009 let focus = focus.and_then(|n| n.checked_sub(base));
2010
2011 match value {
2012 Value::Window(w, info) => {
2013 let focused = matches!(focus, Some(0));
2014
2015 if self.borders {
2016 let title = w.get().get_win_title(self.store);
2017 let block = Block::default()
2018 .title(title)
2019 .borders(Borders::ALL)
2020 .border_style(if focused {
2021 self.border_style_focused
2022 } else {
2023 self.border_style
2024 })
2025 .border_type(self.border_type);
2026 let inner = block.inner(info.area);
2027
2028 block.render(info.area, buf);
2029 w.draw(inner, buf, focused, self.store);
2030 } else {
2031 w.draw(info.area, buf, focused, self.store);
2032 }
2033 },
2034 Value::Tree(t, _) => {
2035 self._draw(t, focus, _outer, buf);
2036 },
2037 }
2038
2039 base += value.size();
2040 };
2041
2042 node.for_each_value(&mut f);
2043 }
2044}
2045
2046impl<W, I> StatefulWidget for WindowLayout<'_, W, I>
2047where
2048 W: Window<I>,
2049 I: ApplicationInfo,
2050{
2051 type State = WindowLayoutState<W, I>;
2052
2053 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
2054 if state.zoom {
2055 if let Some(window) = state.get_mut() {
2056 window.draw(area, buf, self.focused, self.store);
2057 }
2058
2059 return;
2060 }
2061
2062 let focused = self.focused.then_some(state.focused);
2063
2064 state.info.area = area;
2065 state.root.set_area(area, &state.info.resized);
2066
2067 if let Some(root) = &mut state.root {
2068 self._draw(root.as_mut(), focused, area, buf);
2069 }
2070 }
2071}
2072
2073#[cfg(test)]
2074mod tests {
2075 use super::*;
2076 use crate::TerminalCursor;
2077 use modalkit::editing::{completion::CompletionList, store::Store};
2078 use modalkit::errors::EditError;
2079 use rand::Rng;
2080 use ratatui::text::Line;
2081
2082 macro_rules! fc {
2083 ($n: expr) => {
2084 FocusChange::Offset(Count::Exact($n), false)
2085 };
2086 ($n: expr, $b: expr) => {
2087 FocusChange::Offset(Count::Exact($n), $b)
2088 };
2089 }
2090
2091 macro_rules! window_close {
2092 ($tree: expr, $ct: expr, $flags: expr, $ctx: expr, $store: expr) => {
2093 $tree.window_close(&$ct, $flags, $ctx, &mut $store).unwrap()
2094 };
2095 }
2096
2097 macro_rules! window_exchange {
2098 ($tree: expr, $fc: expr, $ctx: expr, $store: expr) => {
2099 $tree.window_exchange(&$fc, $ctx, &mut $store).unwrap()
2100 };
2101 }
2102
2103 macro_rules! window_focus {
2104 ($tree: expr, $fc: expr, $ctx: expr, $store: expr) => {
2105 $tree.window_focus(&$fc, $ctx, &mut $store).unwrap()
2106 };
2107 }
2108
2109 macro_rules! window_focus_off {
2110 ($tree: expr, $c: expr, $ctx: expr, $store: expr) => {
2111 window_focus!($tree, fc!($c), $ctx, $store)
2112 };
2113 }
2114
2115 macro_rules! window_focus_1d {
2116 ($tree: expr, $dir: expr, $c: expr, $ctx: expr, $store: expr) => {
2117 window_focus!($tree, FocusChange::Direction1D($dir, $c, true), $ctx, $store)
2118 };
2119 }
2120
2121 macro_rules! window_focus_2d {
2122 ($tree: expr, $dir: expr, $c: expr, $ctx: expr, $store: expr) => {
2123 window_focus!($tree, FocusChange::Direction2D($dir, $c), $ctx, $store)
2124 };
2125 }
2126
2127 macro_rules! window_move_side {
2128 ($tree: expr, $dir: expr, $ctx: expr, $store: expr) => {
2129 $tree.window_move_side($dir, $ctx, &mut $store).unwrap()
2130 };
2131 }
2132
2133 macro_rules! window_resize {
2134 ($tree: expr, $change: expr, $axis: expr, $szch: expr, $ctx: expr, $store: expr) => {
2135 $tree.window_resize(&$change, $axis, &$szch, $ctx, &mut $store).unwrap()
2136 };
2137 }
2138
2139 macro_rules! window_switch {
2140 ($tree: expr, $target: expr, $ctx: expr, $store: expr) => {
2141 $tree.window_switch(&$target, $ctx, &mut $store).unwrap()
2142 };
2143 }
2144
2145 macro_rules! window_split {
2146 ($tree: expr, $axis: expr, $dir: expr, $count: expr, $ctx: expr, $store: expr) => {
2147 $tree
2148 .window_split(&OpenTarget::Current, $axis, $dir, &$count, $ctx, &mut $store)
2149 .unwrap()
2150 };
2151 }
2152
2153 #[derive(Clone, Debug, Eq, PartialEq)]
2154 enum TestApp {}
2155
2156 impl ApplicationInfo for TestApp {
2157 type Error = String;
2158 type Action = ();
2159 type Store = ();
2160 type WindowId = Option<usize>;
2161 type ContentId = Option<usize>;
2162
2163 fn content_of_command(_: CommandType) -> Option<usize> {
2164 None
2165 }
2166 }
2167
2168 #[derive(Clone, Debug, Eq, PartialEq)]
2169 pub struct TestWindow {
2170 dirty: bool,
2171 term_area: Rect,
2172 id: Option<usize>,
2173 }
2174
2175 impl TestWindow {
2176 fn new() -> Self {
2177 TestWindow { dirty: true, term_area: Rect::default(), id: None }
2178 }
2179 }
2180
2181 impl From<Option<usize>> for TestWindow {
2182 fn from(id: Option<usize>) -> Self {
2183 TestWindow { dirty: true, term_area: Rect::default(), id }
2184 }
2185 }
2186
2187 impl TerminalCursor for TestWindow {
2188 fn get_term_cursor(&self) -> Option<(u16, u16)> {
2189 (self.term_area.left(), self.term_area.top()).into()
2190 }
2191 }
2192
2193 impl WindowOps<TestApp> for TestWindow {
2194 fn dup(&self, _: &mut Store<TestApp>) -> Self {
2195 self.clone()
2196 }
2197
2198 fn close(&mut self, flags: CloseFlags, store: &mut Store<TestApp>) -> bool {
2199 if !flags.contains(CloseFlags::WRITE) {
2200 return true;
2201 }
2202
2203 let flags = if flags.contains(CloseFlags::FORCE) {
2204 WriteFlags::FORCE
2205 } else {
2206 WriteFlags::NONE
2207 };
2208
2209 self.write(None, flags, store).is_ok()
2210 }
2211
2212 fn write(
2213 &mut self,
2214 _: Option<&str>,
2215 flags: WriteFlags,
2216 _: &mut Store<TestApp>,
2217 ) -> UIResult<EditInfo, TestApp> {
2218 if flags.contains(WriteFlags::FORCE) {
2219 self.dirty = false;
2220 Ok(None)
2221 } else {
2222 Err(UIError::Failure("Cannot write".into()))
2223 }
2224 }
2225
2226 fn draw(&mut self, area: Rect, _: &mut Buffer, _: bool, _: &mut Store<TestApp>) {
2227 self.term_area = area;
2228 }
2229
2230 fn get_completions(&self) -> Option<CompletionList> {
2231 None
2232 }
2233
2234 fn get_cursor_word(&self, _: &WordStyle) -> Option<String> {
2235 None
2236 }
2237
2238 fn get_selected_word(&self) -> Option<String> {
2239 None
2240 }
2241 }
2242
2243 impl Window<TestApp> for TestWindow {
2244 fn id(&self) -> Option<usize> {
2245 self.id
2246 }
2247
2248 fn get_win_title(&self, _: &mut Store<TestApp>) -> Line {
2249 Line::from("Window Title")
2250 }
2251
2252 fn open(id: Option<usize>, _: &mut Store<TestApp>) -> UIResult<Self, TestApp> {
2253 Ok(TestWindow::from(id))
2254 }
2255
2256 fn find(name: String, _: &mut Store<TestApp>) -> UIResult<Self, TestApp> {
2257 match name.parse::<usize>() {
2258 Ok(n) => Ok(TestWindow::from(Some(n))),
2259 Err(e) => Err(EditError::from(e).into()),
2260 }
2261 }
2262
2263 fn posn(index: usize, _: &mut Store<TestApp>) -> UIResult<Self, TestApp> {
2264 Ok(TestWindow::from(Some(index)))
2265 }
2266
2267 fn unnamed(_: &mut Store<TestApp>) -> UIResult<Self, TestApp> {
2268 Ok(TestWindow::from(None))
2269 }
2270 }
2271
2272 impl<C> Jumpable<C, TestApp> for TestWindow {
2273 fn jump(
2274 &mut self,
2275 _: PositionList,
2276 _: MoveDir1D,
2277 count: usize,
2278 _: &C,
2279 ) -> UIResult<usize, TestApp> {
2280 return Ok(count);
2281 }
2282 }
2283
2284 fn mkstorectx() -> (Store<TestApp>, EditContext) {
2285 (Store::default(), EditContext::default())
2286 }
2287
2288 fn mktree() -> (WindowLayoutState<TestWindow, TestApp>, Store<TestApp>, EditContext) {
2289 let (store, ctx) = mkstorectx();
2290 let tree = WindowLayoutState::new(TestWindow::new());
2291
2292 return (tree, store, ctx);
2293 }
2294
2295 fn three_by_three() -> (WindowLayoutState<TestWindow, TestApp>, Store<TestApp>, EditContext) {
2296 let (mut tree, mut store, ctx) = mktree();
2309
2310 window_split!(tree, Axis::Vertical, MoveDir1D::Previous, Count::Exact(1), &ctx, store);
2311 window_split!(tree, Axis::Horizontal, MoveDir1D::Previous, Count::Exact(2), &ctx, store);
2312 window_split!(tree, Axis::Vertical, MoveDir1D::Previous, Count::Exact(1), &ctx, store);
2313 window_focus_off!(tree, 3, &ctx, store);
2314 window_split!(tree, Axis::Vertical, MoveDir1D::Previous, Count::Exact(1), &ctx, store);
2315 window_focus_off!(tree, 5, &ctx, store);
2316 window_split!(tree, Axis::Vertical, MoveDir1D::Previous, Count::Exact(1), &ctx, store);
2317 window_focus_off!(tree, 7, &ctx, store);
2318 window_split!(tree, Axis::Horizontal, MoveDir1D::Next, Count::Exact(2), &ctx, store);
2319
2320 let mut idx = 0;
2321
2322 while let Some(slot) = tree.root.get_mut(idx) {
2323 slot.get_mut().id = Some(idx);
2324 idx += 1;
2325 }
2326
2327 return (tree, store, ctx);
2328 }
2329
2330 #[test]
2331 fn test_tree_get() {
2332 let (mut tree, _, _) = three_by_three();
2333
2334 assert_eq!(tree.root.get(10), None);
2335 assert_eq!(tree.root.get(100), None);
2336
2337 assert_eq!(tree.root.get_mut(10), None);
2338 assert_eq!(tree.root.get_mut(100), None);
2339
2340 assert_eq!(tree.root.get(0).unwrap().get().id, Some(0));
2341 assert_eq!(tree.root.get(8).unwrap().get().id, Some(8));
2342
2343 assert_eq!(tree.root.get_mut(0).unwrap().get().id, Some(0));
2344 assert_eq!(tree.root.get_mut(8).unwrap().get().id, Some(8));
2345 }
2346
2347 #[test]
2348 fn test_tree_rotations() {
2349 let (mut store, ctx) = mkstorectx();
2350 let flags = CloseFlags::QUIT;
2351
2352 let mut tree = WindowLayoutState::new(TestWindow::new());
2354 window_split!(tree, Axis::Horizontal, MoveDir1D::Next, Count::Exact(99), &ctx, store);
2355 assert_eq!(tree.root.size(), 100);
2356
2357 for n in 0..100 {
2358 window_close!(tree, WindowTarget::Single(fc!(100 - n)), flags, &ctx, store);
2359 assert_eq!(tree.root.size(), 99 - n);
2360 }
2361
2362 let mut tree = WindowLayoutState::new(TestWindow::new());
2364 window_split!(tree, Axis::Horizontal, MoveDir1D::Next, Count::Exact(99), &ctx, store);
2365 assert_eq!(tree.root.size(), 100);
2366
2367 for n in 0..100 {
2368 window_close!(tree, WindowTarget::Single(fc!(1)), flags, &ctx, store);
2369 assert_eq!(tree.root.size(), 99 - n);
2370 }
2371
2372 let mut tree = WindowLayoutState::new(TestWindow::new());
2374 let mut rng = rand::thread_rng();
2375
2376 assert_eq!(tree.root.size(), 1);
2377
2378 for n in 0..999 {
2379 let size = tree.root.size();
2380 let target = rng.gen_range(1..=size);
2381 window_focus_off!(tree, target, &ctx, store);
2382 window_split!(tree, Axis::Horizontal, MoveDir1D::Next, Count::Exact(1), &ctx, store);
2383 assert_eq!(tree.root.size(), 2 + n);
2384 }
2385
2386 for n in 0..1000 {
2387 let size = tree.root.size();
2388 let target = rng.gen_range(1..=size);
2389 window_close!(tree, WindowTarget::Single(fc!(target)), flags, &ctx, store);
2390 assert_eq!(tree.root.size(), 999 - n);
2391 }
2392 }
2393
2394 #[test]
2395 fn test_window_split() {
2396 let (mut store, ctx) = mkstorectx();
2397 let mut tree = WindowLayoutState::new(TestWindow::new());
2398
2399 assert_eq!(tree.root.size(), 1);
2400 assert_eq!(tree.focused, 0);
2401
2402 window_split!(tree, Axis::Horizontal, MoveDir1D::Previous, Count::Contextual, &ctx, store);
2403 assert_eq!(tree.root.size(), 2);
2404 assert_eq!(tree.focused, 0);
2405
2406 window_split!(tree, Axis::Vertical, MoveDir1D::Previous, Count::Contextual, &ctx, store);
2407 assert_eq!(tree.root.size(), 3);
2408 assert_eq!(tree.focused, 0);
2409
2410 window_split!(tree, Axis::Vertical, MoveDir1D::Next, Count::Contextual, &ctx, store);
2411 assert_eq!(tree.root.size(), 4);
2412 assert_eq!(tree.focused, 1);
2413 }
2414
2415 #[test]
2416 fn test_window_nav_1d() {
2417 let (mut store, ctx) = mkstorectx();
2418 let mut tree = WindowLayoutState::new(TestWindow::new());
2419
2420 window_split!(tree, Axis::Horizontal, MoveDir1D::Previous, Count::Exact(5), &ctx, store);
2421 assert_eq!(tree.root.size(), 6);
2422 assert_eq!(tree.focused, 0);
2423
2424 window_focus_1d!(tree, MoveDir1D::Next, Count::Exact(2), &ctx, store);
2425 assert_eq!(tree.focused, 2);
2426
2427 window_focus_1d!(tree, MoveDir1D::Previous, Count::Exact(1), &ctx, store);
2428 assert_eq!(tree.focused, 1);
2429
2430 window_focus_1d!(tree, MoveDir1D::Previous, Count::Exact(2), &ctx, store);
2431 assert_eq!(tree.focused, 5);
2432
2433 window_focus_1d!(tree, MoveDir1D::Next, Count::Exact(1), &ctx, store);
2434 assert_eq!(tree.focused, 0);
2435 }
2436
2437 #[test]
2438 fn test_window_nav_2d() {
2439 let (mut tree, mut store, ctx) = three_by_three();
2440
2441 assert_eq!(tree.root.size(), 9);
2442 assert_eq!(tree.focused, 8);
2443
2444 let mut buffer = Buffer::empty(Rect::new(0, 0, 100, 100));
2446 let area = Rect::new(0, 0, 100, 100);
2447 let widget = WindowLayout::new(&mut store);
2448 widget.render(area, &mut buffer, &mut tree);
2449
2450 window_focus_2d!(tree, MoveDir2D::Left, Count::Exact(2), &ctx, store);
2452 assert_eq!(tree.focused, 4);
2453
2454 window_focus_2d!(tree, MoveDir2D::Right, Count::Exact(1), &ctx, store);
2456 assert_eq!(tree.focused, 5);
2457
2458 window_focus_2d!(tree, MoveDir2D::Up, Count::Exact(2), &ctx, store);
2460 assert_eq!(tree.focused, 1);
2461
2462 window_focus_2d!(tree, MoveDir2D::Up, Count::Exact(1), &ctx, store);
2464 assert_eq!(tree.focused, 1);
2465
2466 window_focus_2d!(tree, MoveDir2D::Down, Count::Exact(1), &ctx, store);
2468 assert_eq!(tree.focused, 3);
2469
2470 window_focus_2d!(tree, MoveDir2D::Left, Count::Exact(3), &ctx, store);
2472 assert_eq!(tree.focused, 2);
2473 }
2474
2475 #[test]
2476 fn test_window_close() {
2477 let (mut tree, mut store, ctx) = three_by_three();
2478 let flags = CloseFlags::NONE;
2479
2480 let target = WindowTarget::Single(fc!(8));
2481 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2482 assert_eq!(tree.root.size(), 8);
2483
2484 let target = WindowTarget::Single(fc!(6));
2485 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2486 assert_eq!(tree.root.size(), 7);
2487
2488 let target = WindowTarget::Single(fc!(3));
2489 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2490 assert_eq!(tree.root.size(), 6);
2491
2492 let target = WindowTarget::Single(fc!(2));
2493 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2494 assert_eq!(tree.root.size(), 5);
2495
2496 assert_eq!(tree.root.get(0).unwrap().get().id, Some(0));
2508 assert_eq!(tree.root.get(1).unwrap().get().id, Some(3));
2509 assert_eq!(tree.root.get(2).unwrap().get().id, Some(4));
2510 assert_eq!(tree.root.get(3).unwrap().get().id, Some(6));
2511 assert_eq!(tree.root.get(4).unwrap().get().id, Some(8));
2512
2513 let target = WindowTarget::Single(fc!(2));
2514 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2515 assert_eq!(tree.root.size(), 4);
2516
2517 let target = WindowTarget::Single(fc!(3));
2518 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2519 assert_eq!(tree.root.size(), 3);
2520
2521 let target = WindowTarget::Single(fc!(3));
2522 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2523 assert_eq!(tree.root.size(), 2);
2524
2525 let target = WindowTarget::Single(fc!(1));
2526 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2527 assert_eq!(tree.root.size(), 1);
2528
2529 let target = WindowTarget::Single(fc!(1));
2531 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2532 assert_eq!(tree.root.size(), 1);
2533
2534 let target = WindowTarget::Single(fc!(1));
2536 let flags = CloseFlags::QUIT;
2537 assert!(window_close!(tree, target, flags, &ctx, store).is_none());
2538 assert_eq!(tree.root.size(), 0);
2539 }
2540
2541 #[test]
2542 fn test_window_close_allbut() {
2543 for idx in 1usize..=9usize {
2544 let (mut tree, mut store, ctx) = three_by_three();
2545 let target = WindowTarget::AllBut(fc!(idx));
2546 assert!(window_close!(tree, target, CloseFlags::NONE, &ctx, store).is_none());
2547 assert_eq!(tree.root.size(), 1);
2548 assert_eq!(tree.get().unwrap().id, Some(idx.saturating_sub(1)));
2549 }
2550 }
2551
2552 #[test]
2553 fn test_window_close_allbut_resize_lens() {
2554 let (mut tree, mut store, ctx) = mktree();
2555 window_split!(tree, Axis::Horizontal, MoveDir1D::Previous, Count::Exact(1), &ctx, store);
2556 let mut buffer = Buffer::empty(Rect::new(0, 0, 100, 100));
2557 let area = Rect::new(0, 0, 100, 100);
2558 let idx = 1;
2559
2560 WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
2562
2563 tree.resize(idx, Axis::Horizontal, SizeChange::Increase(10));
2565 assert!(tree.info.resized.lengths.is_some());
2566
2567 let target = WindowTarget::AllBut(fc!(idx + 1));
2569 assert!(window_close!(tree, target, CloseFlags::NONE, &ctx, store).is_none());
2570 assert!(tree.info.resized.lengths.is_none());
2571 assert_eq!(tree.root.size(), 1);
2572 assert_eq!(tree.focused, 0);
2573
2574 WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
2576 }
2577
2578 #[test]
2579 fn test_window_close_all() {
2580 let (mut tree, mut store, ctx) = three_by_three();
2581 let target = WindowTarget::All;
2582 assert!(window_close!(tree, target, CloseFlags::NONE, &ctx, store).is_none());
2583 assert_eq!(tree.root.size(), 0);
2584 }
2585
2586 #[test]
2587 fn test_window_write() {
2588 let (mut tree, mut store, ctx) = mktree();
2589 let target = WindowTarget::All;
2590
2591 assert_eq!(tree.get().unwrap().dirty, true);
2593
2594 let res = tree.window_write(&target, None, WriteFlags::NONE, &ctx, &mut store);
2596 assert!(res.is_err());
2597
2598 tree.window_write(&target, None, WriteFlags::FORCE, &ctx, &mut store)
2600 .unwrap();
2601 assert_eq!(tree.get().unwrap().dirty, false);
2602 }
2603
2604 #[test]
2605 fn test_window_exchange() {
2606 let (mut tree, mut store, ctx) = three_by_three();
2607
2608 assert_eq!(tree.root.size(), 9);
2610 assert_eq!(tree.focused, 8);
2611 assert_eq!(tree.root.get(0).unwrap().get().id, Some(0));
2612 assert_eq!(tree.root.get(8).unwrap().get().id, Some(8));
2613
2614 window_exchange!(tree, &fc!(1), &ctx, store);
2616 assert_eq!(tree.root.size(), 9);
2617 assert_eq!(tree.focused, 8);
2618 assert_eq!(tree.root.get(0).unwrap().get().id, Some(8));
2619 assert_eq!(tree.root.get(8).unwrap().get().id, Some(0));
2620
2621 window_exchange!(tree, &fc!(8), &ctx, store);
2623 assert_eq!(tree.root.size(), 9);
2624 assert_eq!(tree.focused, 8);
2625 assert_eq!(tree.root.get(0).unwrap().get().id, Some(8));
2626 assert_eq!(tree.root.get(7).unwrap().get().id, Some(0));
2627 assert_eq!(tree.root.get(8).unwrap().get().id, Some(7));
2628
2629 window_focus_off!(tree, 1, &ctx, store);
2631 assert_eq!(tree.focused, 0);
2632
2633 window_exchange!(tree, &fc!(6), &ctx, store);
2634 assert_eq!(tree.root.size(), 9);
2635 assert_eq!(tree.focused, 0);
2636 assert_eq!(tree.root.get(0).unwrap().get().id, Some(5));
2637 assert_eq!(tree.root.get(5).unwrap().get().id, Some(8));
2638 assert_eq!(tree.root.get(7).unwrap().get().id, Some(0));
2639 assert_eq!(tree.root.get(8).unwrap().get().id, Some(7));
2640 }
2641
2642 #[test]
2643 fn test_window_move_side() {
2644 let (mut tree, mut store, ctx) = three_by_three();
2645
2646 assert_eq!(tree.root.size(), 9);
2647 assert_eq!(tree.root.dimensions(), (3, 3));
2648 assert_eq!(tree.focused, 8);
2649
2650 window_move_side!(tree, MoveDir2D::Left, &ctx, store);
2651 assert_eq!(tree.root.size(), 9);
2652 assert_eq!(tree.root.dimensions(), (4, 3));
2653 assert_eq!(tree.focused, 0);
2654
2655 window_move_side!(tree, MoveDir2D::Right, &ctx, store);
2656 assert_eq!(tree.root.size(), 9);
2657 assert_eq!(tree.root.dimensions(), (4, 3));
2658 assert_eq!(tree.focused, 8);
2659
2660 window_move_side!(tree, MoveDir2D::Up, &ctx, store);
2661 assert_eq!(tree.root.size(), 9);
2662 assert_eq!(tree.root.dimensions(), (3, 4));
2663 assert_eq!(tree.focused, 0);
2664
2665 window_move_side!(tree, MoveDir2D::Down, &ctx, store);
2666 assert_eq!(tree.root.size(), 9);
2667 assert_eq!(tree.focused, 8);
2668 assert_eq!(tree.root.dimensions(), (3, 4));
2669 }
2670
2671 #[test]
2672 fn test_window_resize_vertical_increase() {
2673 let (mut tree, mut store, ctx) = three_by_three();
2674 let mut buffer = Buffer::empty(Rect::new(0, 0, 100, 100));
2675 let area = Rect::new(0, 0, 100, 100);
2676
2677 WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
2679
2680 assert_eq!(tree.root.size(), 9);
2681 assert_eq!(tree.root.dimensions(), (3, 3));
2682 assert_eq!(tree.focused, 8);
2683
2684 assert_eq!(tree.root.get(0).unwrap().get().term_area, Rect::new(0, 0, 34, 34));
2686 assert_eq!(tree.root.get(1).unwrap().get().term_area, Rect::new(34, 0, 33, 34));
2687 assert_eq!(tree.root.get(6).unwrap().get().term_area, Rect::new(67, 0, 33, 34));
2688
2689 assert_eq!(tree.root.get(2).unwrap().get().term_area, Rect::new(0, 34, 34, 33));
2691 assert_eq!(tree.root.get(3).unwrap().get().term_area, Rect::new(34, 34, 33, 33));
2692 assert_eq!(tree.root.get(7).unwrap().get().term_area, Rect::new(67, 34, 33, 33));
2693
2694 assert_eq!(tree.root.get(4).unwrap().get().term_area, Rect::new(0, 67, 34, 33));
2696 assert_eq!(tree.root.get(5).unwrap().get().term_area, Rect::new(34, 67, 33, 33));
2697 assert_eq!(tree.root.get(8).unwrap().get().term_area, Rect::new(67, 67, 33, 33));
2698
2699 window_resize!(
2701 tree,
2702 FocusChange::Offset(8.into(), false),
2703 Vertical,
2704 SizeChange::Increase(5.into()),
2705 &ctx,
2706 store
2707 );
2708
2709 WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
2711
2712 assert_eq!(tree.root.get(0).unwrap().get().term_area, Rect::new(0, 0, 34, 34));
2714 assert_eq!(tree.root.get(1).unwrap().get().term_area, Rect::new(34, 0, 28, 34));
2715 assert_eq!(tree.root.get(6).unwrap().get().term_area, Rect::new(62, 0, 38, 34));
2716
2717 assert_eq!(tree.root.get(2).unwrap().get().term_area, Rect::new(0, 34, 34, 33));
2719 assert_eq!(tree.root.get(3).unwrap().get().term_area, Rect::new(34, 34, 28, 33));
2720 assert_eq!(tree.root.get(7).unwrap().get().term_area, Rect::new(62, 34, 38, 33));
2721
2722 assert_eq!(tree.root.get(4).unwrap().get().term_area, Rect::new(0, 67, 34, 33));
2724 assert_eq!(tree.root.get(5).unwrap().get().term_area, Rect::new(34, 67, 28, 33));
2725 assert_eq!(tree.root.get(8).unwrap().get().term_area, Rect::new(62, 67, 38, 33));
2726 }
2727
2728 #[test]
2729 fn test_window_open_vertical_size() {
2730 let (mut tree, mut store, _) = three_by_three();
2731 let mut buffer = Buffer::empty(Rect::new(0, 0, 100, 100));
2732 let area = Rect::new(0, 0, 100, 100);
2733
2734 WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
2736
2737 assert_eq!(tree.root.size(), 9);
2738 assert_eq!(tree.root.dimensions(), (3, 3));
2739 assert_eq!(tree.focused, 8);
2740
2741 assert_eq!(tree.root.get(0).unwrap().get().term_area, Rect::new(0, 0, 34, 34));
2743 assert_eq!(tree.root.get(1).unwrap().get().term_area, Rect::new(34, 0, 33, 34));
2744 assert_eq!(tree.root.get(6).unwrap().get().term_area, Rect::new(67, 0, 33, 34));
2745
2746 assert_eq!(tree.root.get(2).unwrap().get().term_area, Rect::new(0, 34, 34, 33));
2748 assert_eq!(tree.root.get(3).unwrap().get().term_area, Rect::new(34, 34, 33, 33));
2749 assert_eq!(tree.root.get(7).unwrap().get().term_area, Rect::new(67, 34, 33, 33));
2750
2751 assert_eq!(tree.root.get(4).unwrap().get().term_area, Rect::new(0, 67, 34, 33));
2753 assert_eq!(tree.root.get(5).unwrap().get().term_area, Rect::new(34, 67, 33, 33));
2754 assert_eq!(tree.root.get(8).unwrap().get().term_area, Rect::new(67, 67, 33, 33));
2755
2756 let w = TestWindow::new();
2758 tree.open(w, Some(5), Vertical, MoveDir1D::Previous);
2759
2760 assert_eq!(tree.root.size(), 10);
2762
2763 WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
2765
2766 assert_eq!(tree.root.get(0).unwrap().get().term_area, Rect::new(0, 0, 34, 34));
2768 assert_eq!(tree.root.get(1).unwrap().get().term_area, Rect::new(34, 0, 33, 34));
2769 assert_eq!(tree.root.get(6).unwrap().get().term_area, Rect::new(67, 0, 33, 34));
2770
2771 assert_eq!(tree.root.get(2).unwrap().get().term_area, Rect::new(0, 34, 34, 33));
2773 assert_eq!(tree.root.get(3).unwrap().get().term_area, Rect::new(34, 34, 33, 33));
2774 assert_eq!(tree.root.get(7).unwrap().get().term_area, Rect::new(67, 34, 33, 33));
2775
2776 assert_eq!(tree.root.get(4).unwrap().get().term_area, Rect::new(0, 67, 34, 33));
2778 assert_eq!(tree.root.get(5).unwrap().get().term_area, Rect::new(34, 67, 33, 33));
2779 assert_eq!(tree.root.get(8).unwrap().get().term_area, Rect::new(67, 67, 5, 33));
2780 assert_eq!(tree.root.get(9).unwrap().get().term_area, Rect::new(72, 67, 28, 33));
2781 }
2782
2783 #[test]
2784 fn test_window_open_vertical_no_size() {
2785 let (mut tree, mut store, _) = three_by_three();
2786 let mut buffer = Buffer::empty(Rect::new(0, 0, 100, 100));
2787 let area = Rect::new(0, 0, 100, 100);
2788
2789 WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
2791
2792 assert_eq!(tree.root.size(), 9);
2793 assert_eq!(tree.root.dimensions(), (3, 3));
2794 assert_eq!(tree.focused, 8);
2795
2796 assert_eq!(tree.root.get(0).unwrap().get().term_area, Rect::new(0, 0, 34, 34));
2798 assert_eq!(tree.root.get(1).unwrap().get().term_area, Rect::new(34, 0, 33, 34));
2799 assert_eq!(tree.root.get(6).unwrap().get().term_area, Rect::new(67, 0, 33, 34));
2800
2801 assert_eq!(tree.root.get(2).unwrap().get().term_area, Rect::new(0, 34, 34, 33));
2803 assert_eq!(tree.root.get(3).unwrap().get().term_area, Rect::new(34, 34, 33, 33));
2804 assert_eq!(tree.root.get(7).unwrap().get().term_area, Rect::new(67, 34, 33, 33));
2805
2806 assert_eq!(tree.root.get(4).unwrap().get().term_area, Rect::new(0, 67, 34, 33));
2808 assert_eq!(tree.root.get(5).unwrap().get().term_area, Rect::new(34, 67, 33, 33));
2809 assert_eq!(tree.root.get(8).unwrap().get().term_area, Rect::new(67, 67, 33, 33));
2810
2811 let w = TestWindow::new();
2813 tree.open(w, None, Vertical, MoveDir1D::Previous);
2814
2815 assert_eq!(tree.root.size(), 10);
2817
2818 WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
2820
2821 assert_eq!(tree.root.get(0).unwrap().get().term_area, Rect::new(0, 0, 25, 34));
2823 assert_eq!(tree.root.get(1).unwrap().get().term_area, Rect::new(25, 0, 25, 34));
2824 assert_eq!(tree.root.get(6).unwrap().get().term_area, Rect::new(50, 0, 50, 34));
2825
2826 assert_eq!(tree.root.get(2).unwrap().get().term_area, Rect::new(0, 34, 25, 33));
2828 assert_eq!(tree.root.get(3).unwrap().get().term_area, Rect::new(25, 34, 25, 33));
2829 assert_eq!(tree.root.get(7).unwrap().get().term_area, Rect::new(50, 34, 50, 33));
2830
2831 assert_eq!(tree.root.get(4).unwrap().get().term_area, Rect::new(0, 67, 25, 33));
2833 assert_eq!(tree.root.get(5).unwrap().get().term_area, Rect::new(25, 67, 25, 33));
2834 assert_eq!(tree.root.get(8).unwrap().get().term_area, Rect::new(50, 67, 25, 33));
2835 assert_eq!(tree.root.get(9).unwrap().get().term_area, Rect::new(75, 67, 25, 33));
2836 }
2837
2838 #[test]
2839 fn test_window_switch_and_jump() {
2840 let (mut store, ctx) = mkstorectx();
2841 let mut tree = WindowLayoutState::new(TestWindow::new());
2842 let next = MoveDir1D::Next;
2843 let prev = MoveDir1D::Previous;
2844 let jl = PositionList::JumpList;
2845
2846 window_switch!(tree, OpenTarget::Name("1".into()), &ctx, store);
2847 assert_eq!(tree.get().unwrap().id, Some(1));
2848
2849 window_switch!(tree, OpenTarget::Name("2".into()), &ctx, store);
2850 assert_eq!(tree.get().unwrap().id, Some(2));
2851
2852 window_switch!(tree, OpenTarget::Application(3.into()), &ctx, store);
2853 assert_eq!(tree.get().unwrap().id, Some(3));
2854
2855 window_switch!(tree, OpenTarget::Alternate, &ctx, store);
2856 assert_eq!(tree.get().unwrap().id, Some(2));
2857
2858 let count = tree.jump(jl, prev, 1, &ctx).unwrap();
2859 assert_eq!(count, 0);
2860 assert_eq!(tree.get().unwrap().id, Some(3));
2861
2862 let count = tree.jump(jl, prev, 1, &ctx).unwrap();
2863 assert_eq!(count, 0);
2864 assert_eq!(tree.get().unwrap().id, Some(1));
2865
2866 let count = tree.jump(jl, prev, 1, &ctx).unwrap();
2867 assert_eq!(count, 0);
2868 assert_eq!(tree.get().unwrap().id, None);
2869
2870 let count = tree.jump(jl, next, 2, &ctx).unwrap();
2871 assert_eq!(count, 0);
2872 assert_eq!(tree.get().unwrap().id, Some(3));
2873
2874 let count = tree.jump(jl, next, 2, &ctx).unwrap();
2875 assert_eq!(count, 1);
2876 assert_eq!(tree.get().unwrap().id, Some(2));
2877 }
2878
2879 #[test]
2880 fn test_layout_as_description() {
2881 use WindowLayoutDescription::{Split, Window};
2882
2883 let (mut tree, mut store, _) = three_by_three();
2884 let mut buffer = Buffer::empty(Rect::new(0, 0, 60, 60));
2885 let area = Rect::new(0, 0, 60, 60);
2886 tree._focus(3);
2887
2888 WindowLayout::new(&mut store).render(area, &mut buffer, &mut tree);
2890
2891 let desc1 = tree.as_description();
2892 let exp = WindowLayoutDescription::<TestApp>::Split {
2893 children: vec![Split {
2894 children: vec![
2895 Split {
2896 children: vec![
2897 Split {
2898 children: vec![
2899 Window { window: Some(0), length: Some(20) },
2900 Window { window: Some(1), length: Some(20) },
2901 ],
2902 length: Some(20),
2903 },
2904 Split {
2905 children: vec![
2906 Window { window: Some(2), length: Some(20) },
2907 Window { window: Some(3), length: Some(20) },
2908 ],
2909 length: Some(20),
2910 },
2911 Split {
2912 children: vec![
2913 Window { window: Some(4), length: Some(20) },
2914 Window { window: Some(5), length: Some(20) },
2915 ],
2916 length: Some(20),
2917 },
2918 ],
2919 length: Some(40),
2920 },
2921 Split {
2922 children: vec![
2923 Window { window: Some(6), length: Some(20) },
2924 Window { window: Some(7), length: Some(20) },
2925 Window { window: Some(8), length: Some(20) },
2926 ],
2927 length: Some(20),
2928 },
2929 ],
2930 length: Some(60),
2931 }],
2932 length: None,
2933 };
2934 assert_eq!(desc1.layout, exp);
2935 assert_eq!(desc1.focused, 3);
2936 assert_eq!(desc1.zoomed, false);
2937
2938 let tree = desc1
2940 .clone()
2941 .to_layout::<TestWindow>(tree.info.area.into(), &mut store)
2942 .unwrap();
2943 assert_eq!(tree.as_description(), desc1);
2944
2945 let serialized = serde_json::to_string_pretty(&desc1).unwrap();
2947 let exp = include_str!("../../tests/window-layout.json");
2948 assert_eq!(serialized, exp.trim_end());
2949 }
2950}