Cleanup: Various cleanups to the tree-view API

* Correct URL for documentation (was changed recently).
* Add comments.
* Reevaluate and update which functions are public, protected or
  private.
* Reorder functions and classes to be more logical and readable.
* Add helper class for the public item API so individual functions it
  uses can be made protected/private (the helper class is a friend).
  Also allows splitting API implementation from the C-API.
* Move internal layout builder helper class to the source file, out of
  the header.
* More consistent naming.
* Add alias for item-container, so it's more clear how it can be used.
* Use const.
* Remove unnecessary forward declaration.
This commit is contained in:
Julian Eisel 2021-12-09 12:07:34 +01:00
parent 9183f9f860
commit 69f55b1b62
4 changed files with 238 additions and 180 deletions

View File

@ -3286,7 +3286,7 @@ char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item, const str
* \return True if the drop was handled by the tree-view item.
*/
bool UI_tree_view_item_drop_handle(struct bContext *C,
uiTreeViewItemHandle *item_,
const uiTreeViewItemHandle *item_,
const struct ListBase *drags);
/**
* Can \a item_handle be renamed right now? Not that this isn't just a mere wrapper around

View File

@ -17,8 +17,8 @@
/** \file
* \ingroup editorui
*
* API for simple creation of tree UIs supporting advanced features.
* https://wiki.blender.org/wiki/Source/Interface/Views
* API for simple creation of tree UIs supporting typically needed features.
* https://wiki.blender.org/wiki/Source/Interface/Views/Tree_Views
*/
#pragma once
@ -36,7 +36,6 @@
#include "UI_resources.h"
struct bContext;
struct PointerRNA;
struct uiBlock;
struct uiBut;
struct uiButTreeRow;
@ -53,14 +52,17 @@ class AbstractTreeViewItemDragController;
/* ---------------------------------------------------------------------- */
/** \name Tree-View Item Container
*
* Base class for tree-view and tree-view items, so both can contain children.
* \{ */
/**
* Helper base class to expose common child-item data and functionality to both #AbstractTreeView
* and #AbstractTreeViewItem.
* Both the tree-view (as the root of the tree) and the items can have children. This is the base
* class for both, to store and manage child items. Children are owned by their parent container
* (tree-view or item).
*
* That means this type can be used whenever either a #AbstractTreeView or a
* #AbstractTreeViewItem is needed.
* That means this type can be used whenever either an #AbstractTreeView or an
* #AbstractTreeViewItem is needed, but the #TreeViewOrItem alias is a better name to use then.
*/
class TreeViewItemContainer {
friend class AbstractTreeView;
@ -112,43 +114,10 @@ class TreeViewItemContainer {
ENUM_OPERATORS(TreeViewItemContainer::IterOptions,
TreeViewItemContainer::IterOptions::SkipCollapsed);
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Tree-View Builders
* \{ */
class TreeViewBuilder {
uiBlock &block_;
public:
TreeViewBuilder(uiBlock &block);
void build_tree_view(AbstractTreeView &tree_view);
};
class TreeViewLayoutBuilder {
uiBlock &block_;
friend TreeViewBuilder;
public:
void build_row(AbstractTreeViewItem &item) const;
uiBlock &block() const;
uiLayout *current_layout() const;
private:
/* Created through #TreeViewBuilder. */
TreeViewLayoutBuilder(uiBlock &block);
/**
* Moves the button following the last added chevron closer to the list item.
*
* Iterates backwards over buttons until finding the tree-row button, which is assumed to be the
* first button added for the row, and can act as a delimiter that way.
*/
static void polish_layout(const uiBlock &block);
};
/** The container class is the base for both the tree-view and the items. This alias gives it a
* clearer name for handles that accept both. Use whenever something wants to act on child-items,
* irrespective of if they are stored at root level or as children of some other item. */
using TreeViewOrItem = TreeViewItemContainer;
/** \} */
@ -157,8 +126,8 @@ class TreeViewLayoutBuilder {
* \{ */
class AbstractTreeView : public TreeViewItemContainer {
friend AbstractTreeViewItem;
friend TreeViewBuilder;
friend class AbstractTreeViewItem;
friend class TreeViewBuilder;
/**
* Only one item can be renamed at a time. So the tree is informed about the renaming state to
@ -175,15 +144,16 @@ class AbstractTreeView : public TreeViewItemContainer {
/** Only one item can be renamed at a time. */
bool is_renaming() const;
protected:
virtual void build_tree() = 0;
/**
* Check if the tree is fully (re-)constructed. That means, both #build_tree() and
* #update_from_old() have finished.
*/
bool is_reconstructed() const;
protected:
virtual void build_tree() = 0;
private:
/**
* Match the tree-view against an earlier version of itself (if any) and copy the old UI state
@ -191,10 +161,10 @@ class AbstractTreeView : public TreeViewItemContainer {
* #AbstractTreeViewItem.update_from_old().
*/
void update_from_old(uiBlock &new_block);
static void update_children_from_old_recursive(const TreeViewItemContainer &new_items,
const TreeViewItemContainer &old_items);
static void update_children_from_old_recursive(const TreeViewOrItem &new_items,
const TreeViewOrItem &old_items);
static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item,
const TreeViewItemContainer &items);
const TreeViewOrItem &items);
/**
* Items may want to do additional work when state changes. But these state changes can only be
@ -202,7 +172,6 @@ class AbstractTreeView : public TreeViewItemContainer {
* the actual state changes are done in a delayed manner through this function.
*/
void change_state_delayed();
void build_layout_from_tree(const TreeViewLayoutBuilder &builder);
};
/** \} */
@ -221,17 +190,18 @@ class AbstractTreeView : public TreeViewItemContainer {
class AbstractTreeViewItem : public TreeViewItemContainer {
friend class AbstractTreeView;
friend class TreeViewLayoutBuilder;
/* Higher-level API. */
friend class TreeViewItemAPIWrapper;
public:
private:
bool is_open_ = false;
bool is_active_ = false;
bool is_renaming_ = false;
protected:
/** This label is used for identifying an item (together with its parent's labels). */
/** This label is used for identifying an item within its parent. */
std::string label_{};
/** Every item gets a button of type during the layout building #UI_BTYPE_TREEROW. */
/** Every visible item gets a button of type #UI_BTYPE_TREEROW during the layout building. */
uiButTreeRow *tree_row_but_ = nullptr;
public:
@ -240,28 +210,59 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
virtual void build_row(uiLayout &row) = 0;
virtual void build_context_menu(bContext &C, uiLayout &column) const;
AbstractTreeView &get_tree_view() const;
void begin_renaming();
void toggle_collapsed();
void set_collapsed(bool collapsed);
/**
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we
* can't be sure about the item state.
*/
bool is_collapsed() const;
/**
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we
* can't be sure about the item state.
*/
bool is_active() const;
protected:
/**
* Called when the items state changes from inactive to active.
*/
virtual void on_activate();
/**
* If the result is not empty, it controls whether the item should be active or not,
* usually depending on the data that the view represents.
*/
virtual std::optional<bool> should_be_active() const;
/**
* Queries if the tree-view item supports renaming in principle. Renaming may still fail, e.g. if
* another item is already being renamed.
*/
virtual bool can_rename() const;
virtual bool supports_renaming() const;
/**
* Try renaming the item, or the data it represents. Can assume
* #AbstractTreeViewItem::can_rename() returned true. Sub-classes that override this should
* usually call this, unless they have a custom #AbstractTreeViewItem.matches().
* #AbstractTreeViewItem::supports_renaming() returned true. Sub-classes that override this
* should usually call this, unless they have a custom #AbstractTreeViewItem.matches().
*
* \return True if the renaming was successful.
*/
virtual bool rename(StringRefNull new_name);
/**
* Return whether the item can be collapsed. Used to disable collapsing for items with children.
*/
virtual bool supports_collapsing() const;
/**
* Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of
* the last redraw to this item. If sub-classes introduce more advanced state they should
* override this and make it update their state accordingly.
*/
virtual void update_from_old(const AbstractTreeViewItem &old);
/**
* Compare this item to \a other to check if they represent the same data.
* Used to recognize an item from a previous redraw, to be able to keep its state (e.g.
@ -285,39 +286,6 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
*/
virtual std::unique_ptr<AbstractTreeViewItemDropController> create_drop_controller() const;
void begin_renaming();
void end_renaming();
AbstractTreeView &get_tree_view() const;
int count_parents() const;
void deactivate();
/**
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we
* can't be sure about the item state.
*/
bool is_active() const;
/**
* Can be called from the #AbstractTreeViewItem::build_row() implementation, but not earlier. The
* hovered state can't be queried reliably otherwise.
* Note that this does a linear lookup in the old block, so isn't too great performance-wise.
*/
bool is_hovered() const;
void toggle_collapsed();
/**
* Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we
* can't be sure about the item state.
*/
bool is_collapsed() const;
void set_collapsed(bool collapsed);
bool is_collapsible() const;
bool is_renaming() const;
void ensure_parents_uncollapsed();
bool matches_including_parents(const AbstractTreeViewItem &other) const;
uiButTreeRow *tree_row_button();
protected:
/**
* Activates this item, deactivates other items, calls the #AbstractTreeViewItem::on_activate()
* function and ensures this item's parents are not collapsed (so the item is visible).
@ -325,17 +293,20 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
* actual item state is unknown, possibly calling state-change update functions incorrectly.
*/
void activate();
void deactivate();
/**
* If the result is not empty, it controls whether the item should be active or not,
* usually depending on the data that the view represents.
* Can be called from the #AbstractTreeViewItem::build_row() implementation, but not earlier. The
* hovered state can't be queried reliably otherwise.
* Note that this does a linear lookup in the old block, so isn't too great performance-wise.
*/
virtual std::optional<bool> should_be_active() const;
bool is_hovered() const;
bool is_collapsible() const;
bool is_renaming() const;
/**
* Return whether the item can be collapsed. Used to disable collapsing for items with children.
*/
virtual bool supports_collapsing() const;
void ensure_parents_uncollapsed();
uiButTreeRow *tree_row_button();
private:
static void rename_button_fn(bContext *, void *, char *);
@ -346,13 +317,16 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
/** See #AbstractTreeView::change_state_delayed() */
void change_state_delayed();
void end_renaming();
void add_treerow_button(uiBlock &block);
void add_indent(uiLayout &row) const;
void add_collapse_chevron(uiBlock &block) const;
void add_rename_button(uiLayout &row);
bool matches_including_parents(const AbstractTreeViewItem &other) const;
bool has_active_child() const;
int count_parents() const;
};
/** \} */
@ -464,6 +438,21 @@ class BasicTreeViewItem : public AbstractTreeViewItem {
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Tree-View Builder
* \{ */
class TreeViewBuilder {
uiBlock &block_;
public:
TreeViewBuilder(uiBlock &block);
void build_tree_view(AbstractTreeView &tree_view);
};
/** \} */
/* ---------------------------------------------------------------------- */
template<class ItemT, typename... Args>

View File

@ -87,19 +87,6 @@ bool AbstractTreeView::is_renaming() const
return rename_buffer_ != nullptr;
}
void AbstractTreeView::build_layout_from_tree(const TreeViewLayoutBuilder &builder)
{
uiLayout *prev_layout = builder.current_layout();
uiLayout *box = uiLayoutBox(prev_layout);
uiLayoutColumn(box, false);
foreach_item([&builder](AbstractTreeViewItem &item) { builder.build_row(item); },
IterOptions::SkipCollapsed);
UI_block_layout_set_current(&builder.block(), prev_layout);
}
void AbstractTreeView::update_from_old(uiBlock &new_block)
{
uiBlock *old_block = new_block.oldblock;
@ -130,8 +117,8 @@ void AbstractTreeView::update_from_old(uiBlock &new_block)
is_reconstructed_ = true;
}
void AbstractTreeView::update_children_from_old_recursive(const TreeViewItemContainer &new_items,
const TreeViewItemContainer &old_items)
void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items,
const TreeViewOrItem &old_items)
{
for (const auto &new_item : new_items.children_) {
AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items);
@ -147,7 +134,7 @@ void AbstractTreeView::update_children_from_old_recursive(const TreeViewItemCont
}
AbstractTreeViewItem *AbstractTreeView::find_matching_child(
const AbstractTreeViewItem &lookup_item, const TreeViewItemContainer &items)
const AbstractTreeViewItem &lookup_item, const TreeViewOrItem &items)
{
for (const auto &iter_item : items.children_) {
if (lookup_item.matches(*iter_item)) {
@ -380,7 +367,7 @@ std::unique_ptr<AbstractTreeViewItemDropController> AbstractTreeViewItem::create
return nullptr;
}
bool AbstractTreeViewItem::can_rename() const
bool AbstractTreeViewItem::supports_renaming() const
{
/* No renaming by default. */
return false;
@ -414,7 +401,7 @@ bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const
void AbstractTreeViewItem::begin_renaming()
{
AbstractTreeView &tree_view = get_tree_view();
if (tree_view.is_renaming() || !can_rename()) {
if (tree_view.is_renaming() || !supports_renaming()) {
return;
}
@ -444,7 +431,7 @@ AbstractTreeView &AbstractTreeViewItem::get_tree_view() const
int AbstractTreeViewItem::count_parents() const
{
int i = 0;
for (TreeViewItemContainer *parent = parent_; parent; parent = parent->parent_) {
for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) {
i++;
}
return i;
@ -587,24 +574,42 @@ AbstractTreeViewItemDropController::AbstractTreeViewItemDropController(AbstractT
/* ---------------------------------------------------------------------- */
TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block)
{
}
class TreeViewLayoutBuilder {
uiBlock &block_;
void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view)
{
tree_view.build_tree();
tree_view.update_from_old(block_);
tree_view.change_state_delayed();
tree_view.build_layout_from_tree(TreeViewLayoutBuilder(block_));
}
friend TreeViewBuilder;
/* ---------------------------------------------------------------------- */
public:
void build_from_tree(const AbstractTreeView &tree_view);
void build_row(AbstractTreeViewItem &item) const;
uiBlock &block() const;
uiLayout *current_layout() const;
private:
/* Created through #TreeViewBuilder. */
TreeViewLayoutBuilder(uiBlock &block);
static void polish_layout(const uiBlock &block);
};
TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block)
{
}
void TreeViewLayoutBuilder::build_from_tree(const AbstractTreeView &tree_view)
{
uiLayout *prev_layout = current_layout();
uiLayout *box = uiLayoutBox(prev_layout);
uiLayoutColumn(box, false);
tree_view.foreach_item([this](AbstractTreeViewItem &item) { build_row(item); },
AbstractTreeView::IterOptions::SkipCollapsed);
UI_block_layout_set_current(&block(), prev_layout);
}
void TreeViewLayoutBuilder::polish_layout(const uiBlock &block)
{
LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block.buttons) {
@ -664,6 +669,22 @@ uiLayout *TreeViewLayoutBuilder::current_layout() const
/* ---------------------------------------------------------------------- */
TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block)
{
}
void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view)
{
tree_view.build_tree();
tree_view.update_from_old(block_);
tree_view.change_state_delayed();
TreeViewLayoutBuilder builder(block_);
builder.build_from_tree(tree_view);
}
/* ---------------------------------------------------------------------- */
BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_) : icon(icon_)
{
label_ = label;
@ -710,8 +731,92 @@ std::optional<bool> BasicTreeViewItem::should_be_active() const
return std::nullopt;
}
/* ---------------------------------------------------------------------- */
/**
* Helper for a public (C-)API, presenting higher level functionality. Has access to internal
* data/functionality (friend of #AbstractTreeViewItem), which is sometimes needed when
* functionality of the API needs to be constructed from multiple internal conditions and/or
* functions that on their own shouldn't be part of the API.
*/
class TreeViewItemAPIWrapper {
public:
static bool matches(const AbstractTreeViewItem &a, const AbstractTreeViewItem &b)
{
/* TODO should match the tree-view as well. */
return a.matches_including_parents(b);
}
static bool drag_start(bContext &C, const AbstractTreeViewItem &item)
{
const std::unique_ptr<AbstractTreeViewItemDragController> drag_controller =
item.create_drag_controller();
if (!drag_controller) {
return false;
}
WM_event_start_drag(&C,
ICON_NONE,
drag_controller->get_drag_type(),
drag_controller->create_drag_data(),
0,
WM_DRAG_FREE_DATA);
drag_controller->on_drag_start();
return true;
}
static bool can_drop(const AbstractTreeViewItem &item,
const wmDrag &drag,
const char **r_disabled_hint)
{
const std::unique_ptr<AbstractTreeViewItemDropController> drop_controller =
item.create_drop_controller();
if (!drop_controller) {
return false;
}
return drop_controller->can_drop(drag, r_disabled_hint);
}
static std::string drop_tooltip(const AbstractTreeViewItem &item, const wmDrag &drag)
{
const std::unique_ptr<AbstractTreeViewItemDropController> drop_controller =
item.create_drop_controller();
if (!drop_controller) {
return {};
}
return drop_controller->drop_tooltip(drag);
}
static bool drop_handle(bContext &C, const AbstractTreeViewItem &item, const ListBase &drags)
{
std::unique_ptr<AbstractTreeViewItemDropController> drop_controller =
item.create_drop_controller();
const char *disabled_hint_dummy = nullptr;
LISTBASE_FOREACH (const wmDrag *, drag, &drags) {
if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) {
return drop_controller->on_drop(&C, *drag);
}
}
return false;
}
static bool can_rename(const AbstractTreeViewItem &item)
{
const AbstractTreeView &tree_view = item.get_tree_view();
return !tree_view.is_renaming() && item.supports_renaming();
}
};
} // namespace blender::ui
/* ---------------------------------------------------------------------- */
/* C-API */
using namespace blender::ui;
bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle)
@ -725,28 +830,13 @@ bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle,
{
const AbstractTreeViewItem &a = reinterpret_cast<const AbstractTreeViewItem &>(*a_handle);
const AbstractTreeViewItem &b = reinterpret_cast<const AbstractTreeViewItem &>(*b_handle);
/* TODO should match the tree-view as well. */
return a.matches_including_parents(b);
return TreeViewItemAPIWrapper::matches(a, b);
}
bool UI_tree_view_item_drag_start(bContext *C, uiTreeViewItemHandle *item_)
{
const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
const std::unique_ptr<AbstractTreeViewItemDragController> drag_controller =
item.create_drag_controller();
if (!drag_controller) {
return false;
}
WM_event_start_drag(C,
ICON_NONE,
drag_controller->get_drag_type(),
drag_controller->create_drag_data(),
0,
WM_DRAG_FREE_DATA);
drag_controller->on_drag_start();
return true;
return TreeViewItemAPIWrapper::drag_start(*C, item);
}
bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_,
@ -754,50 +844,29 @@ bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_,
const char **r_disabled_hint)
{
const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
const std::unique_ptr<AbstractTreeViewItemDropController> drop_controller =
item.create_drop_controller();
if (!drop_controller) {
return false;
}
return drop_controller->can_drop(*drag, r_disabled_hint);
return TreeViewItemAPIWrapper::can_drop(item, *drag, r_disabled_hint);
}
char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_, const wmDrag *drag)
{
const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
const std::unique_ptr<AbstractTreeViewItemDropController> drop_controller =
item.create_drop_controller();
if (!drop_controller) {
return nullptr;
}
return BLI_strdup(drop_controller->drop_tooltip(*drag).c_str());
const std::string tooltip = TreeViewItemAPIWrapper::drop_tooltip(item, *drag);
return tooltip.empty() ? nullptr : BLI_strdup(tooltip.c_str());
}
bool UI_tree_view_item_drop_handle(struct bContext *C,
uiTreeViewItemHandle *item_,
bool UI_tree_view_item_drop_handle(bContext *C,
const uiTreeViewItemHandle *item_,
const ListBase *drags)
{
AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_);
std::unique_ptr<AbstractTreeViewItemDropController> drop_controller =
item.create_drop_controller();
const char *disabled_hint_dummy = nullptr;
LISTBASE_FOREACH (const wmDrag *, drag, drags) {
if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) {
return drop_controller->on_drop(C, *drag);
}
}
return false;
const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
return TreeViewItemAPIWrapper::drop_handle(*C, item, *drags);
}
bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle)
{
const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle);
const AbstractTreeView &tree_view = item.get_tree_view();
return !tree_view.is_renaming() && item.can_rename();
return TreeViewItemAPIWrapper::can_rename(item);
}
void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle)

View File

@ -76,7 +76,7 @@ class AssetCatalogTreeView : public ui::AbstractTreeView {
void activate_catalog_by_id(CatalogID catalog_id);
private:
ui::BasicTreeViewItem &build_catalog_items_recursive(ui::TreeViewItemContainer &view_parent_item,
ui::BasicTreeViewItem &build_catalog_items_recursive(ui::TreeViewOrItem &view_parent_item,
AssetCatalogTreeItem &catalog);
AssetCatalogTreeViewAllItem &add_all_item();
@ -98,7 +98,7 @@ class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem {
void build_row(uiLayout &row) override;
void build_context_menu(bContext &C, uiLayout &column) const override;
bool can_rename() const override;
bool supports_renaming() const override;
bool rename(StringRefNull new_name) override;
/** Add drag support for catalog items. */
@ -211,7 +211,7 @@ void AssetCatalogTreeView::build_tree()
}
ui::BasicTreeViewItem &AssetCatalogTreeView::build_catalog_items_recursive(
ui::TreeViewItemContainer &view_parent_item, AssetCatalogTreeItem &catalog)
ui::TreeViewOrItem &view_parent_item, AssetCatalogTreeItem &catalog)
{
ui::BasicTreeViewItem &view_item = view_parent_item.add_tree_item<AssetCatalogTreeViewItem>(
&catalog);
@ -333,7 +333,7 @@ void AssetCatalogTreeViewItem::build_context_menu(bContext &C, uiLayout &column)
UI_menutype_draw(&C, mt, &column);
}
bool AssetCatalogTreeViewItem::can_rename() const
bool AssetCatalogTreeViewItem::supports_renaming() const
{
return true;
}