1use super::entry_dialog::EntryDialogState;
7use super::items::{control_to_value, SettingControl, SettingItem, SettingsPage};
8use super::layout::SettingsHit;
9use super::schema::{parse_schema, SettingCategory, SettingSchema};
10use super::search::{search_settings, SearchResult};
11use crate::config::Config;
12use crate::config_io::ConfigLayer;
13use crate::view::controls::FocusState;
14use crate::view::ui::{FocusManager, ScrollablePanel};
15use std::collections::HashMap;
16
17enum NestedDialogInfo {
19 MapEntry {
20 key: String,
21 value: serde_json::Value,
22 schema: SettingSchema,
23 path: String,
24 is_new: bool,
25 no_delete: bool,
26 },
27 ArrayItem {
28 index: Option<usize>,
29 value: serde_json::Value,
30 schema: SettingSchema,
31 path: String,
32 is_new: bool,
33 },
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38pub enum FocusPanel {
39 #[default]
41 Categories,
42 Settings,
44 Footer,
46}
47
48#[derive(Debug)]
50pub struct SettingsState {
51 categories: Vec<SettingCategory>,
53 pub pages: Vec<SettingsPage>,
55 pub selected_category: usize,
57 pub selected_item: usize,
59 pub focus: FocusManager<FocusPanel>,
61 pub footer_button_index: usize,
63 pub pending_changes: HashMap<String, serde_json::Value>,
65 original_config: serde_json::Value,
67 pub visible: bool,
69 pub search_query: String,
71 pub search_active: bool,
73 pub search_results: Vec<SearchResult>,
75 pub selected_search_result: usize,
77 pub search_scroll_offset: usize,
79 pub search_max_visible: usize,
81 pub showing_confirm_dialog: bool,
83 pub confirm_dialog_selection: usize,
85 pub confirm_dialog_hover: Option<usize>,
87 pub showing_reset_dialog: bool,
89 pub reset_dialog_selection: usize,
91 pub reset_dialog_hover: Option<usize>,
93 pub showing_help: bool,
95 pub scroll_panel: ScrollablePanel,
97 pub sub_focus: Option<usize>,
99 pub editing_text: bool,
101 pub hover_position: Option<(u16, u16)>,
103 pub hover_hit: Option<SettingsHit>,
105 pub entry_dialog_stack: Vec<EntryDialogState>,
108 pub target_layer: ConfigLayer,
112 pub layer_sources: HashMap<String, ConfigLayer>,
116 pub pending_deletions: std::collections::HashSet<String>,
120 pub layout_width: u16,
123}
124
125impl SettingsState {
126 pub fn new(schema_json: &str, config: &Config) -> Result<Self, serde_json::Error> {
128 let categories = parse_schema(schema_json)?;
129 let config_value = serde_json::to_value(config)?;
130 let layer_sources = HashMap::new(); let target_layer = ConfigLayer::User; let pages =
133 super::items::build_pages(&categories, &config_value, &layer_sources, target_layer);
134
135 Ok(Self {
136 categories,
137 pages,
138 selected_category: 0,
139 selected_item: 0,
140 focus: FocusManager::new(vec![
141 FocusPanel::Categories,
142 FocusPanel::Settings,
143 FocusPanel::Footer,
144 ]),
145 footer_button_index: 2, pending_changes: HashMap::new(),
147 original_config: config_value,
148 visible: false,
149 search_query: String::new(),
150 search_active: false,
151 search_results: Vec::new(),
152 selected_search_result: 0,
153 search_scroll_offset: 0,
154 search_max_visible: 5, showing_confirm_dialog: false,
156 confirm_dialog_selection: 0,
157 confirm_dialog_hover: None,
158 showing_reset_dialog: false,
159 reset_dialog_selection: 0,
160 reset_dialog_hover: None,
161 showing_help: false,
162 scroll_panel: ScrollablePanel::new(),
163 sub_focus: None,
164 editing_text: false,
165 hover_position: None,
166 hover_hit: None,
167 entry_dialog_stack: Vec::new(),
168 target_layer,
169 layer_sources,
170 pending_deletions: std::collections::HashSet::new(),
171 layout_width: 0,
172 })
173 }
174
175 #[inline]
177 pub fn focus_panel(&self) -> FocusPanel {
178 self.focus.current().unwrap_or_default()
179 }
180
181 pub fn show(&mut self) {
183 self.visible = true;
184 self.focus.set(FocusPanel::Categories);
185 self.footer_button_index = 2; self.selected_category = 0;
187 self.selected_item = 0;
188 self.scroll_panel = ScrollablePanel::new();
189 self.sub_focus = None;
190 self.showing_confirm_dialog = false;
192 self.confirm_dialog_selection = 0;
193 self.confirm_dialog_hover = None;
194 self.showing_reset_dialog = false;
195 self.reset_dialog_selection = 0;
196 self.reset_dialog_hover = None;
197 self.showing_help = false;
198 }
199
200 pub fn hide(&mut self) {
202 self.visible = false;
203 self.search_active = false;
204 self.search_query.clear();
205 }
206
207 pub fn entry_dialog(&self) -> Option<&EntryDialogState> {
209 self.entry_dialog_stack.last()
210 }
211
212 pub fn entry_dialog_mut(&mut self) -> Option<&mut EntryDialogState> {
214 self.entry_dialog_stack.last_mut()
215 }
216
217 pub fn has_entry_dialog(&self) -> bool {
219 !self.entry_dialog_stack.is_empty()
220 }
221
222 pub fn current_page(&self) -> Option<&SettingsPage> {
224 self.pages.get(self.selected_category)
225 }
226
227 pub fn current_page_mut(&mut self) -> Option<&mut SettingsPage> {
229 self.pages.get_mut(self.selected_category)
230 }
231
232 pub fn current_item(&self) -> Option<&SettingItem> {
234 self.current_page()
235 .and_then(|page| page.items.get(self.selected_item))
236 }
237
238 pub fn current_item_mut(&mut self) -> Option<&mut SettingItem> {
240 self.pages
241 .get_mut(self.selected_category)
242 .and_then(|page| page.items.get_mut(self.selected_item))
243 }
244
245 pub fn can_exit_text_editing(&self) -> bool {
247 self.current_item()
248 .map(|item| {
249 if let SettingControl::Text(state) = &item.control {
250 state.is_valid()
251 } else {
252 true
253 }
254 })
255 .unwrap_or(true)
256 }
257
258 pub fn entry_dialog_can_exit_text_editing(&self) -> bool {
260 self.entry_dialog()
261 .and_then(|dialog| dialog.current_item())
262 .map(|item| {
263 if let SettingControl::Text(state) = &item.control {
264 state.is_valid()
265 } else {
266 true
267 }
268 })
269 .unwrap_or(true)
270 }
271
272 fn init_map_focus(&mut self, from_above: bool) {
275 if let Some(item) = self.current_item_mut() {
276 if let SettingControl::Map(ref mut map_state) = item.control {
277 map_state.init_focus(from_above);
278 }
279 }
280 self.update_map_sub_focus();
282 }
283
284 pub(super) fn update_control_focus(&mut self, focused: bool) {
288 let focus_state = if focused {
289 FocusState::Focused
290 } else {
291 FocusState::Normal
292 };
293 if let Some(item) = self.current_item_mut() {
294 match &mut item.control {
295 SettingControl::Map(ref mut state) => state.focus = focus_state,
296 SettingControl::TextList(ref mut state) => state.focus = focus_state,
297 SettingControl::ObjectArray(ref mut state) => state.focus = focus_state,
298 SettingControl::Toggle(ref mut state) => state.focus = focus_state,
299 SettingControl::Number(ref mut state) => state.focus = focus_state,
300 SettingControl::Dropdown(ref mut state) => state.focus = focus_state,
301 SettingControl::Text(ref mut state) => state.focus = focus_state,
302 SettingControl::Json(_) | SettingControl::Complex { .. } => {} }
304 }
305 }
306
307 fn update_map_sub_focus(&mut self) {
310 self.sub_focus = self.current_item().and_then(|item| {
311 if let SettingControl::Map(ref map_state) = item.control {
312 Some(match map_state.focused_entry {
314 Some(i) => 1 + i,
315 None => 1 + map_state.entries.len(), })
317 } else {
318 None
319 }
320 });
321 }
322
323 pub fn select_prev(&mut self) {
325 match self.focus_panel() {
326 FocusPanel::Categories => {
327 if self.selected_category > 0 {
328 self.update_control_focus(false); self.selected_category -= 1;
330 self.selected_item = 0;
331 self.scroll_panel = ScrollablePanel::new();
332 self.sub_focus = None;
333 self.update_control_focus(true); }
335 }
336 FocusPanel::Settings => {
337 let handled = self
339 .current_item_mut()
340 .and_then(|item| match &mut item.control {
341 SettingControl::Map(map_state) => Some(map_state.focus_prev()),
342 _ => None,
343 })
344 .unwrap_or(false);
345
346 if handled {
347 self.update_map_sub_focus();
349 } else if self.selected_item > 0 {
350 self.update_control_focus(false); self.selected_item -= 1;
352 self.sub_focus = None;
353 self.init_map_focus(false); self.update_control_focus(true); }
356 self.ensure_visible();
357 }
358 FocusPanel::Footer => {
359 if self.footer_button_index > 0 {
361 self.footer_button_index -= 1;
362 }
363 }
364 }
365 }
366
367 pub fn select_next(&mut self) {
369 match self.focus_panel() {
370 FocusPanel::Categories => {
371 if self.selected_category + 1 < self.pages.len() {
372 self.update_control_focus(false); self.selected_category += 1;
374 self.selected_item = 0;
375 self.scroll_panel = ScrollablePanel::new();
376 self.sub_focus = None;
377 self.update_control_focus(true); }
379 }
380 FocusPanel::Settings => {
381 let handled = self
383 .current_item_mut()
384 .and_then(|item| match &mut item.control {
385 SettingControl::Map(map_state) => Some(map_state.focus_next()),
386 _ => None,
387 })
388 .unwrap_or(false);
389
390 if handled {
391 self.update_map_sub_focus();
393 } else {
394 let can_move = self
395 .current_page()
396 .is_some_and(|page| self.selected_item + 1 < page.items.len());
397 if can_move {
398 self.update_control_focus(false); self.selected_item += 1;
400 self.sub_focus = None;
401 self.init_map_focus(true); self.update_control_focus(true); }
404 }
405 self.ensure_visible();
406 }
407 FocusPanel::Footer => {
408 if self.footer_button_index < 2 {
410 self.footer_button_index += 1;
411 }
412 }
413 }
414 }
415
416 pub fn toggle_focus(&mut self) {
418 let old_panel = self.focus_panel();
419 self.focus.focus_next();
420 self.on_panel_changed(old_panel, true);
421 }
422
423 pub fn toggle_focus_backward(&mut self) {
425 let old_panel = self.focus_panel();
426 self.focus.focus_prev();
427 self.on_panel_changed(old_panel, false);
428 }
429
430 fn on_panel_changed(&mut self, old_panel: FocusPanel, forward: bool) {
432 if old_panel == FocusPanel::Settings {
434 self.update_control_focus(false);
435 }
436
437 if self.focus_panel() == FocusPanel::Settings
439 && self.selected_item >= self.current_page().map_or(0, |p| p.items.len())
440 {
441 self.selected_item = 0;
442 }
443 self.sub_focus = None;
444
445 if self.focus_panel() == FocusPanel::Settings {
446 self.init_map_focus(forward); self.update_control_focus(true); }
449
450 if self.focus_panel() == FocusPanel::Footer {
452 self.footer_button_index = if forward {
453 0 } else {
455 4 };
457 }
458
459 self.ensure_visible();
460 }
461
462 pub fn update_layout_widths(&mut self) {
466 let width = self.layout_width;
467 if width > 0 {
468 if let Some(page) = self.pages.get_mut(self.selected_category) {
469 for item in &mut page.items {
470 item.layout_width = width;
471 }
472 }
473 }
474 }
475
476 pub fn ensure_visible(&mut self) {
477 if self.focus_panel() != FocusPanel::Settings {
478 return;
479 }
480
481 self.update_layout_widths();
482
483 let selected_item = self.selected_item;
485 let sub_focus = self.sub_focus;
486 if let Some(page) = self.pages.get(self.selected_category) {
487 self.scroll_panel
488 .ensure_focused_visible(&page.items, selected_item, sub_focus);
489 }
490 }
491
492 pub fn set_pending_change(&mut self, path: &str, value: serde_json::Value) {
494 let original = self.original_config.pointer(path);
496 if original == Some(&value) {
497 self.pending_changes.remove(path);
498 } else {
499 self.pending_changes.insert(path.to_string(), value);
500 }
501 }
502
503 pub fn has_changes(&self) -> bool {
505 !self.pending_changes.is_empty() || !self.pending_deletions.is_empty()
506 }
507
508 pub fn apply_changes(&self, config: &Config) -> Result<Config, serde_json::Error> {
510 let mut config_value = serde_json::to_value(config)?;
511
512 for (path, value) in &self.pending_changes {
513 if let Some(target) = config_value.pointer_mut(path) {
514 *target = value.clone();
515 }
516 }
517
518 serde_json::from_value(config_value)
519 }
520
521 pub fn discard_changes(&mut self) {
523 self.pending_changes.clear();
524 self.pending_deletions.clear();
525 self.pages = super::items::build_pages(
527 &self.categories,
528 &self.original_config,
529 &self.layer_sources,
530 self.target_layer,
531 );
532 }
533
534 pub fn set_target_layer(&mut self, layer: ConfigLayer) {
536 if layer != ConfigLayer::System {
537 self.target_layer = layer;
539 self.pending_changes.clear();
541 self.pending_deletions.clear();
542 self.pages = super::items::build_pages(
544 &self.categories,
545 &self.original_config,
546 &self.layer_sources,
547 self.target_layer,
548 );
549 }
550 }
551
552 pub fn cycle_target_layer(&mut self) {
554 self.target_layer = match self.target_layer {
555 ConfigLayer::System => ConfigLayer::User, ConfigLayer::User => ConfigLayer::Project,
557 ConfigLayer::Project => ConfigLayer::Session,
558 ConfigLayer::Session => ConfigLayer::User,
559 };
560 self.pending_changes.clear();
562 self.pending_deletions.clear();
563 self.pages = super::items::build_pages(
565 &self.categories,
566 &self.original_config,
567 &self.layer_sources,
568 self.target_layer,
569 );
570 }
571
572 pub fn target_layer_name(&self) -> &'static str {
574 match self.target_layer {
575 ConfigLayer::System => "System (read-only)",
576 ConfigLayer::User => "User",
577 ConfigLayer::Project => "Project",
578 ConfigLayer::Session => "Session",
579 }
580 }
581
582 pub fn set_layer_sources(&mut self, sources: HashMap<String, ConfigLayer>) {
585 self.layer_sources = sources;
586 self.pages = super::items::build_pages(
588 &self.categories,
589 &self.original_config,
590 &self.layer_sources,
591 self.target_layer,
592 );
593 }
594
595 pub fn get_layer_source(&self, path: &str) -> ConfigLayer {
598 self.layer_sources
599 .get(path)
600 .copied()
601 .unwrap_or(ConfigLayer::System)
602 }
603
604 pub fn layer_source_label(layer: ConfigLayer) -> &'static str {
606 match layer {
607 ConfigLayer::System => "default",
608 ConfigLayer::User => "user",
609 ConfigLayer::Project => "project",
610 ConfigLayer::Session => "session",
611 }
612 }
613
614 pub fn reset_current_to_default(&mut self) {
622 let reset_info = self.current_item().and_then(|item| {
624 if !item.modified || item.is_auto_managed {
627 return None;
628 }
629 item.default
630 .as_ref()
631 .map(|default| (item.path.clone(), default.clone()))
632 });
633
634 if let Some((path, default)) = reset_info {
635 self.pending_deletions.insert(path.clone());
637 self.pending_changes.remove(&path);
639
640 if let Some(item) = self.current_item_mut() {
644 update_control_from_value(&mut item.control, &default);
645 item.modified = false;
646 item.layer_source = ConfigLayer::System; }
649 }
650 }
651
652 pub fn on_value_changed(&mut self) {
654 let target_layer = self.target_layer;
656
657 let change_info = self.current_item().map(|item| {
659 let value = control_to_value(&item.control);
660 (item.path.clone(), value)
661 });
662
663 if let Some((path, value)) = change_info {
664 self.pending_deletions.remove(&path);
667
668 if let Some(item) = self.current_item_mut() {
670 item.modified = true; item.layer_source = target_layer; }
673 self.set_pending_change(&path, value);
674 }
675 }
676
677 pub fn update_focus_states(&mut self) {
679 let current_focus = self.focus_panel();
680 for (page_idx, page) in self.pages.iter_mut().enumerate() {
681 for (item_idx, item) in page.items.iter_mut().enumerate() {
682 let is_focused = current_focus == FocusPanel::Settings
683 && page_idx == self.selected_category
684 && item_idx == self.selected_item;
685
686 let focus = if is_focused {
687 FocusState::Focused
688 } else {
689 FocusState::Normal
690 };
691
692 match &mut item.control {
693 SettingControl::Toggle(state) => state.focus = focus,
694 SettingControl::Number(state) => state.focus = focus,
695 SettingControl::Dropdown(state) => state.focus = focus,
696 SettingControl::Text(state) => state.focus = focus,
697 SettingControl::TextList(state) => state.focus = focus,
698 SettingControl::Map(state) => state.focus = focus,
699 SettingControl::ObjectArray(state) => state.focus = focus,
700 SettingControl::Json(state) => state.focus = focus,
701 SettingControl::Complex { .. } => {}
702 }
703 }
704 }
705 }
706
707 pub fn start_search(&mut self) {
709 self.search_active = true;
710 self.search_query.clear();
711 self.search_results.clear();
712 self.selected_search_result = 0;
713 self.search_scroll_offset = 0;
714 }
715
716 pub fn cancel_search(&mut self) {
718 self.search_active = false;
719 self.search_query.clear();
720 self.search_results.clear();
721 self.selected_search_result = 0;
722 self.search_scroll_offset = 0;
723 }
724
725 pub fn set_search_query(&mut self, query: String) {
727 self.search_query = query;
728 self.search_results = search_settings(&self.pages, &self.search_query);
729 self.selected_search_result = 0;
730 self.search_scroll_offset = 0;
731 }
732
733 pub fn search_push_char(&mut self, c: char) {
735 self.search_query.push(c);
736 self.search_results = search_settings(&self.pages, &self.search_query);
737 self.selected_search_result = 0;
738 self.search_scroll_offset = 0;
739 }
740
741 pub fn search_pop_char(&mut self) {
743 self.search_query.pop();
744 self.search_results = search_settings(&self.pages, &self.search_query);
745 self.selected_search_result = 0;
746 self.search_scroll_offset = 0;
747 }
748
749 pub fn search_prev(&mut self) {
751 if !self.search_results.is_empty() && self.selected_search_result > 0 {
752 self.selected_search_result -= 1;
753 if self.selected_search_result < self.search_scroll_offset {
755 self.search_scroll_offset = self.selected_search_result;
756 }
757 }
758 }
759
760 pub fn search_next(&mut self) {
762 if !self.search_results.is_empty()
763 && self.selected_search_result + 1 < self.search_results.len()
764 {
765 self.selected_search_result += 1;
766 if self.selected_search_result >= self.search_scroll_offset + self.search_max_visible {
768 self.search_scroll_offset =
769 self.selected_search_result - self.search_max_visible + 1;
770 }
771 }
772 }
773
774 pub fn search_scroll_up(&mut self, delta: usize) -> bool {
776 if self.search_results.is_empty() || self.search_scroll_offset == 0 {
777 return false;
778 }
779 self.search_scroll_offset = self.search_scroll_offset.saturating_sub(delta);
780 if self.selected_search_result >= self.search_scroll_offset + self.search_max_visible {
782 self.selected_search_result = self.search_scroll_offset + self.search_max_visible - 1;
783 }
784 true
785 }
786
787 pub fn search_scroll_down(&mut self, delta: usize) -> bool {
789 if self.search_results.is_empty() {
790 return false;
791 }
792 let max_offset = self
793 .search_results
794 .len()
795 .saturating_sub(self.search_max_visible);
796 if self.search_scroll_offset >= max_offset {
797 return false;
798 }
799 self.search_scroll_offset = (self.search_scroll_offset + delta).min(max_offset);
800 if self.selected_search_result < self.search_scroll_offset {
802 self.selected_search_result = self.search_scroll_offset;
803 }
804 true
805 }
806
807 pub fn search_scroll_to_ratio(&mut self, ratio: f32) -> bool {
809 if self.search_results.is_empty() {
810 return false;
811 }
812 let max_offset = self
813 .search_results
814 .len()
815 .saturating_sub(self.search_max_visible);
816 let new_offset = (ratio * max_offset as f32) as usize;
817 if new_offset != self.search_scroll_offset {
818 self.search_scroll_offset = new_offset.min(max_offset);
819 if self.selected_search_result < self.search_scroll_offset {
821 self.selected_search_result = self.search_scroll_offset;
822 } else if self.selected_search_result
823 >= self.search_scroll_offset + self.search_max_visible
824 {
825 self.selected_search_result =
826 self.search_scroll_offset + self.search_max_visible - 1;
827 }
828 return true;
829 }
830 false
831 }
832
833 pub fn jump_to_search_result(&mut self) {
835 let Some(&SearchResult {
837 page_index,
838 item_index,
839 ..
840 }) = self.search_results.get(self.selected_search_result)
841 else {
842 return;
843 };
844
845 self.update_control_focus(false);
847 self.selected_category = page_index;
848 self.selected_item = item_index;
849 self.focus.set(FocusPanel::Settings);
850 self.scroll_panel.scroll.offset = 0;
852 self.update_layout_widths();
854 if let Some(page) = self.pages.get(self.selected_category) {
855 self.scroll_panel.update_content_height(&page.items);
856 }
857 self.sub_focus = None;
858 self.init_map_focus(true);
859 self.update_control_focus(true); self.ensure_visible();
861 self.cancel_search();
862 }
863
864 pub fn current_search_result(&self) -> Option<&SearchResult> {
866 self.search_results.get(self.selected_search_result)
867 }
868
869 pub fn show_confirm_dialog(&mut self) {
871 self.showing_confirm_dialog = true;
872 self.confirm_dialog_selection = 0; }
874
875 pub fn hide_confirm_dialog(&mut self) {
877 self.showing_confirm_dialog = false;
878 self.confirm_dialog_selection = 0;
879 }
880
881 pub fn confirm_dialog_next(&mut self) {
883 self.confirm_dialog_selection = (self.confirm_dialog_selection + 1) % 3;
884 }
885
886 pub fn confirm_dialog_prev(&mut self) {
888 self.confirm_dialog_selection = if self.confirm_dialog_selection == 0 {
889 2
890 } else {
891 self.confirm_dialog_selection - 1
892 };
893 }
894
895 pub fn toggle_help(&mut self) {
897 self.showing_help = !self.showing_help;
898 }
899
900 pub fn hide_help(&mut self) {
902 self.showing_help = false;
903 }
904
905 pub fn showing_entry_dialog(&self) -> bool {
907 self.has_entry_dialog()
908 }
909
910 pub fn open_entry_dialog(&mut self) {
912 let Some(item) = self.current_item() else {
913 return;
914 };
915
916 let path = item.path.as_str();
918 let SettingControl::Map(map_state) = &item.control else {
919 return;
920 };
921
922 let Some(entry_idx) = map_state.focused_entry else {
924 return;
925 };
926 let Some((key, value)) = map_state.entries.get(entry_idx) else {
927 return;
928 };
929
930 let Some(schema) = map_state.value_schema.as_ref() else {
932 return; };
934
935 let no_delete = map_state.no_add;
937
938 let dialog =
940 EntryDialogState::from_schema(key.clone(), value, schema, path, false, no_delete);
941 self.entry_dialog_stack.push(dialog);
942 }
943
944 pub fn open_add_entry_dialog(&mut self) {
946 let Some(item) = self.current_item() else {
947 return;
948 };
949 let SettingControl::Map(map_state) = &item.control else {
950 return;
951 };
952 let Some(schema) = map_state.value_schema.as_ref() else {
953 return;
954 };
955 let path = item.path.clone();
956
957 let dialog = EntryDialogState::from_schema(
960 String::new(),
961 &serde_json::json!({}),
962 schema,
963 &path,
964 true,
965 false,
966 );
967 self.entry_dialog_stack.push(dialog);
968 }
969
970 pub fn open_add_array_item_dialog(&mut self) {
972 let Some(item) = self.current_item() else {
973 return;
974 };
975 let SettingControl::ObjectArray(array_state) = &item.control else {
976 return;
977 };
978 let Some(schema) = array_state.item_schema.as_ref() else {
979 return;
980 };
981 let path = item.path.clone();
982
983 let dialog =
985 EntryDialogState::for_array_item(None, &serde_json::json!({}), schema, &path, true);
986 self.entry_dialog_stack.push(dialog);
987 }
988
989 pub fn open_edit_array_item_dialog(&mut self) {
991 let Some(item) = self.current_item() else {
992 return;
993 };
994 let SettingControl::ObjectArray(array_state) = &item.control else {
995 return;
996 };
997 let Some(schema) = array_state.item_schema.as_ref() else {
998 return;
999 };
1000 let Some(index) = array_state.focused_index else {
1001 return;
1002 };
1003 let Some(value) = array_state.bindings.get(index) else {
1004 return;
1005 };
1006 let path = item.path.clone();
1007
1008 let dialog = EntryDialogState::for_array_item(Some(index), value, schema, &path, false);
1009 self.entry_dialog_stack.push(dialog);
1010 }
1011
1012 pub fn close_entry_dialog(&mut self) {
1014 self.entry_dialog_stack.pop();
1015 }
1016
1017 pub fn open_nested_entry_dialog(&mut self) {
1022 let nested_info = self.entry_dialog().and_then(|dialog| {
1024 let item = dialog.current_item()?;
1025 let path = format!("{}/{}", dialog.map_path, item.path.trim_start_matches('/'));
1026
1027 match &item.control {
1028 SettingControl::Map(map_state) => {
1029 let schema = map_state.value_schema.as_ref()?;
1030 let no_delete = map_state.no_add; if let Some(entry_idx) = map_state.focused_entry {
1032 let (key, value) = map_state.entries.get(entry_idx)?;
1034 Some(NestedDialogInfo::MapEntry {
1035 key: key.clone(),
1036 value: value.clone(),
1037 schema: schema.as_ref().clone(),
1038 path,
1039 is_new: false,
1040 no_delete,
1041 })
1042 } else {
1043 Some(NestedDialogInfo::MapEntry {
1045 key: String::new(),
1046 value: serde_json::json!({}),
1047 schema: schema.as_ref().clone(),
1048 path,
1049 is_new: true,
1050 no_delete: false, })
1052 }
1053 }
1054 SettingControl::ObjectArray(array_state) => {
1055 let schema = array_state.item_schema.as_ref()?;
1056 if let Some(index) = array_state.focused_index {
1057 let value = array_state.bindings.get(index)?;
1059 Some(NestedDialogInfo::ArrayItem {
1060 index: Some(index),
1061 value: value.clone(),
1062 schema: schema.as_ref().clone(),
1063 path,
1064 is_new: false,
1065 })
1066 } else {
1067 Some(NestedDialogInfo::ArrayItem {
1069 index: None,
1070 value: serde_json::json!({}),
1071 schema: schema.as_ref().clone(),
1072 path,
1073 is_new: true,
1074 })
1075 }
1076 }
1077 _ => None,
1078 }
1079 });
1080
1081 if let Some(info) = nested_info {
1083 let dialog = match info {
1084 NestedDialogInfo::MapEntry {
1085 key,
1086 value,
1087 schema,
1088 path,
1089 is_new,
1090 no_delete,
1091 } => EntryDialogState::from_schema(key, &value, &schema, &path, is_new, no_delete),
1092 NestedDialogInfo::ArrayItem {
1093 index,
1094 value,
1095 schema,
1096 path,
1097 is_new,
1098 } => EntryDialogState::for_array_item(index, &value, &schema, &path, is_new),
1099 };
1100 self.entry_dialog_stack.push(dialog);
1101 }
1102 }
1103
1104 pub fn save_entry_dialog(&mut self) {
1109 let is_array = if self.entry_dialog_stack.len() > 1 {
1113 self.entry_dialog_stack
1115 .get(self.entry_dialog_stack.len() - 2)
1116 .and_then(|parent| parent.current_item())
1117 .map(|item| matches!(item.control, SettingControl::ObjectArray(_)))
1118 .unwrap_or(false)
1119 } else {
1120 self.current_item()
1122 .map(|item| matches!(item.control, SettingControl::ObjectArray(_)))
1123 .unwrap_or(false)
1124 };
1125
1126 if is_array {
1127 self.save_array_item_dialog_inner();
1128 } else {
1129 self.save_map_entry_dialog_inner();
1130 }
1131 }
1132
1133 fn save_map_entry_dialog_inner(&mut self) {
1135 let Some(dialog) = self.entry_dialog_stack.pop() else {
1136 return;
1137 };
1138
1139 let key = dialog.get_key();
1141 if key.is_empty() {
1142 return; }
1144
1145 let value = dialog.to_value();
1146 let map_path = dialog.map_path.clone();
1147 let original_key = dialog.entry_key.clone();
1148 let is_new = dialog.is_new;
1149 let key_changed = !is_new && key != original_key;
1150
1151 if let Some(item) = self.current_item_mut() {
1153 if let SettingControl::Map(map_state) = &mut item.control {
1154 if key_changed {
1156 if let Some(idx) = map_state
1157 .entries
1158 .iter()
1159 .position(|(k, _)| k == &original_key)
1160 {
1161 map_state.entries.remove(idx);
1162 }
1163 }
1164
1165 if let Some(entry) = map_state.entries.iter_mut().find(|(k, _)| k == &key) {
1167 entry.1 = value.clone();
1168 } else {
1169 map_state.entries.push((key.clone(), value.clone()));
1170 map_state.entries.sort_by(|a, b| a.0.cmp(&b.0));
1171 }
1172 }
1173 }
1174
1175 if key_changed {
1177 let old_path = format!("{}/{}", map_path, original_key);
1178 self.pending_changes
1179 .insert(old_path, serde_json::Value::Null);
1180 }
1181
1182 let path = format!("{}/{}", map_path, key);
1184 self.set_pending_change(&path, value);
1185 }
1186
1187 fn save_array_item_dialog_inner(&mut self) {
1189 let Some(dialog) = self.entry_dialog_stack.pop() else {
1190 return;
1191 };
1192
1193 let value = dialog.to_value();
1194 let array_path = dialog.map_path.clone();
1195 let is_new = dialog.is_new;
1196 let entry_key = dialog.entry_key.clone();
1197
1198 let is_nested = !self.entry_dialog_stack.is_empty();
1200
1201 if is_nested {
1202 let array_field = array_path.rsplit('/').next().unwrap_or("").to_string();
1205 let item_path = format!("/{}", array_field);
1206
1207 if let Some(parent) = self.entry_dialog_stack.last_mut() {
1209 if let Some(item) = parent.items.iter_mut().find(|i| i.path == item_path) {
1210 if let SettingControl::ObjectArray(array_state) = &mut item.control {
1211 if is_new {
1212 array_state.bindings.push(value.clone());
1213 } else if let Ok(index) = entry_key.parse::<usize>() {
1214 if index < array_state.bindings.len() {
1215 array_state.bindings[index] = value.clone();
1216 }
1217 }
1218 }
1219 }
1220 }
1221
1222 if let Some(parent) = self.entry_dialog_stack.last() {
1225 if let Some(item) = parent.items.iter().find(|i| i.path == item_path) {
1226 if let SettingControl::ObjectArray(array_state) = &item.control {
1227 let array_value = serde_json::Value::Array(array_state.bindings.clone());
1228 self.set_pending_change(&array_path, array_value);
1229 }
1230 }
1231 }
1232 } else {
1233 if let Some(item) = self.current_item_mut() {
1235 if let SettingControl::ObjectArray(array_state) = &mut item.control {
1236 if is_new {
1237 array_state.bindings.push(value.clone());
1238 } else if let Ok(index) = entry_key.parse::<usize>() {
1239 if index < array_state.bindings.len() {
1240 array_state.bindings[index] = value.clone();
1241 }
1242 }
1243 }
1244 }
1245
1246 if let Some(item) = self.current_item() {
1248 if let SettingControl::ObjectArray(array_state) = &item.control {
1249 let array_value = serde_json::Value::Array(array_state.bindings.clone());
1250 self.set_pending_change(&array_path, array_value);
1251 }
1252 }
1253 }
1254 }
1255
1256 pub fn delete_entry_dialog(&mut self) {
1258 let is_nested = self.entry_dialog_stack.len() > 1;
1260
1261 let Some(dialog) = self.entry_dialog_stack.pop() else {
1262 return;
1263 };
1264
1265 let path = format!("{}/{}", dialog.map_path, dialog.entry_key);
1266
1267 if is_nested {
1269 let map_field = dialog.map_path.rsplit('/').next().unwrap_or("").to_string();
1272 let item_path = format!("/{}", map_field);
1273
1274 if let Some(parent) = self.entry_dialog_stack.last_mut() {
1276 if let Some(item) = parent.items.iter_mut().find(|i| i.path == item_path) {
1277 if let SettingControl::Map(map_state) = &mut item.control {
1278 if let Some(idx) = map_state
1279 .entries
1280 .iter()
1281 .position(|(k, _)| k == &dialog.entry_key)
1282 {
1283 map_state.remove_entry(idx);
1284 }
1285 }
1286 }
1287 }
1288 } else {
1289 if let Some(item) = self.current_item_mut() {
1291 if let SettingControl::Map(map_state) = &mut item.control {
1292 if let Some(idx) = map_state
1293 .entries
1294 .iter()
1295 .position(|(k, _)| k == &dialog.entry_key)
1296 {
1297 map_state.remove_entry(idx);
1298 }
1299 }
1300 }
1301 }
1302
1303 self.set_pending_change(&path, serde_json::Value::Null);
1305 }
1306
1307 pub fn max_scroll(&self) -> u16 {
1309 self.scroll_panel.scroll.max_offset()
1310 }
1311
1312 pub fn scroll_up(&mut self, delta: usize) -> bool {
1315 let old = self.scroll_panel.scroll.offset;
1316 self.scroll_panel.scroll_up(delta as u16);
1317 old != self.scroll_panel.scroll.offset
1318 }
1319
1320 pub fn scroll_down(&mut self, delta: usize) -> bool {
1323 let old = self.scroll_panel.scroll.offset;
1324 self.scroll_panel.scroll_down(delta as u16);
1325 old != self.scroll_panel.scroll.offset
1326 }
1327
1328 pub fn scroll_to_ratio(&mut self, ratio: f32) -> bool {
1331 let old = self.scroll_panel.scroll.offset;
1332 self.scroll_panel.scroll_to_ratio(ratio);
1333 old != self.scroll_panel.scroll.offset
1334 }
1335
1336 pub fn is_number_control(&self) -> bool {
1339 self.current_item()
1340 .is_some_and(|item| matches!(item.control, SettingControl::Number(_)))
1341 }
1342
1343 pub fn start_editing(&mut self) {
1344 if let Some(item) = self.current_item() {
1345 if matches!(
1346 item.control,
1347 SettingControl::TextList(_)
1348 | SettingControl::Text(_)
1349 | SettingControl::Map(_)
1350 | SettingControl::Json(_)
1351 ) {
1352 self.editing_text = true;
1353 }
1354 }
1355 }
1356
1357 pub fn stop_editing(&mut self) {
1359 self.editing_text = false;
1360 }
1361
1362 pub fn is_editable_control(&self) -> bool {
1364 self.current_item().is_some_and(|item| {
1365 matches!(
1366 item.control,
1367 SettingControl::TextList(_)
1368 | SettingControl::Text(_)
1369 | SettingControl::Map(_)
1370 | SettingControl::Json(_)
1371 )
1372 })
1373 }
1374
1375 pub fn is_editing_json(&self) -> bool {
1377 if !self.editing_text {
1378 return false;
1379 }
1380 self.current_item()
1381 .map(|item| matches!(&item.control, SettingControl::Json(_)))
1382 .unwrap_or(false)
1383 }
1384
1385 pub fn text_insert(&mut self, c: char) {
1387 if let Some(item) = self.current_item_mut() {
1388 match &mut item.control {
1389 SettingControl::TextList(state) => state.insert(c),
1390 SettingControl::Text(state) => {
1391 state.value.insert(state.cursor, c);
1392 state.cursor += c.len_utf8();
1393 }
1394 SettingControl::Map(state) => {
1395 state.new_key_text.insert(state.cursor, c);
1396 state.cursor += c.len_utf8();
1397 }
1398 SettingControl::Json(state) => state.insert(c),
1399 _ => {}
1400 }
1401 }
1402 }
1403
1404 pub fn text_backspace(&mut self) {
1406 if let Some(item) = self.current_item_mut() {
1407 match &mut item.control {
1408 SettingControl::TextList(state) => state.backspace(),
1409 SettingControl::Text(state) => {
1410 if state.cursor > 0 {
1411 let mut char_start = state.cursor - 1;
1412 while char_start > 0 && !state.value.is_char_boundary(char_start) {
1413 char_start -= 1;
1414 }
1415 state.value.remove(char_start);
1416 state.cursor = char_start;
1417 }
1418 }
1419 SettingControl::Map(state) => {
1420 if state.cursor > 0 {
1421 let mut char_start = state.cursor - 1;
1422 while char_start > 0 && !state.new_key_text.is_char_boundary(char_start) {
1423 char_start -= 1;
1424 }
1425 state.new_key_text.remove(char_start);
1426 state.cursor = char_start;
1427 }
1428 }
1429 SettingControl::Json(state) => state.backspace(),
1430 _ => {}
1431 }
1432 }
1433 }
1434
1435 pub fn text_move_left(&mut self) {
1437 if let Some(item) = self.current_item_mut() {
1438 match &mut item.control {
1439 SettingControl::TextList(state) => state.move_left(),
1440 SettingControl::Text(state) => {
1441 if state.cursor > 0 {
1442 let mut new_pos = state.cursor - 1;
1443 while new_pos > 0 && !state.value.is_char_boundary(new_pos) {
1444 new_pos -= 1;
1445 }
1446 state.cursor = new_pos;
1447 }
1448 }
1449 SettingControl::Map(state) => {
1450 if state.cursor > 0 {
1451 let mut new_pos = state.cursor - 1;
1452 while new_pos > 0 && !state.new_key_text.is_char_boundary(new_pos) {
1453 new_pos -= 1;
1454 }
1455 state.cursor = new_pos;
1456 }
1457 }
1458 SettingControl::Json(state) => state.move_left(),
1459 _ => {}
1460 }
1461 }
1462 }
1463
1464 pub fn text_move_right(&mut self) {
1466 if let Some(item) = self.current_item_mut() {
1467 match &mut item.control {
1468 SettingControl::TextList(state) => state.move_right(),
1469 SettingControl::Text(state) => {
1470 if state.cursor < state.value.len() {
1471 let mut new_pos = state.cursor + 1;
1472 while new_pos < state.value.len() && !state.value.is_char_boundary(new_pos)
1473 {
1474 new_pos += 1;
1475 }
1476 state.cursor = new_pos;
1477 }
1478 }
1479 SettingControl::Map(state) => {
1480 if state.cursor < state.new_key_text.len() {
1481 let mut new_pos = state.cursor + 1;
1482 while new_pos < state.new_key_text.len()
1483 && !state.new_key_text.is_char_boundary(new_pos)
1484 {
1485 new_pos += 1;
1486 }
1487 state.cursor = new_pos;
1488 }
1489 }
1490 SettingControl::Json(state) => state.move_right(),
1491 _ => {}
1492 }
1493 }
1494 }
1495
1496 pub fn text_focus_prev(&mut self) {
1498 if let Some(item) = self.current_item_mut() {
1499 match &mut item.control {
1500 SettingControl::TextList(state) => state.focus_prev(),
1501 SettingControl::Map(state) => {
1502 state.focus_prev();
1503 }
1504 _ => {}
1505 }
1506 }
1507 }
1508
1509 pub fn text_focus_next(&mut self) {
1511 if let Some(item) = self.current_item_mut() {
1512 match &mut item.control {
1513 SettingControl::TextList(state) => state.focus_next(),
1514 SettingControl::Map(state) => {
1515 state.focus_next();
1516 }
1517 _ => {}
1518 }
1519 }
1520 }
1521
1522 pub fn text_add_item(&mut self) {
1524 if let Some(item) = self.current_item_mut() {
1525 match &mut item.control {
1526 SettingControl::TextList(state) => state.add_item(),
1527 SettingControl::Map(state) => state.add_entry_from_input(),
1528 _ => {}
1529 }
1530 }
1531 self.on_value_changed();
1533 }
1534
1535 pub fn text_remove_focused(&mut self) {
1537 if let Some(item) = self.current_item_mut() {
1538 match &mut item.control {
1539 SettingControl::TextList(state) => {
1540 if let Some(idx) = state.focused_item {
1541 state.remove_item(idx);
1542 }
1543 }
1544 SettingControl::Map(state) => {
1545 if let Some(idx) = state.focused_entry {
1546 state.remove_entry(idx);
1547 }
1548 }
1549 _ => {}
1550 }
1551 }
1552 self.on_value_changed();
1554 }
1555
1556 pub fn json_cursor_up(&mut self) {
1560 if let Some(item) = self.current_item_mut() {
1561 if let SettingControl::Json(state) = &mut item.control {
1562 state.move_up();
1563 }
1564 }
1565 }
1566
1567 pub fn json_cursor_down(&mut self) {
1569 if let Some(item) = self.current_item_mut() {
1570 if let SettingControl::Json(state) = &mut item.control {
1571 state.move_down();
1572 }
1573 }
1574 }
1575
1576 pub fn json_insert_newline(&mut self) {
1578 if let Some(item) = self.current_item_mut() {
1579 if let SettingControl::Json(state) = &mut item.control {
1580 state.insert('\n');
1581 }
1582 }
1583 }
1584
1585 pub fn json_delete(&mut self) {
1587 if let Some(item) = self.current_item_mut() {
1588 if let SettingControl::Json(state) = &mut item.control {
1589 state.delete();
1590 }
1591 }
1592 }
1593
1594 pub fn json_exit_editing(&mut self) {
1596 let is_valid = self
1597 .current_item()
1598 .map(|item| {
1599 if let SettingControl::Json(state) = &item.control {
1600 state.is_valid()
1601 } else {
1602 true
1603 }
1604 })
1605 .unwrap_or(true);
1606
1607 if is_valid {
1608 if let Some(item) = self.current_item_mut() {
1609 if let SettingControl::Json(state) = &mut item.control {
1610 state.commit();
1611 }
1612 }
1613 self.on_value_changed();
1614 } else if let Some(item) = self.current_item_mut() {
1615 if let SettingControl::Json(state) = &mut item.control {
1616 state.revert();
1617 }
1618 }
1619 self.editing_text = false;
1620 }
1621
1622 pub fn json_select_all(&mut self) {
1624 if let Some(item) = self.current_item_mut() {
1625 if let SettingControl::Json(state) = &mut item.control {
1626 state.select_all();
1627 }
1628 }
1629 }
1630
1631 pub fn json_selected_text(&self) -> Option<String> {
1633 if let Some(item) = self.current_item() {
1634 if let SettingControl::Json(state) = &item.control {
1635 return state.selected_text();
1636 }
1637 }
1638 None
1639 }
1640
1641 pub fn json_cursor_up_selecting(&mut self) {
1643 if let Some(item) = self.current_item_mut() {
1644 if let SettingControl::Json(state) = &mut item.control {
1645 state.editor.move_up_selecting();
1646 }
1647 }
1648 }
1649
1650 pub fn json_cursor_down_selecting(&mut self) {
1652 if let Some(item) = self.current_item_mut() {
1653 if let SettingControl::Json(state) = &mut item.control {
1654 state.editor.move_down_selecting();
1655 }
1656 }
1657 }
1658
1659 pub fn json_cursor_left_selecting(&mut self) {
1661 if let Some(item) = self.current_item_mut() {
1662 if let SettingControl::Json(state) = &mut item.control {
1663 state.editor.move_left_selecting();
1664 }
1665 }
1666 }
1667
1668 pub fn json_cursor_right_selecting(&mut self) {
1670 if let Some(item) = self.current_item_mut() {
1671 if let SettingControl::Json(state) = &mut item.control {
1672 state.editor.move_right_selecting();
1673 }
1674 }
1675 }
1676
1677 pub fn is_dropdown_open(&self) -> bool {
1681 self.current_item().is_some_and(|item| {
1682 if let SettingControl::Dropdown(ref d) = item.control {
1683 d.open
1684 } else {
1685 false
1686 }
1687 })
1688 }
1689
1690 pub fn dropdown_toggle(&mut self) {
1692 let mut opened = false;
1693 if let Some(item) = self.current_item_mut() {
1694 if let SettingControl::Dropdown(ref mut d) = item.control {
1695 d.toggle_open();
1696 opened = d.open;
1697 }
1698 }
1699
1700 if opened {
1702 self.update_layout_widths();
1704 let selected_item = self.selected_item;
1705 if let Some(page) = self.pages.get(self.selected_category) {
1706 self.scroll_panel.update_content_height(&page.items);
1707 self.scroll_panel
1709 .ensure_focused_visible(&page.items, selected_item, None);
1710 }
1711 }
1712 }
1713
1714 pub fn dropdown_prev(&mut self) {
1716 if let Some(item) = self.current_item_mut() {
1717 if let SettingControl::Dropdown(ref mut d) = item.control {
1718 d.select_prev();
1719 }
1720 }
1721 }
1722
1723 pub fn dropdown_next(&mut self) {
1725 if let Some(item) = self.current_item_mut() {
1726 if let SettingControl::Dropdown(ref mut d) = item.control {
1727 d.select_next();
1728 }
1729 }
1730 }
1731
1732 pub fn dropdown_home(&mut self) {
1734 if let Some(item) = self.current_item_mut() {
1735 if let SettingControl::Dropdown(ref mut d) = item.control {
1736 if !d.options.is_empty() {
1737 d.selected = 0;
1738 d.ensure_visible();
1739 }
1740 }
1741 }
1742 }
1743
1744 pub fn dropdown_end(&mut self) {
1746 if let Some(item) = self.current_item_mut() {
1747 if let SettingControl::Dropdown(ref mut d) = item.control {
1748 if !d.options.is_empty() {
1749 d.selected = d.options.len() - 1;
1750 d.ensure_visible();
1751 }
1752 }
1753 }
1754 }
1755
1756 pub fn dropdown_confirm(&mut self) {
1758 if let Some(item) = self.current_item_mut() {
1759 if let SettingControl::Dropdown(ref mut d) = item.control {
1760 d.confirm();
1761 }
1762 }
1763 self.on_value_changed();
1764 }
1765
1766 pub fn dropdown_cancel(&mut self) {
1768 if let Some(item) = self.current_item_mut() {
1769 if let SettingControl::Dropdown(ref mut d) = item.control {
1770 d.cancel();
1771 }
1772 }
1773 }
1774
1775 pub fn dropdown_select(&mut self, option_idx: usize) {
1777 if let Some(item) = self.current_item_mut() {
1778 if let SettingControl::Dropdown(ref mut d) = item.control {
1779 if option_idx < d.options.len() {
1780 d.selected = option_idx;
1781 d.confirm();
1782 }
1783 }
1784 }
1785 self.on_value_changed();
1786 }
1787
1788 pub fn set_dropdown_hover(&mut self, hover_idx: Option<usize>) -> bool {
1791 if let Some(item) = self.current_item_mut() {
1792 if let SettingControl::Dropdown(ref mut d) = item.control {
1793 if d.open && d.hover_index != hover_idx {
1794 d.hover_index = hover_idx;
1795 return true;
1796 }
1797 }
1798 }
1799 false
1800 }
1801
1802 pub fn dropdown_scroll(&mut self, delta: i32) {
1804 if let Some(item) = self.current_item_mut() {
1805 if let SettingControl::Dropdown(ref mut d) = item.control {
1806 if d.open {
1807 d.scroll_by(delta);
1808 }
1809 }
1810 }
1811 }
1812
1813 pub fn is_number_editing(&self) -> bool {
1817 self.current_item().is_some_and(|item| {
1818 if let SettingControl::Number(ref n) = item.control {
1819 n.editing()
1820 } else {
1821 false
1822 }
1823 })
1824 }
1825
1826 pub fn start_number_editing(&mut self) {
1828 if let Some(item) = self.current_item_mut() {
1829 if let SettingControl::Number(ref mut n) = item.control {
1830 n.start_editing();
1831 }
1832 }
1833 }
1834
1835 pub fn number_insert(&mut self, c: char) {
1837 if let Some(item) = self.current_item_mut() {
1838 if let SettingControl::Number(ref mut n) = item.control {
1839 n.insert_char(c);
1840 }
1841 }
1842 }
1843
1844 pub fn number_backspace(&mut self) {
1846 if let Some(item) = self.current_item_mut() {
1847 if let SettingControl::Number(ref mut n) = item.control {
1848 n.backspace();
1849 }
1850 }
1851 }
1852
1853 pub fn number_confirm(&mut self) {
1855 if let Some(item) = self.current_item_mut() {
1856 if let SettingControl::Number(ref mut n) = item.control {
1857 n.confirm_editing();
1858 }
1859 }
1860 self.on_value_changed();
1861 }
1862
1863 pub fn number_cancel(&mut self) {
1865 if let Some(item) = self.current_item_mut() {
1866 if let SettingControl::Number(ref mut n) = item.control {
1867 n.cancel_editing();
1868 }
1869 }
1870 }
1871
1872 pub fn number_delete(&mut self) {
1874 if let Some(item) = self.current_item_mut() {
1875 if let SettingControl::Number(ref mut n) = item.control {
1876 n.delete();
1877 }
1878 }
1879 }
1880
1881 pub fn number_move_left(&mut self) {
1883 if let Some(item) = self.current_item_mut() {
1884 if let SettingControl::Number(ref mut n) = item.control {
1885 n.move_left();
1886 }
1887 }
1888 }
1889
1890 pub fn number_move_right(&mut self) {
1892 if let Some(item) = self.current_item_mut() {
1893 if let SettingControl::Number(ref mut n) = item.control {
1894 n.move_right();
1895 }
1896 }
1897 }
1898
1899 pub fn number_move_home(&mut self) {
1901 if let Some(item) = self.current_item_mut() {
1902 if let SettingControl::Number(ref mut n) = item.control {
1903 n.move_home();
1904 }
1905 }
1906 }
1907
1908 pub fn number_move_end(&mut self) {
1910 if let Some(item) = self.current_item_mut() {
1911 if let SettingControl::Number(ref mut n) = item.control {
1912 n.move_end();
1913 }
1914 }
1915 }
1916
1917 pub fn number_move_left_selecting(&mut self) {
1919 if let Some(item) = self.current_item_mut() {
1920 if let SettingControl::Number(ref mut n) = item.control {
1921 n.move_left_selecting();
1922 }
1923 }
1924 }
1925
1926 pub fn number_move_right_selecting(&mut self) {
1928 if let Some(item) = self.current_item_mut() {
1929 if let SettingControl::Number(ref mut n) = item.control {
1930 n.move_right_selecting();
1931 }
1932 }
1933 }
1934
1935 pub fn number_move_home_selecting(&mut self) {
1937 if let Some(item) = self.current_item_mut() {
1938 if let SettingControl::Number(ref mut n) = item.control {
1939 n.move_home_selecting();
1940 }
1941 }
1942 }
1943
1944 pub fn number_move_end_selecting(&mut self) {
1946 if let Some(item) = self.current_item_mut() {
1947 if let SettingControl::Number(ref mut n) = item.control {
1948 n.move_end_selecting();
1949 }
1950 }
1951 }
1952
1953 pub fn number_move_word_left(&mut self) {
1955 if let Some(item) = self.current_item_mut() {
1956 if let SettingControl::Number(ref mut n) = item.control {
1957 n.move_word_left();
1958 }
1959 }
1960 }
1961
1962 pub fn number_move_word_right(&mut self) {
1964 if let Some(item) = self.current_item_mut() {
1965 if let SettingControl::Number(ref mut n) = item.control {
1966 n.move_word_right();
1967 }
1968 }
1969 }
1970
1971 pub fn number_move_word_left_selecting(&mut self) {
1973 if let Some(item) = self.current_item_mut() {
1974 if let SettingControl::Number(ref mut n) = item.control {
1975 n.move_word_left_selecting();
1976 }
1977 }
1978 }
1979
1980 pub fn number_move_word_right_selecting(&mut self) {
1982 if let Some(item) = self.current_item_mut() {
1983 if let SettingControl::Number(ref mut n) = item.control {
1984 n.move_word_right_selecting();
1985 }
1986 }
1987 }
1988
1989 pub fn number_select_all(&mut self) {
1991 if let Some(item) = self.current_item_mut() {
1992 if let SettingControl::Number(ref mut n) = item.control {
1993 n.select_all();
1994 }
1995 }
1996 }
1997
1998 pub fn number_delete_word_backward(&mut self) {
2000 if let Some(item) = self.current_item_mut() {
2001 if let SettingControl::Number(ref mut n) = item.control {
2002 n.delete_word_backward();
2003 }
2004 }
2005 }
2006
2007 pub fn number_delete_word_forward(&mut self) {
2009 if let Some(item) = self.current_item_mut() {
2010 if let SettingControl::Number(ref mut n) = item.control {
2011 n.delete_word_forward();
2012 }
2013 }
2014 }
2015
2016 pub fn get_change_descriptions(&self) -> Vec<String> {
2018 let mut descriptions: Vec<String> = self
2019 .pending_changes
2020 .iter()
2021 .map(|(path, value)| {
2022 let value_str = match value {
2023 serde_json::Value::Bool(b) => b.to_string(),
2024 serde_json::Value::Number(n) => n.to_string(),
2025 serde_json::Value::String(s) => format!("\"{}\"", s),
2026 _ => value.to_string(),
2027 };
2028 format!("{}: {}", path, value_str)
2029 })
2030 .collect();
2031 for path in &self.pending_deletions {
2033 descriptions.push(format!("{}: (reset to default)", path));
2034 }
2035 descriptions.sort();
2036 descriptions
2037 }
2038}
2039
2040fn update_control_from_value(control: &mut SettingControl, value: &serde_json::Value) {
2042 match control {
2043 SettingControl::Toggle(state) => {
2044 if let Some(b) = value.as_bool() {
2045 state.checked = b;
2046 }
2047 }
2048 SettingControl::Number(state) => {
2049 if let Some(n) = value.as_i64() {
2050 state.value = n;
2051 }
2052 }
2053 SettingControl::Dropdown(state) => {
2054 if let Some(s) = value.as_str() {
2055 if let Some(idx) = state.options.iter().position(|o| o == s) {
2056 state.selected = idx;
2057 }
2058 }
2059 }
2060 SettingControl::Text(state) => {
2061 if let Some(s) = value.as_str() {
2062 state.value = s.to_string();
2063 state.cursor = state.value.len();
2064 }
2065 }
2066 SettingControl::TextList(state) => {
2067 if let Some(arr) = value.as_array() {
2068 state.items = arr
2069 .iter()
2070 .filter_map(|v| {
2071 if state.is_integer {
2072 v.as_i64()
2073 .map(|n| n.to_string())
2074 .or_else(|| v.as_u64().map(|n| n.to_string()))
2075 .or_else(|| v.as_f64().map(|n| n.to_string()))
2076 } else {
2077 v.as_str().map(String::from)
2078 }
2079 })
2080 .collect();
2081 }
2082 }
2083 SettingControl::Map(state) => {
2084 if let Some(obj) = value.as_object() {
2085 state.entries = obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
2086 state.entries.sort_by(|a, b| a.0.cmp(&b.0));
2087 }
2088 }
2089 SettingControl::ObjectArray(state) => {
2090 if let Some(arr) = value.as_array() {
2091 state.bindings = arr.clone();
2092 }
2093 }
2094 SettingControl::Json(state) => {
2095 let json_str =
2097 serde_json::to_string_pretty(value).unwrap_or_else(|_| "null".to_string());
2098 let json_str = if json_str.is_empty() {
2099 "null".to_string()
2100 } else {
2101 json_str
2102 };
2103 state.original_text = json_str.clone();
2104 state.editor.set_value(&json_str);
2105 state.scroll_offset = 0;
2106 }
2107 SettingControl::Complex { .. } => {}
2108 }
2109}
2110
2111#[cfg(test)]
2112mod tests {
2113 use super::*;
2114
2115 const TEST_SCHEMA: &str = r#"
2116{
2117 "type": "object",
2118 "properties": {
2119 "theme": {
2120 "type": "string",
2121 "default": "dark"
2122 },
2123 "line_numbers": {
2124 "type": "boolean",
2125 "default": true
2126 }
2127 },
2128 "$defs": {}
2129}
2130"#;
2131
2132 fn test_config() -> Config {
2133 Config::default()
2134 }
2135
2136 #[test]
2137 fn test_settings_state_creation() {
2138 let config = test_config();
2139 let state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2140
2141 assert!(!state.visible);
2142 assert_eq!(state.selected_category, 0);
2143 assert!(!state.has_changes());
2144 }
2145
2146 #[test]
2147 fn test_navigation() {
2148 let config = test_config();
2149 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2150
2151 assert_eq!(state.focus_panel(), FocusPanel::Categories);
2153
2154 state.toggle_focus();
2156 assert_eq!(state.focus_panel(), FocusPanel::Settings);
2157
2158 state.select_next();
2160 assert_eq!(state.selected_item, 1);
2161
2162 state.select_prev();
2163 assert_eq!(state.selected_item, 0);
2164 }
2165
2166 #[test]
2167 fn test_pending_changes() {
2168 let config = test_config();
2169 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2170
2171 assert!(!state.has_changes());
2172
2173 state.set_pending_change("/theme", serde_json::Value::String("light".to_string()));
2174 assert!(state.has_changes());
2175
2176 state.discard_changes();
2177 assert!(!state.has_changes());
2178 }
2179
2180 #[test]
2181 fn test_show_hide() {
2182 let config = test_config();
2183 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2184
2185 assert!(!state.visible);
2186
2187 state.show();
2188 assert!(state.visible);
2189 assert_eq!(state.focus_panel(), FocusPanel::Categories);
2190
2191 state.hide();
2192 assert!(!state.visible);
2193 }
2194
2195 const TEST_SCHEMA_CONTROLS: &str = r#"
2197{
2198 "type": "object",
2199 "properties": {
2200 "theme": {
2201 "type": "string",
2202 "enum": ["dark", "light", "high-contrast"],
2203 "default": "dark"
2204 },
2205 "tab_size": {
2206 "type": "integer",
2207 "minimum": 1,
2208 "maximum": 8,
2209 "default": 4
2210 },
2211 "line_numbers": {
2212 "type": "boolean",
2213 "default": true
2214 }
2215 },
2216 "$defs": {}
2217}
2218"#;
2219
2220 #[test]
2221 fn test_dropdown_toggle() {
2222 let config = test_config();
2223 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2224 state.show();
2225 state.toggle_focus(); state.select_next();
2230 state.select_next();
2231 assert!(!state.is_dropdown_open());
2232
2233 state.dropdown_toggle();
2234 assert!(state.is_dropdown_open());
2235
2236 state.dropdown_toggle();
2237 assert!(!state.is_dropdown_open());
2238 }
2239
2240 #[test]
2241 fn test_dropdown_cancel_restores() {
2242 let config = test_config();
2243 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2244 state.show();
2245 state.toggle_focus();
2246
2247 state.select_next();
2250 state.select_next();
2251
2252 state.dropdown_toggle();
2254 assert!(state.is_dropdown_open());
2255
2256 let initial = state.current_item().and_then(|item| {
2258 if let SettingControl::Dropdown(ref d) = item.control {
2259 Some(d.selected)
2260 } else {
2261 None
2262 }
2263 });
2264
2265 state.dropdown_next();
2267 let after_change = state.current_item().and_then(|item| {
2268 if let SettingControl::Dropdown(ref d) = item.control {
2269 Some(d.selected)
2270 } else {
2271 None
2272 }
2273 });
2274 assert_ne!(initial, after_change);
2275
2276 state.dropdown_cancel();
2278 assert!(!state.is_dropdown_open());
2279
2280 let after_cancel = state.current_item().and_then(|item| {
2281 if let SettingControl::Dropdown(ref d) = item.control {
2282 Some(d.selected)
2283 } else {
2284 None
2285 }
2286 });
2287 assert_eq!(initial, after_cancel);
2288 }
2289
2290 #[test]
2291 fn test_dropdown_confirm_keeps_selection() {
2292 let config = test_config();
2293 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2294 state.show();
2295 state.toggle_focus();
2296
2297 state.dropdown_toggle();
2299
2300 state.dropdown_next();
2302 let after_change = state.current_item().and_then(|item| {
2303 if let SettingControl::Dropdown(ref d) = item.control {
2304 Some(d.selected)
2305 } else {
2306 None
2307 }
2308 });
2309
2310 state.dropdown_confirm();
2312 assert!(!state.is_dropdown_open());
2313
2314 let after_confirm = state.current_item().and_then(|item| {
2315 if let SettingControl::Dropdown(ref d) = item.control {
2316 Some(d.selected)
2317 } else {
2318 None
2319 }
2320 });
2321 assert_eq!(after_change, after_confirm);
2322 }
2323
2324 #[test]
2325 fn test_number_editing() {
2326 let config = test_config();
2327 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2328 state.show();
2329 state.toggle_focus();
2330
2331 state.select_next();
2333
2334 assert!(!state.is_number_editing());
2336
2337 state.start_number_editing();
2339 assert!(state.is_number_editing());
2340
2341 state.number_insert('8');
2343
2344 state.number_confirm();
2346 assert!(!state.is_number_editing());
2347 }
2348
2349 #[test]
2350 fn test_number_cancel_editing() {
2351 let config = test_config();
2352 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2353 state.show();
2354 state.toggle_focus();
2355
2356 state.select_next();
2358
2359 let initial_value = state.current_item().and_then(|item| {
2361 if let SettingControl::Number(ref n) = item.control {
2362 Some(n.value)
2363 } else {
2364 None
2365 }
2366 });
2367
2368 state.start_number_editing();
2370 state.number_backspace();
2371 state.number_insert('9');
2372 state.number_insert('9');
2373
2374 state.number_cancel();
2376 assert!(!state.is_number_editing());
2377
2378 let after_cancel = state.current_item().and_then(|item| {
2380 if let SettingControl::Number(ref n) = item.control {
2381 Some(n.value)
2382 } else {
2383 None
2384 }
2385 });
2386 assert_eq!(initial_value, after_cancel);
2387 }
2388
2389 #[test]
2390 fn test_number_backspace() {
2391 let config = test_config();
2392 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2393 state.show();
2394 state.toggle_focus();
2395 state.select_next();
2396
2397 state.start_number_editing();
2398 state.number_backspace();
2399
2400 let display_text = state.current_item().and_then(|item| {
2402 if let SettingControl::Number(ref n) = item.control {
2403 Some(n.display_text())
2404 } else {
2405 None
2406 }
2407 });
2408 assert_eq!(display_text, Some(String::new()));
2410
2411 state.number_cancel();
2412 }
2413
2414 #[test]
2415 fn test_layer_selection() {
2416 let config = test_config();
2417 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2418
2419 assert_eq!(state.target_layer, ConfigLayer::User);
2421 assert_eq!(state.target_layer_name(), "User");
2422
2423 state.cycle_target_layer();
2425 assert_eq!(state.target_layer, ConfigLayer::Project);
2426 assert_eq!(state.target_layer_name(), "Project");
2427
2428 state.cycle_target_layer();
2429 assert_eq!(state.target_layer, ConfigLayer::Session);
2430 assert_eq!(state.target_layer_name(), "Session");
2431
2432 state.cycle_target_layer();
2433 assert_eq!(state.target_layer, ConfigLayer::User);
2434
2435 state.set_target_layer(ConfigLayer::Project);
2437 assert_eq!(state.target_layer, ConfigLayer::Project);
2438
2439 state.set_target_layer(ConfigLayer::System);
2441 assert_eq!(state.target_layer, ConfigLayer::Project);
2442 }
2443
2444 #[test]
2445 fn test_layer_switch_clears_pending_changes() {
2446 let config = test_config();
2447 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2448
2449 state.set_pending_change("/theme", serde_json::Value::String("light".to_string()));
2451 assert!(state.has_changes());
2452
2453 state.cycle_target_layer();
2455 assert!(!state.has_changes());
2456 }
2457}