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:
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
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue