From 760cdbe1a31837bcb142de6912718fd80f57346b Mon Sep 17 00:00:00 2001 From: Juan Linietsky Date: Sun, 25 Jun 2017 17:30:28 -0300 Subject: [PATCH] -Added folding to property editor, persistent on objects it edits -Some changes to tree to support this properly --- core/core_string_names.cpp | 3 + core/core_string_names.h | 3 + core/object.cpp | 42 +++++++++++++ core/object.h | 6 ++ editor/editor_node.cpp | 1 + editor/property_editor.cpp | 54 ++++++++++++++--- editor/property_editor.h | 7 ++- scene/gui/tree.cpp | 119 +++++++++++++++++++++++++++++++++---- scene/gui/tree.h | 22 +++++++ 9 files changed, 235 insertions(+), 22 deletions(-) diff --git a/core/core_string_names.cpp b/core/core_string_names.cpp index e35ac2b72cb..0ed44b0cb79 100644 --- a/core/core_string_names.cpp +++ b/core/core_string_names.cpp @@ -44,4 +44,7 @@ CoreStringNames::CoreStringNames() { _iter_next = StaticCString::create("_iter_next"); _iter_get = StaticCString::create("_iter_get"); get_rid = StaticCString::create("get_rid"); +#ifdef TOOLS_ENABLED + _sections_unfolded = StaticCString::create("_sections_unfolded"); +#endif } diff --git a/core/core_string_names.h b/core/core_string_names.h index 6672772432e..4b4f87a8f01 100644 --- a/core/core_string_names.h +++ b/core/core_string_names.h @@ -61,6 +61,9 @@ public: StringName _iter_next; StringName _iter_get; StringName get_rid; +#ifdef TOOLS_ENABLED + StringName _sections_unfolded; +#endif }; #endif // SCENE_STRING_NAMES_H diff --git a/core/object.cpp b/core/object.cpp index f20e93f9d7f..d83b2d0c6e6 100644 --- a/core/object.cpp +++ b/core/object.cpp @@ -419,6 +419,16 @@ void Object::set(const StringName &p_name, const Variant &p_value, bool *r_valid if (r_valid) *r_valid = true; return; +#ifdef TOOLS_ENABLED + } else if (p_name == CoreStringNames::get_singleton()->_sections_unfolded) { + Array arr = p_value; + for (int i = 0; i < arr.size(); i++) { + editor_section_folding.insert(arr[i]); + } + if (r_valid) + *r_valid = true; + return; +#endif } else { //something inside the object... :| bool success = _setv(p_name, p_value); @@ -464,6 +474,16 @@ Variant Object::get(const StringName &p_name, bool *r_valid) const { if (r_valid) *r_valid = true; return ret; +#ifdef TOOLS_ENABLED + } else if (p_name == CoreStringNames::get_singleton()->_sections_unfolded) { + Array array; + for (Set::Element *E = editor_section_folding.front(); E; E = E->next()) { + array.push_back(E->get()); + } + if (r_valid) + *r_valid = true; + return array; +#endif } else { //something inside the object... :| bool success = _getv(p_name, ret); @@ -516,6 +536,11 @@ void Object::get_property_list(List *p_list, bool p_reversed) cons if (!is_class("Script")) // can still be set, but this is for userfriendlyness p_list->push_back(PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NONZERO)); +#ifdef TOOLS_ENABLED + if (editor_section_folding.size()) { + p_list->push_back(PropertyInfo(Variant::ARRAY, CoreStringNames::get_singleton()->_sections_unfolded, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } +#endif if (!metadata.empty()) p_list->push_back(PropertyInfo(Variant::DICTIONARY, "__meta__", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_STORE_IF_NONZERO)); if (script_instance && !p_reversed) { @@ -1571,6 +1596,23 @@ void Object::_clear_internal_resource_paths(const Variant &p_var) { } } +#ifdef TOOLS_ENABLED +void Object::editor_set_section_unfold(const String &p_section, bool p_unfolded) { + + set_edited(true); + if (p_unfolded) + editor_section_folding.insert(p_section); + else + editor_section_folding.erase(p_section); +} + +bool Object::editor_is_section_unfolded(const String &p_section) { + + return editor_section_folding.has(p_section); +} + +#endif + void Object::clear_internal_resource_paths() { List pinfo; diff --git a/core/object.h b/core/object.h index 83b03b92398..fabd10fa1f5 100644 --- a/core/object.h +++ b/core/object.h @@ -430,6 +430,7 @@ private: #ifdef TOOLS_ENABLED bool _edited; uint32_t _edited_version; + Set editor_section_folding; #endif ScriptInstance *script_instance; RefPtr script; @@ -666,6 +667,11 @@ public: _FORCE_INLINE_ void set_message_translation(bool p_enable) { _can_translate = p_enable; } _FORCE_INLINE_ bool can_translate_messages() const { return _can_translate; } +#ifdef TOOLS_ENABLED + void editor_set_section_unfold(const String &p_section, bool p_unfolded); + bool editor_is_section_unfolded(const String &p_section); +#endif + void clear_internal_resource_paths(); Object(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 097c2977e84..8657deef03b 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5784,6 +5784,7 @@ EditorNode::EditorNode() { property_editor = memnew(PropertyEditor); property_editor->set_autoclear(true); property_editor->set_show_categories(true); + property_editor->set_use_folding(true); property_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); property_editor->set_use_doc_hints(true); property_editor->set_enable_capitalize_paths(bool(EDITOR_DEF("interface/capitalize_properties", true))); diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index 65ec697b735..86671f1c37b 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -2696,20 +2696,20 @@ void PropertyEditor::_notification(int p_what) { } } -TreeItem *PropertyEditor::get_parent_node(String p_path, HashMap &item_paths, TreeItem *root) { +TreeItem *PropertyEditor::get_parent_node(String p_path, HashMap &item_paths, TreeItem *root, TreeItem *category) { TreeItem *item = NULL; if (p_path == "") { - item = root; + item = category ? category : root; } else if (item_paths.has(p_path)) { item = item_paths.get(p_path); } else { //printf("path %s parent path %s - item name %s\n",p_path.ascii().get_data(),p_path.left( p_path.find_last("/") ).ascii().get_data(),p_path.right( p_path.find_last("/") ).ascii().get_data() ); - TreeItem *parent = get_parent_node(p_path.left(p_path.find_last("/")), item_paths, root); + TreeItem *parent = get_parent_node(p_path.left(p_path.find_last("/")), item_paths, root, NULL); item = tree->create_item(parent); String name = (p_path.find("/") != -1) ? p_path.right(p_path.find_last("/") + 1) : p_path; @@ -2720,10 +2720,22 @@ TreeItem *PropertyEditor::get_parent_node(String p_path, HashMapset_editable(0, false); + if (!subsection_selectable) { + item->set_expand_right(0, true); + } item->set_selectable(0, subsection_selectable); item->set_editable(1, false); item->set_selectable(1, subsection_selectable); + if (use_folding) { + if (!obj->editor_is_section_unfolded(p_path)) { + updating_folding = true; + item->set_collapsed(true); + updating_folding = false; + } + item->set_metadata(0, p_path); + } + if (item->get_parent() == root) { item->set_custom_bg_color(0, get_color("prop_subsection", "Editor")); @@ -2935,17 +2947,21 @@ void PropertyEditor::update_tree() { TreeItem *sep = tree->create_item(root); current_category = sep; String type = p.name; - /*if (has_icon(type,"EditorIcons")) - sep->set_icon(0,get_icon(type,"EditorIcons") ); + //* + if (has_icon(type, "EditorIcons")) + sep->set_icon(0, get_icon(type, "EditorIcons")); else - sep->set_icon(0,get_icon("Object","EditorIcons") ); - print_line("CATEGORY: "+type); - */ + sep->set_icon(0, get_icon("Object", "EditorIcons")); + + //*/ sep->set_text(0, type); + sep->set_expand_right(0, true); sep->set_selectable(0, false); sep->set_selectable(1, false); sep->set_custom_bg_color(0, get_color("prop_category", "Editor")); sep->set_custom_bg_color(1, get_color("prop_category", "Editor")); + sep->set_text_align(0, TreeItem::ALIGN_CENTER); + sep->set_disable_folding(true); if (use_doc_hints) { StringName type = p.name; @@ -3005,7 +3021,7 @@ void PropertyEditor::update_tree() { } //printf("property %s\n",basename.ascii().get_data()); - TreeItem *parent = get_parent_node(path, item_path, current_category ? current_category : root); + TreeItem *parent = get_parent_node(path, item_path, root, current_category); /* if (parent->get_parent()==root) parent=root; @@ -3684,6 +3700,16 @@ void PropertyEditor::_draw_transparency(Object *t, const Rect2 &p_rect) { tree->draw_rect(area, color); } +void PropertyEditor::_item_folded(Object *item_obj) { + + if (updating_folding) + return; + + TreeItem *item = item_obj->cast_to(); + + obj->editor_set_section_unfold(item->get_metadata(0), !item->is_collapsed()); +} + void PropertyEditor::_item_selected() { TreeItem *item = tree->get_selected(); @@ -4187,6 +4213,7 @@ void PropertyEditor::_bind_methods() { ClassDB::bind_method("_item_edited", &PropertyEditor::_item_edited); ClassDB::bind_method("_item_selected", &PropertyEditor::_item_selected); + ClassDB::bind_method("_item_folded", &PropertyEditor::_item_folded); ClassDB::bind_method("_custom_editor_request", &PropertyEditor::_custom_editor_request); ClassDB::bind_method("_custom_editor_edited", &PropertyEditor::_custom_editor_edited); ClassDB::bind_method("_custom_editor_edited_field", &PropertyEditor::_custom_editor_edited_field, DEFVAL("")); @@ -4292,11 +4319,18 @@ void PropertyEditor::set_subsection_selectable(bool p_selectable) { update_tree(); } +void PropertyEditor::set_use_folding(bool p_enable) { + + use_folding = p_enable; + tree->set_hide_folding(false); +} + PropertyEditor::PropertyEditor() { _prop_edited = "property_edited"; hide_script = false; + use_folding = false; undo_redo = NULL; obj = NULL; @@ -4329,6 +4363,7 @@ PropertyEditor::PropertyEditor() { tree->connect("item_edited", this, "_item_edited", varray(), CONNECT_DEFERRED); tree->connect("cell_selected", this, "_item_selected"); + tree->connect("item_collapsed", this, "_item_folded"); tree->set_drag_forwarding(this); @@ -4358,6 +4393,7 @@ PropertyEditor::PropertyEditor() { show_categories = false; refresh_countdown = 0; use_doc_hints = false; + updating_folding = true; use_filter = false; subsection_selectable = false; show_type_icons = EDITOR_DEF("interface/show_type_icons", false); diff --git a/editor/property_editor.h b/editor/property_editor.h index c02c301acf0..47bd807c3f5 100644 --- a/editor/property_editor.h +++ b/editor/property_editor.h @@ -190,6 +190,9 @@ class PropertyEditor : public Control { bool use_filter; bool subsection_selectable; bool hide_script; + bool use_folding; + + bool updating_folding; HashMap pending; String selected_property; @@ -206,7 +209,7 @@ class PropertyEditor : public Control { void _item_selected(); void _item_edited(); - TreeItem *get_parent_node(String p_path, HashMap &item_paths, TreeItem *root); + TreeItem *get_parent_node(String p_path, HashMap &item_paths, TreeItem *root, TreeItem *category); void set_item_text(TreeItem *p_item, int p_type, const String &p_name, int p_hint = PROPERTY_HINT_NONE, const String &p_hint_text = ""); @@ -244,6 +247,7 @@ class PropertyEditor : public Control { void _resource_preview_done(const String &p_path, const Ref &p_preview, Variant p_ud); void _draw_transparency(Object *t, const Rect2 &p_rect); + void _item_folded(Object *item_obj); UndoRedo *undo_redo; @@ -285,6 +289,7 @@ public: void set_subsection_selectable(bool p_selectable); + void set_use_folding(bool p_enable); PropertyEditor(); ~PropertyEditor(); }; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index d864d9fce7b..12679c7ba84 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -622,6 +622,40 @@ bool TreeItem::is_custom_set_as_button(int p_column) const { return cells[p_column].custom_button; } +void TreeItem::set_text_align(int p_column, TextAlign p_align) { + ERR_FAIL_INDEX(p_column, cells.size()); + cells[p_column].text_align = p_align; + _changed_notify(p_column); +} + +TreeItem::TextAlign TreeItem::get_text_align(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), ALIGN_LEFT); + return cells[p_column].text_align; +} + +void TreeItem::set_expand_right(int p_column, bool p_enable) { + + ERR_FAIL_INDEX(p_column, cells.size()); + cells[p_column].expand_right = p_enable; + _changed_notify(p_column); +} + +bool TreeItem::get_expand_right(int p_column) const { + + ERR_FAIL_INDEX_V(p_column, cells.size(), false); + return cells[p_column].expand_right; +} + +void TreeItem::set_disable_folding(bool p_disable) { + + disable_folding = p_disable; + _changed_notify(0); +} + +bool TreeItem::is_folding_disabled() const { + return disable_folding; +} + void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell_mode", "column", "mode"), &TreeItem::set_cell_mode); @@ -692,12 +726,19 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("erase_button", "column", "button_idx"), &TreeItem::erase_button); ClassDB::bind_method(D_METHOD("is_button_disabled", "column", "button_idx"), &TreeItem::is_button_disabled); + ClassDB::bind_method(D_METHOD("set_expand_right", "column", "enable"), &TreeItem::set_expand_right); + ClassDB::bind_method(D_METHOD("get_expand_right", "column"), &TreeItem::get_expand_right); + ClassDB::bind_method(D_METHOD("set_tooltip", "column", "tooltip"), &TreeItem::set_tooltip); ClassDB::bind_method(D_METHOD("get_tooltip", "column"), &TreeItem::get_tooltip); - + ClassDB::bind_method(D_METHOD("set_text_align", "column", "text_align"), &TreeItem::set_text_align); + ClassDB::bind_method(D_METHOD("get_text_align", "column"), &TreeItem::get_text_align); ClassDB::bind_method(D_METHOD("move_to_top"), &TreeItem::move_to_top); ClassDB::bind_method(D_METHOD("move_to_bottom"), &TreeItem::move_to_bottom); + ClassDB::bind_method(D_METHOD("set_disable_folding", "disable"), &TreeItem::set_disable_folding); + ClassDB::bind_method(D_METHOD("is_folding_disabled"), &TreeItem::is_folding_disabled); + BIND_CONSTANT(CELL_MODE_STRING); BIND_CONSTANT(CELL_MODE_CHECK); BIND_CONSTANT(CELL_MODE_RANGE); @@ -724,6 +765,7 @@ TreeItem::TreeItem(Tree *p_tree) { tree = p_tree; collapsed = false; + disable_folding = false; parent = 0; // parent item next = 0; // next in list @@ -894,6 +936,32 @@ int Tree::get_item_height(TreeItem *p_item) const { void Tree::draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color) { Rect2i rect = p_rect; + Ref font = cache.font; + String text = p_cell.text; + if (p_cell.suffix != String()) + text += " " + p_cell.suffix; + + int w = 0; + if (!p_cell.icon.is_null()) { + Size2i bmsize = p_cell.get_icon_size(); + + if (p_cell.icon_max_w > 0 && bmsize.width > p_cell.icon_max_w) { + bmsize.width = p_cell.icon_max_w; + } + w += bmsize.width + cache.hseparation; + } + w += font->get_string_size(text).width; + + switch (p_cell.text_align) { + case TreeItem::ALIGN_LEFT: + break; //do none + case TreeItem::ALIGN_CENTER: + rect.position.x = MAX(0, (rect.size.width - w) / 2); + break; //do none + case TreeItem::ALIGN_RIGHT: + rect.position.x = MAX(0, (rect.size.width - w)); + break; //do none + } RID ci = get_canvas_item(); if (!p_cell.icon.is_null()) { @@ -914,12 +982,6 @@ void Tree::draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, co rect.size.x-=Math::floor(rect.size.y/2); */ - Ref font = cache.font; - - String text = p_cell.text; - if (p_cell.suffix != String()) - text += " " + p_cell.suffix; - rect.position.y += Math::floor((rect.size.y - font->get_height()) / 2.0) + font->get_ascent(); font->draw(ci, rect.position, text, p_color, rect.size.x); } @@ -970,7 +1032,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (!skip && (p_pos.y + label_h - cache.offset.y) > 0) { - if (!hide_folding && p_item->childs) { //has childs, draw the guide box + if (!p_item->disable_folding && !hide_folding && p_item->childs) { //has childs, draw the guide box Ref arrow; @@ -991,9 +1053,15 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int font_ascent = font->get_ascent(); - int ofs = p_pos.x + (hide_folding ? cache.hseparation : cache.item_margin); + int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); + int skip = 0; for (int i = 0; i < columns.size(); i++) { + if (skip) { + skip--; + continue; + } + int w = get_column_width(i); if (i == 0) { @@ -1011,6 +1079,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 w -= cache.hseparation; } + if (p_item->cells[i].expand_right) { + + int plus = 1; + while (i + plus < columns.size() && !p_item->cells[i + plus].editable && p_item->cells[i + plus].mode == TreeItem::CELL_MODE_STRING && p_item->cells[i + plus].text == "" && p_item->cells[i + plus].icon.is_null()) { + w += get_column_width(i + plus); + plus++; + skip++; + } + } + int bw = 0; for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { Ref b = p_item->cells[i].buttons[j].texture; @@ -1295,8 +1373,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 while (c) { if (cache.draw_relationship_lines == 1) { - int root_ofs = children_pos.x + (hide_folding ? cache.hseparation : cache.item_margin); - int parent_ofs = p_pos.x + (hide_folding ? cache.hseparation : cache.item_margin); + int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); + int parent_ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; if (c->get_children() != NULL) root_pos -= Point2i(cache.arrow->get_width(), 0); @@ -1488,7 +1566,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool return -1; } - if (!hide_folding && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + cache.item_margin))) { + if (!p_item->disable_folding && !hide_folding && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + cache.item_margin))) { if (p_item->childs) p_item->set_collapsed(!p_item->is_collapsed()); @@ -1504,6 +1582,18 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool for (int i = 0; i < columns.size(); i++) { col_width = get_column_width(i); + + if (p_item->cells[i].expand_right) { + + int plus = 1; + while (i + plus < columns.size() && !p_item->cells[i + plus].editable && p_item->cells[i + plus].mode == TreeItem::CELL_MODE_STRING && p_item->cells[i + plus].text == "" && p_item->cells[i + plus].icon.is_null()) { + plus++; + col_width += cache.hseparation; + col_width += get_column_width(i + plus); + plus++; + } + } + if (x > col_width) { col_ofs += col_width; x -= col_width; @@ -1528,6 +1618,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool x -= cache.hseparation; } + if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_children()) { + p_item->set_collapsed(!p_item->is_collapsed()); + return -1; //collapse/uncollapse because nothing can be done with item + } + TreeItem::Cell &c = p_item->cells[col]; bool already_selected = c.selected; diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 097e0110e89..59e35bb2302 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -58,6 +58,12 @@ public: CELL_MODE_CUSTOM, ///< Contains a custom value, show a string, and an edit button }; + enum TextAlign { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT + }; + private: friend class Tree; @@ -82,6 +88,9 @@ private: bool custom_bg_outline; Color bg_color; bool custom_button; + bool expand_right; + + TextAlign text_align; Variant meta; String tooltip; @@ -122,6 +131,8 @@ private: custom_bg_color = false; expr = false; icon_max_w = 0; + text_align = ALIGN_LEFT; + expand_right = false; } Size2 get_icon_size() const; @@ -131,6 +142,7 @@ private: Vector cells; bool collapsed; // wont show childs + bool disable_folding; TreeItem *parent; // parent item TreeItem *next; // next in list @@ -248,13 +260,23 @@ public: void clear_children(); + void set_text_align(int p_column, TextAlign p_align); + TextAlign get_text_align(int p_column) const; + + void set_expand_right(int p_column, bool p_enable); + bool get_expand_right(int p_column) const; + void move_to_top(); void move_to_bottom(); + void set_disable_folding(bool p_disable); + bool is_folding_disabled() const; + ~TreeItem(); }; VARIANT_ENUM_CAST(TreeItem::TreeCellMode); +VARIANT_ENUM_CAST(TreeItem::TextAlign); class Tree : public Control {