diff options
Diffstat (limited to 'source/blender/editors/space_outliner')
25 files changed, 729 insertions, 792 deletions
diff --git a/source/blender/editors/space_outliner/CMakeLists.txt b/source/blender/editors/space_outliner/CMakeLists.txt index 59f6bd85d59..97d2957eed2 100644 --- a/source/blender/editors/space_outliner/CMakeLists.txt +++ b/source/blender/editors/space_outliner/CMakeLists.txt @@ -27,6 +27,7 @@ set(SRC outliner_draw.cc outliner_edit.cc outliner_ops.cc + outliner_query.cc outliner_select.cc outliner_sync.cc outliner_tools.cc @@ -57,6 +58,7 @@ set(SRC tree/tree_element_scene_objects.cc tree/tree_element_seq.cc tree/tree_element_view_layer.cc + tree/tree_iterator.cc outliner_intern.hh tree/common.hh @@ -75,6 +77,7 @@ set(SRC tree/tree_element_scene_objects.hh tree/tree_element_seq.hh tree/tree_element_view_layer.hh + tree/tree_iterator.hh ) set(LIB diff --git a/source/blender/editors/space_outliner/outliner_context.cc b/source/blender/editors/space_outliner/outliner_context.cc index d07b6641836..1a804cb58b8 100644 --- a/source/blender/editors/space_outliner/outliner_context.cc +++ b/source/blender/editors/space_outliner/outliner_context.cc @@ -12,23 +12,25 @@ #include "DNA_space_types.h" #include "outliner_intern.hh" +#include "tree/tree_iterator.hh" -static void outliner_context_selected_ids_recursive(const ListBase *subtree, +using namespace blender::ed::outliner; + +static void outliner_context_selected_ids_recursive(const SpaceOutliner &space_outliner, bContextDataResult *result) { - LISTBASE_FOREACH (const TreeElement *, te, subtree) { + tree_iterator::all(space_outliner, [&](const TreeElement *te) { const TreeStoreElem *tse = TREESTORE(te); if ((tse->flag & TSE_SELECTED) && (ELEM(tse->type, TSE_SOME_ID, TSE_LAYER_COLLECTION))) { CTX_data_id_list_add(result, tse->id); } - outliner_context_selected_ids_recursive(&te->subtree, result); - } + }); } static void outliner_context_selected_ids(const SpaceOutliner *space_outliner, bContextDataResult *result) { - outliner_context_selected_ids_recursive(&space_outliner->tree, result); + outliner_context_selected_ids_recursive(*space_outliner, result); CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); } diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.cc b/source/blender/editors/space_outliner/outliner_dragdrop.cc index a22ce9d3d24..e20958c1b1e 100644 --- a/source/blender/editors/space_outliner/outliner_dragdrop.cc +++ b/source/blender/editors/space_outliner/outliner_dragdrop.cc @@ -316,7 +316,7 @@ static bool parent_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) { SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); - bool changed = outliner_flag_set(&space_outliner->tree, TSE_DRAG_ANY, false); + bool changed = outliner_flag_set(*space_outliner, TSE_DRAG_ANY, false); if (changed) { ED_region_tag_redraw_no_rebuild(CTX_wm_region(C)); } @@ -847,8 +847,7 @@ static bool datastack_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); ARegion *region = CTX_wm_region(C); - bool changed = outliner_flag_set( - &space_outliner->tree, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false); + bool changed = outliner_flag_set(*space_outliner, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false); StackDropData *drop_data = reinterpret_cast<StackDropData *>(drag->poin); if (!drop_data) { @@ -1195,8 +1194,7 @@ static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event { SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); ARegion *region = CTX_wm_region(C); - bool changed = outliner_flag_set( - &space_outliner->tree, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false); + bool changed = outliner_flag_set(*space_outliner, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false); CollectionDrop data; if (((event->modifier & KM_SHIFT) == 0) && @@ -1461,7 +1459,7 @@ static int outliner_item_drag_drop_invoke(bContext *C, /* Only drag element under mouse if it was not selected before. */ if ((tselem->flag & TSE_SELECTED) == 0) { - outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 0); + outliner_flag_set(*space_outliner, TSE_SELECTED, 0); tselem->flag |= TSE_SELECTED; } diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc index d165e98d7d4..753de83a10d 100644 --- a/source/blender/editors/space_outliner/outliner_draw.cc +++ b/source/blender/editors/space_outliner/outliner_draw.cc @@ -71,7 +71,9 @@ #include "tree/tree_element_id.hh" #include "tree/tree_element_overrides.hh" #include "tree/tree_element_rna.hh" +#include "tree/tree_iterator.hh" +using namespace blender; using namespace blender::ed::outliner; /* -------------------------------------------------------------------- */ @@ -1714,72 +1716,70 @@ static void outliner_draw_restrictbuts(uiBlock *block, } static void outliner_draw_userbuts(uiBlock *block, - ARegion *region, - SpaceOutliner *space_outliner, - ListBase *lb) + const ARegion *region, + const SpaceOutliner *space_outliner) { + tree_iterator::all_open(*space_outliner, [&](const TreeElement *te) { + if (!outliner_is_element_in_view(te, ®ion->v2d)) { + return; + } - LISTBASE_FOREACH (TreeElement *, te, lb) { - TreeStoreElem *tselem = TREESTORE(te); - if (outliner_is_element_in_view(te, ®ion->v2d)) { - if (tselem->type == TSE_SOME_ID) { - uiBut *bt; - ID *id = tselem->id; - const char *tip = nullptr; - char buf[16] = ""; - int but_flag = UI_BUT_DRAG_LOCK; + const TreeStoreElem *tselem = TREESTORE(te); + if (tselem->type != TSE_SOME_ID) { + return; + } - if (ID_IS_LINKED(id)) { - but_flag |= UI_BUT_DISABLED; - } + uiBut *bt; + ID *id = tselem->id; + const char *tip = nullptr; + char buf[16] = ""; + int but_flag = UI_BUT_DRAG_LOCK; - BLI_str_format_int_grouped(buf, id->us); - bt = uiDefBut(block, - UI_BTYPE_BUT, - 1, - buf, - (int)(region->v2d.cur.xmax - OL_TOG_USER_BUTS_USERS), - te->ys, - UI_UNIT_X, - UI_UNIT_Y, - nullptr, - 0.0, - 0.0, - 0, - 0, - TIP_("Number of users of this data-block")); - UI_but_flag_enable(bt, but_flag); - - if (id->flag & LIB_FAKEUSER) { - tip = TIP_("Data-block will be retained using a fake user"); - } - else { - tip = TIP_("Data-block has no users and will be deleted"); - } - bt = uiDefIconButBitS(block, - UI_BTYPE_ICON_TOGGLE, - LIB_FAKEUSER, - 1, - ICON_FAKE_USER_OFF, - (int)(region->v2d.cur.xmax - OL_TOG_USER_BUTS_STATUS), - te->ys, - UI_UNIT_X, - UI_UNIT_Y, - &id->flag, - 0, - 0, - 0, - 0, - tip); - UI_but_func_set(bt, restrictbutton_id_user_toggle, id, nullptr); - UI_but_flag_enable(bt, but_flag); - } + if (ID_IS_LINKED(id)) { + but_flag |= UI_BUT_DISABLED; } - if (TSELEM_OPEN(tselem, space_outliner)) { - outliner_draw_userbuts(block, region, space_outliner, &te->subtree); + BLI_str_format_int_grouped(buf, id->us); + bt = uiDefBut(block, + UI_BTYPE_BUT, + 1, + buf, + (int)(region->v2d.cur.xmax - OL_TOG_USER_BUTS_USERS), + te->ys, + UI_UNIT_X, + UI_UNIT_Y, + nullptr, + 0.0, + 0.0, + 0, + 0, + TIP_("Number of users of this data-block")); + UI_but_flag_enable(bt, but_flag); + + if (id->flag & LIB_FAKEUSER) { + tip = TIP_("Data-block will be retained using a fake user"); } - } + else { + tip = TIP_("Data-block has no users and will be deleted"); + } + bt = uiDefIconButBitS(block, + UI_BTYPE_ICON_TOGGLE, + LIB_FAKEUSER, + 1, + ICON_FAKE_USER_OFF, + (int)(region->v2d.cur.xmax - OL_TOG_USER_BUTS_STATUS), + te->ys, + UI_UNIT_X, + UI_UNIT_Y, + &id->flag, + 0, + 0, + 0, + 0, + tip); + UI_but_func_set(bt, restrictbutton_id_user_toggle, id, nullptr); + UI_but_flag_enable(bt, but_flag); + }); } static void outliner_draw_overrides_rna_buts(uiBlock *block, @@ -1920,91 +1920,6 @@ static void outliner_draw_overrides_restrictbuts(Main *bmain, } } -static bool outliner_draw_overrides_warning_buts(uiBlock *block, - ARegion *region, - SpaceOutliner *space_outliner, - ListBase *lb, - const bool is_open) -{ - bool any_item_has_warnings = false; - - LISTBASE_FOREACH (TreeElement *, te, lb) { - bool item_has_warnings = false; - const bool do_draw = outliner_is_element_in_view(te, ®ion->v2d); - int but_flag = UI_BUT_DRAG_LOCK; - const char *tip = nullptr; - - TreeStoreElem *tselem = TREESTORE(te); - switch (tselem->type) { - case TSE_LIBRARY_OVERRIDE_BASE: { - ID *id = tselem->id; - - if (id->flag & LIB_LIB_OVERRIDE_RESYNC_LEFTOVER) { - item_has_warnings = true; - if (do_draw) { - tip = TIP_( - "This override data-block is not needed anymore, but was detected as user-edited"); - } - } - else if (ID_IS_OVERRIDE_LIBRARY_REAL(id) && ID_REAL_USERS(id) == 0) { - item_has_warnings = true; - if (do_draw) { - tip = TIP_("This override data-block is unused"); - } - } - break; - } - case TSE_LIBRARY_OVERRIDE: { - TreeElementOverridesProperty &te_override_prop = - *tree_element_cast<TreeElementOverridesProperty>(te); - if (!te_override_prop.is_rna_path_valid) { - item_has_warnings = true; - if (do_draw) { - tip = TIP_( - "This override property does not exist in current data, it will be removed on " - "next .blend file save"); - } - } - break; - } - default: - break; - } - - const bool any_child_has_warnings = outliner_draw_overrides_warning_buts( - block, - region, - space_outliner, - &te->subtree, - is_open && TSELEM_OPEN(tselem, space_outliner)); - - if (do_draw && - (item_has_warnings || (any_child_has_warnings && !TSELEM_OPEN(tselem, space_outliner)))) { - if (tip == nullptr) { - tip = TIP_("Some sub-items require attention"); - } - uiBut *bt = uiDefIconBut(block, - UI_BTYPE_BUT, - 1, - ICON_ERROR, - (int)(region->v2d.cur.xmax - OL_TOG_USER_BUTS_STATUS), - te->ys, - UI_UNIT_X, - UI_UNIT_Y, - nullptr, - 0.0, - 0.0, - 0.0, - 0.0, - tip); - UI_but_flag_enable(bt, but_flag); - } - any_item_has_warnings = any_item_has_warnings || item_has_warnings || any_child_has_warnings; - } - - return any_item_has_warnings; -} - static void outliner_draw_separator(ARegion *region, const int x) { View2D *v2d = ®ion->v2d; @@ -2025,81 +1940,82 @@ static void outliner_draw_separator(ARegion *region, const int x) immUnbindProgram(); } -static void outliner_draw_rnabuts( - uiBlock *block, ARegion *region, SpaceOutliner *space_outliner, int sizex, ListBase *lb) +static void outliner_draw_rnabuts(uiBlock *block, + ARegion *region, + SpaceOutliner *space_outliner, + int sizex) { PointerRNA ptr; PropertyRNA *prop; - LISTBASE_FOREACH (TreeElement *, te, lb) { + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { TreeStoreElem *tselem = TREESTORE(te); - if (outliner_is_element_in_view(te, ®ion->v2d)) { - if (TreeElementRNAProperty *te_rna_prop = tree_element_cast<TreeElementRNAProperty>(te)) { - ptr = te_rna_prop->getPointerRNA(); - prop = te_rna_prop->getPropertyRNA(); - - if (!TSELEM_OPEN(tselem, space_outliner)) { - if (RNA_property_type(prop) == PROP_POINTER) { - uiBut *but = uiDefAutoButR(block, - &ptr, - prop, - -1, - "", - ICON_NONE, - sizex, - te->ys, - OL_RNA_COL_SIZEX, - UI_UNIT_Y - 1); - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - else if (RNA_property_type(prop) == PROP_ENUM) { - uiDefAutoButR(block, - &ptr, - prop, - -1, - nullptr, - ICON_NONE, - sizex, - te->ys, - OL_RNA_COL_SIZEX, - UI_UNIT_Y - 1); - } - else { - uiDefAutoButR(block, - &ptr, - prop, - -1, - "", - ICON_NONE, - sizex, - te->ys, - OL_RNA_COL_SIZEX, - UI_UNIT_Y - 1); - } - } - } - else if (TreeElementRNAArrayElement *te_rna_array_elem = - tree_element_cast<TreeElementRNAArrayElement>(te)) { - ptr = te_rna_array_elem->getPointerRNA(); - prop = te_rna_array_elem->getPropertyRNA(); - - uiDefAutoButR(block, - &ptr, - prop, - te->index, - "", - ICON_NONE, - sizex, - te->ys, - OL_RNA_COL_SIZEX, - UI_UNIT_Y - 1); - } - } - if (TSELEM_OPEN(tselem, space_outliner)) { - outliner_draw_rnabuts(block, region, space_outliner, sizex, &te->subtree); - } - } + if (!outliner_is_element_in_view(te, ®ion->v2d)) { + return; + } + + if (TreeElementRNAProperty *te_rna_prop = tree_element_cast<TreeElementRNAProperty>(te)) { + ptr = te_rna_prop->getPointerRNA(); + prop = te_rna_prop->getPropertyRNA(); + + if (!TSELEM_OPEN(tselem, space_outliner)) { + if (RNA_property_type(prop) == PROP_POINTER) { + uiBut *but = uiDefAutoButR(block, + &ptr, + prop, + -1, + "", + ICON_NONE, + sizex, + te->ys, + OL_RNA_COL_SIZEX, + UI_UNIT_Y - 1); + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + else if (RNA_property_type(prop) == PROP_ENUM) { + uiDefAutoButR(block, + &ptr, + prop, + -1, + nullptr, + ICON_NONE, + sizex, + te->ys, + OL_RNA_COL_SIZEX, + UI_UNIT_Y - 1); + } + else { + uiDefAutoButR(block, + &ptr, + prop, + -1, + "", + ICON_NONE, + sizex, + te->ys, + OL_RNA_COL_SIZEX, + UI_UNIT_Y - 1); + } + } + } + else if (TreeElementRNAArrayElement *te_rna_array_elem = + tree_element_cast<TreeElementRNAArrayElement>(te)) { + ptr = te_rna_array_elem->getPointerRNA(); + prop = te_rna_array_elem->getPropertyRNA(); + + uiDefAutoButR(block, + &ptr, + prop, + te->index, + "", + ICON_NONE, + sizex, + te->ys, + OL_RNA_COL_SIZEX, + UI_UNIT_Y - 1); + } + }); } static void outliner_buttons(const bContext *C, @@ -2185,9 +2101,9 @@ static void outliner_mode_toggle_fn(bContext *C, void *tselem_poin, void *UNUSED static void outliner_draw_mode_column_toggle(uiBlock *block, TreeViewContext *tvc, TreeElement *te, - TreeStoreElem *tselem, const bool lock_object_modes) { + TreeStoreElem *tselem = TREESTORE(te); if ((tselem->type != TSE_SOME_ID) || (te->idcode != ID_OB)) { return; } @@ -2258,59 +2174,63 @@ static void outliner_draw_mode_column_toggle(uiBlock *block, } } -static void outliner_draw_mode_column(const bContext *C, - uiBlock *block, +static void outliner_draw_mode_column(uiBlock *block, TreeViewContext *tvc, - SpaceOutliner *space_outliner, - ListBase *tree) + SpaceOutliner *space_outliner) { - TreeStoreElem *tselem; const bool lock_object_modes = tvc->scene->toolsettings->object_flag & SCE_OBJECT_MODE_LOCK; - LISTBASE_FOREACH (TreeElement *, te, tree) { - tselem = TREESTORE(te); - + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { if (tvc->obact && tvc->obact->mode != OB_MODE_OBJECT) { - outliner_draw_mode_column_toggle(block, tvc, te, tselem, lock_object_modes); + outliner_draw_mode_column_toggle(block, tvc, te, lock_object_modes); } + }); +} - if (TSELEM_OPEN(tselem, space_outliner)) { - outliner_draw_mode_column(C, block, tvc, space_outliner, &te->subtree); +static StringRefNull outliner_draw_get_warning_tree_element_subtree(const TreeElement *parent_te) +{ + LISTBASE_FOREACH (const TreeElement *, sub_te, &parent_te->subtree) { + const AbstractTreeElement *abstract_te = tree_element_cast<AbstractTreeElement>(sub_te); + StringRefNull warning_msg = abstract_te ? abstract_te->getWarning() : ""; + + if (!warning_msg.is_empty()) { + return warning_msg; + } + + warning_msg = outliner_draw_get_warning_tree_element_subtree(sub_te); + if (!warning_msg.is_empty()) { + return warning_msg; } } + + return ""; } -/* Returns `true` if some warning was drawn for that element or one of its sub-elements (if it is - * not open). */ -static bool outliner_draw_warning_tree_element(uiBlock *block, - SpaceOutliner *space_outliner, - TreeElement *te, - TreeStoreElem *tselem, - const bool use_mode_column, - const int te_ys) +static StringRefNull outliner_draw_get_warning_tree_element(const SpaceOutliner &space_outliner, + const TreeElement *te) { - if ((te->flag & TE_HAS_WARNING) == 0) { - /* If given element has no warning, recursively try to display the first sub-elements' warning. - */ - if (!TSELEM_OPEN(tselem, space_outliner)) { - LISTBASE_FOREACH (TreeElement *, sub_te, &te->subtree) { - TreeStoreElem *sub_tselem = TREESTORE(sub_te); + const AbstractTreeElement *abstract_te = tree_element_cast<AbstractTreeElement>(te); + const StringRefNull warning_msg = abstract_te ? abstract_te->getWarning() : ""; - if (outliner_draw_warning_tree_element( - block, space_outliner, sub_te, sub_tselem, use_mode_column, te_ys)) { - return true; - } - } - } - return false; + if (!warning_msg.is_empty()) { + return warning_msg; + } + + /* If given element has no warning, recursively try to display the first sub-element's warning. + */ + if (!TSELEM_OPEN(te->store_elem, &space_outliner)) { + return outliner_draw_get_warning_tree_element_subtree(te); } - int icon = ICON_NONE; - const char *tip = ""; - const bool has_warning = tree_element_warnings_get(te, &icon, &tip); - BLI_assert(has_warning); - UNUSED_VARS_NDEBUG(has_warning); + return ""; +} +static void outliner_draw_warning_tree_element(uiBlock *block, + const SpaceOutliner *space_outliner, + StringRefNull warning_msg, + const bool use_mode_column, + const int te_ys) +{ /* Move the warnings a unit left in view layer mode. */ const short mode_column_offset = (use_mode_column && (space_outliner->outlinevis == SO_SCENES)) ? UI_UNIT_X : @@ -2320,7 +2240,7 @@ static bool outliner_draw_warning_tree_element(uiBlock *block, uiBut *but = uiDefIconBut(block, UI_BTYPE_ICON_TOGGLE, 0, - icon, + ICON_ERROR, mode_column_offset, te_ys, UI_UNIT_X, @@ -2330,28 +2250,25 @@ static bool outliner_draw_warning_tree_element(uiBlock *block, 0.0, 0.0, 0.0, - tip); + warning_msg.c_str()); /* No need for undo here, this is a pure info widget. */ UI_but_flag_disable(but, UI_BUT_UNDO); - - return true; } -static void outliner_draw_warning_column(const bContext *C, - uiBlock *block, - SpaceOutliner *space_outliner, - const bool use_mode_column, - ListBase *tree) +static void outliner_draw_warning_column(uiBlock *block, + const SpaceOutliner *space_outliner, + const bool use_mode_column) { - LISTBASE_FOREACH (TreeElement *, te, tree) { - TreeStoreElem *tselem = TREESTORE(te); + tree_iterator::all_open(*space_outliner, [&](const TreeElement *te) { + /* Get warning for this element, or if there is none and the element is collapsed, the first + * warning in the collapsed sub-tree. */ + StringRefNull warning_msg = outliner_draw_get_warning_tree_element(*space_outliner, te); - outliner_draw_warning_tree_element(block, space_outliner, te, tselem, use_mode_column, te->ys); - - if (TSELEM_OPEN(tselem, space_outliner)) { - outliner_draw_warning_column(C, block, space_outliner, use_mode_column, &te->subtree); + if (!warning_msg.is_empty()) { + outliner_draw_warning_tree_element( + block, space_outliner, warning_msg, use_mode_column, te->ys); } - } + }); } /** \} */ @@ -3232,18 +3149,16 @@ static void outliner_draw_iconrow(bContext *C, } /* closed tree element */ -static void outliner_set_coord_tree_element(TreeElement *te, int startx, int starty) +static void outliner_set_subtree_coords(const TreeElement *te) { - /* closed items may be displayed in row of parent, don't change their coordinate! */ - if ((te->flag & TE_ICONROW) == 0 && (te->flag & TE_ICONROW_MERGED) == 0) { - te->xs = 0; - te->ys = 0; - te->xend = 0; - } - - LISTBASE_FOREACH (TreeElement *, ten, &te->subtree) { - outliner_set_coord_tree_element(ten, startx + UI_UNIT_X, starty); - } + tree_iterator::all(te->subtree, [&](TreeElement *te) { + /* closed items may be displayed in row of parent, don't change their coordinate! */ + if ((te->flag & TE_ICONROW) == 0 && (te->flag & TE_ICONROW_MERGED) == 0) { + te->xs = 0; + te->ys = 0; + te->xend = 0; + } + }); } static bool element_should_draw_faded(const TreeViewContext *tvc, @@ -3495,10 +3410,7 @@ static void outliner_draw_tree_element(bContext *C, } } else { - LISTBASE_FOREACH (TreeElement *, ten, &te->subtree) { - outliner_set_coord_tree_element(ten, startx, *starty); - } - + outliner_set_subtree_coords(te); *starty -= UI_UNIT_Y; } } @@ -3654,22 +3566,21 @@ static void outliner_draw_struct_marks(ARegion *region, } } -static void outliner_draw_highlights_recursive(uint pos, - const ARegion *region, - const SpaceOutliner *space_outliner, - const ListBase *lb, - const float col_selection[4], - const float col_active[4], - const float col_highlight[4], - const float col_searchmatch[4], - int start_x, - int *io_start_y) +static void outliner_draw_highlights(uint pos, + const ARegion *region, + const SpaceOutliner *space_outliner, + const float col_selection[4], + const float col_active[4], + const float col_highlight[4], + const float col_searchmatch[4], + int start_x, + int *io_start_y) { const bool is_searching = (SEARCHING_OUTLINER(space_outliner) || (space_outliner->outlinevis == SO_DATA_API && space_outliner->search_string[0] != 0)); - LISTBASE_FOREACH (TreeElement *, te, lb) { + tree_iterator::all_open(*space_outliner, [&](const TreeElement *te) { const TreeStoreElem *tselem = TREESTORE(te); const int start_y = *io_start_y; @@ -3725,19 +3636,7 @@ static void outliner_draw_highlights_recursive(uint pos, } *io_start_y -= UI_UNIT_Y; - if (TSELEM_OPEN(tselem, space_outliner)) { - outliner_draw_highlights_recursive(pos, - region, - space_outliner, - &te->subtree, - col_selection, - col_active, - col_highlight, - col_searchmatch, - start_x + UI_UNIT_X, - io_start_y); - } - } + }); } static void outliner_draw_highlights(ARegion *region, @@ -3759,16 +3658,15 @@ static void outliner_draw_highlights(ARegion *region, GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - outliner_draw_highlights_recursive(pos, - region, - space_outliner, - &space_outliner->tree, - col_selection, - col_active, - col_highlight, - col_searchmatch, - startx, - starty); + outliner_draw_highlights(pos, + region, + space_outliner, + col_selection, + col_active, + col_highlight, + col_searchmatch, + startx, + starty); immUnbindProgram(); GPU_blend(GPU_BLEND_NONE); } @@ -3961,13 +3859,8 @@ void draw_outliner(const bContext *C) UI_view2d_view_ortho(v2d); /* Only show mode column in View Layers and Scenes view. */ - const bool use_mode_column = (space_outliner->flag & SO_MODE_COLUMN) && - (ELEM(space_outliner->outlinevis, SO_VIEW_LAYER, SO_SCENES)); - - const bool use_warning_column = ELEM(space_outliner->outlinevis, - SO_LIBRARIES, - SO_OVERRIDES_LIBRARY) && - space_outliner->runtime->tree_display->hasWarnings(); + const bool use_mode_column = outliner_shows_mode_column(*space_outliner); + const bool use_warning_column = outliner_has_element_warnings(*space_outliner); /* Draw outliner stuff (background, hierarchy lines and names). */ const float right_column_width = outliner_right_columns_width(space_outliner); @@ -3997,18 +3890,14 @@ void draw_outliner(const bContext *C) outliner_draw_separator(region, buttons_start_x + OL_RNA_COL_SIZEX); UI_block_emboss_set(block, UI_EMBOSS); - outliner_draw_rnabuts(block, region, space_outliner, buttons_start_x, &space_outliner->tree); + outliner_draw_rnabuts(block, region, space_outliner, buttons_start_x); UI_block_emboss_set(block, UI_EMBOSS_NONE_OR_STATUS); } else if (space_outliner->outlinevis == SO_ID_ORPHANS) { /* draw user toggle columns */ - outliner_draw_userbuts(block, region, space_outliner, &space_outliner->tree); + outliner_draw_userbuts(block, region, space_outliner); } else if (space_outliner->outlinevis == SO_OVERRIDES_LIBRARY) { - /* Draw overrides status columns. */ - outliner_draw_overrides_warning_buts( - block, region, space_outliner, &space_outliner->tree, true); - const int x = region->v2d.cur.xmax - right_column_width; outliner_draw_separator(region, x); if (space_outliner->lib_override_view_mode == SO_LIB_OVERRIDE_VIEW_PROPERTIES) { @@ -4037,12 +3926,12 @@ void draw_outliner(const bContext *C) /* Draw mode icons */ if (use_mode_column) { - outliner_draw_mode_column(C, block, &tvc, space_outliner, &space_outliner->tree); + outliner_draw_mode_column(block, &tvc, space_outliner); } /* Draw warning icons */ if (use_warning_column) { - outliner_draw_warning_column(C, block, space_outliner, use_mode_column, &space_outliner->tree); + outliner_draw_warning_column(block, space_outliner, use_mode_column); } UI_block_emboss_set(block, UI_EMBOSS); diff --git a/source/blender/editors/space_outliner/outliner_edit.cc b/source/blender/editors/space_outliner/outliner_edit.cc index 1de45b0ec96..c4a9398a5f7 100644 --- a/source/blender/editors/space_outliner/outliner_edit.cc +++ b/source/blender/editors/space_outliner/outliner_edit.cc @@ -60,6 +60,7 @@ #include "outliner_intern.hh" #include "tree/tree_element_rna.hh" +#include "tree/tree_iterator.hh" using namespace blender::ed::outliner; @@ -107,7 +108,7 @@ static int outliner_highlight_update(bContext *C, wmOperator *UNUSED(op), const if (!hovered_te || !is_over_icon || !(hovered_te->store_elem->flag & TSE_HIGHLIGHTED) || !(icon_te->store_elem->flag & TSE_HIGHLIGHTED_ICON)) { /* Clear highlights when nothing is hovered or when a new item is hovered. */ - changed = outliner_flag_set(&space_outliner->tree, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false); + changed = outliner_flag_set(*space_outliner, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false); if (hovered_te) { hovered_te->store_elem->flag |= TSE_HIGHLIGHTED; changed = true; @@ -167,7 +168,7 @@ void outliner_item_openclose(SpaceOutliner *space_outliner, } if (toggle_all) { - outliner_flag_set(&te->subtree, TSE_CLOSED, !open); + outliner_flag_set(te->subtree, TSE_CLOSED, !open); } } @@ -1077,11 +1078,16 @@ int outliner_flag_is_any_test(ListBase *lb, short flag, const int curlevel) return 0; } -bool outliner_flag_set(ListBase *lb, short flag, short set) +bool outliner_flag_set(const SpaceOutliner &space_outliner, const short flag, const short set) +{ + return outliner_flag_set(space_outliner.tree, flag, set); +} + +bool outliner_flag_set(const ListBase &lb, const short flag, const short set) { bool changed = false; - LISTBASE_FOREACH (TreeElement *, te, lb) { + tree_iterator::all(lb, [&](TreeElement *te) { TreeStoreElem *tselem = TREESTORE(te); bool has_flag = (tselem->flag & flag); if (set == 0) { @@ -1094,21 +1100,24 @@ bool outliner_flag_set(ListBase *lb, short flag, short set) tselem->flag |= flag; changed = true; } - changed |= outliner_flag_set(&te->subtree, flag, set); - } + }); return changed; } -bool outliner_flag_flip(ListBase *lb, short flag) +bool outliner_flag_flip(const SpaceOutliner &space_outliner, const short flag) +{ + return outliner_flag_flip(space_outliner.tree, flag); +} + +bool outliner_flag_flip(const ListBase &lb, const short flag) { bool changed = false; - LISTBASE_FOREACH (TreeElement *, te, lb) { + tree_iterator::all(lb, [&](TreeElement *te) { TreeStoreElem *tselem = TREESTORE(te); tselem->flag ^= flag; - changed |= outliner_flag_flip(&te->subtree, flag); - } + }); return changed; } @@ -1125,10 +1134,10 @@ static int outliner_toggle_expanded_exec(bContext *C, wmOperator *UNUSED(op)) ARegion *region = CTX_wm_region(C); if (outliner_flag_is_any_test(&space_outliner->tree, TSE_CLOSED, 1)) { - outliner_flag_set(&space_outliner->tree, TSE_CLOSED, 0); + outliner_flag_set(*space_outliner, TSE_CLOSED, 0); } else { - outliner_flag_set(&space_outliner->tree, TSE_CLOSED, 1); + outliner_flag_set(*space_outliner, TSE_CLOSED, 1); } ED_region_tag_redraw(region); @@ -1169,13 +1178,13 @@ static int outliner_select_all_exec(bContext *C, wmOperator *op) switch (action) { case SEL_SELECT: - outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 1); + outliner_flag_set(*space_outliner, TSE_SELECTED, 1); break; case SEL_DESELECT: - outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 0); + outliner_flag_set(*space_outliner, TSE_SELECTED, 0); break; case SEL_INVERT: - outliner_flag_flip(&space_outliner->tree, TSE_SELECTED); + outliner_flag_flip(*space_outliner, TSE_SELECTED); break; } @@ -1211,32 +1220,16 @@ void OUTLINER_OT_select_all(wmOperatorType *ot) /** \name View Show Active (Outliner) Operator * \{ */ -static void outliner_set_coordinates_element_recursive(SpaceOutliner *space_outliner, - TreeElement *te, - int startx, - int *starty) -{ - TreeStoreElem *tselem = TREESTORE(te); - - /* store coord and continue, we need coordinates for elements outside view too */ - te->xs = (float)startx; - te->ys = (float)(*starty); - *starty -= UI_UNIT_Y; - - if (TSELEM_OPEN(tselem, space_outliner)) { - LISTBASE_FOREACH (TreeElement *, ten, &te->subtree) { - outliner_set_coordinates_element_recursive(space_outliner, ten, startx + UI_UNIT_X, starty); - } - } -} - -void outliner_set_coordinates(ARegion *region, SpaceOutliner *space_outliner) +void outliner_set_coordinates(const ARegion *region, const SpaceOutliner *space_outliner) { int starty = (int)(region->v2d.tot.ymax) - UI_UNIT_Y; - LISTBASE_FOREACH (TreeElement *, te, &space_outliner->tree) { - outliner_set_coordinates_element_recursive(space_outliner, te, 0, &starty); - } + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { + /* store coord and continue, we need coordinates for elements outside view too */ + te->xs = 0; + te->ys = (float)starty; + starty -= UI_UNIT_Y; + }); } /* return 1 when levels were opened */ @@ -1624,11 +1617,11 @@ static int subtree_has_objects(ListBase *lb) return 0; } -/* recursive helper function for Show Hierarchy operator */ -static void tree_element_show_hierarchy(Scene *scene, SpaceOutliner *space_outliner, ListBase *lb) +/* Helper function for Show Hierarchy operator */ +static void tree_element_show_hierarchy(Scene *scene, SpaceOutliner *space_outliner) { /* open all object elems, close others */ - LISTBASE_FOREACH (TreeElement *, te, lb) { + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { TreeStoreElem *tselem = TREESTORE(te); if (ELEM(tselem->type, @@ -1656,11 +1649,7 @@ static void tree_element_show_hierarchy(Scene *scene, SpaceOutliner *space_outli else { tselem->flag |= TSE_CLOSED; } - - if (TSELEM_OPEN(tselem, space_outliner)) { - tree_element_show_hierarchy(scene, space_outliner, &te->subtree); - } - } + }); } /* show entire object level hierarchy */ @@ -1671,7 +1660,7 @@ static int outliner_show_hierarchy_exec(bContext *C, wmOperator *UNUSED(op)) Scene *scene = CTX_data_scene(C); /* recursively open/close levels */ - tree_element_show_hierarchy(scene, space_outliner, &space_outliner->tree); + tree_element_show_hierarchy(scene, space_outliner); ED_region_tag_redraw(region); @@ -1873,79 +1862,75 @@ enum { DRIVERS_EDITMODE_REMOVE, } /*eDrivers_EditModes*/; -/* Recursively iterate over tree, finding and working on selected items */ +/* Iterate over tree, finding and working on selected items */ static void do_outliner_drivers_editop(SpaceOutliner *space_outliner, - ListBase *tree, ReportList *reports, short mode) { - LISTBASE_FOREACH (TreeElement *, te, tree) { + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { TreeStoreElem *tselem = TREESTORE(te); /* if item is selected, perform operation */ - if (tselem->flag & TSE_SELECTED) { - ID *id = nullptr; - char *path = nullptr; - int array_index = 0; - short flag = 0; - short groupmode = KSP_GROUP_KSNAME; - - TreeElementRNACommon *te_rna = tree_element_cast<TreeElementRNACommon>(te); - PointerRNA ptr = te_rna ? te_rna->getPointerRNA() : PointerRNA_NULL; - PropertyRNA *prop = te_rna ? te_rna->getPropertyRNA() : nullptr; - - /* check if RNA-property described by this selected element is an animatable prop */ - if (prop && RNA_property_animateable(&ptr, prop)) { - /* get id + path + index info from the selected element */ - tree_element_to_path(te, tselem, &id, &path, &array_index, &flag, &groupmode); - } + if (!(tselem->flag & TSE_SELECTED)) { + return; + } - /* only if ID and path were set, should we perform any actions */ - if (id && path) { - short dflags = CREATEDRIVER_WITH_DEFAULT_DVAR; - int arraylen = 1; + ID *id = nullptr; + char *path = nullptr; + int array_index = 0; + short flag = 0; + short groupmode = KSP_GROUP_KSNAME; - /* array checks */ - if (flag & KSP_FLAG_WHOLE_ARRAY) { - /* entire array was selected, so add drivers for all */ - arraylen = RNA_property_array_length(&ptr, prop); - } - else { - arraylen = array_index; - } + TreeElementRNACommon *te_rna = tree_element_cast<TreeElementRNACommon>(te); + PointerRNA ptr = te_rna ? te_rna->getPointerRNA() : PointerRNA_NULL; + PropertyRNA *prop = te_rna ? te_rna->getPropertyRNA() : nullptr; - /* we should do at least one step */ - if (arraylen == array_index) { - arraylen++; - } + /* check if RNA-property described by this selected element is an animatable prop */ + if (prop && RNA_property_animateable(&ptr, prop)) { + /* get id + path + index info from the selected element */ + tree_element_to_path(te, tselem, &id, &path, &array_index, &flag, &groupmode); + } - /* for each array element we should affect, add driver */ - for (; array_index < arraylen; array_index++) { - /* action depends on mode */ - switch (mode) { - case DRIVERS_EDITMODE_ADD: { - /* add a new driver with the information obtained (only if valid) */ - ANIM_add_driver(reports, id, path, array_index, dflags, DRIVER_TYPE_PYTHON); - break; - } - case DRIVERS_EDITMODE_REMOVE: { - /* remove driver matching the information obtained (only if valid) */ - ANIM_remove_driver(reports, id, path, array_index, dflags); - break; - } + /* only if ID and path were set, should we perform any actions */ + if (id && path) { + short dflags = CREATEDRIVER_WITH_DEFAULT_DVAR; + int arraylen = 1; + + /* array checks */ + if (flag & KSP_FLAG_WHOLE_ARRAY) { + /* entire array was selected, so add drivers for all */ + arraylen = RNA_property_array_length(&ptr, prop); + } + else { + arraylen = array_index; + } + + /* we should do at least one step */ + if (arraylen == array_index) { + arraylen++; + } + + /* for each array element we should affect, add driver */ + for (; array_index < arraylen; array_index++) { + /* action depends on mode */ + switch (mode) { + case DRIVERS_EDITMODE_ADD: { + /* add a new driver with the information obtained (only if valid) */ + ANIM_add_driver(reports, id, path, array_index, dflags, DRIVER_TYPE_PYTHON); + break; + } + case DRIVERS_EDITMODE_REMOVE: { + /* remove driver matching the information obtained (only if valid) */ + ANIM_remove_driver(reports, id, path, array_index, dflags); + break; } } - - /* free path, since it had to be generated */ - MEM_freeN(path); } - } - /* go over sub-tree */ - if (TSELEM_OPEN(tselem, space_outliner)) { - do_outliner_drivers_editop(space_outliner, &te->subtree, reports, mode); + /* free path, since it had to be generated */ + MEM_freeN(path); } - } + }); } /** \} */ @@ -1964,8 +1949,7 @@ static int outliner_drivers_addsel_exec(bContext *C, wmOperator *op) } /* recursively go into tree, adding selected items */ - do_outliner_drivers_editop( - space_outliner, &space_outliner->tree, op->reports, DRIVERS_EDITMODE_ADD); + do_outliner_drivers_editop(space_outliner, op->reports, DRIVERS_EDITMODE_ADD); /* send notifiers */ WM_event_add_notifier(C, NC_ANIMATION | ND_FCURVES_ORDER, nullptr); /* XXX */ @@ -2004,8 +1988,7 @@ static int outliner_drivers_deletesel_exec(bContext *C, wmOperator *op) } /* recursively go into tree, adding selected items */ - do_outliner_drivers_editop( - space_outliner, &space_outliner->tree, op->reports, DRIVERS_EDITMODE_REMOVE); + do_outliner_drivers_editop(space_outliner, op->reports, DRIVERS_EDITMODE_REMOVE); /* send notifiers */ WM_event_add_notifier(C, ND_KEYS, nullptr); /* XXX */ @@ -2072,68 +2055,64 @@ static KeyingSet *verify_active_keyingset(Scene *scene, short add) return ks; } -/* Recursively iterate over tree, finding and working on selected items */ +/* Iterate over tree, finding and working on selected items */ static void do_outliner_keyingset_editop(SpaceOutliner *space_outliner, KeyingSet *ks, - ListBase *tree, - short mode) + const short mode) { - LISTBASE_FOREACH (TreeElement *, te, tree) { + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { TreeStoreElem *tselem = TREESTORE(te); /* if item is selected, perform operation */ - if (tselem->flag & TSE_SELECTED) { - ID *id = nullptr; - char *path = nullptr; - int array_index = 0; - short flag = 0; - short groupmode = KSP_GROUP_KSNAME; - - /* check if RNA-property described by this selected element is an animatable prop */ - const TreeElementRNACommon *te_rna = tree_element_cast<TreeElementRNACommon>(te); - PointerRNA ptr = te_rna->getPointerRNA(); - if (te_rna && te_rna->getPropertyRNA() && - RNA_property_animateable(&ptr, te_rna->getPropertyRNA())) { - /* get id + path + index info from the selected element */ - tree_element_to_path(te, tselem, &id, &path, &array_index, &flag, &groupmode); - } + if (!(tselem->flag & TSE_SELECTED)) { + return; + } - /* only if ID and path were set, should we perform any actions */ - if (id && path) { - /* action depends on mode */ - switch (mode) { - case KEYINGSET_EDITMODE_ADD: { - /* add a new path with the information obtained (only if valid) */ - /* TODO: what do we do with group name? - * for now, we don't supply one, and just let this use the KeyingSet name */ - BKE_keyingset_add_path(ks, id, nullptr, path, array_index, flag, groupmode); - ks->active_path = BLI_listbase_count(&ks->paths); - break; - } - case KEYINGSET_EDITMODE_REMOVE: { - /* find the relevant path, then remove it from the KeyingSet */ - KS_Path *ksp = BKE_keyingset_find_path(ks, id, nullptr, path, array_index, groupmode); + ID *id = nullptr; + char *path = nullptr; + int array_index = 0; + short flag = 0; + short groupmode = KSP_GROUP_KSNAME; + + /* check if RNA-property described by this selected element is an animatable prop */ + const TreeElementRNACommon *te_rna = tree_element_cast<TreeElementRNACommon>(te); + PointerRNA ptr = te_rna->getPointerRNA(); + if (te_rna && te_rna->getPropertyRNA() && + RNA_property_animateable(&ptr, te_rna->getPropertyRNA())) { + /* get id + path + index info from the selected element */ + tree_element_to_path(te, tselem, &id, &path, &array_index, &flag, &groupmode); + } - if (ksp) { - /* free path's data */ - BKE_keyingset_free_path(ks, ksp); + /* only if ID and path were set, should we perform any actions */ + if (id && path) { + /* action depends on mode */ + switch (mode) { + case KEYINGSET_EDITMODE_ADD: { + /* add a new path with the information obtained (only if valid) */ + /* TODO: what do we do with group name? + * for now, we don't supply one, and just let this use the KeyingSet name */ + BKE_keyingset_add_path(ks, id, nullptr, path, array_index, flag, groupmode); + ks->active_path = BLI_listbase_count(&ks->paths); + break; + } + case KEYINGSET_EDITMODE_REMOVE: { + /* find the relevant path, then remove it from the KeyingSet */ + KS_Path *ksp = BKE_keyingset_find_path(ks, id, nullptr, path, array_index, groupmode); - ks->active_path = 0; - } - break; + if (ksp) { + /* free path's data */ + BKE_keyingset_free_path(ks, ksp); + + ks->active_path = 0; } + break; } - - /* free path, since it had to be generated */ - MEM_freeN(path); } - } - /* go over sub-tree */ - if (TSELEM_OPEN(tselem, space_outliner)) { - do_outliner_keyingset_editop(space_outliner, ks, &te->subtree, mode); + /* free path, since it had to be generated */ + MEM_freeN(path); } - } + }); } /** \} */ @@ -2158,7 +2137,7 @@ static int outliner_keyingset_additems_exec(bContext *C, wmOperator *op) } /* recursively go into tree, adding selected items */ - do_outliner_keyingset_editop(space_outliner, ks, &space_outliner->tree, KEYINGSET_EDITMODE_ADD); + do_outliner_keyingset_editop(space_outliner, ks, KEYINGSET_EDITMODE_ADD); /* send notifiers */ WM_event_add_notifier(C, NC_SCENE | ND_KEYINGSET, nullptr); @@ -2199,8 +2178,7 @@ static int outliner_keyingset_removeitems_exec(bContext *C, wmOperator *UNUSED(o } /* recursively go into tree, adding selected items */ - do_outliner_keyingset_editop( - space_outliner, ks, &space_outliner->tree, KEYINGSET_EDITMODE_REMOVE); + do_outliner_keyingset_editop(space_outliner, ks, KEYINGSET_EDITMODE_REMOVE); /* send notifiers */ WM_event_add_notifier(C, NC_SCENE | ND_KEYINGSET, nullptr); diff --git a/source/blender/editors/space_outliner/outliner_intern.hh b/source/blender/editors/space_outliner/outliner_intern.hh index f3bcb7b0f1e..a0dcb49aa43 100644 --- a/source/blender/editors/space_outliner/outliner_intern.hh +++ b/source/blender/editors/space_outliner/outliner_intern.hh @@ -160,8 +160,6 @@ enum { /* Child elements of the same type in the icon-row are drawn merged as one icon. * This flag is set for an element that is part of these merged child icons. */ TE_ICONROW_MERGED = (1 << 7), - /* This element has some warning to be displayed. */ - TE_HAS_WARNING = (1 << 8), }; /* button events */ @@ -410,8 +408,12 @@ int outliner_flag_is_any_test(ListBase *lb, short flag, int curlevel); * Set or unset \a flag for all outliner elements in \a lb and sub-trees. * \return if any flag was modified. */ -bool outliner_flag_set(ListBase *lb, short flag, short set); -bool outliner_flag_flip(ListBase *lb, short flag); +extern "C++" { +bool outliner_flag_set(const SpaceOutliner &space_outliner, short flag, short set); +bool outliner_flag_set(const ListBase &lb, short flag, short set); +bool outliner_flag_flip(const SpaceOutliner &space_outliner, short flag); +bool outliner_flag_flip(const ListBase &lb, short flag); +} void item_rename_fn(struct bContext *C, struct ReportList *reports, @@ -453,7 +455,8 @@ void id_remap_fn(struct bContext *C, /** * To retrieve coordinates with redrawing the entire tree. */ -void outliner_set_coordinates(struct ARegion *region, struct SpaceOutliner *space_outliner); +void outliner_set_coordinates(const struct ARegion *region, + const struct SpaceOutliner *space_outliner); /** * Open or close a tree element, optionally toggling all children recursively. @@ -510,6 +513,11 @@ void OUTLINER_OT_drivers_delete_selected(struct wmOperatorType *ot); void OUTLINER_OT_orphans_purge(struct wmOperatorType *ot); +/* outliner_query.cc ---------------------------------------------- */ + +bool outliner_shows_mode_column(const SpaceOutliner &space_outliner); +bool outliner_has_element_warnings(const SpaceOutliner &space_outliner); + /* outliner_tools.c ---------------------------------------------- */ void merged_element_search_menu_invoke(struct bContext *C, diff --git a/source/blender/editors/space_outliner/outliner_query.cc b/source/blender/editors/space_outliner/outliner_query.cc new file mode 100644 index 00000000000..d6483c44fce --- /dev/null +++ b/source/blender/editors/space_outliner/outliner_query.cc @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spoutliner + */ + +#include <functional> + +#include "BLI_listbase.h" + +#include "DNA_space_types.h" + +#include "outliner_intern.hh" +#include "tree/tree_display.hh" + +using namespace blender::ed::outliner; + +bool outliner_shows_mode_column(const SpaceOutliner &space_outliner) +{ + const AbstractTreeDisplay &tree_display = *space_outliner.runtime->tree_display; + + return tree_display.supportsModeColumn() && (space_outliner.flag & SO_MODE_COLUMN); +} + +/** + * Iterate over the entire tree (including collapsed sub-elements), probing if any of the elements + * has a warning to be displayed. + */ +bool outliner_has_element_warnings(const SpaceOutliner &space_outliner) +{ + std::function<bool(const ListBase &)> recursive_fn; + + recursive_fn = [&](const ListBase &lb) { + LISTBASE_FOREACH (const TreeElement *, te, &lb) { + if (te->abstract_element && !te->abstract_element->getWarning().is_empty()) { + return true; + } + + if (recursive_fn(te->subtree)) { + return true; + } + } + + return false; + }; + + return recursive_fn(space_outliner.tree); +} diff --git a/source/blender/editors/space_outliner/outliner_select.cc b/source/blender/editors/space_outliner/outliner_select.cc index fd0ee422df0..bd6d3d89706 100644 --- a/source/blender/editors/space_outliner/outliner_select.cc +++ b/source/blender/editors/space_outliner/outliner_select.cc @@ -66,7 +66,9 @@ #include "RNA_prototypes.h" #include "outliner_intern.hh" +#include "tree/tree_display.hh" #include "tree/tree_element_seq.hh" +#include "tree/tree_iterator.hh" using namespace blender::ed::outliner; @@ -1456,7 +1458,7 @@ void outliner_item_select(bContext *C, /* Clear previous active when activating and clear selection when not extending selection */ const short clear_flag = (activate ? TSE_ACTIVE : 0) | (extend ? 0 : TSE_SELECTED); if (clear_flag) { - outliner_flag_set(&space_outliner->tree, clear_flag, false); + outliner_flag_set(*space_outliner, clear_flag, false); } if (select_flag & OL_ITEM_SELECT) { @@ -1530,7 +1532,7 @@ static void do_outliner_range_select(bContext *C, const bool active_selected = (tselem->flag & TSE_SELECTED); if (!extend) { - outliner_flag_set(&space_outliner->tree, TSE_SELECTED, false); + outliner_flag_set(*space_outliner, TSE_SELECTED, false); } /* Select active if under cursor */ @@ -1557,12 +1559,11 @@ static bool outliner_is_co_within_restrict_columns(const SpaceOutliner *space_ou bool outliner_is_co_within_mode_column(SpaceOutliner *space_outliner, const float view_mval[2]) { - /* Mode toggles only show in View Layer and Scenes modes. */ - if (!ELEM(space_outliner->outlinevis, SO_VIEW_LAYER, SO_SCENES)) { + if (!outliner_shows_mode_column(*space_outliner)) { return false; } - return space_outliner->flag & SO_MODE_COLUMN && view_mval[0] < UI_UNIT_X; + return view_mval[0] < UI_UNIT_X; } static bool outliner_is_co_within_active_mode_column(bContext *C, @@ -1604,7 +1605,7 @@ static int outliner_item_do_activate_from_cursor(bContext *C, if (!(te = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]))) { if (deselect_all) { - changed |= outliner_flag_set(&space_outliner->tree, TSE_SELECTED, false); + changed |= outliner_flag_set(*space_outliner, TSE_SELECTED, false); } } /* Don't allow toggle on scene collection */ @@ -1715,26 +1716,17 @@ void OUTLINER_OT_item_activate(wmOperatorType *ot) /** \name Box Select Operator * \{ */ -static void outliner_item_box_select(bContext *C, - SpaceOutliner *space_outliner, - Scene *scene, - rctf *rectf, - TreeElement *te, - bool select) +static void outliner_box_select(bContext *C, + SpaceOutliner *space_outliner, + const rctf *rectf, + const bool select) { - TreeStoreElem *tselem = TREESTORE(te); - - if (te->ys <= rectf->ymax && te->ys + UI_UNIT_Y >= rectf->ymin) { - outliner_item_select( - C, space_outliner, te, (select ? OL_ITEM_SELECT : OL_ITEM_DESELECT) | OL_ITEM_EXTEND); - } - - /* Look at its children. */ - if (TSELEM_OPEN(tselem, space_outliner)) { - LISTBASE_FOREACH (TreeElement *, te_sub, &te->subtree) { - outliner_item_box_select(C, space_outliner, scene, rectf, te_sub, select); + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { + if (te->ys <= rectf->ymax && te->ys + UI_UNIT_Y >= rectf->ymin) { + outliner_item_select( + C, space_outliner, te, (select ? OL_ITEM_SELECT : OL_ITEM_DESELECT) | OL_ITEM_EXTEND); } - } + }); } static int outliner_box_select_exec(bContext *C, wmOperator *op) @@ -1747,15 +1739,13 @@ static int outliner_box_select_exec(bContext *C, wmOperator *op) const eSelectOp sel_op = (eSelectOp)RNA_enum_get(op->ptr, "mode"); const bool select = (sel_op != SEL_OP_SUB); if (SEL_OP_USE_PRE_DESELECT(sel_op)) { - outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 0); + outliner_flag_set(*space_outliner, TSE_SELECTED, 0); } WM_operator_properties_border_to_rctf(op, &rectf); UI_view2d_region_to_view_rctf(®ion->v2d, &rectf, &rectf); - LISTBASE_FOREACH (TreeElement *, te, &space_outliner->tree) { - outliner_item_box_select(C, space_outliner, scene, &rectf, te, select); - } + outliner_box_select(C, space_outliner, &rectf, select); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index cc81d5ed68d..3b018d59881 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -84,6 +84,7 @@ #include "outliner_intern.hh" #include "tree/tree_element_rna.hh" #include "tree/tree_element_seq.hh" +#include "tree/tree_iterator.hh" static CLG_LogRef LOG = {"ed.outliner.tools"}; @@ -412,11 +413,10 @@ static void outliner_do_libdata_operation(bContext *C, ReportList *reports, Scene *scene, SpaceOutliner *space_outliner, - ListBase *lb, outliner_operation_fn operation_fn, void *user_data) { - LISTBASE_FOREACH (TreeElement *, te, lb) { + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { TreeStoreElem *tselem = TREESTORE(te); if (tselem->flag & TSE_SELECTED) { if (((tselem->type == TSE_SOME_ID) && (te->idcode != 0)) || @@ -425,11 +425,7 @@ static void outliner_do_libdata_operation(bContext *C, operation_fn(C, reports, scene, te, tsep, tselem, user_data); } } - if (TSELEM_OPEN(tselem, space_outliner)) { - outliner_do_libdata_operation( - C, reports, scene, space_outliner, &te->subtree, operation_fn, user_data); - } - } + }); } /** \} */ @@ -763,6 +759,10 @@ static void id_local_fn(bContext *C, struct OutlinerLibOverrideData { bool do_hierarchy; + + /** When creating new overrides, make them all user-editable. */ + bool do_fully_editable; + /** * For resync operation, force keeping newly created override IDs (or original linked IDs) * instead of re-applying relevant existing ID pointer property override operations. Helps @@ -957,7 +957,8 @@ static void id_override_library_create_fn(bContext *C, id_root_reference, id_hierarchy_root_reference, id_instance_hint, - &id_root_override); + &id_root_override, + data->do_fully_editable); BLI_assert(id_root_override != nullptr); BLI_assert(!ID_IS_LINKED(id_root_override)); @@ -1597,21 +1598,17 @@ static void outliner_do_data_operation( SpaceOutliner *space_outliner, int type, int event, - ListBase *lb, void (*operation_fn)(int, TreeElement *, TreeStoreElem *, void *), void *arg) { - LISTBASE_FOREACH (TreeElement *, te, lb) { + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { TreeStoreElem *tselem = TREESTORE(te); if (tselem->flag & TSE_SELECTED) { if (tselem->type == type) { operation_fn(event, te, tselem, arg); } } - if (TSELEM_OPEN(tselem, space_outliner)) { - outliner_do_data_operation(space_outliner, type, event, &te->subtree, operation_fn, arg); - } - } + }); } static Base *outliner_batch_delete_hierarchy( @@ -1775,8 +1772,7 @@ static int outliner_object_operation_exec(bContext *C, wmOperator *op) selection_changed = true; break; case OL_OP_REMAP: - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, &space_outliner->tree, id_remap_fn, nullptr); + outliner_do_libdata_operation(C, op->reports, scene, space_outliner, id_remap_fn, nullptr); /* No undo push here, operator does it itself (since it's a modal one, the op_undo_depth * trick does not work here). */ break; @@ -1979,6 +1975,7 @@ enum eOutlinerIdOpTypes { OUTLINER_IDOP_LOCAL, OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE, OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY, + OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE, OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE, OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET, OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY, @@ -2024,6 +2021,12 @@ static const EnumPropertyItem prop_id_op_types[] = { "Make Library Override Hierarchy", "Make a local override of this linked data-block, and its hierarchy of dependencies - only " "applies to active Outliner item"}, + {OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE, + "OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE", + 0, + "Make Library Override Hierarchy Fully Editable", + "Make a local override of this linked data-block, and its hierarchy of dependencies, making " + "them all fully user-editable - only applies to active Outliner item"}, {OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE, "OVERRIDE_LIBRARY_MAKE_EDITABLE", 0, @@ -2103,6 +2106,7 @@ static bool outliner_id_operation_item_poll(bContext *C, } return false; case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY: + case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE: if (ID_IS_OVERRIDABLE_LIBRARY(tselem->id) || (ID_IS_LINKED(tselem->id))) { return true; } @@ -2179,13 +2183,8 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) case OUTLINER_IDOP_UNLINK: { /* unlink datablock from its parent */ if (objectlevel) { - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - unlink_object_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, unlink_object_fn, nullptr); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, nullptr); ED_undo_push(C, "Unlink Object"); @@ -2194,61 +2193,36 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) switch (idlevel) { case ID_AC: - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - unlink_action_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, unlink_action_fn, nullptr); WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, nullptr); ED_undo_push(C, "Unlink action"); break; case ID_MA: - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - unlink_material_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, unlink_material_fn, nullptr); WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, nullptr); ED_undo_push(C, "Unlink material"); break; case ID_TE: - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - unlink_texture_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, unlink_texture_fn, nullptr); WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, nullptr); ED_undo_push(C, "Unlink texture"); break; case ID_WO: - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - unlink_world_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, unlink_world_fn, nullptr); WM_event_add_notifier(C, NC_SCENE | ND_WORLD, nullptr); ED_undo_push(C, "Unlink world"); break; case ID_GR: - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - unlink_collection_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, unlink_collection_fn, nullptr); WM_event_add_notifier(C, NC_SCENE | ND_LAYER, nullptr); ED_undo_push(C, "Unlink Collection"); @@ -2261,20 +2235,14 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) } case OUTLINER_IDOP_LOCAL: { /* make local */ - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, &space_outliner->tree, id_local_fn, nullptr); + outliner_do_libdata_operation(C, op->reports, scene, space_outliner, id_local_fn, nullptr); ED_undo_push(C, "Localized Data"); break; } case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE: { OutlinerLibOverrideData override_data{}; - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_override_library_create_fn, - &override_data); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); ED_undo_push(C, "Overridden Data"); break; } @@ -2285,19 +2253,30 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) op->reports, scene, space_outliner, - &space_outliner->tree, id_override_library_create_hierarchy_pre_process_fn, &override_data); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); + id_override_library_create_hierarchy_post_process(C, &override_data); + + ED_undo_push(C, "Overridden Data Hierarchy"); + break; + } + case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE: { + OutlinerLibOverrideData override_data{}; + override_data.do_hierarchy = true; + override_data.do_fully_editable = true; outliner_do_libdata_operation(C, op->reports, scene, space_outliner, - &space_outliner->tree, - id_override_library_create_fn, + id_override_library_create_hierarchy_pre_process_fn, &override_data); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); id_override_library_create_hierarchy_post_process(C, &override_data); - ED_undo_push(C, "Overridden Data Hierarchy"); + ED_undo_push(C, "Overridden Data Hierarchy Fully Editable"); break; } case OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE: { @@ -2305,7 +2284,6 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) op->reports, scene, space_outliner, - &space_outliner->tree, id_override_library_toggle_flag_fn, POINTER_FROM_UINT(IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED)); @@ -2314,39 +2292,24 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) } case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET: { OutlinerLibOverrideData override_data{}; - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_override_library_reset_fn, - &override_data); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_override_library_reset_fn, &override_data); ED_undo_push(C, "Reset Overridden Data"); break; } case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY: { OutlinerLibOverrideData override_data{}; override_data.do_hierarchy = true; - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_override_library_reset_fn, - &override_data); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_override_library_reset_fn, &override_data); ED_undo_push(C, "Reset Overridden Data Hierarchy"); break; } case OUTLINER_IDOP_OVERRIDE_LIBRARY_RESYNC_HIERARCHY: { OutlinerLibOverrideData override_data{}; override_data.do_hierarchy = true; - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_override_library_resync_fn, - &override_data); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_override_library_resync_fn, &override_data); ED_undo_push(C, "Resync Overridden Data Hierarchy"); break; } @@ -2354,35 +2317,20 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) OutlinerLibOverrideData override_data{}; override_data.do_hierarchy = true; override_data.do_resync_hierarchy_enforce = true; - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_override_library_resync_fn, - &override_data); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_override_library_resync_fn, &override_data); ED_undo_push(C, "Resync Overridden Data Hierarchy"); break; } case OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_HIERARCHY: { - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_override_library_clear_hierarchy_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_override_library_clear_hierarchy_fn, nullptr); ED_undo_push(C, "Clear Overridden Data Hierarchy"); break; } case OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_SINGLE: { - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_override_library_clear_single_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_override_library_clear_single_fn, nullptr); ED_undo_push(C, "Clear Overridden Data Hierarchy"); break; } @@ -2390,26 +2338,16 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) /* make single user */ switch (idlevel) { case ID_AC: - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - singleuser_action_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, singleuser_action_fn, nullptr); WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, nullptr); ED_undo_push(C, "Single-User Action"); break; case ID_WO: - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - singleuser_world_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, singleuser_world_fn, nullptr); WM_event_add_notifier(C, NC_SCENE | ND_WORLD, nullptr); ED_undo_push(C, "Single-User World"); @@ -2424,15 +2362,14 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) case OUTLINER_IDOP_DELETE: { if (idlevel > 0) { outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, &space_outliner->tree, id_delete_fn, nullptr); + C, op->reports, scene, space_outliner, id_delete_fn, nullptr); ED_undo_push(C, "Delete"); } break; } case OUTLINER_IDOP_REMAP: { if (idlevel > 0) { - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, &space_outliner->tree, id_remap_fn, nullptr); + outliner_do_libdata_operation(C, op->reports, scene, space_outliner, id_remap_fn, nullptr); /* No undo push here, operator does it itself (since it's a modal one, the op_undo_depth * trick does not work here). */ } @@ -2455,13 +2392,8 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) } case OUTLINER_IDOP_FAKE_ADD: { /* set fake user */ - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_fake_user_set_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_fake_user_set_fn, nullptr); WM_event_add_notifier(C, NC_ID | NA_EDITED, nullptr); ED_undo_push(C, "Add Fake User"); @@ -2469,13 +2401,8 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) } case OUTLINER_IDOP_FAKE_CLEAR: { /* clear fake user */ - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_fake_user_clear_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_fake_user_clear_fn, nullptr); WM_event_add_notifier(C, NC_ID | NA_EDITED, nullptr); ED_undo_push(C, "Clear Fake User"); @@ -2484,20 +2411,15 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) case OUTLINER_IDOP_RENAME: { /* rename */ outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, &space_outliner->tree, item_rename_fn, nullptr); + C, op->reports, scene, space_outliner, item_rename_fn, nullptr); WM_event_add_notifier(C, NC_ID | NA_EDITED, nullptr); ED_undo_push(C, "Rename"); break; } case OUTLINER_IDOP_SELECT_LINKED: - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - &space_outliner->tree, - id_select_linked_fn, - nullptr); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_select_linked_fn, nullptr); ED_outliner_select_sync_from_all_tag(C); ED_undo_push(C, "Select"); break; @@ -2580,21 +2502,19 @@ static int outliner_lib_operation_exec(bContext *C, wmOperator *op) eOutlinerLibOpTypes event = (eOutlinerLibOpTypes)RNA_enum_get(op->ptr, "type"); switch (event) { case OL_LIB_DELETE: { - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, &space_outliner->tree, id_delete_fn, nullptr); + outliner_do_libdata_operation(C, op->reports, scene, space_outliner, id_delete_fn, nullptr); ED_undo_push(C, "Delete Library"); break; } case OL_LIB_RELOCATE: { outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, &space_outliner->tree, lib_relocate_fn, nullptr); + C, op->reports, scene, space_outliner, lib_relocate_fn, nullptr); /* No undo push here, operator does it itself (since it's a modal one, the op_undo_depth * trick does not work here). */ break; } case OL_LIB_RELOAD: { - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, &space_outliner->tree, lib_reload_fn, nullptr); + outliner_do_libdata_operation(C, op->reports, scene, space_outliner, lib_reload_fn, nullptr); /* No undo push here, operator does it itself (since it's a modal one, the op_undo_depth * trick does not work here). */ break; @@ -2637,11 +2557,10 @@ void OUTLINER_OT_lib_operation(wmOperatorType *ot) static void outliner_do_id_set_operation( SpaceOutliner *space_outliner, int type, - ListBase *lb, ID *newid, void (*operation_fn)(TreeElement *, TreeStoreElem *, TreeStoreElem *, ID *)) { - LISTBASE_FOREACH (TreeElement *, te, lb) { + tree_iterator::all_open(*space_outliner, [&](TreeElement *te) { TreeStoreElem *tselem = TREESTORE(te); if (tselem->flag & TSE_SELECTED) { if (tselem->type == type) { @@ -2649,10 +2568,7 @@ static void outliner_do_id_set_operation( operation_fn(te, tselem, tsep, newid); } } - if (TSELEM_OPEN(tselem, space_outliner)) { - outliner_do_id_set_operation(space_outliner, type, &te->subtree, newid, operation_fn); - } - } + }); } static void actionset_id_fn(TreeElement *UNUSED(te), @@ -2707,12 +2623,10 @@ static int outliner_action_set_exec(bContext *C, wmOperator *op) /* perform action if valid channel */ if (datalevel == TSE_ANIM_DATA) { - outliner_do_id_set_operation( - space_outliner, datalevel, &space_outliner->tree, (ID *)act, actionset_id_fn); + outliner_do_id_set_operation(space_outliner, datalevel, (ID *)act, actionset_id_fn); } else if (idlevel == ID_AC) { - outliner_do_id_set_operation( - space_outliner, idlevel, &space_outliner->tree, (ID *)act, actionset_id_fn); + outliner_do_id_set_operation(space_outliner, idlevel, (ID *)act, actionset_id_fn); } else { return OPERATOR_CANCELLED; @@ -2799,8 +2713,7 @@ static int outliner_animdata_operation_exec(bContext *C, wmOperator *op) switch (event) { case OUTLINER_ANIMOP_CLEAR_ADT: /* Remove Animation Data - this may remove the active action, in some cases... */ - outliner_do_data_operation( - space_outliner, datalevel, event, &space_outliner->tree, clear_animdata_fn, nullptr); + outliner_do_data_operation(space_outliner, datalevel, event, clear_animdata_fn, nullptr); WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, nullptr); ED_undo_push(C, "Clear Animation Data"); @@ -2817,32 +2730,23 @@ static int outliner_animdata_operation_exec(bContext *C, wmOperator *op) case OUTLINER_ANIMOP_CLEAR_ACT: /* clear active action - using standard rules */ - outliner_do_data_operation( - space_outliner, datalevel, event, &space_outliner->tree, unlinkact_animdata_fn, nullptr); + outliner_do_data_operation(space_outliner, datalevel, event, unlinkact_animdata_fn, nullptr); WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, nullptr); ED_undo_push(C, "Unlink action"); break; case OUTLINER_ANIMOP_REFRESH_DRV: - outliner_do_data_operation(space_outliner, - datalevel, - event, - &space_outliner->tree, - refreshdrivers_animdata_fn, - nullptr); + outliner_do_data_operation( + space_outliner, datalevel, event, refreshdrivers_animdata_fn, nullptr); WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr); // ED_undo_push(C, "Refresh Drivers"); /* No undo needed - shouldn't have any impact? */ break; case OUTLINER_ANIMOP_CLEAR_DRV: - outliner_do_data_operation(space_outliner, - datalevel, - event, - &space_outliner->tree, - cleardrivers_animdata_fn, - nullptr); + outliner_do_data_operation( + space_outliner, datalevel, event, cleardrivers_animdata_fn, nullptr); WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr); ED_undo_push(C, "Clear Drivers"); @@ -2892,8 +2796,7 @@ static int outliner_constraint_operation_exec(bContext *C, wmOperator *op) SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); eOutliner_PropConstraintOps event = (eOutliner_PropConstraintOps)RNA_enum_get(op->ptr, "type"); - outliner_do_data_operation( - space_outliner, TSE_CONSTRAINT, event, &space_outliner->tree, constraint_fn, C); + outliner_do_data_operation(space_outliner, TSE_CONSTRAINT, event, constraint_fn, C); if (event == OL_CONSTRAINTOP_DELETE) { outliner_cleanup_tree(space_outliner); @@ -2939,8 +2842,7 @@ static int outliner_modifier_operation_exec(bContext *C, wmOperator *op) SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); eOutliner_PropModifierOps event = (eOutliner_PropModifierOps)RNA_enum_get(op->ptr, "type"); - outliner_do_data_operation( - space_outliner, TSE_MODIFIER, event, &space_outliner->tree, modifier_fn, C); + outliner_do_data_operation(space_outliner, TSE_MODIFIER, event, modifier_fn, C); if (event == OL_MODIFIER_OP_DELETE) { outliner_cleanup_tree(space_outliner); @@ -2983,24 +2885,21 @@ static int outliner_data_operation_exec(bContext *C, wmOperator *op) eOutliner_PropDataOps event = (eOutliner_PropDataOps)RNA_enum_get(op->ptr, "type"); switch (datalevel) { case TSE_POSE_CHANNEL: { - outliner_do_data_operation( - space_outliner, datalevel, event, &space_outliner->tree, pchan_fn, nullptr); + outliner_do_data_operation(space_outliner, datalevel, event, pchan_fn, nullptr); WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr); ED_undo_push(C, "PoseChannel operation"); break; } case TSE_BONE: { - outliner_do_data_operation( - space_outliner, datalevel, event, &space_outliner->tree, bone_fn, nullptr); + outliner_do_data_operation(space_outliner, datalevel, event, bone_fn, nullptr); WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr); ED_undo_push(C, "Bone operation"); break; } case TSE_EBONE: { - outliner_do_data_operation( - space_outliner, datalevel, event, &space_outliner->tree, ebone_fn, nullptr); + outliner_do_data_operation(space_outliner, datalevel, event, ebone_fn, nullptr); WM_event_add_notifier(C, NC_OBJECT | ND_POSE, nullptr); ED_undo_push(C, "EditBone operation"); @@ -3008,16 +2907,14 @@ static int outliner_data_operation_exec(bContext *C, wmOperator *op) } case TSE_SEQUENCE: { Scene *scene = CTX_data_scene(C); - outliner_do_data_operation( - space_outliner, datalevel, event, &space_outliner->tree, sequence_fn, scene); + outliner_do_data_operation(space_outliner, datalevel, event, sequence_fn, scene); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); ED_undo_push(C, "Sequencer operation"); break; } case TSE_GP_LAYER: { - outliner_do_data_operation( - space_outliner, datalevel, event, &space_outliner->tree, gpencil_layer_fn, nullptr); + outliner_do_data_operation(space_outliner, datalevel, event, gpencil_layer_fn, nullptr); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, nullptr); ED_undo_push(C, "Grease Pencil Layer operation"); @@ -3025,8 +2922,7 @@ static int outliner_data_operation_exec(bContext *C, wmOperator *op) } case TSE_RNA_STRUCT: if (event == OL_DOP_SELECT_LINKED) { - outliner_do_data_operation( - space_outliner, datalevel, event, &space_outliner->tree, data_select_linked_fn, C); + outliner_do_data_operation(space_outliner, datalevel, event, data_select_linked_fn, C); } break; diff --git a/source/blender/editors/space_outliner/outliner_tree.cc b/source/blender/editors/space_outliner/outliner_tree.cc index bbd9b48c260..7b3ce499929 100644 --- a/source/blender/editors/space_outliner/outliner_tree.cc +++ b/source/blender/editors/space_outliner/outliner_tree.cc @@ -919,10 +919,6 @@ TreeElement *outliner_add_element(SpaceOutliner *space_outliner, BLI_assert_msg(false, "Element type should already use new AbstractTreeElement design"); } - if (tree_element_warnings_get(te, nullptr, nullptr)) { - te->flag |= TE_HAS_WARNING; - } - return te; } diff --git a/source/blender/editors/space_outliner/outliner_utils.cc b/source/blender/editors/space_outliner/outliner_utils.cc index 4b947154864..0db612ce6db 100644 --- a/source/blender/editors/space_outliner/outliner_utils.cc +++ b/source/blender/editors/space_outliner/outliner_utils.cc @@ -27,6 +27,9 @@ #include "UI_view2d.h" #include "outliner_intern.hh" +#include "tree/tree_iterator.hh" + +using namespace blender::ed::outliner; /* -------------------------------------------------------------------- */ /** \name Tree View Context diff --git a/source/blender/editors/space_outliner/space_outliner.cc b/source/blender/editors/space_outliner/space_outliner.cc index 97dc659155f..5bcd1edebc0 100644 --- a/source/blender/editors/space_outliner/space_outliner.cc +++ b/source/blender/editors/space_outliner/space_outliner.cc @@ -438,7 +438,7 @@ static void outliner_deactivate(struct ScrArea *area) { /* Remove hover highlights */ SpaceOutliner *space_outliner = reinterpret_cast<SpaceOutliner *>(area->spacedata.first); - outliner_flag_set(&space_outliner->tree, TSE_HIGHLIGHTED_ANY, false); + outliner_flag_set(*space_outliner, TSE_HIGHLIGHTED_ANY, false); ED_region_tag_redraw_no_rebuild(BKE_area_find_region_type(area, RGN_TYPE_WINDOW)); } diff --git a/source/blender/editors/space_outliner/tree/tree_display.cc b/source/blender/editors/space_outliner/tree/tree_display.cc index 141c68594e8..6ab497b3fbb 100644 --- a/source/blender/editors/space_outliner/tree/tree_display.cc +++ b/source/blender/editors/space_outliner/tree/tree_display.cc @@ -45,9 +45,9 @@ std::unique_ptr<AbstractTreeDisplay> AbstractTreeDisplay::createFromDisplayMode( return nullptr; } -bool AbstractTreeDisplay::hasWarnings() const +bool AbstractTreeDisplay::supportsModeColumn() const { - return has_warnings; + return false; } } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_display.hh b/source/blender/editors/space_outliner/tree/tree_display.hh index 327f29aa15e..190e35c81d6 100644 --- a/source/blender/editors/space_outliner/tree/tree_display.hh +++ b/source/blender/editors/space_outliner/tree/tree_display.hh @@ -75,12 +75,16 @@ class AbstractTreeDisplay { */ virtual ListBase buildTree(const TreeSourceData &source_data) = 0; - /** Accessor to whether given tree has some warnings to display. */ - bool hasWarnings() const; + /** + * Define if the display mode should be allowed to show a mode column on the left. This column + * adds an icon to indicate which objects are in the current mode (edit mode, pose mode, etc.) + * and allows adding other objects to the mode by clicking the icon. + * + * Returns false by default. + */ + virtual bool supportsModeColumn() const; protected: - bool has_warnings = false; - /** All derived classes will need a handle to this, so storing it in the base for convenience. */ SpaceOutliner &space_outliner_; }; @@ -100,6 +104,8 @@ class TreeDisplayViewLayer final : public AbstractTreeDisplay { ListBase buildTree(const TreeSourceData &source_data) override; + bool supportsModeColumn() const override; + private: void add_view_layer(Scene &, ListBase &, TreeElement *); void add_layer_collections_recursive(ListBase &, ListBase &, TreeElement &); @@ -212,6 +218,8 @@ class TreeDisplayScenes final : public AbstractTreeDisplay { TreeDisplayScenes(SpaceOutliner &space_outliner); ListBase buildTree(const TreeSourceData &source_data) override; + + bool supportsModeColumn() const override; }; /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/space_outliner/tree/tree_display_libraries.cc b/source/blender/editors/space_outliner/tree/tree_display_libraries.cc index 476bbdb63ae..46a89f17687 100644 --- a/source/blender/editors/space_outliner/tree/tree_display_libraries.cc +++ b/source/blender/editors/space_outliner/tree/tree_display_libraries.cc @@ -136,9 +136,6 @@ TreeElement *TreeDisplayLibraries::add_library_contents(Main &mainvar, ListBase tenlib = outliner_add_element(&space_outliner_, &lb, &mainvar, nullptr, TSE_ID_BASE, 0); tenlib->name = IFACE_("Current File"); } - if (tenlib->flag & TE_HAS_WARNING) { - has_warnings = true; - } } /* Create data-block list parent element on demand. */ diff --git a/source/blender/editors/space_outliner/tree/tree_display_scenes.cc b/source/blender/editors/space_outliner/tree/tree_display_scenes.cc index 9e00a425a5a..6b1de7f8b95 100644 --- a/source/blender/editors/space_outliner/tree/tree_display_scenes.cc +++ b/source/blender/editors/space_outliner/tree/tree_display_scenes.cc @@ -26,6 +26,11 @@ TreeDisplayScenes::TreeDisplayScenes(SpaceOutliner &space_outliner) { } +bool TreeDisplayScenes::supportsModeColumn() const +{ + return true; +} + ListBase TreeDisplayScenes::buildTree(const TreeSourceData &source_data) { /* On first view we open scenes. */ diff --git a/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc b/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc index 19811e45b90..80b3365766a 100644 --- a/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc +++ b/source/blender/editors/space_outliner/tree/tree_display_view_layer.cc @@ -55,6 +55,11 @@ TreeDisplayViewLayer::TreeDisplayViewLayer(SpaceOutliner &space_outliner) { } +bool TreeDisplayViewLayer::supportsModeColumn() const +{ + return true; +} + ListBase TreeDisplayViewLayer::buildTree(const TreeSourceData &source_data) { ListBase tree = {nullptr}; diff --git a/source/blender/editors/space_outliner/tree/tree_element.cc b/source/blender/editors/space_outliner/tree/tree_element.cc index 1e3fd2df7c2..94d55b70e3c 100644 --- a/source/blender/editors/space_outliner/tree/tree_element.cc +++ b/source/blender/editors/space_outliner/tree/tree_element.cc @@ -100,6 +100,11 @@ std::unique_ptr<AbstractTreeElement> AbstractTreeElement::createFromType(const i return nullptr; } +StringRefNull AbstractTreeElement::getWarning() const +{ + return ""; +} + void AbstractTreeElement::uncollapse_by_default(TreeElement *legacy_te) { if (!TREESTORE(legacy_te)->used) { @@ -118,39 +123,4 @@ void tree_element_expand(const AbstractTreeElement &tree_element, SpaceOutliner tree_element.expand(space_outliner); } -bool tree_element_warnings_get(TreeElement *te, int *r_icon, const char **r_message) -{ - TreeStoreElem *tselem = te->store_elem; - - if (tselem->type != TSE_SOME_ID) { - return false; - } - if (te->idcode != ID_LI) { - return false; - } - - Library *library = (Library *)tselem->id; - if (library->tag & LIBRARY_TAG_RESYNC_REQUIRED) { - if (r_icon) { - *r_icon = ICON_ERROR; - } - if (r_message) { - *r_message = TIP_( - "Contains linked library overrides that need to be resynced, updating the library is " - "recommended"); - } - return true; - } - if (library->id.tag & LIB_TAG_MISSING) { - if (r_icon) { - *r_icon = ICON_ERROR; - } - if (r_message) { - *r_message = TIP_("Missing library"); - } - return true; - } - return false; -} - } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_element.hh b/source/blender/editors/space_outliner/tree/tree_element.hh index c6593a517dd..0dcd75d340d 100644 --- a/source/blender/editors/space_outliner/tree/tree_element.hh +++ b/source/blender/editors/space_outliner/tree/tree_element.hh @@ -8,6 +8,8 @@ #include <memory> +#include "BLI_string_ref.hh" + struct ListBase; struct SpaceOutliner; struct TreeElement; @@ -56,6 +58,12 @@ class AbstractTreeElement { } /** + * By letting this return a warning message, the tree element will display a warning icon with + * the message in the tooltip. + */ + virtual StringRefNull getWarning() const; + + /** * Expand this tree element if it is displayed for the first time (as identified by its * tree-store element). * @@ -96,13 +104,4 @@ struct TreeElement *outliner_add_element(SpaceOutliner *space_outliner, void tree_element_expand(const AbstractTreeElement &tree_element, SpaceOutliner &space_outliner); -/** - * Get actual warning data of a tree element, if any. - * - * \param r_icon: The icon to display as warning. - * \param r_message: The message to display as warning. - * \return true if there is a warning, false otherwise. - */ -bool tree_element_warnings_get(struct TreeElement *te, int *r_icon, const char **r_message); - } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_element_id_library.cc b/source/blender/editors/space_outliner/tree/tree_element_id_library.cc index 0dcaec0385a..4f1b951ccaf 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_id_library.cc +++ b/source/blender/editors/space_outliner/tree/tree_element_id_library.cc @@ -4,6 +4,8 @@ * \ingroup spoutliner */ +#include "BLT_translation.h" + #include "DNA_ID.h" #include "DNA_listBase.h" @@ -24,4 +26,21 @@ bool TreeElementIDLibrary::isExpandValid() const return true; } +StringRefNull TreeElementIDLibrary::getWarning() const +{ + Library &library = reinterpret_cast<Library &>(id_); + + if (library.tag & LIBRARY_TAG_RESYNC_REQUIRED) { + return TIP_( + "Contains linked library overrides that need to be resynced, updating the library is " + "recommended"); + } + + if (library.id.tag & LIB_TAG_MISSING) { + return TIP_("Missing library"); + } + + return {}; +} + } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_element_id_library.hh b/source/blender/editors/space_outliner/tree/tree_element_id_library.hh index ed599cf04da..2d89b55813f 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_id_library.hh +++ b/source/blender/editors/space_outliner/tree/tree_element_id_library.hh @@ -17,6 +17,8 @@ class TreeElementIDLibrary final : public TreeElementID { TreeElementIDLibrary(TreeElement &legacy_te, Library &library); bool isExpandValid() const override; + + blender::StringRefNull getWarning() const override; }; } // namespace blender::ed::outliner 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 3a039da86c2..53e7b88c923 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_overrides.cc +++ b/source/blender/editors/space_outliner/tree/tree_element_overrides.cc @@ -38,6 +38,19 @@ TreeElementOverridesBase::TreeElementOverridesBase(TreeElement &legacy_te, ID &i } } +StringRefNull TreeElementOverridesBase::getWarning() const +{ + if (id.flag & LIB_LIB_OVERRIDE_RESYNC_LEFTOVER) { + return TIP_("This override data-block is not needed anymore, but was detected as user-edited"); + } + + if (ID_IS_OVERRIDE_LIBRARY_REAL(&id) && ID_REAL_USERS(&id) == 0) { + return TIP_("This override data-block is unused"); + } + + return {}; +} + void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const { BLI_assert(id.override_library != nullptr); @@ -93,4 +106,15 @@ TreeElementOverridesProperty::TreeElementOverridesProperty(TreeElement &legacy_t legacy_te.name = override_data.override_property.rna_path; } +StringRefNull TreeElementOverridesProperty::getWarning() const +{ + if (!is_rna_path_valid) { + return TIP_( + "This override property does not exist in current data, it will be removed on " + "next .blend file save"); + } + + return {}; +} + } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_element_overrides.hh b/source/blender/editors/space_outliner/tree/tree_element_overrides.hh index b42e1c37a0f..1db46d9af1d 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_overrides.hh +++ b/source/blender/editors/space_outliner/tree/tree_element_overrides.hh @@ -34,6 +34,8 @@ class TreeElementOverridesBase final : public AbstractTreeElement { TreeElementOverridesBase(TreeElement &legacy_te, ID &id); void expand(SpaceOutliner &) const override; + + StringRefNull getWarning() const override; }; class TreeElementOverridesProperty final : public AbstractTreeElement { @@ -46,6 +48,8 @@ class TreeElementOverridesProperty final : public AbstractTreeElement { public: TreeElementOverridesProperty(TreeElement &legacy_te, TreeElementOverridesData &override_data); + + StringRefNull getWarning() const override; }; } // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_iterator.cc b/source/blender/editors/space_outliner/tree/tree_iterator.cc new file mode 100644 index 00000000000..85ff9e6437e --- /dev/null +++ b/source/blender/editors/space_outliner/tree/tree_iterator.cc @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spoutliner + */ + +#include "DNA_space_types.h" + +#include "BLI_listbase.h" + +#include "../outliner_intern.hh" + +#include "tree_iterator.hh" + +namespace blender::ed::outliner::tree_iterator { + +void all(const SpaceOutliner &space_outliner, const VisitorFn visitor) +{ + all_open(space_outliner, space_outliner.tree, visitor); +} + +void all(const ListBase &subtree, const VisitorFn visitor) +{ + LISTBASE_FOREACH_MUTABLE (TreeElement *, element, &subtree) { + /* Get needed data out in case element gets freed. */ + const ListBase subtree = element->subtree; + + visitor(element); + /* Don't access element from now on, it may be freed. */ + + all(subtree, visitor); + } +} + +void all_open(const SpaceOutliner &space_outliner, const VisitorFn visitor) +{ + all_open(space_outliner, space_outliner.tree, visitor); +} + +void all_open(const SpaceOutliner &space_outliner, + const ListBase &subtree, + const VisitorFn visitor) +{ + LISTBASE_FOREACH_MUTABLE (TreeElement *, element, &subtree) { + /* Get needed data out in case element gets freed. */ + const bool is_open = TSELEM_OPEN(element->store_elem, &space_outliner); + const ListBase subtree = element->subtree; + + visitor(element); + /* Don't access element from now on, it may be freed. */ + + if (is_open) { + all_open(space_outliner, subtree, visitor); + } + } +} + +} // namespace blender::ed::outliner::tree_iterator diff --git a/source/blender/editors/space_outliner/tree/tree_iterator.hh b/source/blender/editors/space_outliner/tree/tree_iterator.hh new file mode 100644 index 00000000000..e3b3c90eaad --- /dev/null +++ b/source/blender/editors/space_outliner/tree/tree_iterator.hh @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spoutliner + */ + +#pragma once + +#include "BLI_function_ref.hh" + +struct ListBase; +struct SpaceOutliner; +struct TreeElement; + +namespace blender::ed::outliner { +namespace tree_iterator { + +using VisitorFn = FunctionRef<void(TreeElement *)>; + +/** + * Preorder (meaning depth-first) traversal of all elements (regardless of collapsed state). + * Freeing the currently visited element in \a visitor is fine. + */ +void all(const SpaceOutliner &space_outliner, VisitorFn visitor); +void all(const ListBase &subtree, VisitorFn visitor); + +/** + * Preorder (meaning depth-first) traversal of all elements not part of a collapsed sub-tree. + * Freeing the currently visited element in \a visitor is fine. + */ +void all_open(const SpaceOutliner &, VisitorFn visitor); +void all_open(const SpaceOutliner &, const ListBase &subtree, VisitorFn visitor); + +} // namespace tree_iterator +} // namespace blender::ed::outliner |