Skip to main content

tui/
pane.rs

1//! Focusable sub-pane model.
2//!
3//! Each view declares a static list of its panes; one is always focused.
4//! Focus is stored by value so reads never need to index.
5
6#[cfg(test)]
7mod tests;
8
9/// Named pane within a view.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PaneId {
12    /// The nomiscript input prompt line.
13    Prompt,
14    /// The scrollback transcript.
15    Scrollback,
16}
17
18/// A view's focusable sub-panes with exactly one always focused.
19///
20/// The pane list is `'static` (declared per view); focus is stored by value
21/// so reads never need to index.
22#[derive(Debug)]
23pub struct PaneSet {
24    panes: &'static [PaneId],
25    focused: PaneId,
26}
27
28impl PaneSet {
29    /// Construct a `PaneSet` with the given static pane list and initial focus.
30    pub const fn new(panes: &'static [PaneId], initial: PaneId) -> Self {
31        Self {
32            panes,
33            focused: initial,
34        }
35    }
36
37    /// The currently focused pane.
38    pub fn focused(&self) -> PaneId {
39        self.focused
40    }
41
42    /// Whether `id` is currently focused.
43    pub fn is_focused(&self, id: PaneId) -> bool {
44        self.focused == id
45    }
46
47    /// Move focus to `id`. A no-op if `id` is not in the pane list.
48    pub fn focus(&mut self, id: PaneId) {
49        if self.panes.contains(&id) {
50            self.focused = id;
51        }
52    }
53
54    /// Cycle focus forward or backward through the pane list, wrapping at
55    /// both ends.
56    pub fn cycle(&mut self, forward: bool) {
57        if let Some(i) = self.panes.iter().position(|p| *p == self.focused) {
58            let len = self.panes.len();
59            let next = if forward {
60                (i + 1) % len
61            } else {
62                (i + len - 1) % len
63            };
64            if let Some(p) = self.panes.get(next) {
65                self.focused = *p;
66            }
67        }
68    }
69}