@@ -47,13 +47,21 @@ impl Node {
4747
4848// TODO: screen coord to container + container coordinate helpers
4949
50−#[derive(Debug, PartialEq, Eq)]
50+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5151pub enum Layout {
5252 Horizontal,
5353 Vertical,
5454 // could explore stacked/tabbed
5555}
5656
57+#[derive(Debug, Clone, Copy)]
58+pub enum Direction {
59+ Up,
60+ Down,
61+ Left,
62+ Right,
63+}
64+
5765#[derive(Debug)]
5866pub struct Container {
5967 layout: Layout,
@@ -150,7 +158,6 @@ impl Tree {
150158 } => container,
151159 _ => unreachable!(),
152160 };
153−
154161 if container.layout == layout {
155162 // insert node after the current item if there is children already
156163 let pos = if container.children.is_empty() {
@@ -393,6 +400,112 @@ impl Tree {
393400 Traverse::new(self)
394401 }
395402
403+ // Finds the split in the given direction if it exists
404+ pub fn find_split_in_direction(&self, id: ViewId, direction: Direction) -> Option<ViewId> {
405+ let parent = self.nodes[id].parent;
406+ // Base case, we found the root of the tree
407+ if parent == id {
408+ return None;
409+ }
410+ // Parent must always be a container
411+ let parent_container = match &self.nodes[parent].content {
412+ Content::Container(container) => container,
413+ Content::View(_) => unreachable!(),
414+ };
415+
416+ match (direction, parent_container.layout) {
417+ (Direction::Up, Layout::Vertical)
418+ | (Direction::Left, Layout::Horizontal)
419+ | (Direction::Right, Layout::Horizontal)
420+ | (Direction::Down, Layout::Vertical) => {
421+ // The desired direction of movement is not possible within
422+ // the parent container so the search must continue closer to
423+ // the root of the split tree.
424+ self.find_split_in_direction(parent, direction)
425+ }
426+ (Direction::Up, Layout::Horizontal)
427+ | (Direction::Down, Layout::Horizontal)
428+ | (Direction::Left, Layout::Vertical)
429+ | (Direction::Right, Layout::Vertical) => {
430+ // It's possible to move in the desired direction within
431+ // the parent container so an attempt is made to find the
432+ // correct child.
433+ match self.find_child(id, &parent_container.children, direction) {
434+ // Child is found, search is ended
435+ Some(id) => Some(id),
436+ // A child is not found. This could be because of either two scenarios
437+ // 1. Its not possible to move in the desired direction, and search should end
438+ // 2. A layout like the following with focus at X and desired direction Right
439+ // | _ | x | |
440+ // | _ _ _ | |
441+ // | _ _ _ | |
442+ // The container containing X ends at X so no rightward movement is possible
443+ // however there still exists another view/container to the right that hasn't
444+ // been explored. Thus another search is done here in the parent container
445+ // before concluding it's not possible to move in the desired direction.
446+ None => self.find_split_in_direction(parent, direction),
447+ }
448+ }
449+ }
450+ }
451+
452+ fn find_child(&self, id: ViewId, children: &[ViewId], direction: Direction) -> Option<ViewId> {
453+ let mut child_id = match direction {
454+ // index wise in the child list the Up and Left represents a -1
455+ // thus reversed iterator.
456+ Direction::Up | Direction::Left => children
457+ .iter()
458+ .rev()
459+ .skip_while(|i| **i != id)
460+ .copied()
461+ .nth(1)?,
462+ // Down and Right => +1 index wise in the child list
463+ Direction::Down | Direction::Right => {
464+ children.iter().skip_while(|i| **i != id).copied().nth(1)?
465+ }
466+ };
467+ let (current_x, current_y) = match &self.nodes[self.focus].content {
468+ Content::View(current_view) => (current_view.area.left(), current_view.area.top()),
469+ Content::Container(_) => unreachable!(),
470+ };
471+
472+ // If the child is a container the search finds the closest container child
473+ // visually based on screen location.
474+ while let Content::Container(container) = &self.nodes[child_id].content {
475+ match (direction, container.layout) {
476+ (_, Layout::Vertical) => {
477+ // find closest split based on x because y is irrelevant
478+ // in a vertical container (and already correct based on previous search)
479+ child_id = *container.children.iter().min_by_key(|id| {
480+ let x = match &self.nodes[**id].content {
481+ Content::View(view) => view.inner_area().left(),
482+ Content::Container(container) => container.area.left(),
483+ };
484+ (current_x as i16 - x as i16).abs()
485+ })?;
486+ }
487+ (_, Layout::Horizontal) => {
488+ // find closest split based on y because x is irrelevant
489+ // in a horizontal container (and already correct based on previous search)
490+ child_id = *container.children.iter().min_by_key(|id| {
491+ let y = match &self.nodes[**id].content {
492+ Content::View(view) => view.inner_area().top(),
493+ Content::Container(container) => container.area.top(),
494+ };
495+ (current_y as i16 - y as i16).abs()
496+ })?;
497+ }
498+ }
499+ }
500+ Some(child_id)
501+ }
502+
503+ pub fn focus_direction(&mut self, direction: Direction) {
504+ if let Some(id) = self.find_split_in_direction(self.focus, direction) {
505+ self.focus = id;
506+ }
507+ }
508+
396509 pub fn focus_next(&mut self) {
397510 // This function is very dumb, but that's because we don't store any parent links.
398511 // (we'd be able to go parent.next_sibling() recursively until we find something)
@@ -420,13 +533,12 @@ impl Tree {
420533 // if found = container -> found = first child
421534 // }
422535
423− let iter = self.traverse();
424−
425− let mut iter = iter.skip_while(|&(key, _view)| key != self.focus);
426− iter.next(); // take the focused value
427−
428− if let Some((key, _)) = iter.next() {
429− self.focus = key;
536+ let mut views = self
537+ .traverse()
538+ .skip_while(|&(id, _view)| id != self.focus)
539+ .skip(1); // Skip focused value
540+ if let Some((id, _)) = views.next() {
541+ self.focus = id;
430542 } else {
431543 // extremely crude, take the first item again
432544 let (key, _) = self.traverse().next().unwrap();
@@ -472,3 +584,64 @@ impl<'a> Iterator for Traverse<'a> {
472584 }
473585 }
474586}
587+
588+#[cfg(test)]
589+mod test {
590+ use super::*;
591+ use crate::DocumentId;
592+
593+ #[test]
594+ fn find_split_in_direction() {
595+ let mut tree = Tree::new(Rect {
596+ x: 0,
597+ y: 0,
598+ width: 180,
599+ height: 80,
600+ });
601+ let mut view = View::new(DocumentId::default());
602+ view.area = Rect::new(0, 0, 180, 80);
603+ tree.insert(view);
604+
605+ let l0 = tree.focus;
606+ let view = View::new(DocumentId::default());
607+ tree.split(view, Layout::Vertical);
608+ let r0 = tree.focus;
609+
610+ tree.focus = l0;
611+ let view = View::new(DocumentId::default());
612+ tree.split(view, Layout::Horizontal);
613+ let l1 = tree.focus;
614+
615+ tree.focus = l0;
616+ let view = View::new(DocumentId::default());
617+ tree.split(view, Layout::Vertical);
618+ let l2 = tree.focus;
619+
620+ // Tree in test
621+ // | L0 | L2 | |
622+ // | L1 | R0 |
623+ tree.focus = l2;
624+ assert_eq!(Some(l0), tree.find_split_in_direction(l2, Direction::Left));
625+ assert_eq!(Some(l1), tree.find_split_in_direction(l2, Direction::Down));
626+ assert_eq!(Some(r0), tree.find_split_in_direction(l2, Direction::Right));
627+ assert_eq!(None, tree.find_split_in_direction(l2, Direction::Up));
628+
629+ tree.focus = l1;
630+ assert_eq!(None, tree.find_split_in_direction(l1, Direction::Left));
631+ assert_eq!(None, tree.find_split_in_direction(l1, Direction::Down));
632+ assert_eq!(Some(r0), tree.find_split_in_direction(l1, Direction::Right));
633+ assert_eq!(Some(l0), tree.find_split_in_direction(l1, Direction::Up));
634+
635+ tree.focus = l0;
636+ assert_eq!(None, tree.find_split_in_direction(l0, Direction::Left));
637+ assert_eq!(Some(l1), tree.find_split_in_direction(l0, Direction::Down));
638+ assert_eq!(Some(l2), tree.find_split_in_direction(l0, Direction::Right));
639+ assert_eq!(None, tree.find_split_in_direction(l0, Direction::Up));
640+
641+ tree.focus = r0;
642+ assert_eq!(Some(l2), tree.find_split_in_direction(r0, Direction::Left));
643+ assert_eq!(None, tree.find_split_in_direction(r0, Direction::Down));
644+ assert_eq!(None, tree.find_split_in_direction(r0, Direction::Right));
645+ assert_eq!(None, tree.find_split_in_direction(r0, Direction::Up));
646+ }
647+}