Outliner: Refactor element warning and mode column querying

Uses a inheritance based approach for querying warning of tree elements
and the mode column support of display modes.

For the warnings, tree elements can override the
`AbstractTreeElement::getWarning()` method and return a warning string.
The UI will draw the warning column with warning icons. This makes the
warning column more generalized and easier to extend to more use-cases.
E.g. library override elements will use this after a followup commit.

To support mode toggles a display mode can now just return true in the
`AbstractTreeDisplay::supportsModeColumn()` method. This makes it
trivial to add mode columns to other display modes, and less error prone
because there's no need to hunt down a bunch of display mode checks in
different places.
This commit is contained in:
Julian Eisel 2022-05-25 12:53:07 +02:00
parent 1a516bb714
commit f1df685f57
Notes: blender-bot 2023-02-14 08:42:54 +01:00
Referenced by issue #98461, Regression: Crash running screenshot from the command-line
Referenced by issue #95802, Library Override - Outliner UI/UX
15 changed files with 130 additions and 89 deletions

View File

@ -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

View File

@ -72,6 +72,7 @@
#include "tree/tree_element_overrides.hh"
#include "tree/tree_element_rna.hh"
using namespace blender;
using namespace blender::ed::outliner;
/* -------------------------------------------------------------------- */
@ -2285,19 +2286,19 @@ static void outliner_draw_mode_column(const bContext *C,
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)
{
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 (warning_msg.is_empty()) {
/* If given element has no warning, recursively try to display the first sub-element's warning.
*/
if (!TSELEM_OPEN(te->store_elem, space_outliner)) {
LISTBASE_FOREACH (TreeElement *, sub_te, &te->subtree) {
if (outliner_draw_warning_tree_element(
block, space_outliner, sub_te, sub_tselem, use_mode_column, te_ys)) {
block, space_outliner, sub_te, use_mode_column, te_ys)) {
return true;
}
}
@ -2305,12 +2306,6 @@ static bool outliner_draw_warning_tree_element(uiBlock *block,
return false;
}
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);
/* 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 +2315,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,7 +2325,7 @@ 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);
@ -2344,11 +2339,9 @@ static void outliner_draw_warning_column(const bContext *C,
ListBase *tree)
{
LISTBASE_FOREACH (TreeElement *, te, tree) {
TreeStoreElem *tselem = TREESTORE(te);
outliner_draw_warning_tree_element(block, space_outliner, te, use_mode_column, te->ys);
outliner_draw_warning_tree_element(block, space_outliner, te, tselem, use_mode_column, te->ys);
if (TSELEM_OPEN(tselem, space_outliner)) {
if (TSELEM_OPEN(te->store_elem, space_outliner)) {
outliner_draw_warning_column(C, block, space_outliner, use_mode_column, &te->subtree);
}
}
@ -3961,13 +3954,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);

View File

@ -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 */
@ -510,6 +508,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,

View File

@ -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);
}

View File

@ -66,6 +66,7 @@
#include "RNA_prototypes.h"
#include "outliner_intern.hh"
#include "tree/tree_display.hh"
#include "tree/tree_element_seq.hh"
using namespace blender::ed::outliner;
@ -1557,12 +1558,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,

View File

@ -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;
}

View File

@ -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

View File

@ -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;
};
/* -------------------------------------------------------------------- */

View File

@ -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. */

View File

@ -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. */

View File

@ -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};

View File

@ -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

View File

@ -8,6 +8,8 @@
#include <memory>
#include "BLI_string_ref.hh"
struct ListBase;
struct SpaceOutliner;
struct TreeElement;
@ -55,6 +57,12 @@ class AbstractTreeElement {
return legacy_te_;
}
/**
* By letting this return a warning message, the tree element will display a warning icon with
* the message in the tooltip.
*/
virtual blender::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

View File

@ -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

View File

@ -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