Backported PROPERTY_USAGE_ARRAY from Godot 4. Reused one of the old deprecated property usage flags for it.

Original commit:
Implement properties arrays in the Inspector.
- groud
4bd7700e89
This commit is contained in:
Relintai 2024-03-02 22:35:19 +01:00
parent bd53556507
commit 2b57397fa7
13 changed files with 932 additions and 10 deletions

View File

@ -643,8 +643,7 @@ void register_global_constants() {
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_INTERNATIONALIZED);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_GROUP);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_CATEGORY);
//deprecated, replaced by ClassDB function to check default value
//BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_STORE_IF_NONZERO);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_ARRAY);
//BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_STORE_IF_NONONE);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_NO_INSTANCE_STATE);
BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_RESTART_IF_CHANGED);

View File

@ -890,6 +890,18 @@ void ClassDB::add_property_group(StringName p_class, const String &p_name, const
type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, p_prefix, PROPERTY_USAGE_GROUP));
}
void ClassDB::add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage) {
add_property(p_class, PropertyInfo(Variant::INT, p_count_property, PROPERTY_HINT_NONE, "", p_count_usage | PROPERTY_USAGE_ARRAY, vformat("%s,%s", p_label, p_array_element_prefix)), p_count_setter, p_count_getter);
}
void ClassDB::add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix) {
OBJTYPE_WLOCK;
ClassInfo *type = classes.getptr(p_class);
ERR_FAIL_COND(!type);
type->property_list.push_back(PropertyInfo(Variant::NIL, p_path, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, p_array_element_prefix));
}
void ClassDB::add_property(StringName p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index) {
lock.read_lock();
ClassInfo *type = classes.getptr(p_class);

View File

@ -334,6 +334,10 @@ public:
static void get_signal_list(StringName p_class, List<MethodInfo> *p_signals, bool p_no_inheritance = false);
static void add_property_group(StringName p_class, const String &p_name, const String &p_prefix = "");
static void add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage = PROPERTY_USAGE_EDITOR);
static void add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix);
static void add_property(StringName p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1);
static void set_property_default_value(StringName p_class, const StringName &p_name, const Variant &p_default);
static void get_property_list(StringName p_class, List<PropertyInfo> *p_list, bool p_no_inheritance = false, const Object *p_validator = nullptr);

View File

@ -114,9 +114,7 @@ enum PropertyUsageFlags {
PROPERTY_USAGE_INTERNATIONALIZED = 1 << 6, //hint for internationalized strings
PROPERTY_USAGE_GROUP = 1 << 7, //used for grouping props in the editor
PROPERTY_USAGE_CATEGORY = 1 << 8,
// FIXME: Drop in 4.0, possibly reorder other flags?
// Those below are deprecated thanks to ClassDB's now class value cache
//PROPERTY_USAGE_STORE_IF_NONZERO = 1 << 9, //only store if nonzero //512
PROPERTY_USAGE_ARRAY = 1 << 9, // Used in the inspector to group properties as elements of an array.
//PROPERTY_USAGE_STORE_IF_NONONE = 1 << 10, //only store if false //1024
PROPERTY_USAGE_NO_INSTANCE_STATE = 1 << 11,
PROPERTY_USAGE_RESTART_IF_CHANGED = 1 << 12,
@ -145,6 +143,10 @@ enum PropertyUsageFlags {
#define ADD_PROPERTY_DEFAULT(m_property, m_default) ClassDB::set_property_default_value(get_class_static(), m_property, m_default)
#define ADD_GROUP(m_name, m_prefix) ClassDB::add_property_group(get_class_static(), m_name, m_prefix)
#define ADD_ARRAY_COUNT(m_label, m_count_property, m_count_property_setter, m_count_property_getter, m_prefix) ClassDB::add_property_array_count(get_class_static(), m_label, m_count_property, _scs_create(m_count_property_setter), _scs_create(m_count_property_getter), m_prefix)
#define ADD_ARRAY_COUNT_WITH_USAGE_FLAGS(m_label, m_count_property, m_count_property_setter, m_count_property_getter, m_prefix, m_property_usage_flags) ClassDB::add_property_array_count(get_class_static(), m_label, m_count_property, _scs_create(m_count_property_setter), _scs_create(m_count_property_getter), m_prefix, m_property_usage_flags)
#define ADD_ARRAY(m_array_path, m_prefix) ClassDB::add_property_array(get_class_static(), m_array_path, m_prefix)
struct PropertyInfo {
Variant::Type type;
String name;

View File

@ -333,7 +333,8 @@ void DocData::generate(bool p_basic_types) {
continue;
}
if (E->get().usage & PROPERTY_USAGE_GROUP || E->get().usage & PROPERTY_USAGE_CATEGORY || E->get().usage & PROPERTY_USAGE_INTERNAL) {
if (E->get().usage & PROPERTY_USAGE_GROUP || E->get().usage & PROPERTY_USAGE_CATEGORY ||
E->get().usage & PROPERTY_USAGE_INTERNAL || (E->get().type == Variant::NIL && E->get().usage & PROPERTY_USAGE_ARRAY)) {
continue;
}

View File

@ -445,6 +445,22 @@ UndoRedo &EditorData::get_undo_redo() {
return undo_redo;
}
void EditorData::add_move_array_element_function(const StringName &p_class, const Ref<FuncRef> &p_funcref) {
move_element_functions.insert(p_class, p_funcref);
}
void EditorData::remove_move_array_element_function(const StringName &p_class) {
move_element_functions.erase(p_class);
}
Ref<FuncRef> EditorData::get_move_array_element_function(const StringName &p_class) const {
if (move_element_functions.has(p_class)) {
return move_element_functions[p_class];
}
return Ref<FuncRef>();
}
void EditorData::remove_editor_plugin(EditorPlugin *p_plugin) {
p_plugin->undo_redo = nullptr;
editor_plugins.erase(p_plugin);

View File

@ -42,6 +42,7 @@
#include "core/containers/rb_map.h"
#include "core/containers/rb_set.h"
#include "core/containers/vector.h"
#include "core/object/func_ref.h"
#include "core/object/object_id.h"
#include "core/object/reference.h"
#include "core/object/script_language.h"
@ -161,6 +162,8 @@ private:
List<PropertyData> clipboard;
UndoRedo undo_redo;
RBMap<StringName, Ref<FuncRef>> move_element_functions;
Vector<EditedScene> edited_scene;
int current_edited_scene;
@ -191,6 +194,11 @@ public:
int get_editor_plugin_count() const;
EditorPlugin *get_editor_plugin(int p_idx);
// Function should have this signature: void (Object* undo_redo, Object *modified_object, String array_prefix, int element_index, int new_position)
void add_move_array_element_function(const StringName &p_class, const Ref<FuncRef> &p_funcref);
void remove_move_array_element_function(const StringName &p_class);
Ref<FuncRef> get_move_array_element_function(const StringName &p_class) const;
UndoRedo &get_undo_redo();
void save_editor_global_states();

View File

@ -55,15 +55,21 @@
#include "editor_settings.h"
#include "multi_node_edit.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/scroll_bar.h"
#include "scene/gui/texture_rect.h"
#include "scene/main/canvas_item.h"
#include "scene/main/node.h"
#include "scene/main/property_utils.h"
#include "scene/main/scene_tree.h"
#include "scene/main/viewport.h"
#include "scene/resources/font/font.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/style_box.h"
@ -1028,6 +1034,7 @@ EditorInspectorCategory::EditorInspectorCategory() {
void EditorInspectorSection::_test_unfold() {
if (!vbox_added) {
add_child(vbox);
move_child(vbox, 0);
vbox_added = true;
}
}
@ -1059,6 +1066,9 @@ int EditorInspectorSection::_get_header_height() {
void EditorInspectorSection::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
minimum_size_changed();
} break;
case NOTIFICATION_SORT_CHILDREN: {
Size2 size = get_size();
Point2 offset;
@ -1149,6 +1159,7 @@ void EditorInspectorSection::setup(const String &p_section, const String &p_labe
if (!foldable && !vbox_added) {
add_child(vbox);
move_child(vbox, 0);
vbox_added = true;
}
@ -1209,7 +1220,7 @@ void EditorInspectorSection::fold() {
}
if (!vbox_added) {
return; //kinda pointless
return;
}
object->editor_set_section_unfold(section, false);
@ -1241,6 +1252,748 @@ EditorInspectorSection::~EditorInspectorSection() {
////////////////////////////////////////////////
////////////////////////////////////////////////
int EditorInspectorArray::_get_array_count() {
if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
List<PropertyInfo> object_property_list;
object->get_property_list(&object_property_list);
return _extract_properties_as_array(object_property_list).size();
} else if (mode == MODE_USE_COUNT_PROPERTY) {
bool valid;
int count = object->get(count_property, &valid);
ERR_FAIL_COND_V_MSG(!valid, 0, vformat("%s is not a valid property to be used as array count.", count_property));
return count;
}
return 0;
}
void EditorInspectorArray::_add_button_pressed() {
_move_element(-1, -1);
}
void EditorInspectorArray::_first_page_button_pressed() {
emit_signal("page_change_request", 0);
}
void EditorInspectorArray::_prev_page_button_pressed() {
emit_signal("page_change_request", MAX(0, page - 1));
}
void EditorInspectorArray::_page_line_edit_text_submitted(String p_text) {
if (p_text.is_valid_integer()) {
int new_page = p_text.to_int() - 1;
new_page = MIN(MAX(0, new_page), max_page);
page_line_edit->set_text(Variant(new_page));
emit_signal("page_change_request", new_page);
} else {
page_line_edit->set_text(Variant(page));
}
}
void EditorInspectorArray::_next_page_button_pressed() {
emit_signal("page_change_request", MIN(max_page, page + 1));
}
void EditorInspectorArray::_last_page_button_pressed() {
emit_signal("page_change_request", max_page);
}
void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) {
switch (p_id) {
case OPTION_MOVE_UP:
if (popup_array_index_pressed > 0) {
_move_element(popup_array_index_pressed, popup_array_index_pressed - 1);
}
break;
case OPTION_MOVE_DOWN:
if (popup_array_index_pressed < count - 1) {
_move_element(popup_array_index_pressed, popup_array_index_pressed + 2);
}
break;
case OPTION_NEW_BEFORE:
_move_element(-1, popup_array_index_pressed);
break;
case OPTION_NEW_AFTER:
_move_element(-1, popup_array_index_pressed + 1);
break;
case OPTION_REMOVE:
_move_element(popup_array_index_pressed, -1);
break;
case OPTION_CLEAR_ARRAY:
_clear_array();
break;
case OPTION_RESIZE_ARRAY:
new_size = count;
new_size_line_edit->set_text(Variant(new_size));
resize_dialog->get_ok()->set_disabled(true);
resize_dialog->popup_centered();
new_size_line_edit->grab_focus();
new_size_line_edit->select_all();
break;
default:
break;
}
}
void EditorInspectorArray::_control_dropping_draw() {
int drop_position = _drop_position();
if (dropping && drop_position >= 0) {
Vector2 from;
Vector2 to;
if (drop_position < elements_vbox->get_child_count()) {
Transform2D xform = Object::cast_to<Control>(elements_vbox->get_child(drop_position))->get_transform();
from = xform.xform(Vector2());
to = xform.xform(Vector2(elements_vbox->get_size().x, 0));
} else {
Control *child = Object::cast_to<Control>(elements_vbox->get_child(drop_position - 1));
Transform2D xform = child->get_transform();
from = xform.xform(Vector2(0, child->get_size().y));
to = xform.xform(Vector2(elements_vbox->get_size().x, child->get_size().y));
}
Color color = get_theme_color("accent_color", "Editor");
control_dropping->draw_line(from, to, color, 2);
}
}
void EditorInspectorArray::_vbox_visibility_changed() {
control_dropping->set_visible(vbox->is_visible_in_tree());
}
void EditorInspectorArray::_panel_draw(int p_index) {
ERR_FAIL_INDEX(p_index, (int)array_elements.size());
Ref<StyleBox> style = get_theme_stylebox("Focus", "EditorStyles");
if (!style.is_valid()) {
return;
}
if (array_elements[p_index].panel->has_focus()) {
array_elements[p_index].panel->draw_style_box(style, Rect2(Vector2(), array_elements[p_index].panel->get_size()));
}
}
void EditorInspectorArray::_panel_gui_input(Ref<InputEvent> p_event, int p_index) {
ERR_FAIL_INDEX(p_index, (int)array_elements.size());
Ref<InputEventKey> key_ref = p_event;
if (key_ref.is_valid()) {
const InputEventKey &key = **key_ref;
if (array_elements[p_index].panel->has_focus() && key.is_pressed() && key.get_scancode() == KEY_DELETE) {
_move_element(begin_array_index + p_index, -1);
array_elements[p_index].panel->accept_event();
}
}
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
if (mb->get_button_index() == BUTTON_RIGHT) {
popup_array_index_pressed = begin_array_index + p_index;
rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0);
rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1);
rmb_popup->set_position(mb->get_global_position());
rmb_popup->set_size(Vector2());
rmb_popup->popup();
}
}
}
void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) {
String action_name;
if (p_element_index < 0) {
action_name = vformat("Add element to property array with prefix %s.", array_element_prefix);
} else if (p_to_pos < 0) {
action_name = vformat("Remove element %d from property array with prefix %s.", p_element_index, array_element_prefix);
} else {
action_name = vformat("Move element %d to position %d in property array with prefix %s.", p_element_index, p_to_pos, array_element_prefix);
}
undo_redo->create_action(action_name);
if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
// Call the function.
Ref<FuncRef> move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
if (move_function.is_valid()) {
Variant args[] = { (Object *)undo_redo, object, array_element_prefix, p_element_index, p_to_pos };
const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
Variant::CallError call_error;
move_function->call_func(args_p, 5, call_error);
} else {
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()));
}
} else if (mode == MODE_USE_COUNT_PROPERTY) {
ERR_FAIL_COND(p_to_pos < -1 || p_to_pos > count);
List<PropertyInfo> object_property_list;
object->get_property_list(&object_property_list);
Array properties_as_array = _extract_properties_as_array(object_property_list);
properties_as_array.resize(count);
// For undoing things
undo_redo->add_undo_property(object, count_property, properties_as_array.size());
for (int i = 0; i < (int)properties_as_array.size(); i++) {
Dictionary d = Dictionary(properties_as_array[i]);
Array keys = d.keys();
for (int j = 0; j < keys.size(); j++) {
String key = keys[j];
undo_redo->add_undo_property(object, vformat(key, i), d[key]);
}
}
if (p_element_index < 0) {
// Add an element.
properties_as_array.insert(p_to_pos < 0 ? properties_as_array.size() : p_to_pos, Dictionary());
} else if (p_to_pos < 0) {
// Delete the element.
properties_as_array.remove(p_element_index);
} else {
// Move the element.
properties_as_array.insert(p_to_pos, properties_as_array[p_element_index].duplicate());
properties_as_array.remove(p_to_pos < p_element_index ? p_element_index + 1 : p_element_index);
}
// Change the array size then set the properties.
undo_redo->add_do_property(object, count_property, properties_as_array.size());
for (int i = 0; i < (int)properties_as_array.size(); i++) {
Dictionary d = properties_as_array[i];
Array keys = d.keys();
for (int j = 0; j < keys.size(); j++) {
String key = keys[j];
undo_redo->add_do_property(object, vformat(key, i), d[key]);
}
}
}
undo_redo->commit_action();
// Handle page change and update counts.
if (p_element_index < 0) {
int added_index = p_to_pos < 0 ? count : p_to_pos;
emit_signal("page_change_request", added_index / page_lenght);
count += 1;
} else if (p_to_pos < 0) {
count -= 1;
if (page == max_page && (MAX(0, count - 1) / page_lenght != max_page)) {
emit_signal("page_change_request", max_page - 1);
}
}
begin_array_index = page * page_lenght;
end_array_index = MIN(count, (page + 1) * page_lenght);
max_page = MAX(0, count - 1) / page_lenght;
}
void EditorInspectorArray::_clear_array() {
undo_redo->create_action(vformat("Clear property array with prefix %s.", array_element_prefix));
if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
for (int i = count - 1; i >= 0; i--) {
// Call the function.
Ref<FuncRef> move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
if (move_function.is_valid()) {
Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 };
const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
Variant::CallError call_error;
move_function->call_func(args_p, 5, call_error);
} else {
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()));
}
}
} else if (mode == MODE_USE_COUNT_PROPERTY) {
List<PropertyInfo> object_property_list;
object->get_property_list(&object_property_list);
Array properties_as_array = _extract_properties_as_array(object_property_list);
properties_as_array.resize(count);
// For undoing things
undo_redo->add_undo_property(object, count_property, count);
for (int i = 0; i < (int)properties_as_array.size(); i++) {
Dictionary d = Dictionary(properties_as_array[i]);
Array keys = d.keys();
for (int j = 0; j < keys.size(); j++) {
String key = keys[j];
undo_redo->add_undo_property(object, vformat(key, i), d[key]);
}
}
// Change the array size then set the properties.
undo_redo->add_do_property(object, count_property, 0);
}
undo_redo->commit_action();
// Handle page change and update counts.
emit_signal("page_change_request", 0);
count = 0;
begin_array_index = 0;
end_array_index = 0;
max_page = 0;
}
void EditorInspectorArray::_resize_array(int p_size) {
ERR_FAIL_COND(p_size < 0);
if (p_size == count) {
return;
}
undo_redo->create_action(vformat("Resize property array with prefix %s.", array_element_prefix));
if (p_size > count) {
if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
for (int i = count; i < p_size; i++) {
// Call the function.
Ref<FuncRef> move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
if (move_function.is_valid()) {
Variant args[] = { (Object *)undo_redo, object, array_element_prefix, -1, -1 };
const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
Variant::CallError call_error;
move_function->call_func(args_p, 5, call_error);
} else {
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()));
}
}
} else if (mode == MODE_USE_COUNT_PROPERTY) {
undo_redo->add_undo_property(object, count_property, count);
undo_redo->add_do_property(object, count_property, p_size);
}
} else {
if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
for (int i = count - 1; i > p_size - 1; i--) {
// Call the function.
Ref<FuncRef> move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
if (move_function.is_valid()) {
Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 };
const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
Variant::CallError call_error;
move_function->call_func(args_p, 5, call_error);
} else {
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()));
}
}
} else if (mode == MODE_USE_COUNT_PROPERTY) {
List<PropertyInfo> object_property_list;
object->get_property_list(&object_property_list);
Array properties_as_array = _extract_properties_as_array(object_property_list);
properties_as_array.resize(count);
// For undoing things
undo_redo->add_undo_property(object, count_property, count);
for (int i = count - 1; i > p_size - 1; i--) {
Dictionary d = Dictionary(properties_as_array[i]);
Array keys = d.keys();
for (int j = 0; j < keys.size(); j++) {
String key = keys[j];
undo_redo->add_undo_property(object, vformat(key, i), d[key]);
}
}
// Change the array size then set the properties.
undo_redo->add_do_property(object, count_property, p_size);
}
}
undo_redo->commit_action();
// Handle page change and update counts.
emit_signal("page_change_request", 0);
/*
count = 0;
begin_array_index = 0;
end_array_index = 0;
max_page = 0;
*/
}
Array EditorInspectorArray::_extract_properties_as_array(const List<PropertyInfo> &p_list) {
Array output;
for (const List<PropertyInfo>::Element *E = p_list.front(); E; E = E->next()) {
const PropertyInfo &pi = E->get();
if (pi.name.begins_with(array_element_prefix)) {
String str = pi.name.trim_prefix(array_element_prefix);
int to_char_index = 0;
while (to_char_index < str.length()) {
if (str[to_char_index] < '0' || str[to_char_index] > '9') {
break;
}
to_char_index++;
}
if (to_char_index > 0) {
int array_index = str.left(to_char_index).to_int();
Error error = OK;
if (array_index >= output.size()) {
error = output.resize(array_index + 1);
}
if (error == OK) {
String format_string = String(array_element_prefix) + "%d" + str.substr(to_char_index);
Dictionary dict = output[array_index];
dict[format_string] = object->get(pi.name);
output[array_index] = dict;
} else {
WARN_PRINT(vformat("Array element %s has an index too high. Array allocaiton failed.", pi.name));
}
}
}
}
return output;
}
int EditorInspectorArray::_drop_position() const {
for (int i = 0; i < (int)array_elements.size(); i++) {
const ArrayElement &ae = array_elements[i];
Size2 size = ae.panel->get_size();
Vector2 mp = ae.panel->get_local_mouse_position();
if (Rect2(Vector2(), size).has_point(mp)) {
if (mp.y < size.y / 2) {
return i;
} else {
return i + 1;
}
}
}
return -1;
}
void EditorInspectorArray::_new_size_line_edit_text_changed(String p_text) {
bool valid = false;
if (p_text.is_valid_integer()) {
int val = p_text.to_int();
if (val > 0 && val != count) {
valid = true;
}
}
resize_dialog->get_ok()->set_disabled(!valid);
}
void EditorInspectorArray::_new_size_line_edit_text_submitted(String p_text) {
bool valid = false;
if (p_text.is_valid_integer()) {
int val = p_text.to_int();
if (val > 0 && val != count) {
new_size = val;
valid = true;
}
}
if (valid) {
resize_dialog->hide();
_resize_array(new_size);
} else {
new_size_line_edit->set_text(Variant(new_size));
}
}
void EditorInspectorArray::_resize_dialog_confirmed() {
_new_size_line_edit_text_submitted(new_size_line_edit->get_text());
}
void EditorInspectorArray::_setup() {
// Setup counts.
count = _get_array_count();
begin_array_index = page * page_lenght;
end_array_index = MIN(count, (page + 1) * page_lenght);
max_page = MAX(0, count - 1) / page_lenght;
array_elements.resize(MAX(0, end_array_index - begin_array_index));
if (page < 0 || page > max_page) {
WARN_PRINT(vformat("Invalid page number %d", page));
page = CLAMP(page, 0, max_page);
}
for (int i = 0; i < (int)array_elements.size(); i++) {
ArrayElement &ae = array_elements[i];
// Panel and its hbox.
ae.panel = memnew(PanelContainer);
ae.panel->set_focus_mode(FOCUS_ALL);
ae.panel->set_mouse_filter(MOUSE_FILTER_PASS);
ae.panel->set_drag_forwarding(this);
ae.panel->set_meta("index", begin_array_index + i);
ae.panel->set_tooltip(vformat(TTR("Element %d: %s%d*"), i, array_element_prefix, i));
ae.panel->connect("focus_entered", ae.panel, "update");
ae.panel->connect("focus_exited", ae.panel, "update");
ae.panel->connect("draw", this, "_panel_draw", varray(i));
ae.panel->connect("gui_input", this, "_panel_gui_input", varray(i));
ae.panel->add_theme_style_override("panel", i % 2 ? odd_style : even_style);
elements_vbox->add_child(ae.panel);
ae.margin = memnew(MarginContainer);
ae.margin->set_mouse_filter(MOUSE_FILTER_PASS);
if (is_inside_tree()) {
Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size();
ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);
ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);
ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);
ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);
}
ae.panel->add_child(ae.margin);
ae.hbox = memnew(HBoxContainer);
ae.hbox->set_h_size_flags(SIZE_EXPAND_FILL);
ae.hbox->set_v_size_flags(SIZE_EXPAND_FILL);
ae.margin->add_child(ae.hbox);
// Move button.
ae.move_texture_rect = memnew(TextureRect);
ae.move_texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
if (is_inside_tree()) {
ae.move_texture_rect->set_texture(get_theme_icon("TripleBar", "EditorIcons"));
}
ae.hbox->add_child(ae.move_texture_rect);
// Right vbox.
ae.vbox = memnew(VBoxContainer);
ae.vbox->set_h_size_flags(SIZE_EXPAND_FILL);
ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL);
ae.hbox->add_child(ae.vbox);
}
// Hide/show the add button.
add_button->set_visible(page == max_page);
if (max_page == 0) {
hbox_pagination->hide();
} else {
// Update buttons.
first_page_button->set_disabled(page == 0);
prev_page_button->set_disabled(page == 0);
next_page_button->set_disabled(page == max_page);
last_page_button->set_disabled(page == max_page);
// Update page number and page count.
page_line_edit->set_text(vformat("%d", page + 1));
page_count_label->set_text(vformat("/ %d", max_page + 1));
}
}
Variant EditorInspectorArray::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
int index = p_from->get_meta("index");
Dictionary dict;
dict["type"] = "property_array_element";
dict["property_array_prefix"] = array_element_prefix;
dict["index"] = index;
return dict;
}
void EditorInspectorArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
Dictionary dict = p_data;
int to_drop = dict["index"];
int drop_position = _drop_position();
if (drop_position < 0) {
return;
}
_move_element(to_drop, begin_array_index + drop_position);
}
bool EditorInspectorArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
// First, update drawing.
control_dropping->update();
if (p_data.get_type() != Variant::DICTIONARY) {
return false;
}
Dictionary dict = p_data;
int drop_position = _drop_position();
if (!dict.has("type") || dict["type"] != "property_array_element" || String(dict["property_array_prefix"]) != array_element_prefix || drop_position < 0) {
return false;
}
// Check in dropping at the given index does indeed move the item.
int moved_array_index = (int)dict["index"];
int drop_array_index = begin_array_index + drop_position;
return drop_array_index != moved_array_index && drop_array_index - 1 != moved_array_index;
}
void EditorInspectorArray::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
Color color = get_theme_color("dark_color_1", "Editor");
odd_style->set_bg_color(color.lightened(0.15));
even_style->set_bg_color(color.darkened(0.15));
for (int i = 0; i < (int)array_elements.size(); i++) {
ArrayElement &ae = array_elements[i];
ae.move_texture_rect->set_texture(get_theme_icon("TripleBar", "EditorIcons"));
Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size();
ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);
ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);
ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);
ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);
}
add_button->set_icon(get_theme_icon("Add", "EditorIcons"));
first_page_button->set_icon(get_theme_icon("PageFirst", "EditorIcons"));
prev_page_button->set_icon(get_theme_icon("PagePrevious", "EditorIcons"));
next_page_button->set_icon(get_theme_icon("PageNext", "EditorIcons"));
last_page_button->set_icon(get_theme_icon("PageLast", "EditorIcons"));
minimum_size_changed();
} break;
case NOTIFICATION_DRAG_BEGIN: {
Dictionary dict = get_viewport()->gui_get_drag_data();
if (dict.has("type") && dict["type"] == "property_array_element" && String(dict["property_array_prefix"]) == array_element_prefix) {
dropping = true;
control_dropping->update();
}
} break;
case NOTIFICATION_DRAG_END: {
if (dropping) {
dropping = false;
control_dropping->update();
}
} break;
}
}
void EditorInspectorArray::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &EditorInspectorArray::get_drag_data_fw);
ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &EditorInspectorArray::can_drop_data_fw);
ClassDB::bind_method(D_METHOD("_drop_data_fw"), &EditorInspectorArray::drop_data_fw);
ClassDB::bind_method(D_METHOD("_panel_draw"), &EditorInspectorArray::_panel_draw);
ClassDB::bind_method(D_METHOD("_rmb_popup_id_pressed"), &EditorInspectorArray::_rmb_popup_id_pressed);
ClassDB::bind_method(D_METHOD("_add_button_pressed"), &EditorInspectorArray::_add_button_pressed);
ClassDB::bind_method(D_METHOD("_first_page_button_pressed"), &EditorInspectorArray::_first_page_button_pressed);
ClassDB::bind_method(D_METHOD("_prev_page_button_pressed"), &EditorInspectorArray::_prev_page_button_pressed);
ClassDB::bind_method(D_METHOD("_page_line_edit_text_submitted"), &EditorInspectorArray::_page_line_edit_text_submitted);
ClassDB::bind_method(D_METHOD("_next_page_button_pressed"), &EditorInspectorArray::_next_page_button_pressed);
ClassDB::bind_method(D_METHOD("_last_page_button_pressed"), &EditorInspectorArray::_last_page_button_pressed);
ClassDB::bind_method(D_METHOD("_control_dropping_draw"), &EditorInspectorArray::_control_dropping_draw);
ClassDB::bind_method(D_METHOD("_resize_dialog_confirmed"), &EditorInspectorArray::_resize_dialog_confirmed);
ClassDB::bind_method(D_METHOD("_new_size_line_edit_text_changed"), &EditorInspectorArray::_new_size_line_edit_text_changed);
ClassDB::bind_method(D_METHOD("_new_size_line_edit_text_submitted"), &EditorInspectorArray::_new_size_line_edit_text_submitted);
ClassDB::bind_method(D_METHOD("_vbox_visibility_changed"), &EditorInspectorArray::_vbox_visibility_changed);
ADD_SIGNAL(MethodInfo("page_change_request"));
}
void EditorInspectorArray::set_undo_redo(UndoRedo *p_undo_redo) {
undo_redo = p_undo_redo;
}
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) {
count_property = "";
mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION;
array_element_prefix = p_array_element_prefix;
page = p_page;
EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable);
_setup();
}
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) {
count_property = p_count_property;
mode = MODE_USE_COUNT_PROPERTY;
array_element_prefix = p_array_element_prefix;
page = p_page;
EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable);
_setup();
}
VBoxContainer *EditorInspectorArray::get_vbox(int p_index) {
if (p_index >= begin_array_index && p_index < end_array_index) {
return array_elements[p_index - begin_array_index].vbox;
} else if (p_index < 0) {
return vbox;
} else {
return nullptr;
}
}
EditorInspectorArray::EditorInspectorArray() {
set_mouse_filter(Control::MOUSE_FILTER_STOP);
odd_style.instance();
even_style.instance();
rmb_popup = memnew(PopupMenu);
rmb_popup->add_item(TTR("Move Up"), OPTION_MOVE_UP);
rmb_popup->add_item(TTR("Move Down"), OPTION_MOVE_DOWN);
rmb_popup->add_separator();
rmb_popup->add_item(TTR("Insert New Before"), OPTION_NEW_BEFORE);
rmb_popup->add_item(TTR("Insert New After"), OPTION_NEW_AFTER);
rmb_popup->add_separator();
rmb_popup->add_item(TTR("Remove"), OPTION_REMOVE);
rmb_popup->add_separator();
rmb_popup->add_item(TTR("Clear Array"), OPTION_CLEAR_ARRAY);
rmb_popup->add_item(TTR("Resize Array..."), OPTION_RESIZE_ARRAY);
rmb_popup->connect("id_pressed", this, "_rmb_popup_id_pressed");
add_child(rmb_popup);
elements_vbox = memnew(VBoxContainer);
elements_vbox->add_theme_constant_override("separation", 0);
vbox->add_child(elements_vbox);
add_button = memnew(Button);
add_button->set_text(TTR("Add Element"));
add_button->set_text_align(Button::ALIGN_CENTER);
add_button->connect("pressed", this, "_add_button_pressed");
vbox->add_child(add_button);
hbox_pagination = memnew(HBoxContainer);
hbox_pagination->set_h_size_flags(SIZE_EXPAND_FILL);
hbox_pagination->set_alignment(HBoxContainer::ALIGN_CENTER);
vbox->add_child(hbox_pagination);
first_page_button = memnew(Button);
first_page_button->set_flat(true);
first_page_button->connect("pressed", this, "_first_page_button_pressed");
hbox_pagination->add_child(first_page_button);
prev_page_button = memnew(Button);
prev_page_button->set_flat(true);
prev_page_button->connect("pressed", this, "_prev_page_button_pressed");
hbox_pagination->add_child(prev_page_button);
page_line_edit = memnew(LineEdit);
page_line_edit->connect("text_submitted", this, "_page_line_edit_text_submitted");
page_line_edit->add_theme_constant_override("minimum_character_width", 2);
hbox_pagination->add_child(page_line_edit);
page_count_label = memnew(Label);
hbox_pagination->add_child(page_count_label);
next_page_button = memnew(Button);
next_page_button->set_flat(true);
next_page_button->connect("pressed", this, "_next_page_button_pressed");
hbox_pagination->add_child(next_page_button);
last_page_button = memnew(Button);
last_page_button->set_flat(true);
last_page_button->connect("pressed", this, "_last_page_button_pressed");
hbox_pagination->add_child(last_page_button);
control_dropping = memnew(Control);
control_dropping->connect("draw", this, "_control_dropping_draw");
control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
add_child(control_dropping);
resize_dialog = memnew(AcceptDialog);
resize_dialog->set_title(TTRC("Resize Array"));
resize_dialog->add_cancel();
resize_dialog->connect("confirmed", this, "_resize_dialog_confirmed");
add_child(resize_dialog);
VBoxContainer *resize_dialog_vbox = memnew(VBoxContainer);
resize_dialog->add_child(resize_dialog_vbox);
new_size_line_edit = memnew(LineEdit);
new_size_line_edit->connect("text_changed", this, "_new_size_line_edit_text_changed");
new_size_line_edit->connect("text_submitted", this, "_new_size_line_edit_text_submitted");
resize_dialog_vbox->add_margin_child(TTRC("New Size:"), new_size_line_edit);
vbox->connect("visibility_changed", this, "_vbox_visibility_changed");
}
////////////////////////////////////////////////
////////////////////////////////////////////////
Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS];
int EditorInspector::inspector_plugin_count = 0;

View File

@ -61,6 +61,12 @@ class LineEdit;
class Node;
class PopupMenu;
class VBoxContainer;
class Button;
class AcceptDialog;
class MarginContainer;
class HBoxContainer;
class PanelContainer;
class TextureRect;
class EditorPropertyRevert {
public:
@ -254,9 +260,8 @@ class EditorInspectorSection : public Container {
String label;
String section;
Object *object;
VBoxContainer *vbox;
bool vbox_added; //optimization
bool vbox_added; // Optimization
Color bg_color;
bool foldable;
@ -265,6 +270,9 @@ class EditorInspectorSection : public Container {
Ref<Texture> _get_arrow();
protected:
Object *object;
VBoxContainer *vbox;
void _notification(int p_what);
static void _bind_methods();
void _gui_input(const Ref<InputEvent> &p_event);
@ -281,6 +289,121 @@ public:
~EditorInspectorSection();
};
class EditorInspectorArray : public EditorInspectorSection {
GDCLASS(EditorInspectorArray, EditorInspectorSection);
UndoRedo *undo_redo;
enum Mode {
MODE_NONE,
MODE_USE_COUNT_PROPERTY,
MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION,
} mode;
StringName count_property;
StringName array_element_prefix;
int count = 0;
VBoxContainer *elements_vbox;
Control *control_dropping;
bool dropping = false;
Button *add_button;
AcceptDialog *resize_dialog;
int new_size = 0;
LineEdit *new_size_line_edit;
// Pagination
int page_lenght = 5;
int page = 0;
int max_page = 0;
int begin_array_index = 0;
int end_array_index = 0;
HBoxContainer *hbox_pagination;
Button *first_page_button;
Button *prev_page_button;
LineEdit *page_line_edit;
Label *page_count_label;
Button *next_page_button;
Button *last_page_button;
enum MenuOptions {
OPTION_MOVE_UP = 0,
OPTION_MOVE_DOWN,
OPTION_NEW_BEFORE,
OPTION_NEW_AFTER,
OPTION_REMOVE,
OPTION_CLEAR_ARRAY,
OPTION_RESIZE_ARRAY,
};
int popup_array_index_pressed = -1;
PopupMenu *rmb_popup;
struct ArrayElement {
PanelContainer *panel;
MarginContainer *margin;
HBoxContainer *hbox;
TextureRect *move_texture_rect;
VBoxContainer *vbox;
};
LocalVector<ArrayElement> array_elements;
Ref<StyleBoxFlat> odd_style;
Ref<StyleBoxFlat> even_style;
int _get_array_count();
void _add_button_pressed();
void _first_page_button_pressed();
void _prev_page_button_pressed();
void _page_line_edit_text_submitted(String p_text);
void _next_page_button_pressed();
void _last_page_button_pressed();
void _rmb_popup_id_pressed(int p_id);
void _control_dropping_draw();
void _vbox_visibility_changed();
void _panel_draw(int p_index);
void _panel_gui_input(Ref<InputEvent> p_event, int p_index);
void _move_element(int p_element_index, int p_to_pos);
void _clear_array();
void _resize_array(int p_size);
Array _extract_properties_as_array(const List<PropertyInfo> &p_list);
int _drop_position() const;
void _new_size_line_edit_text_changed(String p_text);
void _new_size_line_edit_text_submitted(String p_text);
void _resize_dialog_confirmed();
void _update_elements_visibility();
void _setup();
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_undo_redo(UndoRedo *p_undo_redo);
void 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);
void 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);
VBoxContainer *get_vbox(int p_index);
EditorInspectorArray();
};
class EditorInspector : public ScrollContainer {
GDCLASS(EditorInspector, ScrollContainer);

View File

@ -0,0 +1 @@
<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M6 3 3 6l3 3m3-6v6" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#e0e0e0" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 213 B

View File

@ -0,0 +1 @@
<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="m6 9 3-3-3-3M3 9V3" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#e0e0e0" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 213 B

View File

@ -0,0 +1 @@
<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="m4.5 9 3-3-3-3" fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 209 B

View File

@ -0,0 +1 @@
<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="m7.5 3-3 3 3 3" fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 209 B