1use super::render::ControlLayoutInfo;
6use crate::view::ui::point_in_rect;
7use ratatui::layout::Rect;
8
9#[derive(Debug, Clone, Default)]
11pub struct SettingsLayout {
12 pub modal_area: Rect,
14 pub categories: Vec<(usize, Rect)>,
16 pub items: Vec<ItemLayout>,
18 pub search_results: Vec<SearchResultLayout>,
20 pub layer_button: Option<Rect>,
22 pub edit_button: Option<Rect>,
24 pub save_button: Option<Rect>,
26 pub cancel_button: Option<Rect>,
28 pub reset_button: Option<Rect>,
30 pub clear_category_button: Option<Rect>,
32 pub sections: Vec<(usize, usize, Rect)>,
34 pub category_disclosures: Vec<(usize, Rect)>,
36 pub categories_panel_area: Option<Rect>,
38 pub categories_scrollbar_area: Option<Rect>,
40 pub settings_panel_area: Option<Rect>,
42 pub scrollbar_area: Option<Rect>,
44 pub search_scrollbar_area: Option<Rect>,
46 pub search_results_area: Option<Rect>,
48}
49
50#[derive(Debug, Clone)]
52pub struct SearchResultLayout {
53 pub page_index: usize,
55 pub item_index: usize,
57 pub area: Rect,
59}
60
61#[derive(Debug, Clone)]
63pub struct ItemLayout {
64 pub index: usize,
66 pub path: String,
68 pub area: Rect,
70 pub control: ControlLayoutInfo,
72 pub inherit_button: Option<Rect>,
74}
75
76impl SettingsLayout {
77 pub fn new(modal_area: Rect) -> Self {
79 Self {
80 modal_area,
81 categories: Vec::new(),
82 items: Vec::new(),
83 search_results: Vec::new(),
84 layer_button: None,
85 edit_button: None,
86 save_button: None,
87 cancel_button: None,
88 reset_button: None,
89 clear_category_button: None,
90 sections: Vec::new(),
91 category_disclosures: Vec::new(),
92 categories_panel_area: None,
93 categories_scrollbar_area: None,
94 settings_panel_area: None,
95 scrollbar_area: None,
96 search_scrollbar_area: None,
97 search_results_area: None,
98 }
99 }
100
101 pub fn add_category(&mut self, index: usize, area: Rect) {
103 self.categories.push((index, area));
104 }
105
106 pub fn add_section(&mut self, cat_idx: usize, section_idx: usize, area: Rect) {
108 self.sections.push((cat_idx, section_idx, area));
109 }
110
111 pub fn add_category_disclosure(&mut self, cat_idx: usize, area: Rect) {
113 self.category_disclosures.push((cat_idx, area));
114 }
115
116 pub fn add_item(
118 &mut self,
119 index: usize,
120 path: String,
121 area: Rect,
122 control: ControlLayoutInfo,
123 inherit_button: Option<Rect>,
124 ) {
125 self.items.push(ItemLayout {
126 index,
127 path,
128 area,
129 control,
130 inherit_button,
131 });
132 }
133
134 pub fn add_search_result(&mut self, page_index: usize, item_index: usize, area: Rect) {
136 self.search_results.push(SearchResultLayout {
137 page_index,
138 item_index,
139 area,
140 });
141 }
142
143 pub fn hit_test(&self, x: u16, y: u16) -> Option<SettingsHit> {
145 if !point_in_rect(self.modal_area, x, y) {
147 return Some(SettingsHit::Outside);
148 }
149
150 if let Some(ref layer) = self.layer_button {
152 if point_in_rect(*layer, x, y) {
153 return Some(SettingsHit::LayerButton);
154 }
155 }
156 if let Some(ref edit) = self.edit_button {
157 if point_in_rect(*edit, x, y) {
158 return Some(SettingsHit::EditButton);
159 }
160 }
161 if let Some(ref save) = self.save_button {
162 if point_in_rect(*save, x, y) {
163 return Some(SettingsHit::SaveButton);
164 }
165 }
166 if let Some(ref cancel) = self.cancel_button {
167 if point_in_rect(*cancel, x, y) {
168 return Some(SettingsHit::CancelButton);
169 }
170 }
171 if let Some(ref reset) = self.reset_button {
172 if point_in_rect(*reset, x, y) {
173 return Some(SettingsHit::ResetButton);
174 }
175 }
176 if let Some(ref clear_cat) = self.clear_category_button {
177 if point_in_rect(*clear_cat, x, y) {
178 return Some(SettingsHit::ClearCategoryButton);
179 }
180 }
181
182 for (cat_idx, area) in &self.category_disclosures {
186 if point_in_rect(*area, x, y) {
187 return Some(SettingsHit::CategoryDisclosure(*cat_idx));
188 }
189 }
190 for (cat_idx, section_idx, area) in &self.sections {
191 if point_in_rect(*area, x, y) {
192 return Some(SettingsHit::CategorySection(*cat_idx, *section_idx));
193 }
194 }
195 for (index, area) in &self.categories {
196 if point_in_rect(*area, x, y) {
197 return Some(SettingsHit::Category(*index));
198 }
199 }
200 if let Some(ref scrollbar) = self.categories_scrollbar_area {
201 if point_in_rect(*scrollbar, x, y) {
202 return Some(SettingsHit::CategoriesScrollbar);
203 }
204 }
205 if let Some(ref panel) = self.categories_panel_area {
206 if point_in_rect(*panel, x, y) {
207 return Some(SettingsHit::CategoriesPanel);
208 }
209 }
210
211 if let Some(ref scrollbar) = self.search_scrollbar_area {
213 if point_in_rect(*scrollbar, x, y) {
214 return Some(SettingsHit::SearchScrollbar);
215 }
216 }
217
218 for (idx, result) in self.search_results.iter().enumerate() {
220 if point_in_rect(result.area, x, y) {
221 return Some(SettingsHit::SearchResult(idx));
222 }
223 }
224
225 if let Some(ref area) = self.search_results_area {
227 if point_in_rect(*area, x, y) {
228 return Some(SettingsHit::SearchResultsPanel);
229 }
230 }
231
232 for item in &self.items {
234 if point_in_rect(item.area, x, y) {
235 if let Some(ref inherit_area) = item.inherit_button {
237 if point_in_rect(*inherit_area, x, y) {
238 return Some(SettingsHit::ControlInherit(item.index));
239 }
240 }
241
242 match &item.control {
244 ControlLayoutInfo::Toggle(toggle_area) => {
245 if point_in_rect(*toggle_area, x, y) {
246 return Some(SettingsHit::ControlToggle(item.index));
247 }
248 }
249 ControlLayoutInfo::Number {
250 decrement,
251 increment,
252 value,
253 } => {
254 if point_in_rect(*decrement, x, y) {
255 return Some(SettingsHit::ControlDecrement(item.index));
256 }
257 if point_in_rect(*increment, x, y) {
258 return Some(SettingsHit::ControlIncrement(item.index));
259 }
260 if point_in_rect(*value, x, y) {
261 return Some(SettingsHit::ControlNumberValue(item.index));
262 }
263 }
264 ControlLayoutInfo::Dropdown {
265 button_area,
266 option_areas,
267 scroll_offset,
268 } => {
269 for (i, area) in option_areas.iter().enumerate() {
271 if point_in_rect(*area, x, y) {
272 return Some(SettingsHit::ControlDropdownOption(
273 item.index,
274 scroll_offset + i,
275 ));
276 }
277 }
278 if point_in_rect(*button_area, x, y) {
279 return Some(SettingsHit::ControlDropdown(item.index));
280 }
281 }
282 ControlLayoutInfo::Text(area) => {
283 if point_in_rect(*area, x, y) {
284 return Some(SettingsHit::ControlText(item.index));
285 }
286 }
287 ControlLayoutInfo::TextList { rows } => {
288 for &(data_idx, row_area) in rows.iter() {
289 if point_in_rect(row_area, x, y) {
290 return Some(SettingsHit::ControlTextListRow(
291 item.index,
292 data_idx.unwrap_or(usize::MAX),
293 ));
294 }
295 }
296 }
297 ControlLayoutInfo::Map {
298 entry_rows,
299 add_row_area,
300 } => {
301 if let Some(add_area) = add_row_area {
303 if point_in_rect(*add_area, x, y) {
304 return Some(SettingsHit::ControlMapAddNew(item.index));
305 }
306 }
307 for &(data_idx, row_area) in entry_rows.iter() {
308 if point_in_rect(row_area, x, y) {
309 return Some(SettingsHit::ControlMapRow(item.index, data_idx));
310 }
311 }
312 }
313 ControlLayoutInfo::ObjectArray { entry_rows } => {
314 for &(data_idx, row_area) in entry_rows.iter() {
315 if point_in_rect(row_area, x, y) {
316 return Some(SettingsHit::ControlMapRow(item.index, data_idx));
317 }
318 }
319 }
320 ControlLayoutInfo::DualList(dual_layout) => {
321 use crate::view::controls::DualListHit;
322 if let Some(hit) = dual_layout.hit_test(x, y) {
323 return Some(match hit {
324 DualListHit::AvailableRow(row) => {
325 SettingsHit::ControlDualListAvailable(item.index, row)
326 }
327 DualListHit::IncludedRow(row) => {
328 SettingsHit::ControlDualListIncluded(item.index, row)
329 }
330 DualListHit::AddButton => {
331 SettingsHit::ControlDualListAdd(item.index)
332 }
333 DualListHit::RemoveButton => {
334 SettingsHit::ControlDualListRemove(item.index)
335 }
336 DualListHit::MoveUpButton => {
337 SettingsHit::ControlDualListMoveUp(item.index)
338 }
339 DualListHit::MoveDownButton => {
340 SettingsHit::ControlDualListMoveDown(item.index)
341 }
342 });
343 }
344 }
345 ControlLayoutInfo::Json { edit_area } => {
346 if point_in_rect(*edit_area, x, y) {
347 return Some(SettingsHit::ControlText(item.index));
348 }
349 }
350 ControlLayoutInfo::Complex => {}
351 }
352
353 return Some(SettingsHit::Item(item.index));
354 }
355 }
356
357 if let Some(ref scrollbar) = self.scrollbar_area {
359 if point_in_rect(*scrollbar, x, y) {
360 return Some(SettingsHit::Scrollbar);
361 }
362 }
363
364 if let Some(ref panel) = self.settings_panel_area {
366 if point_in_rect(*panel, x, y) {
367 return Some(SettingsHit::SettingsPanel);
368 }
369 }
370
371 Some(SettingsHit::Background)
372 }
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377pub enum SettingsHit {
378 Outside,
380 Background,
382 Category(usize),
384 CategoryDisclosure(usize),
387 CategorySection(usize, usize),
389 CategoriesPanel,
391 CategoriesScrollbar,
393 Item(usize),
395 SearchResult(usize),
397 ControlToggle(usize),
399 ControlDecrement(usize),
401 ControlIncrement(usize),
403 ControlNumberValue(usize),
406 ControlDropdown(usize),
408 ControlDropdownOption(usize, usize),
410 ControlText(usize),
412 ControlTextListRow(usize, usize),
414 ControlMapRow(usize, usize),
416 ControlMapAddNew(usize),
418 ControlInherit(usize),
420 ControlDualListAvailable(usize, usize),
422 ControlDualListIncluded(usize, usize),
424 ControlDualListAdd(usize),
426 ControlDualListRemove(usize),
428 ControlDualListMoveUp(usize),
430 ControlDualListMoveDown(usize),
432 LayerButton,
434 EditButton,
436 SaveButton,
438 CancelButton,
440 ResetButton,
442 ClearCategoryButton,
444 Scrollbar,
446 SettingsPanel,
448 SearchScrollbar,
450 SearchResultsPanel,
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
459 fn test_layout_creation() {
460 let modal = Rect::new(10, 5, 80, 30);
461 let mut layout = SettingsLayout::new(modal);
462
463 layout.add_category(0, Rect::new(11, 6, 20, 1));
464 layout.add_category(1, Rect::new(11, 7, 20, 1));
465
466 assert_eq!(layout.categories.len(), 2);
467 }
468
469 #[test]
470 fn test_hit_test_outside() {
471 let modal = Rect::new(10, 5, 80, 30);
472 let layout = SettingsLayout::new(modal);
473
474 assert_eq!(layout.hit_test(0, 0), Some(SettingsHit::Outside));
475 assert_eq!(layout.hit_test(5, 5), Some(SettingsHit::Outside));
476 }
477
478 #[test]
479 fn test_hit_test_category() {
480 let modal = Rect::new(10, 5, 80, 30);
481 let mut layout = SettingsLayout::new(modal);
482
483 layout.add_category(0, Rect::new(11, 6, 20, 1));
484 layout.add_category(1, Rect::new(11, 7, 20, 1));
485
486 assert_eq!(layout.hit_test(15, 6), Some(SettingsHit::Category(0)));
487 assert_eq!(layout.hit_test(15, 7), Some(SettingsHit::Category(1)));
488 }
489
490 #[test]
491 fn test_hit_test_buttons() {
492 let modal = Rect::new(10, 5, 80, 30);
493 let mut layout = SettingsLayout::new(modal);
494
495 layout.save_button = Some(Rect::new(60, 32, 8, 1));
496 layout.cancel_button = Some(Rect::new(70, 32, 10, 1));
497
498 assert_eq!(layout.hit_test(62, 32), Some(SettingsHit::SaveButton));
499 assert_eq!(layout.hit_test(75, 32), Some(SettingsHit::CancelButton));
500 }
501
502 #[test]
503 fn test_hit_test_item_with_toggle() {
504 let modal = Rect::new(10, 5, 80, 30);
505 let mut layout = SettingsLayout::new(modal);
506
507 layout.add_item(
508 0,
509 "/test".to_string(),
510 Rect::new(35, 10, 50, 2),
511 ControlLayoutInfo::Toggle(Rect::new(37, 11, 15, 1)),
512 None,
513 );
514
515 assert_eq!(layout.hit_test(40, 11), Some(SettingsHit::ControlToggle(0)));
517
518 assert_eq!(layout.hit_test(35, 10), Some(SettingsHit::Item(0)));
520 }
521
522 #[test]
527 fn test_hit_test_number_value_area() {
528 let modal = Rect::new(10, 5, 80, 30);
529 let mut layout = SettingsLayout::new(modal);
530
531 let item_area = Rect::new(35, 10, 50, 1);
532 let decrement = Rect::new(50, 10, 3, 1);
533 let increment = Rect::new(54, 10, 3, 1);
534 let value = Rect::new(42, 10, 7, 1);
535
536 layout.add_item(
537 0,
538 "/tab_size".to_string(),
539 item_area,
540 ControlLayoutInfo::Number {
541 decrement,
542 increment,
543 value,
544 },
545 None,
546 );
547
548 assert_eq!(
549 layout.hit_test(45, 10),
550 Some(SettingsHit::ControlNumberValue(0))
551 );
552 assert_eq!(
553 layout.hit_test(51, 10),
554 Some(SettingsHit::ControlDecrement(0))
555 );
556 assert_eq!(
557 layout.hit_test(55, 10),
558 Some(SettingsHit::ControlIncrement(0))
559 );
560 }
561}