@@ -47,13 +47,21 @@ impl Node {
48// TODO: screen coord to container + container coordinate helpers
48// TODO: screen coord to container + container coordinate helpers
50−#[derive(Debug, PartialEq, Eq)]
50+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum Layout {
51pub enum Layout {
52 Horizontal,
52 Horizontal,
54 // could explore stacked/tabbed
54 // could explore stacked/tabbed
57+#[derive(Debug, Clone, Copy)]
57#[derive(Debug)]
65#[derive(Debug)]
58pub struct Container {
66pub struct Container {
59 layout: Layout,
67 layout: Layout,
@@ -150,7 +158,6 @@ impl Tree {
150 } => container,
158 } => container,
151 _ => unreachable!(),
159 _ => unreachable!(),
154 if container.layout == layout {
161 if container.layout == layout {
155 // insert node after the current item if there is children already
162 // insert node after the current item if there is children already
156 let pos = if container.children.is_empty() {
163 let pos = if container.children.is_empty() {
@@ -393,6 +400,112 @@ impl Tree {
393 Traverse::new(self)
400 Traverse::new(self)
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
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!(),
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)
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
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
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),
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
459+ .skip_while(|i| **i != id)
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)?
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!(),
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(),
484+ (current_x as i16 - x as i16).abs()
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(),
495+ (current_y as i16 - y as i16).abs()
503+ pub fn focus_direction(&mut self, direction: Direction) {
504+ if let Some(id) = self.find_split_in_direction(self.focus, direction) {
396 pub fn focus_next(&mut self) {
509 pub fn focus_next(&mut self) {
397 // This function is very dumb, but that's because we don't store any parent links.
510 // This function is very dumb, but that's because we don't store any parent links.
398 // (we'd be able to go parent.next_sibling() recursively until we find something)
511 // (we'd be able to go parent.next_sibling() recursively until we find something)
@@ -420,13 +533,12 @@ impl Tree {
420 // if found = container -> found = first child
533 // if found = container -> found = first child
536+ let mut views = self
423− let iter = self.traverse();
537+ .traverse()
425− let mut iter = iter.skip_while(|&(key, _view)| key != self.focus);
538+ .skip_while(|&(id, _view)| id != self.focus)
426− iter.next(); // take the focused value
539+ .skip(1); // Skip focused value
428− if let Some((key, _)) = iter.next() {
540+ if let Some((id, _)) = views.next() {
429− self.focus = key;
541+ self.focus = id;
431 // extremely crude, take the first item again
543 // extremely crude, take the first item again
432 let (key, _) = self.traverse().next().unwrap();
544 let (key, _) = self.traverse().next().unwrap();
@@ -472,3 +584,64 @@ impl<'a> Iterator for Traverse<'a> {
591+ use crate::DocumentId;
594+ fn find_split_in_direction() {
595+ let mut tree = Tree::new(Rect {
601+ let mut view = View::new(DocumentId::default());
602+ view.area = Rect::new(0, 0, 180, 80);
605+ let l0 = tree.focus;
606+ let view = View::new(DocumentId::default());
607+ tree.split(view, Layout::Vertical);
608+ let r0 = tree.focus;
611+ let view = View::new(DocumentId::default());
612+ tree.split(view, Layout::Horizontal);
613+ let l1 = tree.focus;
616+ let view = View::new(DocumentId::default());
617+ tree.split(view, Layout::Vertical);
618+ let l2 = tree.focus;
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));
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));
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));
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));