1use super::render::ControlLayoutInfo;
6use ratatui::layout::Rect;
7
8#[derive(Debug, Clone, Default)]
10pub struct SettingsLayout {
11 pub modal_area: Rect,
13 pub categories: Vec<(usize, Rect)>,
15 pub items: Vec<ItemLayout>,
17 pub search_results: Vec<SearchResultLayout>,
19 pub layer_button: Option<Rect>,
21 pub edit_button: Option<Rect>,
23 pub save_button: Option<Rect>,
25 pub cancel_button: Option<Rect>,
27 pub reset_button: Option<Rect>,
29 pub settings_panel_area: Option<Rect>,
31 pub scrollbar_area: Option<Rect>,
33}
34
35#[derive(Debug, Clone)]
37pub struct SearchResultLayout {
38 pub page_index: usize,
40 pub item_index: usize,
42 pub area: Rect,
44}
45
46#[derive(Debug, Clone)]
48pub struct ItemLayout {
49 pub index: usize,
51 pub path: String,
53 pub area: Rect,
55 pub control: ControlLayoutInfo,
57}
58
59impl SettingsLayout {
60 pub fn new(modal_area: Rect) -> Self {
62 Self {
63 modal_area,
64 categories: Vec::new(),
65 items: Vec::new(),
66 search_results: Vec::new(),
67 layer_button: None,
68 edit_button: None,
69 save_button: None,
70 cancel_button: None,
71 reset_button: None,
72 settings_panel_area: None,
73 scrollbar_area: None,
74 }
75 }
76
77 pub fn add_category(&mut self, index: usize, area: Rect) {
79 self.categories.push((index, area));
80 }
81
82 pub fn add_item(&mut self, index: usize, path: String, area: Rect, control: ControlLayoutInfo) {
84 self.items.push(ItemLayout {
85 index,
86 path,
87 area,
88 control,
89 });
90 }
91
92 pub fn add_search_result(&mut self, page_index: usize, item_index: usize, area: Rect) {
94 self.search_results.push(SearchResultLayout {
95 page_index,
96 item_index,
97 area,
98 });
99 }
100
101 pub fn hit_test(&self, x: u16, y: u16) -> Option<SettingsHit> {
103 if !self.contains(self.modal_area, x, y) {
105 return Some(SettingsHit::Outside);
106 }
107
108 if let Some(ref layer) = self.layer_button {
110 if self.contains(*layer, x, y) {
111 return Some(SettingsHit::LayerButton);
112 }
113 }
114 if let Some(ref edit) = self.edit_button {
115 if self.contains(*edit, x, y) {
116 return Some(SettingsHit::EditButton);
117 }
118 }
119 if let Some(ref save) = self.save_button {
120 if self.contains(*save, x, y) {
121 return Some(SettingsHit::SaveButton);
122 }
123 }
124 if let Some(ref cancel) = self.cancel_button {
125 if self.contains(*cancel, x, y) {
126 return Some(SettingsHit::CancelButton);
127 }
128 }
129 if let Some(ref reset) = self.reset_button {
130 if self.contains(*reset, x, y) {
131 return Some(SettingsHit::ResetButton);
132 }
133 }
134
135 for (index, area) in &self.categories {
137 if self.contains(*area, x, y) {
138 return Some(SettingsHit::Category(*index));
139 }
140 }
141
142 for item in &self.items {
144 if self.contains(item.area, x, y) {
145 match &item.control {
147 ControlLayoutInfo::Toggle(toggle_area) => {
148 if self.contains(*toggle_area, x, y) {
149 return Some(SettingsHit::ControlToggle(item.index));
150 }
151 }
152 ControlLayoutInfo::Number {
153 decrement,
154 increment,
155 value,
156 } => {
157 if self.contains(*decrement, x, y) {
158 return Some(SettingsHit::ControlDecrement(item.index));
159 }
160 if self.contains(*increment, x, y) {
161 return Some(SettingsHit::ControlIncrement(item.index));
162 }
163 if self.contains(*value, x, y) {
164 return Some(SettingsHit::Item(item.index));
165 }
166 }
167 ControlLayoutInfo::Dropdown(area) => {
168 if self.contains(*area, x, y) {
169 return Some(SettingsHit::ControlDropdown(item.index));
170 }
171 }
172 ControlLayoutInfo::Text(area) => {
173 if self.contains(*area, x, y) {
174 return Some(SettingsHit::ControlText(item.index));
175 }
176 }
177 ControlLayoutInfo::TextList { rows } => {
178 for (row_idx, row_area) in rows.iter().enumerate() {
179 if self.contains(*row_area, x, y) {
180 return Some(SettingsHit::ControlTextListRow(item.index, row_idx));
181 }
182 }
183 }
184 ControlLayoutInfo::Map {
185 entry_rows,
186 add_row_area,
187 } => {
188 if let Some(add_area) = add_row_area {
190 if self.contains(*add_area, x, y) {
191 return Some(SettingsHit::ControlMapAddNew(item.index));
192 }
193 }
194 for (row_idx, row_area) in entry_rows.iter().enumerate() {
195 if self.contains(*row_area, x, y) {
196 return Some(SettingsHit::ControlMapRow(item.index, row_idx));
197 }
198 }
199 }
200 ControlLayoutInfo::ObjectArray { entry_rows } => {
201 for (row_idx, row_area) in entry_rows.iter().enumerate() {
202 if self.contains(*row_area, x, y) {
203 return Some(SettingsHit::ControlMapRow(item.index, row_idx));
204 }
205 }
206 }
207 ControlLayoutInfo::Json { edit_area } => {
208 if self.contains(*edit_area, x, y) {
209 return Some(SettingsHit::ControlText(item.index));
210 }
211 }
212 ControlLayoutInfo::Complex => {}
213 }
214
215 return Some(SettingsHit::Item(item.index));
216 }
217 }
218
219 if let Some(ref scrollbar) = self.scrollbar_area {
221 if self.contains(*scrollbar, x, y) {
222 return Some(SettingsHit::Scrollbar);
223 }
224 }
225
226 if let Some(ref panel) = self.settings_panel_area {
228 if self.contains(*panel, x, y) {
229 return Some(SettingsHit::SettingsPanel);
230 }
231 }
232
233 Some(SettingsHit::Background)
234 }
235
236 fn contains(&self, rect: Rect, x: u16, y: u16) -> bool {
238 x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height
239 }
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub enum SettingsHit {
245 Outside,
247 Background,
249 Category(usize),
251 Item(usize),
253 ControlToggle(usize),
255 ControlDecrement(usize),
257 ControlIncrement(usize),
259 ControlDropdown(usize),
261 ControlText(usize),
263 ControlTextListRow(usize, usize),
265 ControlMapRow(usize, usize),
267 ControlMapAddNew(usize),
269 LayerButton,
271 EditButton,
273 SaveButton,
275 CancelButton,
277 ResetButton,
279 Scrollbar,
281 SettingsPanel,
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_layout_creation() {
291 let modal = Rect::new(10, 5, 80, 30);
292 let mut layout = SettingsLayout::new(modal);
293
294 layout.add_category(0, Rect::new(11, 6, 20, 1));
295 layout.add_category(1, Rect::new(11, 7, 20, 1));
296
297 assert_eq!(layout.categories.len(), 2);
298 }
299
300 #[test]
301 fn test_hit_test_outside() {
302 let modal = Rect::new(10, 5, 80, 30);
303 let layout = SettingsLayout::new(modal);
304
305 assert_eq!(layout.hit_test(0, 0), Some(SettingsHit::Outside));
306 assert_eq!(layout.hit_test(5, 5), Some(SettingsHit::Outside));
307 }
308
309 #[test]
310 fn test_hit_test_category() {
311 let modal = Rect::new(10, 5, 80, 30);
312 let mut layout = SettingsLayout::new(modal);
313
314 layout.add_category(0, Rect::new(11, 6, 20, 1));
315 layout.add_category(1, Rect::new(11, 7, 20, 1));
316
317 assert_eq!(layout.hit_test(15, 6), Some(SettingsHit::Category(0)));
318 assert_eq!(layout.hit_test(15, 7), Some(SettingsHit::Category(1)));
319 }
320
321 #[test]
322 fn test_hit_test_buttons() {
323 let modal = Rect::new(10, 5, 80, 30);
324 let mut layout = SettingsLayout::new(modal);
325
326 layout.save_button = Some(Rect::new(60, 32, 8, 1));
327 layout.cancel_button = Some(Rect::new(70, 32, 10, 1));
328
329 assert_eq!(layout.hit_test(62, 32), Some(SettingsHit::SaveButton));
330 assert_eq!(layout.hit_test(75, 32), Some(SettingsHit::CancelButton));
331 }
332
333 #[test]
334 fn test_hit_test_item_with_toggle() {
335 let modal = Rect::new(10, 5, 80, 30);
336 let mut layout = SettingsLayout::new(modal);
337
338 layout.add_item(
339 0,
340 "/test".to_string(),
341 Rect::new(35, 10, 50, 2),
342 ControlLayoutInfo::Toggle(Rect::new(37, 11, 15, 1)),
343 );
344
345 assert_eq!(layout.hit_test(40, 11), Some(SettingsHit::ControlToggle(0)));
347
348 assert_eq!(layout.hit_test(35, 10), Some(SettingsHit::Item(0)));
350 }
351}