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