1use super::items::{build_item_from_value, control_to_value, SettingControl, SettingItem};
7use super::schema::{SettingSchema, SettingType};
8use crate::view::controls::{FocusState, TextInputState};
9use serde_json::Value;
10
11#[derive(Debug, Clone)]
13pub struct EntryDialogState {
14 pub entry_key: String,
16 pub map_path: String,
18 pub title: String,
20 pub is_new: bool,
22 pub items: Vec<SettingItem>,
24 pub selected_item: usize,
26 pub sub_focus: Option<usize>,
28 pub editing_text: bool,
30 pub focused_button: usize,
32 pub focus_on_buttons: bool,
34 pub delete_requested: bool,
36 pub scroll_offset: usize,
38 pub viewport_height: usize,
40 pub hover_item: Option<usize>,
42 pub hover_button: Option<usize>,
44 pub original_value: Value,
46 pub first_editable_index: usize,
49 pub no_delete: bool,
51 pub is_single_value: bool,
54}
55
56impl EntryDialogState {
57 pub fn from_schema(
63 key: String,
64 value: &Value,
65 schema: &SettingSchema,
66 map_path: &str,
67 is_new: bool,
68 no_delete: bool,
69 ) -> Self {
70 let mut items = Vec::new();
71
72 let key_item = SettingItem {
74 path: "__key__".to_string(),
75 name: "Key".to_string(),
76 description: Some("unique identifier for this entry".to_string()),
77 control: SettingControl::Text(TextInputState::new("Key").with_value(&key)),
78 default: None,
79 modified: false,
80 layer_source: crate::config_io::ConfigLayer::System,
81 read_only: !is_new, is_auto_managed: false,
83 section: None,
84 is_section_start: false,
85 layout_width: 0,
86 };
87 items.push(key_item);
88
89 let is_single_value = !matches!(&schema.setting_type, SettingType::Object { .. });
91 if let SettingType::Object { properties } = &schema.setting_type {
92 for prop in properties {
93 let field_name = prop.path.trim_start_matches('/');
94 let field_value = value.get(field_name);
95 let item = build_item_from_value(prop, field_value);
96 items.push(item);
97 }
98 } else {
99 let item = build_item_from_value(schema, Some(value));
102 items.push(item);
103 }
104
105 items.sort_by_key(|item| !item.read_only);
107
108 Self::compute_section_starts(&mut items);
110
111 let first_editable_index = items
113 .iter()
114 .position(|item| !item.read_only)
115 .unwrap_or(items.len());
116
117 let focus_on_buttons = first_editable_index >= items.len();
119 let selected_item = if focus_on_buttons {
120 0
121 } else {
122 first_editable_index
123 };
124
125 let title = if is_new {
126 format!("Add {}", schema.name)
127 } else {
128 format!("Edit {}", schema.name)
129 };
130
131 let mut result = Self {
132 entry_key: key,
133 map_path: map_path.to_string(),
134 title,
135 is_new,
136 items,
137 selected_item,
138 sub_focus: None,
139 editing_text: false,
140 focused_button: 0,
141 focus_on_buttons,
142 delete_requested: false,
143 scroll_offset: 0,
144 viewport_height: 20, hover_item: None,
146 hover_button: None,
147 original_value: value.clone(),
148 first_editable_index,
149 no_delete,
150 is_single_value,
151 };
152 result.init_object_array_focus();
155 result
156 }
157
158 pub fn for_array_item(
162 index: Option<usize>,
163 value: &Value,
164 schema: &SettingSchema,
165 array_path: &str,
166 is_new: bool,
167 ) -> Self {
168 let mut items = Vec::new();
169
170 if let SettingType::Object { properties } = &schema.setting_type {
172 for prop in properties {
173 let field_name = prop.path.trim_start_matches('/');
174 let field_value = value.get(field_name);
175 let item = build_item_from_value(prop, field_value);
176 items.push(item);
177 }
178 }
179
180 items.sort_by_key(|item| !item.read_only);
182
183 Self::compute_section_starts(&mut items);
185
186 let first_editable_index = items
188 .iter()
189 .position(|item| !item.read_only)
190 .unwrap_or(items.len());
191
192 let focus_on_buttons = first_editable_index >= items.len();
194 let selected_item = if focus_on_buttons {
195 0
196 } else {
197 first_editable_index
198 };
199
200 let title = if is_new {
201 format!("Add {}", schema.name)
202 } else {
203 format!("Edit {}", schema.name)
204 };
205
206 Self {
207 entry_key: index.map_or(String::new(), |i| i.to_string()),
208 map_path: array_path.to_string(),
209 title,
210 is_new,
211 items,
212 selected_item,
213 sub_focus: None,
214 editing_text: false,
215 focused_button: 0,
216 focus_on_buttons,
217 delete_requested: false,
218 scroll_offset: 0,
219 viewport_height: 20,
220 hover_item: None,
221 hover_button: None,
222 original_value: value.clone(),
223 first_editable_index,
224 no_delete: false, is_single_value: false,
226 }
227 }
228
229 fn compute_section_starts(items: &mut [SettingItem]) {
232 let mut last_section: Option<&str> = None;
233 for item in items.iter_mut() {
234 let current = item.section.as_deref();
235 if current.is_some() && current != last_section {
236 item.is_section_start = true;
237 }
238 if current.is_some() {
239 last_section = current;
240 }
241 }
242 }
243
244 pub fn get_key(&self) -> String {
246 for item in &self.items {
248 if item.path == "__key__" {
249 if let SettingControl::Text(state) = &item.control {
250 return state.value.clone();
251 }
252 }
253 }
254 self.entry_key.clone()
255 }
256
257 pub fn button_count(&self) -> usize {
259 if self.is_new || self.no_delete {
260 2 } else {
262 3
263 }
264 }
265
266 pub fn to_value(&self) -> Value {
268 if self.is_single_value {
271 for item in &self.items {
272 if item.path != "__key__" {
273 return control_to_value(&item.control);
274 }
275 }
276 }
277
278 let mut obj = serde_json::Map::new();
279
280 for item in &self.items {
281 if item.path == "__key__" {
283 continue;
284 }
285
286 let field_name = item.path.trim_start_matches('/');
287 let value = control_to_value(&item.control);
288 obj.insert(field_name.to_string(), value);
289 }
290
291 Value::Object(obj)
292 }
293
294 pub fn current_item(&self) -> Option<&SettingItem> {
296 if self.focus_on_buttons {
297 None
298 } else {
299 self.items.get(self.selected_item)
300 }
301 }
302
303 pub fn current_item_mut(&mut self) -> Option<&mut SettingItem> {
305 if self.focus_on_buttons {
306 None
307 } else {
308 self.items.get_mut(self.selected_item)
309 }
310 }
311
312 pub fn focus_next(&mut self) {
319 if self.editing_text {
320 return;
321 }
322
323 if self.focus_on_buttons {
324 if self.focused_button + 1 < self.button_count() {
325 self.focused_button += 1;
326 } else {
327 if self.first_editable_index < self.items.len() {
329 self.focus_on_buttons = false;
330 self.selected_item = self.first_editable_index;
331 self.sub_focus = None;
332 self.init_composite_focus(true);
333 }
334 }
335 } else {
336 let handled = self.try_composite_focus_next();
338 if !handled {
339 if self.selected_item + 1 < self.items.len() {
341 self.selected_item += 1;
342 self.sub_focus = None;
343 self.init_composite_focus(true);
344 } else {
345 self.focus_on_buttons = true;
347 self.focused_button = 0;
348 }
349 }
350 }
351
352 self.update_focus_states();
353 self.ensure_selected_visible(self.viewport_height);
354 }
355
356 pub fn focus_prev(&mut self) {
362 if self.editing_text {
363 return;
364 }
365
366 if self.focus_on_buttons {
367 if self.focused_button > 0 {
368 self.focused_button -= 1;
369 } else {
370 if self.first_editable_index < self.items.len() {
372 self.focus_on_buttons = false;
373 self.selected_item = self.items.len().saturating_sub(1);
374 self.sub_focus = None;
375 self.init_composite_focus(false);
376 }
377 }
378 } else {
379 let handled = self.try_composite_focus_prev();
381 if !handled {
382 if self.selected_item > self.first_editable_index {
384 self.selected_item -= 1;
385 self.sub_focus = None;
386 self.init_composite_focus(false);
387 } else {
388 self.focus_on_buttons = true;
390 self.focused_button = self.button_count().saturating_sub(1);
391 }
392 }
393 }
394
395 self.update_focus_states();
396 self.ensure_selected_visible(self.viewport_height);
397 }
398
399 fn try_composite_focus_next(&mut self) -> bool {
402 let item = match self.items.get(self.selected_item) {
403 Some(item) => item,
404 None => return false,
405 };
406 match &item.control {
407 SettingControl::Map(state) => {
408 let at_boundary = state.focused_entry.is_none(); if at_boundary {
411 return false;
412 }
413 if let Some(item) = self.items.get_mut(self.selected_item) {
414 if let SettingControl::Map(state) = &mut item.control {
415 return state.focus_next();
416 }
417 }
418 false
419 }
420 SettingControl::ObjectArray(state) => {
421 if state.focused_index.is_none() {
423 return false;
424 }
425 if let Some(item) = self.items.get_mut(self.selected_item) {
426 if let SettingControl::ObjectArray(state) = &mut item.control {
427 state.focus_next();
428 return true;
429 }
430 }
431 false
432 }
433 SettingControl::TextList(state) => {
434 if state.focused_item.is_none() {
436 return false;
437 }
438 if let Some(item) = self.items.get_mut(self.selected_item) {
439 if let SettingControl::TextList(state) = &mut item.control {
440 state.focus_next();
441 return true;
442 }
443 }
444 false
445 }
446 _ => false,
447 }
448 }
449
450 fn try_composite_focus_prev(&mut self) -> bool {
453 let item = match self.items.get(self.selected_item) {
454 Some(item) => item,
455 None => return false,
456 };
457 match &item.control {
458 SettingControl::Map(state) => {
459 let at_boundary = matches!(state.focused_entry, Some(0))
461 || (state.focused_entry.is_none() && state.entries.is_empty());
462 if at_boundary {
463 return false;
464 }
465 if let Some(item) = self.items.get_mut(self.selected_item) {
466 if let SettingControl::Map(state) = &mut item.control {
467 return state.focus_prev();
468 }
469 }
470 false
471 }
472 SettingControl::ObjectArray(state) => {
473 if matches!(state.focused_index, Some(0))
475 || (state.focused_index.is_none() && state.bindings.is_empty())
476 {
477 return false;
478 }
479 if let Some(item) = self.items.get_mut(self.selected_item) {
480 if let SettingControl::ObjectArray(state) = &mut item.control {
481 state.focus_prev();
482 return true;
483 }
484 }
485 false
486 }
487 SettingControl::TextList(state) => {
488 if matches!(state.focused_item, Some(0))
490 || (state.focused_item.is_none() && state.items.is_empty())
491 {
492 return false;
493 }
494 if let Some(item) = self.items.get_mut(self.selected_item) {
495 if let SettingControl::TextList(state) = &mut item.control {
496 state.focus_prev();
497 return true;
498 }
499 }
500 false
501 }
502 _ => false,
503 }
504 }
505
506 fn init_composite_focus(&mut self, from_above: bool) {
510 if let Some(item) = self.items.get_mut(self.selected_item) {
511 match &mut item.control {
512 SettingControl::Map(state) => {
513 state.init_focus(from_above);
514 }
515 SettingControl::ObjectArray(state) => {
516 if from_above {
517 state.focused_index = if state.bindings.is_empty() {
518 None
519 } else {
520 Some(0)
521 };
522 } else {
523 state.focused_index = None;
525 }
526 }
527 SettingControl::TextList(state) => {
528 if from_above {
529 state.focused_item = if state.items.is_empty() {
530 None
531 } else {
532 Some(0)
533 };
534 } else {
535 state.focused_item = None;
537 }
538 }
539 _ => {}
540 }
541 }
542 }
543
544 pub fn toggle_focus_region(&mut self) {
547 self.toggle_focus_region_direction(true);
548 }
549
550 pub fn toggle_focus_region_direction(&mut self, forward: bool) {
554 if self.editing_text {
555 return;
556 }
557
558 if self.focus_on_buttons {
559 if forward {
560 if self.focused_button + 1 < self.button_count() {
562 self.focused_button += 1;
563 } else {
564 if self.first_editable_index < self.items.len() {
566 self.focus_on_buttons = false;
567 if self.selected_item < self.first_editable_index {
568 self.selected_item = self.first_editable_index;
569 }
570 } else {
571 self.focused_button = 0;
573 }
574 }
575 } else {
576 if self.focused_button > 0 {
578 self.focused_button -= 1;
579 } else {
580 if self.first_editable_index < self.items.len() {
582 self.focus_on_buttons = false;
583 if self.selected_item < self.first_editable_index {
584 self.selected_item = self.first_editable_index;
585 }
586 } else {
587 self.focused_button = self.button_count().saturating_sub(1);
589 }
590 }
591 }
592 } else {
593 self.focus_on_buttons = true;
595 self.focused_button = if forward {
596 0
597 } else {
598 self.button_count().saturating_sub(1)
599 };
600 }
601
602 self.update_focus_states();
603 self.ensure_selected_visible(self.viewport_height);
604 }
605
606 fn init_object_array_focus(&mut self) {
608 self.init_composite_focus(true);
609 }
610
611 pub fn update_focus_states(&mut self) {
613 for (idx, item) in self.items.iter_mut().enumerate() {
614 let state = if !self.focus_on_buttons && idx == self.selected_item {
615 FocusState::Focused
616 } else {
617 FocusState::Normal
618 };
619
620 match &mut item.control {
621 SettingControl::Toggle(s) => s.focus = state,
622 SettingControl::Number(s) => s.focus = state,
623 SettingControl::Dropdown(s) => s.focus = state,
624 SettingControl::Text(s) => s.focus = state,
625 SettingControl::TextList(s) => s.focus = state,
626 SettingControl::Map(s) => s.focus = state,
627 SettingControl::ObjectArray(s) => s.focus = state,
628 SettingControl::Json(s) => s.focus = state,
629 SettingControl::Complex { .. } => {}
630 }
631 }
632 }
633
634 const SECTION_HEADER_HEIGHT: usize = 2;
636
637 pub fn total_content_height(&self) -> usize {
639 let items_height: usize = self
640 .items
641 .iter()
642 .map(|item| {
643 let section_h = if item.is_section_start {
644 Self::SECTION_HEADER_HEIGHT
645 } else {
646 0
647 };
648 item.control.control_height() as usize + section_h
649 })
650 .sum();
651 let separator_height =
653 if self.first_editable_index > 0 && self.first_editable_index < self.items.len() {
654 1
655 } else {
656 0
657 };
658 items_height + separator_height
659 }
660
661 pub fn selected_item_offset(&self) -> usize {
663 let items_offset: usize = self
664 .items
665 .iter()
666 .take(self.selected_item)
667 .map(|item| {
668 let section_h = if item.is_section_start {
669 Self::SECTION_HEADER_HEIGHT
670 } else {
671 0
672 };
673 item.control.control_height() as usize + section_h
674 })
675 .sum();
676 let separator_offset = if self.first_editable_index > 0
678 && self.first_editable_index < self.items.len()
679 && self.selected_item >= self.first_editable_index
680 {
681 1
682 } else {
683 0
684 };
685 let own_section_h = self
687 .items
688 .get(self.selected_item)
689 .map(|item| {
690 if item.is_section_start {
691 Self::SECTION_HEADER_HEIGHT
692 } else {
693 0
694 }
695 })
696 .unwrap_or(0);
697 items_offset + separator_offset + own_section_h
698 }
699
700 pub fn selected_item_height(&self) -> usize {
702 self.items
703 .get(self.selected_item)
704 .map(|item| item.control.control_height() as usize)
705 .unwrap_or(1)
706 }
707
708 pub fn ensure_selected_visible(&mut self, viewport_height: usize) {
710 if self.focus_on_buttons {
711 let total = self.total_content_height();
713 if total > viewport_height {
714 self.scroll_offset = total.saturating_sub(viewport_height);
715 }
716 return;
717 }
718
719 let item_start = self.selected_item_offset();
720 let item_end = item_start + self.selected_item_height();
721
722 if item_start < self.scroll_offset {
724 self.scroll_offset = item_start;
725 }
726 else if item_end > self.scroll_offset + viewport_height {
728 self.scroll_offset = item_end.saturating_sub(viewport_height);
729 }
730 }
731
732 pub fn ensure_cursor_visible(&mut self) {
737 if !self.editing_text || self.focus_on_buttons {
738 return;
739 }
740
741 let cursor_row = if let Some(item) = self.items.get(self.selected_item) {
743 if let SettingControl::Json(state) = &item.control {
744 state.cursor_pos().0
745 } else {
746 return; }
748 } else {
749 return;
750 };
751
752 let item_offset = self.selected_item_offset();
755 let cursor_content_row = item_offset + 1 + cursor_row;
756
757 let viewport_height = self.viewport_height;
758
759 if cursor_content_row < self.scroll_offset {
761 self.scroll_offset = cursor_content_row;
762 }
763 else if cursor_content_row >= self.scroll_offset + viewport_height {
765 self.scroll_offset = cursor_content_row.saturating_sub(viewport_height) + 1;
766 }
767 }
768
769 pub fn scroll_up(&mut self) {
771 self.scroll_offset = self.scroll_offset.saturating_sub(1);
772 }
773
774 pub fn scroll_down(&mut self, viewport_height: usize) {
776 let max_scroll = self.total_content_height().saturating_sub(viewport_height);
777 if self.scroll_offset < max_scroll {
778 self.scroll_offset += 1;
779 }
780 }
781
782 pub fn scroll_to_ratio(&mut self, ratio: f32) {
786 let max_scroll = self
787 .total_content_height()
788 .saturating_sub(self.viewport_height);
789 let new_offset = (ratio * max_scroll as f32).round() as usize;
790 self.scroll_offset = new_offset.min(max_scroll);
791 }
792
793 pub fn start_editing(&mut self) {
795 if let Some(item) = self.current_item_mut() {
796 if item.read_only {
798 return;
799 }
800 match &mut item.control {
801 SettingControl::Text(state) => {
802 state.cursor = state.value.len();
804 self.editing_text = true;
805 }
806 SettingControl::TextList(state) => {
807 state.focus_new_item();
809 self.editing_text = true;
810 }
811 SettingControl::Number(state) => {
812 state.start_editing();
813 self.editing_text = true;
814 }
815 SettingControl::Json(_) => {
816 self.editing_text = true;
818 }
819 _ => {}
820 }
821 }
822 }
823
824 pub fn stop_editing(&mut self) {
826 if let Some(item) = self.current_item_mut() {
827 if let SettingControl::Number(state) = &mut item.control {
828 state.cancel_editing();
829 }
830 }
831 self.editing_text = false;
832 }
833
834 pub fn insert_char(&mut self, c: char) {
836 if !self.editing_text {
837 return;
838 }
839 if let Some(item) = self.current_item_mut() {
840 match &mut item.control {
841 SettingControl::Text(state) => {
842 state.insert(c);
843 }
844 SettingControl::TextList(state) => {
845 state.insert(c);
846 }
847 SettingControl::Number(state) => {
848 state.insert_char(c);
849 }
850 SettingControl::Json(state) => {
851 state.insert(c);
852 }
853 _ => {}
854 }
855 }
856 }
857
858 pub fn insert_str(&mut self, s: &str) {
859 if !self.editing_text {
860 return;
861 }
862 if let Some(item) = self.current_item_mut() {
863 match &mut item.control {
864 SettingControl::Text(state) => {
865 state.insert_str(s);
866 }
867 SettingControl::TextList(state) => {
868 state.insert_str(s);
869 }
870 SettingControl::Number(state) => {
871 for c in s.chars() {
872 state.insert_char(c);
873 }
874 }
875 SettingControl::Json(state) => {
876 state.insert_str(s);
877 }
878 _ => {}
879 }
880 }
881 }
882
883 pub fn backspace(&mut self) {
885 if !self.editing_text {
886 return;
887 }
888 if let Some(item) = self.current_item_mut() {
889 match &mut item.control {
890 SettingControl::Text(state) => {
891 state.backspace();
892 }
893 SettingControl::TextList(state) => {
894 state.backspace();
895 }
896 SettingControl::Number(state) => {
897 state.backspace();
898 }
899 SettingControl::Json(state) => {
900 state.backspace();
901 }
902 _ => {}
903 }
904 }
905 }
906
907 pub fn cursor_left(&mut self) {
909 if !self.editing_text {
910 return;
911 }
912 if let Some(item) = self.current_item_mut() {
913 match &mut item.control {
914 SettingControl::Text(state) => {
915 state.move_left();
916 }
917 SettingControl::TextList(state) => {
918 state.move_left();
919 }
920 SettingControl::Json(state) => {
921 state.move_left();
922 }
923 _ => {}
924 }
925 }
926 }
927
928 pub fn cursor_left_selecting(&mut self) {
930 if !self.editing_text {
931 return;
932 }
933 if let Some(item) = self.current_item_mut() {
934 if let SettingControl::Json(state) = &mut item.control {
935 state.editor.move_left_selecting();
936 }
937 }
938 }
939
940 pub fn cursor_right(&mut self) {
942 if !self.editing_text {
943 return;
944 }
945 if let Some(item) = self.current_item_mut() {
946 match &mut item.control {
947 SettingControl::Text(state) => {
948 state.move_right();
949 }
950 SettingControl::TextList(state) => {
951 state.move_right();
952 }
953 SettingControl::Json(state) => {
954 state.move_right();
955 }
956 _ => {}
957 }
958 }
959 }
960
961 pub fn cursor_right_selecting(&mut self) {
963 if !self.editing_text {
964 return;
965 }
966 if let Some(item) = self.current_item_mut() {
967 if let SettingControl::Json(state) = &mut item.control {
968 state.editor.move_right_selecting();
969 }
970 }
971 }
972
973 pub fn cursor_up(&mut self) {
975 if !self.editing_text {
976 return;
977 }
978 if let Some(item) = self.current_item_mut() {
979 if let SettingControl::Json(state) = &mut item.control {
980 state.move_up();
981 }
982 }
983 self.ensure_cursor_visible();
984 }
985
986 pub fn cursor_up_selecting(&mut self) {
988 if !self.editing_text {
989 return;
990 }
991 if let Some(item) = self.current_item_mut() {
992 if let SettingControl::Json(state) = &mut item.control {
993 state.editor.move_up_selecting();
994 }
995 }
996 self.ensure_cursor_visible();
997 }
998
999 pub fn cursor_down(&mut self) {
1001 if !self.editing_text {
1002 return;
1003 }
1004 if let Some(item) = self.current_item_mut() {
1005 if let SettingControl::Json(state) = &mut item.control {
1006 state.move_down();
1007 }
1008 }
1009 self.ensure_cursor_visible();
1010 }
1011
1012 pub fn cursor_down_selecting(&mut self) {
1014 if !self.editing_text {
1015 return;
1016 }
1017 if let Some(item) = self.current_item_mut() {
1018 if let SettingControl::Json(state) = &mut item.control {
1019 state.editor.move_down_selecting();
1020 }
1021 }
1022 self.ensure_cursor_visible();
1023 }
1024
1025 pub fn insert_newline(&mut self) {
1027 if !self.editing_text {
1028 return;
1029 }
1030 if let Some(item) = self.current_item_mut() {
1031 if let SettingControl::Json(state) = &mut item.control {
1032 state.insert('\n');
1033 }
1034 }
1035 }
1036
1037 pub fn revert_json_and_stop(&mut self) {
1039 if let Some(item) = self.current_item_mut() {
1040 if let SettingControl::Json(state) = &mut item.control {
1041 state.revert();
1042 }
1043 }
1044 self.editing_text = false;
1045 }
1046
1047 pub fn is_editing_json(&self) -> bool {
1049 if !self.editing_text {
1050 return false;
1051 }
1052 self.current_item()
1053 .map(|item| matches!(&item.control, SettingControl::Json(_)))
1054 .unwrap_or(false)
1055 }
1056
1057 pub fn toggle_bool(&mut self) {
1059 if let Some(item) = self.current_item_mut() {
1060 if item.read_only {
1062 return;
1063 }
1064 if let SettingControl::Toggle(state) = &mut item.control {
1065 state.checked = !state.checked;
1066 }
1067 }
1068 }
1069
1070 pub fn toggle_dropdown(&mut self) {
1072 if let Some(item) = self.current_item_mut() {
1073 if item.read_only {
1075 return;
1076 }
1077 if let SettingControl::Dropdown(state) = &mut item.control {
1078 state.open = !state.open;
1079 }
1080 }
1081 }
1082
1083 pub fn dropdown_prev(&mut self) {
1085 if let Some(item) = self.current_item_mut() {
1086 if let SettingControl::Dropdown(state) = &mut item.control {
1087 if state.open {
1088 state.select_prev();
1089 }
1090 }
1091 }
1092 }
1093
1094 pub fn dropdown_next(&mut self) {
1096 if let Some(item) = self.current_item_mut() {
1097 if let SettingControl::Dropdown(state) = &mut item.control {
1098 if state.open {
1099 state.select_next();
1100 }
1101 }
1102 }
1103 }
1104
1105 pub fn dropdown_confirm(&mut self) {
1107 if let Some(item) = self.current_item_mut() {
1108 if let SettingControl::Dropdown(state) = &mut item.control {
1109 state.open = false;
1110 }
1111 }
1112 }
1113
1114 pub fn increment_number(&mut self) {
1116 if let Some(item) = self.current_item_mut() {
1117 if item.read_only {
1119 return;
1120 }
1121 if let SettingControl::Number(state) = &mut item.control {
1122 state.increment();
1123 }
1124 }
1125 }
1126
1127 pub fn decrement_number(&mut self) {
1129 if let Some(item) = self.current_item_mut() {
1130 if item.read_only {
1132 return;
1133 }
1134 if let SettingControl::Number(state) = &mut item.control {
1135 state.decrement();
1136 }
1137 }
1138 }
1139
1140 pub fn delete_list_item(&mut self) {
1142 if let Some(item) = self.current_item_mut() {
1143 if let SettingControl::TextList(state) = &mut item.control {
1144 if let Some(idx) = state.focused_item {
1146 state.remove_item(idx);
1147 }
1148 }
1149 }
1150 }
1151
1152 pub fn delete(&mut self) {
1154 if !self.editing_text {
1155 return;
1156 }
1157 if let Some(item) = self.current_item_mut() {
1158 match &mut item.control {
1159 SettingControl::Text(state) => {
1160 state.delete();
1161 }
1162 SettingControl::TextList(state) => {
1163 state.delete();
1164 }
1165 SettingControl::Json(state) => {
1166 state.delete();
1167 }
1168 _ => {}
1169 }
1170 }
1171 }
1172
1173 pub fn cursor_home(&mut self) {
1175 if !self.editing_text {
1176 return;
1177 }
1178 if let Some(item) = self.current_item_mut() {
1179 match &mut item.control {
1180 SettingControl::Text(state) => {
1181 state.move_home();
1182 }
1183 SettingControl::TextList(state) => {
1184 state.move_home();
1185 }
1186 SettingControl::Json(state) => {
1187 state.move_home();
1188 }
1189 _ => {}
1190 }
1191 }
1192 }
1193
1194 pub fn cursor_end(&mut self) {
1196 if !self.editing_text {
1197 return;
1198 }
1199 if let Some(item) = self.current_item_mut() {
1200 match &mut item.control {
1201 SettingControl::Text(state) => {
1202 state.move_end();
1203 }
1204 SettingControl::TextList(state) => {
1205 state.move_end();
1206 }
1207 SettingControl::Json(state) => {
1208 state.move_end();
1209 }
1210 _ => {}
1211 }
1212 }
1213 }
1214
1215 pub fn select_all(&mut self) {
1217 if !self.editing_text {
1218 return;
1219 }
1220 if let Some(item) = self.current_item_mut() {
1221 if let SettingControl::Json(state) = &mut item.control {
1222 state.select_all();
1223 }
1224 }
1226 }
1227
1228 pub fn selected_text(&self) -> Option<String> {
1230 if !self.editing_text {
1231 return None;
1232 }
1233 if let Some(item) = self.current_item() {
1234 if let SettingControl::Json(state) = &item.control {
1235 return state.selected_text();
1236 }
1237 }
1238 None
1239 }
1240
1241 pub fn is_editing(&self) -> bool {
1243 self.editing_text
1244 || self
1245 .current_item()
1246 .map(|item| {
1247 matches!(
1248 &item.control,
1249 SettingControl::Dropdown(s) if s.open
1250 )
1251 })
1252 .unwrap_or(false)
1253 }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258 use super::*;
1259
1260 fn create_test_schema() -> SettingSchema {
1261 SettingSchema {
1262 path: "/test".to_string(),
1263 name: "Test".to_string(),
1264 description: Some("Test schema".to_string()),
1265 setting_type: SettingType::Object {
1266 properties: vec![
1267 SettingSchema {
1268 path: "/enabled".to_string(),
1269 name: "Enabled".to_string(),
1270 description: Some("Enable this".to_string()),
1271 setting_type: SettingType::Boolean,
1272 default: Some(serde_json::json!(true)),
1273 read_only: false,
1274 section: None,
1275 order: None,
1276 },
1277 SettingSchema {
1278 path: "/command".to_string(),
1279 name: "Command".to_string(),
1280 description: Some("Command to run".to_string()),
1281 setting_type: SettingType::String,
1282 default: Some(serde_json::json!("")),
1283 read_only: false,
1284 section: None,
1285 order: None,
1286 },
1287 ],
1288 },
1289 default: None,
1290 read_only: false,
1291 section: None,
1292 order: None,
1293 }
1294 }
1295
1296 #[test]
1297 fn from_schema_creates_key_item_first() {
1298 let schema = create_test_schema();
1299 let dialog = EntryDialogState::from_schema(
1300 "test".to_string(),
1301 &serde_json::json!({}),
1302 &schema,
1303 "/test",
1304 false,
1305 false,
1306 );
1307
1308 assert!(!dialog.items.is_empty());
1309 assert_eq!(dialog.items[0].path, "__key__");
1310 assert_eq!(dialog.items[0].name, "Key");
1311 }
1312
1313 #[test]
1314 fn from_schema_creates_items_from_properties() {
1315 let schema = create_test_schema();
1316 let dialog = EntryDialogState::from_schema(
1317 "test".to_string(),
1318 &serde_json::json!({"enabled": true, "command": "test-cmd"}),
1319 &schema,
1320 "/test",
1321 false,
1322 false,
1323 );
1324
1325 assert_eq!(dialog.items.len(), 3);
1327 assert_eq!(dialog.items[1].name, "Enabled");
1328 assert_eq!(dialog.items[2].name, "Command");
1329 }
1330
1331 #[test]
1332 fn get_key_returns_key_value() {
1333 let schema = create_test_schema();
1334 let dialog = EntryDialogState::from_schema(
1335 "mykey".to_string(),
1336 &serde_json::json!({}),
1337 &schema,
1338 "/test",
1339 false,
1340 false,
1341 );
1342
1343 assert_eq!(dialog.get_key(), "mykey");
1344 }
1345
1346 #[test]
1347 fn to_value_excludes_key() {
1348 let schema = create_test_schema();
1349 let dialog = EntryDialogState::from_schema(
1350 "test".to_string(),
1351 &serde_json::json!({"enabled": true, "command": "cmd"}),
1352 &schema,
1353 "/test",
1354 false,
1355 false,
1356 );
1357
1358 let value = dialog.to_value();
1359 assert!(value.get("__key__").is_none());
1360 assert!(value.get("enabled").is_some());
1361 }
1362
1363 #[test]
1364 fn focus_navigation_works() {
1365 let schema = create_test_schema();
1366 let mut dialog = EntryDialogState::from_schema(
1367 "test".to_string(),
1368 &serde_json::json!({}),
1369 &schema,
1370 "/test",
1371 false, false, );
1374
1375 assert_eq!(dialog.first_editable_index, 1);
1379 assert_eq!(dialog.selected_item, 1); assert!(!dialog.focus_on_buttons);
1381
1382 dialog.focus_next();
1383 assert_eq!(dialog.selected_item, 2); dialog.focus_next();
1386 assert!(dialog.focus_on_buttons); assert_eq!(dialog.focused_button, 0);
1388
1389 dialog.focus_prev();
1391 assert!(!dialog.focus_on_buttons);
1392 assert_eq!(dialog.selected_item, 2); dialog.focus_prev();
1395 assert_eq!(dialog.selected_item, 1); dialog.focus_prev();
1398 assert!(dialog.focus_on_buttons); }
1400
1401 #[test]
1402 fn button_count_differs_for_new_vs_existing() {
1403 let schema = create_test_schema();
1404
1405 let new_dialog = EntryDialogState::from_schema(
1406 "test".to_string(),
1407 &serde_json::json!({}),
1408 &schema,
1409 "/test",
1410 true,
1411 false,
1412 );
1413 assert_eq!(new_dialog.button_count(), 2); let existing_dialog = EntryDialogState::from_schema(
1416 "test".to_string(),
1417 &serde_json::json!({}),
1418 &schema,
1419 "/test",
1420 false,
1421 false, );
1423 assert_eq!(existing_dialog.button_count(), 3); let no_delete_dialog = EntryDialogState::from_schema(
1427 "test".to_string(),
1428 &serde_json::json!({}),
1429 &schema,
1430 "/test",
1431 false,
1432 true, );
1434 assert_eq!(no_delete_dialog.button_count(), 2); }
1436}