Skip to main content

pito_tui/
model.rs

1use pito_footer::Key;
2use pito_header::{Nav, NavKeys};
3
4use crate::label::Label;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum Item {
9    Group(usize),
10    Tab(usize),
11}
12
13pub(crate) struct Heading {
14    pub(crate) label: Label,
15    pub(crate) keys: &'static [Key],
16    shown: bool,
17}
18
19pub(crate) struct Tab {
20    pub(crate) group: usize,
21    pub(crate) label: Label,
22    pub(crate) screen: usize,
23    shown: bool,
24}
25
26pub(crate) struct Model {
27    pub(crate) headings: Vec<Heading>,
28    pub(crate) tabs: Vec<Tab>,
29    pub(crate) nav: Nav,
30    pub(crate) keys: NavKeys,
31    pub(crate) visible: Vec<usize>,
32    pub(crate) groups: Vec<usize>,
33    last: Vec<Option<usize>>,
34}
35
36impl Model {
37    pub(crate) fn new() -> Self {
38        Model {
39            headings: Vec::new(),
40            tabs: Vec::new(),
41            nav: Nav::new(Vec::new()),
42            keys: NavKeys::HEY,
43            visible: Vec::new(),
44            groups: Vec::new(),
45            last: Vec::new(),
46        }
47    }
48
49    pub(crate) fn add_group(&mut self, label: Label) {
50        self.headings.push(Heading {
51            label,
52            keys: &[],
53            shown: true,
54        });
55        self.last.push(None);
56        self.rebuild();
57    }
58
59    pub(crate) fn add_tab(&mut self, label: Label, screen: usize) {
60        if self.headings.is_empty() {
61            self.headings.push(Heading {
62                label: Label::new(""),
63                keys: &[],
64                shown: true,
65            });
66            self.last.push(None);
67        }
68        self.tabs.push(Tab {
69            group: self.headings.len() - 1,
70            label,
71            screen,
72            shown: true,
73        });
74        self.rebuild();
75    }
76
77    pub(crate) fn group_keys(&mut self, keys: &'static [Key]) {
78        if let Some(heading) = self.headings.last_mut() {
79            heading.keys = keys;
80        }
81    }
82
83    pub(crate) fn set_keys(&mut self, keys: NavKeys) {
84        self.keys = keys;
85        self.rebuild();
86    }
87
88    pub(crate) fn show(&mut self, shown: &dyn Fn(Item) -> bool) -> bool {
89        let before = self.current_screen();
90        for (index, heading) in self.headings.iter_mut().enumerate() {
91            heading.shown = shown(Item::Group(index));
92        }
93        for tab in &mut self.tabs {
94            tab.shown = shown(Item::Tab(tab.screen));
95        }
96        self.rebuild();
97        self.current_screen() != before
98    }
99
100    fn harvest(&mut self) {
101        for (group, heading) in self.groups.iter().enumerate() {
102            if let Some(section) = self.nav.selected(group)
103                && let Some(tab) = self.in_group(group).get(section)
104            {
105                self.last[*heading] = Some(*tab);
106            }
107        }
108    }
109
110    fn in_group(&self, group: usize) -> Vec<usize> {
111        let Some(heading) = self.groups.get(group) else {
112            return Vec::new();
113        };
114        self.visible
115            .iter()
116            .copied()
117            .filter(|tab| self.tabs[*tab].group == *heading)
118            .collect()
119    }
120
121    fn rebuild(&mut self) {
122        self.harvest();
123        let current = self.current_tab();
124        self.visible.clear();
125        self.groups.clear();
126        let mut groups = Vec::new();
127        for (index, heading) in self.headings.iter().enumerate() {
128            if !heading.shown {
129                continue;
130            }
131            let tabs: Vec<usize> = (0..self.tabs.len())
132                .filter(|tab| self.tabs[*tab].group == index && self.tabs[*tab].shown)
133                .collect();
134            if tabs.is_empty() && !self.tabs.iter().all(|tab| tab.group != index) {
135                continue;
136            }
137            let mut group = heading.label.group();
138            for tab in &tabs {
139                group = group.section(self.tabs[*tab].label.section());
140            }
141            self.visible.extend(tabs);
142            self.groups.push(index);
143            groups.push(group);
144        }
145        self.nav = Nav::new(groups).keys(self.keys);
146        for group in 0..self.groups.len() {
147            let last = self.last[self.groups[group]];
148            if let Some(at) =
149                last.and_then(|tab| self.in_group(group).iter().position(|t| *t == tab))
150            {
151                self.nav.go_to(group, at);
152            }
153        }
154        let target = current.and_then(|tab| self.place_of(tab)).or_else(|| {
155            let heading = self.tabs.get(current?)?.group;
156            let group = self.groups.iter().position(|kept| *kept == heading)?;
157            Some((group, self.nav.selected(group).unwrap_or(0)))
158        });
159        let target = target.or_else(|| {
160            let group = (0..self.groups.len()).find(|group| !self.in_group(*group).is_empty())?;
161            Some((group, self.nav.selected(group).unwrap_or(0)))
162        });
163        if let Some((group, section)) = target {
164            self.nav.go_to(group, section);
165        }
166    }
167
168    fn place_of(&self, tab: usize) -> Option<(usize, usize)> {
169        let heading = self.tabs.get(tab)?.group;
170        let group = self.groups.iter().position(|kept| *kept == heading)?;
171        let section = self.in_group(group).iter().position(|t| *t == tab)?;
172        Some((group, section))
173    }
174
175    pub(crate) fn current_tab(&self) -> Option<usize> {
176        let number = self.nav.place().number.checked_sub(1)?;
177        self.visible.get(number).copied()
178    }
179
180    pub(crate) fn current_screen(&self) -> Option<usize> {
181        self.current_tab().map(|tab| self.tabs[tab].screen)
182    }
183
184    pub(crate) fn go_screen(&mut self, screen: usize) -> bool {
185        let Some(tab) = self.tabs.iter().position(|tab| tab.screen == screen) else {
186            return false;
187        };
188        let Some((group, section)) = self.place_of(tab) else {
189            return false;
190        };
191        self.nav.go_to(group, section).is_some()
192    }
193
194    pub(crate) fn go_heading(&mut self, heading: usize) -> bool {
195        let Some(group) = self.groups.iter().position(|kept| *kept == heading) else {
196            return false;
197        };
198        let section = self.nav.selected(group).unwrap_or(0);
199        self.nav.go_to(group, section).is_some()
200    }
201
202    pub(crate) fn heading_for(&self, key: Key) -> Option<usize> {
203        self.headings
204            .iter()
205            .position(|heading| heading.keys.contains(&key))
206    }
207
208    pub(crate) fn group_tabs(&self) -> Vec<usize> {
209        self.in_group(self.nav.place().group)
210    }
211
212    pub(crate) fn lone(&self, group: usize) -> bool {
213        self.in_group(group).len() == 1
214    }
215}