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 settings_panel_area: Option<Rect>,
32 pub scrollbar_area: Option<Rect>,
34 pub search_scrollbar_area: Option<Rect>,
36 pub search_results_area: Option<Rect>,
38}
39
40#[derive(Debug, Clone)]
42pub struct SearchResultLayout {
43 pub page_index: usize,
45 pub item_index: usize,
47 pub area: Rect,
49}
50
51#[derive(Debug, Clone)]
53pub struct ItemLayout {
54 pub index: usize,
56 pub path: String,
58 pub area: Rect,
60 pub control: ControlLayoutInfo,
62}
63
64impl SettingsLayout {
65 pub fn new(modal_area: Rect) -> Self {
67 Self {
68 modal_area,
69 categories: Vec::new(),
70 items: Vec::new(),
71 search_results: Vec::new(),
72 layer_button: None,
73 edit_button: None,
74 save_button: None,
75 cancel_button: None,
76 reset_button: None,
77 settings_panel_area: None,
78 scrollbar_area: None,
79 search_scrollbar_area: None,
80 search_results_area: None,
81 }
82 }
83
84 pub fn add_category(&mut self, index: usize, area: Rect) {
86 self.categories.push((index, area));
87 }
88
89 pub fn add_item(&mut self, index: usize, path: String, area: Rect, control: ControlLayoutInfo) {
91 self.items.push(ItemLayout {
92 index,
93 path,
94 area,
95 control,
96 });
97 }
98
99 pub fn add_search_result(&mut self, page_index: usize, item_index: usize, area: Rect) {
101 self.search_results.push(SearchResultLayout {
102 page_index,
103 item_index,
104 area,
105 });
106 }
107
108 pub fn hit_test(&self, x: u16, y: u16) -> Option<SettingsHit> {
110 if !point_in_rect(self.modal_area, x, y) {
112 return Some(SettingsHit::Outside);
113 }
114
115 if let Some(ref layer) = self.layer_button {
117 if point_in_rect(*layer, x, y) {
118 return Some(SettingsHit::LayerButton);
119 }
120 }
121 if let Some(ref edit) = self.edit_button {
122 if point_in_rect(*edit, x, y) {
123 return Some(SettingsHit::EditButton);
124 }
125 }
126 if let Some(ref save) = self.save_button {
127 if point_in_rect(*save, x, y) {
128 return Some(SettingsHit::SaveButton);
129 }
130 }
131 if let Some(ref cancel) = self.cancel_button {
132 if point_in_rect(*cancel, x, y) {
133 return Some(SettingsHit::CancelButton);
134 }
135 }
136 if let Some(ref reset) = self.reset_button {
137 if point_in_rect(*reset, x, y) {
138 return Some(SettingsHit::ResetButton);
139 }
140 }
141
142 for (index, area) in &self.categories {
144 if point_in_rect(*area, x, y) {
145 return Some(SettingsHit::Category(*index));
146 }
147 }
148
149 if let Some(ref scrollbar) = self.search_scrollbar_area {
151 if point_in_rect(*scrollbar, x, y) {
152 return Some(SettingsHit::SearchScrollbar);
153 }
154 }
155
156 for (idx, result) in self.search_results.iter().enumerate() {
158 if point_in_rect(result.area, x, y) {
159 return Some(SettingsHit::SearchResult(idx));
160 }
161 }
162
163 if let Some(ref area) = self.search_results_area {
165 if point_in_rect(*area, x, y) {
166 return Some(SettingsHit::SearchResultsPanel);
167 }
168 }
169
170 for item in &self.items {
172 if point_in_rect(item.area, x, y) {
173 match &item.control {
175 ControlLayoutInfo::Toggle(toggle_area) => {
176 if point_in_rect(*toggle_area, x, y) {
177 return Some(SettingsHit::ControlToggle(item.index));
178 }
179 }
180 ControlLayoutInfo::Number {
181 decrement,
182 increment,
183 value,
184 } => {
185 if point_in_rect(*decrement, x, y) {
186 return Some(SettingsHit::ControlDecrement(item.index));
187 }
188 if point_in_rect(*increment, x, y) {
189 return Some(SettingsHit::ControlIncrement(item.index));
190 }
191 if point_in_rect(*value, x, y) {
192 return Some(SettingsHit::Item(item.index));
193 }
194 }
195 ControlLayoutInfo::Dropdown {
196 button_area,
197 option_areas,
198 scroll_offset,
199 } => {
200 for (i, area) in option_areas.iter().enumerate() {
202 if point_in_rect(*area, x, y) {
203 return Some(SettingsHit::ControlDropdownOption(
204 item.index,
205 scroll_offset + i,
206 ));
207 }
208 }
209 if point_in_rect(*button_area, x, y) {
210 return Some(SettingsHit::ControlDropdown(item.index));
211 }
212 }
213 ControlLayoutInfo::Text(area) => {
214 if point_in_rect(*area, x, y) {
215 return Some(SettingsHit::ControlText(item.index));
216 }
217 }
218 ControlLayoutInfo::TextList { rows } => {
219 for (row_idx, row_area) in rows.iter().enumerate() {
220 if point_in_rect(*row_area, x, y) {
221 return Some(SettingsHit::ControlTextListRow(item.index, row_idx));
222 }
223 }
224 }
225 ControlLayoutInfo::Map {
226 entry_rows,
227 add_row_area,
228 } => {
229 if let Some(add_area) = add_row_area {
231 if point_in_rect(*add_area, x, y) {
232 return Some(SettingsHit::ControlMapAddNew(item.index));
233 }
234 }
235 for (row_idx, row_area) in entry_rows.iter().enumerate() {
236 if point_in_rect(*row_area, x, y) {
237 return Some(SettingsHit::ControlMapRow(item.index, row_idx));
238 }
239 }
240 }
241 ControlLayoutInfo::ObjectArray { entry_rows } => {
242 for (row_idx, row_area) in entry_rows.iter().enumerate() {
243 if point_in_rect(*row_area, x, y) {
244 return Some(SettingsHit::ControlMapRow(item.index, row_idx));
245 }
246 }
247 }
248 ControlLayoutInfo::Json { edit_area } => {
249 if point_in_rect(*edit_area, x, y) {
250 return Some(SettingsHit::ControlText(item.index));
251 }
252 }
253 ControlLayoutInfo::Complex => {}
254 }
255
256 return Some(SettingsHit::Item(item.index));
257 }
258 }
259
260 if let Some(ref scrollbar) = self.scrollbar_area {
262 if point_in_rect(*scrollbar, x, y) {
263 return Some(SettingsHit::Scrollbar);
264 }
265 }
266
267 if let Some(ref panel) = self.settings_panel_area {
269 if point_in_rect(*panel, x, y) {
270 return Some(SettingsHit::SettingsPanel);
271 }
272 }
273
274 Some(SettingsHit::Background)
275 }
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub enum SettingsHit {
281 Outside,
283 Background,
285 Category(usize),
287 Item(usize),
289 SearchResult(usize),
291 ControlToggle(usize),
293 ControlDecrement(usize),
295 ControlIncrement(usize),
297 ControlDropdown(usize),
299 ControlDropdownOption(usize, usize),
301 ControlText(usize),
303 ControlTextListRow(usize, usize),
305 ControlMapRow(usize, usize),
307 ControlMapAddNew(usize),
309 LayerButton,
311 EditButton,
313 SaveButton,
315 CancelButton,
317 ResetButton,
319 Scrollbar,
321 SettingsPanel,
323 SearchScrollbar,
325 SearchResultsPanel,
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_layout_creation() {
335 let modal = Rect::new(10, 5, 80, 30);
336 let mut layout = SettingsLayout::new(modal);
337
338 layout.add_category(0, Rect::new(11, 6, 20, 1));
339 layout.add_category(1, Rect::new(11, 7, 20, 1));
340
341 assert_eq!(layout.categories.len(), 2);
342 }
343
344 #[test]
345 fn test_hit_test_outside() {
346 let modal = Rect::new(10, 5, 80, 30);
347 let layout = SettingsLayout::new(modal);
348
349 assert_eq!(layout.hit_test(0, 0), Some(SettingsHit::Outside));
350 assert_eq!(layout.hit_test(5, 5), Some(SettingsHit::Outside));
351 }
352
353 #[test]
354 fn test_hit_test_category() {
355 let modal = Rect::new(10, 5, 80, 30);
356 let mut layout = SettingsLayout::new(modal);
357
358 layout.add_category(0, Rect::new(11, 6, 20, 1));
359 layout.add_category(1, Rect::new(11, 7, 20, 1));
360
361 assert_eq!(layout.hit_test(15, 6), Some(SettingsHit::Category(0)));
362 assert_eq!(layout.hit_test(15, 7), Some(SettingsHit::Category(1)));
363 }
364
365 #[test]
366 fn test_hit_test_buttons() {
367 let modal = Rect::new(10, 5, 80, 30);
368 let mut layout = SettingsLayout::new(modal);
369
370 layout.save_button = Some(Rect::new(60, 32, 8, 1));
371 layout.cancel_button = Some(Rect::new(70, 32, 10, 1));
372
373 assert_eq!(layout.hit_test(62, 32), Some(SettingsHit::SaveButton));
374 assert_eq!(layout.hit_test(75, 32), Some(SettingsHit::CancelButton));
375 }
376
377 #[test]
378 fn test_hit_test_item_with_toggle() {
379 let modal = Rect::new(10, 5, 80, 30);
380 let mut layout = SettingsLayout::new(modal);
381
382 layout.add_item(
383 0,
384 "/test".to_string(),
385 Rect::new(35, 10, 50, 2),
386 ControlLayoutInfo::Toggle(Rect::new(37, 11, 15, 1)),
387 );
388
389 assert_eq!(layout.hit_test(40, 11), Some(SettingsHit::ControlToggle(0)));
391
392 assert_eq!(layout.hit_test(35, 10), Some(SettingsHit::Item(0)));
394 }
395}