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 &(data_idx, row_area) in rows.iter() {
220 if point_in_rect(row_area, x, y) {
221 return Some(SettingsHit::ControlTextListRow(
222 item.index,
223 data_idx.unwrap_or(usize::MAX),
224 ));
225 }
226 }
227 }
228 ControlLayoutInfo::Map {
229 entry_rows,
230 add_row_area,
231 } => {
232 if let Some(add_area) = add_row_area {
234 if point_in_rect(*add_area, x, y) {
235 return Some(SettingsHit::ControlMapAddNew(item.index));
236 }
237 }
238 for &(data_idx, row_area) in entry_rows.iter() {
239 if point_in_rect(row_area, x, y) {
240 return Some(SettingsHit::ControlMapRow(item.index, data_idx));
241 }
242 }
243 }
244 ControlLayoutInfo::ObjectArray { entry_rows } => {
245 for &(data_idx, row_area) in entry_rows.iter() {
246 if point_in_rect(row_area, x, y) {
247 return Some(SettingsHit::ControlMapRow(item.index, data_idx));
248 }
249 }
250 }
251 ControlLayoutInfo::Json { edit_area } => {
252 if point_in_rect(*edit_area, x, y) {
253 return Some(SettingsHit::ControlText(item.index));
254 }
255 }
256 ControlLayoutInfo::Complex => {}
257 }
258
259 return Some(SettingsHit::Item(item.index));
260 }
261 }
262
263 if let Some(ref scrollbar) = self.scrollbar_area {
265 if point_in_rect(*scrollbar, x, y) {
266 return Some(SettingsHit::Scrollbar);
267 }
268 }
269
270 if let Some(ref panel) = self.settings_panel_area {
272 if point_in_rect(*panel, x, y) {
273 return Some(SettingsHit::SettingsPanel);
274 }
275 }
276
277 Some(SettingsHit::Background)
278 }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub enum SettingsHit {
284 Outside,
286 Background,
288 Category(usize),
290 Item(usize),
292 SearchResult(usize),
294 ControlToggle(usize),
296 ControlDecrement(usize),
298 ControlIncrement(usize),
300 ControlDropdown(usize),
302 ControlDropdownOption(usize, usize),
304 ControlText(usize),
306 ControlTextListRow(usize, usize),
308 ControlMapRow(usize, usize),
310 ControlMapAddNew(usize),
312 LayerButton,
314 EditButton,
316 SaveButton,
318 CancelButton,
320 ResetButton,
322 Scrollbar,
324 SettingsPanel,
326 SearchScrollbar,
328 SearchResultsPanel,
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn test_layout_creation() {
338 let modal = Rect::new(10, 5, 80, 30);
339 let mut layout = SettingsLayout::new(modal);
340
341 layout.add_category(0, Rect::new(11, 6, 20, 1));
342 layout.add_category(1, Rect::new(11, 7, 20, 1));
343
344 assert_eq!(layout.categories.len(), 2);
345 }
346
347 #[test]
348 fn test_hit_test_outside() {
349 let modal = Rect::new(10, 5, 80, 30);
350 let layout = SettingsLayout::new(modal);
351
352 assert_eq!(layout.hit_test(0, 0), Some(SettingsHit::Outside));
353 assert_eq!(layout.hit_test(5, 5), Some(SettingsHit::Outside));
354 }
355
356 #[test]
357 fn test_hit_test_category() {
358 let modal = Rect::new(10, 5, 80, 30);
359 let mut layout = SettingsLayout::new(modal);
360
361 layout.add_category(0, Rect::new(11, 6, 20, 1));
362 layout.add_category(1, Rect::new(11, 7, 20, 1));
363
364 assert_eq!(layout.hit_test(15, 6), Some(SettingsHit::Category(0)));
365 assert_eq!(layout.hit_test(15, 7), Some(SettingsHit::Category(1)));
366 }
367
368 #[test]
369 fn test_hit_test_buttons() {
370 let modal = Rect::new(10, 5, 80, 30);
371 let mut layout = SettingsLayout::new(modal);
372
373 layout.save_button = Some(Rect::new(60, 32, 8, 1));
374 layout.cancel_button = Some(Rect::new(70, 32, 10, 1));
375
376 assert_eq!(layout.hit_test(62, 32), Some(SettingsHit::SaveButton));
377 assert_eq!(layout.hit_test(75, 32), Some(SettingsHit::CancelButton));
378 }
379
380 #[test]
381 fn test_hit_test_item_with_toggle() {
382 let modal = Rect::new(10, 5, 80, 30);
383 let mut layout = SettingsLayout::new(modal);
384
385 layout.add_item(
386 0,
387 "/test".to_string(),
388 Rect::new(35, 10, 50, 2),
389 ControlLayoutInfo::Toggle(Rect::new(37, 11, 15, 1)),
390 );
391
392 assert_eq!(layout.hit_test(40, 11), Some(SettingsHit::ControlToggle(0)));
394
395 assert_eq!(layout.hit_test(35, 10), Some(SettingsHit::Item(0)));
397 }
398}