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}