Skip to main content

guise/panegroup/
nav.rs

1//! Focus navigation between panes: directional (via layout rects) and ordinal.
2
3use super::id::PaneId;
4use super::layout::{Layout, Rect};
5use super::tree::PaneTree;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum Direction {
9    Up,
10    Down,
11    Left,
12    Right,
13}
14
15const EPS: f32 = 0.001;
16
17/// The pane reached by moving `direction` from `from`: the pane whose facing
18/// edge is closest (adjoining wins) and whose center is nearest on the
19/// perpendicular axis. `None` at the workspace edge or if `from` is unknown.
20pub fn neighbor(layout: &Layout, from: PaneId, direction: Direction) -> Option<PaneId> {
21    let origin = layout.pane_rect(from)?;
22    let mut best: Option<(f32, f32, PaneId)> = None;
23    for (pane, rect) in &layout.panes {
24        if *pane == from {
25            continue;
26        }
27        let Some(gap) = edgegap(origin, *rect, direction) else {
28            continue;
29        };
30        let dist = centerdistance(origin, *rect, direction);
31        let closer = match best {
32            None => true,
33            Some((bgap, bdist, _)) => {
34                gap < bgap - EPS || ((gap - bgap).abs() <= EPS && dist < bdist)
35            }
36        };
37        if closer {
38            best = Some((gap, dist, *pane));
39        }
40    }
41    best.map(|(_, _, pane)| pane)
42}
43
44/// Next pane after `from` in layout order, wrapping.
45pub fn next(tree: &PaneTree, from: PaneId) -> Option<PaneId> {
46    let panes = tree.panes();
47    let i = panes.iter().position(|p| *p == from)?;
48    Some(panes[(i + 1) % panes.len()])
49}
50
51/// Previous pane before `from` in layout order, wrapping.
52pub fn prev(tree: &PaneTree, from: PaneId) -> Option<PaneId> {
53    let panes = tree.panes();
54    let i = panes.iter().position(|p| *p == from)?;
55    Some(panes[(i + panes.len() - 1) % panes.len()])
56}
57
58/// Distance from `from`'s facing edge to `to`'s opposing edge, or `None`
59/// if `to` is not on the `dir` side of `from`.
60fn edgegap(from: Rect, to: Rect, dir: Direction) -> Option<f32> {
61    let gap = match dir {
62        Direction::Left => from.x - (to.x + to.w),
63        Direction::Right => to.x - (from.x + from.w),
64        Direction::Up => from.y - (to.y + to.h),
65        Direction::Down => to.y - (from.y + from.h),
66    };
67    (gap >= -EPS).then(|| gap.max(0.0))
68}
69
70fn centerdistance(from: Rect, to: Rect, dir: Direction) -> f32 {
71    let (fx, fy) = from.center();
72    let (tx, ty) = to.center();
73    match dir {
74        Direction::Left | Direction::Right => (fy - ty).abs(),
75        Direction::Up | Direction::Down => (fx - tx).abs(),
76    }
77}