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 settings_panel_area: Option<Rect>,
34 pub scrollbar_area: Option<Rect>,
36 pub search_scrollbar_area: Option<Rect>,
38 pub search_results_area: Option<Rect>,
40}
41
42#[derive(Debug, Clone)]
44pub struct SearchResultLayout {
45 pub page_index: usize,
47 pub item_index: usize,
49 pub area: Rect,
51}
52
53#[derive(Debug, Clone)]
55pub struct ItemLayout {
56 pub index: usize,
58 pub path: String,
60 pub area: Rect,
62 pub control: ControlLayoutInfo,
64 pub inherit_button: Option<Rect>,
66}
67
68impl SettingsLayout {
69 pub fn new(modal_area: Rect) -> Self {
71 Self {
72 modal_area,
73 categories: Vec::new(),
74 items: Vec::new(),
75 search_results: Vec::new(),
76 layer_button: None,
77 edit_button: None,
78 save_button: None,
79 cancel_button: None,
80 reset_button: None,
81 clear_category_button: None,
82 settings_panel_area: None,
83 scrollbar_area: None,
84 search_scrollbar_area: None,
85 search_results_area: None,
86 }
87 }
88
89 pub fn add_category(&mut self, index: usize, area: Rect) {
91 self.categories.push((index, area));
92 }
93
94 pub fn add_item(
96 &mut self,
97 index: usize,
98 path: String,
99 area: Rect,
100 control: ControlLayoutInfo,
101 inherit_button: Option<Rect>,
102 ) {
103 self.items.push(ItemLayout {
104 index,
105 path,
106 area,
107 control,
108 inherit_button,
109 });
110 }
111
112 pub fn add_search_result(&mut self, page_index: usize, item_index: usize, area: Rect) {
114 self.search_results.push(SearchResultLayout {
115 page_index,
116 item_index,
117 area,
118 });
119 }
120
121 pub fn hit_test(&self, x: u16, y: u16) -> Option<SettingsHit> {
123 if !point_in_rect(self.modal_area, x, y) {
125 return Some(SettingsHit::Outside);
126 }
127
128 if let Some(ref layer) = self.layer_button {
130 if point_in_rect(*layer, x, y) {
131 return Some(SettingsHit::LayerButton);
132 }
133 }
134 if let Some(ref edit) = self.edit_button {
135 if point_in_rect(*edit, x, y) {
136 return Some(SettingsHit::EditButton);
137 }
138 }
139 if let Some(ref save) = self.save_button {
140 if point_in_rect(*save, x, y) {
141 return Some(SettingsHit::SaveButton);
142 }
143 }
144 if let Some(ref cancel) = self.cancel_button {
145 if point_in_rect(*cancel, x, y) {
146 return Some(SettingsHit::CancelButton);
147 }
148 }
149 if let Some(ref reset) = self.reset_button {
150 if point_in_rect(*reset, x, y) {
151 return Some(SettingsHit::ResetButton);
152 }
153 }
154 if let Some(ref clear_cat) = self.clear_category_button {
155 if point_in_rect(*clear_cat, x, y) {
156 return Some(SettingsHit::ClearCategoryButton);
157 }
158 }
159
160 for (index, area) in &self.categories {
162 if point_in_rect(*area, x, y) {
163 return Some(SettingsHit::Category(*index));
164 }
165 }
166
167 if let Some(ref scrollbar) = self.search_scrollbar_area {
169 if point_in_rect(*scrollbar, x, y) {
170 return Some(SettingsHit::SearchScrollbar);
171 }
172 }
173
174 for (idx, result) in self.search_results.iter().enumerate() {
176 if point_in_rect(result.area, x, y) {
177 return Some(SettingsHit::SearchResult(idx));
178 }
179 }
180
181 if let Some(ref area) = self.search_results_area {
183 if point_in_rect(*area, x, y) {
184 return Some(SettingsHit::SearchResultsPanel);
185 }
186 }
187
188 for item in &self.items {
190 if point_in_rect(item.area, x, y) {
191 if let Some(ref inherit_area) = item.inherit_button {
193 if point_in_rect(*inherit_area, x, y) {
194 return Some(SettingsHit::ControlInherit(item.index));
195 }
196 }
197
198 match &item.control {
200 ControlLayoutInfo::Toggle(toggle_area) => {
201 if point_in_rect(*toggle_area, x, y) {
202 return Some(SettingsHit::ControlToggle(item.index));
203 }
204 }
205 ControlLayoutInfo::Number {
206 decrement,
207 increment,
208 value,
209 } => {
210 if point_in_rect(*decrement, x, y) {
211 return Some(SettingsHit::ControlDecrement(item.index));
212 }
213 if point_in_rect(*increment, x, y) {
214 return Some(SettingsHit::ControlIncrement(item.index));
215 }
216 if point_in_rect(*value, x, y) {
217 return Some(SettingsHit::Item(item.index));
218 }
219 }
220 ControlLayoutInfo::Dropdown {
221 button_area,
222 option_areas,
223 scroll_offset,
224 } => {
225 for (i, area) in option_areas.iter().enumerate() {
227 if point_in_rect(*area, x, y) {
228 return Some(SettingsHit::ControlDropdownOption(
229 item.index,
230 scroll_offset + i,
231 ));
232 }
233 }
234 if point_in_rect(*button_area, x, y) {
235 return Some(SettingsHit::ControlDropdown(item.index));
236 }
237 }
238 ControlLayoutInfo::Text(area) => {
239 if point_in_rect(*area, x, y) {
240 return Some(SettingsHit::ControlText(item.index));
241 }
242 }
243 ControlLayoutInfo::TextList { rows } => {
244 for &(data_idx, row_area) in rows.iter() {
245 if point_in_rect(row_area, x, y) {
246 return Some(SettingsHit::ControlTextListRow(
247 item.index,
248 data_idx.unwrap_or(usize::MAX),
249 ));
250 }
251 }
252 }
253 ControlLayoutInfo::Map {
254 entry_rows,
255 add_row_area,
256 } => {
257 if let Some(add_area) = add_row_area {
259 if point_in_rect(*add_area, x, y) {
260 return Some(SettingsHit::ControlMapAddNew(item.index));
261 }
262 }
263 for &(data_idx, row_area) in entry_rows.iter() {
264 if point_in_rect(row_area, x, y) {
265 return Some(SettingsHit::ControlMapRow(item.index, data_idx));
266 }
267 }
268 }
269 ControlLayoutInfo::ObjectArray { entry_rows } => {
270 for &(data_idx, row_area) in entry_rows.iter() {
271 if point_in_rect(row_area, x, y) {
272 return Some(SettingsHit::ControlMapRow(item.index, data_idx));
273 }
274 }
275 }
276 ControlLayoutInfo::DualList(dual_layout) => {
277 use crate::view::controls::DualListHit;
278 if let Some(hit) = dual_layout.hit_test(x, y) {
279 return Some(match hit {
280 DualListHit::AvailableRow(row) => {
281 SettingsHit::ControlDualListAvailable(item.index, row)
282 }
283 DualListHit::IncludedRow(row) => {
284 SettingsHit::ControlDualListIncluded(item.index, row)
285 }
286 DualListHit::AddButton => {
287 SettingsHit::ControlDualListAdd(item.index)
288 }
289 DualListHit::RemoveButton => {
290 SettingsHit::ControlDualListRemove(item.index)
291 }
292 DualListHit::MoveUpButton => {
293 SettingsHit::ControlDualListMoveUp(item.index)
294 }
295 DualListHit::MoveDownButton => {
296 SettingsHit::ControlDualListMoveDown(item.index)
297 }
298 });
299 }
300 }
301 ControlLayoutInfo::Json { edit_area } => {
302 if point_in_rect(*edit_area, x, y) {
303 return Some(SettingsHit::ControlText(item.index));
304 }
305 }
306 ControlLayoutInfo::Complex => {}
307 }
308
309 return Some(SettingsHit::Item(item.index));
310 }
311 }
312
313 if let Some(ref scrollbar) = self.scrollbar_area {
315 if point_in_rect(*scrollbar, x, y) {
316 return Some(SettingsHit::Scrollbar);
317 }
318 }
319
320 if let Some(ref panel) = self.settings_panel_area {
322 if point_in_rect(*panel, x, y) {
323 return Some(SettingsHit::SettingsPanel);
324 }
325 }
326
327 Some(SettingsHit::Background)
328 }
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333pub enum SettingsHit {
334 Outside,
336 Background,
338 Category(usize),
340 Item(usize),
342 SearchResult(usize),
344 ControlToggle(usize),
346 ControlDecrement(usize),
348 ControlIncrement(usize),
350 ControlDropdown(usize),
352 ControlDropdownOption(usize, usize),
354 ControlText(usize),
356 ControlTextListRow(usize, usize),
358 ControlMapRow(usize, usize),
360 ControlMapAddNew(usize),
362 ControlInherit(usize),
364 ControlDualListAvailable(usize, usize),
366 ControlDualListIncluded(usize, usize),
368 ControlDualListAdd(usize),
370 ControlDualListRemove(usize),
372 ControlDualListMoveUp(usize),
374 ControlDualListMoveDown(usize),
376 LayerButton,
378 EditButton,
380 SaveButton,
382 CancelButton,
384 ResetButton,
386 ClearCategoryButton,
388 Scrollbar,
390 SettingsPanel,
392 SearchScrollbar,
394 SearchResultsPanel,
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn test_layout_creation() {
404 let modal = Rect::new(10, 5, 80, 30);
405 let mut layout = SettingsLayout::new(modal);
406
407 layout.add_category(0, Rect::new(11, 6, 20, 1));
408 layout.add_category(1, Rect::new(11, 7, 20, 1));
409
410 assert_eq!(layout.categories.len(), 2);
411 }
412
413 #[test]
414 fn test_hit_test_outside() {
415 let modal = Rect::new(10, 5, 80, 30);
416 let layout = SettingsLayout::new(modal);
417
418 assert_eq!(layout.hit_test(0, 0), Some(SettingsHit::Outside));
419 assert_eq!(layout.hit_test(5, 5), Some(SettingsHit::Outside));
420 }
421
422 #[test]
423 fn test_hit_test_category() {
424 let modal = Rect::new(10, 5, 80, 30);
425 let mut layout = SettingsLayout::new(modal);
426
427 layout.add_category(0, Rect::new(11, 6, 20, 1));
428 layout.add_category(1, Rect::new(11, 7, 20, 1));
429
430 assert_eq!(layout.hit_test(15, 6), Some(SettingsHit::Category(0)));
431 assert_eq!(layout.hit_test(15, 7), Some(SettingsHit::Category(1)));
432 }
433
434 #[test]
435 fn test_hit_test_buttons() {
436 let modal = Rect::new(10, 5, 80, 30);
437 let mut layout = SettingsLayout::new(modal);
438
439 layout.save_button = Some(Rect::new(60, 32, 8, 1));
440 layout.cancel_button = Some(Rect::new(70, 32, 10, 1));
441
442 assert_eq!(layout.hit_test(62, 32), Some(SettingsHit::SaveButton));
443 assert_eq!(layout.hit_test(75, 32), Some(SettingsHit::CancelButton));
444 }
445
446 #[test]
447 fn test_hit_test_item_with_toggle() {
448 let modal = Rect::new(10, 5, 80, 30);
449 let mut layout = SettingsLayout::new(modal);
450
451 layout.add_item(
452 0,
453 "/test".to_string(),
454 Rect::new(35, 10, 50, 2),
455 ControlLayoutInfo::Toggle(Rect::new(37, 11, 15, 1)),
456 None,
457 );
458
459 assert_eq!(layout.hit_test(40, 11), Some(SettingsHit::ControlToggle(0)));
461
462 assert_eq!(layout.hit_test(35, 10), Some(SettingsHit::Item(0)));
464 }
465}