UI: Add context menu support for tree-view items

Tree-view items can now easily define their own context menu. This works
by overriding the `ui::AbstractTreeViewItem::build_context_menu()`
function. See the documentation:
https://wiki.blender.org/wiki/Source/Interface/Views#Context_Menus

Consistently with the Outliner and File Browser, the right-clicked item
also gets activated. This makes sure the correct context is set for the
operators and makes it clear to the user which item is operated on.

An operator to rename the active item is also added, which is something
you'd typically want to put in the context menu as well.
This commit is contained in:
Julian Eisel 2021-10-08 19:56:24 +02:00
parent 7bd0de9240
commit 17c928e975
9 changed files with 109 additions and 0 deletions

View File

@ -2783,7 +2783,12 @@ char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item,
bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle);
void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle);
void UI_tree_view_item_context_menu_build(struct bContext *C,
const uiTreeViewItemHandle *item,
uiLayout *column);
uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *region, int x, int y);
uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const struct ARegion *region);
#ifdef __cplusplus
}

View File

@ -234,6 +234,7 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
virtual ~AbstractTreeViewItem() = default;
virtual void build_row(uiLayout &row) = 0;
virtual void build_context_menu(bContext &C, uiLayout &column) const;
virtual void on_activate();
/**

View File

@ -925,6 +925,18 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *ev
}
}
{
const ARegion *region = CTX_wm_region(C);
uiButTreeRow *treerow_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(
region, event->x, event->y);
if (treerow_but) {
BLI_assert(treerow_but->but.type == UI_BTYPE_TREEROW);
UI_tree_view_item_context_menu_build(
C, treerow_but->tree_item, uiLayoutColumn(layout, false));
uiItemS(layout);
}
}
/* If the button represents an id, it can set the "id" context pointer. */
if (U.experimental.use_extended_asset_browser && ED_asset_can_mark_single_from_context(C)) {
ID *id = CTX_data_pointer_get_type(C, "id", &RNA_ID).data;

View File

@ -7961,6 +7961,14 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
/* handle menu */
if ((event->type == RIGHTMOUSE) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) &&
(event->val == KM_PRESS)) {
/* For some button types that are typically representing entire sets of data, right-clicking
* to spawn the context menu should also activate the item. This makes it clear which item
* will be operated on.
* Apply the button immediately, so context menu polls get the right active item. */
if (ELEM(but->type, UI_BTYPE_TREEROW)) {
ui_apply_but(C, but->block, but, but->active, true);
}
/* RMB has two options now */
if (ui_popup_context_menu_for_button(C, but, event)) {
return WM_UI_HANDLER_BREAK;

View File

@ -1177,6 +1177,7 @@ uiBut *ui_list_row_find_from_index(const struct ARegion *region,
const int index,
uiBut *listbox) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int x, const int y);
uiBut *ui_tree_row_find_active(const struct ARegion *region);
typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata);
uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region,

View File

@ -1962,6 +1962,49 @@ static void UI_OT_tree_view_drop(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI Tree-View Item Rename Operator
*
* General purpose renaming operator for tree-views. Thanks to this, to add a rename button to
* context menus for example, tree-view API users don't have to implement own renaming operators
* with the same logic as they already have for their #ui::AbstractTreeViewItem::rename() override.
*
* \{ */
static bool ui_tree_view_item_rename_poll(bContext *C)
{
const ARegion *region = CTX_wm_region(C);
const uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region);
return active_item != NULL && UI_tree_view_item_can_rename(active_item);
}
static int ui_tree_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op))
{
ARegion *region = CTX_wm_region(C);
uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region);
UI_tree_view_item_begin_rename(active_item);
ED_region_tag_redraw(region);
return OPERATOR_FINISHED;
}
static void UI_OT_tree_view_item_rename(wmOperatorType *ot)
{
ot->name = "Rename Tree-View Item";
ot->idname = "UI_OT_tree_view_item_rename";
ot->description = "Rename the active item in the tree";
ot->exec = ui_tree_view_item_rename_exec;
ot->poll = ui_tree_view_item_rename_poll;
/* Could get a custom tooltip via the `get_description()` callback and another overridable
* function of the tree-view. */
ot->flag = OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operator & Keymap Registration
* \{ */
@ -1990,6 +2033,7 @@ void ED_operatortypes_ui(void)
WM_operatortype_append(UI_OT_list_start_filter);
WM_operatortype_append(UI_OT_tree_view_drop);
WM_operatortype_append(UI_OT_tree_view_item_rename);
/* external */
WM_operatortype_append(UI_OT_eyedropper_color);

View File

@ -473,6 +473,21 @@ uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int x, const int
return ui_but_find_mouse_over_ex(region, x, y, false, ui_but_is_treerow, NULL);
}
static bool ui_but_is_active_treerow(const uiBut *but, const void *customdata)
{
if (!ui_but_is_treerow(but, customdata)) {
return false;
}
const uiButTreeRow *treerow_but = (const uiButTreeRow *)but;
return UI_tree_view_item_is_active(treerow_but->tree_item);
}
uiBut *ui_tree_row_find_active(const ARegion *region)
{
return ui_but_find(region, ui_but_is_active_treerow, NULL);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -94,6 +94,16 @@ uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region,
return tree_row_but->tree_item;
}
uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const ARegion *region)
{
uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_active(region);
if (!tree_row_but) {
return nullptr;
}
return tree_row_but->tree_item;
}
static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractTreeView &view)
{
/* First get the idname the of the view we're looking for. */

View File

@ -369,6 +369,11 @@ bool AbstractTreeViewItem::rename(StringRefNull new_name)
return true;
}
void AbstractTreeViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*/) const
{
/* No context menu by default. */
}
void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old)
{
is_open_ = old.is_open_;
@ -707,3 +712,11 @@ void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle)
AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_handle);
item.begin_renaming();
}
void UI_tree_view_item_context_menu_build(bContext *C,
const uiTreeViewItemHandle *item_handle,
uiLayout *column)
{
const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle);
item.build_context_menu(*C, *column);
}