UI: Support easy dropping into/onto rows in new tree-view API

Adds an easy way to add drop support for tree-view rows.

Most of the work is handled by the tree-view UI code. The tree items can
simply override a few functions (`can_drop()`, `on_drop()`,
`drop_tooltip()`) to implement their custom drop behavior.

While dragging over a tree-view item that can be dropped into/onto, the
item can show a custom and dynamic tooltip explaining what's gonna
happen on drop.

This isn't used yet, but will soon be for asset catalogs.

See documentation here:
https://wiki.blender.org/wiki/Source/Interface/Views#Further_Customizations
This commit is contained in:
Julian Eisel 2021-09-30 16:26:56 +02:00
parent 42ce88f15c
commit 4ee2d9df42
Notes: blender-bot 2023-02-13 11:53:01 +01:00
Referenced by commit c4dca65228, Asset Browser: Support dragging assets into catalogs
11 changed files with 222 additions and 3 deletions

View File

@ -2593,6 +2593,7 @@ typedef struct uiDragColorHandle {
void ED_operatortypes_ui(void);
void ED_keymap_ui(struct wmKeyConfig *keyconf);
void ED_dropboxes_ui(void);
void ED_uilisttypes_ui(void);
void UI_drop_color_copy(struct wmDrag *drag, struct wmDropBox *drop);
@ -2763,6 +2764,14 @@ void UI_interface_tag_script_reload(void);
bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item);
bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a, const uiTreeViewItemHandle *b);
bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const struct wmDrag *drag);
bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const struct ListBase *drags);
char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item,
const struct bContext *C,
const struct wmDrag *drag,
const struct wmEvent *event);
uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *region, int x, int y);
#ifdef __cplusplus
}

View File

@ -29,11 +29,14 @@
#include "UI_resources.h"
struct bContext;
struct PointerRNA;
struct uiBlock;
struct uiBut;
struct uiButTreeRow;
struct uiLayout;
struct wmEvent;
struct wmDrag;
namespace blender::ui {
@ -185,10 +188,19 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
virtual void build_row(uiLayout &row) = 0;
virtual void on_activate();
virtual bool on_drop(const wmDrag &drag);
virtual bool can_drop(const wmDrag &drag) const;
/** Custom text to display when dragging over a tree item. Should explain what happens when
* dropping the data onto this item. Will only be used if #AbstractTreeViewItem::can_drop()
* returns true, so the implementing override doesn't have to check that again.
* The returned value must be a translated string. */
virtual std::string drop_tooltip(const bContext &C,
const wmDrag &drag,
const wmEvent &event) 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. */
/** 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. This is critical for
* being able to recognize an item from a previous redraw, to be able to keep its state (e.g.

View File

@ -42,6 +42,7 @@ set(SRC
interface_button_group.c
interface_context_menu.c
interface_draw.c
interface_dropboxes.cc
interface_eyedropper.c
interface_eyedropper_color.c
interface_eyedropper_colorband.c

View File

@ -0,0 +1,66 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup edinterface
*/
#include "BKE_context.h"
#include "DNA_space_types.h"
#include "WM_api.h"
#include "UI_interface.h"
static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
{
const ARegion *region = CTX_wm_region(C);
const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
region, event->x, event->y);
if (!hovered_tree_item) {
return false;
}
return UI_tree_view_item_can_drop(hovered_tree_item, drag);
}
static char *ui_tree_view_drop_tooltip(bContext *C,
wmDrag *drag,
const wmEvent *event,
wmDropBox *UNUSED(drop))
{
const ARegion *region = CTX_wm_region(C);
const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
region, event->x, event->y);
if (!hovered_tree_item) {
return nullptr;
}
return UI_tree_view_item_drop_tooltip(hovered_tree_item, C, drag, event);
}
void ED_dropboxes_ui()
{
ListBase *lb = WM_dropboxmap_find("User Interface", SPACE_EMPTY, 0);
WM_dropbox_add(lb,
"UI_OT_tree_view_drop",
ui_tree_view_drop_poll,
nullptr,
nullptr,
ui_tree_view_drop_tooltip);
}

View File

@ -1171,6 +1171,7 @@ uiBut *ui_list_row_find_mouse_over(const struct ARegion *region,
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);
typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata);
uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region,

View File

@ -1917,6 +1917,51 @@ static void UI_OT_list_start_filter(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI Tree-View Drop Operator
* \{ */
static bool ui_tree_view_drop_poll(bContext *C)
{
const wmWindow *win = CTX_wm_window(C);
const ARegion *region = CTX_wm_region(C);
const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
region, win->eventstate->x, win->eventstate->y);
return hovered_tree_item != NULL;
}
static int ui_tree_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
{
if (event->custom != EVT_DATA_DRAGDROP) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
const ARegion *region = CTX_wm_region(C);
uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
region, event->x, event->y);
if (!UI_tree_view_item_drop_handle(hovered_tree_item, event->customdata)) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
return OPERATOR_FINISHED;
}
static void UI_OT_tree_view_drop(wmOperatorType *ot)
{
ot->name = "Tree View drop";
ot->idname = "UI_OT_tree_view_drop";
ot->description = "Drag and drop items onto a tree item";
ot->invoke = ui_tree_view_drop_invoke;
ot->poll = ui_tree_view_drop_poll;
ot->flag = OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operator & Keymap Registration
* \{ */
@ -1944,6 +1989,8 @@ void ED_operatortypes_ui(void)
WM_operatortype_append(UI_OT_list_start_filter);
WM_operatortype_append(UI_OT_tree_view_drop);
/* external */
WM_operatortype_append(UI_OT_eyedropper_color);
WM_operatortype_append(UI_OT_eyedropper_colorramp);

View File

@ -463,6 +463,16 @@ uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut
return ui_but_find(region, ui_but_is_listrow_at_index, &data);
}
static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata))
{
return but->type == UI_BTYPE_TREEROW;
}
uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int x, const int y)
{
return ui_but_find_mouse_over_ex(region, x, y, false, ui_but_is_treerow, NULL);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -26,6 +26,8 @@
#include <memory>
#include <variant>
#include "DNA_screen_types.h"
#include "BLI_listbase.h"
#include "interface_intern.h"
@ -77,6 +79,21 @@ void ui_block_free_views(uiBlock *block)
}
}
/**
* \param x, y: Coordinate to find a tree-row item at, in window space.
*/
uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region,
const int x,
const int y)
{
uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, x, y);
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

@ -20,6 +20,8 @@
#include "DNA_userdef_types.h"
#include "BLT_translation.h"
#include "interface_intern.h"
#include "UI_interface.h"
@ -139,6 +141,24 @@ void AbstractTreeViewItem::on_activate()
/* Do nothing by default. */
}
bool AbstractTreeViewItem::on_drop(const wmDrag & /*drag*/)
{
/* Do nothing by default. */
return false;
}
bool AbstractTreeViewItem::can_drop(const wmDrag & /*drag*/) const
{
return false;
}
std::string AbstractTreeViewItem::drop_tooltip(const bContext & /*C*/,
const wmDrag & /*drag*/,
const wmEvent & /*event*/) const
{
return TIP_("Drop into/onto tree item");
}
void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old)
{
is_open_ = old.is_open_;
@ -327,3 +347,35 @@ bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle,
const AbstractTreeViewItem &b = reinterpret_cast<const AbstractTreeViewItem &>(*b_handle);
return a.matches(b);
}
bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const wmDrag *drag)
{
const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
return item.can_drop(*drag);
}
char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_,
const bContext *C,
const wmDrag *drag,
const wmEvent *event)
{
const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
return BLI_strdup(item.drop_tooltip(*C, *drag, *event).c_str());
}
/**
* Let a tree-view item handle a drop event.
* \return True if the drop was handled by the tree-view item.
*/
bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const ListBase *drags)
{
AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_);
LISTBASE_FOREACH (const wmDrag *, drag, drags) {
if (item.can_drop(*drag)) {
return item.on_drop(*drag);
}
}
return false;
}

View File

@ -1699,6 +1699,9 @@ static void ed_default_handlers(
wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "User Interface", 0, 0);
WM_event_add_keymap_handler(handlers, keymap);
ListBase *dropboxes = WM_dropboxmap_find("User Interface", 0, 0);
WM_event_add_dropbox_handler(handlers, dropboxes);
/* user interface widgets */
UI_region_handlers_add(handlers);
}

View File

@ -177,6 +177,7 @@ void ED_spacemacros_init(void)
ED_operatormacros_gpencil();
/* Register dropboxes (can use macros). */
ED_dropboxes_ui();
const ListBase *spacetypes = BKE_spacetypes_list();
LISTBASE_FOREACH (const SpaceType *, type, spacetypes) {
if (type->dropboxes) {