diff options
Diffstat (limited to 'source/blender/editors/space_outliner/tree/tree_element_overrides.cc')
-rw-r--r-- | source/blender/editors/space_outliner/tree/tree_element_overrides.cc | 397 |
1 files changed, 374 insertions, 23 deletions
diff --git a/source/blender/editors/space_outliner/tree/tree_element_overrides.cc b/source/blender/editors/space_outliner/tree/tree_element_overrides.cc index 871de39b1dd..11067d37966 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_overrides.cc +++ b/source/blender/editors/space_outliner/tree/tree_element_overrides.cc @@ -7,30 +7,60 @@ #include "BKE_collection.h" #include "BKE_lib_override.h" -#include "BLI_utildefines.h" - +#include "BLI_function_ref.hh" #include "BLI_listbase_wrapper.hh" +#include "BLI_map.hh" +#include "BLI_utildefines.h" #include "BLT_translation.h" #include "DNA_space_types.h" #include "RNA_access.h" +#include "RNA_path.h" #include "../outliner_intern.hh" +#include "tree_element_label.hh" #include "tree_element_overrides.hh" namespace blender::ed::outliner { +class OverrideRNAPathTreeBuilder { + SpaceOutliner &space_outliner_; + Map<std::string, TreeElement *> path_te_map; + + public: + OverrideRNAPathTreeBuilder(SpaceOutliner &space_outliner); + void build_path(TreeElement &parent, TreeElementOverridesData &override_data, short &index); + + private: + TreeElement &ensure_label_element_for_prop( + TreeElement &parent, StringRef elem_path, PointerRNA &ptr, PropertyRNA &prop, short &index); + TreeElement &ensure_label_element_for_ptr(TreeElement &parent, + StringRef elem_path, + PointerRNA &ptr, + short &index); + void ensure_entire_collection(TreeElement &te_to_expand, + const TreeElementOverridesData &override_data, + const char *coll_prop_path, + short &index); +}; + +/* -------------------------------------------------------------------- */ +/** \name Base Element + * + * Represents an ID that has overridden properties. The expanding will invoke building of tree + * elements for the full RNA path of the property. + * + * \{ */ + TreeElementOverridesBase::TreeElementOverridesBase(TreeElement &legacy_te, ID &id) : AbstractTreeElement(legacy_te), id(id) { BLI_assert(legacy_te.store_elem->type == TSE_LIBRARY_OVERRIDE_BASE); if (legacy_te.parent != nullptr && - ELEM(legacy_te.parent->store_elem->type, TSE_SOME_ID, TSE_LAYER_COLLECTION)) - - { + ELEM(legacy_te.parent->store_elem->type, TSE_SOME_ID, TSE_LAYER_COLLECTION)) { legacy_te.name = IFACE_("Library Overrides"); } else { @@ -51,21 +81,17 @@ StringRefNull TreeElementOverridesBase::getWarning() const return {}; } -void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const +static void iterate_properties_to_display(ID &id, + const bool show_system_overrides, + FunctionRef<void(TreeElementOverridesData &data)> fn) { - BLI_assert(id.override_library != nullptr); + PointerRNA override_rna_ptr; + PropertyRNA *override_rna_prop; - const bool show_system_overrides = (SUPPORT_FILTER_OUTLINER(&space_outliner) && - (space_outliner.filter & SO_FILTER_SHOW_SYSTEM_OVERRIDES) != - 0); PointerRNA idpoin; RNA_id_pointer_create(&id, &idpoin); - PointerRNA override_rna_ptr; - PropertyRNA *override_rna_prop; - short index = 0; - - for (auto *override_prop : + for (IDOverrideLibraryProperty *override_prop : ListBaseWrapper<IDOverrideLibraryProperty>(id.override_library->properties)) { int rnaprop_index = 0; const bool is_rna_path_valid = BKE_lib_override_rna_property_find( @@ -80,15 +106,13 @@ void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const /* Matching ID pointers are considered as system overrides. */ if (ELEM(override_prop->rna_prop_type, PROP_POINTER, PROP_COLLECTION) && RNA_struct_is_ID(RNA_property_pointer_type(&override_rna_ptr, override_rna_prop))) { - for (auto *override_prop_op : + for (IDOverrideLibraryPropertyOperation *override_prop_op : ListBaseWrapper<IDOverrideLibraryPropertyOperation>(override_prop->operations)) { if ((override_prop_op->flag & IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) == 0) { do_skip = false; break; } - else { - is_system_override = true; - } + is_system_override = true; } } @@ -105,11 +129,36 @@ void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const TreeElementOverridesData data = { id, *override_prop, override_rna_ptr, *override_rna_prop, is_rna_path_valid}; - outliner_add_element( - &space_outliner, &legacy_te_.subtree, &data, &legacy_te_, TSE_LIBRARY_OVERRIDE, index++); + + fn(data); } } +void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const +{ + BLI_assert(id.override_library != nullptr); + + const bool show_system_overrides = (SUPPORT_FILTER_OUTLINER(&space_outliner) && + (space_outliner.filter & SO_FILTER_SHOW_SYSTEM_OVERRIDES) != + 0); + + OverrideRNAPathTreeBuilder path_builder(space_outliner); + short index = 0; + + iterate_properties_to_display(id, show_system_overrides, [&](TreeElementOverridesData &data) { + path_builder.build_path(legacy_te_, data, index); + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Overridden Property + * + * Represents an RNA property that was overridden. + * + * \{ */ + TreeElementOverridesProperty::TreeElementOverridesProperty(TreeElement &legacy_te, TreeElementOverridesData &override_data) : AbstractTreeElement(legacy_te), @@ -118,9 +167,10 @@ TreeElementOverridesProperty::TreeElementOverridesProperty(TreeElement &legacy_t rna_path(override_data.override_property.rna_path), is_rna_path_valid(override_data.is_rna_path_valid) { - BLI_assert(legacy_te.store_elem->type == TSE_LIBRARY_OVERRIDE); + BLI_assert( + ELEM(legacy_te.store_elem->type, TSE_LIBRARY_OVERRIDE, TSE_LIBRARY_OVERRIDE_OPERATION)); - legacy_te.name = override_data.override_property.rna_path; + legacy_te.name = RNA_property_ui_name(&override_data.override_rna_prop); } StringRefNull TreeElementOverridesProperty::getWarning() const @@ -134,4 +184,305 @@ StringRefNull TreeElementOverridesProperty::getWarning() const return {}; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Overridden Property Operation + * + * See #TreeElementOverridesPropertyOperation. + * \{ */ + +TreeElementOverridesPropertyOperation::TreeElementOverridesPropertyOperation( + TreeElement &legacy_te, TreeElementOverridesData &override_data) + : TreeElementOverridesProperty(legacy_te, override_data) +{ + BLI_assert(legacy_te.store_elem->type == TSE_LIBRARY_OVERRIDE_OPERATION); + BLI_assert_msg(RNA_property_type(&override_rna_prop) == PROP_COLLECTION, + "Override operations are only supported for collections right now"); + /* Quiet Clang Static Analyzer warning by throwing instead of asserting (possible + * null-dereference). */ + if (!override_data.operation) { + throw std::invalid_argument("missing operation"); + } + + operation_ = std::make_unique<IDOverrideLibraryPropertyOperation>(*override_data.operation); + /* Just for extra sanity. */ + operation_->next = operation_->prev = nullptr; + + if (std::optional<PointerRNA> col_item_ptr = get_collection_ptr()) { + const char *dyn_name = RNA_struct_name_get_alloc(&*col_item_ptr, nullptr, 0, nullptr); + if (dyn_name) { + legacy_te.name = dyn_name; + legacy_te.flag |= TE_FREE_NAME; + } + else { + legacy_te.name = RNA_struct_ui_name(col_item_ptr->type); + } + } +} + +StringRefNull TreeElementOverridesPropertyOperation::getOverrideOperationLabel() const +{ + if (ELEM(operation_->operation, + IDOVERRIDE_LIBRARY_OP_INSERT_AFTER, + IDOVERRIDE_LIBRARY_OP_INSERT_BEFORE)) { + return TIP_("Added through override"); + } + + BLI_assert_unreachable(); + return {}; +} + +std::optional<BIFIconID> TreeElementOverridesPropertyOperation::getIcon() const +{ + if (const std::optional<PointerRNA> col_item_ptr = get_collection_ptr()) { + return (BIFIconID)RNA_struct_ui_icon(col_item_ptr->type); + } + + return {}; +} + +std::optional<PointerRNA> TreeElementOverridesPropertyOperation::get_collection_ptr() const +{ + PointerRNA col_item_ptr; + if (RNA_property_collection_lookup_int(const_cast<PointerRNA *>(&override_rna_ptr), + &override_rna_prop, + operation_->subitem_local_index, + &col_item_ptr)) { + return col_item_ptr; + } + + return {}; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Helper to build a hierarchy from an RNA path. + * + * Builds a nice hierarchy representing the nested structs of the override property's RNA path + * using UI names and icons. For example `animation_visualization_mothion_path.frame_end` becomes: + * - Animation Visualization + * - Motion Paths + * - End Frame + * + * Paths are merged so that each RNA sub-path is only represented once in the tree. So there is + * some finicky path building going on to create a path -> tree-element map. + * + * This is more complicated than you'd think it needs to be. Mostly because of RNA collection + * overrides: + * - A single override may add (and in future remove) multiple collection items. So all operations + * of the override have to be considered. + * - The order of collection items may matter (e.g. for modifiers), so if collection items are + * added/removed, we want to show all other collection items too, in the right order. + * + * - If the override is inside some collection item, the collection item has to be built, but the + * RNA path iterator doesn't + * \{ */ + +OverrideRNAPathTreeBuilder::OverrideRNAPathTreeBuilder(SpaceOutliner &space_outliner) + : space_outliner_(space_outliner) +{ +} + +void OverrideRNAPathTreeBuilder::build_path(TreeElement &parent, + TreeElementOverridesData &override_data, + short &index) +{ + PointerRNA idpoin; + RNA_id_pointer_create(&override_data.id, &idpoin); + + ListBase path_elems = {nullptr}; + if (!RNA_path_resolve_elements(&idpoin, override_data.override_property.rna_path, &path_elems)) { + return; + } + + const char *elem_path = nullptr; + TreeElement *te_to_expand = &parent; + + LISTBASE_FOREACH (PropertyElemRNA *, elem, &path_elems) { + if (!elem->next) { + /* The last element is added as #TSE_LIBRARY_OVERRIDE below. */ + break; + } + const char *previous_path = elem_path; + const char *new_path = RNA_path_append(previous_path, &elem->ptr, elem->prop, -1, nullptr); + + te_to_expand = &ensure_label_element_for_prop( + *te_to_expand, new_path, elem->ptr, *elem->prop, index); + + /* Above the collection property was added (e.g. "Modifiers"), to get the actual collection + * item the path refers to, we have to peek at the following path element and add a tree + * element for its pointer (e.g. "My Subdiv Modifier"). */ + if (RNA_property_type(elem->prop) == PROP_COLLECTION) { + const int coll_item_idx = RNA_property_collection_lookup_index( + &elem->ptr, elem->prop, &elem->next->ptr); + const char *coll_item_path = RNA_path_append( + previous_path, &elem->ptr, elem->prop, coll_item_idx, nullptr); + + te_to_expand = &ensure_label_element_for_ptr( + *te_to_expand, coll_item_path, elem->next->ptr, index); + + MEM_delete(new_path); + new_path = coll_item_path; + } + + if (new_path) { + MEM_delete(elem_path); + elem_path = new_path; + } + } + BLI_freelistN(&path_elems); + + /* Special case: Overriding collections, e.g. adding or removing items. In this case we add + * elements for all collection items to show full context, and indicate which ones were + * added/removed (currently added only). Note that a single collection override may add/remove + * multiple items. */ + if (RNA_property_type(&override_data.override_rna_prop) == PROP_COLLECTION) { + /* Tree element for the actual collection item (e.g. "Modifiers"). Can just use the override + * ptr & prop here, since they point to the collection property (e.g. `modifiers`). */ + te_to_expand = &ensure_label_element_for_prop(*te_to_expand, + override_data.override_property.rna_path, + override_data.override_rna_ptr, + override_data.override_rna_prop, + index); + + ensure_entire_collection(*te_to_expand, override_data, elem_path, index); + } + /* Some properties have multiple operations (e.g. an array property with multiple changed + * values), so the element may already be present. At this point they are displayed as a single + * property in the tree, so don't add it multiple times here. */ + else if (!path_te_map.contains(override_data.override_property.rna_path)) { + outliner_add_element(&space_outliner_, + &te_to_expand->subtree, + &override_data, + te_to_expand, + TSE_LIBRARY_OVERRIDE, + index++); + } + + MEM_delete(elem_path); +} + +void OverrideRNAPathTreeBuilder::ensure_entire_collection( + TreeElement &te_to_expand, + const TreeElementOverridesData &override_data, + /* The path of the owning collection property. */ + const char *coll_prop_path, + short &index) +{ + BLI_assert(tree_element_cast<AbstractTreeElement>(&te_to_expand) != nullptr); + + TreeElement *previous_te = nullptr; + int item_idx = 0; + RNA_PROP_BEGIN (&override_data.override_rna_ptr, itemptr, &override_data.override_rna_prop) { + const char *coll_item_path = RNA_path_append(coll_prop_path, + &override_data.override_rna_ptr, + &override_data.override_rna_prop, + item_idx, + nullptr); + IDOverrideLibraryPropertyOperation *item_operation = + BKE_lib_override_library_property_operation_find( + &override_data.override_property, nullptr, nullptr, -1, item_idx, false, nullptr); + TreeElement *current_te = nullptr; + + TreeElement *existing_te = path_te_map.lookup_default(coll_item_path, nullptr); + + if (existing_te) { + /* Reinsert the element to make sure the order is right. It may have been inserted by a + * previous override. */ + BLI_remlink(&te_to_expand.subtree, existing_te); + BLI_insertlinkafter(&te_to_expand.subtree, previous_te, existing_te); + current_te = existing_te; + } + /* Is there an operation for this item (added or removed the item to/from the collection)? If + * so indicate it as override using #TSE_LIBRARY_OVERRIDE_OPERATION. Otherwise it's just a + * regular collection we display for context. */ + else if (item_operation) { + TreeElementOverridesData override_op_data = override_data; + override_op_data.operation = item_operation; + + current_te = outliner_add_element(&space_outliner_, + &te_to_expand.subtree, + /* Element will store a copy. */ + &override_op_data, + &te_to_expand, + TSE_LIBRARY_OVERRIDE_OPERATION, + index++); + } + else { + current_te = &ensure_label_element_for_ptr(te_to_expand, coll_item_path, itemptr, index); + } + + MEM_delete(coll_item_path); + item_idx++; + previous_te = current_te; + } + RNA_PROP_END; +} + +static BIFIconID get_property_icon(PointerRNA &ptr, PropertyRNA &prop) +{ + BIFIconID icon = (BIFIconID)RNA_property_ui_icon(&prop); + if (icon) { + return icon; + } + + /* Try if the collection item type has a dedicated icon (e.g. #ICON_MODIFIER for the + * #Object.modifiers property). */ + if (RNA_property_type(&prop) == PROP_COLLECTION) { + const StructRNA *coll_ptr_type = RNA_property_pointer_type(&ptr, &prop); + icon = (BIFIconID)RNA_struct_ui_icon(coll_ptr_type); + if (icon != ICON_DOT) { + return icon; + } + } + + return ICON_NONE; +} + +TreeElement &OverrideRNAPathTreeBuilder::ensure_label_element_for_prop( + TreeElement &parent, StringRef elem_path, PointerRNA &ptr, PropertyRNA &prop, short &index) +{ + return *path_te_map.lookup_or_add_cb(elem_path, [&]() { + TreeElement *new_te = outliner_add_element(&space_outliner_, + &parent.subtree, + (void *)RNA_property_ui_name(&prop), + &parent, + TSE_GENERIC_LABEL, + index++, + false); + TreeElementLabel *te_label = tree_element_cast<TreeElementLabel>(new_te); + + te_label->setIcon(get_property_icon(ptr, prop)); + return new_te; + }); +} + +TreeElement &OverrideRNAPathTreeBuilder::ensure_label_element_for_ptr(TreeElement &parent, + StringRef elem_path, + PointerRNA &ptr, + short &index) +{ + return *path_te_map.lookup_or_add_cb(elem_path, [&]() { + const char *dyn_name = RNA_struct_name_get_alloc(&ptr, nullptr, 0, nullptr); + + TreeElement *new_te = outliner_add_element( + &space_outliner_, + &parent.subtree, + (void *)(dyn_name ? dyn_name : RNA_struct_ui_name(ptr.type)), + &parent, + TSE_GENERIC_LABEL, + index++); + TreeElementLabel *te_label = tree_element_cast<TreeElementLabel>(new_te); + te_label->setIcon((BIFIconID)RNA_struct_ui_icon(ptr.type)); + + MEM_delete(dyn_name); + + return new_te; + }); +} + +/** \} */ + } // namespace blender::ed::outliner |