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, DeepMatch, 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 select_next_page(&mut self) {
418 let page_size = self.scroll_panel.viewport_height().max(1);
419 for _ in 0..page_size {
420 self.select_next();
421 }
422 }
423
424 pub fn select_prev_page(&mut self) {
426 let page_size = self.scroll_panel.viewport_height().max(1);
427 for _ in 0..page_size {
428 self.select_prev();
429 }
430 }
431
432 pub fn toggle_focus(&mut self) {
434 let old_panel = self.focus_panel();
435 self.focus.focus_next();
436 self.on_panel_changed(old_panel, true);
437 }
438
439 pub fn toggle_focus_backward(&mut self) {
441 let old_panel = self.focus_panel();
442 self.focus.focus_prev();
443 self.on_panel_changed(old_panel, false);
444 }
445
446 fn on_panel_changed(&mut self, old_panel: FocusPanel, forward: bool) {
448 if old_panel == FocusPanel::Settings {
450 self.update_control_focus(false);
451 }
452
453 if self.focus_panel() == FocusPanel::Settings
455 && self.selected_item >= self.current_page().map_or(0, |p| p.items.len())
456 {
457 self.selected_item = 0;
458 }
459 self.sub_focus = None;
460
461 if self.focus_panel() == FocusPanel::Settings {
462 self.init_map_focus(forward); self.update_control_focus(true); }
465
466 if self.focus_panel() == FocusPanel::Footer {
468 self.footer_button_index = if forward {
469 0 } else {
471 4 };
473 }
474
475 self.ensure_visible();
476 }
477
478 pub fn update_layout_widths(&mut self) {
482 let width = self.layout_width;
483 if width > 0 {
484 if let Some(page) = self.pages.get_mut(self.selected_category) {
485 for item in &mut page.items {
486 item.layout_width = width;
487 }
488 }
489 }
490 }
491
492 pub fn ensure_visible(&mut self) {
493 if self.focus_panel() != FocusPanel::Settings {
494 return;
495 }
496
497 self.update_layout_widths();
498
499 let selected_item = self.selected_item;
501 let sub_focus = self.sub_focus;
502 if let Some(page) = self.pages.get(self.selected_category) {
503 self.scroll_panel
504 .ensure_focused_visible(&page.items, selected_item, sub_focus);
505 }
506 }
507
508 pub fn set_pending_change(&mut self, path: &str, value: serde_json::Value) {
510 let original = self.original_config.pointer(path);
512 if original == Some(&value) {
513 self.pending_changes.remove(path);
514 } else {
515 self.pending_changes.insert(path.to_string(), value);
516 }
517 }
518
519 pub fn has_changes(&self) -> bool {
521 !self.pending_changes.is_empty() || !self.pending_deletions.is_empty()
522 }
523
524 pub fn apply_changes(&self, config: &Config) -> Result<Config, serde_json::Error> {
526 let mut config_value = serde_json::to_value(config)?;
527
528 for (path, value) in &self.pending_changes {
529 if let Some(target) = config_value.pointer_mut(path) {
530 *target = value.clone();
531 }
532 }
533
534 serde_json::from_value(config_value)
535 }
536
537 pub fn discard_changes(&mut self) {
539 self.pending_changes.clear();
540 self.pending_deletions.clear();
541 self.pages = super::items::build_pages(
543 &self.categories,
544 &self.original_config,
545 &self.layer_sources,
546 self.target_layer,
547 );
548 }
549
550 pub fn set_target_layer(&mut self, layer: ConfigLayer) {
552 if layer != ConfigLayer::System {
553 self.target_layer = layer;
555 self.pending_changes.clear();
557 self.pending_deletions.clear();
558 self.pages = super::items::build_pages(
560 &self.categories,
561 &self.original_config,
562 &self.layer_sources,
563 self.target_layer,
564 );
565 }
566 }
567
568 pub fn cycle_target_layer(&mut self) {
570 self.target_layer = match self.target_layer {
571 ConfigLayer::System => ConfigLayer::User, ConfigLayer::User => ConfigLayer::Project,
573 ConfigLayer::Project => ConfigLayer::Session,
574 ConfigLayer::Session => ConfigLayer::User,
575 };
576 self.pending_changes.clear();
578 self.pending_deletions.clear();
579 self.pages = super::items::build_pages(
581 &self.categories,
582 &self.original_config,
583 &self.layer_sources,
584 self.target_layer,
585 );
586 }
587
588 pub fn target_layer_name(&self) -> &'static str {
590 match self.target_layer {
591 ConfigLayer::System => "System (read-only)",
592 ConfigLayer::User => "User",
593 ConfigLayer::Project => "Project",
594 ConfigLayer::Session => "Session",
595 }
596 }
597
598 pub fn set_layer_sources(&mut self, sources: HashMap<String, ConfigLayer>) {
601 self.layer_sources = sources;
602 self.pages = super::items::build_pages(
604 &self.categories,
605 &self.original_config,
606 &self.layer_sources,
607 self.target_layer,
608 );
609 }
610
611 pub fn get_layer_source(&self, path: &str) -> ConfigLayer {
614 self.layer_sources
615 .get(path)
616 .copied()
617 .unwrap_or(ConfigLayer::System)
618 }
619
620 pub fn layer_source_label(layer: ConfigLayer) -> &'static str {
622 match layer {
623 ConfigLayer::System => "default",
624 ConfigLayer::User => "user",
625 ConfigLayer::Project => "project",
626 ConfigLayer::Session => "session",
627 }
628 }
629
630 pub fn reset_current_to_default(&mut self) {
638 let reset_info = self.current_item().and_then(|item| {
640 if !item.modified || item.is_auto_managed {
643 return None;
644 }
645 item.default
646 .as_ref()
647 .map(|default| (item.path.clone(), default.clone()))
648 });
649
650 if let Some((path, default)) = reset_info {
651 self.pending_deletions.insert(path.clone());
653 self.pending_changes.remove(&path);
655
656 if let Some(item) = self.current_item_mut() {
660 update_control_from_value(&mut item.control, &default);
661 item.modified = false;
662 item.layer_source = ConfigLayer::System; }
665 }
666 }
667
668 pub fn on_value_changed(&mut self) {
670 let target_layer = self.target_layer;
672
673 let change_info = self.current_item().map(|item| {
675 let value = control_to_value(&item.control);
676 (item.path.clone(), value)
677 });
678
679 if let Some((path, value)) = change_info {
680 self.pending_deletions.remove(&path);
683
684 if let Some(item) = self.current_item_mut() {
686 item.modified = true; item.layer_source = target_layer; }
689 self.set_pending_change(&path, value);
690 }
691 }
692
693 pub fn update_focus_states(&mut self) {
695 let current_focus = self.focus_panel();
696 for (page_idx, page) in self.pages.iter_mut().enumerate() {
697 for (item_idx, item) in page.items.iter_mut().enumerate() {
698 let is_focused = current_focus == FocusPanel::Settings
699 && page_idx == self.selected_category
700 && item_idx == self.selected_item;
701
702 let focus = if is_focused {
703 FocusState::Focused
704 } else {
705 FocusState::Normal
706 };
707
708 match &mut item.control {
709 SettingControl::Toggle(state) => state.focus = focus,
710 SettingControl::Number(state) => state.focus = focus,
711 SettingControl::Dropdown(state) => state.focus = focus,
712 SettingControl::Text(state) => state.focus = focus,
713 SettingControl::TextList(state) => state.focus = focus,
714 SettingControl::Map(state) => state.focus = focus,
715 SettingControl::ObjectArray(state) => state.focus = focus,
716 SettingControl::Json(state) => state.focus = focus,
717 SettingControl::Complex { .. } => {}
718 }
719 }
720 }
721 }
722
723 pub fn start_search(&mut self) {
725 self.search_active = true;
726 self.search_query.clear();
727 self.search_results.clear();
728 self.selected_search_result = 0;
729 self.search_scroll_offset = 0;
730 }
731
732 pub fn cancel_search(&mut self) {
734 self.search_active = false;
735 self.search_query.clear();
736 self.search_results.clear();
737 self.selected_search_result = 0;
738 self.search_scroll_offset = 0;
739 }
740
741 pub fn set_search_query(&mut self, query: String) {
743 self.search_query = query;
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_push_char(&mut self, c: char) {
751 self.search_query.push(c);
752 self.search_results = search_settings(&self.pages, &self.search_query);
753 self.selected_search_result = 0;
754 self.search_scroll_offset = 0;
755 }
756
757 pub fn search_pop_char(&mut self) {
759 self.search_query.pop();
760 self.search_results = search_settings(&self.pages, &self.search_query);
761 self.selected_search_result = 0;
762 self.search_scroll_offset = 0;
763 }
764
765 pub fn search_prev(&mut self) {
767 if !self.search_results.is_empty() && self.selected_search_result > 0 {
768 self.selected_search_result -= 1;
769 if self.selected_search_result < self.search_scroll_offset {
771 self.search_scroll_offset = self.selected_search_result;
772 }
773 }
774 }
775
776 pub fn search_next(&mut self) {
778 if !self.search_results.is_empty()
779 && self.selected_search_result + 1 < self.search_results.len()
780 {
781 self.selected_search_result += 1;
782 if self.selected_search_result >= self.search_scroll_offset + self.search_max_visible {
784 self.search_scroll_offset =
785 self.selected_search_result - self.search_max_visible + 1;
786 }
787 }
788 }
789
790 pub fn search_scroll_up(&mut self, delta: usize) -> bool {
792 if self.search_results.is_empty() || self.search_scroll_offset == 0 {
793 return false;
794 }
795 self.search_scroll_offset = self.search_scroll_offset.saturating_sub(delta);
796 if self.selected_search_result >= self.search_scroll_offset + self.search_max_visible {
798 self.selected_search_result = self.search_scroll_offset + self.search_max_visible - 1;
799 }
800 true
801 }
802
803 pub fn search_scroll_down(&mut self, delta: usize) -> bool {
805 if self.search_results.is_empty() {
806 return false;
807 }
808 let max_offset = self
809 .search_results
810 .len()
811 .saturating_sub(self.search_max_visible);
812 if self.search_scroll_offset >= max_offset {
813 return false;
814 }
815 self.search_scroll_offset = (self.search_scroll_offset + delta).min(max_offset);
816 if self.selected_search_result < self.search_scroll_offset {
818 self.selected_search_result = self.search_scroll_offset;
819 }
820 true
821 }
822
823 pub fn search_scroll_to_ratio(&mut self, ratio: f32) -> bool {
825 if self.search_results.is_empty() {
826 return false;
827 }
828 let max_offset = self
829 .search_results
830 .len()
831 .saturating_sub(self.search_max_visible);
832 let new_offset = (ratio * max_offset as f32) as usize;
833 if new_offset != self.search_scroll_offset {
834 self.search_scroll_offset = new_offset.min(max_offset);
835 if self.selected_search_result < self.search_scroll_offset {
837 self.selected_search_result = self.search_scroll_offset;
838 } else if self.selected_search_result
839 >= self.search_scroll_offset + self.search_max_visible
840 {
841 self.selected_search_result =
842 self.search_scroll_offset + self.search_max_visible - 1;
843 }
844 return true;
845 }
846 false
847 }
848
849 pub fn jump_to_search_result(&mut self) {
851 let Some(result) = self
853 .search_results
854 .get(self.selected_search_result)
855 .cloned()
856 else {
857 return;
858 };
859 let page_index = result.page_index;
860 let item_index = result.item_index;
861
862 self.update_control_focus(false);
864 self.selected_category = page_index;
865 self.selected_item = item_index;
866 self.focus.set(FocusPanel::Settings);
867 self.scroll_panel.scroll.offset = 0;
869 self.update_layout_widths();
871 if let Some(page) = self.pages.get(self.selected_category) {
872 self.scroll_panel.update_content_height(&page.items);
873 }
874 self.sub_focus = None;
875 self.init_map_focus(true);
876
877 if let Some(ref deep_match) = result.deep_match {
879 self.jump_to_deep_match(deep_match);
880 }
881
882 self.update_control_focus(true); self.ensure_visible();
884 self.cancel_search();
885 }
886
887 fn jump_to_deep_match(&mut self, deep_match: &DeepMatch) {
889 match deep_match {
890 DeepMatch::MapKey { entry_index, .. } | DeepMatch::MapValue { entry_index, .. } => {
891 if let Some(item) = self.current_item_mut() {
892 if let SettingControl::Map(ref mut map_state) = item.control {
893 map_state.focused_entry = Some(*entry_index);
894 }
895 }
896 self.update_map_sub_focus();
897 }
898 DeepMatch::TextListItem { item_index, .. } => {
899 if let Some(item) = self.current_item_mut() {
900 if let SettingControl::TextList(ref mut list_state) = item.control {
901 list_state.focused_item = Some(*item_index);
902 }
903 }
904 self.sub_focus = Some(1 + *item_index);
906 }
907 }
908 }
909
910 pub fn current_search_result(&self) -> Option<&SearchResult> {
912 self.search_results.get(self.selected_search_result)
913 }
914
915 pub fn show_confirm_dialog(&mut self) {
917 self.showing_confirm_dialog = true;
918 self.confirm_dialog_selection = 0; }
920
921 pub fn hide_confirm_dialog(&mut self) {
923 self.showing_confirm_dialog = false;
924 self.confirm_dialog_selection = 0;
925 }
926
927 pub fn confirm_dialog_next(&mut self) {
929 self.confirm_dialog_selection = (self.confirm_dialog_selection + 1) % 3;
930 }
931
932 pub fn confirm_dialog_prev(&mut self) {
934 self.confirm_dialog_selection = if self.confirm_dialog_selection == 0 {
935 2
936 } else {
937 self.confirm_dialog_selection - 1
938 };
939 }
940
941 pub fn toggle_help(&mut self) {
943 self.showing_help = !self.showing_help;
944 }
945
946 pub fn hide_help(&mut self) {
948 self.showing_help = false;
949 }
950
951 pub fn showing_entry_dialog(&self) -> bool {
953 self.has_entry_dialog()
954 }
955
956 pub fn open_entry_dialog(&mut self) {
958 let Some(item) = self.current_item() else {
959 return;
960 };
961
962 let path = item.path.as_str();
964 let SettingControl::Map(map_state) = &item.control else {
965 return;
966 };
967
968 let Some(entry_idx) = map_state.focused_entry else {
970 return;
971 };
972 let Some((key, value)) = map_state.entries.get(entry_idx) else {
973 return;
974 };
975
976 let Some(schema) = map_state.value_schema.as_ref() else {
978 return; };
980
981 let no_delete = map_state.no_add;
983
984 let dialog =
986 EntryDialogState::from_schema(key.clone(), value, schema, path, false, no_delete);
987 self.entry_dialog_stack.push(dialog);
988 }
989
990 pub fn open_add_entry_dialog(&mut self) {
992 let Some(item) = self.current_item() else {
993 return;
994 };
995 let SettingControl::Map(map_state) = &item.control else {
996 return;
997 };
998 let Some(schema) = map_state.value_schema.as_ref() else {
999 return;
1000 };
1001 let path = item.path.clone();
1002
1003 let dialog = EntryDialogState::from_schema(
1006 String::new(),
1007 &serde_json::json!({}),
1008 schema,
1009 &path,
1010 true,
1011 false,
1012 );
1013 self.entry_dialog_stack.push(dialog);
1014 }
1015
1016 pub fn open_add_array_item_dialog(&mut self) {
1018 let Some(item) = self.current_item() else {
1019 return;
1020 };
1021 let SettingControl::ObjectArray(array_state) = &item.control else {
1022 return;
1023 };
1024 let Some(schema) = array_state.item_schema.as_ref() else {
1025 return;
1026 };
1027 let path = item.path.clone();
1028
1029 let dialog =
1031 EntryDialogState::for_array_item(None, &serde_json::json!({}), schema, &path, true);
1032 self.entry_dialog_stack.push(dialog);
1033 }
1034
1035 pub fn open_edit_array_item_dialog(&mut self) {
1037 let Some(item) = self.current_item() else {
1038 return;
1039 };
1040 let SettingControl::ObjectArray(array_state) = &item.control else {
1041 return;
1042 };
1043 let Some(schema) = array_state.item_schema.as_ref() else {
1044 return;
1045 };
1046 let Some(index) = array_state.focused_index else {
1047 return;
1048 };
1049 let Some(value) = array_state.bindings.get(index) else {
1050 return;
1051 };
1052 let path = item.path.clone();
1053
1054 let dialog = EntryDialogState::for_array_item(Some(index), value, schema, &path, false);
1055 self.entry_dialog_stack.push(dialog);
1056 }
1057
1058 pub fn close_entry_dialog(&mut self) {
1060 self.entry_dialog_stack.pop();
1061 }
1062
1063 pub fn open_nested_entry_dialog(&mut self) {
1068 let nested_info = self.entry_dialog().and_then(|dialog| {
1070 let item = dialog.current_item()?;
1071 let path = format!("{}/{}", dialog.map_path, item.path.trim_start_matches('/'));
1072
1073 match &item.control {
1074 SettingControl::Map(map_state) => {
1075 let schema = map_state.value_schema.as_ref()?;
1076 let no_delete = map_state.no_add; if let Some(entry_idx) = map_state.focused_entry {
1078 let (key, value) = map_state.entries.get(entry_idx)?;
1080 Some(NestedDialogInfo::MapEntry {
1081 key: key.clone(),
1082 value: value.clone(),
1083 schema: schema.as_ref().clone(),
1084 path,
1085 is_new: false,
1086 no_delete,
1087 })
1088 } else {
1089 Some(NestedDialogInfo::MapEntry {
1091 key: String::new(),
1092 value: serde_json::json!({}),
1093 schema: schema.as_ref().clone(),
1094 path,
1095 is_new: true,
1096 no_delete: false, })
1098 }
1099 }
1100 SettingControl::ObjectArray(array_state) => {
1101 let schema = array_state.item_schema.as_ref()?;
1102 if let Some(index) = array_state.focused_index {
1103 let value = array_state.bindings.get(index)?;
1105 Some(NestedDialogInfo::ArrayItem {
1106 index: Some(index),
1107 value: value.clone(),
1108 schema: schema.as_ref().clone(),
1109 path,
1110 is_new: false,
1111 })
1112 } else {
1113 Some(NestedDialogInfo::ArrayItem {
1115 index: None,
1116 value: serde_json::json!({}),
1117 schema: schema.as_ref().clone(),
1118 path,
1119 is_new: true,
1120 })
1121 }
1122 }
1123 _ => None,
1124 }
1125 });
1126
1127 if let Some(info) = nested_info {
1129 let dialog = match info {
1130 NestedDialogInfo::MapEntry {
1131 key,
1132 value,
1133 schema,
1134 path,
1135 is_new,
1136 no_delete,
1137 } => EntryDialogState::from_schema(key, &value, &schema, &path, is_new, no_delete),
1138 NestedDialogInfo::ArrayItem {
1139 index,
1140 value,
1141 schema,
1142 path,
1143 is_new,
1144 } => EntryDialogState::for_array_item(index, &value, &schema, &path, is_new),
1145 };
1146 self.entry_dialog_stack.push(dialog);
1147 }
1148 }
1149
1150 pub fn save_entry_dialog(&mut self) {
1155 let is_array = if self.entry_dialog_stack.len() > 1 {
1159 self.entry_dialog_stack
1161 .get(self.entry_dialog_stack.len() - 2)
1162 .and_then(|parent| parent.current_item())
1163 .map(|item| matches!(item.control, SettingControl::ObjectArray(_)))
1164 .unwrap_or(false)
1165 } else {
1166 self.current_item()
1168 .map(|item| matches!(item.control, SettingControl::ObjectArray(_)))
1169 .unwrap_or(false)
1170 };
1171
1172 if is_array {
1173 self.save_array_item_dialog_inner();
1174 } else {
1175 self.save_map_entry_dialog_inner();
1176 }
1177 }
1178
1179 fn save_map_entry_dialog_inner(&mut self) {
1181 let Some(dialog) = self.entry_dialog_stack.pop() else {
1182 return;
1183 };
1184
1185 let key = dialog.get_key();
1187 if key.is_empty() {
1188 return; }
1190
1191 let value = dialog.to_value();
1192 let map_path = dialog.map_path.clone();
1193 let original_key = dialog.entry_key.clone();
1194 let is_new = dialog.is_new;
1195 let key_changed = !is_new && key != original_key;
1196
1197 if let Some(item) = self.current_item_mut() {
1199 if let SettingControl::Map(map_state) = &mut item.control {
1200 if key_changed {
1202 if let Some(idx) = map_state
1203 .entries
1204 .iter()
1205 .position(|(k, _)| k == &original_key)
1206 {
1207 map_state.entries.remove(idx);
1208 }
1209 }
1210
1211 if let Some(entry) = map_state.entries.iter_mut().find(|(k, _)| k == &key) {
1213 entry.1 = value.clone();
1214 } else {
1215 map_state.entries.push((key.clone(), value.clone()));
1216 map_state.entries.sort_by(|a, b| a.0.cmp(&b.0));
1217 }
1218 }
1219 }
1220
1221 if key_changed {
1223 let old_path = format!("{}/{}", map_path, original_key);
1224 self.pending_changes
1225 .insert(old_path, serde_json::Value::Null);
1226 }
1227
1228 let path = format!("{}/{}", map_path, key);
1230 self.set_pending_change(&path, value);
1231 }
1232
1233 fn save_array_item_dialog_inner(&mut self) {
1235 let Some(dialog) = self.entry_dialog_stack.pop() else {
1236 return;
1237 };
1238
1239 let value = dialog.to_value();
1240 let array_path = dialog.map_path.clone();
1241 let is_new = dialog.is_new;
1242 let entry_key = dialog.entry_key.clone();
1243
1244 let is_nested = !self.entry_dialog_stack.is_empty();
1246
1247 if is_nested {
1248 let parent_map_path = self
1253 .entry_dialog_stack
1254 .last()
1255 .map(|p| p.map_path.as_str())
1256 .unwrap_or("");
1257 let item_path = array_path
1258 .strip_prefix(parent_map_path)
1259 .unwrap_or(&array_path)
1260 .trim_end_matches('/')
1261 .to_string();
1262
1263 if let Some(parent) = self.entry_dialog_stack.last_mut() {
1265 if let Some(item) = parent.items.iter_mut().find(|i| i.path == item_path) {
1266 if let SettingControl::ObjectArray(array_state) = &mut item.control {
1267 if is_new {
1268 array_state.bindings.push(value.clone());
1269 } else if let Ok(index) = entry_key.parse::<usize>() {
1270 if index < array_state.bindings.len() {
1271 array_state.bindings[index] = value.clone();
1272 }
1273 }
1274 }
1275 }
1276 }
1277
1278 if let Some(parent) = self.entry_dialog_stack.last() {
1281 if let Some(item) = parent.items.iter().find(|i| i.path == item_path) {
1282 if let SettingControl::ObjectArray(array_state) = &item.control {
1283 let array_value = serde_json::Value::Array(array_state.bindings.clone());
1284 self.set_pending_change(&array_path, array_value);
1285 }
1286 }
1287 }
1288 } else {
1289 if let Some(item) = self.current_item_mut() {
1291 if let SettingControl::ObjectArray(array_state) = &mut item.control {
1292 if is_new {
1293 array_state.bindings.push(value.clone());
1294 } else if let Ok(index) = entry_key.parse::<usize>() {
1295 if index < array_state.bindings.len() {
1296 array_state.bindings[index] = value.clone();
1297 }
1298 }
1299 }
1300 }
1301
1302 if let Some(item) = self.current_item() {
1304 if let SettingControl::ObjectArray(array_state) = &item.control {
1305 let array_value = serde_json::Value::Array(array_state.bindings.clone());
1306 self.set_pending_change(&array_path, array_value);
1307 }
1308 }
1309 }
1310 }
1311
1312 pub fn delete_entry_dialog(&mut self) {
1314 let is_nested = self.entry_dialog_stack.len() > 1;
1316
1317 let Some(dialog) = self.entry_dialog_stack.pop() else {
1318 return;
1319 };
1320
1321 let path = format!("{}/{}", dialog.map_path, dialog.entry_key);
1322
1323 if is_nested {
1325 let map_field = dialog.map_path.rsplit('/').next().unwrap_or("").to_string();
1328 let item_path = format!("/{}", map_field);
1329
1330 if let Some(parent) = self.entry_dialog_stack.last_mut() {
1332 if let Some(item) = parent.items.iter_mut().find(|i| i.path == item_path) {
1333 if let SettingControl::Map(map_state) = &mut item.control {
1334 if let Some(idx) = map_state
1335 .entries
1336 .iter()
1337 .position(|(k, _)| k == &dialog.entry_key)
1338 {
1339 map_state.remove_entry(idx);
1340 }
1341 }
1342 }
1343 }
1344 } else {
1345 if let Some(item) = self.current_item_mut() {
1347 if let SettingControl::Map(map_state) = &mut item.control {
1348 if let Some(idx) = map_state
1349 .entries
1350 .iter()
1351 .position(|(k, _)| k == &dialog.entry_key)
1352 {
1353 map_state.remove_entry(idx);
1354 }
1355 }
1356 }
1357 }
1358
1359 self.set_pending_change(&path, serde_json::Value::Null);
1361 }
1362
1363 pub fn max_scroll(&self) -> u16 {
1365 self.scroll_panel.scroll.max_offset()
1366 }
1367
1368 pub fn scroll_up(&mut self, delta: usize) -> bool {
1371 let old = self.scroll_panel.scroll.offset;
1372 self.scroll_panel.scroll_up(delta as u16);
1373 old != self.scroll_panel.scroll.offset
1374 }
1375
1376 pub fn scroll_down(&mut self, delta: usize) -> bool {
1379 let old = self.scroll_panel.scroll.offset;
1380 self.scroll_panel.scroll_down(delta as u16);
1381 old != self.scroll_panel.scroll.offset
1382 }
1383
1384 pub fn scroll_to_ratio(&mut self, ratio: f32) -> bool {
1387 let old = self.scroll_panel.scroll.offset;
1388 self.scroll_panel.scroll_to_ratio(ratio);
1389 old != self.scroll_panel.scroll.offset
1390 }
1391
1392 pub fn is_number_control(&self) -> bool {
1395 self.current_item()
1396 .is_some_and(|item| matches!(item.control, SettingControl::Number(_)))
1397 }
1398
1399 pub fn start_editing(&mut self) {
1400 if let Some(item) = self.current_item() {
1401 if matches!(
1402 item.control,
1403 SettingControl::TextList(_)
1404 | SettingControl::Text(_)
1405 | SettingControl::Map(_)
1406 | SettingControl::Json(_)
1407 ) {
1408 self.editing_text = true;
1409 }
1410 }
1411 }
1412
1413 pub fn stop_editing(&mut self) {
1415 self.editing_text = false;
1416 }
1417
1418 pub fn is_editable_control(&self) -> bool {
1420 self.current_item().is_some_and(|item| {
1421 matches!(
1422 item.control,
1423 SettingControl::TextList(_)
1424 | SettingControl::Text(_)
1425 | SettingControl::Map(_)
1426 | SettingControl::Json(_)
1427 )
1428 })
1429 }
1430
1431 pub fn is_editing_json(&self) -> bool {
1433 if !self.editing_text {
1434 return false;
1435 }
1436 self.current_item()
1437 .map(|item| matches!(&item.control, SettingControl::Json(_)))
1438 .unwrap_or(false)
1439 }
1440
1441 pub fn text_insert(&mut self, c: char) {
1443 if let Some(item) = self.current_item_mut() {
1444 match &mut item.control {
1445 SettingControl::TextList(state) => state.insert(c),
1446 SettingControl::Text(state) => {
1447 state.value.insert(state.cursor, c);
1448 state.cursor += c.len_utf8();
1449 }
1450 SettingControl::Map(state) => {
1451 state.new_key_text.insert(state.cursor, c);
1452 state.cursor += c.len_utf8();
1453 }
1454 SettingControl::Json(state) => state.insert(c),
1455 _ => {}
1456 }
1457 }
1458 }
1459
1460 pub fn text_backspace(&mut self) {
1462 if let Some(item) = self.current_item_mut() {
1463 match &mut item.control {
1464 SettingControl::TextList(state) => state.backspace(),
1465 SettingControl::Text(state) => {
1466 if state.cursor > 0 {
1467 let mut char_start = state.cursor - 1;
1468 while char_start > 0 && !state.value.is_char_boundary(char_start) {
1469 char_start -= 1;
1470 }
1471 state.value.remove(char_start);
1472 state.cursor = char_start;
1473 }
1474 }
1475 SettingControl::Map(state) => {
1476 if state.cursor > 0 {
1477 let mut char_start = state.cursor - 1;
1478 while char_start > 0 && !state.new_key_text.is_char_boundary(char_start) {
1479 char_start -= 1;
1480 }
1481 state.new_key_text.remove(char_start);
1482 state.cursor = char_start;
1483 }
1484 }
1485 SettingControl::Json(state) => state.backspace(),
1486 _ => {}
1487 }
1488 }
1489 }
1490
1491 pub fn text_move_left(&mut self) {
1493 if let Some(item) = self.current_item_mut() {
1494 match &mut item.control {
1495 SettingControl::TextList(state) => state.move_left(),
1496 SettingControl::Text(state) => {
1497 if state.cursor > 0 {
1498 let mut new_pos = state.cursor - 1;
1499 while new_pos > 0 && !state.value.is_char_boundary(new_pos) {
1500 new_pos -= 1;
1501 }
1502 state.cursor = new_pos;
1503 }
1504 }
1505 SettingControl::Map(state) => {
1506 if state.cursor > 0 {
1507 let mut new_pos = state.cursor - 1;
1508 while new_pos > 0 && !state.new_key_text.is_char_boundary(new_pos) {
1509 new_pos -= 1;
1510 }
1511 state.cursor = new_pos;
1512 }
1513 }
1514 SettingControl::Json(state) => state.move_left(),
1515 _ => {}
1516 }
1517 }
1518 }
1519
1520 pub fn text_move_right(&mut self) {
1522 if let Some(item) = self.current_item_mut() {
1523 match &mut item.control {
1524 SettingControl::TextList(state) => state.move_right(),
1525 SettingControl::Text(state) => {
1526 if state.cursor < state.value.len() {
1527 let mut new_pos = state.cursor + 1;
1528 while new_pos < state.value.len() && !state.value.is_char_boundary(new_pos)
1529 {
1530 new_pos += 1;
1531 }
1532 state.cursor = new_pos;
1533 }
1534 }
1535 SettingControl::Map(state) => {
1536 if state.cursor < state.new_key_text.len() {
1537 let mut new_pos = state.cursor + 1;
1538 while new_pos < state.new_key_text.len()
1539 && !state.new_key_text.is_char_boundary(new_pos)
1540 {
1541 new_pos += 1;
1542 }
1543 state.cursor = new_pos;
1544 }
1545 }
1546 SettingControl::Json(state) => state.move_right(),
1547 _ => {}
1548 }
1549 }
1550 }
1551
1552 pub fn text_focus_prev(&mut self) {
1554 if let Some(item) = self.current_item_mut() {
1555 match &mut item.control {
1556 SettingControl::TextList(state) => state.focus_prev(),
1557 SettingControl::Map(state) => {
1558 state.focus_prev();
1559 }
1560 _ => {}
1561 }
1562 }
1563 }
1564
1565 pub fn text_focus_next(&mut self) {
1567 if let Some(item) = self.current_item_mut() {
1568 match &mut item.control {
1569 SettingControl::TextList(state) => state.focus_next(),
1570 SettingControl::Map(state) => {
1571 state.focus_next();
1572 }
1573 _ => {}
1574 }
1575 }
1576 }
1577
1578 pub fn text_add_item(&mut self) {
1580 if let Some(item) = self.current_item_mut() {
1581 match &mut item.control {
1582 SettingControl::TextList(state) => state.add_item(),
1583 SettingControl::Map(state) => state.add_entry_from_input(),
1584 _ => {}
1585 }
1586 }
1587 self.on_value_changed();
1589 }
1590
1591 pub fn text_remove_focused(&mut self) {
1593 if let Some(item) = self.current_item_mut() {
1594 match &mut item.control {
1595 SettingControl::TextList(state) => {
1596 if let Some(idx) = state.focused_item {
1597 state.remove_item(idx);
1598 }
1599 }
1600 SettingControl::Map(state) => {
1601 if let Some(idx) = state.focused_entry {
1602 state.remove_entry(idx);
1603 }
1604 }
1605 _ => {}
1606 }
1607 }
1608 self.on_value_changed();
1610 }
1611
1612 pub fn json_cursor_up(&mut self) {
1616 if let Some(item) = self.current_item_mut() {
1617 if let SettingControl::Json(state) = &mut item.control {
1618 state.move_up();
1619 }
1620 }
1621 }
1622
1623 pub fn json_cursor_down(&mut self) {
1625 if let Some(item) = self.current_item_mut() {
1626 if let SettingControl::Json(state) = &mut item.control {
1627 state.move_down();
1628 }
1629 }
1630 }
1631
1632 pub fn json_insert_newline(&mut self) {
1634 if let Some(item) = self.current_item_mut() {
1635 if let SettingControl::Json(state) = &mut item.control {
1636 state.insert('\n');
1637 }
1638 }
1639 }
1640
1641 pub fn json_delete(&mut self) {
1643 if let Some(item) = self.current_item_mut() {
1644 if let SettingControl::Json(state) = &mut item.control {
1645 state.delete();
1646 }
1647 }
1648 }
1649
1650 pub fn json_exit_editing(&mut self) {
1652 let is_valid = self
1653 .current_item()
1654 .map(|item| {
1655 if let SettingControl::Json(state) = &item.control {
1656 state.is_valid()
1657 } else {
1658 true
1659 }
1660 })
1661 .unwrap_or(true);
1662
1663 if is_valid {
1664 if let Some(item) = self.current_item_mut() {
1665 if let SettingControl::Json(state) = &mut item.control {
1666 state.commit();
1667 }
1668 }
1669 self.on_value_changed();
1670 } else if let Some(item) = self.current_item_mut() {
1671 if let SettingControl::Json(state) = &mut item.control {
1672 state.revert();
1673 }
1674 }
1675 self.editing_text = false;
1676 }
1677
1678 pub fn json_select_all(&mut self) {
1680 if let Some(item) = self.current_item_mut() {
1681 if let SettingControl::Json(state) = &mut item.control {
1682 state.select_all();
1683 }
1684 }
1685 }
1686
1687 pub fn json_selected_text(&self) -> Option<String> {
1689 if let Some(item) = self.current_item() {
1690 if let SettingControl::Json(state) = &item.control {
1691 return state.selected_text();
1692 }
1693 }
1694 None
1695 }
1696
1697 pub fn json_cursor_up_selecting(&mut self) {
1699 if let Some(item) = self.current_item_mut() {
1700 if let SettingControl::Json(state) = &mut item.control {
1701 state.editor.move_up_selecting();
1702 }
1703 }
1704 }
1705
1706 pub fn json_cursor_down_selecting(&mut self) {
1708 if let Some(item) = self.current_item_mut() {
1709 if let SettingControl::Json(state) = &mut item.control {
1710 state.editor.move_down_selecting();
1711 }
1712 }
1713 }
1714
1715 pub fn json_cursor_left_selecting(&mut self) {
1717 if let Some(item) = self.current_item_mut() {
1718 if let SettingControl::Json(state) = &mut item.control {
1719 state.editor.move_left_selecting();
1720 }
1721 }
1722 }
1723
1724 pub fn json_cursor_right_selecting(&mut self) {
1726 if let Some(item) = self.current_item_mut() {
1727 if let SettingControl::Json(state) = &mut item.control {
1728 state.editor.move_right_selecting();
1729 }
1730 }
1731 }
1732
1733 pub fn is_dropdown_open(&self) -> bool {
1737 self.current_item().is_some_and(|item| {
1738 if let SettingControl::Dropdown(ref d) = item.control {
1739 d.open
1740 } else {
1741 false
1742 }
1743 })
1744 }
1745
1746 pub fn dropdown_toggle(&mut self) {
1748 let mut opened = false;
1749 if let Some(item) = self.current_item_mut() {
1750 if let SettingControl::Dropdown(ref mut d) = item.control {
1751 d.toggle_open();
1752 opened = d.open;
1753 }
1754 }
1755
1756 if opened {
1758 self.update_layout_widths();
1760 let selected_item = self.selected_item;
1761 if let Some(page) = self.pages.get(self.selected_category) {
1762 self.scroll_panel.update_content_height(&page.items);
1763 self.scroll_panel
1765 .ensure_focused_visible(&page.items, selected_item, None);
1766 }
1767 }
1768 }
1769
1770 pub fn dropdown_prev(&mut self) {
1772 if let Some(item) = self.current_item_mut() {
1773 if let SettingControl::Dropdown(ref mut d) = item.control {
1774 d.select_prev();
1775 }
1776 }
1777 }
1778
1779 pub fn dropdown_next(&mut self) {
1781 if let Some(item) = self.current_item_mut() {
1782 if let SettingControl::Dropdown(ref mut d) = item.control {
1783 d.select_next();
1784 }
1785 }
1786 }
1787
1788 pub fn dropdown_home(&mut self) {
1790 if let Some(item) = self.current_item_mut() {
1791 if let SettingControl::Dropdown(ref mut d) = item.control {
1792 if !d.options.is_empty() {
1793 d.selected = 0;
1794 d.ensure_visible();
1795 }
1796 }
1797 }
1798 }
1799
1800 pub fn dropdown_end(&mut self) {
1802 if let Some(item) = self.current_item_mut() {
1803 if let SettingControl::Dropdown(ref mut d) = item.control {
1804 if !d.options.is_empty() {
1805 d.selected = d.options.len() - 1;
1806 d.ensure_visible();
1807 }
1808 }
1809 }
1810 }
1811
1812 pub fn dropdown_confirm(&mut self) {
1814 if let Some(item) = self.current_item_mut() {
1815 if let SettingControl::Dropdown(ref mut d) = item.control {
1816 d.confirm();
1817 }
1818 }
1819 self.on_value_changed();
1820 }
1821
1822 pub fn dropdown_cancel(&mut self) {
1824 if let Some(item) = self.current_item_mut() {
1825 if let SettingControl::Dropdown(ref mut d) = item.control {
1826 d.cancel();
1827 }
1828 }
1829 }
1830
1831 pub fn dropdown_select(&mut self, option_idx: usize) {
1833 if let Some(item) = self.current_item_mut() {
1834 if let SettingControl::Dropdown(ref mut d) = item.control {
1835 if option_idx < d.options.len() {
1836 d.selected = option_idx;
1837 d.confirm();
1838 }
1839 }
1840 }
1841 self.on_value_changed();
1842 }
1843
1844 pub fn set_dropdown_hover(&mut self, hover_idx: Option<usize>) -> bool {
1847 if let Some(item) = self.current_item_mut() {
1848 if let SettingControl::Dropdown(ref mut d) = item.control {
1849 if d.open && d.hover_index != hover_idx {
1850 d.hover_index = hover_idx;
1851 return true;
1852 }
1853 }
1854 }
1855 false
1856 }
1857
1858 pub fn dropdown_scroll(&mut self, delta: i32) {
1860 if let Some(item) = self.current_item_mut() {
1861 if let SettingControl::Dropdown(ref mut d) = item.control {
1862 if d.open {
1863 d.scroll_by(delta);
1864 }
1865 }
1866 }
1867 }
1868
1869 pub fn is_number_editing(&self) -> bool {
1873 self.current_item().is_some_and(|item| {
1874 if let SettingControl::Number(ref n) = item.control {
1875 n.editing()
1876 } else {
1877 false
1878 }
1879 })
1880 }
1881
1882 pub fn start_number_editing(&mut self) {
1884 if let Some(item) = self.current_item_mut() {
1885 if let SettingControl::Number(ref mut n) = item.control {
1886 n.start_editing();
1887 }
1888 }
1889 }
1890
1891 pub fn number_insert(&mut self, c: char) {
1893 if let Some(item) = self.current_item_mut() {
1894 if let SettingControl::Number(ref mut n) = item.control {
1895 n.insert_char(c);
1896 }
1897 }
1898 }
1899
1900 pub fn number_backspace(&mut self) {
1902 if let Some(item) = self.current_item_mut() {
1903 if let SettingControl::Number(ref mut n) = item.control {
1904 n.backspace();
1905 }
1906 }
1907 }
1908
1909 pub fn number_confirm(&mut self) {
1911 if let Some(item) = self.current_item_mut() {
1912 if let SettingControl::Number(ref mut n) = item.control {
1913 n.confirm_editing();
1914 }
1915 }
1916 self.on_value_changed();
1917 }
1918
1919 pub fn number_cancel(&mut self) {
1921 if let Some(item) = self.current_item_mut() {
1922 if let SettingControl::Number(ref mut n) = item.control {
1923 n.cancel_editing();
1924 }
1925 }
1926 }
1927
1928 pub fn number_delete(&mut self) {
1930 if let Some(item) = self.current_item_mut() {
1931 if let SettingControl::Number(ref mut n) = item.control {
1932 n.delete();
1933 }
1934 }
1935 }
1936
1937 pub fn number_move_left(&mut self) {
1939 if let Some(item) = self.current_item_mut() {
1940 if let SettingControl::Number(ref mut n) = item.control {
1941 n.move_left();
1942 }
1943 }
1944 }
1945
1946 pub fn number_move_right(&mut self) {
1948 if let Some(item) = self.current_item_mut() {
1949 if let SettingControl::Number(ref mut n) = item.control {
1950 n.move_right();
1951 }
1952 }
1953 }
1954
1955 pub fn number_move_home(&mut self) {
1957 if let Some(item) = self.current_item_mut() {
1958 if let SettingControl::Number(ref mut n) = item.control {
1959 n.move_home();
1960 }
1961 }
1962 }
1963
1964 pub fn number_move_end(&mut self) {
1966 if let Some(item) = self.current_item_mut() {
1967 if let SettingControl::Number(ref mut n) = item.control {
1968 n.move_end();
1969 }
1970 }
1971 }
1972
1973 pub fn number_move_left_selecting(&mut self) {
1975 if let Some(item) = self.current_item_mut() {
1976 if let SettingControl::Number(ref mut n) = item.control {
1977 n.move_left_selecting();
1978 }
1979 }
1980 }
1981
1982 pub fn number_move_right_selecting(&mut self) {
1984 if let Some(item) = self.current_item_mut() {
1985 if let SettingControl::Number(ref mut n) = item.control {
1986 n.move_right_selecting();
1987 }
1988 }
1989 }
1990
1991 pub fn number_move_home_selecting(&mut self) {
1993 if let Some(item) = self.current_item_mut() {
1994 if let SettingControl::Number(ref mut n) = item.control {
1995 n.move_home_selecting();
1996 }
1997 }
1998 }
1999
2000 pub fn number_move_end_selecting(&mut self) {
2002 if let Some(item) = self.current_item_mut() {
2003 if let SettingControl::Number(ref mut n) = item.control {
2004 n.move_end_selecting();
2005 }
2006 }
2007 }
2008
2009 pub fn number_move_word_left(&mut self) {
2011 if let Some(item) = self.current_item_mut() {
2012 if let SettingControl::Number(ref mut n) = item.control {
2013 n.move_word_left();
2014 }
2015 }
2016 }
2017
2018 pub fn number_move_word_right(&mut self) {
2020 if let Some(item) = self.current_item_mut() {
2021 if let SettingControl::Number(ref mut n) = item.control {
2022 n.move_word_right();
2023 }
2024 }
2025 }
2026
2027 pub fn number_move_word_left_selecting(&mut self) {
2029 if let Some(item) = self.current_item_mut() {
2030 if let SettingControl::Number(ref mut n) = item.control {
2031 n.move_word_left_selecting();
2032 }
2033 }
2034 }
2035
2036 pub fn number_move_word_right_selecting(&mut self) {
2038 if let Some(item) = self.current_item_mut() {
2039 if let SettingControl::Number(ref mut n) = item.control {
2040 n.move_word_right_selecting();
2041 }
2042 }
2043 }
2044
2045 pub fn number_select_all(&mut self) {
2047 if let Some(item) = self.current_item_mut() {
2048 if let SettingControl::Number(ref mut n) = item.control {
2049 n.select_all();
2050 }
2051 }
2052 }
2053
2054 pub fn number_delete_word_backward(&mut self) {
2056 if let Some(item) = self.current_item_mut() {
2057 if let SettingControl::Number(ref mut n) = item.control {
2058 n.delete_word_backward();
2059 }
2060 }
2061 }
2062
2063 pub fn number_delete_word_forward(&mut self) {
2065 if let Some(item) = self.current_item_mut() {
2066 if let SettingControl::Number(ref mut n) = item.control {
2067 n.delete_word_forward();
2068 }
2069 }
2070 }
2071
2072 pub fn get_change_descriptions(&self) -> Vec<String> {
2074 let mut descriptions: Vec<String> = self
2075 .pending_changes
2076 .iter()
2077 .map(|(path, value)| {
2078 let value_str = match value {
2079 serde_json::Value::Bool(b) => b.to_string(),
2080 serde_json::Value::Number(n) => n.to_string(),
2081 serde_json::Value::String(s) => format!("\"{}\"", s),
2082 _ => value.to_string(),
2083 };
2084 format!("{}: {}", path, value_str)
2085 })
2086 .collect();
2087 for path in &self.pending_deletions {
2089 descriptions.push(format!("{}: (reset to default)", path));
2090 }
2091 descriptions.sort();
2092 descriptions
2093 }
2094}
2095
2096fn update_control_from_value(control: &mut SettingControl, value: &serde_json::Value) {
2098 match control {
2099 SettingControl::Toggle(state) => {
2100 if let Some(b) = value.as_bool() {
2101 state.checked = b;
2102 }
2103 }
2104 SettingControl::Number(state) => {
2105 if let Some(n) = value.as_i64() {
2106 state.value = n;
2107 }
2108 }
2109 SettingControl::Dropdown(state) => {
2110 if let Some(s) = value.as_str() {
2111 if let Some(idx) = state.options.iter().position(|o| o == s) {
2112 state.selected = idx;
2113 }
2114 }
2115 }
2116 SettingControl::Text(state) => {
2117 if let Some(s) = value.as_str() {
2118 state.value = s.to_string();
2119 state.cursor = state.value.len();
2120 }
2121 }
2122 SettingControl::TextList(state) => {
2123 if let Some(arr) = value.as_array() {
2124 state.items = arr
2125 .iter()
2126 .filter_map(|v| {
2127 if state.is_integer {
2128 v.as_i64()
2129 .map(|n| n.to_string())
2130 .or_else(|| v.as_u64().map(|n| n.to_string()))
2131 .or_else(|| v.as_f64().map(|n| n.to_string()))
2132 } else {
2133 v.as_str().map(String::from)
2134 }
2135 })
2136 .collect();
2137 }
2138 }
2139 SettingControl::Map(state) => {
2140 if let Some(obj) = value.as_object() {
2141 state.entries = obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
2142 state.entries.sort_by(|a, b| a.0.cmp(&b.0));
2143 }
2144 }
2145 SettingControl::ObjectArray(state) => {
2146 if let Some(arr) = value.as_array() {
2147 state.bindings = arr.clone();
2148 }
2149 }
2150 SettingControl::Json(state) => {
2151 let json_str =
2153 serde_json::to_string_pretty(value).unwrap_or_else(|_| "null".to_string());
2154 let json_str = if json_str.is_empty() {
2155 "null".to_string()
2156 } else {
2157 json_str
2158 };
2159 state.original_text = json_str.clone();
2160 state.editor.set_value(&json_str);
2161 state.scroll_offset = 0;
2162 }
2163 SettingControl::Complex { .. } => {}
2164 }
2165}
2166
2167#[cfg(test)]
2168mod tests {
2169 use super::*;
2170
2171 const TEST_SCHEMA: &str = r#"
2172{
2173 "type": "object",
2174 "properties": {
2175 "theme": {
2176 "type": "string",
2177 "default": "dark"
2178 },
2179 "line_numbers": {
2180 "type": "boolean",
2181 "default": true
2182 }
2183 },
2184 "$defs": {}
2185}
2186"#;
2187
2188 fn test_config() -> Config {
2189 Config::default()
2190 }
2191
2192 #[test]
2193 fn test_settings_state_creation() {
2194 let config = test_config();
2195 let state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2196
2197 assert!(!state.visible);
2198 assert_eq!(state.selected_category, 0);
2199 assert!(!state.has_changes());
2200 }
2201
2202 #[test]
2203 fn test_navigation() {
2204 let config = test_config();
2205 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2206
2207 assert_eq!(state.focus_panel(), FocusPanel::Categories);
2209
2210 state.toggle_focus();
2212 assert_eq!(state.focus_panel(), FocusPanel::Settings);
2213
2214 state.select_next();
2216 assert_eq!(state.selected_item, 1);
2217
2218 state.select_prev();
2219 assert_eq!(state.selected_item, 0);
2220 }
2221
2222 #[test]
2223 fn test_pending_changes() {
2224 let config = test_config();
2225 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2226
2227 assert!(!state.has_changes());
2228
2229 state.set_pending_change("/theme", serde_json::Value::String("light".to_string()));
2230 assert!(state.has_changes());
2231
2232 state.discard_changes();
2233 assert!(!state.has_changes());
2234 }
2235
2236 #[test]
2237 fn test_show_hide() {
2238 let config = test_config();
2239 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2240
2241 assert!(!state.visible);
2242
2243 state.show();
2244 assert!(state.visible);
2245 assert_eq!(state.focus_panel(), FocusPanel::Categories);
2246
2247 state.hide();
2248 assert!(!state.visible);
2249 }
2250
2251 const TEST_SCHEMA_CONTROLS: &str = r#"
2253{
2254 "type": "object",
2255 "properties": {
2256 "theme": {
2257 "type": "string",
2258 "enum": ["dark", "light", "high-contrast"],
2259 "default": "dark"
2260 },
2261 "tab_size": {
2262 "type": "integer",
2263 "minimum": 1,
2264 "maximum": 8,
2265 "default": 4
2266 },
2267 "line_numbers": {
2268 "type": "boolean",
2269 "default": true
2270 }
2271 },
2272 "$defs": {}
2273}
2274"#;
2275
2276 #[test]
2277 fn test_dropdown_toggle() {
2278 let config = test_config();
2279 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2280 state.show();
2281 state.toggle_focus(); state.select_next();
2286 state.select_next();
2287 assert!(!state.is_dropdown_open());
2288
2289 state.dropdown_toggle();
2290 assert!(state.is_dropdown_open());
2291
2292 state.dropdown_toggle();
2293 assert!(!state.is_dropdown_open());
2294 }
2295
2296 #[test]
2297 fn test_dropdown_cancel_restores() {
2298 let config = test_config();
2299 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2300 state.show();
2301 state.toggle_focus();
2302
2303 state.select_next();
2306 state.select_next();
2307
2308 state.dropdown_toggle();
2310 assert!(state.is_dropdown_open());
2311
2312 let initial = state.current_item().and_then(|item| {
2314 if let SettingControl::Dropdown(ref d) = item.control {
2315 Some(d.selected)
2316 } else {
2317 None
2318 }
2319 });
2320
2321 state.dropdown_next();
2323 let after_change = state.current_item().and_then(|item| {
2324 if let SettingControl::Dropdown(ref d) = item.control {
2325 Some(d.selected)
2326 } else {
2327 None
2328 }
2329 });
2330 assert_ne!(initial, after_change);
2331
2332 state.dropdown_cancel();
2334 assert!(!state.is_dropdown_open());
2335
2336 let after_cancel = state.current_item().and_then(|item| {
2337 if let SettingControl::Dropdown(ref d) = item.control {
2338 Some(d.selected)
2339 } else {
2340 None
2341 }
2342 });
2343 assert_eq!(initial, after_cancel);
2344 }
2345
2346 #[test]
2347 fn test_dropdown_confirm_keeps_selection() {
2348 let config = test_config();
2349 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2350 state.show();
2351 state.toggle_focus();
2352
2353 state.dropdown_toggle();
2355
2356 state.dropdown_next();
2358 let after_change = state.current_item().and_then(|item| {
2359 if let SettingControl::Dropdown(ref d) = item.control {
2360 Some(d.selected)
2361 } else {
2362 None
2363 }
2364 });
2365
2366 state.dropdown_confirm();
2368 assert!(!state.is_dropdown_open());
2369
2370 let after_confirm = state.current_item().and_then(|item| {
2371 if let SettingControl::Dropdown(ref d) = item.control {
2372 Some(d.selected)
2373 } else {
2374 None
2375 }
2376 });
2377 assert_eq!(after_change, after_confirm);
2378 }
2379
2380 #[test]
2381 fn test_number_editing() {
2382 let config = test_config();
2383 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2384 state.show();
2385 state.toggle_focus();
2386
2387 state.select_next();
2389
2390 assert!(!state.is_number_editing());
2392
2393 state.start_number_editing();
2395 assert!(state.is_number_editing());
2396
2397 state.number_insert('8');
2399
2400 state.number_confirm();
2402 assert!(!state.is_number_editing());
2403 }
2404
2405 #[test]
2406 fn test_number_cancel_editing() {
2407 let config = test_config();
2408 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2409 state.show();
2410 state.toggle_focus();
2411
2412 state.select_next();
2414
2415 let initial_value = state.current_item().and_then(|item| {
2417 if let SettingControl::Number(ref n) = item.control {
2418 Some(n.value)
2419 } else {
2420 None
2421 }
2422 });
2423
2424 state.start_number_editing();
2426 state.number_backspace();
2427 state.number_insert('9');
2428 state.number_insert('9');
2429
2430 state.number_cancel();
2432 assert!(!state.is_number_editing());
2433
2434 let after_cancel = state.current_item().and_then(|item| {
2436 if let SettingControl::Number(ref n) = item.control {
2437 Some(n.value)
2438 } else {
2439 None
2440 }
2441 });
2442 assert_eq!(initial_value, after_cancel);
2443 }
2444
2445 #[test]
2446 fn test_number_backspace() {
2447 let config = test_config();
2448 let mut state = SettingsState::new(TEST_SCHEMA_CONTROLS, &config).unwrap();
2449 state.show();
2450 state.toggle_focus();
2451 state.select_next();
2452
2453 state.start_number_editing();
2454 state.number_backspace();
2455
2456 let display_text = state.current_item().and_then(|item| {
2458 if let SettingControl::Number(ref n) = item.control {
2459 Some(n.display_text())
2460 } else {
2461 None
2462 }
2463 });
2464 assert_eq!(display_text, Some(String::new()));
2466
2467 state.number_cancel();
2468 }
2469
2470 #[test]
2471 fn test_layer_selection() {
2472 let config = test_config();
2473 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2474
2475 assert_eq!(state.target_layer, ConfigLayer::User);
2477 assert_eq!(state.target_layer_name(), "User");
2478
2479 state.cycle_target_layer();
2481 assert_eq!(state.target_layer, ConfigLayer::Project);
2482 assert_eq!(state.target_layer_name(), "Project");
2483
2484 state.cycle_target_layer();
2485 assert_eq!(state.target_layer, ConfigLayer::Session);
2486 assert_eq!(state.target_layer_name(), "Session");
2487
2488 state.cycle_target_layer();
2489 assert_eq!(state.target_layer, ConfigLayer::User);
2490
2491 state.set_target_layer(ConfigLayer::Project);
2493 assert_eq!(state.target_layer, ConfigLayer::Project);
2494
2495 state.set_target_layer(ConfigLayer::System);
2497 assert_eq!(state.target_layer, ConfigLayer::Project);
2498 }
2499
2500 #[test]
2501 fn test_layer_switch_clears_pending_changes() {
2502 let config = test_config();
2503 let mut state = SettingsState::new(TEST_SCHEMA, &config).unwrap();
2504
2505 state.set_pending_change("/theme", serde_json::Value::String("light".to_string()));
2507 assert!(state.has_changes());
2508
2509 state.cycle_target_layer();
2511 assert!(!state.has_changes());
2512 }
2513}