khora_editor/widgets/inspector/
walker.rs1use khora_sdk::editor_ui::{UiBuilder, UiTheme};
27use serde_json::Value;
28
29use crate::widgets::enum_variants::editable_variants;
30
31use super::renderers::{
32 humanise, is_color, is_quat, is_vec3, render_color, render_number_row, render_numeric_quad,
33 render_numeric_triple, render_quat, render_vec3,
34};
35
36pub fn render_value(ui: &mut dyn UiBuilder, value: &mut Value, theme: &UiTheme) -> bool {
39 match value {
40 Value::Object(map) => render_object(ui, map, theme),
41 Value::Array(arr) => render_array(ui, "", arr, theme),
42 _ => render_field(ui, "(value)", value, theme),
46 }
47}
48
49fn render_object(
51 ui: &mut dyn UiBuilder,
52 map: &mut serde_json::Map<String, Value>,
53 theme: &UiTheme,
54) -> bool {
55 if map.len() == 1 {
57 let key = map.keys().next().cloned().unwrap();
58
59 if let Some(variants) = editable_variants(&key) {
63 let names: Vec<&str> = variants.iter().map(|(n, _)| *n).collect();
64 let mut current = variants
65 .iter()
66 .position(|(n, _)| *n == key.as_str())
67 .unwrap_or(0);
68 let combo_salt = format!("variant::{}", names.join("|"));
72 let combo_changed = ui.combo_box(&combo_salt, "Variant", &mut current, &names);
73 if combo_changed {
74 if let Some((_, Value::Object(new_map))) = variants.get(current) {
75 map.clear();
76 for (k, v) in new_map.iter() {
77 map.insert(k.clone(), v.clone());
78 }
79 return true;
80 }
81 }
82 let inner_key = map.keys().next().cloned().unwrap();
85 let inner = map.get_mut(&inner_key).unwrap();
86 return match inner {
87 Value::Object(m) => render_object(ui, m, theme),
88 Value::Null => false,
89 _ => render_field(ui, "value", inner, theme),
90 };
91 }
92
93 let inner = map.get_mut(&key).unwrap();
95 ui.colored_label(theme.text_dim, &format!("Variant: {}", key));
96 return match inner {
97 Value::Object(m) => render_object(ui, m, theme),
98 Value::Null => false,
99 _ => render_field(ui, "value", inner, theme),
100 };
101 }
102
103 if is_color(map) {
105 return render_color(ui, map, theme);
106 }
107 if is_vec3(map) {
108 return render_vec3(ui, map, theme);
109 }
110 if is_quat(map) {
111 return render_quat(ui, map);
112 }
113
114 let mut changed = false;
116 let keys: Vec<String> = map.keys().cloned().collect();
117 for key in keys {
118 let label = humanise(&key);
119 let val = map.get_mut(&key).unwrap();
120 if render_field(ui, &label, val, theme) {
121 changed = true;
122 }
123 }
124 changed
125}
126
127fn render_field(ui: &mut dyn UiBuilder, label: &str, value: &mut Value, theme: &UiTheme) -> bool {
130 match value {
131 Value::Bool(b) => {
132 let mut local = *b;
133 let changed = ui.checkbox(&mut local, label);
134 if changed {
135 *value = Value::Bool(local);
136 }
137 changed
138 }
139 Value::Number(_) => render_number_row(ui, label, value),
140 Value::String(s) => {
141 let mut local = s.clone();
142 let mut changed = false;
143 ui.horizontal(&mut |row| {
144 row.label(label);
145 if row.text_edit_singleline(&mut local) {
146 changed = true;
147 }
148 });
149 if changed {
150 *value = Value::String(local);
151 }
152 changed
153 }
154 Value::Null => {
155 ui.colored_label(theme.text_muted, &format!("{}: null", label));
156 false
157 }
158 Value::Array(arr) => render_array(ui, label, arr, theme),
159 Value::Object(_) => {
160 let mut changed = false;
163 ui.collapsing(label, true, &mut |inner| {
164 if let Value::Object(m) = value {
165 if render_object(inner, m, theme) {
166 changed = true;
167 }
168 }
169 });
170 changed
171 }
172 }
173}
174
175fn render_array(ui: &mut dyn UiBuilder, label: &str, arr: &mut [Value], theme: &UiTheme) -> bool {
179 if arr.iter().all(|v| v.is_number()) {
180 match arr.len() {
181 3 => return render_numeric_triple(ui, label, arr),
182 4 => return render_numeric_quad(ui, label, arr, theme),
183 _ => {}
184 }
185 }
186
187 let mut changed = false;
188 ui.collapsing(label, true, &mut |inner| {
189 for (i, v) in arr.iter_mut().enumerate() {
190 if render_field(inner, &format!("[{}]", i), v, theme) {
191 changed = true;
192 }
193 }
194 });
195 changed
196}