1 | /**************************************************************************/ |
2 | /* editor_properties_array_dict.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_properties_array_dict.h" |
32 | |
33 | #include "core/input/input.h" |
34 | #include "core/io/marshalls.h" |
35 | #include "editor/editor_properties.h" |
36 | #include "editor/editor_properties_vector.h" |
37 | #include "editor/editor_scale.h" |
38 | #include "editor/editor_settings.h" |
39 | #include "editor/editor_string_names.h" |
40 | #include "editor/gui/editor_spin_slider.h" |
41 | #include "editor/inspector_dock.h" |
42 | #include "scene/gui/button.h" |
43 | #include "scene/resources/packed_scene.h" |
44 | |
45 | bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) { |
46 | String name = p_name; |
47 | |
48 | if (!name.begins_with("indices" )) { |
49 | return false; |
50 | } |
51 | |
52 | int index; |
53 | if (name.begins_with("metadata/" )) { |
54 | index = name.get_slice("/" , 2).to_int(); |
55 | } else { |
56 | index = name.get_slice("/" , 1).to_int(); |
57 | } |
58 | |
59 | array.set(index, p_value); |
60 | return true; |
61 | } |
62 | |
63 | bool EditorPropertyArrayObject::_get(const StringName &p_name, Variant &r_ret) const { |
64 | String name = p_name; |
65 | |
66 | if (!name.begins_with("indices" )) { |
67 | return false; |
68 | } |
69 | |
70 | int index; |
71 | if (name.begins_with("metadata/" )) { |
72 | index = name.get_slice("/" , 2).to_int(); |
73 | } else { |
74 | index = name.get_slice("/" , 1).to_int(); |
75 | } |
76 | |
77 | bool valid; |
78 | r_ret = array.get(index, &valid); |
79 | |
80 | if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) { |
81 | r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id(); |
82 | } |
83 | |
84 | return valid; |
85 | } |
86 | |
87 | void EditorPropertyArrayObject::set_array(const Variant &p_array) { |
88 | array = p_array; |
89 | } |
90 | |
91 | Variant EditorPropertyArrayObject::get_array() { |
92 | return array; |
93 | } |
94 | |
95 | EditorPropertyArrayObject::EditorPropertyArrayObject() { |
96 | } |
97 | |
98 | /////////////////// |
99 | |
100 | bool EditorPropertyDictionaryObject::_set(const StringName &p_name, const Variant &p_value) { |
101 | String name = p_name; |
102 | |
103 | if (name == "new_item_key" ) { |
104 | new_item_key = p_value; |
105 | return true; |
106 | } |
107 | |
108 | if (name == "new_item_value" ) { |
109 | new_item_value = p_value; |
110 | return true; |
111 | } |
112 | |
113 | if (name.begins_with("indices" )) { |
114 | int index = name.get_slicec('/', 1).to_int(); |
115 | Variant key = dict.get_key_at_index(index); |
116 | dict[key] = p_value; |
117 | return true; |
118 | } |
119 | |
120 | return false; |
121 | } |
122 | |
123 | bool EditorPropertyDictionaryObject::_get(const StringName &p_name, Variant &r_ret) const { |
124 | String name = p_name; |
125 | |
126 | if (name == "new_item_key" ) { |
127 | r_ret = new_item_key; |
128 | return true; |
129 | } |
130 | |
131 | if (name == "new_item_value" ) { |
132 | r_ret = new_item_value; |
133 | return true; |
134 | } |
135 | |
136 | if (name.begins_with("indices" )) { |
137 | int index = name.get_slicec('/', 1).to_int(); |
138 | Variant key = dict.get_key_at_index(index); |
139 | r_ret = dict[key]; |
140 | if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) { |
141 | r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id(); |
142 | } |
143 | |
144 | return true; |
145 | } |
146 | |
147 | return false; |
148 | } |
149 | |
150 | void EditorPropertyDictionaryObject::set_dict(const Dictionary &p_dict) { |
151 | dict = p_dict; |
152 | } |
153 | |
154 | Dictionary EditorPropertyDictionaryObject::get_dict() { |
155 | return dict; |
156 | } |
157 | |
158 | void EditorPropertyDictionaryObject::set_new_item_key(const Variant &p_new_item) { |
159 | new_item_key = p_new_item; |
160 | } |
161 | |
162 | Variant EditorPropertyDictionaryObject::get_new_item_key() { |
163 | return new_item_key; |
164 | } |
165 | |
166 | void EditorPropertyDictionaryObject::set_new_item_value(const Variant &p_new_item) { |
167 | new_item_value = p_new_item; |
168 | } |
169 | |
170 | Variant EditorPropertyDictionaryObject::get_new_item_value() { |
171 | return new_item_value; |
172 | } |
173 | |
174 | EditorPropertyDictionaryObject::EditorPropertyDictionaryObject() { |
175 | } |
176 | |
177 | ///////////////////// ARRAY /////////////////////////// |
178 | |
179 | void EditorPropertyArray::initialize_array(Variant &p_array) { |
180 | if (array_type == Variant::ARRAY && subtype != Variant::NIL) { |
181 | Array array; |
182 | StringName subtype_class; |
183 | Ref<Script> subtype_script; |
184 | if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) { |
185 | if (ClassDB::class_exists(subtype_hint_string)) { |
186 | subtype_class = subtype_hint_string; |
187 | } |
188 | } |
189 | array.set_typed(subtype, subtype_class, subtype_script); |
190 | p_array = array; |
191 | } else { |
192 | VariantInternal::initialize(&p_array, array_type); |
193 | } |
194 | } |
195 | |
196 | void EditorPropertyArray::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { |
197 | if (!p_property.begins_with("indices" )) { |
198 | return; |
199 | } |
200 | |
201 | int index; |
202 | if (p_property.begins_with("metadata/" )) { |
203 | index = p_property.get_slice("/" , 2).to_int(); |
204 | } else { |
205 | index = p_property.get_slice("/" , 1).to_int(); |
206 | } |
207 | |
208 | Variant array = object->get_array().duplicate(); |
209 | array.set(index, p_value); |
210 | object->set_array(array); |
211 | emit_changed(get_edited_property(), array, "" , true); |
212 | } |
213 | |
214 | void EditorPropertyArray::_change_type(Object *p_button, int p_index) { |
215 | Button *button = Object::cast_to<Button>(p_button); |
216 | changing_type_index = p_index; |
217 | Rect2 rect = button->get_screen_rect(); |
218 | change_type->reset_size(); |
219 | change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0)); |
220 | change_type->popup(); |
221 | } |
222 | |
223 | void EditorPropertyArray::(int p_index) { |
224 | if (p_index == Variant::VARIANT_MAX) { |
225 | _remove_pressed(changing_type_index); |
226 | return; |
227 | } |
228 | |
229 | Variant value; |
230 | VariantInternal::initialize(&value, Variant::Type(p_index)); |
231 | |
232 | Variant array = object->get_array().duplicate(); |
233 | array.set(changing_type_index, value); |
234 | |
235 | emit_changed(get_edited_property(), array, "" , true); |
236 | update_property(); |
237 | } |
238 | |
239 | void EditorPropertyArray::_object_id_selected(const StringName &p_property, ObjectID p_id) { |
240 | emit_signal(SNAME("object_id_selected" ), p_property, p_id); |
241 | } |
242 | |
243 | void EditorPropertyArray::update_property() { |
244 | Variant array = get_edited_property_value(); |
245 | |
246 | String array_type_name = Variant::get_type_name(array_type); |
247 | if (array_type == Variant::ARRAY && subtype != Variant::NIL) { |
248 | String type_name; |
249 | if (subtype == Variant::OBJECT && (subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || subtype_hint == PROPERTY_HINT_NODE_TYPE)) { |
250 | type_name = subtype_hint_string; |
251 | } else { |
252 | type_name = Variant::get_type_name(subtype); |
253 | } |
254 | |
255 | array_type_name = vformat("%s[%s]" , array_type_name, type_name); |
256 | } |
257 | |
258 | if (array.get_type() == Variant::NIL) { |
259 | edit->set_text(vformat(TTR("(Nil) %s" ), array_type_name)); |
260 | edit->set_pressed(false); |
261 | if (container) { |
262 | set_bottom_editor(nullptr); |
263 | memdelete(container); |
264 | button_add_item = nullptr; |
265 | container = nullptr; |
266 | } |
267 | return; |
268 | } |
269 | |
270 | object->set_array(array); |
271 | |
272 | int size = array.call("size" ); |
273 | int max_page = MAX(0, size - 1) / page_length; |
274 | page_index = MIN(page_index, max_page); |
275 | int offset = page_index * page_length; |
276 | |
277 | edit->set_text(vformat(TTR("%s (size %s)" ), array_type_name, itos(size))); |
278 | |
279 | bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); |
280 | if (edit->is_pressed() != unfolded) { |
281 | edit->set_pressed(unfolded); |
282 | } |
283 | |
284 | if (unfolded) { |
285 | updating = true; |
286 | |
287 | if (!container) { |
288 | container = memnew(MarginContainer); |
289 | container->set_theme_type_variation("MarginContainer4px" ); |
290 | add_child(container); |
291 | set_bottom_editor(container); |
292 | |
293 | VBoxContainer *vbox = memnew(VBoxContainer); |
294 | container->add_child(vbox); |
295 | |
296 | HBoxContainer *hbox = memnew(HBoxContainer); |
297 | vbox->add_child(hbox); |
298 | |
299 | Label *size_label = memnew(Label(TTR("Size:" ))); |
300 | size_label->set_h_size_flags(SIZE_EXPAND_FILL); |
301 | hbox->add_child(size_label); |
302 | |
303 | size_slider = memnew(EditorSpinSlider); |
304 | size_slider->set_step(1); |
305 | size_slider->set_max(INT32_MAX); |
306 | size_slider->set_h_size_flags(SIZE_EXPAND_FILL); |
307 | size_slider->set_read_only(is_read_only()); |
308 | size_slider->connect("value_changed" , callable_mp(this, &EditorPropertyArray::_length_changed)); |
309 | hbox->add_child(size_slider); |
310 | |
311 | property_vbox = memnew(VBoxContainer); |
312 | property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); |
313 | vbox->add_child(property_vbox); |
314 | |
315 | button_add_item = EditorInspector::create_inspector_action_button(TTR("Add Element" )); |
316 | button_add_item->set_icon(get_editor_theme_icon(SNAME("Add" ))); |
317 | button_add_item->connect(SNAME("pressed" ), callable_mp(this, &EditorPropertyArray::_add_element)); |
318 | button_add_item->set_disabled(is_read_only()); |
319 | vbox->add_child(button_add_item); |
320 | |
321 | paginator = memnew(EditorPaginator); |
322 | paginator->connect("page_changed" , callable_mp(this, &EditorPropertyArray::_page_changed)); |
323 | vbox->add_child(paginator); |
324 | } else { |
325 | // Bye bye children of the box. |
326 | for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { |
327 | Node *child = property_vbox->get_child(i); |
328 | if (child == reorder_selected_element_hbox) { |
329 | continue; // Don't remove the property that the user is moving. |
330 | } |
331 | |
332 | child->queue_free(); // Button still needed after pressed is called. |
333 | property_vbox->remove_child(child); |
334 | } |
335 | } |
336 | |
337 | size_slider->set_value(size); |
338 | property_vbox->set_visible(size > 0); |
339 | button_add_item->set_visible(page_index == max_page); |
340 | paginator->update(page_index, max_page); |
341 | paginator->set_visible(max_page > 0); |
342 | |
343 | int amount = MIN(size - offset, page_length); |
344 | for (int i = 0; i < amount; i++) { |
345 | bool reorder_is_from_current_page = reorder_from_index / page_length == page_index; |
346 | if (reorder_is_from_current_page && i == reorder_from_index % page_length) { |
347 | // Don't duplicate the property that the user is moving. |
348 | continue; |
349 | } |
350 | if (!reorder_is_from_current_page && i == reorder_to_index % page_length) { |
351 | // Don't create the property the moving property will take the place of, |
352 | // e.g. (if page_length == 20) don't create element 20 if dragging an item from |
353 | // the first page to the second page because element 20 would become element 19. |
354 | continue; |
355 | } |
356 | |
357 | HBoxContainer *hbox = memnew(HBoxContainer); |
358 | property_vbox->add_child(hbox); |
359 | |
360 | Button *reorder_button = memnew(Button); |
361 | reorder_button->set_icon(get_editor_theme_icon(SNAME("TripleBar" ))); |
362 | reorder_button->set_default_cursor_shape(Control::CURSOR_MOVE); |
363 | reorder_button->set_disabled(is_read_only()); |
364 | reorder_button->connect("gui_input" , callable_mp(this, &EditorPropertyArray::_reorder_button_gui_input)); |
365 | reorder_button->connect("button_down" , callable_mp(this, &EditorPropertyArray::_reorder_button_down).bind(i + offset)); |
366 | reorder_button->connect("button_up" , callable_mp(this, &EditorPropertyArray::_reorder_button_up)); |
367 | hbox->add_child(reorder_button); |
368 | |
369 | String prop_name = "indices/" + itos(i + offset); |
370 | |
371 | EditorProperty *prop = nullptr; |
372 | Variant value = array.get(i + offset); |
373 | Variant::Type value_type = value.get_type(); |
374 | |
375 | if (value_type == Variant::NIL && subtype != Variant::NIL) { |
376 | value_type = subtype; |
377 | } |
378 | |
379 | if (value_type == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(value)) { |
380 | EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID); |
381 | editor->setup("Object" ); |
382 | prop = editor; |
383 | } else { |
384 | prop = EditorInspector::instantiate_property_editor(nullptr, value_type, "" , subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE); |
385 | } |
386 | |
387 | prop->set_object_and_property(object.ptr(), prop_name); |
388 | prop->set_label(itos(i + offset)); |
389 | prop->set_selectable(false); |
390 | prop->set_use_folding(is_using_folding()); |
391 | prop->connect("property_changed" , callable_mp(this, &EditorPropertyArray::_property_changed)); |
392 | prop->connect("object_id_selected" , callable_mp(this, &EditorPropertyArray::_object_id_selected)); |
393 | prop->set_h_size_flags(SIZE_EXPAND_FILL); |
394 | prop->set_read_only(is_read_only()); |
395 | hbox->add_child(prop); |
396 | |
397 | bool is_untyped_array = array.get_type() == Variant::ARRAY && subtype == Variant::NIL; |
398 | |
399 | if (is_untyped_array) { |
400 | Button *edit_btn = memnew(Button); |
401 | edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit" ))); |
402 | hbox->add_child(edit_btn); |
403 | edit_btn->set_disabled(is_read_only()); |
404 | edit_btn->connect("pressed" , callable_mp(this, &EditorPropertyArray::_change_type).bind(edit_btn, i + offset)); |
405 | } else { |
406 | Button *remove_btn = memnew(Button); |
407 | remove_btn->set_icon(get_editor_theme_icon(SNAME("Remove" ))); |
408 | remove_btn->set_disabled(is_read_only()); |
409 | remove_btn->connect("pressed" , callable_mp(this, &EditorPropertyArray::_remove_pressed).bind(i + offset)); |
410 | hbox->add_child(remove_btn); |
411 | } |
412 | |
413 | prop->update_property(); |
414 | } |
415 | |
416 | if (reorder_to_index % page_length > 0) { |
417 | property_vbox->move_child(property_vbox->get_child(0), reorder_to_index % page_length); |
418 | } |
419 | |
420 | updating = false; |
421 | |
422 | } else { |
423 | if (container) { |
424 | set_bottom_editor(nullptr); |
425 | memdelete(container); |
426 | button_add_item = nullptr; |
427 | container = nullptr; |
428 | } |
429 | } |
430 | } |
431 | |
432 | void EditorPropertyArray::_remove_pressed(int p_index) { |
433 | Variant array = object->get_array().duplicate(); |
434 | array.call("remove_at" , p_index); |
435 | |
436 | emit_changed(get_edited_property(), array, "" , false); |
437 | update_property(); |
438 | } |
439 | |
440 | void EditorPropertyArray::_button_draw() { |
441 | if (dropping) { |
442 | Color color = get_theme_color(SNAME("accent_color" ), EditorStringName(Editor)); |
443 | edit->draw_rect(Rect2(Point2(), edit->get_size()), color, false); |
444 | } |
445 | } |
446 | |
447 | bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const { |
448 | if (is_read_only()) { |
449 | return false; |
450 | } |
451 | |
452 | String allowed_type = Variant::get_type_name(subtype); |
453 | |
454 | // When the subtype is of type Object, an additional subtype may be specified in the hint string |
455 | // (e.g. Resource, Texture2D, ShaderMaterial, etc). We want the allowed type to be that, not just "Object". |
456 | if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) { |
457 | allowed_type = subtype_hint_string; |
458 | } |
459 | |
460 | Dictionary drag_data = p_drag_data; |
461 | |
462 | if (drag_data.has("type" ) && String(drag_data["type" ]) == "files" ) { |
463 | Vector<String> files = drag_data["files" ]; |
464 | |
465 | for (int i = 0; i < files.size(); i++) { |
466 | String file = files[i]; |
467 | String ftype = EditorFileSystem::get_singleton()->get_file_type(file); |
468 | |
469 | for (int j = 0; j < allowed_type.get_slice_count("," ); j++) { |
470 | String at = allowed_type.get_slice("," , j).strip_edges(); |
471 | // Fail if one of the files is not of allowed type. |
472 | if (!ClassDB::is_parent_class(ftype, at)) { |
473 | return false; |
474 | } |
475 | } |
476 | } |
477 | |
478 | // If no files fail, drop is valid. |
479 | return true; |
480 | } |
481 | |
482 | return false; |
483 | } |
484 | |
485 | bool EditorPropertyArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { |
486 | return _is_drop_valid(p_data); |
487 | } |
488 | |
489 | void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { |
490 | ERR_FAIL_COND(!_is_drop_valid(p_data)); |
491 | |
492 | Dictionary drag_data = p_data; |
493 | |
494 | if (drag_data.has("type" ) && String(drag_data["type" ]) == "files" ) { |
495 | Vector<String> files = drag_data["files" ]; |
496 | |
497 | Variant array = object->get_array(); |
498 | |
499 | // Handle the case where array is not initialized yet. |
500 | if (!array.is_array()) { |
501 | initialize_array(array); |
502 | } else { |
503 | array = array.duplicate(); |
504 | } |
505 | |
506 | // Loop the file array and add to existing array. |
507 | for (int i = 0; i < files.size(); i++) { |
508 | String file = files[i]; |
509 | |
510 | Ref<Resource> res = ResourceLoader::load(file); |
511 | if (res.is_valid()) { |
512 | array.call("push_back" , res); |
513 | } |
514 | } |
515 | |
516 | emit_changed(get_edited_property(), array, "" , false); |
517 | update_property(); |
518 | } |
519 | } |
520 | |
521 | void EditorPropertyArray::_notification(int p_what) { |
522 | switch (p_what) { |
523 | case NOTIFICATION_THEME_CHANGED: |
524 | case NOTIFICATION_ENTER_TREE: { |
525 | change_type->clear(); |
526 | for (int i = 0; i < Variant::VARIANT_MAX; i++) { |
527 | if (i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) { |
528 | // These types can't be constructed or serialized properly, so skip them. |
529 | continue; |
530 | } |
531 | |
532 | String type = Variant::get_type_name(Variant::Type(i)); |
533 | change_type->add_icon_item(get_editor_theme_icon(type), type, i); |
534 | } |
535 | change_type->add_separator(); |
536 | change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove" )), TTR("Remove Item" ), Variant::VARIANT_MAX); |
537 | |
538 | if (button_add_item) { |
539 | button_add_item->set_icon(get_editor_theme_icon(SNAME("Add" ))); |
540 | } |
541 | } break; |
542 | |
543 | case NOTIFICATION_DRAG_BEGIN: { |
544 | if (is_visible_in_tree()) { |
545 | if (_is_drop_valid(get_viewport()->gui_get_drag_data())) { |
546 | dropping = true; |
547 | edit->queue_redraw(); |
548 | } |
549 | } |
550 | } break; |
551 | |
552 | case NOTIFICATION_DRAG_END: { |
553 | if (dropping) { |
554 | dropping = false; |
555 | edit->queue_redraw(); |
556 | } |
557 | } break; |
558 | } |
559 | } |
560 | |
561 | void EditorPropertyArray::_edit_pressed() { |
562 | Variant array = get_edited_property_value(); |
563 | if (!array.is_array() && edit->is_pressed()) { |
564 | initialize_array(array); |
565 | get_edited_object()->set(get_edited_property(), array); |
566 | } |
567 | |
568 | get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed()); |
569 | update_property(); |
570 | } |
571 | |
572 | void EditorPropertyArray::_page_changed(int p_page) { |
573 | if (updating) { |
574 | return; |
575 | } |
576 | page_index = p_page; |
577 | update_property(); |
578 | } |
579 | |
580 | void EditorPropertyArray::_length_changed(double p_page) { |
581 | if (updating) { |
582 | return; |
583 | } |
584 | |
585 | Variant array = object->get_array().duplicate(); |
586 | array.call("resize" , int(p_page)); |
587 | |
588 | emit_changed(get_edited_property(), array, "" , false); |
589 | update_property(); |
590 | } |
591 | |
592 | void EditorPropertyArray::_add_element() { |
593 | _length_changed(double(object->get_array().call("size" )) + 1.0); |
594 | } |
595 | |
596 | void EditorPropertyArray::setup(Variant::Type p_array_type, const String &p_hint_string) { |
597 | array_type = p_array_type; |
598 | |
599 | // The format of p_hint_string is: |
600 | // subType/subTypeHint:nextSubtype ... etc. |
601 | if (!p_hint_string.is_empty()) { |
602 | int hint_subtype_separator = p_hint_string.find(":" ); |
603 | if (hint_subtype_separator >= 0) { |
604 | String subtype_string = p_hint_string.substr(0, hint_subtype_separator); |
605 | int slash_pos = subtype_string.find("/" ); |
606 | if (slash_pos >= 0) { |
607 | subtype_hint = PropertyHint(subtype_string.substr(slash_pos + 1, subtype_string.size() - slash_pos - 1).to_int()); |
608 | subtype_string = subtype_string.substr(0, slash_pos); |
609 | } |
610 | |
611 | subtype_hint_string = p_hint_string.substr(hint_subtype_separator + 1, p_hint_string.size() - hint_subtype_separator - 1); |
612 | subtype = Variant::Type(subtype_string.to_int()); |
613 | } |
614 | } |
615 | } |
616 | |
617 | void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_event) { |
618 | if (reorder_from_index < 0 || is_read_only()) { |
619 | return; |
620 | } |
621 | |
622 | Ref<InputEventMouseMotion> mm = p_event; |
623 | if (mm.is_valid()) { |
624 | Variant array = object->get_array(); |
625 | int size = array.call("size" ); |
626 | |
627 | // Cumulate the mouse delta, many small changes (dragging slowly) should result in reordering at some point. |
628 | reorder_mouse_y_delta += mm->get_relative().y; |
629 | |
630 | // Reordering is done by moving the dragged element by +1/-1 index at a time based on the cumulated mouse delta so if |
631 | // already at the array bounds make sure to ignore the remaining out of bounds drag (by resetting the cumulated delta). |
632 | if ((reorder_to_index == 0 && reorder_mouse_y_delta < 0.0f) || (reorder_to_index == size - 1 && reorder_mouse_y_delta > 0.0f)) { |
633 | reorder_mouse_y_delta = 0.0f; |
634 | return; |
635 | } |
636 | |
637 | float required_y_distance = 20.0f * EDSCALE; |
638 | if (ABS(reorder_mouse_y_delta) > required_y_distance) { |
639 | int direction = reorder_mouse_y_delta > 0.0f ? 1 : -1; |
640 | reorder_mouse_y_delta -= required_y_distance * direction; |
641 | |
642 | reorder_to_index += direction; |
643 | if ((direction < 0 && reorder_to_index % page_length == page_length - 1) || (direction > 0 && reorder_to_index % page_length == 0)) { |
644 | // Automatically move to the next/previous page. |
645 | _page_changed(page_index + direction); |
646 | } |
647 | property_vbox->move_child(reorder_selected_element_hbox, reorder_to_index % page_length); |
648 | // Ensure the moving element is visible. |
649 | InspectorDock::get_inspector_singleton()->ensure_control_visible(reorder_selected_element_hbox); |
650 | } |
651 | } |
652 | } |
653 | |
654 | void EditorPropertyArray::_reorder_button_down(int p_index) { |
655 | if (is_read_only()) { |
656 | return; |
657 | } |
658 | |
659 | reorder_from_index = p_index; |
660 | reorder_to_index = p_index; |
661 | reorder_selected_element_hbox = Object::cast_to<HBoxContainer>(property_vbox->get_child(p_index % page_length)); |
662 | reorder_selected_button = Object::cast_to<Button>(reorder_selected_element_hbox->get_child(0)); |
663 | // Ideally it'd to be able to show the mouse but I had issues with |
664 | // Control's `mouse_exit()`/`mouse_entered()` signals not getting called. |
665 | Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); |
666 | } |
667 | |
668 | void EditorPropertyArray::_reorder_button_up() { |
669 | if (is_read_only()) { |
670 | return; |
671 | } |
672 | |
673 | if (reorder_from_index != reorder_to_index) { |
674 | // Move the element. |
675 | Variant array = object->get_array().duplicate(); |
676 | |
677 | Variant value_to_move = array.get(reorder_from_index); |
678 | array.call("remove_at" , reorder_from_index); |
679 | array.call("insert" , reorder_to_index, value_to_move); |
680 | |
681 | emit_changed(get_edited_property(), array, "" , false); |
682 | update_property(); |
683 | } |
684 | |
685 | reorder_from_index = -1; |
686 | reorder_to_index = -1; |
687 | reorder_mouse_y_delta = 0.0f; |
688 | |
689 | Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); |
690 | |
691 | ERR_FAIL_NULL(reorder_selected_button); |
692 | reorder_selected_button->warp_mouse(reorder_selected_button->get_size() / 2.0f); |
693 | |
694 | reorder_selected_element_hbox = nullptr; |
695 | reorder_selected_button = nullptr; |
696 | } |
697 | |
698 | void EditorPropertyArray::_bind_methods() { |
699 | } |
700 | |
701 | EditorPropertyArray::EditorPropertyArray() { |
702 | object.instantiate(); |
703 | page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page" )); |
704 | |
705 | edit = memnew(Button); |
706 | edit->set_h_size_flags(SIZE_EXPAND_FILL); |
707 | edit->set_clip_text(true); |
708 | edit->connect("pressed" , callable_mp(this, &EditorPropertyArray::_edit_pressed)); |
709 | edit->set_toggle_mode(true); |
710 | SET_DRAG_FORWARDING_CD(edit, EditorPropertyArray); |
711 | edit->connect("draw" , callable_mp(this, &EditorPropertyArray::_button_draw)); |
712 | add_child(edit); |
713 | add_focusable(edit); |
714 | |
715 | change_type = memnew(PopupMenu); |
716 | add_child(change_type); |
717 | change_type->connect("id_pressed" , callable_mp(this, &EditorPropertyArray::_change_type_menu)); |
718 | changing_type_index = -1; |
719 | |
720 | subtype = Variant::NIL; |
721 | subtype_hint = PROPERTY_HINT_NONE; |
722 | subtype_hint_string = "" ; |
723 | } |
724 | |
725 | ///////////////////// DICTIONARY /////////////////////////// |
726 | |
727 | void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { |
728 | if (p_property == "new_item_key" ) { |
729 | object->set_new_item_key(p_value); |
730 | } else if (p_property == "new_item_value" ) { |
731 | object->set_new_item_value(p_value); |
732 | } else if (p_property.begins_with("indices" )) { |
733 | int index = p_property.get_slice("/" , 1).to_int(); |
734 | |
735 | Dictionary dict = object->get_dict().duplicate(); |
736 | Variant key = dict.get_key_at_index(index); |
737 | dict[key] = p_value; |
738 | |
739 | object->set_dict(dict); |
740 | emit_changed(get_edited_property(), dict, "" , true); |
741 | } |
742 | } |
743 | |
744 | void EditorPropertyDictionary::_change_type(Object *p_button, int p_index) { |
745 | Button *button = Object::cast_to<Button>(p_button); |
746 | |
747 | Rect2 rect = button->get_screen_rect(); |
748 | change_type->set_item_disabled(change_type->get_item_index(Variant::VARIANT_MAX), p_index < 0); |
749 | change_type->reset_size(); |
750 | change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0)); |
751 | change_type->popup(); |
752 | changing_type_index = p_index; |
753 | } |
754 | |
755 | void EditorPropertyDictionary::_add_key_value() { |
756 | // Do not allow nil as valid key. I experienced errors with this |
757 | if (object->get_new_item_key().get_type() == Variant::NIL) { |
758 | return; |
759 | } |
760 | |
761 | Dictionary dict = object->get_dict().duplicate(); |
762 | dict[object->get_new_item_key()] = object->get_new_item_value(); |
763 | object->set_new_item_key(Variant()); |
764 | object->set_new_item_value(Variant()); |
765 | |
766 | emit_changed(get_edited_property(), dict, "" , false); |
767 | update_property(); |
768 | } |
769 | |
770 | void EditorPropertyDictionary::(int p_index) { |
771 | if (changing_type_index < 0) { |
772 | Variant value; |
773 | VariantInternal::initialize(&value, Variant::Type(p_index)); |
774 | if (changing_type_index == -1) { |
775 | object->set_new_item_key(value); |
776 | } else { |
777 | object->set_new_item_value(value); |
778 | } |
779 | update_property(); |
780 | return; |
781 | } |
782 | |
783 | Dictionary dict = object->get_dict().duplicate(); |
784 | if (p_index < Variant::VARIANT_MAX) { |
785 | Variant value; |
786 | VariantInternal::initialize(&value, Variant::Type(p_index)); |
787 | Variant key = dict.get_key_at_index(changing_type_index); |
788 | dict[key] = value; |
789 | } else { |
790 | Variant key = dict.get_key_at_index(changing_type_index); |
791 | dict.erase(key); |
792 | } |
793 | |
794 | emit_changed(get_edited_property(), dict, "" , false); |
795 | update_property(); |
796 | } |
797 | |
798 | void EditorPropertyDictionary::setup(PropertyHint p_hint) { |
799 | property_hint = p_hint; |
800 | } |
801 | |
802 | void EditorPropertyDictionary::update_property() { |
803 | Variant updated_val = get_edited_property_value(); |
804 | |
805 | if (updated_val.get_type() == Variant::NIL) { |
806 | edit->set_text(TTR("Dictionary (Nil)" )); // This provides symmetry with the array property. |
807 | edit->set_pressed(false); |
808 | if (container) { |
809 | set_bottom_editor(nullptr); |
810 | memdelete(container); |
811 | button_add_item = nullptr; |
812 | container = nullptr; |
813 | } |
814 | return; |
815 | } |
816 | |
817 | Dictionary dict = updated_val; |
818 | object->set_dict(updated_val); |
819 | |
820 | edit->set_text(vformat(TTR("Dictionary (size %d)" ), dict.size())); |
821 | |
822 | bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); |
823 | if (edit->is_pressed() != unfolded) { |
824 | edit->set_pressed(unfolded); |
825 | } |
826 | |
827 | if (unfolded) { |
828 | updating = true; |
829 | |
830 | if (!container) { |
831 | container = memnew(MarginContainer); |
832 | container->set_theme_type_variation("MarginContainer4px" ); |
833 | add_child(container); |
834 | set_bottom_editor(container); |
835 | |
836 | VBoxContainer *vbox = memnew(VBoxContainer); |
837 | container->add_child(vbox); |
838 | |
839 | property_vbox = memnew(VBoxContainer); |
840 | property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); |
841 | vbox->add_child(property_vbox); |
842 | |
843 | paginator = memnew(EditorPaginator); |
844 | paginator->connect("page_changed" , callable_mp(this, &EditorPropertyDictionary::_page_changed)); |
845 | vbox->add_child(paginator); |
846 | } else { |
847 | // Queue children for deletion, deleting immediately might cause errors. |
848 | for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { |
849 | property_vbox->get_child(i)->queue_free(); |
850 | } |
851 | } |
852 | |
853 | int size = dict.size(); |
854 | |
855 | int max_page = MAX(0, size - 1) / page_length; |
856 | page_index = MIN(page_index, max_page); |
857 | |
858 | paginator->update(page_index, max_page); |
859 | paginator->set_visible(max_page > 0); |
860 | |
861 | int offset = page_index * page_length; |
862 | |
863 | int amount = MIN(size - offset, page_length); |
864 | int total_amount = page_index == max_page ? amount + 2 : amount; // For the "Add Key/Value Pair" box on last page. |
865 | |
866 | VBoxContainer *add_vbox = nullptr; |
867 | double default_float_step = EDITOR_GET("interface/inspector/default_float_step" ); |
868 | |
869 | for (int i = 0; i < total_amount; i++) { |
870 | String prop_name; |
871 | Variant key; |
872 | Variant value; |
873 | |
874 | if (i < amount) { |
875 | prop_name = "indices/" + itos(i + offset); |
876 | key = dict.get_key_at_index(i + offset); |
877 | value = dict.get_value_at_index(i + offset); |
878 | } else if (i == amount) { |
879 | prop_name = "new_item_key" ; |
880 | value = object->get_new_item_key(); |
881 | } else if (i == amount + 1) { |
882 | prop_name = "new_item_value" ; |
883 | value = object->get_new_item_value(); |
884 | } |
885 | |
886 | EditorProperty *prop = nullptr; |
887 | |
888 | switch (value.get_type()) { |
889 | case Variant::NIL: { |
890 | prop = memnew(EditorPropertyNil); |
891 | |
892 | } break; |
893 | |
894 | // Atomic types. |
895 | case Variant::BOOL: { |
896 | prop = memnew(EditorPropertyCheck); |
897 | |
898 | } break; |
899 | case Variant::INT: { |
900 | EditorPropertyInteger *editor = memnew(EditorPropertyInteger); |
901 | editor->setup(-100000, 100000, 1, false, true, true); |
902 | prop = editor; |
903 | |
904 | } break; |
905 | case Variant::FLOAT: { |
906 | EditorPropertyFloat *editor = memnew(EditorPropertyFloat); |
907 | editor->setup(-100000, 100000, default_float_step, true, false, true, true); |
908 | prop = editor; |
909 | } break; |
910 | case Variant::STRING: { |
911 | if (i != amount && property_hint == PROPERTY_HINT_MULTILINE_TEXT) { |
912 | // If this is NOT the new key field and there's a multiline hint, |
913 | // show the field as multiline |
914 | prop = memnew(EditorPropertyMultilineText); |
915 | } else { |
916 | prop = memnew(EditorPropertyText); |
917 | } |
918 | |
919 | } break; |
920 | |
921 | // Math types. |
922 | case Variant::VECTOR2: { |
923 | EditorPropertyVector2 *editor = memnew(EditorPropertyVector2); |
924 | editor->setup(-100000, 100000, default_float_step, true); |
925 | prop = editor; |
926 | |
927 | } break; |
928 | case Variant::VECTOR2I: { |
929 | EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i); |
930 | editor->setup(-100000, 100000); |
931 | prop = editor; |
932 | |
933 | } break; |
934 | case Variant::RECT2: { |
935 | EditorPropertyRect2 *editor = memnew(EditorPropertyRect2); |
936 | editor->setup(-100000, 100000, default_float_step, true); |
937 | prop = editor; |
938 | |
939 | } break; |
940 | case Variant::RECT2I: { |
941 | EditorPropertyRect2i *editor = memnew(EditorPropertyRect2i); |
942 | editor->setup(-100000, 100000); |
943 | prop = editor; |
944 | |
945 | } break; |
946 | case Variant::VECTOR3: { |
947 | EditorPropertyVector3 *editor = memnew(EditorPropertyVector3); |
948 | editor->setup(-100000, 100000, default_float_step, true); |
949 | prop = editor; |
950 | |
951 | } break; |
952 | case Variant::VECTOR3I: { |
953 | EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i); |
954 | editor->setup(-100000, 100000); |
955 | prop = editor; |
956 | |
957 | } break; |
958 | case Variant::VECTOR4: { |
959 | EditorPropertyVector4 *editor = memnew(EditorPropertyVector4); |
960 | editor->setup(-100000, 100000, default_float_step, true); |
961 | prop = editor; |
962 | |
963 | } break; |
964 | case Variant::VECTOR4I: { |
965 | EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i); |
966 | editor->setup(-100000, 100000); |
967 | prop = editor; |
968 | |
969 | } break; |
970 | case Variant::TRANSFORM2D: { |
971 | EditorPropertyTransform2D *editor = memnew(EditorPropertyTransform2D); |
972 | editor->setup(-100000, 100000, default_float_step, true); |
973 | prop = editor; |
974 | |
975 | } break; |
976 | case Variant::PLANE: { |
977 | EditorPropertyPlane *editor = memnew(EditorPropertyPlane); |
978 | editor->setup(-100000, 100000, default_float_step, true); |
979 | prop = editor; |
980 | |
981 | } break; |
982 | case Variant::QUATERNION: { |
983 | EditorPropertyQuaternion *editor = memnew(EditorPropertyQuaternion); |
984 | editor->setup(-100000, 100000, default_float_step, true); |
985 | prop = editor; |
986 | |
987 | } break; |
988 | case Variant::AABB: { |
989 | EditorPropertyAABB *editor = memnew(EditorPropertyAABB); |
990 | editor->setup(-100000, 100000, default_float_step, true); |
991 | prop = editor; |
992 | |
993 | } break; |
994 | case Variant::BASIS: { |
995 | EditorPropertyBasis *editor = memnew(EditorPropertyBasis); |
996 | editor->setup(-100000, 100000, default_float_step, true); |
997 | prop = editor; |
998 | |
999 | } break; |
1000 | case Variant::TRANSFORM3D: { |
1001 | EditorPropertyTransform3D *editor = memnew(EditorPropertyTransform3D); |
1002 | editor->setup(-100000, 100000, default_float_step, true); |
1003 | prop = editor; |
1004 | |
1005 | } break; |
1006 | case Variant::PROJECTION: { |
1007 | EditorPropertyProjection *editor = memnew(EditorPropertyProjection); |
1008 | editor->setup(-100000, 100000, default_float_step, true); |
1009 | prop = editor; |
1010 | |
1011 | } break; |
1012 | |
1013 | // Miscellaneous types. |
1014 | case Variant::COLOR: { |
1015 | prop = memnew(EditorPropertyColor); |
1016 | |
1017 | } break; |
1018 | case Variant::STRING_NAME: { |
1019 | EditorPropertyText *ept = memnew(EditorPropertyText); |
1020 | ept->set_string_name(true); |
1021 | prop = ept; |
1022 | |
1023 | } break; |
1024 | case Variant::NODE_PATH: { |
1025 | prop = memnew(EditorPropertyNodePath); |
1026 | |
1027 | } break; |
1028 | case Variant::RID: { |
1029 | prop = memnew(EditorPropertyRID); |
1030 | |
1031 | } break; |
1032 | case Variant::SIGNAL: { |
1033 | prop = memnew(EditorPropertySignal); |
1034 | |
1035 | } break; |
1036 | case Variant::CALLABLE: { |
1037 | prop = memnew(EditorPropertyCallable); |
1038 | |
1039 | } break; |
1040 | case Variant::OBJECT: { |
1041 | if (Object::cast_to<EncodedObjectAsID>(value)) { |
1042 | EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID); |
1043 | editor->setup("Object" ); |
1044 | prop = editor; |
1045 | |
1046 | } else { |
1047 | EditorPropertyResource *editor = memnew(EditorPropertyResource); |
1048 | editor->setup(object.ptr(), prop_name, "Resource" ); |
1049 | editor->set_use_folding(is_using_folding()); |
1050 | prop = editor; |
1051 | } |
1052 | |
1053 | } break; |
1054 | case Variant::DICTIONARY: { |
1055 | prop = memnew(EditorPropertyDictionary); |
1056 | |
1057 | } break; |
1058 | case Variant::ARRAY: { |
1059 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1060 | editor->setup(Variant::ARRAY); |
1061 | prop = editor; |
1062 | } break; |
1063 | |
1064 | // Arrays. |
1065 | case Variant::PACKED_BYTE_ARRAY: { |
1066 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1067 | editor->setup(Variant::PACKED_BYTE_ARRAY); |
1068 | prop = editor; |
1069 | } break; |
1070 | case Variant::PACKED_INT32_ARRAY: { |
1071 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1072 | editor->setup(Variant::PACKED_INT32_ARRAY); |
1073 | prop = editor; |
1074 | } break; |
1075 | case Variant::PACKED_FLOAT32_ARRAY: { |
1076 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1077 | editor->setup(Variant::PACKED_FLOAT32_ARRAY); |
1078 | prop = editor; |
1079 | } break; |
1080 | case Variant::PACKED_INT64_ARRAY: { |
1081 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1082 | editor->setup(Variant::PACKED_INT64_ARRAY); |
1083 | prop = editor; |
1084 | } break; |
1085 | case Variant::PACKED_FLOAT64_ARRAY: { |
1086 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1087 | editor->setup(Variant::PACKED_FLOAT64_ARRAY); |
1088 | prop = editor; |
1089 | } break; |
1090 | case Variant::PACKED_STRING_ARRAY: { |
1091 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1092 | editor->setup(Variant::PACKED_STRING_ARRAY); |
1093 | prop = editor; |
1094 | } break; |
1095 | case Variant::PACKED_VECTOR2_ARRAY: { |
1096 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1097 | editor->setup(Variant::PACKED_VECTOR2_ARRAY); |
1098 | prop = editor; |
1099 | } break; |
1100 | case Variant::PACKED_VECTOR3_ARRAY: { |
1101 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1102 | editor->setup(Variant::PACKED_VECTOR3_ARRAY); |
1103 | prop = editor; |
1104 | } break; |
1105 | case Variant::PACKED_COLOR_ARRAY: { |
1106 | EditorPropertyArray *editor = memnew(EditorPropertyArray); |
1107 | editor->setup(Variant::PACKED_COLOR_ARRAY); |
1108 | prop = editor; |
1109 | } break; |
1110 | default: { |
1111 | } |
1112 | } |
1113 | |
1114 | ERR_FAIL_NULL(prop); |
1115 | |
1116 | prop->set_read_only(is_read_only()); |
1117 | |
1118 | if (i == amount) { |
1119 | PanelContainer *pc = memnew(PanelContainer); |
1120 | property_vbox->add_child(pc); |
1121 | pc->add_theme_style_override(SNAME("panel" ), get_theme_stylebox(SNAME("DictionaryAddItem" ), EditorStringName(EditorStyles))); |
1122 | |
1123 | add_vbox = memnew(VBoxContainer); |
1124 | pc->add_child(add_vbox); |
1125 | } |
1126 | prop->set_object_and_property(object.ptr(), prop_name); |
1127 | int change_index = 0; |
1128 | |
1129 | if (i < amount) { |
1130 | String cs = key.get_construct_string(); |
1131 | prop->set_label(key.get_construct_string()); |
1132 | prop->set_tooltip_text(cs); |
1133 | change_index = i + offset; |
1134 | } else if (i == amount) { |
1135 | prop->set_label(TTR("New Key:" )); |
1136 | change_index = -1; |
1137 | } else if (i == amount + 1) { |
1138 | prop->set_label(TTR("New Value:" )); |
1139 | change_index = -2; |
1140 | } |
1141 | |
1142 | prop->set_selectable(false); |
1143 | prop->connect("property_changed" , callable_mp(this, &EditorPropertyDictionary::_property_changed)); |
1144 | prop->connect("object_id_selected" , callable_mp(this, &EditorPropertyDictionary::_object_id_selected)); |
1145 | |
1146 | HBoxContainer *hbox = memnew(HBoxContainer); |
1147 | if (add_vbox) { |
1148 | add_vbox->add_child(hbox); |
1149 | } else { |
1150 | property_vbox->add_child(hbox); |
1151 | } |
1152 | hbox->add_child(prop); |
1153 | prop->set_h_size_flags(SIZE_EXPAND_FILL); |
1154 | Button *edit_btn = memnew(Button); |
1155 | edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit" ))); |
1156 | edit_btn->set_disabled(is_read_only()); |
1157 | hbox->add_child(edit_btn); |
1158 | edit_btn->connect("pressed" , callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, change_index)); |
1159 | |
1160 | prop->update_property(); |
1161 | |
1162 | if (i == amount + 1) { |
1163 | button_add_item = EditorInspector::create_inspector_action_button(TTR("Add Key/Value Pair" )); |
1164 | button_add_item->set_icon(get_editor_theme_icon(SNAME("Add" ))); |
1165 | button_add_item->set_disabled(is_read_only()); |
1166 | button_add_item->connect("pressed" , callable_mp(this, &EditorPropertyDictionary::_add_key_value)); |
1167 | add_vbox->add_child(button_add_item); |
1168 | } |
1169 | } |
1170 | |
1171 | updating = false; |
1172 | |
1173 | } else { |
1174 | if (container) { |
1175 | set_bottom_editor(nullptr); |
1176 | memdelete(container); |
1177 | button_add_item = nullptr; |
1178 | container = nullptr; |
1179 | } |
1180 | } |
1181 | } |
1182 | |
1183 | void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) { |
1184 | emit_signal(SNAME("object_id_selected" ), p_property, p_id); |
1185 | } |
1186 | |
1187 | void EditorPropertyDictionary::_notification(int p_what) { |
1188 | switch (p_what) { |
1189 | case NOTIFICATION_THEME_CHANGED: |
1190 | case NOTIFICATION_ENTER_TREE: { |
1191 | change_type->clear(); |
1192 | for (int i = 0; i < Variant::VARIANT_MAX; i++) { |
1193 | if (i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) { |
1194 | // These types can't be constructed or serialized properly, so skip them. |
1195 | continue; |
1196 | } |
1197 | |
1198 | String type = Variant::get_type_name(Variant::Type(i)); |
1199 | change_type->add_icon_item(get_editor_theme_icon(type), type, i); |
1200 | } |
1201 | change_type->add_separator(); |
1202 | change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove" )), TTR("Remove Item" ), Variant::VARIANT_MAX); |
1203 | |
1204 | if (button_add_item) { |
1205 | button_add_item->set_icon(get_editor_theme_icon(SNAME("Add" ))); |
1206 | } |
1207 | } break; |
1208 | } |
1209 | } |
1210 | |
1211 | void EditorPropertyDictionary::_edit_pressed() { |
1212 | Variant prop_val = get_edited_property_value(); |
1213 | if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) { |
1214 | VariantInternal::initialize(&prop_val, Variant::DICTIONARY); |
1215 | get_edited_object()->set(get_edited_property(), prop_val); |
1216 | } |
1217 | |
1218 | get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed()); |
1219 | update_property(); |
1220 | } |
1221 | |
1222 | void EditorPropertyDictionary::_page_changed(int p_page) { |
1223 | if (updating) { |
1224 | return; |
1225 | } |
1226 | page_index = p_page; |
1227 | update_property(); |
1228 | } |
1229 | |
1230 | void EditorPropertyDictionary::_bind_methods() { |
1231 | } |
1232 | |
1233 | EditorPropertyDictionary::EditorPropertyDictionary() { |
1234 | object.instantiate(); |
1235 | page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page" )); |
1236 | |
1237 | edit = memnew(Button); |
1238 | edit->set_h_size_flags(SIZE_EXPAND_FILL); |
1239 | edit->set_clip_text(true); |
1240 | edit->connect("pressed" , callable_mp(this, &EditorPropertyDictionary::_edit_pressed)); |
1241 | edit->set_toggle_mode(true); |
1242 | add_child(edit); |
1243 | add_focusable(edit); |
1244 | |
1245 | container = nullptr; |
1246 | button_add_item = nullptr; |
1247 | paginator = nullptr; |
1248 | change_type = memnew(PopupMenu); |
1249 | add_child(change_type); |
1250 | change_type->connect("id_pressed" , callable_mp(this, &EditorPropertyDictionary::_change_type_menu)); |
1251 | changing_type_index = -1; |
1252 | } |
1253 | |
1254 | ///////////////////// LOCALIZABLE STRING /////////////////////////// |
1255 | |
1256 | void EditorPropertyLocalizableString::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { |
1257 | if (p_property.begins_with("indices" )) { |
1258 | int index = p_property.get_slice("/" , 1).to_int(); |
1259 | |
1260 | Dictionary dict = object->get_dict().duplicate(); |
1261 | Variant key = dict.get_key_at_index(index); |
1262 | dict[key] = p_value; |
1263 | |
1264 | object->set_dict(dict); |
1265 | emit_changed(get_edited_property(), dict, "" , true); |
1266 | } |
1267 | } |
1268 | |
1269 | void EditorPropertyLocalizableString::() { |
1270 | locale_select->popup_locale_dialog(); |
1271 | } |
1272 | |
1273 | void EditorPropertyLocalizableString::_add_locale(const String &p_locale) { |
1274 | Dictionary dict = object->get_dict().duplicate(); |
1275 | object->set_new_item_key(p_locale); |
1276 | object->set_new_item_value(String()); |
1277 | dict[object->get_new_item_key()] = object->get_new_item_value(); |
1278 | |
1279 | emit_changed(get_edited_property(), dict, "" , false); |
1280 | update_property(); |
1281 | } |
1282 | |
1283 | void EditorPropertyLocalizableString::_remove_item(Object *p_button, int p_index) { |
1284 | Dictionary dict = object->get_dict().duplicate(); |
1285 | |
1286 | Variant key = dict.get_key_at_index(p_index); |
1287 | dict.erase(key); |
1288 | |
1289 | emit_changed(get_edited_property(), dict, "" , false); |
1290 | update_property(); |
1291 | } |
1292 | |
1293 | void EditorPropertyLocalizableString::update_property() { |
1294 | Variant updated_val = get_edited_property_value(); |
1295 | |
1296 | if (updated_val.get_type() == Variant::NIL) { |
1297 | edit->set_text(TTR("Localizable String (Nil)" )); // This provides symmetry with the array property. |
1298 | edit->set_pressed(false); |
1299 | if (container) { |
1300 | set_bottom_editor(nullptr); |
1301 | memdelete(container); |
1302 | button_add_item = nullptr; |
1303 | container = nullptr; |
1304 | } |
1305 | return; |
1306 | } |
1307 | |
1308 | Dictionary dict = updated_val; |
1309 | object->set_dict(dict); |
1310 | |
1311 | edit->set_text(vformat(TTR("Localizable String (size %d)" ), dict.size())); |
1312 | |
1313 | bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); |
1314 | if (edit->is_pressed() != unfolded) { |
1315 | edit->set_pressed(unfolded); |
1316 | } |
1317 | |
1318 | if (unfolded) { |
1319 | updating = true; |
1320 | |
1321 | if (!container) { |
1322 | container = memnew(MarginContainer); |
1323 | container->set_theme_type_variation("MarginContainer4px" ); |
1324 | add_child(container); |
1325 | set_bottom_editor(container); |
1326 | |
1327 | VBoxContainer *vbox = memnew(VBoxContainer); |
1328 | container->add_child(vbox); |
1329 | |
1330 | property_vbox = memnew(VBoxContainer); |
1331 | property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); |
1332 | vbox->add_child(property_vbox); |
1333 | |
1334 | paginator = memnew(EditorPaginator); |
1335 | paginator->connect("page_changed" , callable_mp(this, &EditorPropertyLocalizableString::_page_changed)); |
1336 | vbox->add_child(paginator); |
1337 | } else { |
1338 | // Queue children for deletion, deleting immediately might cause errors. |
1339 | for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { |
1340 | property_vbox->get_child(i)->queue_free(); |
1341 | } |
1342 | } |
1343 | |
1344 | int size = dict.size(); |
1345 | |
1346 | int max_page = MAX(0, size - 1) / page_length; |
1347 | page_index = MIN(page_index, max_page); |
1348 | |
1349 | paginator->update(page_index, max_page); |
1350 | paginator->set_visible(max_page > 0); |
1351 | |
1352 | int offset = page_index * page_length; |
1353 | |
1354 | int amount = MIN(size - offset, page_length); |
1355 | |
1356 | for (int i = 0; i < amount; i++) { |
1357 | String prop_name; |
1358 | Variant key; |
1359 | Variant value; |
1360 | |
1361 | prop_name = "indices/" + itos(i + offset); |
1362 | key = dict.get_key_at_index(i + offset); |
1363 | value = dict.get_value_at_index(i + offset); |
1364 | |
1365 | EditorProperty *prop = memnew(EditorPropertyText); |
1366 | |
1367 | prop->set_object_and_property(object.ptr(), prop_name); |
1368 | int remove_index = 0; |
1369 | |
1370 | String cs = key.get_construct_string(); |
1371 | prop->set_label(cs); |
1372 | prop->set_tooltip_text(cs); |
1373 | remove_index = i + offset; |
1374 | |
1375 | prop->set_selectable(false); |
1376 | prop->connect("property_changed" , callable_mp(this, &EditorPropertyLocalizableString::_property_changed)); |
1377 | prop->connect("object_id_selected" , callable_mp(this, &EditorPropertyLocalizableString::_object_id_selected)); |
1378 | |
1379 | HBoxContainer *hbox = memnew(HBoxContainer); |
1380 | property_vbox->add_child(hbox); |
1381 | hbox->add_child(prop); |
1382 | prop->set_h_size_flags(SIZE_EXPAND_FILL); |
1383 | Button *edit_btn = memnew(Button); |
1384 | edit_btn->set_icon(get_editor_theme_icon(SNAME("Remove" ))); |
1385 | hbox->add_child(edit_btn); |
1386 | edit_btn->connect("pressed" , callable_mp(this, &EditorPropertyLocalizableString::_remove_item).bind(edit_btn, remove_index)); |
1387 | |
1388 | prop->update_property(); |
1389 | } |
1390 | |
1391 | if (page_index == max_page) { |
1392 | button_add_item = EditorInspector::create_inspector_action_button(TTR("Add Translation" )); |
1393 | button_add_item->set_icon(get_editor_theme_icon(SNAME("Add" ))); |
1394 | button_add_item->connect("pressed" , callable_mp(this, &EditorPropertyLocalizableString::_add_locale_popup)); |
1395 | property_vbox->add_child(button_add_item); |
1396 | } |
1397 | |
1398 | updating = false; |
1399 | |
1400 | } else { |
1401 | if (container) { |
1402 | set_bottom_editor(nullptr); |
1403 | memdelete(container); |
1404 | button_add_item = nullptr; |
1405 | container = nullptr; |
1406 | } |
1407 | } |
1408 | } |
1409 | |
1410 | void EditorPropertyLocalizableString::_object_id_selected(const StringName &p_property, ObjectID p_id) { |
1411 | emit_signal(SNAME("object_id_selected" ), p_property, p_id); |
1412 | } |
1413 | |
1414 | void EditorPropertyLocalizableString::_notification(int p_what) { |
1415 | switch (p_what) { |
1416 | case NOTIFICATION_THEME_CHANGED: |
1417 | case NOTIFICATION_ENTER_TREE: { |
1418 | if (button_add_item) { |
1419 | button_add_item->set_icon(get_editor_theme_icon(SNAME("Add" ))); |
1420 | } |
1421 | } break; |
1422 | } |
1423 | } |
1424 | |
1425 | void EditorPropertyLocalizableString::_edit_pressed() { |
1426 | Variant prop_val = get_edited_property_value(); |
1427 | if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) { |
1428 | VariantInternal::initialize(&prop_val, Variant::DICTIONARY); |
1429 | get_edited_object()->set(get_edited_property(), prop_val); |
1430 | } |
1431 | |
1432 | get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed()); |
1433 | update_property(); |
1434 | } |
1435 | |
1436 | void EditorPropertyLocalizableString::_page_changed(int p_page) { |
1437 | if (updating) { |
1438 | return; |
1439 | } |
1440 | page_index = p_page; |
1441 | update_property(); |
1442 | } |
1443 | |
1444 | void EditorPropertyLocalizableString::_bind_methods() { |
1445 | } |
1446 | |
1447 | EditorPropertyLocalizableString::EditorPropertyLocalizableString() { |
1448 | object.instantiate(); |
1449 | page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page" )); |
1450 | |
1451 | edit = memnew(Button); |
1452 | edit->set_h_size_flags(SIZE_EXPAND_FILL); |
1453 | edit->set_clip_text(true); |
1454 | edit->connect("pressed" , callable_mp(this, &EditorPropertyLocalizableString::_edit_pressed)); |
1455 | edit->set_toggle_mode(true); |
1456 | add_child(edit); |
1457 | add_focusable(edit); |
1458 | |
1459 | container = nullptr; |
1460 | button_add_item = nullptr; |
1461 | paginator = nullptr; |
1462 | updating = false; |
1463 | |
1464 | locale_select = memnew(EditorLocaleDialog); |
1465 | locale_select->connect("locale_selected" , callable_mp(this, &EditorPropertyLocalizableString::_add_locale)); |
1466 | add_child(locale_select); |
1467 | } |
1468 | |