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}