1 | /**************************************************************************/ |
2 | /* editor_inspector.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "editor_inspector.h" |
32 | |
33 | #include "core/os/keyboard.h" |
34 | #include "editor/doc_tools.h" |
35 | #include "editor/editor_feature_profile.h" |
36 | #include "editor/editor_node.h" |
37 | #include "editor/editor_property_name_processor.h" |
38 | #include "editor/editor_scale.h" |
39 | #include "editor/editor_settings.h" |
40 | #include "editor/editor_string_names.h" |
41 | #include "editor/editor_undo_redo_manager.h" |
42 | #include "editor/gui/editor_validation_panel.h" |
43 | #include "editor/inspector_dock.h" |
44 | #include "editor/plugins/script_editor_plugin.h" |
45 | #include "multi_node_edit.h" |
46 | #include "scene/gui/spin_box.h" |
47 | #include "scene/gui/texture_rect.h" |
48 | #include "scene/property_utils.h" |
49 | #include "scene/resources/packed_scene.h" |
50 | #include "scene/resources/style_box_flat.h" |
51 | |
52 | bool EditorInspector::_property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style) { |
53 | if (p_property_path.findn(p_filter) != -1) { |
54 | return true; |
55 | } |
56 | |
57 | const Vector<String> prop_sections = p_property_path.split("/" ); |
58 | for (int i = 0; i < prop_sections.size(); i++) { |
59 | if (p_filter.is_subsequence_ofn(EditorPropertyNameProcessor::get_singleton()->process_name(prop_sections[i], p_style))) { |
60 | return true; |
61 | } |
62 | } |
63 | return false; |
64 | } |
65 | |
66 | Size2 EditorProperty::get_minimum_size() const { |
67 | Size2 ms; |
68 | Ref<Font> font = get_theme_font(SNAME("font" ), SNAME("Tree" )); |
69 | int font_size = get_theme_font_size(SNAME("font_size" ), SNAME("Tree" )); |
70 | ms.height = font->get_height(font_size) + 4 * EDSCALE; |
71 | |
72 | for (int i = 0; i < get_child_count(); i++) { |
73 | Control *c = Object::cast_to<Control>(get_child(i)); |
74 | if (!c) { |
75 | continue; |
76 | } |
77 | if (c->is_set_as_top_level()) { |
78 | continue; |
79 | } |
80 | if (!c->is_visible()) { |
81 | continue; |
82 | } |
83 | if (c == bottom_editor) { |
84 | continue; |
85 | } |
86 | |
87 | Size2 minsize = c->get_combined_minimum_size(); |
88 | ms.width = MAX(ms.width, minsize.width); |
89 | ms.height = MAX(ms.height, minsize.height); |
90 | } |
91 | |
92 | if (keying) { |
93 | Ref<Texture2D> key = get_editor_theme_icon(SNAME("Key" )); |
94 | ms.width += key->get_width() + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )); |
95 | } |
96 | |
97 | if (deletable) { |
98 | Ref<Texture2D> key = get_editor_theme_icon(SNAME("Close" )); |
99 | ms.width += key->get_width() + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )); |
100 | } |
101 | |
102 | if (checkable) { |
103 | Ref<Texture2D> check = get_theme_icon(SNAME("checked" ), SNAME("CheckBox" )); |
104 | ms.width += check->get_width() + get_theme_constant(SNAME("h_separation" ), SNAME("CheckBox" )) + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )); |
105 | } |
106 | |
107 | if (bottom_editor != nullptr && bottom_editor->is_visible()) { |
108 | ms.height += get_theme_constant(SNAME("v_separation" )); |
109 | Size2 bems = bottom_editor->get_combined_minimum_size(); |
110 | //bems.width += get_constant("item_margin", "Tree"); |
111 | ms.height += bems.height; |
112 | ms.width = MAX(ms.width, bems.width); |
113 | } |
114 | |
115 | return ms; |
116 | } |
117 | |
118 | void EditorProperty::emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field, bool p_changing) { |
119 | Variant args[4] = { p_property, p_value, p_field, p_changing }; |
120 | const Variant *argptrs[4] = { &args[0], &args[1], &args[2], &args[3] }; |
121 | |
122 | cache[p_property] = p_value; |
123 | emit_signalp(SNAME("property_changed" ), (const Variant **)argptrs, 4); |
124 | } |
125 | |
126 | void EditorProperty::_notification(int p_what) { |
127 | switch (p_what) { |
128 | case NOTIFICATION_SORT_CHILDREN: { |
129 | Size2 size = get_size(); |
130 | Rect2 rect; |
131 | Rect2 bottom_rect; |
132 | |
133 | right_child_rect = Rect2(); |
134 | bottom_child_rect = Rect2(); |
135 | |
136 | { |
137 | int child_room = size.width * (1.0 - split_ratio); |
138 | Ref<Font> font = get_theme_font(SNAME("font" ), SNAME("Tree" )); |
139 | int font_size = get_theme_font_size(SNAME("font_size" ), SNAME("Tree" )); |
140 | int height = font->get_height(font_size) + 4 * EDSCALE; |
141 | bool no_children = true; |
142 | |
143 | //compute room needed |
144 | for (int i = 0; i < get_child_count(); i++) { |
145 | Control *c = Object::cast_to<Control>(get_child(i)); |
146 | if (!c) { |
147 | continue; |
148 | } |
149 | if (c->is_set_as_top_level()) { |
150 | continue; |
151 | } |
152 | if (c == bottom_editor) { |
153 | continue; |
154 | } |
155 | |
156 | Size2 minsize = c->get_combined_minimum_size(); |
157 | child_room = MAX(child_room, minsize.width); |
158 | height = MAX(height, minsize.height); |
159 | no_children = false; |
160 | } |
161 | |
162 | if (no_children) { |
163 | text_size = size.width; |
164 | rect = Rect2(size.width - 1, 0, 1, height); |
165 | } else { |
166 | text_size = MAX(0, size.width - (child_room + 4 * EDSCALE)); |
167 | if (is_layout_rtl()) { |
168 | rect = Rect2(1, 0, child_room, height); |
169 | } else { |
170 | rect = Rect2(size.width - child_room, 0, child_room, height); |
171 | } |
172 | } |
173 | |
174 | if (bottom_editor) { |
175 | int m = 0; //get_constant("item_margin", "Tree"); |
176 | |
177 | bottom_rect = Rect2(m, rect.size.height + get_theme_constant(SNAME("v_separation" )), size.width - m, bottom_editor->get_combined_minimum_size().height); |
178 | } |
179 | |
180 | if (keying) { |
181 | Ref<Texture2D> key; |
182 | |
183 | if (use_keying_next()) { |
184 | key = get_editor_theme_icon(SNAME("KeyNext" )); |
185 | } else { |
186 | key = get_editor_theme_icon(SNAME("Key" )); |
187 | } |
188 | |
189 | rect.size.x -= key->get_width() + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )); |
190 | if (is_layout_rtl()) { |
191 | rect.position.x += key->get_width() + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )); |
192 | } |
193 | |
194 | if (no_children) { |
195 | text_size -= key->get_width() + 4 * EDSCALE; |
196 | } |
197 | } |
198 | |
199 | if (deletable) { |
200 | Ref<Texture2D> close; |
201 | |
202 | close = get_editor_theme_icon(SNAME("Close" )); |
203 | |
204 | rect.size.x -= close->get_width() + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )); |
205 | |
206 | if (is_layout_rtl()) { |
207 | rect.position.x += close->get_width() + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )); |
208 | } |
209 | |
210 | if (no_children) { |
211 | text_size -= close->get_width() + 4 * EDSCALE; |
212 | } |
213 | } |
214 | } |
215 | |
216 | //set children |
217 | for (int i = 0; i < get_child_count(); i++) { |
218 | Control *c = Object::cast_to<Control>(get_child(i)); |
219 | if (!c) { |
220 | continue; |
221 | } |
222 | if (c->is_set_as_top_level()) { |
223 | continue; |
224 | } |
225 | if (c == bottom_editor) { |
226 | continue; |
227 | } |
228 | |
229 | fit_child_in_rect(c, rect); |
230 | right_child_rect = rect; |
231 | } |
232 | |
233 | if (bottom_editor) { |
234 | fit_child_in_rect(bottom_editor, bottom_rect); |
235 | bottom_child_rect = bottom_rect; |
236 | } |
237 | |
238 | queue_redraw(); //need to redraw text |
239 | } break; |
240 | |
241 | case NOTIFICATION_DRAW: { |
242 | Ref<Font> font = get_theme_font(SNAME("font" ), SNAME("Tree" )); |
243 | int font_size = get_theme_font_size(SNAME("font_size" ), SNAME("Tree" )); |
244 | bool rtl = is_layout_rtl(); |
245 | |
246 | Size2 size = get_size(); |
247 | if (bottom_editor) { |
248 | size.height = bottom_editor->get_offset(SIDE_TOP) - get_theme_constant(SNAME("v_separation" )); |
249 | } else if (label_reference) { |
250 | size.height = label_reference->get_size().height; |
251 | } |
252 | |
253 | Ref<StyleBox> sb = get_theme_stylebox(selected ? SNAME("bg_selected" ) : SNAME("bg" )); |
254 | draw_style_box(sb, Rect2(Vector2(), size)); |
255 | |
256 | Ref<StyleBox> bg_stylebox = get_theme_stylebox(SNAME("child_bg" )); |
257 | if (draw_top_bg && right_child_rect != Rect2()) { |
258 | draw_style_box(bg_stylebox, right_child_rect); |
259 | } |
260 | if (bottom_child_rect != Rect2()) { |
261 | draw_style_box(bg_stylebox, bottom_child_rect); |
262 | } |
263 | |
264 | Color color; |
265 | if (draw_warning || draw_prop_warning) { |
266 | color = get_theme_color(is_read_only() ? SNAME("readonly_warning_color" ) : SNAME("warning_color" )); |
267 | } else { |
268 | color = get_theme_color(is_read_only() ? SNAME("readonly_color" ) : SNAME("property_color" )); |
269 | } |
270 | if (label.contains("." )) { |
271 | // FIXME: Move this to the project settings editor, as this is only used |
272 | // for project settings feature tag overrides. |
273 | color.a = 0.5; |
274 | } |
275 | |
276 | int ofs = get_theme_constant(SNAME("font_offset" )); |
277 | int text_limit = text_size - ofs; |
278 | |
279 | if (checkable) { |
280 | Ref<Texture2D> checkbox; |
281 | if (checked) { |
282 | checkbox = get_editor_theme_icon(SNAME("GuiChecked" )); |
283 | } else { |
284 | checkbox = get_editor_theme_icon(SNAME("GuiUnchecked" )); |
285 | } |
286 | |
287 | Color color2(1, 1, 1); |
288 | if (check_hover) { |
289 | color2.r *= 1.2; |
290 | color2.g *= 1.2; |
291 | color2.b *= 1.2; |
292 | } |
293 | check_rect = Rect2(ofs, ((size.height - checkbox->get_height()) / 2), checkbox->get_width(), checkbox->get_height()); |
294 | if (rtl) { |
295 | draw_texture(checkbox, Vector2(size.width - check_rect.position.x - checkbox->get_width(), check_rect.position.y), color2); |
296 | } else { |
297 | draw_texture(checkbox, check_rect.position, color2); |
298 | } |
299 | int check_ofs = get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )) + checkbox->get_width() + get_theme_constant(SNAME("h_separation" ), SNAME("CheckBox" )); |
300 | ofs += check_ofs; |
301 | text_limit -= check_ofs; |
302 | } else { |
303 | check_rect = Rect2(); |
304 | } |
305 | |
306 | if (can_revert && !is_read_only()) { |
307 | Ref<Texture2D> reload_icon = get_editor_theme_icon(SNAME("ReloadSmall" )); |
308 | text_limit -= reload_icon->get_width() + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )) * 2; |
309 | revert_rect = Rect2(ofs + text_limit, (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height()); |
310 | |
311 | Color color2(1, 1, 1); |
312 | if (revert_hover) { |
313 | color2.r *= 1.2; |
314 | color2.g *= 1.2; |
315 | color2.b *= 1.2; |
316 | } |
317 | if (rtl) { |
318 | draw_texture(reload_icon, Vector2(size.width - revert_rect.position.x - reload_icon->get_width(), revert_rect.position.y), color2); |
319 | } else { |
320 | draw_texture(reload_icon, revert_rect.position, color2); |
321 | } |
322 | } else { |
323 | revert_rect = Rect2(); |
324 | } |
325 | |
326 | if (!pin_hidden && pinned) { |
327 | Ref<Texture2D> pinned_icon = get_editor_theme_icon(SNAME("Pin" )); |
328 | int margin_w = get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )) * 2; |
329 | int total_icon_w = margin_w + pinned_icon->get_width(); |
330 | int text_w = font->get_string_size(label, rtl ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT, text_limit - total_icon_w, font_size).x; |
331 | int y = (size.height - pinned_icon->get_height()) / 2; |
332 | if (rtl) { |
333 | draw_texture(pinned_icon, Vector2(size.width - ofs - text_w - total_icon_w, y), color); |
334 | } else { |
335 | draw_texture(pinned_icon, Vector2(ofs + text_w + margin_w, y), color); |
336 | } |
337 | text_limit -= total_icon_w; |
338 | } |
339 | |
340 | int v_ofs = (size.height - font->get_height(font_size)) / 2; |
341 | if (rtl) { |
342 | draw_string(font, Point2(size.width - ofs - text_limit, v_ofs + font->get_ascent(font_size)), label, HORIZONTAL_ALIGNMENT_RIGHT, text_limit, font_size, color); |
343 | } else { |
344 | draw_string(font, Point2(ofs, v_ofs + font->get_ascent(font_size)), label, HORIZONTAL_ALIGNMENT_LEFT, text_limit, font_size, color); |
345 | } |
346 | |
347 | ofs = size.width; |
348 | |
349 | if (keying) { |
350 | Ref<Texture2D> key; |
351 | |
352 | if (use_keying_next()) { |
353 | key = get_editor_theme_icon(SNAME("KeyNext" )); |
354 | } else { |
355 | key = get_editor_theme_icon(SNAME("Key" )); |
356 | } |
357 | |
358 | ofs -= key->get_width() + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )); |
359 | |
360 | Color color2(1, 1, 1); |
361 | if (keying_hover) { |
362 | color2.r *= 1.2; |
363 | color2.g *= 1.2; |
364 | color2.b *= 1.2; |
365 | } |
366 | keying_rect = Rect2(ofs, ((size.height - key->get_height()) / 2), key->get_width(), key->get_height()); |
367 | if (rtl) { |
368 | draw_texture(key, Vector2(size.width - keying_rect.position.x - key->get_width(), keying_rect.position.y), color2); |
369 | } else { |
370 | draw_texture(key, keying_rect.position, color2); |
371 | } |
372 | |
373 | } else { |
374 | keying_rect = Rect2(); |
375 | } |
376 | |
377 | if (deletable) { |
378 | Ref<Texture2D> close; |
379 | |
380 | close = get_editor_theme_icon(SNAME("Close" )); |
381 | |
382 | ofs -= close->get_width() + get_theme_constant(SNAME("hseparator" ), SNAME("Tree" )); |
383 | |
384 | Color color2(1, 1, 1); |
385 | if (delete_hover) { |
386 | color2.r *= 1.2; |
387 | color2.g *= 1.2; |
388 | color2.b *= 1.2; |
389 | } |
390 | delete_rect = Rect2(ofs, ((size.height - close->get_height()) / 2), close->get_width(), close->get_height()); |
391 | if (rtl) { |
392 | draw_texture(close, Vector2(size.width - delete_rect.position.x - close->get_width(), delete_rect.position.y), color2); |
393 | } else { |
394 | draw_texture(close, delete_rect.position, color2); |
395 | } |
396 | } else { |
397 | delete_rect = Rect2(); |
398 | } |
399 | } break; |
400 | } |
401 | } |
402 | |
403 | void EditorProperty::set_label(const String &p_label) { |
404 | label = p_label; |
405 | queue_redraw(); |
406 | } |
407 | |
408 | String EditorProperty::get_label() const { |
409 | return label; |
410 | } |
411 | |
412 | Object *EditorProperty::get_edited_object() { |
413 | return object; |
414 | } |
415 | |
416 | StringName EditorProperty::get_edited_property() const { |
417 | return property; |
418 | } |
419 | |
420 | EditorInspector *EditorProperty::get_parent_inspector() const { |
421 | Node *parent = get_parent(); |
422 | while (parent) { |
423 | EditorInspector *ei = Object::cast_to<EditorInspector>(parent); |
424 | if (ei) { |
425 | return ei; |
426 | } |
427 | parent = parent->get_parent(); |
428 | } |
429 | ERR_FAIL_V_MSG(nullptr, "EditorProperty is outside inspector." ); |
430 | } |
431 | |
432 | void EditorProperty::set_doc_path(const String &p_doc_path) { |
433 | doc_path = p_doc_path; |
434 | } |
435 | |
436 | void EditorProperty::update_property() { |
437 | GDVIRTUAL_CALL(_update_property); |
438 | } |
439 | |
440 | void EditorProperty::_set_read_only(bool p_read_only) { |
441 | } |
442 | |
443 | void EditorProperty::set_read_only(bool p_read_only) { |
444 | read_only = p_read_only; |
445 | if (GDVIRTUAL_CALL(_set_read_only, p_read_only)) { |
446 | return; |
447 | } |
448 | _set_read_only(p_read_only); |
449 | } |
450 | |
451 | bool EditorProperty::is_read_only() const { |
452 | return read_only; |
453 | } |
454 | |
455 | Variant EditorPropertyRevert::get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid) { |
456 | if (p_object->property_can_revert(p_property)) { |
457 | if (r_is_valid) { |
458 | *r_is_valid = true; |
459 | } |
460 | return p_object->property_get_revert(p_property); |
461 | } |
462 | |
463 | return PropertyUtils::get_property_default_value(p_object, p_property, r_is_valid); |
464 | } |
465 | |
466 | bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value) { |
467 | bool is_valid_revert = false; |
468 | Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_object, p_property, &is_valid_revert); |
469 | if (!is_valid_revert) { |
470 | return false; |
471 | } |
472 | Variant current_value = p_custom_current_value ? *p_custom_current_value : p_object->get(p_property); |
473 | return PropertyUtils::is_property_value_different(current_value, revert_value); |
474 | } |
475 | |
476 | StringName EditorProperty::_get_revert_property() const { |
477 | return property; |
478 | } |
479 | |
480 | void EditorProperty::update_editor_property_status() { |
481 | if (property == StringName()) { |
482 | return; //no property, so nothing to do |
483 | } |
484 | |
485 | bool new_pinned = false; |
486 | if (can_pin) { |
487 | Node *node = Object::cast_to<Node>(object); |
488 | CRASH_COND(!node); |
489 | new_pinned = node->is_property_pinned(property); |
490 | } |
491 | |
492 | bool new_warning = false; |
493 | if (object->has_method("_get_property_warning" )) { |
494 | new_warning = !String(object->call("_get_property_warning" , property)).is_empty(); |
495 | } |
496 | |
497 | Variant current = object->get(_get_revert_property()); |
498 | bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property, ¤t) && !is_read_only(); |
499 | |
500 | bool new_checked = checked; |
501 | if (checkable) { // for properties like theme overrides. |
502 | bool valid = false; |
503 | Variant value = object->get(property, &valid); |
504 | if (valid) { |
505 | new_checked = value.get_type() != Variant::NIL; |
506 | } |
507 | } |
508 | |
509 | if (new_can_revert != can_revert || new_pinned != pinned || new_checked != checked || new_warning != draw_prop_warning) { |
510 | if (new_can_revert != can_revert) { |
511 | emit_signal(SNAME("property_can_revert_changed" ), property, new_can_revert); |
512 | } |
513 | draw_prop_warning = new_warning; |
514 | can_revert = new_can_revert; |
515 | pinned = new_pinned; |
516 | checked = new_checked; |
517 | queue_redraw(); |
518 | } |
519 | } |
520 | |
521 | bool EditorProperty::use_keying_next() const { |
522 | List<PropertyInfo> plist; |
523 | object->get_property_list(&plist, true); |
524 | |
525 | for (List<PropertyInfo>::Element *I = plist.front(); I; I = I->next()) { |
526 | PropertyInfo &p = I->get(); |
527 | |
528 | if (p.name == property) { |
529 | return (p.usage & PROPERTY_USAGE_KEYING_INCREMENTS); |
530 | } |
531 | } |
532 | |
533 | return false; |
534 | } |
535 | |
536 | void EditorProperty::set_checkable(bool p_checkable) { |
537 | checkable = p_checkable; |
538 | queue_redraw(); |
539 | queue_sort(); |
540 | } |
541 | |
542 | bool EditorProperty::is_checkable() const { |
543 | return checkable; |
544 | } |
545 | |
546 | void EditorProperty::set_checked(bool p_checked) { |
547 | checked = p_checked; |
548 | queue_redraw(); |
549 | } |
550 | |
551 | bool EditorProperty::is_checked() const { |
552 | return checked; |
553 | } |
554 | |
555 | void EditorProperty::set_draw_warning(bool p_draw_warning) { |
556 | draw_warning = p_draw_warning; |
557 | queue_redraw(); |
558 | } |
559 | |
560 | void EditorProperty::set_keying(bool p_keying) { |
561 | keying = p_keying; |
562 | queue_redraw(); |
563 | queue_sort(); |
564 | } |
565 | |
566 | void EditorProperty::set_deletable(bool p_deletable) { |
567 | deletable = p_deletable; |
568 | queue_redraw(); |
569 | queue_sort(); |
570 | } |
571 | |
572 | bool EditorProperty::is_deletable() const { |
573 | return deletable; |
574 | } |
575 | |
576 | bool EditorProperty::is_keying() const { |
577 | return keying; |
578 | } |
579 | |
580 | bool EditorProperty::is_draw_warning() const { |
581 | return draw_warning; |
582 | } |
583 | |
584 | void EditorProperty::_focusable_focused(int p_index) { |
585 | if (!selectable) { |
586 | return; |
587 | } |
588 | bool already_selected = selected; |
589 | selected = true; |
590 | selected_focusable = p_index; |
591 | queue_redraw(); |
592 | if (!already_selected && selected) { |
593 | emit_signal(SNAME("selected" ), property, selected_focusable); |
594 | } |
595 | } |
596 | |
597 | void EditorProperty::add_focusable(Control *p_control) { |
598 | p_control->connect("focus_entered" , callable_mp(this, &EditorProperty::_focusable_focused).bind(focusables.size())); |
599 | focusables.push_back(p_control); |
600 | } |
601 | |
602 | void EditorProperty::select(int p_focusable) { |
603 | bool already_selected = selected; |
604 | if (!selectable) { |
605 | return; |
606 | } |
607 | |
608 | if (p_focusable >= 0) { |
609 | ERR_FAIL_INDEX(p_focusable, focusables.size()); |
610 | focusables[p_focusable]->grab_focus(); |
611 | } else { |
612 | selected = true; |
613 | queue_redraw(); |
614 | } |
615 | |
616 | if (!already_selected && selected) { |
617 | emit_signal(SNAME("selected" ), property, selected_focusable); |
618 | } |
619 | } |
620 | |
621 | void EditorProperty::deselect() { |
622 | selected = false; |
623 | selected_focusable = -1; |
624 | queue_redraw(); |
625 | } |
626 | |
627 | bool EditorProperty::is_selected() const { |
628 | return selected; |
629 | } |
630 | |
631 | void EditorProperty::gui_input(const Ref<InputEvent> &p_event) { |
632 | ERR_FAIL_COND(p_event.is_null()); |
633 | |
634 | if (property == StringName()) { |
635 | return; |
636 | } |
637 | |
638 | Ref<InputEventMouse> me = p_event; |
639 | |
640 | if (me.is_valid()) { |
641 | Vector2 mpos = me->get_position(); |
642 | if (is_layout_rtl()) { |
643 | mpos.x = get_size().x - mpos.x; |
644 | } |
645 | bool button_left = me->get_button_mask().has_flag(MouseButtonMask::LEFT); |
646 | |
647 | bool new_keying_hover = keying_rect.has_point(mpos) && !button_left; |
648 | if (new_keying_hover != keying_hover) { |
649 | keying_hover = new_keying_hover; |
650 | queue_redraw(); |
651 | } |
652 | |
653 | bool new_delete_hover = delete_rect.has_point(mpos) && !button_left; |
654 | if (new_delete_hover != delete_hover) { |
655 | delete_hover = new_delete_hover; |
656 | queue_redraw(); |
657 | } |
658 | |
659 | bool new_revert_hover = revert_rect.has_point(mpos) && !button_left; |
660 | if (new_revert_hover != revert_hover) { |
661 | revert_hover = new_revert_hover; |
662 | queue_redraw(); |
663 | } |
664 | |
665 | bool new_check_hover = check_rect.has_point(mpos) && !button_left; |
666 | if (new_check_hover != check_hover) { |
667 | check_hover = new_check_hover; |
668 | queue_redraw(); |
669 | } |
670 | } |
671 | |
672 | Ref<InputEventMouseButton> mb = p_event; |
673 | |
674 | if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { |
675 | Vector2 mpos = mb->get_position(); |
676 | if (is_layout_rtl()) { |
677 | mpos.x = get_size().x - mpos.x; |
678 | } |
679 | |
680 | select(); |
681 | |
682 | if (keying_rect.has_point(mpos)) { |
683 | accept_event(); |
684 | emit_signal(SNAME("property_keyed" ), property, use_keying_next()); |
685 | |
686 | if (use_keying_next()) { |
687 | if (property == "frame_coords" && (object->is_class("Sprite2D" ) || object->is_class("Sprite3D" ))) { |
688 | Vector2i new_coords = object->get(property); |
689 | new_coords.x++; |
690 | if (new_coords.x >= int64_t(object->get("hframes" ))) { |
691 | new_coords.x = 0; |
692 | new_coords.y++; |
693 | } |
694 | if (new_coords.x < int64_t(object->get("hframes" )) && new_coords.y < int64_t(object->get("vframes" ))) { |
695 | call_deferred(SNAME("emit_changed" ), property, new_coords, "" , false); |
696 | } |
697 | } else { |
698 | if (int64_t(object->get(property)) + 1 < (int64_t(object->get("hframes" )) * int64_t(object->get("vframes" )))) { |
699 | call_deferred(SNAME("emit_changed" ), property, object->get(property).operator int64_t() + 1, "" , false); |
700 | } |
701 | } |
702 | |
703 | call_deferred(SNAME("update_property" )); |
704 | } |
705 | } |
706 | if (delete_rect.has_point(mpos)) { |
707 | accept_event(); |
708 | emit_signal(SNAME("property_deleted" ), property); |
709 | } |
710 | |
711 | if (revert_rect.has_point(mpos)) { |
712 | accept_event(); |
713 | get_viewport()->gui_release_focus(); |
714 | bool is_valid_revert = false; |
715 | Variant revert_value = EditorPropertyRevert::get_property_revert_value(object, property, &is_valid_revert); |
716 | ERR_FAIL_COND(!is_valid_revert); |
717 | emit_changed(_get_revert_property(), revert_value); |
718 | update_property(); |
719 | } |
720 | |
721 | if (check_rect.has_point(mpos)) { |
722 | accept_event(); |
723 | checked = !checked; |
724 | queue_redraw(); |
725 | emit_signal(SNAME("property_checked" ), property, checked); |
726 | } |
727 | } else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { |
728 | accept_event(); |
729 | _update_popup(); |
730 | menu->set_position(get_screen_position() + get_local_mouse_position()); |
731 | menu->reset_size(); |
732 | menu->popup(); |
733 | select(); |
734 | return; |
735 | } |
736 | } |
737 | |
738 | void EditorProperty::shortcut_input(const Ref<InputEvent> &p_event) { |
739 | if (!selected || !p_event->is_pressed()) { |
740 | return; |
741 | } |
742 | |
743 | const Ref<InputEventKey> k = p_event; |
744 | |
745 | if (k.is_valid() && k->is_pressed()) { |
746 | if (ED_IS_SHORTCUT("property_editor/copy_value" , p_event)) { |
747 | menu_option(MENU_COPY_VALUE); |
748 | accept_event(); |
749 | } else if (ED_IS_SHORTCUT("property_editor/paste_value" , p_event) && !is_read_only()) { |
750 | menu_option(MENU_PASTE_VALUE); |
751 | accept_event(); |
752 | } else if (ED_IS_SHORTCUT("property_editor/copy_property_path" , p_event)) { |
753 | menu_option(MENU_COPY_PROPERTY_PATH); |
754 | accept_event(); |
755 | } |
756 | } |
757 | } |
758 | |
759 | const Color *EditorProperty::_get_property_colors() { |
760 | static Color c[4]; |
761 | c[0] = get_theme_color(SNAME("property_color_x" ), EditorStringName(Editor)); |
762 | c[1] = get_theme_color(SNAME("property_color_y" ), EditorStringName(Editor)); |
763 | c[2] = get_theme_color(SNAME("property_color_z" ), EditorStringName(Editor)); |
764 | c[3] = get_theme_color(SNAME("property_color_w" ), EditorStringName(Editor)); |
765 | return c; |
766 | } |
767 | |
768 | void EditorProperty::set_label_reference(Control *p_control) { |
769 | label_reference = p_control; |
770 | } |
771 | |
772 | void EditorProperty::set_bottom_editor(Control *p_control) { |
773 | bottom_editor = p_control; |
774 | } |
775 | |
776 | Variant EditorProperty::_get_cache_value(const StringName &p_prop, bool &r_valid) const { |
777 | return object->get(p_prop, &r_valid); |
778 | } |
779 | |
780 | bool EditorProperty::is_cache_valid() const { |
781 | if (object) { |
782 | for (const KeyValue<StringName, Variant> &E : cache) { |
783 | bool valid; |
784 | Variant value = _get_cache_value(E.key, valid); |
785 | if (!valid || value != E.value) { |
786 | return false; |
787 | } |
788 | } |
789 | } |
790 | return true; |
791 | } |
792 | void EditorProperty::update_cache() { |
793 | cache.clear(); |
794 | if (object && property != StringName()) { |
795 | bool valid; |
796 | Variant value = _get_cache_value(property, valid); |
797 | if (valid) { |
798 | cache[property] = value; |
799 | } |
800 | } |
801 | } |
802 | Variant EditorProperty::get_drag_data(const Point2 &p_point) { |
803 | if (property == StringName()) { |
804 | return Variant(); |
805 | } |
806 | |
807 | Dictionary dp; |
808 | dp["type" ] = "obj_property" ; |
809 | dp["object" ] = object; |
810 | dp["property" ] = property; |
811 | dp["value" ] = object->get(property); |
812 | |
813 | Label *drag_label = memnew(Label); |
814 | drag_label->set_text(property); |
815 | set_drag_preview(drag_label); |
816 | return dp; |
817 | } |
818 | |
819 | void EditorProperty::set_use_folding(bool p_use_folding) { |
820 | use_folding = p_use_folding; |
821 | } |
822 | |
823 | bool EditorProperty::is_using_folding() const { |
824 | return use_folding; |
825 | } |
826 | |
827 | void EditorProperty::expand_all_folding() { |
828 | } |
829 | |
830 | void EditorProperty::collapse_all_folding() { |
831 | } |
832 | |
833 | void EditorProperty::expand_revertable() { |
834 | } |
835 | |
836 | void EditorProperty::set_selectable(bool p_selectable) { |
837 | selectable = p_selectable; |
838 | } |
839 | |
840 | bool EditorProperty::is_selectable() const { |
841 | return selectable; |
842 | } |
843 | |
844 | void EditorProperty::set_name_split_ratio(float p_ratio) { |
845 | split_ratio = p_ratio; |
846 | } |
847 | |
848 | float EditorProperty::get_name_split_ratio() const { |
849 | return split_ratio; |
850 | } |
851 | |
852 | void EditorProperty::set_object_and_property(Object *p_object, const StringName &p_property) { |
853 | object = p_object; |
854 | property = p_property; |
855 | _update_pin_flags(); |
856 | } |
857 | |
858 | static bool _is_value_potential_override(Node *p_node, const String &p_property) { |
859 | // Consider a value is potentially overriding another if either of the following is true: |
860 | // a) The node is foreign (inheriting or an instance), so the original value may come from another scene. |
861 | // b) The node belongs to the scene, but the original value comes from somewhere but the builtin class (i.e., a script). |
862 | Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); |
863 | Vector<SceneState::PackState> states_stack = PropertyUtils::get_node_states_stack(p_node, edited_scene); |
864 | if (states_stack.size()) { |
865 | return true; |
866 | } else { |
867 | bool is_valid_default = false; |
868 | bool is_class_default = false; |
869 | PropertyUtils::get_property_default_value(p_node, p_property, &is_valid_default, &states_stack, false, nullptr, &is_class_default); |
870 | return !is_class_default; |
871 | } |
872 | } |
873 | |
874 | void EditorProperty::_update_pin_flags() { |
875 | can_pin = false; |
876 | pin_hidden = true; |
877 | if (read_only) { |
878 | return; |
879 | } |
880 | if (Node *node = Object::cast_to<Node>(object)) { |
881 | // Avoid errors down the road by ignoring nodes which are not part of a scene |
882 | if (!node->get_owner()) { |
883 | bool is_scene_root = false; |
884 | for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); ++i) { |
885 | if (EditorNode::get_editor_data().get_edited_scene_root(i) == node) { |
886 | is_scene_root = true; |
887 | break; |
888 | } |
889 | } |
890 | if (!is_scene_root) { |
891 | return; |
892 | } |
893 | } |
894 | if (!_is_value_potential_override(node, property)) { |
895 | return; |
896 | } |
897 | pin_hidden = false; |
898 | { |
899 | HashSet<StringName> storable_properties; |
900 | node->get_storable_properties(storable_properties); |
901 | if (storable_properties.has(node->get_property_store_alias(property))) { |
902 | can_pin = true; |
903 | } |
904 | } |
905 | } |
906 | } |
907 | |
908 | static Control *make_help_bit(const String &p_item_type, const String &p_text, const String &p_warning, const Color &p_warn_color) { |
909 | // `p_text` is expected to be something like this: |
910 | // `item_name|Item description.`. |
911 | // Note that the description can be empty or contain `|`. |
912 | PackedStringArray slices = p_text.split("|" , true, 1); |
913 | if (slices.size() < 2) { |
914 | return nullptr; // Use default tooltip instead. |
915 | } |
916 | |
917 | String item_name = slices[0].strip_edges(); |
918 | String item_descr = slices[1].strip_edges(); |
919 | |
920 | String text; |
921 | if (!p_item_type.is_empty()) { |
922 | text = p_item_type + " " ; |
923 | } |
924 | text += "[u][b]" + item_name + "[/b][/u]\n" ; |
925 | if (item_descr.is_empty()) { |
926 | text += "[i]" + TTR("No description." ) + "[/i]" ; |
927 | } else { |
928 | text += item_descr; |
929 | } |
930 | if (!p_warning.is_empty()) { |
931 | text += "\n[b][color=" + p_warn_color.to_html(false) + "]" + p_warning + "[/color][/b]" ; |
932 | } |
933 | |
934 | EditorHelpBit *help_bit = memnew(EditorHelpBit); |
935 | help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); |
936 | help_bit->set_text(text); |
937 | |
938 | return help_bit; |
939 | } |
940 | |
941 | Control *EditorProperty::make_custom_tooltip(const String &p_text) const { |
942 | String warn; |
943 | Color warn_color; |
944 | if (object->has_method("_get_property_warning" )) { |
945 | warn = object->call("_get_property_warning" , property); |
946 | warn_color = get_theme_color(SNAME("warning_color" )); |
947 | } |
948 | return make_help_bit(TTR("Property:" ), p_text, warn, warn_color); |
949 | } |
950 | |
951 | void EditorProperty::(int p_option) { |
952 | switch (p_option) { |
953 | case MENU_COPY_VALUE: { |
954 | InspectorDock::get_inspector_singleton()->set_property_clipboard(object->get(property)); |
955 | } break; |
956 | case MENU_PASTE_VALUE: { |
957 | emit_changed(property, InspectorDock::get_inspector_singleton()->get_property_clipboard()); |
958 | } break; |
959 | case MENU_COPY_PROPERTY_PATH: { |
960 | DisplayServer::get_singleton()->clipboard_set(property_path); |
961 | } break; |
962 | case MENU_PIN_VALUE: { |
963 | emit_signal(SNAME("property_pinned" ), property, !pinned); |
964 | queue_redraw(); |
965 | } break; |
966 | case MENU_OPEN_DOCUMENTATION: { |
967 | ScriptEditor::get_singleton()->goto_help(doc_path); |
968 | EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); |
969 | } break; |
970 | } |
971 | } |
972 | |
973 | void EditorProperty::_bind_methods() { |
974 | ClassDB::bind_method(D_METHOD("set_label" , "text" ), &EditorProperty::set_label); |
975 | ClassDB::bind_method(D_METHOD("get_label" ), &EditorProperty::get_label); |
976 | |
977 | ClassDB::bind_method(D_METHOD("set_read_only" , "read_only" ), &EditorProperty::set_read_only); |
978 | ClassDB::bind_method(D_METHOD("is_read_only" ), &EditorProperty::is_read_only); |
979 | |
980 | ClassDB::bind_method(D_METHOD("set_checkable" , "checkable" ), &EditorProperty::set_checkable); |
981 | ClassDB::bind_method(D_METHOD("is_checkable" ), &EditorProperty::is_checkable); |
982 | |
983 | ClassDB::bind_method(D_METHOD("set_checked" , "checked" ), &EditorProperty::set_checked); |
984 | ClassDB::bind_method(D_METHOD("is_checked" ), &EditorProperty::is_checked); |
985 | |
986 | ClassDB::bind_method(D_METHOD("set_draw_warning" , "draw_warning" ), &EditorProperty::set_draw_warning); |
987 | ClassDB::bind_method(D_METHOD("is_draw_warning" ), &EditorProperty::is_draw_warning); |
988 | |
989 | ClassDB::bind_method(D_METHOD("set_keying" , "keying" ), &EditorProperty::set_keying); |
990 | ClassDB::bind_method(D_METHOD("is_keying" ), &EditorProperty::is_keying); |
991 | |
992 | ClassDB::bind_method(D_METHOD("set_deletable" , "deletable" ), &EditorProperty::set_deletable); |
993 | ClassDB::bind_method(D_METHOD("is_deletable" ), &EditorProperty::is_deletable); |
994 | |
995 | ClassDB::bind_method(D_METHOD("get_edited_property" ), &EditorProperty::get_edited_property); |
996 | ClassDB::bind_method(D_METHOD("get_edited_object" ), &EditorProperty::get_edited_object); |
997 | |
998 | ClassDB::bind_method(D_METHOD("update_property" ), &EditorProperty::update_property); |
999 | |
1000 | ClassDB::bind_method(D_METHOD("add_focusable" , "control" ), &EditorProperty::add_focusable); |
1001 | ClassDB::bind_method(D_METHOD("set_bottom_editor" , "editor" ), &EditorProperty::set_bottom_editor); |
1002 | |
1003 | ClassDB::bind_method(D_METHOD("emit_changed" , "property" , "value" , "field" , "changing" ), &EditorProperty::emit_changed, DEFVAL(StringName()), DEFVAL(false)); |
1004 | |
1005 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "label" ), "set_label" , "get_label" ); |
1006 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only" ), "set_read_only" , "is_read_only" ); |
1007 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checkable" ), "set_checkable" , "is_checkable" ); |
1008 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checked" ), "set_checked" , "is_checked" ); |
1009 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_warning" ), "set_draw_warning" , "is_draw_warning" ); |
1010 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keying" ), "set_keying" , "is_keying" ); |
1011 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deletable" ), "set_deletable" , "is_deletable" ); |
1012 | |
1013 | ADD_SIGNAL(MethodInfo("property_changed" , PropertyInfo(Variant::STRING_NAME, "property" ), PropertyInfo(Variant::NIL, "value" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::STRING_NAME, "field" ), PropertyInfo(Variant::BOOL, "changing" ))); |
1014 | ADD_SIGNAL(MethodInfo("multiple_properties_changed" , PropertyInfo(Variant::PACKED_STRING_ARRAY, "properties" ), PropertyInfo(Variant::ARRAY, "value" ))); |
1015 | ADD_SIGNAL(MethodInfo("property_keyed" , PropertyInfo(Variant::STRING_NAME, "property" ))); |
1016 | ADD_SIGNAL(MethodInfo("property_deleted" , PropertyInfo(Variant::STRING_NAME, "property" ))); |
1017 | ADD_SIGNAL(MethodInfo("property_keyed_with_value" , PropertyInfo(Variant::STRING_NAME, "property" ), PropertyInfo(Variant::NIL, "value" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NIL_IS_VARIANT))); |
1018 | ADD_SIGNAL(MethodInfo("property_checked" , PropertyInfo(Variant::STRING_NAME, "property" ), PropertyInfo(Variant::BOOL, "checked" ))); |
1019 | ADD_SIGNAL(MethodInfo("property_pinned" , PropertyInfo(Variant::STRING_NAME, "property" ), PropertyInfo(Variant::BOOL, "pinned" ))); |
1020 | ADD_SIGNAL(MethodInfo("property_can_revert_changed" , PropertyInfo(Variant::STRING_NAME, "property" ), PropertyInfo(Variant::BOOL, "can_revert" ))); |
1021 | ADD_SIGNAL(MethodInfo("resource_selected" , PropertyInfo(Variant::STRING, "path" ), PropertyInfo(Variant::OBJECT, "resource" , PROPERTY_HINT_RESOURCE_TYPE, "Resource" ))); |
1022 | ADD_SIGNAL(MethodInfo("object_id_selected" , PropertyInfo(Variant::STRING_NAME, "property" ), PropertyInfo(Variant::INT, "id" ))); |
1023 | ADD_SIGNAL(MethodInfo("selected" , PropertyInfo(Variant::STRING, "path" ), PropertyInfo(Variant::INT, "focusable_idx" ))); |
1024 | |
1025 | GDVIRTUAL_BIND(_update_property) |
1026 | GDVIRTUAL_BIND(_set_read_only, "read_only" ) |
1027 | |
1028 | ClassDB::bind_method(D_METHOD("_update_editor_property_status" ), &EditorProperty::update_editor_property_status); |
1029 | } |
1030 | |
1031 | EditorProperty::EditorProperty() { |
1032 | object = nullptr; |
1033 | split_ratio = 0.5; |
1034 | text_size = 0; |
1035 | property_usage = 0; |
1036 | selected_focusable = -1; |
1037 | label_reference = nullptr; |
1038 | bottom_editor = nullptr; |
1039 | menu = nullptr; |
1040 | set_process_shortcut_input(true); |
1041 | } |
1042 | |
1043 | void EditorProperty::() { |
1044 | if (menu) { |
1045 | menu->clear(); |
1046 | } else { |
1047 | menu = memnew(PopupMenu); |
1048 | add_child(menu); |
1049 | menu->connect("id_pressed" , callable_mp(this, &EditorProperty::menu_option)); |
1050 | } |
1051 | menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionCopy" )), ED_GET_SHORTCUT("property_editor/copy_value" ), MENU_COPY_VALUE); |
1052 | menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionPaste" )), ED_GET_SHORTCUT("property_editor/paste_value" ), MENU_PASTE_VALUE); |
1053 | menu->add_icon_shortcut(get_editor_theme_icon(SNAME("CopyNodePath" )), ED_GET_SHORTCUT("property_editor/copy_property_path" ), MENU_COPY_PROPERTY_PATH); |
1054 | menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only()); |
1055 | if (!pin_hidden) { |
1056 | menu->add_separator(); |
1057 | if (can_pin) { |
1058 | menu->add_icon_check_item(get_editor_theme_icon(SNAME("Pin" )), TTR("Pin Value" ), MENU_PIN_VALUE); |
1059 | menu->set_item_checked(menu->get_item_index(MENU_PIN_VALUE), pinned); |
1060 | } else { |
1061 | menu->add_icon_check_item(get_editor_theme_icon(SNAME("Pin" )), vformat(TTR("Pin Value [Disabled because '%s' is editor-only]" ), property), MENU_PIN_VALUE); |
1062 | menu->set_item_disabled(menu->get_item_index(MENU_PIN_VALUE), true); |
1063 | } |
1064 | menu->set_item_tooltip(menu->get_item_index(MENU_PIN_VALUE), TTR("Pinning a value forces it to be saved even if it's equal to the default." )); |
1065 | } |
1066 | |
1067 | if (!doc_path.is_empty()) { |
1068 | menu->add_separator(); |
1069 | menu->add_icon_item(get_editor_theme_icon(SNAME("Help" )), TTR("Open Documentation" ), MENU_OPEN_DOCUMENTATION); |
1070 | } |
1071 | } |
1072 | |
1073 | //////////////////////////////////////////////// |
1074 | //////////////////////////////////////////////// |
1075 | |
1076 | void EditorInspectorPlugin::add_custom_control(Control *control) { |
1077 | AddedEditor ae; |
1078 | ae.property_editor = control; |
1079 | added_editors.push_back(ae); |
1080 | } |
1081 | |
1082 | void EditorInspectorPlugin::add_property_editor(const String &p_for_property, Control *p_prop, bool p_add_to_end) { |
1083 | AddedEditor ae; |
1084 | ae.properties.push_back(p_for_property); |
1085 | ae.property_editor = p_prop; |
1086 | ae.add_to_end = p_add_to_end; |
1087 | added_editors.push_back(ae); |
1088 | } |
1089 | |
1090 | void EditorInspectorPlugin::add_property_editor_for_multiple_properties(const String &p_label, const Vector<String> &p_properties, Control *p_prop) { |
1091 | AddedEditor ae; |
1092 | ae.properties = p_properties; |
1093 | ae.property_editor = p_prop; |
1094 | ae.label = p_label; |
1095 | added_editors.push_back(ae); |
1096 | } |
1097 | |
1098 | bool EditorInspectorPlugin::can_handle(Object *p_object) { |
1099 | bool success = false; |
1100 | GDVIRTUAL_CALL(_can_handle, p_object, success); |
1101 | return success; |
1102 | } |
1103 | |
1104 | void EditorInspectorPlugin::parse_begin(Object *p_object) { |
1105 | GDVIRTUAL_CALL(_parse_begin, p_object); |
1106 | } |
1107 | |
1108 | void EditorInspectorPlugin::parse_category(Object *p_object, const String &p_category) { |
1109 | GDVIRTUAL_CALL(_parse_category, p_object, p_category); |
1110 | } |
1111 | |
1112 | void EditorInspectorPlugin::parse_group(Object *p_object, const String &p_group) { |
1113 | GDVIRTUAL_CALL(_parse_group, p_object, p_group); |
1114 | } |
1115 | |
1116 | bool EditorInspectorPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { |
1117 | bool ret = false; |
1118 | GDVIRTUAL_CALL(_parse_property, p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide, ret); |
1119 | return ret; |
1120 | } |
1121 | |
1122 | void EditorInspectorPlugin::parse_end(Object *p_object) { |
1123 | GDVIRTUAL_CALL(_parse_end, p_object); |
1124 | } |
1125 | |
1126 | void EditorInspectorPlugin::_bind_methods() { |
1127 | ClassDB::bind_method(D_METHOD("add_custom_control" , "control" ), &EditorInspectorPlugin::add_custom_control); |
1128 | ClassDB::bind_method(D_METHOD("add_property_editor" , "property" , "editor" , "add_to_end" ), &EditorInspectorPlugin::add_property_editor, DEFVAL(false)); |
1129 | ClassDB::bind_method(D_METHOD("add_property_editor_for_multiple_properties" , "label" , "properties" , "editor" ), &EditorInspectorPlugin::add_property_editor_for_multiple_properties); |
1130 | |
1131 | GDVIRTUAL_BIND(_can_handle, "object" ) |
1132 | GDVIRTUAL_BIND(_parse_begin, "object" ) |
1133 | GDVIRTUAL_BIND(_parse_category, "object" , "category" ) |
1134 | GDVIRTUAL_BIND(_parse_group, "object" , "group" ) |
1135 | GDVIRTUAL_BIND(_parse_property, "object" , "type" , "name" , "hint_type" , "hint_string" , "usage_flags" , "wide" ); |
1136 | GDVIRTUAL_BIND(_parse_end, "object" ) |
1137 | } |
1138 | |
1139 | //////////////////////////////////////////////// |
1140 | //////////////////////////////////////////////// |
1141 | |
1142 | void EditorInspectorCategory::_notification(int p_what) { |
1143 | switch (p_what) { |
1144 | case NOTIFICATION_ENTER_TREE: |
1145 | case NOTIFICATION_THEME_CHANGED: { |
1146 | menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), get_editor_theme_icon(SNAME("Help" ))); |
1147 | } break; |
1148 | case NOTIFICATION_DRAW: { |
1149 | Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg" )); |
1150 | |
1151 | draw_style_box(sb, Rect2(Vector2(), get_size())); |
1152 | |
1153 | Ref<Font> font = get_theme_font(SNAME("bold" ), EditorStringName(EditorFonts)); |
1154 | int font_size = get_theme_font_size(SNAME("bold_size" ), EditorStringName(EditorFonts)); |
1155 | |
1156 | int hs = get_theme_constant(SNAME("h_separation" ), SNAME("Tree" )); |
1157 | int icon_size = get_theme_constant(SNAME("class_icon_size" ), EditorStringName(Editor)); |
1158 | |
1159 | int w = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; |
1160 | if (icon.is_valid()) { |
1161 | w += hs + icon_size; |
1162 | } |
1163 | |
1164 | int ofs = (get_size().width - w) / 2; |
1165 | |
1166 | if (icon.is_valid()) { |
1167 | Size2 rect_size = Size2(icon_size, icon_size); |
1168 | Point2 rect_pos = Point2(ofs, (get_size().height - icon_size) / 2).floor(); |
1169 | draw_texture_rect(icon, Rect2(rect_pos, rect_size)); |
1170 | |
1171 | ofs += hs + icon_size; |
1172 | } |
1173 | |
1174 | Color color = get_theme_color(SNAME("font_color" ), SNAME("Tree" )); |
1175 | draw_string(font, Point2(ofs, font->get_ascent(font_size) + (get_size().height - font->get_height(font_size)) / 2).floor(), label, HORIZONTAL_ALIGNMENT_LEFT, get_size().width, font_size, color); |
1176 | } break; |
1177 | } |
1178 | } |
1179 | |
1180 | Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const { |
1181 | return make_help_bit(TTR("Class:" ), p_text, String(), Color()); |
1182 | } |
1183 | |
1184 | Size2 EditorInspectorCategory::get_minimum_size() const { |
1185 | Ref<Font> font = get_theme_font(SNAME("bold" ), EditorStringName(EditorFonts)); |
1186 | int font_size = get_theme_font_size(SNAME("bold_size" ), EditorStringName(EditorFonts)); |
1187 | |
1188 | Size2 ms; |
1189 | ms.height = font->get_height(font_size); |
1190 | if (icon.is_valid()) { |
1191 | ms.height = MAX(icon->get_height(), ms.height); |
1192 | } |
1193 | ms.height += get_theme_constant(SNAME("v_separation" ), SNAME("Tree" )); |
1194 | |
1195 | return ms; |
1196 | } |
1197 | |
1198 | void EditorInspectorCategory::_handle_menu_option(int p_option) { |
1199 | switch (p_option) { |
1200 | case MENU_OPEN_DOCS: |
1201 | ScriptEditor::get_singleton()->goto_help("class:" + doc_class_name); |
1202 | EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); |
1203 | break; |
1204 | } |
1205 | } |
1206 | |
1207 | void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) { |
1208 | if (doc_class_name.is_empty()) { |
1209 | return; |
1210 | } |
1211 | |
1212 | const Ref<InputEventMouseButton> &mb_event = p_event; |
1213 | if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) { |
1214 | return; |
1215 | } |
1216 | |
1217 | menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name)); |
1218 | |
1219 | menu->set_position(get_screen_position() + mb_event->get_position()); |
1220 | menu->reset_size(); |
1221 | menu->popup(); |
1222 | } |
1223 | |
1224 | EditorInspectorCategory::EditorInspectorCategory() { |
1225 | menu = memnew(PopupMenu); |
1226 | menu->connect("id_pressed" , callable_mp(this, &EditorInspectorCategory::_handle_menu_option)); |
1227 | menu->add_item(TTR("Open Documentation" ), MENU_OPEN_DOCS); |
1228 | add_child(menu); |
1229 | } |
1230 | |
1231 | //////////////////////////////////////////////// |
1232 | //////////////////////////////////////////////// |
1233 | |
1234 | void EditorInspectorSection::_test_unfold() { |
1235 | if (!vbox_added) { |
1236 | add_child(vbox); |
1237 | move_child(vbox, 0); |
1238 | vbox_added = true; |
1239 | } |
1240 | } |
1241 | |
1242 | Ref<Texture2D> EditorInspectorSection::_get_arrow() { |
1243 | Ref<Texture2D> arrow; |
1244 | if (foldable) { |
1245 | if (object->editor_is_section_unfolded(section)) { |
1246 | arrow = get_theme_icon(SNAME("arrow" ), SNAME("Tree" )); |
1247 | } else { |
1248 | if (is_layout_rtl()) { |
1249 | arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored" ), SNAME("Tree" )); |
1250 | } else { |
1251 | arrow = get_theme_icon(SNAME("arrow_collapsed" ), SNAME("Tree" )); |
1252 | } |
1253 | } |
1254 | } |
1255 | return arrow; |
1256 | } |
1257 | |
1258 | int EditorInspectorSection::() { |
1259 | Ref<Font> font = get_theme_font(SNAME("bold" ), EditorStringName(EditorFonts)); |
1260 | int font_size = get_theme_font_size(SNAME("bold_size" ), EditorStringName(EditorFonts)); |
1261 | |
1262 | int = font->get_height(font_size); |
1263 | Ref<Texture2D> arrow = _get_arrow(); |
1264 | if (arrow.is_valid()) { |
1265 | header_height = MAX(header_height, arrow->get_height()); |
1266 | } |
1267 | header_height += get_theme_constant(SNAME("v_separation" ), SNAME("Tree" )); |
1268 | |
1269 | return header_height; |
1270 | } |
1271 | |
1272 | void EditorInspectorSection::_notification(int p_what) { |
1273 | switch (p_what) { |
1274 | case NOTIFICATION_THEME_CHANGED: { |
1275 | update_minimum_size(); |
1276 | } break; |
1277 | |
1278 | case NOTIFICATION_SORT_CHILDREN: { |
1279 | if (!vbox_added) { |
1280 | return; |
1281 | } |
1282 | |
1283 | int inspector_margin = get_theme_constant(SNAME("inspector_margin" ), EditorStringName(Editor)); |
1284 | int section_indent_size = get_theme_constant(SNAME("indent_size" ), SNAME("EditorInspectorSection" )); |
1285 | if (indent_depth > 0 && section_indent_size > 0) { |
1286 | inspector_margin += indent_depth * section_indent_size; |
1287 | } |
1288 | Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box" ), SNAME("EditorInspectorSection" )); |
1289 | if (indent_depth > 0 && section_indent_style.is_valid()) { |
1290 | inspector_margin += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT); |
1291 | } |
1292 | |
1293 | Size2 size = get_size() - Vector2(inspector_margin, 0); |
1294 | int = _get_header_height(); |
1295 | Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height); |
1296 | for (int i = 0; i < get_child_count(); i++) { |
1297 | Control *c = Object::cast_to<Control>(get_child(i)); |
1298 | if (!c) { |
1299 | continue; |
1300 | } |
1301 | if (c->is_set_as_top_level()) { |
1302 | continue; |
1303 | } |
1304 | |
1305 | fit_child_in_rect(c, Rect2(offset, size)); |
1306 | } |
1307 | } break; |
1308 | |
1309 | case NOTIFICATION_DRAW: { |
1310 | int section_indent = 0; |
1311 | int section_indent_size = get_theme_constant(SNAME("indent_size" ), SNAME("EditorInspectorSection" )); |
1312 | if (indent_depth > 0 && section_indent_size > 0) { |
1313 | section_indent = indent_depth * section_indent_size; |
1314 | } |
1315 | Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box" ), SNAME("EditorInspectorSection" )); |
1316 | if (indent_depth > 0 && section_indent_style.is_valid()) { |
1317 | section_indent += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT); |
1318 | } |
1319 | |
1320 | int = get_size().width - section_indent; |
1321 | int = 0.0; |
1322 | bool rtl = is_layout_rtl(); |
1323 | if (!rtl) { |
1324 | header_offset_x += section_indent; |
1325 | } |
1326 | |
1327 | // Draw header area. |
1328 | int = _get_header_height(); |
1329 | Rect2 = Rect2(Vector2(header_offset_x, 0.0), Vector2(header_width, header_height)); |
1330 | Color c = bg_color; |
1331 | c.a *= 0.4; |
1332 | if (foldable && header_rect.has_point(get_local_mouse_position())) { |
1333 | c = c.lightened(Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) ? -0.05 : 0.2); |
1334 | } |
1335 | draw_rect(header_rect, c); |
1336 | |
1337 | // Draw header title, folding arrow and count of revertable properties. |
1338 | { |
1339 | int separation = Math::round(2 * EDSCALE); |
1340 | |
1341 | int margin_start = section_indent + separation; |
1342 | int margin_end = separation; |
1343 | |
1344 | // - Arrow. |
1345 | Ref<Texture2D> arrow = _get_arrow(); |
1346 | if (arrow.is_valid()) { |
1347 | Point2 arrow_position; |
1348 | if (rtl) { |
1349 | arrow_position.x = get_size().width - (margin_start + arrow->get_width()); |
1350 | } else { |
1351 | arrow_position.x = margin_start; |
1352 | } |
1353 | arrow_position.y = (header_height - arrow->get_height()) / 2; |
1354 | draw_texture(arrow, arrow_position); |
1355 | margin_start += arrow->get_width(); |
1356 | } |
1357 | |
1358 | int available = get_size().width - (margin_start + margin_end); |
1359 | |
1360 | // - Count of revertable properties. |
1361 | String num_revertable_str; |
1362 | int num_revertable_width = 0; |
1363 | |
1364 | bool folded = foldable && !object->editor_is_section_unfolded(section); |
1365 | |
1366 | Ref<Font> font = get_theme_font(SNAME("bold" ), EditorStringName(EditorFonts)); |
1367 | int font_size = get_theme_font_size(SNAME("bold_size" ), EditorStringName(EditorFonts)); |
1368 | Color font_color = get_theme_color(SNAME("font_color" ), EditorStringName(Editor)); |
1369 | |
1370 | if (folded && revertable_properties.size()) { |
1371 | int label_width = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, available, font_size, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS).x; |
1372 | |
1373 | Ref<Font> light_font = get_theme_font(SNAME("main" ), EditorStringName(EditorFonts)); |
1374 | int light_font_size = get_theme_font_size(SNAME("main_size" ), EditorStringName(EditorFonts)); |
1375 | Color light_font_color = get_theme_color(SNAME("disabled_font_color" ), EditorStringName(Editor)); |
1376 | |
1377 | // Can we fit the long version of the revertable count text? |
1378 | num_revertable_str = vformat(TTRN("(%d change)" , "(%d changes)" , revertable_properties.size()), revertable_properties.size()); |
1379 | num_revertable_width = light_font->get_string_size(num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, TextServer::JUSTIFICATION_NONE).x; |
1380 | if (label_width + separation + num_revertable_width > available) { |
1381 | // We'll have to use the short version. |
1382 | num_revertable_str = vformat("(%d)" , revertable_properties.size()); |
1383 | num_revertable_width = light_font->get_string_size(num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, TextServer::JUSTIFICATION_NONE).x; |
1384 | } |
1385 | |
1386 | Point2 text_offset = Point2( |
1387 | margin_end, |
1388 | light_font->get_ascent(light_font_size) + (header_height - light_font->get_height(light_font_size)) / 2); |
1389 | if (!rtl) { |
1390 | text_offset.x = get_size().width - (text_offset.x + num_revertable_width); |
1391 | } |
1392 | draw_string(light_font, text_offset, num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, light_font_color, TextServer::JUSTIFICATION_NONE); |
1393 | margin_end += num_revertable_width + separation; |
1394 | available -= num_revertable_width + separation; |
1395 | } |
1396 | |
1397 | // - Label. |
1398 | Point2 text_offset = Point2( |
1399 | margin_start, |
1400 | font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2); |
1401 | if (rtl) { |
1402 | text_offset.x = margin_end; |
1403 | } |
1404 | HorizontalAlignment text_align = rtl ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT; |
1405 | draw_string(font, text_offset, label, text_align, available, font_size, font_color, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); |
1406 | } |
1407 | |
1408 | // Draw dropping highlight. |
1409 | if (dropping && !vbox->is_visible_in_tree()) { |
1410 | Color accent_color = get_theme_color(SNAME("accent_color" ), EditorStringName(Editor)); |
1411 | draw_rect(Rect2(Point2(), get_size()), accent_color, false); |
1412 | } |
1413 | |
1414 | // Draw section indentation. |
1415 | if (section_indent_style.is_valid() && section_indent > 0) { |
1416 | Rect2 indent_rect = Rect2(Vector2(), Vector2(indent_depth * section_indent_size, get_size().height)); |
1417 | if (rtl) { |
1418 | indent_rect.position.x = get_size().width - section_indent + section_indent_style->get_margin(SIDE_RIGHT); |
1419 | } else { |
1420 | indent_rect.position.x = section_indent_style->get_margin(SIDE_LEFT); |
1421 | } |
1422 | draw_style_box(section_indent_style, indent_rect); |
1423 | } |
1424 | } break; |
1425 | |
1426 | case NOTIFICATION_DRAG_BEGIN: { |
1427 | dropping_for_unfold = true; |
1428 | } break; |
1429 | |
1430 | case NOTIFICATION_DRAG_END: { |
1431 | dropping_for_unfold = false; |
1432 | } break; |
1433 | |
1434 | case NOTIFICATION_MOUSE_ENTER: { |
1435 | if (dropping || dropping_for_unfold) { |
1436 | dropping_unfold_timer->start(); |
1437 | } |
1438 | queue_redraw(); |
1439 | } break; |
1440 | |
1441 | case NOTIFICATION_MOUSE_EXIT: { |
1442 | if (dropping || dropping_for_unfold) { |
1443 | dropping_unfold_timer->stop(); |
1444 | } |
1445 | queue_redraw(); |
1446 | } break; |
1447 | } |
1448 | } |
1449 | |
1450 | Size2 EditorInspectorSection::get_minimum_size() const { |
1451 | Size2 ms; |
1452 | for (int i = 0; i < get_child_count(); i++) { |
1453 | Control *c = Object::cast_to<Control>(get_child(i)); |
1454 | if (!c) { |
1455 | continue; |
1456 | } |
1457 | if (c->is_set_as_top_level()) { |
1458 | continue; |
1459 | } |
1460 | if (!c->is_visible()) { |
1461 | continue; |
1462 | } |
1463 | Size2 minsize = c->get_combined_minimum_size(); |
1464 | ms.width = MAX(ms.width, minsize.width); |
1465 | ms.height = MAX(ms.height, minsize.height); |
1466 | } |
1467 | |
1468 | Ref<Font> font = get_theme_font(SNAME("font" ), SNAME("Tree" )); |
1469 | int font_size = get_theme_font_size(SNAME("font_size" ), SNAME("Tree" )); |
1470 | ms.height += font->get_height(font_size) + get_theme_constant(SNAME("v_separation" ), SNAME("Tree" )); |
1471 | ms.width += get_theme_constant(SNAME("inspector_margin" ), EditorStringName(Editor)); |
1472 | |
1473 | int section_indent_size = get_theme_constant(SNAME("indent_size" ), SNAME("EditorInspectorSection" )); |
1474 | if (indent_depth > 0 && section_indent_size > 0) { |
1475 | ms.width += indent_depth * section_indent_size; |
1476 | } |
1477 | Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box" ), SNAME("EditorInspectorSection" )); |
1478 | if (indent_depth > 0 && section_indent_style.is_valid()) { |
1479 | ms.width += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT); |
1480 | } |
1481 | |
1482 | return ms; |
1483 | } |
1484 | |
1485 | void EditorInspectorSection::setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth) { |
1486 | section = p_section; |
1487 | label = p_label; |
1488 | object = p_object; |
1489 | bg_color = p_bg_color; |
1490 | foldable = p_foldable; |
1491 | indent_depth = p_indent_depth; |
1492 | |
1493 | if (!foldable && !vbox_added) { |
1494 | add_child(vbox); |
1495 | move_child(vbox, 0); |
1496 | vbox_added = true; |
1497 | } |
1498 | |
1499 | if (foldable) { |
1500 | _test_unfold(); |
1501 | if (object->editor_is_section_unfolded(section)) { |
1502 | vbox->show(); |
1503 | } else { |
1504 | vbox->hide(); |
1505 | } |
1506 | } |
1507 | } |
1508 | |
1509 | void EditorInspectorSection::gui_input(const Ref<InputEvent> &p_event) { |
1510 | ERR_FAIL_COND(p_event.is_null()); |
1511 | |
1512 | if (!foldable) { |
1513 | return; |
1514 | } |
1515 | |
1516 | Ref<InputEventMouseButton> mb = p_event; |
1517 | if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { |
1518 | if (object->editor_is_section_unfolded(section)) { |
1519 | int = _get_header_height(); |
1520 | |
1521 | if (mb->get_position().y >= header_height) { |
1522 | return; |
1523 | } |
1524 | } |
1525 | |
1526 | accept_event(); |
1527 | |
1528 | bool should_unfold = !object->editor_is_section_unfolded(section); |
1529 | if (should_unfold) { |
1530 | unfold(); |
1531 | } else { |
1532 | fold(); |
1533 | } |
1534 | } else if (mb.is_valid() && !mb->is_pressed()) { |
1535 | queue_redraw(); |
1536 | } |
1537 | } |
1538 | |
1539 | VBoxContainer *EditorInspectorSection::get_vbox() { |
1540 | return vbox; |
1541 | } |
1542 | |
1543 | void EditorInspectorSection::unfold() { |
1544 | if (!foldable) { |
1545 | return; |
1546 | } |
1547 | |
1548 | _test_unfold(); |
1549 | |
1550 | object->editor_set_section_unfold(section, true); |
1551 | vbox->show(); |
1552 | queue_redraw(); |
1553 | } |
1554 | |
1555 | void EditorInspectorSection::fold() { |
1556 | if (!foldable) { |
1557 | return; |
1558 | } |
1559 | |
1560 | if (!vbox_added) { |
1561 | return; |
1562 | } |
1563 | |
1564 | object->editor_set_section_unfold(section, false); |
1565 | vbox->hide(); |
1566 | queue_redraw(); |
1567 | } |
1568 | |
1569 | void EditorInspectorSection::set_bg_color(const Color &p_bg_color) { |
1570 | bg_color = p_bg_color; |
1571 | queue_redraw(); |
1572 | } |
1573 | |
1574 | bool EditorInspectorSection::has_revertable_properties() const { |
1575 | return !revertable_properties.is_empty(); |
1576 | } |
1577 | |
1578 | void EditorInspectorSection::property_can_revert_changed(const String &p_path, bool p_can_revert) { |
1579 | bool had_revertable_properties = has_revertable_properties(); |
1580 | if (p_can_revert) { |
1581 | revertable_properties.insert(p_path); |
1582 | } else { |
1583 | revertable_properties.erase(p_path); |
1584 | } |
1585 | if (has_revertable_properties() != had_revertable_properties) { |
1586 | queue_redraw(); |
1587 | } |
1588 | } |
1589 | |
1590 | void EditorInspectorSection::_bind_methods() { |
1591 | ClassDB::bind_method(D_METHOD("setup" , "section" , "label" , "object" , "bg_color" , "foldable" ), &EditorInspectorSection::setup); |
1592 | ClassDB::bind_method(D_METHOD("get_vbox" ), &EditorInspectorSection::get_vbox); |
1593 | ClassDB::bind_method(D_METHOD("unfold" ), &EditorInspectorSection::unfold); |
1594 | ClassDB::bind_method(D_METHOD("fold" ), &EditorInspectorSection::fold); |
1595 | } |
1596 | |
1597 | EditorInspectorSection::EditorInspectorSection() { |
1598 | vbox = memnew(VBoxContainer); |
1599 | |
1600 | dropping_unfold_timer = memnew(Timer); |
1601 | dropping_unfold_timer->set_wait_time(0.6); |
1602 | dropping_unfold_timer->set_one_shot(true); |
1603 | add_child(dropping_unfold_timer); |
1604 | dropping_unfold_timer->connect("timeout" , callable_mp(this, &EditorInspectorSection::unfold)); |
1605 | } |
1606 | |
1607 | EditorInspectorSection::~EditorInspectorSection() { |
1608 | if (!vbox_added) { |
1609 | memdelete(vbox); |
1610 | } |
1611 | } |
1612 | |
1613 | //////////////////////////////////////////////// |
1614 | //////////////////////////////////////////////// |
1615 | |
1616 | int EditorInspectorArray::_get_array_count() { |
1617 | if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { |
1618 | List<PropertyInfo> object_property_list; |
1619 | object->get_property_list(&object_property_list); |
1620 | return _extract_properties_as_array(object_property_list).size(); |
1621 | } else if (mode == MODE_USE_COUNT_PROPERTY) { |
1622 | bool valid; |
1623 | int count_val = object->get(count_property, &valid); |
1624 | ERR_FAIL_COND_V_MSG(!valid, 0, vformat("%s is not a valid property to be used as array count." , count_property)); |
1625 | return count_val; |
1626 | } |
1627 | return 0; |
1628 | } |
1629 | |
1630 | void EditorInspectorArray::_add_button_pressed() { |
1631 | _move_element(-1, -1); |
1632 | } |
1633 | |
1634 | void EditorInspectorArray::_paginator_page_changed(int p_page) { |
1635 | emit_signal("page_change_request" , p_page); |
1636 | } |
1637 | |
1638 | void EditorInspectorArray::(int p_id) { |
1639 | switch (p_id) { |
1640 | case OPTION_MOVE_UP: |
1641 | if (popup_array_index_pressed > 0) { |
1642 | _move_element(popup_array_index_pressed, popup_array_index_pressed - 1); |
1643 | } |
1644 | break; |
1645 | case OPTION_MOVE_DOWN: |
1646 | if (popup_array_index_pressed < count - 1) { |
1647 | _move_element(popup_array_index_pressed, popup_array_index_pressed + 2); |
1648 | } |
1649 | break; |
1650 | case OPTION_NEW_BEFORE: |
1651 | _move_element(-1, popup_array_index_pressed); |
1652 | break; |
1653 | case OPTION_NEW_AFTER: |
1654 | _move_element(-1, popup_array_index_pressed + 1); |
1655 | break; |
1656 | case OPTION_REMOVE: |
1657 | _move_element(popup_array_index_pressed, -1); |
1658 | break; |
1659 | case OPTION_CLEAR_ARRAY: |
1660 | _clear_array(); |
1661 | break; |
1662 | case OPTION_RESIZE_ARRAY: |
1663 | new_size_spin_box->set_value(count); |
1664 | resize_dialog->get_ok_button()->set_disabled(true); |
1665 | resize_dialog->popup_centered(Size2(250, 0) * EDSCALE); |
1666 | new_size_spin_box->get_line_edit()->grab_focus(); |
1667 | new_size_spin_box->get_line_edit()->select_all(); |
1668 | break; |
1669 | default: |
1670 | break; |
1671 | } |
1672 | } |
1673 | |
1674 | void EditorInspectorArray::_control_dropping_draw() { |
1675 | int drop_position = _drop_position(); |
1676 | |
1677 | if (dropping && drop_position >= 0) { |
1678 | Vector2 from; |
1679 | Vector2 to; |
1680 | if (drop_position < elements_vbox->get_child_count()) { |
1681 | Transform2D xform = Object::cast_to<Control>(elements_vbox->get_child(drop_position))->get_transform(); |
1682 | from = xform.xform(Vector2()); |
1683 | to = xform.xform(Vector2(elements_vbox->get_size().x, 0)); |
1684 | } else { |
1685 | Control *child = Object::cast_to<Control>(elements_vbox->get_child(drop_position - 1)); |
1686 | Transform2D xform = child->get_transform(); |
1687 | from = xform.xform(Vector2(0, child->get_size().y)); |
1688 | to = xform.xform(Vector2(elements_vbox->get_size().x, child->get_size().y)); |
1689 | } |
1690 | Color color = get_theme_color(SNAME("accent_color" ), EditorStringName(Editor)); |
1691 | control_dropping->draw_line(from, to, color, 2); |
1692 | } |
1693 | } |
1694 | |
1695 | void EditorInspectorArray::_vbox_visibility_changed() { |
1696 | control_dropping->set_visible(vbox->is_visible_in_tree()); |
1697 | } |
1698 | |
1699 | void EditorInspectorArray::_panel_draw(int p_index) { |
1700 | ERR_FAIL_INDEX(p_index, (int)array_elements.size()); |
1701 | |
1702 | Ref<StyleBox> style = get_theme_stylebox(SNAME("Focus" ), EditorStringName(EditorStyles)); |
1703 | if (!style.is_valid()) { |
1704 | return; |
1705 | } |
1706 | if (array_elements[p_index].panel->has_focus()) { |
1707 | array_elements[p_index].panel->draw_style_box(style, Rect2(Vector2(), array_elements[p_index].panel->get_size())); |
1708 | } |
1709 | } |
1710 | |
1711 | void EditorInspectorArray::_panel_gui_input(Ref<InputEvent> p_event, int p_index) { |
1712 | ERR_FAIL_INDEX(p_index, (int)array_elements.size()); |
1713 | |
1714 | if (read_only) { |
1715 | return; |
1716 | } |
1717 | |
1718 | Ref<InputEventKey> key_ref = p_event; |
1719 | if (key_ref.is_valid()) { |
1720 | const InputEventKey &key = **key_ref; |
1721 | |
1722 | if (array_elements[p_index].panel->has_focus() && key.is_pressed() && key.get_keycode() == Key::KEY_DELETE) { |
1723 | _move_element(begin_array_index + p_index, -1); |
1724 | array_elements[p_index].panel->accept_event(); |
1725 | } |
1726 | } |
1727 | |
1728 | Ref<InputEventMouseButton> mb = p_event; |
1729 | if (mb.is_valid()) { |
1730 | if (movable && mb->get_button_index() == MouseButton::RIGHT) { |
1731 | array_elements[p_index].panel->accept_event(); |
1732 | popup_array_index_pressed = begin_array_index + p_index; |
1733 | rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0); |
1734 | rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1); |
1735 | rmb_popup->set_position(get_screen_position() + mb->get_position()); |
1736 | rmb_popup->reset_size(); |
1737 | rmb_popup->popup(); |
1738 | } |
1739 | } |
1740 | } |
1741 | |
1742 | void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) { |
1743 | String action_name; |
1744 | if (p_element_index < 0) { |
1745 | action_name = vformat(TTR("Add element to property array with prefix %s." ), array_element_prefix); |
1746 | } else if (p_to_pos < 0) { |
1747 | action_name = vformat(TTR("Remove element %d from property array with prefix %s." ), p_element_index, array_element_prefix); |
1748 | } else { |
1749 | action_name = vformat(TTR("Move element %d to position %d in property array with prefix %s." ), p_element_index, p_to_pos, array_element_prefix); |
1750 | } |
1751 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
1752 | undo_redo->create_action(action_name); |
1753 | if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { |
1754 | // Call the function. |
1755 | Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name()); |
1756 | if (move_function.is_valid()) { |
1757 | Variant args[] = { undo_redo, object, array_element_prefix, p_element_index, p_to_pos }; |
1758 | const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; |
1759 | Variant return_value; |
1760 | Callable::CallError call_error; |
1761 | move_function.callp(args_p, 5, return_value, call_error); |
1762 | } else { |
1763 | WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function" , object->get_class_name())); |
1764 | } |
1765 | } else if (mode == MODE_USE_COUNT_PROPERTY) { |
1766 | ERR_FAIL_COND(p_to_pos < -1 || p_to_pos > count); |
1767 | |
1768 | if (!swap_method.is_empty()) { |
1769 | ERR_FAIL_COND(!object->has_method(swap_method)); |
1770 | |
1771 | // Swap method was provided, use it. |
1772 | if (p_element_index < 0) { |
1773 | // Add an element at position |
1774 | undo_redo->add_do_property(object, count_property, count + 1); |
1775 | if (p_to_pos >= 0) { |
1776 | for (int i = count; i > p_to_pos; i--) { |
1777 | undo_redo->add_do_method(object, swap_method, i, i - 1); |
1778 | } |
1779 | for (int i = p_to_pos; i < count; i++) { |
1780 | undo_redo->add_undo_method(object, swap_method, i, i + 1); |
1781 | } |
1782 | } |
1783 | undo_redo->add_undo_property(object, count_property, count); |
1784 | |
1785 | } else if (p_to_pos < 0) { |
1786 | if (count > 0) { |
1787 | // Remove element at position |
1788 | undo_redo->add_undo_property(object, count_property, count); |
1789 | |
1790 | List<PropertyInfo> object_property_list; |
1791 | object->get_property_list(&object_property_list); |
1792 | |
1793 | for (int i = p_element_index; i < count - 1; i++) { |
1794 | undo_redo->add_do_method(object, swap_method, i, i + 1); |
1795 | } |
1796 | |
1797 | for (int i = count; i > p_element_index; i--) { |
1798 | undo_redo->add_undo_method(object, swap_method, i, i - 1); |
1799 | } |
1800 | |
1801 | String erase_prefix = String(array_element_prefix) + itos(p_element_index); |
1802 | |
1803 | for (const PropertyInfo &E : object_property_list) { |
1804 | if (E.name.begins_with(erase_prefix)) { |
1805 | undo_redo->add_undo_property(object, E.name, object->get(E.name)); |
1806 | } |
1807 | } |
1808 | |
1809 | undo_redo->add_do_property(object, count_property, count - 1); |
1810 | } |
1811 | } else { |
1812 | if (p_to_pos > p_element_index) { |
1813 | p_to_pos--; |
1814 | } |
1815 | |
1816 | if (p_to_pos < p_element_index) { |
1817 | for (int i = p_element_index; i > p_to_pos; i--) { |
1818 | undo_redo->add_do_method(object, swap_method, i, i - 1); |
1819 | } |
1820 | for (int i = p_to_pos; i < p_element_index; i++) { |
1821 | undo_redo->add_undo_method(object, swap_method, i, i + 1); |
1822 | } |
1823 | } else if (p_to_pos > p_element_index) { |
1824 | for (int i = p_element_index; i < p_to_pos; i++) { |
1825 | undo_redo->add_do_method(object, swap_method, i, i + 1); |
1826 | } |
1827 | |
1828 | for (int i = p_to_pos; i > p_element_index; i--) { |
1829 | undo_redo->add_undo_method(object, swap_method, i, i - 1); |
1830 | } |
1831 | } |
1832 | } |
1833 | } else { |
1834 | // Use standard properties. |
1835 | List<PropertyInfo> object_property_list; |
1836 | object->get_property_list(&object_property_list); |
1837 | |
1838 | Array properties_as_array = _extract_properties_as_array(object_property_list); |
1839 | properties_as_array.resize(count); |
1840 | |
1841 | // For undoing things |
1842 | undo_redo->add_undo_property(object, count_property, properties_as_array.size()); |
1843 | for (int i = 0; i < (int)properties_as_array.size(); i++) { |
1844 | Dictionary d = Dictionary(properties_as_array[i]); |
1845 | Array keys = d.keys(); |
1846 | for (int j = 0; j < keys.size(); j++) { |
1847 | String key = keys[j]; |
1848 | undo_redo->add_undo_property(object, vformat(key, i), d[key]); |
1849 | } |
1850 | } |
1851 | |
1852 | if (p_element_index < 0) { |
1853 | // Add an element. |
1854 | properties_as_array.insert(p_to_pos < 0 ? properties_as_array.size() : p_to_pos, Dictionary()); |
1855 | } else if (p_to_pos < 0) { |
1856 | // Delete the element. |
1857 | properties_as_array.remove_at(p_element_index); |
1858 | } else { |
1859 | // Move the element. |
1860 | properties_as_array.insert(p_to_pos, properties_as_array[p_element_index].duplicate()); |
1861 | properties_as_array.remove_at(p_to_pos < p_element_index ? p_element_index + 1 : p_element_index); |
1862 | } |
1863 | |
1864 | // Change the array size then set the properties. |
1865 | undo_redo->add_do_property(object, count_property, properties_as_array.size()); |
1866 | for (int i = 0; i < (int)properties_as_array.size(); i++) { |
1867 | Dictionary d = properties_as_array[i]; |
1868 | Array keys = d.keys(); |
1869 | for (int j = 0; j < keys.size(); j++) { |
1870 | String key = keys[j]; |
1871 | undo_redo->add_do_property(object, vformat(key, i), d[key]); |
1872 | } |
1873 | } |
1874 | } |
1875 | } |
1876 | undo_redo->commit_action(); |
1877 | |
1878 | // Handle page change and update counts. |
1879 | if (p_element_index < 0) { |
1880 | int added_index = p_to_pos < 0 ? count : p_to_pos; |
1881 | emit_signal(SNAME("page_change_request" ), added_index / page_length); |
1882 | count += 1; |
1883 | } else if (p_to_pos < 0) { |
1884 | count -= 1; |
1885 | if (page == max_page && (MAX(0, count - 1) / page_length != max_page)) { |
1886 | emit_signal(SNAME("page_change_request" ), max_page - 1); |
1887 | } |
1888 | } else if (p_to_pos == begin_array_index - 1) { |
1889 | emit_signal(SNAME("page_change_request" ), page - 1); |
1890 | } else if (p_to_pos > end_array_index) { |
1891 | emit_signal(SNAME("page_change_request" ), page + 1); |
1892 | } |
1893 | begin_array_index = page * page_length; |
1894 | end_array_index = MIN(count, (page + 1) * page_length); |
1895 | max_page = MAX(0, count - 1) / page_length; |
1896 | } |
1897 | |
1898 | void EditorInspectorArray::_clear_array() { |
1899 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
1900 | undo_redo->create_action(vformat(TTR("Clear Property Array with Prefix %s" ), array_element_prefix)); |
1901 | if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { |
1902 | for (int i = count - 1; i >= 0; i--) { |
1903 | // Call the function. |
1904 | Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name()); |
1905 | if (move_function.is_valid()) { |
1906 | Variant args[] = { undo_redo, object, array_element_prefix, i, -1 }; |
1907 | const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; |
1908 | Variant return_value; |
1909 | Callable::CallError call_error; |
1910 | move_function.callp(args_p, 5, return_value, call_error); |
1911 | } else { |
1912 | WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function" , object->get_class_name())); |
1913 | } |
1914 | } |
1915 | } else if (mode == MODE_USE_COUNT_PROPERTY) { |
1916 | List<PropertyInfo> object_property_list; |
1917 | object->get_property_list(&object_property_list); |
1918 | |
1919 | Array properties_as_array = _extract_properties_as_array(object_property_list); |
1920 | properties_as_array.resize(count); |
1921 | |
1922 | // For undoing things |
1923 | undo_redo->add_undo_property(object, count_property, count); |
1924 | for (int i = 0; i < (int)properties_as_array.size(); i++) { |
1925 | Dictionary d = Dictionary(properties_as_array[i]); |
1926 | Array keys = d.keys(); |
1927 | for (int j = 0; j < keys.size(); j++) { |
1928 | String key = keys[j]; |
1929 | undo_redo->add_undo_property(object, vformat(key, i), d[key]); |
1930 | } |
1931 | } |
1932 | |
1933 | // Change the array size then set the properties. |
1934 | undo_redo->add_do_property(object, count_property, 0); |
1935 | } |
1936 | undo_redo->commit_action(); |
1937 | |
1938 | // Handle page change and update counts. |
1939 | emit_signal(SNAME("page_change_request" ), 0); |
1940 | count = 0; |
1941 | begin_array_index = 0; |
1942 | end_array_index = 0; |
1943 | max_page = 0; |
1944 | } |
1945 | |
1946 | void EditorInspectorArray::_resize_array(int p_size) { |
1947 | ERR_FAIL_COND(p_size < 0); |
1948 | if (p_size == count) { |
1949 | return; |
1950 | } |
1951 | |
1952 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
1953 | undo_redo->create_action(vformat(TTR("Resize Property Array with Prefix %s" ), array_element_prefix)); |
1954 | if (p_size > count) { |
1955 | if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { |
1956 | for (int i = count; i < p_size; i++) { |
1957 | // Call the function. |
1958 | Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name()); |
1959 | if (move_function.is_valid()) { |
1960 | Variant args[] = { undo_redo, object, array_element_prefix, -1, -1 }; |
1961 | const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; |
1962 | Variant return_value; |
1963 | Callable::CallError call_error; |
1964 | move_function.callp(args_p, 5, return_value, call_error); |
1965 | } else { |
1966 | WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function" , object->get_class_name())); |
1967 | } |
1968 | } |
1969 | } else if (mode == MODE_USE_COUNT_PROPERTY) { |
1970 | undo_redo->add_undo_property(object, count_property, count); |
1971 | undo_redo->add_do_property(object, count_property, p_size); |
1972 | } |
1973 | } else { |
1974 | if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { |
1975 | for (int i = count - 1; i > p_size - 1; i--) { |
1976 | // Call the function. |
1977 | Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name()); |
1978 | if (move_function.is_valid()) { |
1979 | Variant args[] = { undo_redo, object, array_element_prefix, i, -1 }; |
1980 | const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] }; |
1981 | Variant return_value; |
1982 | Callable::CallError call_error; |
1983 | move_function.callp(args_p, 5, return_value, call_error); |
1984 | } else { |
1985 | WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function" , object->get_class_name())); |
1986 | } |
1987 | } |
1988 | } else if (mode == MODE_USE_COUNT_PROPERTY) { |
1989 | List<PropertyInfo> object_property_list; |
1990 | object->get_property_list(&object_property_list); |
1991 | |
1992 | Array properties_as_array = _extract_properties_as_array(object_property_list); |
1993 | properties_as_array.resize(count); |
1994 | |
1995 | // For undoing things |
1996 | undo_redo->add_undo_property(object, count_property, count); |
1997 | for (int i = count - 1; i > p_size - 1; i--) { |
1998 | Dictionary d = Dictionary(properties_as_array[i]); |
1999 | Array keys = d.keys(); |
2000 | for (int j = 0; j < keys.size(); j++) { |
2001 | String key = keys[j]; |
2002 | undo_redo->add_undo_property(object, vformat(key, i), d[key]); |
2003 | } |
2004 | } |
2005 | |
2006 | // Change the array size then set the properties. |
2007 | undo_redo->add_do_property(object, count_property, p_size); |
2008 | } |
2009 | } |
2010 | undo_redo->commit_action(); |
2011 | |
2012 | // Handle page change and update counts. |
2013 | emit_signal(SNAME("page_change_request" ), 0); |
2014 | /* |
2015 | count = 0; |
2016 | begin_array_index = 0; |
2017 | end_array_index = 0; |
2018 | max_page = 0; |
2019 | */ |
2020 | } |
2021 | |
2022 | Array EditorInspectorArray::(const List<PropertyInfo> &p_list) { |
2023 | Array output; |
2024 | |
2025 | for (const PropertyInfo &pi : p_list) { |
2026 | if (pi.name.begins_with(array_element_prefix)) { |
2027 | String str = pi.name.trim_prefix(array_element_prefix); |
2028 | |
2029 | int to_char_index = 0; |
2030 | while (to_char_index < str.length()) { |
2031 | if (!is_digit(str[to_char_index])) { |
2032 | break; |
2033 | } |
2034 | to_char_index++; |
2035 | } |
2036 | if (to_char_index > 0) { |
2037 | int array_index = str.left(to_char_index).to_int(); |
2038 | Error error = OK; |
2039 | if (array_index >= output.size()) { |
2040 | error = output.resize(array_index + 1); |
2041 | } |
2042 | if (error == OK) { |
2043 | String format_string = String(array_element_prefix) + "%d" + str.substr(to_char_index); |
2044 | Dictionary dict = output[array_index]; |
2045 | dict[format_string] = object->get(pi.name); |
2046 | output[array_index] = dict; |
2047 | } else { |
2048 | WARN_PRINT(vformat("Array element %s has an index too high. Array allocation failed." , pi.name)); |
2049 | } |
2050 | } |
2051 | } |
2052 | } |
2053 | return output; |
2054 | } |
2055 | |
2056 | int EditorInspectorArray::_drop_position() const { |
2057 | for (int i = 0; i < (int)array_elements.size(); i++) { |
2058 | const ArrayElement &ae = array_elements[i]; |
2059 | |
2060 | Size2 size = ae.panel->get_size(); |
2061 | Vector2 mp = ae.panel->get_local_mouse_position(); |
2062 | |
2063 | if (Rect2(Vector2(), size).has_point(mp)) { |
2064 | if (mp.y < size.y / 2) { |
2065 | return i; |
2066 | } else { |
2067 | return i + 1; |
2068 | } |
2069 | } |
2070 | } |
2071 | return -1; |
2072 | } |
2073 | |
2074 | void EditorInspectorArray::_resize_dialog_confirmed() { |
2075 | if (int(new_size_spin_box->get_value()) == count) { |
2076 | return; |
2077 | } |
2078 | |
2079 | resize_dialog->hide(); |
2080 | _resize_array(int(new_size_spin_box->get_value())); |
2081 | } |
2082 | |
2083 | void EditorInspectorArray::_new_size_spin_box_value_changed(float p_value) { |
2084 | resize_dialog->get_ok_button()->set_disabled(int(p_value) == count); |
2085 | } |
2086 | |
2087 | void EditorInspectorArray::_new_size_spin_box_text_submitted(String p_text) { |
2088 | _resize_dialog_confirmed(); |
2089 | } |
2090 | |
2091 | void EditorInspectorArray::_setup() { |
2092 | // Setup counts. |
2093 | count = _get_array_count(); |
2094 | begin_array_index = page * page_length; |
2095 | end_array_index = MIN(count, (page + 1) * page_length); |
2096 | max_page = MAX(0, count - 1) / page_length; |
2097 | array_elements.resize(MAX(0, end_array_index - begin_array_index)); |
2098 | if (page < 0 || page > max_page) { |
2099 | WARN_PRINT(vformat("Invalid page number %d" , page)); |
2100 | page = CLAMP(page, 0, max_page); |
2101 | } |
2102 | |
2103 | Ref<Font> numbers_font; |
2104 | int numbers_min_w = 0; |
2105 | |
2106 | if (numbered) { |
2107 | numbers_font = get_theme_font(SNAME("bold" ), EditorStringName(EditorFonts)); |
2108 | int digits_found = count; |
2109 | String test; |
2110 | while (digits_found) { |
2111 | test += "8" ; |
2112 | digits_found /= 10; |
2113 | } |
2114 | numbers_min_w = numbers_font->get_string_size(test).width; |
2115 | } |
2116 | |
2117 | for (int i = 0; i < (int)array_elements.size(); i++) { |
2118 | ArrayElement &ae = array_elements[i]; |
2119 | |
2120 | // Panel and its hbox. |
2121 | ae.panel = memnew(PanelContainer); |
2122 | ae.panel->set_focus_mode(FOCUS_ALL); |
2123 | ae.panel->set_mouse_filter(MOUSE_FILTER_PASS); |
2124 | SET_DRAG_FORWARDING_GCD(ae.panel, EditorInspectorArray); |
2125 | |
2126 | int element_position = begin_array_index + i; |
2127 | ae.panel->set_meta("index" , element_position); |
2128 | ae.panel->set_tooltip_text(vformat(TTR("Element %d: %s%d*" ), element_position, array_element_prefix, element_position)); |
2129 | ae.panel->connect("focus_entered" , callable_mp((CanvasItem *)ae.panel, &PanelContainer::queue_redraw)); |
2130 | ae.panel->connect("focus_exited" , callable_mp((CanvasItem *)ae.panel, &PanelContainer::queue_redraw)); |
2131 | ae.panel->connect("draw" , callable_mp(this, &EditorInspectorArray::_panel_draw).bind(i)); |
2132 | ae.panel->connect("gui_input" , callable_mp(this, &EditorInspectorArray::_panel_gui_input).bind(i)); |
2133 | ae.panel->add_theme_style_override(SNAME("panel" ), i % 2 ? odd_style : even_style); |
2134 | elements_vbox->add_child(ae.panel); |
2135 | |
2136 | ae.margin = memnew(MarginContainer); |
2137 | ae.margin->set_mouse_filter(MOUSE_FILTER_PASS); |
2138 | if (is_inside_tree()) { |
2139 | Size2 min_size = get_theme_stylebox(SNAME("Focus" ), EditorStringName(EditorStyles))->get_minimum_size(); |
2140 | ae.margin->add_theme_constant_override("margin_left" , min_size.x / 2); |
2141 | ae.margin->add_theme_constant_override("margin_top" , min_size.y / 2); |
2142 | ae.margin->add_theme_constant_override("margin_right" , min_size.x / 2); |
2143 | ae.margin->add_theme_constant_override("margin_bottom" , min_size.y / 2); |
2144 | } |
2145 | ae.panel->add_child(ae.margin); |
2146 | |
2147 | ae.hbox = memnew(HBoxContainer); |
2148 | ae.hbox->set_h_size_flags(SIZE_EXPAND_FILL); |
2149 | ae.hbox->set_v_size_flags(SIZE_EXPAND_FILL); |
2150 | ae.margin->add_child(ae.hbox); |
2151 | |
2152 | // Move button. |
2153 | if (movable) { |
2154 | VBoxContainer *move_vbox = memnew(VBoxContainer); |
2155 | move_vbox->set_v_size_flags(SIZE_EXPAND_FILL); |
2156 | move_vbox->set_alignment(BoxContainer::ALIGNMENT_CENTER); |
2157 | ae.hbox->add_child(move_vbox); |
2158 | |
2159 | if (element_position > 0) { |
2160 | ae.move_up = memnew(Button); |
2161 | ae.move_up->set_icon(get_editor_theme_icon(SNAME("MoveUp" ))); |
2162 | ae.move_up->connect("pressed" , callable_mp(this, &EditorInspectorArray::_move_element).bind(element_position, element_position - 1)); |
2163 | move_vbox->add_child(ae.move_up); |
2164 | } |
2165 | |
2166 | ae.move_texture_rect = memnew(TextureRect); |
2167 | ae.move_texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); |
2168 | ae.move_texture_rect->set_default_cursor_shape(Control::CURSOR_MOVE); |
2169 | |
2170 | if (is_inside_tree()) { |
2171 | ae.move_texture_rect->set_texture(get_editor_theme_icon(SNAME("TripleBar" ))); |
2172 | } |
2173 | move_vbox->add_child(ae.move_texture_rect); |
2174 | |
2175 | if (element_position < _get_array_count() - 1) { |
2176 | ae.move_down = memnew(Button); |
2177 | ae.move_down->set_icon(get_editor_theme_icon(SNAME("MoveDown" ))); |
2178 | ae.move_down->connect("pressed" , callable_mp(this, &EditorInspectorArray::_move_element).bind(element_position, element_position + 2)); |
2179 | move_vbox->add_child(ae.move_down); |
2180 | } |
2181 | } |
2182 | |
2183 | if (numbered) { |
2184 | ae.number = memnew(Label); |
2185 | ae.number->add_theme_font_override("font" , numbers_font); |
2186 | ae.number->set_custom_minimum_size(Size2(numbers_min_w, 0)); |
2187 | ae.number->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); |
2188 | ae.number->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); |
2189 | ae.number->set_text(itos(element_position)); |
2190 | ae.hbox->add_child(ae.number); |
2191 | } |
2192 | |
2193 | // Right vbox. |
2194 | ae.vbox = memnew(VBoxContainer); |
2195 | ae.vbox->set_h_size_flags(SIZE_EXPAND_FILL); |
2196 | ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL); |
2197 | ae.hbox->add_child(ae.vbox); |
2198 | |
2199 | ae.erase = memnew(Button); |
2200 | ae.erase->set_icon(get_editor_theme_icon(SNAME("Remove" ))); |
2201 | ae.erase->set_v_size_flags(SIZE_SHRINK_CENTER); |
2202 | ae.erase->connect("pressed" , callable_mp(this, &EditorInspectorArray::_remove_item).bind(element_position)); |
2203 | ae.hbox->add_child(ae.erase); |
2204 | } |
2205 | |
2206 | // Hide/show the add button. |
2207 | add_button->set_visible(page == max_page); |
2208 | |
2209 | // Add paginator if there's more than 1 page. |
2210 | if (max_page > 0) { |
2211 | EditorPaginator *paginator = memnew(EditorPaginator); |
2212 | paginator->update(page, max_page); |
2213 | paginator->connect("page_changed" , callable_mp(this, &EditorInspectorArray::_paginator_page_changed)); |
2214 | vbox->add_child(paginator); |
2215 | } |
2216 | } |
2217 | |
2218 | void EditorInspectorArray::_remove_item(int p_index) { |
2219 | _move_element(p_index, -1); |
2220 | } |
2221 | |
2222 | Variant EditorInspectorArray::get_drag_data_fw(const Point2 &p_point, Control *p_from) { |
2223 | if (!movable) { |
2224 | return Variant(); |
2225 | } |
2226 | int index = p_from->get_meta("index" ); |
2227 | Dictionary dict; |
2228 | dict["type" ] = "property_array_element" ; |
2229 | dict["property_array_prefix" ] = array_element_prefix; |
2230 | dict["index" ] = index; |
2231 | |
2232 | return dict; |
2233 | } |
2234 | |
2235 | void EditorInspectorArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { |
2236 | Dictionary dict = p_data; |
2237 | |
2238 | int to_drop = dict["index" ]; |
2239 | int drop_position = _drop_position(); |
2240 | if (drop_position < 0) { |
2241 | return; |
2242 | } |
2243 | _move_element(to_drop, begin_array_index + drop_position); |
2244 | } |
2245 | |
2246 | bool EditorInspectorArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { |
2247 | if (!movable || read_only) { |
2248 | return false; |
2249 | } |
2250 | // First, update drawing. |
2251 | control_dropping->queue_redraw(); |
2252 | |
2253 | if (p_data.get_type() != Variant::DICTIONARY) { |
2254 | return false; |
2255 | } |
2256 | Dictionary dict = p_data; |
2257 | int drop_position = _drop_position(); |
2258 | if (!dict.has("type" ) || dict["type" ] != "property_array_element" || String(dict["property_array_prefix" ]) != array_element_prefix || drop_position < 0) { |
2259 | return false; |
2260 | } |
2261 | |
2262 | // Check in dropping at the given index does indeed move the item. |
2263 | int moved_array_index = (int)dict["index" ]; |
2264 | int drop_array_index = begin_array_index + drop_position; |
2265 | |
2266 | return drop_array_index != moved_array_index && drop_array_index - 1 != moved_array_index; |
2267 | } |
2268 | |
2269 | void EditorInspectorArray::_notification(int p_what) { |
2270 | switch (p_what) { |
2271 | case NOTIFICATION_ENTER_TREE: |
2272 | case NOTIFICATION_THEME_CHANGED: { |
2273 | Color color = get_theme_color(SNAME("dark_color_1" ), EditorStringName(Editor)); |
2274 | odd_style->set_bg_color(color.darkened(-0.08)); |
2275 | even_style->set_bg_color(color.darkened(0.08)); |
2276 | |
2277 | for (ArrayElement &ae : array_elements) { |
2278 | if (ae.move_texture_rect) { |
2279 | ae.move_texture_rect->set_texture(get_editor_theme_icon(SNAME("TripleBar" ))); |
2280 | } |
2281 | if (ae.move_up) { |
2282 | ae.move_up->set_icon(get_editor_theme_icon(SNAME("MoveUp" ))); |
2283 | } |
2284 | if (ae.move_down) { |
2285 | ae.move_down->set_icon(get_editor_theme_icon(SNAME("MoveDown" ))); |
2286 | } |
2287 | Size2 min_size = get_theme_stylebox(SNAME("Focus" ), EditorStringName(EditorStyles))->get_minimum_size(); |
2288 | ae.margin->add_theme_constant_override("margin_left" , min_size.x / 2); |
2289 | ae.margin->add_theme_constant_override("margin_top" , min_size.y / 2); |
2290 | ae.margin->add_theme_constant_override("margin_right" , min_size.x / 2); |
2291 | ae.margin->add_theme_constant_override("margin_bottom" , min_size.y / 2); |
2292 | |
2293 | if (ae.erase) { |
2294 | ae.erase->set_icon(get_editor_theme_icon(SNAME("Remove" ))); |
2295 | } |
2296 | } |
2297 | |
2298 | add_button->set_icon(get_editor_theme_icon(SNAME("Add" ))); |
2299 | update_minimum_size(); |
2300 | } break; |
2301 | |
2302 | case NOTIFICATION_DRAG_BEGIN: { |
2303 | Dictionary dict = get_viewport()->gui_get_drag_data(); |
2304 | if (dict.has("type" ) && dict["type" ] == "property_array_element" && String(dict["property_array_prefix" ]) == array_element_prefix) { |
2305 | dropping = true; |
2306 | control_dropping->queue_redraw(); |
2307 | } |
2308 | } break; |
2309 | |
2310 | case NOTIFICATION_DRAG_END: { |
2311 | if (dropping) { |
2312 | dropping = false; |
2313 | control_dropping->queue_redraw(); |
2314 | } |
2315 | } break; |
2316 | } |
2317 | } |
2318 | |
2319 | void EditorInspectorArray::_bind_methods() { |
2320 | ADD_SIGNAL(MethodInfo("page_change_request" )); |
2321 | } |
2322 | |
2323 | void EditorInspectorArray::setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_numbered, int p_page_length, const String &p_add_item_text) { |
2324 | count_property = "" ; |
2325 | mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION; |
2326 | array_element_prefix = p_array_element_prefix; |
2327 | page = p_page; |
2328 | movable = p_movable; |
2329 | page_length = p_page_length; |
2330 | numbered = p_numbered; |
2331 | |
2332 | EditorInspectorSection::setup(String(p_array_element_prefix) + "_array" , p_label, p_object, p_bg_color, p_foldable, 0); |
2333 | |
2334 | _setup(); |
2335 | } |
2336 | |
2337 | void EditorInspectorArray::setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_numbered, int p_page_length, const String &p_add_item_text, const String &p_swap_method) { |
2338 | count_property = p_count_property; |
2339 | mode = MODE_USE_COUNT_PROPERTY; |
2340 | array_element_prefix = p_array_element_prefix; |
2341 | page = p_page; |
2342 | movable = p_movable; |
2343 | page_length = p_page_length; |
2344 | numbered = p_numbered; |
2345 | swap_method = p_swap_method; |
2346 | |
2347 | add_button->set_text(p_add_item_text); |
2348 | EditorInspectorSection::setup(String(count_property) + "_array" , p_label, p_object, p_bg_color, p_foldable, 0); |
2349 | |
2350 | _setup(); |
2351 | } |
2352 | |
2353 | VBoxContainer *EditorInspectorArray::get_vbox(int p_index) { |
2354 | if (p_index >= begin_array_index && p_index < end_array_index) { |
2355 | return array_elements[p_index - begin_array_index].vbox; |
2356 | } else if (p_index < 0) { |
2357 | return vbox; |
2358 | } else { |
2359 | return nullptr; |
2360 | } |
2361 | } |
2362 | |
2363 | EditorInspectorArray::EditorInspectorArray(bool p_read_only) { |
2364 | read_only = p_read_only; |
2365 | |
2366 | set_mouse_filter(Control::MOUSE_FILTER_STOP); |
2367 | |
2368 | odd_style.instantiate(); |
2369 | even_style.instantiate(); |
2370 | |
2371 | rmb_popup = memnew(PopupMenu); |
2372 | rmb_popup->add_item(TTR("Move Up" ), OPTION_MOVE_UP); |
2373 | rmb_popup->add_item(TTR("Move Down" ), OPTION_MOVE_DOWN); |
2374 | rmb_popup->add_separator(); |
2375 | rmb_popup->add_item(TTR("Insert New Before" ), OPTION_NEW_BEFORE); |
2376 | rmb_popup->add_item(TTR("Insert New After" ), OPTION_NEW_AFTER); |
2377 | rmb_popup->add_separator(); |
2378 | rmb_popup->add_item(TTR("Remove" ), OPTION_REMOVE); |
2379 | rmb_popup->add_separator(); |
2380 | rmb_popup->add_item(TTR("Clear Array" ), OPTION_CLEAR_ARRAY); |
2381 | rmb_popup->add_item(TTR("Resize Array..." ), OPTION_RESIZE_ARRAY); |
2382 | rmb_popup->connect("id_pressed" , callable_mp(this, &EditorInspectorArray::_rmb_popup_id_pressed)); |
2383 | add_child(rmb_popup); |
2384 | |
2385 | elements_vbox = memnew(VBoxContainer); |
2386 | elements_vbox->add_theme_constant_override("separation" , 0); |
2387 | vbox->add_child(elements_vbox); |
2388 | |
2389 | add_button = EditorInspector::create_inspector_action_button(TTR("Add Element" )); |
2390 | add_button->connect("pressed" , callable_mp(this, &EditorInspectorArray::_add_button_pressed)); |
2391 | add_button->set_disabled(read_only); |
2392 | vbox->add_child(add_button); |
2393 | |
2394 | control_dropping = memnew(Control); |
2395 | control_dropping->connect("draw" , callable_mp(this, &EditorInspectorArray::_control_dropping_draw)); |
2396 | control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); |
2397 | add_child(control_dropping); |
2398 | |
2399 | resize_dialog = memnew(AcceptDialog); |
2400 | resize_dialog->set_title(TTRC("Resize Array" )); |
2401 | resize_dialog->add_cancel_button(); |
2402 | resize_dialog->connect("confirmed" , callable_mp(this, &EditorInspectorArray::_resize_dialog_confirmed)); |
2403 | add_child(resize_dialog); |
2404 | |
2405 | VBoxContainer *resize_dialog_vbox = memnew(VBoxContainer); |
2406 | resize_dialog->add_child(resize_dialog_vbox); |
2407 | |
2408 | new_size_spin_box = memnew(SpinBox); |
2409 | new_size_spin_box->set_max(16384); |
2410 | new_size_spin_box->connect("value_changed" , callable_mp(this, &EditorInspectorArray::_new_size_spin_box_value_changed)); |
2411 | new_size_spin_box->get_line_edit()->connect("text_submitted" , callable_mp(this, &EditorInspectorArray::_new_size_spin_box_text_submitted)); |
2412 | new_size_spin_box->set_editable(!read_only); |
2413 | resize_dialog_vbox->add_margin_child(TTRC("New Size:" ), new_size_spin_box); |
2414 | |
2415 | vbox->connect("visibility_changed" , callable_mp(this, &EditorInspectorArray::_vbox_visibility_changed)); |
2416 | } |
2417 | |
2418 | //////////////////////////////////////////////// |
2419 | //////////////////////////////////////////////// |
2420 | |
2421 | void EditorPaginator::_first_page_button_pressed() { |
2422 | emit_signal("page_changed" , 0); |
2423 | } |
2424 | |
2425 | void EditorPaginator::_prev_page_button_pressed() { |
2426 | emit_signal("page_changed" , MAX(0, page - 1)); |
2427 | } |
2428 | |
2429 | void EditorPaginator::_page_line_edit_text_submitted(String p_text) { |
2430 | if (p_text.is_valid_int()) { |
2431 | int new_page = p_text.to_int() - 1; |
2432 | new_page = MIN(MAX(0, new_page), max_page); |
2433 | page_line_edit->set_text(Variant(new_page)); |
2434 | emit_signal("page_changed" , new_page); |
2435 | } else { |
2436 | page_line_edit->set_text(Variant(page)); |
2437 | } |
2438 | } |
2439 | |
2440 | void EditorPaginator::_next_page_button_pressed() { |
2441 | emit_signal("page_changed" , MIN(max_page, page + 1)); |
2442 | } |
2443 | |
2444 | void EditorPaginator::_last_page_button_pressed() { |
2445 | emit_signal("page_changed" , max_page); |
2446 | } |
2447 | |
2448 | void EditorPaginator::update(int p_page, int p_max_page) { |
2449 | page = p_page; |
2450 | max_page = p_max_page; |
2451 | |
2452 | // Update buttons. |
2453 | first_page_button->set_disabled(page == 0); |
2454 | prev_page_button->set_disabled(page == 0); |
2455 | next_page_button->set_disabled(page == max_page); |
2456 | last_page_button->set_disabled(page == max_page); |
2457 | |
2458 | // Update page number and page count. |
2459 | page_line_edit->set_text(vformat("%d" , page + 1)); |
2460 | page_count_label->set_text(vformat("/ %d" , max_page + 1)); |
2461 | } |
2462 | |
2463 | void EditorPaginator::_notification(int p_what) { |
2464 | switch (p_what) { |
2465 | case NOTIFICATION_ENTER_TREE: |
2466 | case NOTIFICATION_THEME_CHANGED: { |
2467 | first_page_button->set_icon(get_editor_theme_icon(SNAME("PageFirst" ))); |
2468 | prev_page_button->set_icon(get_editor_theme_icon(SNAME("PagePrevious" ))); |
2469 | next_page_button->set_icon(get_editor_theme_icon(SNAME("PageNext" ))); |
2470 | last_page_button->set_icon(get_editor_theme_icon(SNAME("PageLast" ))); |
2471 | } break; |
2472 | } |
2473 | } |
2474 | |
2475 | void EditorPaginator::_bind_methods() { |
2476 | ADD_SIGNAL(MethodInfo("page_changed" , PropertyInfo(Variant::INT, "page" ))); |
2477 | } |
2478 | |
2479 | EditorPaginator::EditorPaginator() { |
2480 | set_h_size_flags(SIZE_EXPAND_FILL); |
2481 | set_alignment(ALIGNMENT_CENTER); |
2482 | |
2483 | first_page_button = memnew(Button); |
2484 | first_page_button->set_flat(true); |
2485 | first_page_button->connect("pressed" , callable_mp(this, &EditorPaginator::_first_page_button_pressed)); |
2486 | add_child(first_page_button); |
2487 | |
2488 | prev_page_button = memnew(Button); |
2489 | prev_page_button->set_flat(true); |
2490 | prev_page_button->connect("pressed" , callable_mp(this, &EditorPaginator::_prev_page_button_pressed)); |
2491 | add_child(prev_page_button); |
2492 | |
2493 | page_line_edit = memnew(LineEdit); |
2494 | page_line_edit->connect("text_submitted" , callable_mp(this, &EditorPaginator::_page_line_edit_text_submitted)); |
2495 | page_line_edit->add_theme_constant_override("minimum_character_width" , 2); |
2496 | add_child(page_line_edit); |
2497 | |
2498 | page_count_label = memnew(Label); |
2499 | add_child(page_count_label); |
2500 | |
2501 | next_page_button = memnew(Button); |
2502 | next_page_button->set_flat(true); |
2503 | next_page_button->connect("pressed" , callable_mp(this, &EditorPaginator::_next_page_button_pressed)); |
2504 | add_child(next_page_button); |
2505 | |
2506 | last_page_button = memnew(Button); |
2507 | last_page_button->set_flat(true); |
2508 | last_page_button->connect("pressed" , callable_mp(this, &EditorPaginator::_last_page_button_pressed)); |
2509 | add_child(last_page_button); |
2510 | } |
2511 | |
2512 | //////////////////////////////////////////////// |
2513 | //////////////////////////////////////////////// |
2514 | |
2515 | Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS]; |
2516 | int EditorInspector::inspector_plugin_count = 0; |
2517 | |
2518 | EditorProperty *EditorInspector::instantiate_property_editor(Object *p_object, const Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { |
2519 | for (int i = inspector_plugin_count - 1; i >= 0; i--) { |
2520 | inspector_plugins[i]->parse_property(p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide); |
2521 | if (inspector_plugins[i]->added_editors.size()) { |
2522 | for (int j = 1; j < inspector_plugins[i]->added_editors.size(); j++) { //only keep first one |
2523 | memdelete(inspector_plugins[i]->added_editors[j].property_editor); |
2524 | } |
2525 | |
2526 | EditorProperty *prop = Object::cast_to<EditorProperty>(inspector_plugins[i]->added_editors[0].property_editor); |
2527 | if (prop) { |
2528 | inspector_plugins[i]->added_editors.clear(); |
2529 | return prop; |
2530 | } else { |
2531 | memdelete(inspector_plugins[i]->added_editors[0].property_editor); |
2532 | inspector_plugins[i]->added_editors.clear(); |
2533 | } |
2534 | } |
2535 | } |
2536 | return nullptr; |
2537 | } |
2538 | |
2539 | void EditorInspector::add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) { |
2540 | ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS); |
2541 | |
2542 | for (int i = 0; i < inspector_plugin_count; i++) { |
2543 | if (inspector_plugins[i] == p_plugin) { |
2544 | return; //already exists |
2545 | } |
2546 | } |
2547 | inspector_plugins[inspector_plugin_count++] = p_plugin; |
2548 | } |
2549 | |
2550 | void EditorInspector::remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) { |
2551 | ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS); |
2552 | |
2553 | int idx = -1; |
2554 | for (int i = 0; i < inspector_plugin_count; i++) { |
2555 | if (inspector_plugins[i] == p_plugin) { |
2556 | idx = i; |
2557 | break; |
2558 | } |
2559 | } |
2560 | |
2561 | ERR_FAIL_COND_MSG(idx == -1, "Trying to remove nonexistent inspector plugin." ); |
2562 | for (int i = idx; i < inspector_plugin_count - 1; i++) { |
2563 | inspector_plugins[i] = inspector_plugins[i + 1]; |
2564 | } |
2565 | inspector_plugins[inspector_plugin_count - 1] = Ref<EditorInspectorPlugin>(); |
2566 | |
2567 | inspector_plugin_count--; |
2568 | } |
2569 | |
2570 | void EditorInspector::cleanup_plugins() { |
2571 | for (int i = 0; i < inspector_plugin_count; i++) { |
2572 | inspector_plugins[i].unref(); |
2573 | } |
2574 | inspector_plugin_count = 0; |
2575 | } |
2576 | |
2577 | Button *EditorInspector::create_inspector_action_button(const String &p_text) { |
2578 | Button *button = memnew(Button); |
2579 | button->set_text(p_text); |
2580 | button->set_theme_type_variation(SNAME("InspectorActionButton" )); |
2581 | button->set_h_size_flags(SIZE_SHRINK_CENTER); |
2582 | return button; |
2583 | } |
2584 | |
2585 | bool EditorInspector::is_main_editor_inspector() const { |
2586 | return InspectorDock::get_singleton() && InspectorDock::get_inspector_singleton() == this; |
2587 | } |
2588 | |
2589 | String EditorInspector::get_selected_path() const { |
2590 | return property_selected; |
2591 | } |
2592 | |
2593 | void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref<EditorInspectorPlugin> ped) { |
2594 | for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) { |
2595 | EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor); |
2596 | current_vbox->add_child(F.property_editor); |
2597 | |
2598 | if (ep) { |
2599 | ep->object = object; |
2600 | ep->connect("property_changed" , callable_mp(this, &EditorInspector::_property_changed).bind(false)); |
2601 | ep->connect("property_keyed" , callable_mp(this, &EditorInspector::_property_keyed)); |
2602 | ep->connect("property_deleted" , callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED); |
2603 | ep->connect("property_keyed_with_value" , callable_mp(this, &EditorInspector::_property_keyed_with_value)); |
2604 | ep->connect("property_checked" , callable_mp(this, &EditorInspector::_property_checked)); |
2605 | ep->connect("property_pinned" , callable_mp(this, &EditorInspector::_property_pinned)); |
2606 | ep->connect("selected" , callable_mp(this, &EditorInspector::_property_selected)); |
2607 | ep->connect("multiple_properties_changed" , callable_mp(this, &EditorInspector::_multiple_properties_changed)); |
2608 | ep->connect("resource_selected" , callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED); |
2609 | ep->connect("object_id_selected" , callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED); |
2610 | |
2611 | if (F.properties.size()) { |
2612 | if (F.properties.size() == 1) { |
2613 | //since it's one, associate: |
2614 | ep->property = F.properties[0]; |
2615 | ep->property_path = property_prefix + F.properties[0]; |
2616 | ep->property_usage = 0; |
2617 | } |
2618 | |
2619 | if (!F.label.is_empty()) { |
2620 | ep->set_label(F.label); |
2621 | } |
2622 | |
2623 | for (int i = 0; i < F.properties.size(); i++) { |
2624 | String prop = F.properties[i]; |
2625 | |
2626 | if (!editor_property_map.has(prop)) { |
2627 | editor_property_map[prop] = List<EditorProperty *>(); |
2628 | } |
2629 | editor_property_map[prop].push_back(ep); |
2630 | } |
2631 | } |
2632 | |
2633 | if (p_section) { |
2634 | ep->connect("property_can_revert_changed" , callable_mp(p_section, &EditorInspectorSection::property_can_revert_changed)); |
2635 | } |
2636 | |
2637 | ep->set_read_only(read_only); |
2638 | ep->update_property(); |
2639 | ep->_update_pin_flags(); |
2640 | ep->update_editor_property_status(); |
2641 | ep->set_deletable(deletable_properties); |
2642 | ep->update_cache(); |
2643 | } |
2644 | } |
2645 | ped->added_editors.clear(); |
2646 | } |
2647 | |
2648 | bool EditorInspector::_is_property_disabled_by_feature_profile(const StringName &p_property) { |
2649 | Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile(); |
2650 | if (profile.is_null()) { |
2651 | return false; |
2652 | } |
2653 | |
2654 | StringName class_name = object->get_class(); |
2655 | |
2656 | while (class_name != StringName()) { |
2657 | if (profile->is_class_property_disabled(class_name, p_property)) { |
2658 | return true; |
2659 | } |
2660 | if (profile->is_class_disabled(class_name)) { |
2661 | //won't see properties of a disabled class |
2662 | return true; |
2663 | } |
2664 | class_name = ClassDB::get_parent_class(class_name); |
2665 | } |
2666 | |
2667 | return false; |
2668 | } |
2669 | |
2670 | void EditorInspector::update_tree() { |
2671 | // Store currently selected and focused elements to restore after the update. |
2672 | // TODO: Can be useful to store more context for the focusable, such as the caret position in LineEdit. |
2673 | StringName current_selected = property_selected; |
2674 | int current_focusable = -1; |
2675 | |
2676 | if (property_focusable != -1) { |
2677 | // Check that focusable is actually focusable. |
2678 | bool restore_focus = false; |
2679 | Control *focused = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; |
2680 | if (focused) { |
2681 | Node *parent = focused->get_parent(); |
2682 | while (parent) { |
2683 | EditorInspector *inspector = Object::cast_to<EditorInspector>(parent); |
2684 | if (inspector) { |
2685 | restore_focus = inspector == this; // May be owned by another inspector. |
2686 | break; // Exit after the first inspector is found, since there may be nested ones. |
2687 | } |
2688 | parent = parent->get_parent(); |
2689 | } |
2690 | } |
2691 | |
2692 | if (restore_focus) { |
2693 | current_focusable = property_focusable; |
2694 | } |
2695 | } |
2696 | |
2697 | // Only hide plugins if we are not editing any object. |
2698 | // This should be handled outside of the update_tree call anyway (see EditorInspector::edit), but might as well keep it safe. |
2699 | _clear(!object); |
2700 | |
2701 | if (!object) { |
2702 | return; |
2703 | } |
2704 | |
2705 | List<Ref<EditorInspectorPlugin>> valid_plugins; |
2706 | |
2707 | for (int i = inspector_plugin_count - 1; i >= 0; i--) { //start by last, so lastly added can override newly added |
2708 | if (!inspector_plugins[i]->can_handle(object)) { |
2709 | continue; |
2710 | } |
2711 | valid_plugins.push_back(inspector_plugins[i]); |
2712 | } |
2713 | |
2714 | // Decide if properties should be drawn with the warning color (yellow), |
2715 | // or if the whole object should be considered read-only. |
2716 | bool draw_warning = false; |
2717 | bool all_read_only = false; |
2718 | if (is_inside_tree()) { |
2719 | if (object->has_method("_is_read_only" )) { |
2720 | all_read_only = object->call("_is_read_only" ); |
2721 | } |
2722 | |
2723 | Node *nod = Object::cast_to<Node>(object); |
2724 | Node *es = EditorNode::get_singleton()->get_edited_scene(); |
2725 | if (nod && es != nod && nod->get_owner() != es) { |
2726 | // Draw in warning color edited nodes that are not in the currently edited scene, |
2727 | // as changes may be lost in the future. |
2728 | draw_warning = true; |
2729 | } else { |
2730 | if (!all_read_only) { |
2731 | Resource *res = Object::cast_to<Resource>(object); |
2732 | if (res) { |
2733 | all_read_only = EditorNode::get_singleton()->is_resource_read_only(res); |
2734 | } |
2735 | } |
2736 | } |
2737 | } |
2738 | |
2739 | String filter = search_box ? search_box->get_text() : "" ; |
2740 | String group; |
2741 | String group_base; |
2742 | String subgroup; |
2743 | String subgroup_base; |
2744 | int section_depth = 0; |
2745 | VBoxContainer *category_vbox = nullptr; |
2746 | |
2747 | List<PropertyInfo> plist; |
2748 | object->get_property_list(&plist, true); |
2749 | |
2750 | HashMap<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path; |
2751 | HashMap<String, EditorInspectorArray *> editor_inspector_array_per_prefix; |
2752 | |
2753 | Color sscolor = get_theme_color(SNAME("prop_subsection" ), EditorStringName(Editor)); |
2754 | |
2755 | // Get the lists of editors to add the beginning. |
2756 | for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { |
2757 | ped->parse_begin(object); |
2758 | _parse_added_editors(main_vbox, nullptr, ped); |
2759 | } |
2760 | |
2761 | StringName doc_name; |
2762 | |
2763 | // Get the lists of editors for properties. |
2764 | for (List<PropertyInfo>::Element *E_property = plist.front(); E_property; E_property = E_property->next()) { |
2765 | PropertyInfo &p = E_property->get(); |
2766 | |
2767 | if (p.usage & PROPERTY_USAGE_SUBGROUP) { |
2768 | // Setup a property sub-group. |
2769 | subgroup = p.name; |
2770 | |
2771 | Vector<String> hint_parts = p.hint_string.split("," ); |
2772 | subgroup_base = hint_parts[0]; |
2773 | if (hint_parts.size() > 1) { |
2774 | section_depth = hint_parts[1].to_int(); |
2775 | } else { |
2776 | section_depth = 0; |
2777 | } |
2778 | |
2779 | continue; |
2780 | |
2781 | } else if (p.usage & PROPERTY_USAGE_GROUP) { |
2782 | // Setup a property group. |
2783 | group = p.name; |
2784 | |
2785 | Vector<String> hint_parts = p.hint_string.split("," ); |
2786 | group_base = hint_parts[0]; |
2787 | if (hint_parts.size() > 1) { |
2788 | section_depth = hint_parts[1].to_int(); |
2789 | } else { |
2790 | section_depth = 0; |
2791 | } |
2792 | |
2793 | subgroup = "" ; |
2794 | subgroup_base = "" ; |
2795 | |
2796 | continue; |
2797 | |
2798 | } else if (p.usage & PROPERTY_USAGE_CATEGORY) { |
2799 | // Setup a property category. |
2800 | group = "" ; |
2801 | group_base = "" ; |
2802 | subgroup = "" ; |
2803 | subgroup_base = "" ; |
2804 | section_depth = 0; |
2805 | |
2806 | if (!show_categories) { |
2807 | continue; |
2808 | } |
2809 | |
2810 | // Hide the "MultiNodeEdit" category for MultiNodeEdit. |
2811 | if (Object::cast_to<MultiNodeEdit>(object) && p.name == "MultiNodeEdit" ) { |
2812 | continue; |
2813 | } |
2814 | |
2815 | // Iterate over remaining properties. If no properties in category, skip the category. |
2816 | List<PropertyInfo>::Element *N = E_property->next(); |
2817 | bool valid = true; |
2818 | while (N) { |
2819 | if (!N->get().name.begins_with("metadata/_" ) && N->get().usage & PROPERTY_USAGE_EDITOR && |
2820 | (!filter.is_empty() || !restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { |
2821 | break; |
2822 | } |
2823 | if (N->get().usage & PROPERTY_USAGE_CATEGORY) { |
2824 | valid = false; |
2825 | break; |
2826 | } |
2827 | N = N->next(); |
2828 | } |
2829 | if (!valid) { |
2830 | continue; // Empty, ignore it. |
2831 | } |
2832 | |
2833 | // Create an EditorInspectorCategory and add it to the inspector. |
2834 | EditorInspectorCategory *category = memnew(EditorInspectorCategory); |
2835 | main_vbox->add_child(category); |
2836 | category_vbox = nullptr; //reset |
2837 | |
2838 | // `hint_script` should contain a native class name or a script path. |
2839 | // Otherwise the category was probably added via `@export_category` or `_get_property_list()`. |
2840 | if (p.hint_string.is_empty()) { |
2841 | category->label = p.name; |
2842 | category->set_tooltip_text(p.name); |
2843 | continue; // Do not add an icon, do not change the current class (`doc_name`). |
2844 | } |
2845 | |
2846 | String type = p.name; |
2847 | String label = p.name; |
2848 | doc_name = p.name; |
2849 | |
2850 | // Use category's owner script to update some of its information. |
2851 | if (!EditorNode::get_editor_data().is_type_recognized(type) && p.hint_string.length() && ResourceLoader::exists(p.hint_string)) { |
2852 | Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script" ); |
2853 | if (scr.is_valid()) { |
2854 | StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path()); |
2855 | |
2856 | // Update the docs reference and the label based on the script. |
2857 | Vector<DocData::ClassDoc> docs = scr->get_documentation(); |
2858 | if (!docs.is_empty()) { |
2859 | // The documentation of a GDScript's main class is at the end of the array. |
2860 | // Hacky because this isn't necessarily always guaranteed. |
2861 | doc_name = docs[docs.size() - 1].name; |
2862 | } |
2863 | if (script_name != StringName()) { |
2864 | label = script_name; |
2865 | } |
2866 | |
2867 | // Find the icon corresponding to the script. |
2868 | if (script_name != StringName()) { |
2869 | category->icon = EditorNode::get_singleton()->get_class_icon(script_name); |
2870 | } else { |
2871 | category->icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object" ); |
2872 | } |
2873 | } |
2874 | } |
2875 | |
2876 | if (category->icon.is_null() && !type.is_empty()) { |
2877 | category->icon = EditorNode::get_singleton()->get_class_icon(type); |
2878 | } |
2879 | |
2880 | // Set the category label. |
2881 | category->label = label; |
2882 | category->doc_class_name = doc_name; |
2883 | |
2884 | if (use_doc_hints) { |
2885 | String descr = "" ; |
2886 | // Sets the category tooltip to show documentation. |
2887 | if (!class_descr_cache.has(doc_name)) { |
2888 | DocTools *dd = EditorHelp::get_doc_data(); |
2889 | HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(doc_name); |
2890 | if (E) { |
2891 | descr = E->value.brief_description; |
2892 | } |
2893 | if (ClassDB::class_exists(doc_name)) { |
2894 | descr = DTR(descr); // Do not translate the class description of scripts. |
2895 | class_descr_cache[doc_name] = descr; // Do not cache the class description of scripts. |
2896 | } |
2897 | } else { |
2898 | descr = class_descr_cache[doc_name]; |
2899 | } |
2900 | |
2901 | // `|` separator used in `make_help_bit()` for formatting. |
2902 | category->set_tooltip_text(p.name + "|" + descr); |
2903 | } |
2904 | |
2905 | // Add editors at the start of a category. |
2906 | for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { |
2907 | ped->parse_category(object, p.name); |
2908 | _parse_added_editors(main_vbox, nullptr, ped); |
2909 | } |
2910 | |
2911 | continue; |
2912 | |
2913 | } else if (p.name.begins_with("metadata/_" ) || !(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) || |
2914 | (filter.is_empty() && restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) { |
2915 | // Ignore properties that are not supposed to be in the inspector. |
2916 | continue; |
2917 | } |
2918 | |
2919 | if (p.name == "script" ) { |
2920 | // Script should go into its own category. |
2921 | category_vbox = nullptr; |
2922 | } |
2923 | |
2924 | if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) { |
2925 | // Do not show this property in low end gfx. |
2926 | continue; |
2927 | } |
2928 | |
2929 | if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector" )))) { |
2930 | // Hide script variables from inspector if required. |
2931 | continue; |
2932 | } |
2933 | |
2934 | if (p.name.begins_with("metadata/" ) && bool(object->call("_hide_metadata_from_inspector" ))) { |
2935 | // Hide metadata from inspector if required. |
2936 | continue; |
2937 | } |
2938 | |
2939 | // Get the path for property. |
2940 | String path = p.name; |
2941 | |
2942 | // First check if we have an array that fits the prefix. |
2943 | String array_prefix = "" ; |
2944 | int array_index = -1; |
2945 | for (KeyValue<String, EditorInspectorArray *> &E : editor_inspector_array_per_prefix) { |
2946 | if (p.name.begins_with(E.key) && E.key.length() > array_prefix.length()) { |
2947 | array_prefix = E.key; |
2948 | } |
2949 | } |
2950 | |
2951 | if (!array_prefix.is_empty()) { |
2952 | // If we have an array element, find the according index in array. |
2953 | String str = p.name.trim_prefix(array_prefix); |
2954 | int to_char_index = 0; |
2955 | while (to_char_index < str.length()) { |
2956 | if (!is_digit(str[to_char_index])) { |
2957 | break; |
2958 | } |
2959 | to_char_index++; |
2960 | } |
2961 | if (to_char_index > 0) { |
2962 | array_index = str.left(to_char_index).to_int(); |
2963 | } else { |
2964 | array_prefix = "" ; |
2965 | } |
2966 | } |
2967 | |
2968 | if (!array_prefix.is_empty()) { |
2969 | path = path.trim_prefix(array_prefix); |
2970 | int char_index = path.find("/" ); |
2971 | if (char_index >= 0) { |
2972 | path = path.right(-char_index - 1); |
2973 | } else { |
2974 | path = vformat(TTR("Element %s" ), array_index); |
2975 | } |
2976 | } else { |
2977 | // Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string. |
2978 | if (!subgroup.is_empty() && !subgroup_base.is_empty()) { |
2979 | if (path.begins_with(subgroup_base)) { |
2980 | path = path.trim_prefix(subgroup_base); |
2981 | } else if (subgroup_base.begins_with(path)) { |
2982 | // Keep it, this is used pretty often. |
2983 | } else { |
2984 | subgroup = "" ; // The prefix changed, we are no longer in the subgroup. |
2985 | } |
2986 | } |
2987 | |
2988 | // Check if we exit or not a group. If there is a prefix, remove it from the property label string. |
2989 | if (!group.is_empty() && !group_base.is_empty() && subgroup.is_empty()) { |
2990 | if (path.begins_with(group_base)) { |
2991 | path = path.trim_prefix(group_base); |
2992 | } else if (group_base.begins_with(path)) { |
2993 | // Keep it, this is used pretty often. |
2994 | } else { |
2995 | group = "" ; // The prefix changed, we are no longer in the group. |
2996 | subgroup = "" ; |
2997 | } |
2998 | } |
2999 | |
3000 | // Add the group and subgroup to the path. |
3001 | if (!subgroup.is_empty()) { |
3002 | path = subgroup + "/" + path; |
3003 | } |
3004 | if (!group.is_empty()) { |
3005 | path = group + "/" + path; |
3006 | } |
3007 | } |
3008 | |
3009 | // Get the property label's string. |
3010 | String name_override = (path.contains("/" )) ? path.substr(path.rfind("/" ) + 1) : path; |
3011 | String feature_tag; |
3012 | { |
3013 | const int dot = name_override.find("." ); |
3014 | if (dot != -1) { |
3015 | feature_tag = name_override.substr(dot); |
3016 | name_override = name_override.substr(0, dot); |
3017 | } |
3018 | } |
3019 | |
3020 | // Don't localize script variables. |
3021 | EditorPropertyNameProcessor::Style name_style = property_name_style; |
3022 | if ((p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE) && name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) { |
3023 | name_style = EditorPropertyNameProcessor::STYLE_CAPITALIZED; |
3024 | } |
3025 | const String property_label_string = EditorPropertyNameProcessor::get_singleton()->process_name(name_override, name_style) + feature_tag; |
3026 | |
3027 | // Remove the property from the path. |
3028 | int idx = path.rfind("/" ); |
3029 | if (idx > -1) { |
3030 | path = path.left(idx); |
3031 | } else { |
3032 | path = "" ; |
3033 | } |
3034 | |
3035 | // Ignore properties that do not fit the filter. |
3036 | if (use_filter && !filter.is_empty()) { |
3037 | const String property_path = property_prefix + (path.is_empty() ? "" : path + "/" ) + name_override; |
3038 | if (!_property_path_matches(property_path, filter, property_name_style)) { |
3039 | continue; |
3040 | } |
3041 | } |
3042 | |
3043 | // Recreate the category vbox if it was reset. |
3044 | if (category_vbox == nullptr) { |
3045 | category_vbox = memnew(VBoxContainer); |
3046 | main_vbox->add_child(category_vbox); |
3047 | } |
3048 | |
3049 | // Find the correct section/vbox to add the property editor to. |
3050 | VBoxContainer *root_vbox = array_prefix.is_empty() ? main_vbox : editor_inspector_array_per_prefix[array_prefix]->get_vbox(array_index); |
3051 | if (!root_vbox) { |
3052 | continue; |
3053 | } |
3054 | |
3055 | if (!vbox_per_path.has(root_vbox)) { |
3056 | vbox_per_path[root_vbox] = HashMap<String, VBoxContainer *>(); |
3057 | vbox_per_path[root_vbox]["" ] = root_vbox; |
3058 | } |
3059 | |
3060 | VBoxContainer *current_vbox = root_vbox; |
3061 | String acc_path = "" ; |
3062 | int level = 1; |
3063 | |
3064 | Vector<String> components = path.split("/" ); |
3065 | for (int i = 0; i < components.size(); i++) { |
3066 | String component = components[i]; |
3067 | acc_path += (i > 0) ? "/" + component : component; |
3068 | |
3069 | if (!vbox_per_path[root_vbox].has(acc_path)) { |
3070 | // If the section does not exists, create it. |
3071 | EditorInspectorSection *section = memnew(EditorInspectorSection); |
3072 | current_vbox->add_child(section); |
3073 | sections.push_back(section); |
3074 | |
3075 | String label; |
3076 | String tooltip; |
3077 | |
3078 | // Don't localize groups for script variables. |
3079 | EditorPropertyNameProcessor::Style section_name_style = property_name_style; |
3080 | if ((p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE) && section_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) { |
3081 | section_name_style = EditorPropertyNameProcessor::STYLE_CAPITALIZED; |
3082 | } |
3083 | |
3084 | // Only process group label if this is not the group or subgroup. |
3085 | if ((i == 0 && component == group) || (i == 1 && component == subgroup)) { |
3086 | if (section_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) { |
3087 | label = EditorPropertyNameProcessor::get_singleton()->translate_group_name(component); |
3088 | tooltip = component; |
3089 | } else { |
3090 | label = component; |
3091 | tooltip = EditorPropertyNameProcessor::get_singleton()->translate_group_name(component); |
3092 | } |
3093 | } else { |
3094 | label = EditorPropertyNameProcessor::get_singleton()->process_name(component, section_name_style); |
3095 | tooltip = EditorPropertyNameProcessor::get_singleton()->process_name(component, EditorPropertyNameProcessor::get_tooltip_style(section_name_style)); |
3096 | } |
3097 | |
3098 | Color c = sscolor; |
3099 | c.a /= level; |
3100 | section->setup(acc_path, label, object, c, use_folding, section_depth); |
3101 | section->set_tooltip_text(tooltip); |
3102 | |
3103 | // Add editors at the start of a group. |
3104 | for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { |
3105 | ped->parse_group(object, path); |
3106 | _parse_added_editors(section->get_vbox(), section, ped); |
3107 | } |
3108 | |
3109 | vbox_per_path[root_vbox][acc_path] = section->get_vbox(); |
3110 | } |
3111 | |
3112 | current_vbox = vbox_per_path[root_vbox][acc_path]; |
3113 | level = (MIN(level + 1, 4)); |
3114 | } |
3115 | |
3116 | // If we did not find a section to add the property to, add it to the category vbox instead (the category vbox handles margins correctly). |
3117 | if (current_vbox == main_vbox) { |
3118 | current_vbox = category_vbox; |
3119 | } |
3120 | |
3121 | // Check if the property is an array counter, if so create a dedicated array editor for the array. |
3122 | if (p.usage & PROPERTY_USAGE_ARRAY) { |
3123 | EditorInspectorArray *editor_inspector_array = nullptr; |
3124 | StringName array_element_prefix; |
3125 | Color c = sscolor; |
3126 | c.a /= level; |
3127 | |
3128 | Vector<String> class_name_components = String(p.class_name).split("," ); |
3129 | |
3130 | int page_size = 5; |
3131 | bool movable = true; |
3132 | bool numbered = false; |
3133 | bool foldable = use_folding; |
3134 | String add_button_text = TTR("Add Element" ); |
3135 | String swap_method; |
3136 | for (int i = (p.type == Variant::NIL ? 1 : 2); i < class_name_components.size(); i++) { |
3137 | if (class_name_components[i].begins_with("page_size" ) && class_name_components[i].get_slice_count("=" ) == 2) { |
3138 | page_size = class_name_components[i].get_slice("=" , 1).to_int(); |
3139 | } else if (class_name_components[i].begins_with("add_button_text" ) && class_name_components[i].get_slice_count("=" ) == 2) { |
3140 | add_button_text = class_name_components[i].get_slice("=" , 1).strip_edges(); |
3141 | } else if (class_name_components[i] == "static" ) { |
3142 | movable = false; |
3143 | } else if (class_name_components[i] == "numbered" ) { |
3144 | numbered = true; |
3145 | } else if (class_name_components[i] == "unfoldable" ) { |
3146 | foldable = false; |
3147 | } else if (class_name_components[i].begins_with("swap_method" ) && class_name_components[i].get_slice_count("=" ) == 2) { |
3148 | swap_method = class_name_components[i].get_slice("=" , 1).strip_edges(); |
3149 | } |
3150 | } |
3151 | |
3152 | if (p.type == Variant::NIL) { |
3153 | // Setup the array to use a method to create/move/delete elements. |
3154 | array_element_prefix = class_name_components[0]; |
3155 | editor_inspector_array = memnew(EditorInspectorArray(all_read_only)); |
3156 | |
3157 | String array_label = path.contains("/" ) ? path.substr(path.rfind("/" ) + 1) : path; |
3158 | array_label = EditorPropertyNameProcessor::get_singleton()->process_name(property_label_string, property_name_style); |
3159 | int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; |
3160 | editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding); |
3161 | editor_inspector_array->connect("page_change_request" , callable_mp(this, &EditorInspector::_page_change_request).bind(array_element_prefix)); |
3162 | } else if (p.type == Variant::INT) { |
3163 | // Setup the array to use the count property and built-in functions to create/move/delete elements. |
3164 | if (class_name_components.size() >= 2) { |
3165 | array_element_prefix = class_name_components[1]; |
3166 | editor_inspector_array = memnew(EditorInspectorArray(all_read_only)); |
3167 | int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; |
3168 | |
3169 | editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, foldable, movable, numbered, page_size, add_button_text, swap_method); |
3170 | editor_inspector_array->connect("page_change_request" , callable_mp(this, &EditorInspector::_page_change_request).bind(array_element_prefix)); |
3171 | } |
3172 | } |
3173 | |
3174 | if (editor_inspector_array) { |
3175 | current_vbox->add_child(editor_inspector_array); |
3176 | editor_inspector_array_per_prefix[array_element_prefix] = editor_inspector_array; |
3177 | } |
3178 | |
3179 | continue; |
3180 | } |
3181 | |
3182 | // Checkable and checked properties. |
3183 | bool checkable = false; |
3184 | bool checked = false; |
3185 | if (p.usage & PROPERTY_USAGE_CHECKABLE) { |
3186 | checkable = true; |
3187 | checked = p.usage & PROPERTY_USAGE_CHECKED; |
3188 | } |
3189 | |
3190 | bool property_read_only = (p.usage & PROPERTY_USAGE_READ_ONLY) || read_only; |
3191 | |
3192 | // Mark properties that would require an editor restart (mostly when editing editor settings). |
3193 | if (p.usage & PROPERTY_USAGE_RESTART_IF_CHANGED) { |
3194 | restart_request_props.insert(p.name); |
3195 | } |
3196 | |
3197 | PropertyDocInfo doc_info; |
3198 | |
3199 | if (use_doc_hints) { |
3200 | // Build the doc hint, to use as tooltip. |
3201 | |
3202 | // Get the class name. |
3203 | StringName classname = doc_name; |
3204 | if (!object_class.is_empty()) { |
3205 | classname = object_class; |
3206 | } else if (Object::cast_to<MultiNodeEdit>(object)) { |
3207 | classname = Object::cast_to<MultiNodeEdit>(object)->get_edited_class_name(); |
3208 | } else if (classname == "" ) { |
3209 | classname = object->get_class_name(); |
3210 | Resource *res = Object::cast_to<Resource>(object); |
3211 | if (res && !res->get_script().is_null()) { |
3212 | // Grab the script of this resource to get the evaluated script class. |
3213 | Ref<Script> scr = res->get_script(); |
3214 | if (scr.is_valid()) { |
3215 | Vector<DocData::ClassDoc> docs = scr->get_documentation(); |
3216 | if (!docs.is_empty()) { |
3217 | // The documentation of a GDScript's main class is at the end of the array. |
3218 | // Hacky because this isn't necessarily always guaranteed. |
3219 | classname = docs[docs.size() - 1].name; |
3220 | } |
3221 | } |
3222 | } |
3223 | } |
3224 | |
3225 | StringName propname = property_prefix + p.name; |
3226 | bool found = false; |
3227 | |
3228 | // Small hack for theme_overrides. They are listed under Control, but come from another class. |
3229 | if (classname == "Control" && p.name.begins_with("theme_override_" )) { |
3230 | classname = get_edited_object()->get_class(); |
3231 | } |
3232 | |
3233 | // Search for the property description in the cache. |
3234 | HashMap<StringName, HashMap<StringName, PropertyDocInfo>>::Iterator E = doc_info_cache.find(classname); |
3235 | if (E) { |
3236 | HashMap<StringName, PropertyDocInfo>::Iterator F = E->value.find(propname); |
3237 | if (F) { |
3238 | found = true; |
3239 | doc_info = F->value; |
3240 | } |
3241 | } |
3242 | |
3243 | if (!found) { |
3244 | bool is_native_class = ClassDB::class_exists(classname); |
3245 | |
3246 | // Build the property description String and add it to the cache. |
3247 | DocTools *dd = EditorHelp::get_doc_data(); |
3248 | HashMap<String, DocData::ClassDoc>::ConstIterator F = dd->class_list.find(classname); |
3249 | while (F && doc_info.description.is_empty()) { |
3250 | for (int i = 0; i < F->value.properties.size(); i++) { |
3251 | if (F->value.properties[i].name == propname.operator String()) { |
3252 | doc_info.description = F->value.properties[i].description; |
3253 | if (is_native_class) { |
3254 | doc_info.description = DTR(doc_info.description); // Do not translate the property description of scripts. |
3255 | } |
3256 | |
3257 | const Vector<String> class_enum = F->value.properties[i].enumeration.split("." ); |
3258 | const String class_name = class_enum[0]; |
3259 | const String enum_name = class_enum.size() >= 2 ? class_enum[1] : "" ; |
3260 | if (!enum_name.is_empty()) { |
3261 | HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_name); |
3262 | if (enum_class) { |
3263 | for (DocData::ConstantDoc val : enum_class->value.constants) { |
3264 | // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector. |
3265 | if (val.enumeration == enum_name && !val.name.ends_with("_MAX" )) { |
3266 | const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); |
3267 | // Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value". |
3268 | String desc = val.description; |
3269 | if (is_native_class) { |
3270 | desc = DTR(desc); // Do not translate the enum value description of scripts. |
3271 | } |
3272 | desc = desc.trim_prefix("\n" ); |
3273 | doc_info.description += vformat( |
3274 | "\n[b]%s:[/b] %s" , |
3275 | enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " " ), |
3276 | desc.is_empty() ? ("[i]" + TTR("No description." ) + "[/i]" ) : desc); |
3277 | } |
3278 | } |
3279 | } |
3280 | } |
3281 | |
3282 | doc_info.path = "class_property:" + F->value.name + ":" + F->value.properties[i].name; |
3283 | break; |
3284 | } |
3285 | } |
3286 | |
3287 | Vector<String> slices = propname.operator String().split("/" ); |
3288 | if (slices.size() == 2 && slices[0].begins_with("theme_override_" )) { |
3289 | for (int i = 0; i < F->value.theme_properties.size(); i++) { |
3290 | if (F->value.theme_properties[i].name == slices[1]) { |
3291 | doc_info.description = F->value.theme_properties[i].description; |
3292 | if (is_native_class) { |
3293 | doc_info.description = DTR(doc_info.description); // Do not translate the theme item description of scripts. |
3294 | } |
3295 | doc_info.path = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name; |
3296 | break; |
3297 | } |
3298 | } |
3299 | } |
3300 | |
3301 | if (!F->value.inherits.is_empty()) { |
3302 | F = dd->class_list.find(F->value.inherits); |
3303 | } else { |
3304 | break; |
3305 | } |
3306 | } |
3307 | |
3308 | if (is_native_class) { |
3309 | doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts. |
3310 | } |
3311 | } |
3312 | } |
3313 | |
3314 | Vector<EditorInspectorPlugin::AddedEditor> editors; |
3315 | Vector<EditorInspectorPlugin::AddedEditor> late_editors; |
3316 | |
3317 | // Search for the inspector plugin that will handle the properties. Then add the correct property editor to it. |
3318 | for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { |
3319 | bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage, wide_editors); |
3320 | |
3321 | for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) { |
3322 | if (F.add_to_end) { |
3323 | late_editors.push_back(F); |
3324 | } else { |
3325 | editors.push_back(F); |
3326 | } |
3327 | } |
3328 | |
3329 | ped->added_editors.clear(); |
3330 | |
3331 | if (exclusive) { |
3332 | break; |
3333 | } |
3334 | } |
3335 | |
3336 | editors.append_array(late_editors); |
3337 | |
3338 | for (int i = 0; i < editors.size(); i++) { |
3339 | EditorProperty *ep = Object::cast_to<EditorProperty>(editors[i].property_editor); |
3340 | const Vector<String> &properties = editors[i].properties; |
3341 | |
3342 | if (ep) { |
3343 | // Set all this before the control gets the ENTER_TREE notification. |
3344 | ep->object = object; |
3345 | |
3346 | if (properties.size()) { |
3347 | if (properties.size() == 1) { |
3348 | //since it's one, associate: |
3349 | ep->property = properties[0]; |
3350 | ep->property_path = property_prefix + properties[0]; |
3351 | ep->property_usage = p.usage; |
3352 | //and set label? |
3353 | } |
3354 | if (!editors[i].label.is_empty()) { |
3355 | ep->set_label(editors[i].label); |
3356 | } else { |
3357 | // Use the existing one. |
3358 | ep->set_label(property_label_string); |
3359 | } |
3360 | |
3361 | for (int j = 0; j < properties.size(); j++) { |
3362 | String prop = properties[j]; |
3363 | |
3364 | if (!editor_property_map.has(prop)) { |
3365 | editor_property_map[prop] = List<EditorProperty *>(); |
3366 | } |
3367 | editor_property_map[prop].push_back(ep); |
3368 | } |
3369 | } |
3370 | |
3371 | EditorInspectorSection *section = Object::cast_to<EditorInspectorSection>(current_vbox->get_parent()); |
3372 | if (section) { |
3373 | ep->connect("property_can_revert_changed" , callable_mp(section, &EditorInspectorSection::property_can_revert_changed)); |
3374 | } |
3375 | |
3376 | ep->set_draw_warning(draw_warning); |
3377 | ep->set_use_folding(use_folding); |
3378 | ep->set_checkable(checkable); |
3379 | ep->set_checked(checked); |
3380 | ep->set_keying(keying); |
3381 | ep->set_read_only(property_read_only || all_read_only); |
3382 | ep->set_deletable(deletable_properties || p.name.begins_with("metadata/" )); |
3383 | } |
3384 | |
3385 | current_vbox->add_child(editors[i].property_editor); |
3386 | |
3387 | if (ep) { |
3388 | // Eventually, set other properties/signals after the property editor got added to the tree. |
3389 | bool update_all = (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED); |
3390 | ep->connect("property_changed" , callable_mp(this, &EditorInspector::_property_changed).bind(update_all)); |
3391 | ep->connect("property_keyed" , callable_mp(this, &EditorInspector::_property_keyed)); |
3392 | ep->connect("property_deleted" , callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED); |
3393 | ep->connect("property_keyed_with_value" , callable_mp(this, &EditorInspector::_property_keyed_with_value)); |
3394 | ep->connect("property_checked" , callable_mp(this, &EditorInspector::_property_checked)); |
3395 | ep->connect("property_pinned" , callable_mp(this, &EditorInspector::_property_pinned)); |
3396 | ep->connect("selected" , callable_mp(this, &EditorInspector::_property_selected)); |
3397 | ep->connect("multiple_properties_changed" , callable_mp(this, &EditorInspector::_multiple_properties_changed)); |
3398 | ep->connect("resource_selected" , callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED); |
3399 | ep->connect("object_id_selected" , callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED); |
3400 | // `|` separator used in `make_help_bit()` for formatting. |
3401 | ep->set_tooltip_text(property_prefix + p.name + "|" + doc_info.description); |
3402 | ep->set_doc_path(doc_info.path); |
3403 | ep->update_property(); |
3404 | ep->_update_pin_flags(); |
3405 | ep->update_editor_property_status(); |
3406 | ep->update_cache(); |
3407 | |
3408 | if (current_selected && ep->property == current_selected) { |
3409 | ep->select(current_focusable); |
3410 | } |
3411 | } |
3412 | } |
3413 | } |
3414 | |
3415 | if (!hide_metadata && !object->call("_hide_metadata_from_inspector" )) { |
3416 | // Add 4px of spacing between the "Add Metadata" button and the content above it. |
3417 | Control *spacer = memnew(Control); |
3418 | spacer->set_custom_minimum_size(Size2(0, 4) * EDSCALE); |
3419 | main_vbox->add_child(spacer); |
3420 | |
3421 | Button *add_md = EditorInspector::create_inspector_action_button(TTR("Add Metadata" )); |
3422 | add_md->set_icon(get_editor_theme_icon(SNAME("Add" ))); |
3423 | add_md->connect(SNAME("pressed" ), callable_mp(this, &EditorInspector::_show_add_meta_dialog)); |
3424 | main_vbox->add_child(add_md); |
3425 | if (all_read_only) { |
3426 | add_md->set_disabled(true); |
3427 | } |
3428 | } |
3429 | |
3430 | // Get the lists of to add at the end. |
3431 | for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { |
3432 | ped->parse_end(object); |
3433 | _parse_added_editors(main_vbox, nullptr, ped); |
3434 | } |
3435 | |
3436 | if (is_main_editor_inspector()) { |
3437 | // Updating inspector might invalidate some editing owners. |
3438 | EditorNode::get_singleton()->hide_unused_editors(); |
3439 | } |
3440 | } |
3441 | |
3442 | void EditorInspector::update_property(const String &p_prop) { |
3443 | if (!editor_property_map.has(p_prop)) { |
3444 | return; |
3445 | } |
3446 | |
3447 | for (EditorProperty *E : editor_property_map[p_prop]) { |
3448 | E->update_property(); |
3449 | E->update_editor_property_status(); |
3450 | E->update_cache(); |
3451 | } |
3452 | } |
3453 | |
3454 | void EditorInspector::_clear(bool p_hide_plugins) { |
3455 | while (main_vbox->get_child_count()) { |
3456 | memdelete(main_vbox->get_child(0)); |
3457 | } |
3458 | |
3459 | property_selected = StringName(); |
3460 | property_focusable = -1; |
3461 | editor_property_map.clear(); |
3462 | sections.clear(); |
3463 | pending.clear(); |
3464 | restart_request_props.clear(); |
3465 | |
3466 | if (p_hide_plugins && is_main_editor_inspector()) { |
3467 | EditorNode::get_singleton()->hide_unused_editors(this); |
3468 | } |
3469 | } |
3470 | |
3471 | Object *EditorInspector::get_edited_object() { |
3472 | return object; |
3473 | } |
3474 | |
3475 | void EditorInspector::edit(Object *p_object) { |
3476 | if (object == p_object) { |
3477 | return; |
3478 | } |
3479 | |
3480 | if (object) { |
3481 | _clear(); |
3482 | object->disconnect("property_list_changed" , callable_mp(this, &EditorInspector::_changed_callback)); |
3483 | } |
3484 | per_array_page.clear(); |
3485 | |
3486 | object = p_object; |
3487 | |
3488 | if (object) { |
3489 | update_scroll_request = 0; //reset |
3490 | if (scroll_cache.has(object->get_instance_id())) { //if exists, set something else |
3491 | update_scroll_request = scroll_cache[object->get_instance_id()]; //done this way because wait until full size is accommodated |
3492 | } |
3493 | object->connect("property_list_changed" , callable_mp(this, &EditorInspector::_changed_callback)); |
3494 | update_tree(); |
3495 | } |
3496 | emit_signal(SNAME("edited_object_changed" )); |
3497 | } |
3498 | |
3499 | void EditorInspector::set_keying(bool p_active) { |
3500 | if (keying == p_active) { |
3501 | return; |
3502 | } |
3503 | keying = p_active; |
3504 | _keying_changed(); |
3505 | } |
3506 | |
3507 | void EditorInspector::_keying_changed() { |
3508 | for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { |
3509 | for (EditorProperty *E : F.value) { |
3510 | if (E) { |
3511 | E->set_keying(keying); |
3512 | } |
3513 | } |
3514 | } |
3515 | } |
3516 | |
3517 | void EditorInspector::set_read_only(bool p_read_only) { |
3518 | if (p_read_only == read_only) { |
3519 | return; |
3520 | } |
3521 | read_only = p_read_only; |
3522 | update_tree(); |
3523 | } |
3524 | |
3525 | EditorPropertyNameProcessor::Style EditorInspector::get_property_name_style() const { |
3526 | return property_name_style; |
3527 | } |
3528 | |
3529 | void EditorInspector::set_property_name_style(EditorPropertyNameProcessor::Style p_style) { |
3530 | if (property_name_style == p_style) { |
3531 | return; |
3532 | } |
3533 | property_name_style = p_style; |
3534 | update_tree(); |
3535 | } |
3536 | |
3537 | void EditorInspector::set_use_settings_name_style(bool p_enable) { |
3538 | if (use_settings_name_style == p_enable) { |
3539 | return; |
3540 | } |
3541 | use_settings_name_style = p_enable; |
3542 | if (use_settings_name_style) { |
3543 | set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style()); |
3544 | } |
3545 | } |
3546 | |
3547 | void EditorInspector::set_autoclear(bool p_enable) { |
3548 | autoclear = p_enable; |
3549 | } |
3550 | |
3551 | void EditorInspector::set_show_categories(bool p_show) { |
3552 | show_categories = p_show; |
3553 | update_tree(); |
3554 | } |
3555 | |
3556 | void EditorInspector::set_use_doc_hints(bool p_enable) { |
3557 | use_doc_hints = p_enable; |
3558 | update_tree(); |
3559 | } |
3560 | |
3561 | void EditorInspector::set_hide_script(bool p_hide) { |
3562 | hide_script = p_hide; |
3563 | update_tree(); |
3564 | } |
3565 | |
3566 | void EditorInspector::set_hide_metadata(bool p_hide) { |
3567 | hide_metadata = p_hide; |
3568 | update_tree(); |
3569 | } |
3570 | |
3571 | void EditorInspector::set_use_filter(bool p_use) { |
3572 | use_filter = p_use; |
3573 | update_tree(); |
3574 | } |
3575 | |
3576 | void EditorInspector::register_text_enter(Node *p_line_edit) { |
3577 | search_box = Object::cast_to<LineEdit>(p_line_edit); |
3578 | if (search_box) { |
3579 | search_box->connect("text_changed" , callable_mp(this, &EditorInspector::_filter_changed)); |
3580 | } |
3581 | } |
3582 | |
3583 | void EditorInspector::_filter_changed(const String &p_text) { |
3584 | update_tree(); |
3585 | } |
3586 | |
3587 | void EditorInspector::set_use_folding(bool p_use_folding, bool p_update_tree) { |
3588 | use_folding = p_use_folding; |
3589 | |
3590 | if (p_update_tree) { |
3591 | update_tree(); |
3592 | } |
3593 | } |
3594 | |
3595 | bool EditorInspector::is_using_folding() { |
3596 | return use_folding; |
3597 | } |
3598 | |
3599 | void EditorInspector::collapse_all_folding() { |
3600 | for (EditorInspectorSection *E : sections) { |
3601 | E->fold(); |
3602 | } |
3603 | |
3604 | for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { |
3605 | for (EditorProperty *E : F.value) { |
3606 | E->collapse_all_folding(); |
3607 | } |
3608 | } |
3609 | } |
3610 | |
3611 | void EditorInspector::expand_all_folding() { |
3612 | for (EditorInspectorSection *E : sections) { |
3613 | E->unfold(); |
3614 | } |
3615 | for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { |
3616 | for (EditorProperty *E : F.value) { |
3617 | E->expand_all_folding(); |
3618 | } |
3619 | } |
3620 | } |
3621 | |
3622 | void EditorInspector::expand_revertable() { |
3623 | HashSet<EditorInspectorSection *> sections_to_unfold[2]; |
3624 | for (EditorInspectorSection *E : sections) { |
3625 | if (E->has_revertable_properties()) { |
3626 | sections_to_unfold[0].insert(E); |
3627 | } |
3628 | } |
3629 | |
3630 | // Climb up the hierarchy doing double buffering with the sets. |
3631 | int a = 0; |
3632 | int b = 1; |
3633 | while (sections_to_unfold[a].size()) { |
3634 | for (EditorInspectorSection *E : sections_to_unfold[a]) { |
3635 | E->unfold(); |
3636 | |
3637 | Node *n = E->get_parent(); |
3638 | while (n) { |
3639 | if (Object::cast_to<EditorInspector>(n)) { |
3640 | break; |
3641 | } |
3642 | if (Object::cast_to<EditorInspectorSection>(n) && !sections_to_unfold[a].has((EditorInspectorSection *)n)) { |
3643 | sections_to_unfold[b].insert((EditorInspectorSection *)n); |
3644 | } |
3645 | n = n->get_parent(); |
3646 | } |
3647 | } |
3648 | |
3649 | sections_to_unfold[a].clear(); |
3650 | SWAP(a, b); |
3651 | } |
3652 | |
3653 | for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { |
3654 | for (EditorProperty *E : F.value) { |
3655 | E->expand_revertable(); |
3656 | } |
3657 | } |
3658 | } |
3659 | |
3660 | void EditorInspector::set_scroll_offset(int p_offset) { |
3661 | set_v_scroll(p_offset); |
3662 | } |
3663 | |
3664 | int EditorInspector::get_scroll_offset() const { |
3665 | return get_v_scroll(); |
3666 | } |
3667 | |
3668 | void EditorInspector::set_use_wide_editors(bool p_enable) { |
3669 | wide_editors = p_enable; |
3670 | } |
3671 | |
3672 | void EditorInspector::_update_inspector_bg() { |
3673 | if (sub_inspector) { |
3674 | int count_subinspectors = 0; |
3675 | Node *n = get_parent(); |
3676 | while (n) { |
3677 | EditorInspector *ei = Object::cast_to<EditorInspector>(n); |
3678 | if (ei && ei->sub_inspector) { |
3679 | count_subinspectors++; |
3680 | } |
3681 | n = n->get_parent(); |
3682 | } |
3683 | count_subinspectors = MIN(15, count_subinspectors); |
3684 | add_theme_style_override("panel" , get_theme_stylebox("sub_inspector_bg" + itos(count_subinspectors), EditorStringName(Editor))); |
3685 | } else { |
3686 | add_theme_style_override("panel" , get_theme_stylebox(SNAME("panel" ), SNAME("Tree" ))); |
3687 | } |
3688 | } |
3689 | void EditorInspector::set_sub_inspector(bool p_enable) { |
3690 | sub_inspector = p_enable; |
3691 | if (!is_inside_tree()) { |
3692 | return; |
3693 | } |
3694 | |
3695 | _update_inspector_bg(); |
3696 | } |
3697 | |
3698 | void EditorInspector::set_use_deletable_properties(bool p_enabled) { |
3699 | deletable_properties = p_enabled; |
3700 | } |
3701 | |
3702 | void EditorInspector::_page_change_request(int p_new_page, const StringName &p_array_prefix) { |
3703 | int prev_page = per_array_page.has(p_array_prefix) ? per_array_page[p_array_prefix] : 0; |
3704 | int new_page = MAX(0, p_new_page); |
3705 | if (new_page != prev_page) { |
3706 | per_array_page[p_array_prefix] = new_page; |
3707 | update_tree_pending = true; |
3708 | } |
3709 | } |
3710 | |
3711 | void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) { |
3712 | if (object != p_object) { //may be undoing/redoing for a non edited object, so ignore |
3713 | return; |
3714 | } |
3715 | |
3716 | if (changing) { |
3717 | return; |
3718 | } |
3719 | |
3720 | if (p_property.is_empty()) { |
3721 | update_tree_pending = true; |
3722 | } else { |
3723 | pending.insert(p_property); |
3724 | } |
3725 | } |
3726 | |
3727 | void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field) { |
3728 | if (autoclear && editor_property_map.has(p_name)) { |
3729 | for (EditorProperty *E : editor_property_map[p_name]) { |
3730 | if (E->is_checkable()) { |
3731 | E->set_checked(true); |
3732 | } |
3733 | } |
3734 | } |
3735 | |
3736 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
3737 | if (bool(object->call("_dont_undo_redo" ))) { |
3738 | object->set(p_name, p_value); |
3739 | if (p_refresh_all) { |
3740 | _edit_request_change(object, "" ); |
3741 | } else { |
3742 | _edit_request_change(object, p_name); |
3743 | } |
3744 | |
3745 | emit_signal(_prop_edited, p_name); |
3746 | |
3747 | } else if (Object::cast_to<MultiNodeEdit>(object)) { |
3748 | Object::cast_to<MultiNodeEdit>(object)->set_property_field(p_name, p_value, p_changed_field); |
3749 | _edit_request_change(object, p_name); |
3750 | emit_signal(_prop_edited, p_name); |
3751 | } else { |
3752 | undo_redo->create_action(vformat(TTR("Set %s" ), p_name), UndoRedo::MERGE_ENDS); |
3753 | undo_redo->add_do_property(object, p_name, p_value); |
3754 | bool valid = false; |
3755 | Variant value = object->get(p_name, &valid); |
3756 | if (valid) { |
3757 | undo_redo->add_undo_property(object, p_name, value); |
3758 | } |
3759 | |
3760 | List<StringName> linked_properties; |
3761 | ClassDB::get_linked_properties_info(object->get_class_name(), p_name, &linked_properties); |
3762 | |
3763 | for (const StringName &linked_prop : linked_properties) { |
3764 | valid = false; |
3765 | Variant undo_value = object->get(linked_prop, &valid); |
3766 | if (valid) { |
3767 | undo_redo->add_undo_property(object, linked_prop, undo_value); |
3768 | } |
3769 | } |
3770 | |
3771 | PackedStringArray linked_properties_dynamic = object->call("_get_linked_undo_properties" , p_name, p_value); |
3772 | for (int i = 0; i < linked_properties_dynamic.size(); i++) { |
3773 | valid = false; |
3774 | Variant undo_value = object->get(linked_properties_dynamic[i], &valid); |
3775 | if (valid) { |
3776 | undo_redo->add_undo_property(object, linked_properties_dynamic[i], undo_value); |
3777 | } |
3778 | } |
3779 | |
3780 | Variant v_undo_redo = undo_redo; |
3781 | Variant v_object = object; |
3782 | Variant v_name = p_name; |
3783 | const Vector<Callable> &callbacks = EditorNode::get_editor_data().get_undo_redo_inspector_hook_callback(); |
3784 | for (int i = 0; i < callbacks.size(); i++) { |
3785 | const Callable &callback = callbacks[i]; |
3786 | |
3787 | const Variant *p_arguments[] = { &v_undo_redo, &v_object, &v_name, &p_value }; |
3788 | Variant return_value; |
3789 | Callable::CallError call_error; |
3790 | |
3791 | callback.callp(p_arguments, 4, return_value, call_error); |
3792 | if (call_error.error != Callable::CallError::CALL_OK) { |
3793 | ERR_PRINT("Invalid UndoRedo callback." ); |
3794 | } |
3795 | } |
3796 | |
3797 | if (p_refresh_all) { |
3798 | undo_redo->add_do_method(this, "_edit_request_change" , object, "" ); |
3799 | undo_redo->add_undo_method(this, "_edit_request_change" , object, "" ); |
3800 | } else { |
3801 | undo_redo->add_do_method(this, "_edit_request_change" , object, p_name); |
3802 | undo_redo->add_undo_method(this, "_edit_request_change" , object, p_name); |
3803 | } |
3804 | |
3805 | Resource *r = Object::cast_to<Resource>(object); |
3806 | if (r) { |
3807 | if (String(p_name) == "resource_local_to_scene" ) { |
3808 | bool prev = object->get(p_name); |
3809 | bool next = p_value; |
3810 | if (next) { |
3811 | undo_redo->add_do_method(r, "setup_local_to_scene" ); |
3812 | } |
3813 | if (prev) { |
3814 | undo_redo->add_undo_method(r, "setup_local_to_scene" ); |
3815 | } |
3816 | } |
3817 | } |
3818 | undo_redo->add_do_method(this, "emit_signal" , _prop_edited, p_name); |
3819 | undo_redo->add_undo_method(this, "emit_signal" , _prop_edited, p_name); |
3820 | undo_redo->commit_action(); |
3821 | } |
3822 | |
3823 | if (editor_property_map.has(p_name)) { |
3824 | for (EditorProperty *E : editor_property_map[p_name]) { |
3825 | E->update_editor_property_status(); |
3826 | } |
3827 | } |
3828 | } |
3829 | |
3830 | void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing, bool p_update_all) { |
3831 | // The "changing" variable must be true for properties that trigger events as typing occurs, |
3832 | // like "text_changed" signal. E.g. text property of Label, Button, RichTextLabel, etc. |
3833 | if (p_changing) { |
3834 | this->changing++; |
3835 | } |
3836 | |
3837 | _edit_set(p_path, p_value, p_update_all, p_name); |
3838 | |
3839 | if (p_changing) { |
3840 | this->changing--; |
3841 | } |
3842 | |
3843 | if (restart_request_props.has(p_path)) { |
3844 | emit_signal(SNAME("restart_requested" )); |
3845 | } |
3846 | } |
3847 | |
3848 | void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing) { |
3849 | ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0); |
3850 | ERR_FAIL_COND(p_paths.size() != p_values.size()); |
3851 | String names; |
3852 | for (int i = 0; i < p_paths.size(); i++) { |
3853 | if (i > 0) { |
3854 | names += "," ; |
3855 | } |
3856 | names += p_paths[i]; |
3857 | } |
3858 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
3859 | // TRANSLATORS: This is describing a change to multiple properties at once. The parameter is a list of property names. |
3860 | undo_redo->create_action(vformat(TTR("Set Multiple: %s" ), names), UndoRedo::MERGE_ENDS); |
3861 | for (int i = 0; i < p_paths.size(); i++) { |
3862 | _edit_set(p_paths[i], p_values[i], false, "" ); |
3863 | if (restart_request_props.has(p_paths[i])) { |
3864 | emit_signal(SNAME("restart_requested" )); |
3865 | } |
3866 | } |
3867 | if (p_changing) { |
3868 | changing++; |
3869 | } |
3870 | undo_redo->commit_action(); |
3871 | if (p_changing) { |
3872 | changing--; |
3873 | } |
3874 | } |
3875 | |
3876 | void EditorInspector::_property_keyed(const String &p_path, bool p_advance) { |
3877 | if (!object) { |
3878 | return; |
3879 | } |
3880 | |
3881 | // The second parameter could be null, causing the event to fire with less arguments, so use the pointer call which preserves it. |
3882 | const Variant args[3] = { p_path, object->get(p_path), p_advance }; |
3883 | const Variant *argp[3] = { &args[0], &args[1], &args[2] }; |
3884 | emit_signalp(SNAME("property_keyed" ), argp, 3); |
3885 | } |
3886 | |
3887 | void EditorInspector::_property_deleted(const String &p_path) { |
3888 | if (!object) { |
3889 | return; |
3890 | } |
3891 | |
3892 | if (p_path.begins_with("metadata/" )) { |
3893 | String name = p_path.replace_first("metadata/" , "" ); |
3894 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
3895 | undo_redo->create_action(vformat(TTR("Remove metadata %s" ), name)); |
3896 | undo_redo->add_do_method(object, "remove_meta" , name); |
3897 | undo_redo->add_undo_method(object, "set_meta" , name, object->get_meta(name)); |
3898 | undo_redo->commit_action(); |
3899 | } |
3900 | |
3901 | emit_signal(SNAME("property_deleted" ), p_path); |
3902 | } |
3903 | |
3904 | void EditorInspector::_property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance) { |
3905 | if (!object) { |
3906 | return; |
3907 | } |
3908 | |
3909 | // The second parameter could be null, causing the event to fire with less arguments, so use the pointer call which preserves it. |
3910 | const Variant args[3] = { p_path, p_value, p_advance }; |
3911 | const Variant *argp[3] = { &args[0], &args[1], &args[2] }; |
3912 | emit_signalp(SNAME("property_keyed" ), argp, 3); |
3913 | } |
3914 | |
3915 | void EditorInspector::_property_checked(const String &p_path, bool p_checked) { |
3916 | if (!object) { |
3917 | return; |
3918 | } |
3919 | |
3920 | //property checked |
3921 | if (autoclear) { |
3922 | if (!p_checked) { |
3923 | object->set(p_path, Variant()); |
3924 | } else { |
3925 | Variant to_create; |
3926 | List<PropertyInfo> pinfo; |
3927 | object->get_property_list(&pinfo); |
3928 | for (const PropertyInfo &E : pinfo) { |
3929 | if (E.name == p_path) { |
3930 | Callable::CallError ce; |
3931 | Variant::construct(E.type, to_create, nullptr, 0, ce); |
3932 | break; |
3933 | } |
3934 | } |
3935 | object->set(p_path, to_create); |
3936 | } |
3937 | |
3938 | if (editor_property_map.has(p_path)) { |
3939 | for (EditorProperty *E : editor_property_map[p_path]) { |
3940 | E->set_checked(p_checked); |
3941 | E->update_property(); |
3942 | E->update_editor_property_status(); |
3943 | E->update_cache(); |
3944 | } |
3945 | } |
3946 | |
3947 | } else { |
3948 | emit_signal(SNAME("property_toggled" ), p_path, p_checked); |
3949 | } |
3950 | } |
3951 | |
3952 | void EditorInspector::_property_pinned(const String &p_path, bool p_pinned) { |
3953 | if (!object) { |
3954 | return; |
3955 | } |
3956 | |
3957 | Node *node = Object::cast_to<Node>(object); |
3958 | ERR_FAIL_NULL(node); |
3959 | |
3960 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
3961 | undo_redo->create_action(vformat(p_pinned ? TTR("Pinned %s" ) : TTR("Unpinned %s" ), p_path)); |
3962 | undo_redo->add_do_method(node, "_set_property_pinned" , p_path, p_pinned); |
3963 | undo_redo->add_undo_method(node, "_set_property_pinned" , p_path, !p_pinned); |
3964 | if (editor_property_map.has(p_path)) { |
3965 | for (List<EditorProperty *>::Element *E = editor_property_map[p_path].front(); E; E = E->next()) { |
3966 | undo_redo->add_do_method(E->get(), "_update_editor_property_status" ); |
3967 | undo_redo->add_undo_method(E->get(), "_update_editor_property_status" ); |
3968 | } |
3969 | } |
3970 | undo_redo->commit_action(); |
3971 | } |
3972 | |
3973 | void EditorInspector::_property_selected(const String &p_path, int p_focusable) { |
3974 | property_selected = p_path; |
3975 | property_focusable = p_focusable; |
3976 | // Deselect the others. |
3977 | for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { |
3978 | if (F.key == property_selected) { |
3979 | continue; |
3980 | } |
3981 | for (EditorProperty *E : F.value) { |
3982 | if (E->is_selected()) { |
3983 | E->deselect(); |
3984 | } |
3985 | } |
3986 | } |
3987 | |
3988 | emit_signal(SNAME("property_selected" ), p_path); |
3989 | } |
3990 | |
3991 | void EditorInspector::_object_id_selected(const String &p_path, ObjectID p_id) { |
3992 | emit_signal(SNAME("object_id_selected" ), p_id); |
3993 | } |
3994 | |
3995 | void EditorInspector::_resource_selected(const String &p_path, Ref<Resource> p_resource) { |
3996 | emit_signal(SNAME("resource_selected" ), p_resource, p_path); |
3997 | } |
3998 | |
3999 | void EditorInspector::_node_removed(Node *p_node) { |
4000 | if (p_node == object) { |
4001 | edit(nullptr); |
4002 | } |
4003 | } |
4004 | |
4005 | void EditorInspector::_notification(int p_what) { |
4006 | switch (p_what) { |
4007 | case NOTIFICATION_READY: { |
4008 | EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed" , callable_mp(this, &EditorInspector::_feature_profile_changed)); |
4009 | set_process(is_visible_in_tree()); |
4010 | _update_inspector_bg(); |
4011 | } break; |
4012 | |
4013 | case NOTIFICATION_ENTER_TREE: { |
4014 | if (!sub_inspector) { |
4015 | get_tree()->connect("node_removed" , callable_mp(this, &EditorInspector::_node_removed)); |
4016 | } |
4017 | } break; |
4018 | |
4019 | case NOTIFICATION_PREDELETE: { |
4020 | edit(nullptr); //just in case |
4021 | } break; |
4022 | |
4023 | case NOTIFICATION_EXIT_TREE: { |
4024 | if (!sub_inspector) { |
4025 | get_tree()->disconnect("node_removed" , callable_mp(this, &EditorInspector::_node_removed)); |
4026 | } |
4027 | edit(nullptr); |
4028 | } break; |
4029 | |
4030 | case NOTIFICATION_VISIBILITY_CHANGED: { |
4031 | set_process(is_visible_in_tree()); |
4032 | } break; |
4033 | |
4034 | case NOTIFICATION_PROCESS: { |
4035 | if (update_scroll_request >= 0) { |
4036 | get_v_scroll_bar()->call_deferred(SNAME("set_value" ), update_scroll_request); |
4037 | update_scroll_request = -1; |
4038 | } |
4039 | if (update_tree_pending) { |
4040 | refresh_countdown = float(EDITOR_GET("docks/property_editor/auto_refresh_interval" )); |
4041 | } else if (refresh_countdown > 0) { |
4042 | refresh_countdown -= get_process_delta_time(); |
4043 | if (refresh_countdown <= 0) { |
4044 | for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) { |
4045 | for (EditorProperty *E : F.value) { |
4046 | if (E && !E->is_cache_valid()) { |
4047 | E->update_property(); |
4048 | E->update_editor_property_status(); |
4049 | E->update_cache(); |
4050 | } |
4051 | } |
4052 | } |
4053 | refresh_countdown = float(EDITOR_GET("docks/property_editor/auto_refresh_interval" )); |
4054 | } |
4055 | } |
4056 | |
4057 | changing++; |
4058 | |
4059 | if (update_tree_pending) { |
4060 | update_tree(); |
4061 | update_tree_pending = false; |
4062 | pending.clear(); |
4063 | |
4064 | } else { |
4065 | while (pending.size()) { |
4066 | StringName prop = *pending.begin(); |
4067 | if (editor_property_map.has(prop)) { |
4068 | for (EditorProperty *E : editor_property_map[prop]) { |
4069 | E->update_property(); |
4070 | E->update_editor_property_status(); |
4071 | E->update_cache(); |
4072 | } |
4073 | } |
4074 | pending.remove(pending.begin()); |
4075 | } |
4076 | } |
4077 | |
4078 | changing--; |
4079 | } break; |
4080 | |
4081 | case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { |
4082 | _update_inspector_bg(); |
4083 | |
4084 | bool needs_update = false; |
4085 | |
4086 | if (use_settings_name_style && EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/localize_settings" )) { |
4087 | EditorPropertyNameProcessor::Style style = EditorPropertyNameProcessor::get_settings_style(); |
4088 | if (property_name_style != style) { |
4089 | property_name_style = style; |
4090 | needs_update = true; |
4091 | } |
4092 | } |
4093 | if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/inspector" )) { |
4094 | needs_update = true; |
4095 | } |
4096 | |
4097 | if (needs_update) { |
4098 | update_tree(); |
4099 | } |
4100 | } break; |
4101 | } |
4102 | } |
4103 | |
4104 | void EditorInspector::_changed_callback() { |
4105 | //this is called when property change is notified via notify_property_list_changed() |
4106 | if (object != nullptr) { |
4107 | _edit_request_change(object, String()); |
4108 | } |
4109 | } |
4110 | |
4111 | void EditorInspector::_vscroll_changed(double p_offset) { |
4112 | if (update_scroll_request >= 0) { //waiting, do nothing |
4113 | return; |
4114 | } |
4115 | |
4116 | if (object) { |
4117 | scroll_cache[object->get_instance_id()] = p_offset; |
4118 | } |
4119 | } |
4120 | |
4121 | void EditorInspector::set_property_prefix(const String &p_prefix) { |
4122 | property_prefix = p_prefix; |
4123 | } |
4124 | |
4125 | String EditorInspector::get_property_prefix() const { |
4126 | return property_prefix; |
4127 | } |
4128 | |
4129 | void EditorInspector::set_object_class(const String &p_class) { |
4130 | object_class = p_class; |
4131 | } |
4132 | |
4133 | String EditorInspector::get_object_class() const { |
4134 | return object_class; |
4135 | } |
4136 | |
4137 | void EditorInspector::_feature_profile_changed() { |
4138 | update_tree(); |
4139 | } |
4140 | |
4141 | void EditorInspector::set_restrict_to_basic_settings(bool p_restrict) { |
4142 | restrict_to_basic = p_restrict; |
4143 | update_tree(); |
4144 | } |
4145 | |
4146 | void EditorInspector::set_property_clipboard(const Variant &p_value) { |
4147 | property_clipboard = p_value; |
4148 | } |
4149 | |
4150 | Variant EditorInspector::get_property_clipboard() const { |
4151 | return property_clipboard; |
4152 | } |
4153 | |
4154 | void EditorInspector::_add_meta_confirm() { |
4155 | String name = add_meta_name->get_text(); |
4156 | |
4157 | object->editor_set_section_unfold("metadata" , true); // Ensure metadata is unfolded when adding a new metadata. |
4158 | |
4159 | Variant defval; |
4160 | Callable::CallError ce; |
4161 | Variant::construct(Variant::Type(add_meta_type->get_selected_id()), defval, nullptr, 0, ce); |
4162 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
4163 | undo_redo->create_action(vformat(TTR("Add metadata %s" ), name)); |
4164 | undo_redo->add_do_method(object, "set_meta" , name, defval); |
4165 | undo_redo->add_undo_method(object, "remove_meta" , name); |
4166 | undo_redo->commit_action(); |
4167 | } |
4168 | |
4169 | void EditorInspector::_check_meta_name() { |
4170 | const String meta_name = add_meta_name->get_text(); |
4171 | |
4172 | if (meta_name.is_empty()) { |
4173 | validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty." ), EditorValidationPanel::MSG_ERROR); |
4174 | } else if (!meta_name.is_valid_identifier()) { |
4175 | validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name must be a valid identifier." ), EditorValidationPanel::MSG_ERROR); |
4176 | } else if (object->has_meta(meta_name)) { |
4177 | validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists." ), meta_name), EditorValidationPanel::MSG_ERROR); |
4178 | } else if (meta_name[0] == '_') { |
4179 | validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Names starting with _ are reserved for editor-only metadata." ), EditorValidationPanel::MSG_ERROR); |
4180 | } |
4181 | } |
4182 | |
4183 | void EditorInspector::_show_add_meta_dialog() { |
4184 | if (!add_meta_dialog) { |
4185 | add_meta_dialog = memnew(ConfirmationDialog); |
4186 | |
4187 | VBoxContainer *vbc = memnew(VBoxContainer); |
4188 | add_meta_dialog->add_child(vbc); |
4189 | |
4190 | HBoxContainer *hbc = memnew(HBoxContainer); |
4191 | vbc->add_child(hbc); |
4192 | hbc->add_child(memnew(Label(TTR("Name:" )))); |
4193 | |
4194 | add_meta_name = memnew(LineEdit); |
4195 | add_meta_name->set_custom_minimum_size(Size2(200 * EDSCALE, 1)); |
4196 | hbc->add_child(add_meta_name); |
4197 | hbc->add_child(memnew(Label(TTR("Type:" )))); |
4198 | |
4199 | add_meta_type = memnew(OptionButton); |
4200 | for (int i = 0; i < Variant::VARIANT_MAX; i++) { |
4201 | if (i == Variant::NIL || i == Variant::RID || i == Variant::CALLABLE || i == Variant::SIGNAL) { |
4202 | continue; //not editable by inspector. |
4203 | } |
4204 | String type = i == Variant::OBJECT ? String("Resource" ) : Variant::get_type_name(Variant::Type(i)); |
4205 | |
4206 | add_meta_type->add_icon_item(get_editor_theme_icon(type), type, i); |
4207 | } |
4208 | hbc->add_child(add_meta_type); |
4209 | |
4210 | Control *spacing = memnew(Control); |
4211 | vbc->add_child(spacing); |
4212 | spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); |
4213 | |
4214 | add_meta_dialog->set_ok_button_text(TTR("Add" )); |
4215 | add_child(add_meta_dialog); |
4216 | add_meta_dialog->register_text_enter(add_meta_name); |
4217 | add_meta_dialog->connect("confirmed" , callable_mp(this, &EditorInspector::_add_meta_confirm)); |
4218 | |
4219 | validation_panel = memnew(EditorValidationPanel); |
4220 | vbc->add_child(validation_panel); |
4221 | validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name is valid." )); |
4222 | validation_panel->set_update_callback(callable_mp(this, &EditorInspector::_check_meta_name)); |
4223 | validation_panel->set_accept_button(add_meta_dialog->get_ok_button()); |
4224 | |
4225 | add_meta_name->connect("text_changed" , callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); |
4226 | } |
4227 | |
4228 | Node *node = Object::cast_to<Node>(object); |
4229 | if (node) { |
4230 | add_meta_dialog->set_title(vformat(TTR("Add Metadata Property for \"%s\"" ), node->get_name())); |
4231 | } else { |
4232 | // This should normally be reached when the object is derived from Resource. |
4233 | add_meta_dialog->set_title(vformat(TTR("Add Metadata Property for \"%s\"" ), object->get_class())); |
4234 | } |
4235 | |
4236 | add_meta_dialog->popup_centered(); |
4237 | add_meta_name->grab_focus(); |
4238 | add_meta_name->set_text("" ); |
4239 | validation_panel->update(); |
4240 | } |
4241 | |
4242 | void EditorInspector::_bind_methods() { |
4243 | ClassDB::bind_method("_edit_request_change" , &EditorInspector::_edit_request_change); |
4244 | ClassDB::bind_method("get_selected_path" , &EditorInspector::get_selected_path); |
4245 | |
4246 | ADD_SIGNAL(MethodInfo("property_selected" , PropertyInfo(Variant::STRING, "property" ))); |
4247 | ADD_SIGNAL(MethodInfo("property_keyed" , PropertyInfo(Variant::STRING, "property" ), PropertyInfo(Variant::NIL, "value" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::BOOL, "advance" ))); |
4248 | ADD_SIGNAL(MethodInfo("property_deleted" , PropertyInfo(Variant::STRING, "property" ))); |
4249 | ADD_SIGNAL(MethodInfo("resource_selected" , PropertyInfo(Variant::OBJECT, "resource" , PROPERTY_HINT_RESOURCE_TYPE, "Resource" ), PropertyInfo(Variant::STRING, "path" ))); |
4250 | ADD_SIGNAL(MethodInfo("object_id_selected" , PropertyInfo(Variant::INT, "id" ))); |
4251 | ADD_SIGNAL(MethodInfo("property_edited" , PropertyInfo(Variant::STRING, "property" ))); |
4252 | ADD_SIGNAL(MethodInfo("property_toggled" , PropertyInfo(Variant::STRING, "property" ), PropertyInfo(Variant::BOOL, "checked" ))); |
4253 | ADD_SIGNAL(MethodInfo("edited_object_changed" )); |
4254 | ADD_SIGNAL(MethodInfo("restart_requested" )); |
4255 | } |
4256 | |
4257 | EditorInspector::EditorInspector() { |
4258 | object = nullptr; |
4259 | main_vbox = memnew(VBoxContainer); |
4260 | main_vbox->set_h_size_flags(SIZE_EXPAND_FILL); |
4261 | main_vbox->add_theme_constant_override("separation" , 0); |
4262 | add_child(main_vbox); |
4263 | set_horizontal_scroll_mode(SCROLL_MODE_DISABLED); |
4264 | |
4265 | changing = 0; |
4266 | search_box = nullptr; |
4267 | _prop_edited = "property_edited" ; |
4268 | set_process(false); |
4269 | property_focusable = -1; |
4270 | property_clipboard = Variant(); |
4271 | |
4272 | get_v_scroll_bar()->connect("value_changed" , callable_mp(this, &EditorInspector::_vscroll_changed)); |
4273 | update_scroll_request = -1; |
4274 | if (EditorSettings::get_singleton()) { |
4275 | refresh_countdown = float(EDITOR_GET("docks/property_editor/auto_refresh_interval" )); |
4276 | } else { |
4277 | //used when class is created by the docgen to dump default values of everything bindable, editorsettings may not be created |
4278 | refresh_countdown = 0.33; |
4279 | } |
4280 | |
4281 | ED_SHORTCUT("property_editor/copy_value" , TTR("Copy Value" ), KeyModifierMask::CMD_OR_CTRL | Key::C); |
4282 | ED_SHORTCUT("property_editor/paste_value" , TTR("Paste Value" ), KeyModifierMask::CMD_OR_CTRL | Key::V); |
4283 | ED_SHORTCUT("property_editor/copy_property_path" , TTR("Copy Property Path" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C); |
4284 | |
4285 | // `use_settings_name_style` is true by default, set the name style accordingly. |
4286 | set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style()); |
4287 | } |
4288 | |