1use crate::utils;
2
3use super::{FrameworkError, FrameworkItem};
4use ratatui::layout::{Constraint, Direction, Layout, Rect};
5
6#[derive(Clone)]
8pub struct RowItem {
9 pub item: Box<dyn FrameworkItem>,
11 pub width: Constraint,
13}
14
15#[derive(Clone)]
17pub struct Row {
18 pub items: Vec<RowItem>,
20 pub centered: bool,
22 pub height: Constraint,
24}
25
26#[derive(Clone)]
28pub struct State(pub Vec<Row>);
29
30impl State {
31 pub fn selectables(&self) -> Vec<Vec<(usize, usize)>> {
36 let mut selectables = Vec::new();
37
38 self.0.iter().enumerate().for_each(|(y, row)| {
39 let mut row_selectables = Vec::new();
40 row.items.iter().enumerate().for_each(|(x, row_item)| {
41 if row_item.item.selectable() {
42 row_selectables.push((x, y));
43 }
44 });
45 if !row_selectables.is_empty() {
46 selectables.push(row_selectables);
47 }
48 });
49
50 selectables
51 }
52
53 pub fn get_chunks(&self, area: Rect) -> Vec<Vec<Rect>> {
55 let mut row_constraints = vec![Constraint::Length(0)];
57 row_constraints.extend(self.0.iter().map(|row| row.height));
58 row_constraints.push(Constraint::Length(0));
59
60 let row_constraints_length = row_constraints.len() - 2;
61
62 Layout::default()
63 .direction(Direction::Vertical)
64 .constraints(row_constraints)
65 .split(area)
66 .iter()
67 .skip(1)
68 .take(row_constraints_length)
69 .zip(self.0.iter().map(|row| {
70 let begin_length = if row.centered {
71 Constraint::Length(
72 (area.width
73 - row
74 .items
75 .iter()
76 .map(|item| utils::constraint_apply(item.width, area.width))
77 .sum::<u16>())
78 / 2,
79 )
80 } else {
81 Constraint::Length(0)
82 };
83
84 let mut out = vec![begin_length];
85 out.extend(row.items.iter().map(|item| item.width));
86 out.push(Constraint::Length(0));
87 out
88 }))
89 .map(|(row_chunk, constraints)| {
90 let constraints_length = constraints.len() - 2;
91
92 Layout::default()
93 .direction(Direction::Horizontal)
94 .constraints(constraints)
95 .split(*row_chunk)
96 .iter()
97 .skip(1)
98 .take(constraints_length)
99 .copied()
100 .collect()
101 })
102 .collect::<Vec<_>>()
103 }
104
105 pub fn get(&self, x: usize, y: usize) -> &dyn FrameworkItem {
107 &*self.0[y].items[x].item
108 }
109
110 pub fn get_mut(&mut self, x: usize, y: usize) -> &mut Box<dyn FrameworkItem> {
112 &mut self.0[y].items[x].item
113 }
114}
115
116#[derive(Clone, Copy, PartialEq, Eq)]
120pub enum CursorState {
121 None,
123 Hover(usize, usize),
125 Selected(usize, usize),
127}
128
129impl Default for CursorState {
130 fn default() -> Self {
131 Self::None
132 }
133}
134
135impl CursorState {
136 pub fn is_selected(&self) -> bool {
137 matches!(self, Self::Selected(_, _))
138 }
139
140 pub fn is_hover(&self) -> bool {
141 matches!(self, Self::Hover(_, _))
142 }
143
144 pub fn is_none(&self) -> bool {
145 self == &Self::None
146 }
147}
148
149impl CursorState {
150 pub fn select(&mut self) -> Result<(), FrameworkError> {
153 match self {
154 Self::Hover(x, y) => *self = Self::Selected(*x, *y),
155 _ => return Err(FrameworkError::CursorStateMismatch),
156 }
157
158 Ok(())
159 }
160
161 pub fn deselect(&mut self) -> Result<(), FrameworkError> {
163 match self {
164 Self::Selected(x, y) => *self = Self::Hover(*x, *y),
165 _ => return Err(FrameworkError::CursorStateMismatch),
166 }
167
168 Ok(())
169 }
170}
171
172impl CursorState {
173 pub fn to_hover(location: (usize, usize)) -> Self {
174 Self::Hover(location.0, location.1)
175 }
176
177 pub fn to_selected(location: (usize, usize)) -> Self {
178 Self::Selected(location.0, location.1)
179 }
180
181 pub fn hover(&self, selectables: &[Vec<(usize, usize)>]) -> Option<(usize, usize)> {
182 match self {
183 Self::Hover(x, y) if !selectables.is_empty() => {
184 Some(Self::selectables_to_coors(selectables, (*x, *y)))
185 }
186 _ => None,
187 }
188 }
189
190 pub fn selected(&self, selectables: &[Vec<(usize, usize)>]) -> Option<(usize, usize)> {
191 match self {
192 Self::Selected(x, y) if !selectables.is_empty() => {
193 Some(Self::selectables_to_coors(selectables, (*x, *y)))
194 }
195 _ => None,
196 }
197 }
198
199 fn selectables_to_coors(
200 selectables: &[Vec<(usize, usize)>],
201 location: (usize, usize),
202 ) -> (usize, usize) {
203 let (location_x, location_y) = location;
204
205 selectables[location_y][location_x]
206 }
207}
208
209impl CursorState {
210 pub fn r#move(
212 &mut self,
213 direction: FrameworkDirection,
214 selectables: &[Vec<(usize, usize)>],
215 ) -> Result<(), FrameworkError> {
216 match direction {
217 FrameworkDirection::Up => self.up(),
218 FrameworkDirection::Down => self.down(),
219 FrameworkDirection::Left => self.left(),
220 FrameworkDirection::Right => self.right(),
221 }?;
222
223 self.move_check(selectables);
224
225 Ok(())
226 }
227
228 fn move_check(&mut self, selectables: &[Vec<(usize, usize)>]) {
229 if let Self::Hover(x, y) = self {
230 if selectables.is_empty() {
231 *x = 0;
232 *y = 0;
233 return;
234 }
235 let y_max = selectables.len() - 1;
236 if *y > y_max {
237 *y = y_max;
238 }
239
240 let x_max = selectables[*y].len() - 1;
241 if *x > x_max {
242 *x = x_max;
243 }
244 } else {
245 unreachable!("move_check is only called after a hovering cursor is moved, when cursor is at hover state")
246 }
247 }
248
249 fn left(&mut self) -> Result<(), FrameworkError> {
250 match self {
251 Self::Hover(x, _) => {
252 if *x != 0 {
253 *x -= 1
254 }
255 }
256 Self::None => *self = Self::Hover(0, 0),
257 Self::Selected(_, _) => return Err(FrameworkError::MoveSelected),
258 }
259
260 Ok(())
261 }
262
263 fn right(&mut self) -> Result<(), FrameworkError> {
264 match self {
265 Self::Hover(x, _) => *x += 1,
266 Self::None => *self = Self::Hover(usize::MAX, 0),
267 Self::Selected(_, _) => return Err(FrameworkError::MoveSelected),
268 }
269
270 Ok(())
271 }
272
273 fn up(&mut self) -> Result<(), FrameworkError> {
274 match self {
275 Self::Hover(_, y) => {
276 if *y != 0 {
277 *y -= 1
278 }
279 }
280 Self::None => *self = Self::Hover(0, 0),
281 Self::Selected(_, _) => return Err(FrameworkError::MoveSelected),
282 }
283
284 Ok(())
285 }
286
287 fn down(&mut self) -> Result<(), FrameworkError> {
288 match self {
289 Self::Hover(_, y) => *y += 1,
290 Self::None => *self = Self::Hover(0, usize::MAX),
291 Self::Selected(_, _) => return Err(FrameworkError::MoveSelected),
292 }
293
294 Ok(())
295 }
296}
297
298#[derive(Clone, Copy)]
300pub enum FrameworkDirection {
301 Up,
302 Down,
303 Left,
304 Right,
305}
306
307#[derive(Clone, Copy)]
309pub struct ItemInfo {
310 pub selected: bool,
311 pub hover: bool,
312 pub x: usize,
313 pub y: usize,
314}