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::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_panel: 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 showing_confirm_dialog: bool,
79 pub confirm_dialog_selection: usize,
81 pub showing_help: bool,
83 pub scroll_panel: ScrollablePanel,
85 pub sub_focus: Option<usize>,
87 pub editing_text: bool,
89 pub hover_position: Option<(u16, u16)>,
91 pub hover_hit: Option<SettingsHit>,
93 pub entry_dialog_stack: Vec<EntryDialogState>,
96 pub target_layer: ConfigLayer,
100 pub layer_sources: HashMap<String, ConfigLayer>,
104 pub pending_deletions: std::collections::HashSet<String>,
108}
109
110impl SettingsState {
111 pub fn new(schema_json: &str, config: &Config) -> Result<Self, serde_json::Error> {
113 let categories = parse_schema(schema_json)?;
114 let config_value = serde_json::to_value(config)?;
115 let layer_sources = HashMap::new(); let target_layer = ConfigLayer::User; let pages =
118 super::items::build_pages(&categories, &config_value, &layer_sources, target_layer);
119
120 Ok(Self {
121 categories,
122 pages,
123 selected_category: 0,
124 selected_item: 0,
125 focus_panel: FocusPanel::Categories,
126 footer_button_index: 2, pending_changes: HashMap::new(),
128 original_config: config_value,
129 visible: false,
130 search_query: String::new(),
131 search_active: false,
132 search_results: Vec::new(),
133 selected_search_result: 0,
134 showing_confirm_dialog: false,
135 confirm_dialog_selection: 0,
136 showing_help: false,
137 scroll_panel: ScrollablePanel::new(),
138 sub_focus: None,
139 editing_text: false,
140 hover_position: None,
141 hover_hit: None,
142 entry_dialog_stack: Vec::new(),
143 target_layer,
144 layer_sources,
145 pending_deletions: std::collections::HashSet::new(),
146 })
147 }
148
149 pub fn show(&mut self) {
151 self.visible = true;
152 self.focus_panel = FocusPanel::Categories;
153 self.footer_button_index = 2; self.selected_category = 0;
155 self.selected_item = 0;
156 self.scroll_panel = ScrollablePanel::new();
157 self.sub_focus = None;
158 }
159
160 pub fn hide(&mut self) {
162 self.visible = false;
163 self.search_active = false;
164 self.search_query.clear();
165 }
166
167 pub fn entry_dialog(&self) -> Option<&EntryDialogState> {
169 self.entry_dialog_stack.last()
170 }
171
172 pub fn entry_dialog_mut(&mut self) -> Option<&mut EntryDialogState> {
174 self.entry_dialog_stack.last_mut()
175 }
176
177 pub fn has_entry_dialog(&self) -> bool {
179 !self.entry_dialog_stack.is_empty()
180 }
181
182 pub fn current_page(&self) -> Option<&SettingsPage> {
184 self.pages.get(self.selected_category)
185 }
186
187 pub fn current_page_mut(&mut self) -> Option<&mut SettingsPage> {
189 self.pages.get_mut(self.selected_category)
190 }
191
192 pub fn current_item(&self) -> Option<&SettingItem> {
194 self.current_page()
195 .and_then(|page| page.items.get(self.selected_item))
196 }
197
198 pub fn current_item_mut(&mut self) -> Option<&mut SettingItem> {
200 self.pages
201 .get_mut(self.selected_category)
202 .and_then(|page| page.items.get_mut(self.selected_item))
203 }
204
205 pub fn can_exit_text_editing(&self) -> bool {
207 self.current_item()
208 .map(|item| {
209 if let SettingControl::Text(state) = &item.control {
210 state.is_valid()
211 } else {
212 true
213 }
214 })
215 .unwrap_or(true)
216 }
217
218 pub fn entry_dialog_can_exit_text_editing(&self) -> bool {
220 self.entry_dialog()
221 .and_then(|dialog| dialog.current_item())
222 .map(|item| {
223 if let SettingControl::Text(state) = &item.control {
224 state.is_valid()
225 } else {
226 true
227 }
228 })
229 .unwrap_or(true)
230 }
231
232 fn init_map_focus(&mut self, from_above: bool) {
235 if let Some(item) = self.current_item_mut() {
236 if let SettingControl::Map(ref mut map_state) = item.control {
237 map_state.init_focus(from_above);
238 }
239 }
240 }
241
242 pub fn select_prev(&mut self) {
244 match self.focus_panel {
245 FocusPanel::Categories => {
246 if self.selected_category > 0 {
247 self.selected_category -= 1;
248 self.selected_item = 0;
249 self.scroll_panel = ScrollablePanel::new();
250 self.sub_focus = None;
251 }
252 }
253 FocusPanel::Settings => {
254 let handled = self
256 .current_item_mut()
257 .and_then(|item| match &mut item.control {
258 SettingControl::Map(map_state) => Some(map_state.focus_prev()),
259 _ => None,
260 })
261 .unwrap_or(false);
262
263 if !handled && self.selected_item > 0 {
264 self.selected_item -= 1;
265 self.sub_focus = None;
266 self.init_map_focus(false); }
268 self.ensure_visible();
269 }
270 FocusPanel::Footer => {
271 if self.footer_button_index > 0 {
273 self.footer_button_index -= 1;
274 }
275 }
276 }
277 }
278
279 pub fn select_next(&mut self) {
281 match self.focus_panel {
282 FocusPanel::Categories => {
283 if self.selected_category + 1 < self.pages.len() {
284 self.selected_category += 1;
285 self.selected_item = 0;
286 self.scroll_panel = ScrollablePanel::new();
287 self.sub_focus = None;
288 }
289 }
290 FocusPanel::Settings => {
291 let handled = self
293 .current_item_mut()
294 .and_then(|item| match &mut item.control {
295 SettingControl::Map(map_state) => Some(map_state.focus_next()),
296 _ => None,
297 })
298 .unwrap_or(false);
299
300 if !handled {
301 let can_move = self
302 .current_page()
303 .is_some_and(|page| self.selected_item + 1 < page.items.len());
304 if can_move {
305 self.selected_item += 1;
306 self.sub_focus = None;
307 self.init_map_focus(true); }
309 }
310 self.ensure_visible();
311 }
312 FocusPanel::Footer => {
313 if self.footer_button_index < 2 {
315 self.footer_button_index += 1;
316 }
317 }
318 }
319 }
320
321 pub fn toggle_focus(&mut self) {
323 self.focus_panel = match self.focus_panel {
324 FocusPanel::Categories => FocusPanel::Settings,
325 FocusPanel::Settings => FocusPanel::Footer,
326 FocusPanel::Footer => FocusPanel::Categories,
327 };
328
329 if self.focus_panel == FocusPanel::Settings
331 && self.selected_item >= self.current_page().map_or(0, |p| p.items.len())
332 {
333 self.selected_item = 0;
334 }
335 self.sub_focus = None;
336
337 if self.focus_panel == FocusPanel::Settings {
338 self.init_map_focus(true); }
340
341 if self.focus_panel == FocusPanel::Footer {
343 self.footer_button_index = 2; }
345
346 self.ensure_visible();
347 }
348
349 pub fn ensure_visible(&mut self) {
351 if self.focus_panel != FocusPanel::Settings {
352 return;
353 }
354
355 let selected_item = self.selected_item;
357 let sub_focus = self.sub_focus;
358 if let Some(page) = self.pages.get(self.selected_category) {
359 self.scroll_panel
360 .ensure_focused_visible(&page.items, selected_item, sub_focus);
361 }
362 }
363
364 pub fn set_pending_change(&mut self, path: &str, value: serde_json::Value) {
366 let original = self.original_config.pointer(path);
368 if original == Some(&value) {
369 self.pending_changes.remove(path);
370 } else {
371 self.pending_changes.insert(path.to_string(), value);
372 }
373 }
374
375 pub fn has_changes(&self) -> bool {
377 !self.pending_changes.is_empty() || !self.pending_deletions.is_empty()
378 }
379
380 pub fn apply_changes(&self, config: &Config) -> Result<Config, serde_json::Error> {
382 let mut config_value = serde_json::to_value(config)?;
383
384 for (path, value) in &self.pending_changes {
385 if let Some(target) = config_value.pointer_mut(path) {
386 *target = value.clone();
387 }
388 }
389
390 serde_json::from_value(config_value)
391 }
392
393 pub fn discard_changes(&mut self) {
395 self.pending_changes.clear();
396 self.pending_deletions.clear();
397 self.pages = super::items::build_pages(
399 &self.categories,
400 &self.original_config,
401 &self.layer_sources,
402 self.target_layer,
403 );
404 }
405
406 pub fn set_target_layer(&mut self, layer: ConfigLayer) {
408 if layer != ConfigLayer::System {
409 self.target_layer = layer;
411 self.pending_changes.clear();
413 self.pending_deletions.clear();
414 self.pages = super::items::build_pages(
416 &self.categories,
417 &self.original_config,
418 &self.layer_sources,
419 self.target_layer,
420 );
421 }
422 }
423
424 pub fn cycle_target_layer(&mut self) {
426 self.target_layer = match self.target_layer {
427 ConfigLayer::System => ConfigLayer::User, ConfigLayer::User => ConfigLayer::Project,
429 ConfigLayer::Project => ConfigLayer::Session,
430 ConfigLayer::Session => ConfigLayer::User,
431 };
432 self.pending_changes.clear();
434 self.pending_deletions.clear();
435 self.pages = super::items::build_pages(
437 &self.categories,
438 &self.original_config,
439 &self.layer_sources,
440 self.target_layer,
441 );
442 }
443
444 pub fn target_layer_name(&self) -> &'static str {
446 match self.target_layer {
447 ConfigLayer::System => "System (read-only)",
448 ConfigLayer::User => "User",
449 ConfigLayer::Project => "Project",
450 ConfigLayer::Session => "Session",
451 }
452 }
453
454 pub fn set_layer_sources(&mut self, sources: HashMap<String, ConfigLayer>) {
457 self.layer_sources = sources;
458 self.pages = super::items::build_pages(
460 &self.categories,
461 &self.original_config,
462 &self.layer_sources,
463 self.target_layer,
464 );
465 }
466
467 pub fn get_layer_source(&self, path: &str) -> ConfigLayer {
470 self.layer_sources
471 .get(path)
472 .copied()
473 .unwrap_or(ConfigLayer::System)
474 }
475
476 pub fn layer_source_label(layer: ConfigLayer) -> &'static str {
478 match layer {
479 ConfigLayer::System => "default",
480 ConfigLayer::User => "user",
481 ConfigLayer::Project => "project",
482 ConfigLayer::Session => "session",
483 }
484 }
485
486 pub fn reset_current_to_default(&mut self) {
494 let reset_info = self.current_item().and_then(|item| {
496 if !item.modified || item.is_auto_managed {
499 return None;
500 }
501 item.default
502 .as_ref()
503 .map(|default| (item.path.clone(), default.clone()))
504 });
505
506 if let Some((path, default)) = reset_info {
507 self.pending_deletions.insert(path.clone());
509 self.pending_changes.remove(&path);
511
512 if let Some(item) = self.current_item_mut() {
516 update_control_from_value(&mut item.control, &default);
517 item.modified = false;
518 item.layer_source = ConfigLayer::System; }
521 }
522 }
523
524 pub fn on_value_changed(&mut self) {
526 let target_layer = self.target_layer;
528
529 let change_info = self.current_item().map(|item| {
531 let value = control_to_value(&item.control);
532 (item.path.clone(), value)
533 });
534
535 if let Some((path, value)) = change_info {
536 self.pending_deletions.remove(&path);
539
540 if let Some(item) = self.current_item_mut() {
542 item.modified = true; item.layer_source = target_layer; }
545 self.set_pending_change(&path, value);
546 }
547 }
548
549 pub fn update_focus_states(&mut self) {
551 for (page_idx, page) in self.pages.iter_mut().enumerate() {
552 for (item_idx, item) in page.items.iter_mut().enumerate() {
553 let is_focused = self.focus_panel == FocusPanel::Settings
554 && page_idx == self.selected_category
555 && item_idx == self.selected_item;
556
557 let focus = if is_focused {
558 FocusState::Focused
559 } else {
560 FocusState::Normal
561 };
562
563 match &mut item.control {
564 SettingControl::Toggle(state) => state.focus = focus,
565 SettingControl::Number(state) => state.focus = focus,
566 SettingControl::Dropdown(state) => state.focus = focus,
567 SettingControl::Text(state) => state.focus = focus,
568 SettingControl::TextList(state) => state.focus = focus,
569 SettingControl::Map(state) => state.focus = focus,
570 SettingControl::ObjectArray(state) => state.focus = focus,
571 SettingControl::Json(state) => state.focus = focus,
572 SettingControl::Complex { .. } => {}
573 }
574 }
575 }
576 }
577
578 pub fn start_search(&mut self) {
580 self.search_active = true;
581 self.search_query.clear();
582 self.search_results.clear();
583 self.selected_search_result = 0;
584 }
585
586 pub fn cancel_search(&mut self) {
588 self.search_active = false;
589 self.search_query.clear();
590 self.search_results.clear();
591 self.selected_search_result = 0;
592 }
593
594 pub fn set_search_query(&mut self, query: String) {
596 self.search_query = query;
597 self.search_results = search_settings(&self.pages, &self.search_query);
598 self.selected_search_result = 0;
599 }
600
601 pub fn search_push_char(&mut self, c: char) {
603 self.search_query.push(c);
604 self.search_results = search_settings(&self.pages, &self.search_query);
605 self.selected_search_result = 0;
606 }
607
608 pub fn search_pop_char(&mut self) {
610 self.search_query.pop();
611 self.search_results = search_settings(&self.pages, &self.search_query);
612 self.selected_search_result = 0;
613 }
614
615 pub fn search_prev(&mut self) {
617 if !self.search_results.is_empty() && self.selected_search_result > 0 {
618 self.selected_search_result -= 1;
619 }
620 }
621
622 pub fn search_next(&mut self) {
624 if !self.search_results.is_empty()
625 && self.selected_search_result + 1 < self.search_results.len()
626 {
627 self.selected_search_result += 1;
628 }
629 }
630
631 pub fn jump_to_search_result(&mut self) {
633 if let Some(result) = self.search_results.get(self.selected_search_result) {
634 self.selected_category = result.page_index;
635 self.selected_item = result.item_index;
636 self.focus_panel = FocusPanel::Settings;
637 self.scroll_panel.scroll.offset = 0;
639 if let Some(page) = self.pages.get(self.selected_category) {
641 self.scroll_panel.update_content_height(&page.items);
642 }
643 self.sub_focus = None;
644 self.init_map_focus(true);
645 self.ensure_visible();
646 self.cancel_search();
647 }
648 }
649
650 pub fn current_search_result(&self) -> Option<&SearchResult> {
652 self.search_results.get(self.selected_search_result)
653 }
654
655 pub fn show_confirm_dialog(&mut self) {
657 self.showing_confirm_dialog = true;
658 self.confirm_dialog_selection = 0; }
660
661 pub fn hide_confirm_dialog(&mut self) {
663 self.showing_confirm_dialog = false;
664 self.confirm_dialog_selection = 0;
665 }
666
667 pub fn confirm_dialog_next(&mut self) {
669 self.confirm_dialog_selection = (self.confirm_dialog_selection + 1) % 3;
670 }
671
672 pub fn confirm_dialog_prev(&mut self) {
674 self.confirm_dialog_selection = if self.confirm_dialog_selection == 0 {
675 2
676 } else {
677 self.confirm_dialog_selection - 1
678 };
679 }
680
681 pub fn toggle_help(&mut self) {
683 self.showing_help = !self.showing_help;
684 }
685
686 pub fn hide_help(&mut self) {
688 self.showing_help = false;
689 }
690
691 pub fn showing_entry_dialog(&self) -> bool {
693 self.has_entry_dialog()
694 }
695
696 pub fn open_entry_dialog(&mut self) {
698 let Some(item) = self.current_item() else {
699 return;
700 };
701
702 let path = item.path.as_str();
704 let SettingControl::Map(map_state) = &item.control else {
705 return;
706 };
707
708 let Some(entry_idx) = map_state.focused_entry else {
710 return;
711 };
712 let Some((key, value)) = map_state.entries.get(entry_idx) else {
713 return;
714 };
715
716 let Some(schema) = map_state.value_schema.as_ref() else {
718 return; };
720
721 let no_delete = map_state.no_add;
723
724 let dialog =
726 EntryDialogState::from_schema(key.clone(), value, schema, path, false, no_delete);
727 self.entry_dialog_stack.push(dialog);
728 }
729
730 pub fn open_add_entry_dialog(&mut self) {
732 let Some(item) = self.current_item() else {
733 return;
734 };
735 let SettingControl::Map(map_state) = &item.control else {
736 return;
737 };
738 let Some(schema) = map_state.value_schema.as_ref() else {
739 return;
740 };
741 let path = item.path.clone();
742
743 let dialog = EntryDialogState::from_schema(
746 String::new(),
747 &serde_json::json!({}),
748 schema,
749 &path,
750 true,
751 false,
752 );
753 self.entry_dialog_stack.push(dialog);
754 }
755
756 pub fn open_add_array_item_dialog(&mut self) {
758 let Some(item) = self.current_item() else {
759 return;
760 };
761 let SettingControl::ObjectArray(array_state) = &item.control else {
762 return;
763 };
764 let Some(schema) = array_state.item_schema.as_ref() else {
765 return;
766 };
767 let path = item.path.clone();
768
769 let dialog =
771 EntryDialogState::for_array_item(None, &serde_json::json!({}), schema, &path, true);
772 self.entry_dialog_stack.push(dialog);
773 }
774
775 pub fn open_edit_array_item_dialog(&mut self) {
777 let Some(item) = self.current_item() else {
778 return;
779 };
780 let SettingControl::ObjectArray(array_state) = &item.control else {
781 return;
782 };
783 let Some(schema) = array_state.item_schema.as_ref() else {
784 return;
785 };
786 let Some(index) = array_state.focused_index else {
787 return;
788 };
789 let Some(value) = array_state.bindings.get(index) else {
790 return;
791 };
792 let path = item.path.clone();
793
794 let dialog = EntryDialogState::for_array_item(Some(index), value, schema, &path, false);
795 self.entry_dialog_stack.push(dialog);
796 }
797
798 pub fn close_entry_dialog(&mut self) {
800 self.entry_dialog_stack.pop();
801 }
802
803 pub fn open_nested_entry_dialog(&mut self) {
808 let nested_info = self.entry_dialog().and_then(|dialog| {
810 let item = dialog.current_item()?;
811 let path = format!("{}/{}", dialog.map_path, item.path.trim_start_matches('/'));
812
813 match &item.control {
814 SettingControl::Map(map_state) => {
815 let schema = map_state.value_schema.as_ref()?;
816 let no_delete = map_state.no_add; if let Some(entry_idx) = map_state.focused_entry {
818 let (key, value) = map_state.entries.get(entry_idx)?;
820 Some(NestedDialogInfo::MapEntry {
821 key: key.clone(),
822 value: value.clone(),
823 schema: schema.as_ref().clone(),
824 path,
825 is_new: false,
826 no_delete,
827 })
828 } else {
829 Some(NestedDialogInfo::MapEntry {
831 key: String::new(),
832 value: serde_json::json!({}),
833 schema: schema.as_ref().clone(),
834 path,
835 is_new: true,
836 no_delete: false, })
838 }
839 }
840 SettingControl::ObjectArray(array_state) => {
841 let schema = array_state.item_schema.as_ref()?;
842 if let Some(index) = array_state.focused_index {
843 let value = array_state.bindings.get(index)?;
845 Some(NestedDialogInfo::ArrayItem {
846 index: Some(index),
847 value: value.clone(),
848 schema: schema.as_ref().clone(),
849 path,
850 is_new: false,
851 })
852 } else {
853 Some(NestedDialogInfo::ArrayItem {
855 index: None,
856 value: serde_json::json!({}),
857 schema: schema.as_ref().clone(),
858 path,
859 is_new: true,
860 })
861 }
862 }
863 _ => None,
864 }
865 });
866
867 if let Some(info) = nested_info {
869 let dialog = match info {
870 NestedDialogInfo::MapEntry {
871 key,
872 value,
873 schema,
874 path,
875 is_new,
876 no_delete,
877 } => EntryDialogState::from_schema(key, &value, &schema, &path, is_new, no_delete),
878 NestedDialogInfo::ArrayItem {
879 index,
880 value,
881 schema,
882 path,
883 is_new,
884 } => EntryDialogState::for_array_item(index, &value, &schema, &path, is_new),
885 };
886 self.entry_dialog_stack.push(dialog);
887 }
888 }
889
890 pub fn save_entry_dialog(&mut self) {
895 let is_array = if self.entry_dialog_stack.len() > 1 {
899 self.entry_dialog_stack
901 .get(self.entry_dialog_stack.len() - 2)
902 .and_then(|parent| parent.current_item())
903 .map(|item| matches!(item.control, SettingControl::ObjectArray(_)))
904 .unwrap_or(false)
905 } else {
906 self.current_item()
908 .map(|item| matches!(item.control, SettingControl::ObjectArray(_)))
909 .unwrap_or(false)
910 };
911
912 if is_array {
913 self.save_array_item_dialog_inner();
914 } else {
915 self.save_map_entry_dialog_inner();
916 }
917 }
918
919 fn save_map_entry_dialog_inner(&mut self) {
921 let Some(dialog) = self.entry_dialog_stack.pop() else {
922 return;
923 };
924
925 let key = dialog.get_key();
927 if key.is_empty() {
928 return; }
930
931 let value = dialog.to_value();
932 let map_path = dialog.map_path.clone();
933 let original_key = dialog.entry_key.clone();
934 let is_new = dialog.is_new;
935 let key_changed = !is_new && key != original_key;
936
937 if let Some(item) = self.current_item_mut() {
939 if let SettingControl::Map(map_state) = &mut item.control {
940 if key_changed {
942 if let Some(idx) = map_state
943 .entries
944 .iter()
945 .position(|(k, _)| k == &original_key)
946 {
947 map_state.entries.remove(idx);
948 }
949 }
950
951 if let Some(entry) = map_state.entries.iter_mut().find(|(k, _)| k == &key) {
953 entry.1 = value.clone();
954 } else {
955 map_state.entries.push((key.clone(), value.clone()));
956 map_state.entries.sort_by(|a, b| a.0.cmp(&b.0));
957 }
958 }
959 }
960
961 if key_changed {
963 let old_path = format!("{}/{}", map_path, original_key);
964 self.pending_changes
965 .insert(old_path, serde_json::Value::Null);
966 }
967
968 let path = format!("{}/{}", map_path, key);
970 self.set_pending_change(&path, value);
971 }
972
973 fn save_array_item_dialog_inner(&mut self) {
975 let Some(dialog) = self.entry_dialog_stack.pop() else {
976 return;
977 };
978
979 let value = dialog.to_value();
980 let array_path = dialog.map_path.clone();
981 let is_new = dialog.is_new;
982 let entry_key = dialog.entry_key.clone();
983
984 let is_nested = !self.entry_dialog_stack.is_empty();
986
987 if is_nested {
988 let array_field = array_path.rsplit('/').next().unwrap_or("").to_string();
991 let item_path = format!("/{}", array_field);
992
993 if let Some(parent) = self.entry_dialog_stack.last_mut() {
995 if let Some(item) = parent.items.iter_mut().find(|i| i.path == item_path) {
996 if let SettingControl::ObjectArray(array_state) = &mut item.control {
997 if is_new {
998 array_state.bindings.push(value.clone());
999 } else if let Ok(index) = entry_key.parse::<usize>() {
1000 if index < array_state.bindings.len() {
1001 array_state.bindings[index] = value.clone();
1002 }
1003 }
1004 }
1005 }
1006 }
1007
1008 if let Some(parent) = self.entry_dialog_stack.last() {
1011 if let Some(item) = parent.items.iter().find(|i| i.path == item_path) {
1012 if let SettingControl::ObjectArray(array_state) = &item.control {
1013 let array_value = serde_json::Value::Array(array_state.bindings.clone());
1014 self.set_pending_change(&array_path, array_value);
1015 }
1016 }
1017 }
1018 } else {
1019 if let Some(item) = self.current_item_mut() {
1021 if let SettingControl::ObjectArray(array_state) = &mut item.control {
1022 if is_new {
1023 array_state.bindings.push(value.clone());
1024 } else if let Ok(index) = entry_key.parse::<usize>() {
1025 if index < array_state.bindings.len() {
1026 array_state.bindings[index] = value.clone();
1027 }
1028 }
1029 }
1030 }
1031
1032 if let Some(item) = self.current_item() {
1034 if let SettingControl::ObjectArray(array_state) = &item.control {
1035 let array_value = serde_json::Value::Array(array_state.bindings.clone());
1036 self.set_pending_change(&array_path, array_value);
1037 }
1038 }
1039 }
1040 }
1041
1042 pub fn delete_entry_dialog(&mut self) {
1044 let is_nested = self.entry_dialog_stack.len() > 1;
1046
1047 let Some(dialog) = self.entry_dialog_stack.pop() else {
1048 return;
1049 };
1050
1051 let path = format!("{}/{}", dialog.map_path, dialog.entry_key);
1052
1053 if is_nested {
1055 let map_field = dialog.map_path.rsplit('/').next().unwrap_or("").to_string();
1058 let item_path = format!("/{}", map_field);
1059
1060 if let Some(parent) = self.entry_dialog_stack.last_mut() {
1062 if let Some(item) = parent.items.iter_mut().find(|i| i.path == item_path) {
1063 if let SettingControl::Map(map_state) = &mut item.control {
1064 if let Some(idx) = map_state
1065 .entries
1066 .iter()
1067 .position(|(k, _)| k == &dialog.entry_key)
1068 {
1069 map_state.remove_entry(idx);
1070 }
1071 }
1072 }
1073 }
1074 } else {
1075 if let Some(item) = self.current_item_mut() {
1077 if let SettingControl::Map(map_state) = &mut item.control {
1078 if let Some(idx) = map_state
1079 .entries
1080 .iter()
1081 .position(|(k, _)| k == &dialog.entry_key)
1082 {
1083 map_state.remove_entry(idx);
1084 }
1085 }
1086 }
1087 }
1088
1089 self.set_pending_change(&path, serde_json::Value::Null);
1091 }
1092
1093 pub fn max_scroll(&self) -> u16 {
1095 self.scroll_panel.scroll.max_offset()
1096 }
1097
1098 pub fn scroll_up(&mut self, delta: usize) -> bool {
1101 let old = self.scroll_panel.scroll.offset;
1102 self.scroll_panel.scroll_up(delta as u16);
1103 old != self.scroll_panel.scroll.offset
1104 }
1105
1106 pub fn scroll_down(&mut self, delta: usize) -> bool {
1109 let old = self.scroll_panel.scroll.offset;
1110 self.scroll_panel.scroll_down(delta as u16);
1111 old != self.scroll_panel.scroll.offset
1112 }
1113
1114 pub fn scroll_to_ratio(&mut self, ratio: f32) -> bool {
1117 let old = self.scroll_panel.scroll.offset;
1118 self.scroll_panel.scroll_to_ratio(ratio);
1119 old != self.scroll_panel.scroll.offset
1120 }
1121
1122 pub fn start_editing(&mut self) {
1124 if let Some(item) = self.current_item() {
1125 if matches!(
1126 item.control,
1127 SettingControl::TextList(_) | SettingControl::Text(_) | SettingControl::Map(_)
1128 ) {
1129 self.editing_text = true;
1130 }
1131 }
1132 }
1133
1134 pub fn stop_editing(&mut self) {
1136 self.editing_text = false;
1137 }
1138
1139 pub fn is_editable_control(&self) -> bool {
1141 self.current_item().is_some_and(|item| {
1142 matches!(
1143 item.control,
1144 SettingControl::TextList(_) | SettingControl::Text(_) | SettingControl::Map(_)
1145 )
1146 })
1147 }
1148
1149 pub fn text_insert(&mut self, c: char) {
1151 if let Some(item) = self.current_item_mut() {
1152 match &mut item.control {
1153 SettingControl::TextList(state) => state.insert(c),
1154 SettingControl::Text(state) => {
1155 state.value.insert(state.cursor, c);
1156 state.cursor += c.len_utf8();
1157 }
1158 SettingControl::Map(state) => {
1159 state.new_key_text.insert(state.cursor, c);
1160 state.cursor += c.len_utf8();
1161 }
1162 _ => {}
1163 }
1164 }
1165 }
1166
1167 pub fn text_backspace(&mut self) {
1169 if let Some(item) = self.current_item_mut() {
1170 match &mut item.control {
1171 SettingControl::TextList(state) => state.backspace(),
1172 SettingControl::Text(state) => {
1173 if state.cursor > 0 {
1174 let mut char_start = state.cursor - 1;
1175 while char_start > 0 && !state.value.is_char_boundary(char_start) {
1176 char_start -= 1;
1177 }
1178 state.value.remove(char_start);
1179 state.cursor = char_start;
1180 }
1181 }
1182 SettingControl::Map(state) => {
1183 if state.cursor > 0 {
1184 let mut char_start = state.cursor - 1;
1185 while char_start > 0 && !state.new_key_text.is_char_boundary(char_start) {
1186 char_start -= 1;
1187 }
1188 state.new_key_text.remove(char_start);
1189 state.cursor = char_start;
1190 }
1191 }
1192 _ => {}
1193 }
1194 }
1195 }
1196
1197 pub fn text_move_left(&mut self) {
1199 if let Some(item) = self.current_item_mut() {
1200 match &mut item.control {
1201 SettingControl::TextList(state) => state.move_left(),
1202 SettingControl::Text(state) => {
1203 if state.cursor > 0 {
1204 let mut new_pos = state.cursor - 1;
1205 while new_pos > 0 && !state.value.is_char_boundary(new_pos) {
1206 new_pos -= 1;
1207 }
1208 state.cursor = new_pos;
1209 }
1210 }
1211 SettingControl::Map(state) => {
1212 if state.cursor > 0 {
1213 let mut new_pos = state.cursor - 1;
1214 while new_pos > 0 && !state.new_key_text.is_char_boundary(new_pos) {
1215 new_pos -= 1;
1216 }
1217 state.cursor = new_pos;
1218 }
1219 }
1220 _ => {}
1221 }
1222 }
1223 }
1224
1225 pub fn text_move_right(&mut self) {
1227 if let Some(item) = self.current_item_mut() {
1228 match &mut item.control {
1229 SettingControl::TextList(state) => state.move_right(),
1230 SettingControl::Text(state) => {
1231 if state.cursor < state.value.len() {
1232 let mut new_pos = state.cursor + 1;
1233 while new_pos < state.value.len() && !state.value.is_char_boundary(new_pos)
1234 {
1235 new_pos += 1;
1236 }
1237 state.cursor = new_pos;
1238 }
1239 }
1240 SettingControl::Map(state) => {
1241 if state.cursor < state.new_key_text.len() {
1242 let mut new_pos = state.cursor + 1;
1243 while new_pos < state.new_key_text.len()
1244 && !state.new_key_text.is_char_boundary(new_pos)
1245 {
1246 new_pos += 1;
1247 }
1248 state.cursor = new_pos;
1249 }
1250 }
1251 _ => {}
1252 }
1253 }
1254 }
1255
1256 pub fn text_focus_prev(&mut self) {
1258 if let Some(item) = self.current_item_mut() {
1259 match &mut item.control {
1260 SettingControl::TextList(state) => state.focus_prev(),
1261 SettingControl::Map(state) => {
1262 state.focus_prev();
1263 }
1264 _ => {}
1265 }
1266 }
1267 }
1268
1269 pub fn text_focus_next(&mut self) {
1271 if let Some(item) = self.current_item_mut() {
1272 match &mut item.control {
1273 SettingControl::TextList(state) => state.focus_next(),
1274 SettingControl::Map(state) => {
1275 state.focus_next();
1276 }
1277 _ => {}
1278 }
1279 }
1280 }
1281
1282 pub fn text_add_item(&mut self) {
1284 if let Some(item) = self.current_item_mut() {
1285 match &mut item.control {
1286 SettingControl::TextList(state) => state.add_item(),
1287 SettingControl::Map(state) => state.add_entry_from_input(),
1288 _ => {}
1289 }
1290 }
1291 self.on_value_changed();
1293 }
1294
1295 pub fn text_remove_focused(&mut self) {
1297 if let Some(item) = self.current_item_mut() {
1298 match &mut item.control {
1299 SettingControl::TextList(state) => {
1300 if let Some(idx) = state.focused_item {
1301 state.remove_item(idx);
1302 }
1303 }
1304 SettingControl::Map(state) => {
1305 if let Some(idx) = state.focused_entry {
1306 state.remove_entry(idx);
1307 }
1308 }
1309 _ => {}
1310 }
1311 }
1312 self.on_value_changed();
1314 }
1315
1316 pub fn is_dropdown_open(&self) -> bool {
1320 self.current_item().is_some_and(|item| {
1321 if let SettingControl::Dropdown(ref d) = item.control {
1322 d.open
1323 } else {
1324 false
1325 }
1326 })
1327 }
1328
1329 pub fn dropdown_toggle(&mut self) {
1331 let mut opened = false;
1332 if let Some(item) = self.current_item_mut() {
1333 if let SettingControl::Dropdown(ref mut d) = item.control {
1334 d.toggle_open();
1335 opened = d.open;
1336 }
1337 }
1338
1339 if opened {
1341 let selected_item = self.selected_item;
1343 if let Some(page) = self.pages.get(self.selected_category) {
1344 self.scroll_panel.update_content_height(&page.items);
1345 self.scroll_panel
1347 .ensure_focused_visible(&page.items, selected_item, None);
1348 }
1349 }
1350 }
1351
1352 pub fn dropdown_prev(&mut self) {
1354 if let Some(item) = self.current_item_mut() {
1355 if let SettingControl::Dropdown(ref mut d) = item.control {
1356 d.select_prev();
1357 }
1358 }
1359 }
1360
1361 pub fn dropdown_next(&mut self) {
1363 if let Some(item) = self.current_item_mut() {
1364 if let SettingControl::Dropdown(ref mut d) = item.control {
1365 d.select_next();
1366 }
1367 }
1368 }
1369
1370 pub fn dropdown_home(&mut self) {
1372 if let Some(item) = self.current_item_mut() {
1373 if let SettingControl::Dropdown(ref mut d) = item.control {
1374 if !d.options.is_empty() {
1375 d.selected = 0;
1376 d.ensure_visible();
1377 }
1378 }
1379 }
1380 }
1381
1382 pub fn dropdown_end(&mut self) {
1384 if let Some(item) = self.current_item_mut() {
1385 if let SettingControl::Dropdown(ref mut d) = item.control {
1386 if !d.options.is_empty() {
1387 d.selected = d.options.len() - 1;
1388 d.ensure_visible();
1389 }
1390 }
1391 }
1392 }
1393
1394 pub fn dropdown_confirm(&mut self) {
1396 if let Some(item) = self.current_item_mut() {
1397 if let SettingControl::Dropdown(ref mut d) = item.control {
1398 d.confirm();
1399 }
1400 }
1401 self.on_value_changed();
1402 }
1403
1404 pub fn dropdown_cancel(&mut self) {
1406 if let Some(item) = self.current_item_mut() {
1407 if let SettingControl::Dropdown(ref mut d) = item.control {
1408 d.cancel();
1409 }
1410 }
1411 }
1412
1413 pub fn dropdown_scroll(&mut self, delta: i32) {
1415 if let Some(item) = self.current_item_mut() {
1416 if let SettingControl::Dropdown(ref mut d) = item.control {
1417 if d.open {
1418 d.scroll_by(delta);
1419 }
1420 }
1421 }
1422 }
1423
1424 pub fn is_number_editing(&self) -> bool {
1428 self.current_item().is_some_and(|item| {
1429 if let SettingControl::Number(ref n) = item.control {
1430 n.editing()
1431 } else {
1432 false
1433 }
1434 })
1435 }
1436
1437 pub fn start_number_editing(&mut self) {
1439 if let Some(item) = self.current_item_mut() {
1440 if let SettingControl::Number(ref mut n) = item.control {
1441 n.start_editing();
1442 }
1443 }
1444 }
1445
1446 pub fn number_insert(&mut self, c: char) {
1448 if let Some(item) = self.current_item_mut() {
1449 if let SettingControl::Number(ref mut n) = item.control {
1450 n.insert_char(c);
1451 }
1452 }
1453 }
1454
1455 pub fn number_backspace(&mut self) {
1457 if let Some(item) = self.current_item_mut() {
1458 if let SettingControl::Number(ref mut n) = item.control {
1459 n.backspace();
1460 }
1461 }
1462 }
1463
1464 pub fn number_confirm(&mut self) {
1466 if let Some(item) = self.current_item_mut() {
1467 if let SettingControl::Number(ref mut n) = item.control {
1468 n.confirm_editing();
1469 }
1470 }
1471 self.on_value_changed();
1472 }
1473
1474 pub fn number_cancel(&mut self) {
1476 if let Some(item) = self.current_item_mut() {
1477 if let SettingControl::Number(ref mut n) = item.control {
1478 n.cancel_editing();
1479 }
1480 }
1481 }
1482
1483 pub fn number_delete(&mut self) {
1485 if let Some(item) = self.current_item_mut() {
1486 if let SettingControl::Number(ref mut n) = item.control {
1487 n.delete();
1488 }
1489 }
1490 }
1491
1492 pub fn number_move_left(&mut self) {
1494 if let Some(item) = self.current_item_mut() {
1495 if let SettingControl::Number(ref mut n) = item.control {
1496 n.move_left();
1497 }
1498 }
1499 }
1500
1501 pub fn number_move_right(&mut self) {
1503 if let Some(item) = self.current_item_mut() {
1504 if let SettingControl::Number(ref mut n) = item.control {
1505 n.move_right();
1506 }
1507 }
1508 }
1509
1510 pub fn number_move_home(&mut self) {
1512 if let Some(item) = self.current_item_mut() {
1513 if let SettingControl::Number(ref mut n) = item.control {
1514 n.move_home();
1515 }
1516 }
1517 }
1518
1519 pub fn number_move_end(&mut self) {
1521 if let Some(item) = self.current_item_mut() {
1522 if let SettingControl::Number(ref mut n) = item.control {
1523 n.move_end();
1524 }
1525 }
1526 }
1527
1528 pub fn number_move_left_selecting(&mut self) {
1530 if let Some(item) = self.current_item_mut() {
1531 if let SettingControl::Number(ref mut n) = item.control {
1532 n.move_left_selecting();
1533 }
1534 }
1535 }
1536
1537 pub fn number_move_right_selecting(&mut self) {
1539 if let Some(item) = self.current_item_mut() {
1540 if let SettingControl::Number(ref mut n) = item.control {
1541 n.move_right_selecting();
1542 }
1543 }
1544 }
1545
1546 pub fn number_move_home_selecting(&mut self) {
1548 if let Some(item) = self.current_item_mut() {
1549 if let SettingControl::Number(ref mut n) = item.control {
1550 n.move_home_selecting();
1551 }
1552 }
1553 }
1554
1555 pub fn number_move_end_selecting(&mut self) {
1557 if let Some(item) = self.current_item_mut() {
1558 if let SettingControl::Number(ref mut n) = item.control {
1559 n.move_end_selecting();
1560 }
1561 }
1562 }
1563
1564 pub fn number_move_word_left(&mut self) {
1566 if let Some(item) = self.current_item_mut() {
1567 if let SettingControl::Number(ref mut n) = item.control {
1568 n.move_word_left();
1569 }
1570 }
1571 }
1572
1573 pub fn number_move_word_right(&mut self) {
1575 if let Some(item) = self.current_item_mut() {
1576 if let SettingControl::Number(ref mut n) = item.control {
1577 n.move_word_right();
1578 }
1579 }
1580 }
1581
1582 pub fn number_move_word_left_selecting(&mut self) {
1584 if let Some(item) = self.current_item_mut() {
1585 if let SettingControl::Number(ref mut n) = item.control {
1586 n.move_word_left_selecting();
1587 }
1588 }
1589 }
1590
1591 pub fn number_move_word_right_selecting(&mut self) {
1593 if let Some(item) = self.current_item_mut() {
1594 if let SettingControl::Number(ref mut n) = item.control {
1595 n.move_word_right_selecting();
1596 }
1597 }
1598 }
1599
1600 pub fn number_select_all(&mut self) {
1602 if let Some(item) = self.current_item_mut() {
1603 if let SettingControl::Number(ref mut n) = item.control {
1604 n.select_all();
1605 }
1606 }
1607 }
1608
1609 pub fn number_delete_word_backward(&mut self) {
1611 if let Some(item) = self.current_item_mut() {
1612 if let SettingControl::Number(ref mut n) = item.control {
1613 n.delete_word_backward();
1614 }
1615 }
1616 }
1617
1618 pub fn number_delete_word_forward(&mut self) {
1620 if let Some(item) = self.current_item_mut() {
1621 if let SettingControl::Number(ref mut n) = item.control {
1622 n.delete_word_forward();
1623 }
1624 }
1625 }
1626
1627 pub fn get_change_descriptions(&self) -> Vec<String> {
1629 self.pending_changes
1630 .iter()
1631 .map(|(path, value)| {
1632 let value_str = match value {
1633 serde_json::Value::Bool(b) => b.to_string(),
1634 serde_json::Value::Number(n) => n.to_string(),
1635 serde_json::Value::String(s) => format!("\"{}\"", s),
1636 _ => value.to_string(),
1637 };
1638 format!("{}: {}", path, value_str)
1639 })
1640 .collect()
1641 }
1642}
1643
1644fn update_control_from_value(control: &mut SettingControl, value: &serde_json::Value) {
1646 match control {
1647 SettingControl::Toggle(state) => {
1648 if let Some(b) = value.as_bool() {
1649 state.checked = b;
1650 }
1651 }
1652 SettingControl::Number(state) => {
1653 if let Some(n) = value.as_i64() {
1654 state.value = n;
1655 }
1656 }
1657 SettingControl::Dropdown(state) => {
1658 if let Some(s) = value.as_str() {
1659 if let Some(idx) = state.options.iter().position(|o| o == s) {
1660 state.selected = idx;
1661 }
1662 }
1663 }
1664 SettingControl::Text(state) => {
1665 if let Some(s) = value.as_str() {
1666 state.value = s.to_string();
1667 state.cursor = state.value.len();
1668 }
1669 }
1670 SettingControl::TextList(state) => {
1671 if let Some(arr) = value.as_array() {
1672 state.items = arr
1673 .iter()
1674 .filter_map(|v| v.as_str().map(String::from))
1675 .collect();
1676 }
1677 }
1678 SettingControl::Map(state) => {
1679 if let Some(obj) = value.as_object() {
1680 state.entries = obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
1681 state.entries.sort_by(|a, b| a.0.cmp(&b.0));
1682 }
1683 }
1684 SettingControl::ObjectArray(state) => {
1685 if let Some(arr) = value.as_array() {
1686 state.bindings = arr.clone();
1687 }
1688 }
1689 SettingControl::Json(state) => {
1690 let json_str =
1692 serde_json::to_string_pretty(value).unwrap_or_else(|_| "null".to_string());
1693 let json_str = if json_str.is_empty() {
1694 "null".to_string()
1695 } else {
1696 json_str
1697 };
1698 state.original_text = json_str.clone();
1699 state.editor.set_value(&json_str);
1700 state.scroll_offset = 0;
1701 }
1702 SettingControl::Complex { .. } => {}
1703 }
1704}
1705
1706#[cfg(test)]
1707mod tests {
1708 use super::*;
1709
1710 const TEST_SCHEMA: &str = r#"
1711{
1712 "type": "object",
1713 "properties": {
1714 "theme": {
1715 "type": "string",
1716 "default": "dark"
1717 },
1718 "line_numbers": {
1719 "type": "boolean",
1720 "default": true
1721 }
1722 },
1723 "$defs": {}
1724}
1725"#;
1726
1727 fn test_config() -> Config {
1728 Config::default()
1729 }
1730
1731 #[test]
1732 fn test_settings_state_creation() {
1733 let config = test_config();
1734 let state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
1735
1736 assert!(!state.visible);
1737 assert_eq!(state.selected_category, 0);
1738 assert!(!state.has_changes());
1739 }
1740
1741 #[test]
1742 fn test_navigation() {
1743 let config = test_config();
1744 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
1745
1746 assert_eq!(state.focus_panel, FocusPanel::Categories);
1748
1749 state.toggle_focus();
1751 assert_eq!(state.focus_panel, FocusPanel::Settings);
1752
1753 state.select_next();
1755 assert_eq!(state.selected_item, 1);
1756
1757 state.select_prev();
1758 assert_eq!(state.selected_item, 0);
1759 }
1760
1761 #[test]
1762 fn test_pending_changes() {
1763 let config = test_config();
1764 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
1765
1766 assert!(!state.has_changes());
1767
1768 state.set_pending_change("/theme", serde_json::Value::String("light".to_string()));
1769 assert!(state.has_changes());
1770
1771 state.discard_changes();
1772 assert!(!state.has_changes());
1773 }
1774
1775 #[test]
1776 fn test_show_hide() {
1777 let config = test_config();
1778 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
1779
1780 assert!(!state.visible);
1781
1782 state.show();
1783 assert!(state.visible);
1784 assert_eq!(state.focus_panel, FocusPanel::Categories);
1785
1786 state.hide();
1787 assert!(!state.visible);
1788 }
1789
1790 const TEST_SCHEMA_CONTROLS: &str = r#"
1792{
1793 "type": "object",
1794 "properties": {
1795 "theme": {
1796 "type": "string",
1797 "enum": ["dark", "light", "high-contrast"],
1798 "default": "dark"
1799 },
1800 "tab_size": {
1801 "type": "integer",
1802 "minimum": 1,
1803 "maximum": 8,
1804 "default": 4
1805 },
1806 "line_numbers": {
1807 "type": "boolean",
1808 "default": true
1809 }
1810 },
1811 "$defs": {}
1812}
1813"#;
1814
1815 #[test]
1816 fn test_dropdown_toggle() {
1817 let config = test_config();
1818 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
1819 state.show();
1820 state.toggle_focus(); state.select_next();
1825 state.select_next();
1826 assert!(!state.is_dropdown_open());
1827
1828 state.dropdown_toggle();
1829 assert!(state.is_dropdown_open());
1830
1831 state.dropdown_toggle();
1832 assert!(!state.is_dropdown_open());
1833 }
1834
1835 #[test]
1836 fn test_dropdown_cancel_restores() {
1837 let config = test_config();
1838 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
1839 state.show();
1840 state.toggle_focus();
1841
1842 state.select_next();
1845 state.select_next();
1846
1847 state.dropdown_toggle();
1849 assert!(state.is_dropdown_open());
1850
1851 let initial = state.current_item().and_then(|item| {
1853 if let SettingControl::Dropdown(ref d) = item.control {
1854 Some(d.selected)
1855 } else {
1856 None
1857 }
1858 });
1859
1860 state.dropdown_next();
1862 let after_change = state.current_item().and_then(|item| {
1863 if let SettingControl::Dropdown(ref d) = item.control {
1864 Some(d.selected)
1865 } else {
1866 None
1867 }
1868 });
1869 assert_ne!(initial, after_change);
1870
1871 state.dropdown_cancel();
1873 assert!(!state.is_dropdown_open());
1874
1875 let after_cancel = state.current_item().and_then(|item| {
1876 if let SettingControl::Dropdown(ref d) = item.control {
1877 Some(d.selected)
1878 } else {
1879 None
1880 }
1881 });
1882 assert_eq!(initial, after_cancel);
1883 }
1884
1885 #[test]
1886 fn test_dropdown_confirm_keeps_selection() {
1887 let config = test_config();
1888 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
1889 state.show();
1890 state.toggle_focus();
1891
1892 state.dropdown_toggle();
1894
1895 state.dropdown_next();
1897 let after_change = state.current_item().and_then(|item| {
1898 if let SettingControl::Dropdown(ref d) = item.control {
1899 Some(d.selected)
1900 } else {
1901 None
1902 }
1903 });
1904
1905 state.dropdown_confirm();
1907 assert!(!state.is_dropdown_open());
1908
1909 let after_confirm = state.current_item().and_then(|item| {
1910 if let SettingControl::Dropdown(ref d) = item.control {
1911 Some(d.selected)
1912 } else {
1913 None
1914 }
1915 });
1916 assert_eq!(after_change, after_confirm);
1917 }
1918
1919 #[test]
1920 fn test_number_editing() {
1921 let config = test_config();
1922 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
1923 state.show();
1924 state.toggle_focus();
1925
1926 state.select_next();
1928
1929 assert!(!state.is_number_editing());
1931
1932 state.start_number_editing();
1934 assert!(state.is_number_editing());
1935
1936 state.number_insert('8');
1938
1939 state.number_confirm();
1941 assert!(!state.is_number_editing());
1942 }
1943
1944 #[test]
1945 fn test_number_cancel_editing() {
1946 let config = test_config();
1947 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
1948 state.show();
1949 state.toggle_focus();
1950
1951 state.select_next();
1953
1954 let initial_value = state.current_item().and_then(|item| {
1956 if let SettingControl::Number(ref n) = item.control {
1957 Some(n.value)
1958 } else {
1959 None
1960 }
1961 });
1962
1963 state.start_number_editing();
1965 state.number_backspace();
1966 state.number_insert('9');
1967 state.number_insert('9');
1968
1969 state.number_cancel();
1971 assert!(!state.is_number_editing());
1972
1973 let after_cancel = state.current_item().and_then(|item| {
1975 if let SettingControl::Number(ref n) = item.control {
1976 Some(n.value)
1977 } else {
1978 None
1979 }
1980 });
1981 assert_eq!(initial_value, after_cancel);
1982 }
1983
1984 #[test]
1985 fn test_number_backspace() {
1986 let config = test_config();
1987 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
1988 state.show();
1989 state.toggle_focus();
1990 state.select_next();
1991
1992 state.start_number_editing();
1993 state.number_backspace();
1994
1995 let display_text = state.current_item().and_then(|item| {
1997 if let SettingControl::Number(ref n) = item.control {
1998 Some(n.display_text())
1999 } else {
2000 None
2001 }
2002 });
2003 assert_eq!(display_text, Some(String::new()));
2005
2006 state.number_cancel();
2007 }
2008
2009 #[test]
2010 fn test_layer_selection() {
2011 let config = test_config();
2012 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2013
2014 assert_eq!(state.target_layer, ConfigLayer::User);
2016 assert_eq!(state.target_layer_name(), "User");
2017
2018 state.cycle_target_layer();
2020 assert_eq!(state.target_layer, ConfigLayer::Project);
2021 assert_eq!(state.target_layer_name(), "Project");
2022
2023 state.cycle_target_layer();
2024 assert_eq!(state.target_layer, ConfigLayer::Session);
2025 assert_eq!(state.target_layer_name(), "Session");
2026
2027 state.cycle_target_layer();
2028 assert_eq!(state.target_layer, ConfigLayer::User);
2029
2030 state.set_target_layer(ConfigLayer::Project);
2032 assert_eq!(state.target_layer, ConfigLayer::Project);
2033
2034 state.set_target_layer(ConfigLayer::System);
2036 assert_eq!(state.target_layer, ConfigLayer::Project);
2037 }
2038
2039 #[test]
2040 fn test_layer_switch_clears_pending_changes() {
2041 let config = test_config();
2042 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2043
2044 state.set_pending_change("/theme", serde_json::Value::String("light".to_string()));
2046 assert!(state.has_changes());
2047
2048 state.cycle_target_layer();
2050 assert!(!state.has_changes());
2051 }
2052}