1
0
Fork 0

Add support for "ordered" list properties.

Add support for "ordered" list properties.
This commit is contained in:
Pāvels Nadtočajevs 2025-02-27 09:35:55 +02:00
parent 15ff450680
commit 9cc6e4a1be
7 changed files with 280 additions and 3 deletions

View File

@ -678,6 +678,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_PASSWORD);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_TOOL_BUTTON);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ONESHOT);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ORDERED_LIST);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX);
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NONE);

View File

@ -90,6 +90,7 @@ enum PropertyHint {
PROPERTY_HINT_TOOL_BUTTON,
PROPERTY_HINT_ONESHOT, ///< the property will be changed by self after setting, such as AudioStreamPlayer.playing, Particles.emitting.
PROPERTY_HINT_NO_NODEPATH, /// < this property will not contain a NodePath, regardless of type (Array, Dictionary, List, etc.). Needed for SceneTreeDock.
PROPERTY_HINT_ORDERED_LIST,
PROPERTY_HINT_MAX,
};

View File

@ -2954,7 +2954,11 @@
<constant name="PROPERTY_HINT_ONESHOT" value="40" enum="PropertyHint">
Hints that a property will be changed on its own after setting, such as [member AudioStreamPlayer.playing] or [member GPUParticles3D.emitting].
</constant>
<constant name="PROPERTY_HINT_MAX" value="42" enum="PropertyHint">
<constant name="PROPERTY_HINT_ORDERED_LIST" value="42" enum="PropertyHint">
Hints that a property is ordered list of values.
The hint string is a comma separated list of display name/value pairs separated by [code]:[/code] such as [code]"Hello:hello,Something Else:else"[/code].
</constant>
<constant name="PROPERTY_HINT_MAX" value="43" enum="PropertyHint">
Represents the size of the [enum PropertyHint] enum.
</constant>
<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags" is_bitfield="true">

View File

@ -52,6 +52,7 @@
#include "scene/3d/gpu_particles_3d.h"
#include "scene/gui/color_picker.h"
#include "scene/gui/grid_container.h"
#include "scene/gui/tree.h"
#include "scene/main/window.h"
#include "scene/resources/font.h"
#include "scene/resources/mesh.h"
@ -249,6 +250,225 @@ EditorPropertyMultilineText::EditorPropertyMultilineText(bool p_expression) {
}
}
///////////////////// ORDERED LIST /////////////////////////
void EditorPropertyOrderedList::_set_read_only(bool p_read_only) {
read_only = p_read_only;
_update_tree();
}
void EditorPropertyOrderedList::_update_tree() {
tree->clear();
TreeItem *root = tree->create_item(nullptr);
// Add missing keys to the value.
for (const KeyValue<String, String> &E : names) {
if (!values.has(E.key)) {
values.push_back(E.key);
}
}
// Update tree.
for (int i = 0; i < values.size(); i++) {
if (!names.has(values[i])) {
WARN_PRINT(vformat("Invalid list item %s for property %s.", values[i], get_edited_property()));
continue;
}
TreeItem *it = tree->create_item(root);
it->set_text(0, names[values[i]]);
it->set_metadata(0, values[i]);
if (!read_only) {
it->add_button(0, get_editor_theme_icon(SNAME("ArrowUp")), BUTTON_UP, (i == 0), TTR("Move Up"));
it->add_button(0, get_editor_theme_icon(SNAME("ArrowDown")), BUTTON_DOWN, (i == values.size() - 1), TTR("Move Down"));
}
}
// Update size.
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("TextEdit"));
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("TextEdit"));
tree->set_custom_minimum_size(tree->get_background_size() + Size2i(0, MIN(tree->get_internal_min_size().height, 6 * font->get_height(font_size))));
}
Variant EditorPropertyOrderedList::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
if (read_only) {
return Variant();
}
if (tree->get_button_id_at_position(p_point) != -1) {
return Variant();
}
TreeItem *item = tree->get_next_selected(nullptr);
if (!item) {
return Variant();
}
String value = item->get_metadata(0);
if (!names.has(value)) {
return Variant();
}
String name = names[value];
// Preview.
HBoxContainer *hb = memnew(HBoxContainer);
Label *label_prev = memnew(Label(vformat("%s (%s)", name, value)));
label_prev->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
hb->add_child(label_prev);
set_drag_preview(hb);
// Drag data.
Dictionary drag_data;
drag_data["type"] = "list_reorder";
drag_data["id"] = get_instance_id();
drag_data["value"] = value;
tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
return drag_data;
}
bool EditorPropertyOrderedList::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
if (read_only) {
return false;
}
Dictionary d = p_data;
if (!d.has("type") || !d.has("id") || !d.has("value")) {
return false;
}
if (d["type"] != "list_reorder" || d["id"] != get_instance_id()) {
return false;
}
TreeItem *item = tree->get_item_at_position(p_point);
if (!item) {
return false;
}
int section = tree->get_drop_section_at_position(p_point);
if (section == -100) {
return false;
}
return true;
}
void EditorPropertyOrderedList::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
if (!can_drop_data_fw(p_point, p_data, p_from)) {
return;
}
Dictionary d = p_data;
String drop_value = d["value"];
TreeItem *item = tree->get_item_at_position(p_point);
ERR_FAIL_COND(!item);
String target_value = item->get_metadata(0);
int section = tree->get_drop_section_at_position(p_point);
ERR_FAIL_COND(section == -100);
tree->set_drop_mode_flags(Tree::DROP_MODE_DISABLED);
Vector<String> new_values = values;
new_values.erase(drop_value);
if (section == 1) {
int pos = new_values.find(target_value);
if (pos == -1) {
return;
}
new_values.insert(pos + 1, drop_value);
} else {
int pos = new_values.find(target_value);
if (pos == -1) {
return;
}
new_values.insert(pos, drop_value);
}
values = new_values;
_update_tree();
emit_changed(get_edited_property(), String(",").join(values));
}
void EditorPropertyOrderedList::_tree_button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) {
ERR_FAIL_COND(!p_item);
if (p_button != MouseButton::LEFT) {
return;
}
String drop_value = p_item->get_metadata(0);
Vector<String> new_values = values;
if (p_id == BUTTON_DOWN) {
int pos = new_values.find(drop_value);
if (pos == -1) {
return;
}
new_values.erase(drop_value);
new_values.insert(pos + 1, drop_value);
} else {
int pos = new_values.find(drop_value);
if (pos == -1) {
return;
}
new_values.erase(drop_value);
new_values.insert(pos - 1, drop_value);
}
values = new_values;
_update_tree();
emit_changed(get_edited_property(), String(",").join(values));
}
void EditorPropertyOrderedList::update_property() {
String current_value = get_edited_property_value();
values = current_value.split(",");
print_line(values);
_update_tree();
}
void EditorPropertyOrderedList::setup(const Vector<String> &p_options) {
values.clear();
names.clear();
for (const String &option : p_options) {
Vector<String> text_split = option.split(":");
if (text_split.size() != 1) {
names[text_split[1]] = text_split[0];
} else {
names[text_split[0]] = text_split[0];
}
}
_update_tree();
}
void EditorPropertyOrderedList::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
tree->add_theme_style_override(SNAME("panel"), get_theme_stylebox(SNAME("normal"), SNAME("Button")));
tree->add_theme_style_override(SNAME("focus"), get_theme_stylebox(SNAME("focus"), SNAME("Button")));
_update_tree();
} break;
}
}
EditorPropertyOrderedList::EditorPropertyOrderedList() {
HBoxContainer *hb = memnew(HBoxContainer);
add_child(hb);
default_layout = memnew(HBoxContainer);
default_layout->set_h_size_flags(SIZE_EXPAND_FILL);
hb->add_child(default_layout);
tree = memnew(Tree);
tree->set_h_size_flags(SIZE_EXPAND_FILL);
tree->set_hide_root(true);
tree->connect("button_clicked", callable_mp(this, &EditorPropertyOrderedList::_tree_button_pressed));
default_layout->add_child(tree);
SET_DRAG_FORWARDING_GCD(tree, EditorPropertyOrderedList);
add_focusable(tree);
}
///////////////////// TEXT ENUM /////////////////////////
void EditorPropertyTextEnum::_set_read_only(bool p_read_only) {
@ -3615,7 +3835,12 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
}
} break;
case Variant::STRING: {
if (p_hint == PROPERTY_HINT_ENUM || p_hint == PROPERTY_HINT_ENUM_SUGGESTION) {
if (p_hint == PROPERTY_HINT_ORDERED_LIST) {
EditorPropertyOrderedList *editor = memnew(EditorPropertyOrderedList);
Vector<String> options = p_hint_text.split(",", false);
editor->setup(options);
return editor;
} else if (p_hint == PROPERTY_HINT_ENUM || p_hint == PROPERTY_HINT_ENUM_SUGGESTION) {
EditorPropertyTextEnum *editor = memnew(EditorPropertyTextEnum);
Vector<String> options = p_hint_text.split(",", false);
editor->setup(options, false, (p_hint == PROPERTY_HINT_ENUM_SUGGESTION));

View File

@ -45,6 +45,8 @@ class PropertySelector;
class SceneTreeDialog;
class TextEdit;
class TextureButton;
class Tree;
class TreeItem;
class EditorPropertyNil : public EditorProperty {
GDCLASS(EditorPropertyNil, EditorProperty);
@ -132,6 +134,39 @@ public:
EditorPropertyTextEnum();
};
class EditorPropertyOrderedList : public EditorProperty {
GDCLASS(EditorPropertyOrderedList, EditorProperty);
enum EditorPropertyOrderedListButtonID {
BUTTON_UP,
BUTTON_DOWN,
};
HBoxContainer *default_layout = nullptr;
bool read_only = false;
Tree *tree = nullptr;
HashMap<String, String> names;
Vector<String> values;
void _update_tree();
void _tree_button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button);
protected:
virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
public:
void setup(const Vector<String> &p_options);
virtual void update_property() override;
EditorPropertyOrderedList();
};
class EditorPropertyPath : public EditorProperty {
GDCLASS(EditorPropertyPath, EditorProperty);
Vector<String> extensions;

View File

@ -4196,10 +4196,20 @@ void Tree::set_editor_selection(int p_from_line, int p_to_line, int p_from_colum
}
}
Size2 Tree::get_background_size() const {
const Ref<StyleBox> background = theme_cache.panel_style;
// This is the background stylebox's content rect.
const real_t width = background->get_margin(SIDE_LEFT) + background->get_margin(SIDE_RIGHT);
const real_t height = background->get_margin(SIDE_TOP) + background->get_margin(SIDE_BOTTOM);
return Size2(width, height);
}
Size2 Tree::get_internal_min_size() const {
Size2i size;
if (root) {
size.height += get_item_height(root);
size.height -= theme_cache.v_separation;
}
for (int i = 0; i < columns.size(); i++) {
size.width += get_column_minimum_width(i);

View File

@ -657,7 +657,6 @@ private:
bool h_scroll_enabled = true;
bool v_scroll_enabled = true;
Size2 get_internal_min_size() const;
void update_scrollbars();
Rect2 search_item_rect(TreeItem *p_from, TreeItem *p_item);
@ -798,6 +797,8 @@ public:
void ensure_cursor_is_visible();
Rect2 get_custom_popup_rect() const;
Size2 get_background_size() const;
Size2 get_internal_min_size() const;
int get_item_offset(TreeItem *p_item) const;
Rect2 get_item_rect(TreeItem *p_item, int p_column = -1, int p_button = -1) const;