diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml index 84f8523da3a..586458bd283 100644 --- a/doc/classes/EditorProperty.xml +++ b/doc/classes/EditorProperty.xml @@ -101,6 +101,13 @@ Used by sub-inspectors. Emit it if what was selected was an Object ID. + + + + + Emitted when the revertability (i.e., whether it has a non-default value and thus is displayed with a revert icon) of a property has changed. + + diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 133a4a3398b..e06e3cbc5f7 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -471,6 +471,9 @@ void EditorProperty::update_revert_and_pin_status() { bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property, ¤t) && !is_read_only(); if (new_can_revert != can_revert || new_pinned != pinned) { + if (new_can_revert != can_revert) { + emit_signal(SNAME("property_can_revert_changed"), property, new_can_revert); + } can_revert = new_can_revert; pinned = new_pinned; update(); @@ -784,6 +787,9 @@ void EditorProperty::expand_all_folding() { void EditorProperty::collapse_all_folding() { } +void EditorProperty::expand_revertable() { +} + void EditorProperty::set_selectable(bool p_selectable) { selectable = p_selectable; } @@ -966,6 +972,7 @@ void EditorProperty::_bind_methods() { ADD_SIGNAL(MethodInfo("property_keyed_with_value", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); ADD_SIGNAL(MethodInfo("property_checked", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "checked"))); ADD_SIGNAL(MethodInfo("property_pinned", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "pinned"))); + ADD_SIGNAL(MethodInfo("property_can_revert_changed", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "can_revert"))); ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "focusable_idx"))); @@ -1225,12 +1232,15 @@ void EditorInspectorSection::_notification(int p_what) { // Get the section header font. Ref font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + Color font_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); // Get the right direction arrow texture, if the section is foldable. Ref arrow; + bool folded = foldable; if (foldable) { if (object->editor_is_section_unfolded(section)) { arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); + folded = false; } else { if (is_layout_rtl()) { arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); @@ -1274,28 +1284,71 @@ void EditorInspectorSection::_notification(int p_what) { } draw_rect(header_rect, c); - // Draw header title and folding arrow. - const int arrow_margin = 2; - const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0; - Color color = get_theme_color(SNAME("font_color")); - float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE) - section_indent; - Point2 text_offset = Point2(0, font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2); - HorizontalAlignment text_align = HORIZONTAL_ALIGNMENT_LEFT; - if (rtl) { - text_align = HORIZONTAL_ALIGNMENT_RIGHT; - } else { - text_offset.x = section_indent + Math::round(arrow_width + arrow_margin * EDSCALE); - } - draw_string(font, text_offset.floor(), label, text_align, text_width, font_size, color); + // Draw header title, folding arrow and coutn of revertable properties. + { + int separation = Math::round(2 * EDSCALE); - if (arrow.is_valid()) { - Point2 arrow_position = Point2(0, (header_height - arrow->get_height()) / 2); - if (rtl) { - arrow_position.x = get_size().width - section_indent - arrow->get_width() - Math::round(arrow_margin * EDSCALE); - } else { - arrow_position.x = section_indent + Math::round(arrow_margin * EDSCALE); + int margin_start = section_indent + separation; + int margin_end = separation; + + // - Arrow. + if (arrow.is_valid()) { + Point2 arrow_position; + if (rtl) { + arrow_position.x = get_size().width - (margin_start + arrow->get_width()); + } else { + arrow_position.x = margin_start; + } + arrow_position.y = (header_height - arrow->get_height()) / 2; + draw_texture(arrow, arrow_position); + margin_start += arrow->get_width(); } - draw_texture(arrow, arrow_position.floor()); + + int available = get_size().width - (margin_start + margin_end); + + // - Count of revertable properties. + String num_revertable_str; + int num_revertable_width = 0; + if (folded && revertable_properties.size()) { + int label_width = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, available, font_size, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS).x; + + Ref light_font = get_theme_font(SNAME("main"), SNAME("EditorFonts")); + int light_font_size = get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")); + Color light_font_color = get_theme_color(SNAME("disabled_font_color"), SNAME("Editor")); + + // Can we fit the long version of the revertable count text? + if (revertable_properties.size() == 1) { + num_revertable_str = "(1 change)"; + } else { + num_revertable_str = vformat("(%d changes)", revertable_properties.size()); + } + num_revertable_width = light_font->get_string_size(num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, TextServer::JUSTIFICATION_NONE).x; + if (label_width + separation + num_revertable_width > available) { + // We'll have to use the short version. + num_revertable_str = vformat("(%d)", revertable_properties.size()); + num_revertable_width = light_font->get_string_size(num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, TextServer::JUSTIFICATION_NONE).x; + } + + Point2 text_offset = Point2( + margin_end, + light_font->get_ascent(light_font_size) + (header_height - light_font->get_height(light_font_size)) / 2); + if (!rtl) { + text_offset.x = get_size().width - (text_offset.x + num_revertable_width); + } + draw_string(light_font, text_offset, num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, light_font_color, TextServer::JUSTIFICATION_NONE); + margin_end += num_revertable_width + separation; + available -= num_revertable_width + separation; + } + + // - Label. + Point2 text_offset = Point2( + margin_start, + font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2); + if (rtl) { + text_offset.x = margin_end; + } + HorizontalAlignment text_align = rtl ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT; + draw_string(font, text_offset, label, text_align, available, font_size, font_color, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); } // Draw dropping highlight. @@ -1471,6 +1524,22 @@ void EditorInspectorSection::fold() { update(); } +bool EditorInspectorSection::has_revertable_properties() const { + return !revertable_properties.is_empty(); +} + +void EditorInspectorSection::property_can_revert_changed(const String &p_path, bool p_can_revert) { + bool had_revertable_properties = has_revertable_properties(); + if (p_can_revert) { + revertable_properties.insert(p_path); + } else { + revertable_properties.erase(p_path); + } + if (has_revertable_properties() != had_revertable_properties) { + update(); + } +} + void EditorInspectorSection::_bind_methods() { ClassDB::bind_method(D_METHOD("setup", "section", "label", "object", "bg_color", "foldable"), &EditorInspectorSection::setup); ClassDB::bind_method(D_METHOD("get_vbox"), &EditorInspectorSection::get_vbox); @@ -2330,7 +2399,7 @@ String EditorInspector::get_selected_path() const { return property_selected; } -void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, Ref ped) { +void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref ped) { for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) { EditorProperty *ep = Object::cast_to(F.property_editor); current_vbox->add_child(F.property_editor); @@ -2370,6 +2439,10 @@ void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, Refconnect("property_can_revert_changed", callable_mp(p_section, &EditorInspectorSection::property_can_revert_changed)); + } + ep->set_read_only(read_only); ep->update_property(); ep->_update_pin_flags(); @@ -2476,7 +2549,7 @@ void EditorInspector::update_tree() { // Get the lists of editors to add the beginning. for (Ref &ped : valid_plugins) { ped->parse_begin(object); - _parse_added_editors(main_vbox, ped); + _parse_added_editors(main_vbox, nullptr, ped); } StringName type_name; @@ -2601,7 +2674,7 @@ void EditorInspector::update_tree() { // Add editors at the start of a category. for (Ref &ped : valid_plugins) { ped->parse_category(object, p.name); - _parse_added_editors(main_vbox, ped); + _parse_added_editors(main_vbox, nullptr, ped); } continue; @@ -2793,7 +2866,7 @@ void EditorInspector::update_tree() { // Add editors at the start of a group. for (Ref &ped : valid_plugins) { ped->parse_group(object, path); - _parse_added_editors(section->get_vbox(), ped); + _parse_added_editors(section->get_vbox(), section, ped); } vbox_per_path[root_vbox][acc_path] = section->get_vbox(); @@ -2975,6 +3048,12 @@ void EditorInspector::update_tree() { editor_property_map[prop].push_back(ep); } } + + EditorInspectorSection *section = Object::cast_to(current_vbox->get_parent()); + if (section) { + ep->connect("property_can_revert_changed", callable_mp(section, &EditorInspectorSection::property_can_revert_changed)); + } + ep->set_draw_warning(draw_warning); ep->set_use_folding(use_folding); ep->set_checkable(checkable); @@ -3027,7 +3106,7 @@ void EditorInspector::update_tree() { // Get the lists of to add at the end. for (Ref &ped : valid_plugins) { ped->parse_end(object); - _parse_added_editors(main_vbox, ped); + _parse_added_editors(main_vbox, nullptr, ped); } } @@ -3180,6 +3259,44 @@ void EditorInspector::expand_all_folding() { } } +void EditorInspector::expand_revertable() { + HashSet sections_to_unfold[2]; + for (EditorInspectorSection *E : sections) { + if (E->has_revertable_properties()) { + sections_to_unfold[0].insert(E); + } + } + + // Climb up the hierachy doing double buffering with the sets. + int a = 0; + int b = 1; + while (sections_to_unfold[a].size()) { + for (EditorInspectorSection *E : sections_to_unfold[a]) { + E->unfold(); + + Node *n = E->get_parent(); + while (n) { + if (Object::cast_to(n)) { + break; + } + if (Object::cast_to(n) && !sections_to_unfold[a].has((EditorInspectorSection *)n)) { + sections_to_unfold[b].insert((EditorInspectorSection *)n); + } + n = n->get_parent(); + } + } + + sections_to_unfold[a].clear(); + SWAP(a, b); + } + + for (const KeyValue> &F : editor_property_map) { + for (EditorProperty *E : F.value) { + E->expand_revertable(); + } + } +} + void EditorInspector::set_scroll_offset(int p_offset) { set_v_scroll(p_offset); } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 4ec3513da5e..54533de9607 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -184,6 +184,7 @@ public: virtual void expand_all_folding(); virtual void collapse_all_folding(); + virtual void expand_revertable(); virtual Variant get_drag_data(const Point2 &p_point) override; virtual void update_cache(); @@ -281,6 +282,8 @@ class EditorInspectorSection : public Container { Timer *dropping_unfold_timer = nullptr; bool dropping = false; + HashSet revertable_properties; + void _test_unfold(); protected: @@ -299,6 +302,9 @@ public: void unfold(); void fold(); + bool has_revertable_properties() const; + void property_can_revert_changed(const String &p_path, bool p_can_revert); + EditorInspectorSection(); ~EditorInspectorSection(); }; @@ -517,7 +523,7 @@ class EditorInspector : public ScrollContainer { void _edit_request_change(Object *p_object, const String &p_prop); void _filter_changed(const String &p_text); - void _parse_added_editors(VBoxContainer *current_vbox, Ref ped); + void _parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref ped); void _vscroll_changed(double); @@ -579,6 +585,7 @@ public: void collapse_all_folding(); void expand_all_folding(); + void expand_revertable(); void set_scroll_offset(int p_offset); int get_scroll_offset() const; diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 919443eeb8d..09c55d5d242 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -3918,6 +3918,12 @@ void EditorPropertyResource::expand_all_folding() { } } +void EditorPropertyResource::expand_revertable() { + if (sub_inspector) { + sub_inspector->expand_revertable(); + } +} + void EditorPropertyResource::set_use_sub_inspector(bool p_enable) { use_sub_inspector = p_enable; } diff --git a/editor/editor_properties.h b/editor/editor_properties.h index b3aac6e8ca3..cfe0c2f38c0 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -856,6 +856,7 @@ public: void collapse_all_folding() override; void expand_all_folding() override; + void expand_revertable() override; void set_use_sub_inspector(bool p_enable); diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index 4cf3d416441..79d94246adb 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -65,6 +65,9 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) { case COLLAPSE_ALL: { _menu_collapseall(); } break; + case EXPAND_REVERTABLE: { + _menu_expand_revertable(); + } break; case RESOURCE_SAVE: { _save_resource(false); @@ -401,6 +404,10 @@ void InspectorDock::_menu_expandall() { inspector->expand_all_folding(); } +void InspectorDock::_menu_expand_revertable() { + inspector->expand_revertable(); +} + void InspectorDock::_warning_pressed() { warning_dialog->popup_centered(); } @@ -519,6 +526,8 @@ void InspectorDock::update(Object *p_object) { p->clear(); p->add_icon_shortcut(get_theme_icon(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons")), ED_SHORTCUT("property_editor/expand_all", TTR("Expand All")), EXPAND_ALL); p->add_icon_shortcut(get_theme_icon(SNAME("GuiTreeArrowRight"), SNAME("EditorIcons")), ED_SHORTCUT("property_editor/collapse_all", TTR("Collapse All")), COLLAPSE_ALL); + // Calling it 'revertable' internally, because that's what the implementation is based on, but labeling it as 'non-default' because that's more user friendly, even if not 100% accurate. + p->add_shortcut(ED_SHORTCUT("property_editor/expand_revertable", TTR("Expand Non-Default")), EXPAND_REVERTABLE); p->add_separator(TTR("Property Name Style")); p->add_radio_check_item(TTR("Raw"), PROPERTY_NAME_STYLE_RAW); diff --git a/editor/inspector_dock.h b/editor/inspector_dock.h index e3d35c21570..e32410151ff 100644 --- a/editor/inspector_dock.h +++ b/editor/inspector_dock.h @@ -61,6 +61,7 @@ class InspectorDock : public VBoxContainer { COLLAPSE_ALL, EXPAND_ALL, + EXPAND_REVERTABLE, // Matches `EditorPropertyNameProcessor::Style`. PROPERTY_NAME_STYLE_RAW, @@ -124,6 +125,7 @@ class InspectorDock : public VBoxContainer { void _edit_back(); void _menu_collapseall(); void _menu_expandall(); + void _menu_expand_revertable(); void _select_history(int p_idx); void _prepare_history();