UI: Generalize drop target API, support them for UI views #105963

Closed
Julian Eisel wants to merge 8 commits from JulianEisel:temp-ui-view-drop-controller into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
13 changed files with 376 additions and 193 deletions

View File

@ -12,34 +12,44 @@
* - Custom context menus * - Custom context menus
* - Notifier listening * - Notifier listening
* - Drag controllers (dragging view items) * - Drag controllers (dragging view items)
* - Drop controllers (dropping onto/into view items) * - Drop targets (dropping onto/into view items)
*/ */
#pragma once #pragma once
#include <array> #include <array>
#include <memory> #include <memory>
#include <optional>
#include "DNA_defs.h" #include "DNA_defs.h"
#include "DNA_vec_types.h"
#include "BLI_span.hh" #include "BLI_span.hh"
#include "BLI_string_ref.hh" #include "BLI_string_ref.hh"
#include "UI_interface.hh"
struct bContext; struct bContext;
struct uiBlock; struct uiBlock;
struct uiLayout; struct uiLayout;
struct uiViewItemHandle; struct uiViewItemHandle;
struct ViewLink;
struct wmDrag; struct wmDrag;
struct wmNotifier; struct wmNotifier;
namespace blender::ui { namespace blender::ui {
class AbstractViewItem; class AbstractViewItem;
class AbstractViewItemDropController; class AbstractViewItemDropTarget;
class AbstractViewItemDragController; class AbstractViewItemDragController;
/** The view drop target can share logic with the view item drop target for now, so just an alias.
*/
using AbstractViewDropTarget = AbstractViewItemDropTarget;
class AbstractView { class AbstractView {
friend class AbstractViewItem; friend class AbstractViewItem;
friend struct ::ViewLink;
bool is_reconstructed_ = false; bool is_reconstructed_ = false;
/** /**
@ -51,9 +61,21 @@ class AbstractView {
*/ */
std::unique_ptr<std::array<char, MAX_NAME>> rename_buffer_; std::unique_ptr<std::array<char, MAX_NAME>> rename_buffer_;
/* See #get_bounds(). */
std::optional<rcti> bounds_;
public: public:
virtual ~AbstractView() = default; virtual ~AbstractView() = default;
/**
* If a view wants to support dropping data into it, it has to return a drop target here.
* That is an object implementing #AbstractViewDropTarget.
*
* \note This drop target may be requested for each event. The view doesn't keep the drop target
* around currently. So it cannot contain persistent state.
*/
virtual std::unique_ptr<AbstractViewDropTarget> create_drop_target() const;
/** Listen to a notifier, returning true if a redraw is needed. */ /** Listen to a notifier, returning true if a redraw is needed. */
virtual bool listen(const wmNotifier &) const; virtual bool listen(const wmNotifier &) const;
@ -70,6 +92,11 @@ class AbstractView {
void end_renaming(); void end_renaming();
Span<char> get_rename_buffer() const; Span<char> get_rename_buffer() const;
MutableSpan<char> get_rename_buffer(); MutableSpan<char> get_rename_buffer();
/**
* Get the rectangle containing all the view items that are in the layout, in button space.
* Updated as part of #UI_block_end(), before that it's unset.
*/
std::optional<rcti> get_bounds() const;
protected: protected:
AbstractView() = default; AbstractView() = default;
@ -133,13 +160,13 @@ class AbstractViewItem {
*/ */
virtual std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const; virtual std::unique_ptr<AbstractViewItemDragController> create_drag_controller() const;
/** /**
* If an item wants to support dropping data into it, it has to return a drop controller here. * If an item wants to support dropping data into it, it has to return a drop target here.
* That is an object implementing #AbstractViewItemDropController. * That is an object implementing #AbstractViewItemDropTarget.
* *
* \note This drop controller may be requested for each event. The view doesn't keep a drop * \note This drop target may be requested for each event. The view doesn't keep a drop target
* controller around currently. So it can not contain persistent state. * around currently. So it can not contain persistent state.
*/ */
virtual std::unique_ptr<AbstractViewItemDropController> create_drop_controller() const; virtual std::unique_ptr<AbstractViewItemDropTarget> create_drop_target() const;
/** Get the view this item is registered for using #AbstractView::register_item(). */ /** Get the view this item is registered for using #AbstractView::register_item(). */
AbstractView &get_view() const; AbstractView &get_view() const;
@ -200,7 +227,7 @@ template<typename ToType> ToType *AbstractViewItem::from_item_handle(uiViewItemH
* \{ */ * \{ */
/** /**
* Class to enable dragging a view item. An item can return a drop controller for itself by * Class to enable dragging a view item. An item can return a drag controller for itself by
* implementing #AbstractViewItem::create_drag_controller(). * implementing #AbstractViewItem::create_drag_controller().
*/ */
class AbstractViewItemDragController { class AbstractViewItemDragController {
@ -222,38 +249,15 @@ class AbstractViewItemDragController {
/** /**
* Class to define the behavior when dropping something onto/into a view item, plus the behavior * Class to define the behavior when dropping something onto/into a view item, plus the behavior
* when dragging over this item. An item can return a drop controller for itself via a custom * when dragging over this item. An item can return a drop target for itself via a custom
* implementation of #AbstractViewItem::create_drop_controller(). * implementation of #AbstractViewItem::create_drop_target().
*/ */
class AbstractViewItemDropController { class AbstractViewItemDropTarget : public DropTargetInterface {
protected: protected:
AbstractView &view_; AbstractView &view_;
public: public:
AbstractViewItemDropController(AbstractView &view); AbstractViewItemDropTarget(AbstractView &view);
virtual ~AbstractViewItemDropController() = default;
/**
* Check if the data dragged with \a drag can be dropped on the item this controller is for.
* \param r_disabled_hint: Return a static string to display to the user, explaining why dropping
* isn't possible on this item. Shouldn't be done too aggressively, e.g.
* don't set this if the drag-type can't be dropped here; only if it can
* but there's another reason it can't be dropped.
* Can assume this is a non-null pointer.
*/
virtual bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const = 0;
/**
* Custom text to display when dragging over a view item. Should explain what happens when
* dropping the data onto this item. Will only be used if #AbstractViewItem::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 wmDrag &drag) const = 0;
/**
* Execute the logic to apply a drop of the data dragged with \a drag onto/into the item this
* controller is for.
*/
virtual bool on_drop(struct bContext *C, const wmDrag &drag) = 0;
/** Request the view the item is registered for as type #ViewType. Throws a `std::bad_cast` /** Request the view the item is registered for as type #ViewType. Throws a `std::bad_cast`
* exception if the view is not of the requested type. */ * exception if the view is not of the requested type. */
@ -267,7 +271,7 @@ template<class ViewType> ViewType &AbstractViewItemDragController::get_view() co
return dynamic_cast<ViewType &>(view_); return dynamic_cast<ViewType &>(view_);
} }
template<class ViewType> ViewType &AbstractViewItemDropController::get_view() const template<class ViewType> ViewType &AbstractViewItemDropTarget::get_view() const
{ {
static_assert(std::is_base_of<AbstractView, ViewType>::value, static_assert(std::is_base_of<AbstractView, ViewType>::value,
"Type must derive from and implement the ui::AbstractView interface"); "Type must derive from and implement the ui::AbstractView interface");

View File

@ -3274,18 +3274,12 @@ void UI_view_item_context_menu_build(struct bContext *C,
* \return True if dragging started successfully, otherwise false. * \return True if dragging started successfully, otherwise false.
*/ */
bool UI_view_item_drag_start(struct bContext *C, const uiViewItemHandle *item_); bool UI_view_item_drag_start(struct bContext *C, const uiViewItemHandle *item_);
bool UI_view_item_can_drop(const uiViewItemHandle *item_,
const struct wmDrag *drag,
const char **r_disabled_hint);
char *UI_view_item_drop_tooltip(const uiViewItemHandle *item, const struct wmDrag *drag);
/**
* Let a view item handle a drop event.
* \return True if the drop was handled by the view item.
*/
bool UI_view_item_drop_handle(struct bContext *C,
const uiViewItemHandle *item_,
const struct ListBase *drags);
/**
* \param xy: Coordinate to find a view item at, in window space.
* \param pad: Extra padding added to the bounding box of the view.
*/
uiViewHandle *UI_region_view_find_at(const struct ARegion *region, const int xy[2], int pad);
/** /**
* \param xy: Coordinate to find a view item at, in window space. * \param xy: Coordinate to find a view item at, in window space.
*/ */

View File

@ -18,11 +18,17 @@ namespace blender::nodes::geo_eval_log {
struct GeometryAttributeInfo; struct GeometryAttributeInfo;
} }
struct ARegion;
struct bContext;
struct PointerRNA; struct PointerRNA;
struct StructRNA; struct StructRNA;
struct uiBlock; struct uiBlock;
struct uiLayout;
struct uiList; struct uiList;
struct uiSearchItems; struct uiSearchItems;
struct uiViewHandle;
struct uiViewItemHandle;
struct wmDrag;
namespace blender::ui { namespace blender::ui {
@ -54,6 +60,67 @@ void attribute_search_add_items(StringRefNull str,
uiSearchItems *items, uiSearchItems *items,
bool is_first); bool is_first);
/**
* This provides a common interface for UI elements that want to support dragging & dropping
* entities into/onto them. With it, the element can determine if the dragged entity can be dropped
JulianEisel marked this conversation as resolved Outdated

This sort of "would probably be nice to use this more in the future" comment shouldn't be added to main I think. That makes sense in code documentation or design tasks, but the code should stand for itself generally, and this comment will just become out of date otherwise.

This sort of "would probably be nice to use this more in the future" comment shouldn't be added to main I think. That makes sense in code documentation or design tasks, but the code should stand for itself generally, and this comment will just become out of date otherwise.
* onto itself, provide feedback while dragging and run custom code for the dropping.
*
* Note that this is just an interface. A #wmDropBox is needed to request instances of it from a UI
* element and call its functions. For example the drop box using "UI_OT_view_drop" implements
* dropping for views and view items via this interface. To support other kinds of UI elements,
* similar drop boxes would be necessary.
*/
class DropTargetInterface {
public:
DropTargetInterface() = default;
virtual ~DropTargetInterface() = default;
/**
* Check if the data dragged with \a drag can be dropped on the element this drop target is for.
* \param r_disabled_hint: Return a static string to display to the user, explaining why dropping
* isn't possible on this UI element. Shouldn't be done too aggressively,
* e.g. don't set this if the drag-type can't be dropped here; only if it
* can but there's another reason it can't be dropped. Can assume this is
* a non-null pointer.
*/
virtual bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const = 0;
/**
* Custom text to display when dragging over the element using this drop target. Should
* explain what happens when dropping the data onto this UI element. Will only be used if
* #DropTargetInterface::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 wmDrag &drag) const = 0;
/**
* Execute the logic to apply a drop of the data dragged with \a drag onto/into the UI element
* this drop target is for.
*/
virtual bool on_drop(bContext *C, const wmDrag &drag) const = 0;
};
/**
* Let a drop target handle a drop event.
* \return True if the dropping was successful.
*/
bool drop_target_apply_drop(bContext &C,
const DropTargetInterface &drop_target,
const ListBase &drags);
/**
* Call #DropTargetInterface::drop_tooltip() and return the result as newly allocated C string
* (unless the result is empty, returns null then). Needs freeing with MEM_freeN().
*/
char *drop_target_tooltip(const DropTargetInterface &drop_target, const wmDrag &drag);
std::unique_ptr<DropTargetInterface> view_drop_target(const uiViewHandle *view_handle);
std::unique_ptr<DropTargetInterface> view_item_drop_target(const uiViewItemHandle *item_handle);
/**
* Try to find a view item with a drop target under the mouse cursor, or if not found, a view
* with a drop target.
* \param xy: Coordinate to find a drop target at, in window space.
*/
std::unique_ptr<DropTargetInterface> region_views_find_drop_target_at(const ARegion *region,
const int xy[2]);
} // namespace blender::ui } // namespace blender::ui
enum eUIListFilterResult { enum eUIListFilterResult {

View File

@ -46,6 +46,7 @@ set(SRC
interface_context_path.cc interface_context_path.cc
interface_drag.cc interface_drag.cc
interface_draw.cc interface_draw.cc
interface_drop.cc
interface_dropboxes.cc interface_dropboxes.cc
interface_handlers.cc interface_handlers.cc
interface_icons.cc interface_icons.cc

View File

@ -2014,6 +2014,8 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x
break; break;
} }
ui_block_views_bounds_calc(block);
if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) { if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) {
UI_block_bounds_set_normal(block, 0); UI_block_bounds_set_normal(block, 0);
} }

View File

@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edinterface
*/
#include "UI_interface.hh"
namespace blender::ui {
bool drop_target_apply_drop(bContext &C,
const DropTargetInterface &drop_target,
const ListBase &drags)
{
const char *disabled_hint_dummy = nullptr;
LISTBASE_FOREACH (const wmDrag *, drag, &drags) {
if (drop_target.can_drop(*drag, &disabled_hint_dummy)) {
return drop_target.on_drop(&C, *drag);
}
}
return false;
}
char *drop_target_tooltip(const DropTargetInterface &drop_target, const wmDrag &drag)
{
const std::string tooltip = drop_target.drop_tooltip(drag);
return tooltip.empty() ? nullptr : BLI_strdup(tooltip.c_str());
}
} // namespace blender::ui

View File

@ -20,6 +20,9 @@
#include "WM_api.h" #include "WM_api.h"
#include "UI_interface.h" #include "UI_interface.h"
#include "UI_interface.hh"
using namespace blender::ui;
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/** \name View Drag/Drop Callbacks /** \name View Drag/Drop Callbacks
@ -28,28 +31,28 @@
static bool ui_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) static bool ui_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
{ {
const ARegion *region = CTX_wm_region(C); const ARegion *region = CTX_wm_region(C);
const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, event->xy);
if (!hovered_item) { std::unique_ptr<DropTargetInterface> drop_target = region_views_find_drop_target_at(
region, event->xy);
if (!drop_target) {
return false; return false;
} }
if (drag->drop_state.free_disabled_info) { if (drag->drop_state.free_disabled_info) {
MEM_SAFE_FREE(drag->drop_state.disabled_info); MEM_SAFE_FREE(drag->drop_state.disabled_info);
} }
drag->drop_state.free_disabled_info = false; drag->drop_state.free_disabled_info = false;
return UI_view_item_can_drop(hovered_item, drag, &drag->drop_state.disabled_info);
return drop_target->can_drop(*drag, &drag->drop_state.disabled_info);
} }
static char *ui_view_drop_tooltip(bContext *C, wmDrag *drag, const int xy[2], wmDropBox * /*drop*/) static char *ui_view_drop_tooltip(bContext *C, wmDrag *drag, const int xy[2], wmDropBox * /*drop*/)
{ {
const ARegion *region = CTX_wm_region(C); const ARegion *region = CTX_wm_region(C);
const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, xy); std::unique_ptr<DropTargetInterface> drop_target = region_views_find_drop_target_at(region,
if (!hovered_item) { xy);
return nullptr;
}
return UI_view_item_drop_tooltip(hovered_item, drag); return drop_target_tooltip(*drop_target, *drag);
} }
/** \} */ /** \} */

View File

@ -1452,6 +1452,7 @@ void ui_interface_tag_script_reload_queries();
/* interface_view.cc */ /* interface_view.cc */
void ui_block_free_views(uiBlock *block); void ui_block_free_views(uiBlock *block);
void ui_block_views_bounds_calc(const uiBlock *block);
void ui_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params); void ui_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params);
uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
const uiViewHandle *new_view); const uiViewHandle *new_view);

View File

@ -47,6 +47,7 @@
#include "RNA_types.h" #include "RNA_types.h"
#include "UI_interface.h" #include "UI_interface.h"
#include "UI_interface.hh"
#include "interface_intern.hh" #include "interface_intern.hh"
@ -65,6 +66,8 @@
#include "ED_screen.h" #include "ED_screen.h"
#include "ED_text.h" #include "ED_text.h"
using namespace blender::ui;
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/** \name Immediate redraw helper /** \name Immediate redraw helper
* *
@ -2351,7 +2354,7 @@ static void UI_OT_list_start_filter(wmOperatorType *ot)
/** \} */ /** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/** \name UI Tree-View Drop Operator /** \name UI View Drop Operator
* \{ */ * \{ */
static bool ui_view_drop_poll(bContext *C) static bool ui_view_drop_poll(bContext *C)
@ -2361,9 +2364,7 @@ static bool ui_view_drop_poll(bContext *C)
if (region == nullptr) { if (region == nullptr) {
return false; return false;
} }
const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, win->eventstate->xy); return region_views_find_drop_target_at(region, win->eventstate->xy) != nullptr;
return hovered_item != nullptr;
} }
static int ui_view_drop_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event) static int ui_view_drop_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
@ -2373,10 +2374,11 @@ static int ui_view_drop_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *
} }
const ARegion *region = CTX_wm_region(C); const ARegion *region = CTX_wm_region(C);
uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, event->xy); std::unique_ptr<DropTargetInterface> drop_target = region_views_find_drop_target_at(
region, event->xy);
if (!UI_view_item_drop_handle( if (!drop_target_apply_drop(
C, hovered_item, static_cast<const ListBase *>(event->customdata))) { *C, *drop_target, *static_cast<const ListBase *>(event->customdata))) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
} }
@ -2385,9 +2387,9 @@ static int ui_view_drop_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *
static void UI_OT_view_drop(wmOperatorType *ot) static void UI_OT_view_drop(wmOperatorType *ot)
{ {
ot->name = "View drop"; ot->name = "View Drop";
ot->idname = "UI_OT_view_drop"; ot->idname = "UI_OT_view_drop";
ot->description = "Drag and drop items onto a data-set item"; ot->description = "Drag and drop onto a data-set or item within the data-set";
ot->invoke = ui_view_drop_invoke; ot->invoke = ui_view_drop_invoke;
ot->poll = ui_view_drop_poll; ot->poll = ui_view_drop_poll;

View File

@ -62,6 +62,12 @@ void AbstractView::update_from_old(uiBlock &new_block)
/** \name Default implementations of virtual functions /** \name Default implementations of virtual functions
* \{ */ * \{ */
std::unique_ptr<AbstractViewDropTarget> AbstractView::create_drop_target() const
{
/* There's no drop target (and hence no drop support) by default. */
return nullptr;
}
bool AbstractView::listen(const wmNotifier & /*notifier*/) const bool AbstractView::listen(const wmNotifier & /*notifier*/) const
{ {
/* Nothing by default. */ /* Nothing by default. */
@ -104,6 +110,23 @@ MutableSpan<char> AbstractView::get_rename_buffer()
return *rename_buffer_; return *rename_buffer_;
} }
std::optional<rcti> AbstractView::get_bounds() const
{
return bounds_;
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name General API functions
* \{ */
std::unique_ptr<DropTargetInterface> view_drop_target(const uiViewHandle *view_handle)
{
const AbstractView &view = reinterpret_cast<const AbstractView &>(*view_handle);
return view.create_drop_target();
}
/** \} */ /** \} */
} // namespace blender::ui } // namespace blender::ui

View File

@ -174,9 +174,9 @@ std::unique_ptr<AbstractViewItemDragController> AbstractViewItem::create_drag_co
return nullptr; return nullptr;
} }
std::unique_ptr<AbstractViewItemDropController> AbstractViewItem::create_drop_controller() const std::unique_ptr<AbstractViewItemDropTarget> AbstractViewItem::create_drop_target() const
{ {
/* There's no drop controller (and hence no drop support) by default. */ /* There's no drop target (and hence no drop support) by default. */
return nullptr; return nullptr;
} }
@ -189,7 +189,7 @@ void AbstractViewItemDragController::on_drag_start()
/* Do nothing by default. */ /* Do nothing by default. */
} }
AbstractViewItemDropController::AbstractViewItemDropController(AbstractView &view) : view_(view) AbstractViewItemDropTarget::AbstractViewItemDropTarget(AbstractView &view) : view_(view)
{ {
} }
@ -217,6 +217,18 @@ bool AbstractViewItem::is_active() const
/** \} */ /** \} */
/* ---------------------------------------------------------------------- */
/** \name General API functions
* \{ */
std::unique_ptr<DropTargetInterface> view_item_drop_target(const uiViewItemHandle *item_handle)
{
const AbstractViewItem &item = reinterpret_cast<const AbstractViewItem &>(*item_handle);
return item.create_drop_target();
}
/** \} */
} // namespace blender::ui } // namespace blender::ui
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
@ -264,45 +276,6 @@ class ViewItemAPIWrapper {
return true; return true;
} }
static bool can_drop(const AbstractViewItem &item,
const wmDrag &drag,
const char **r_disabled_hint)
{
const std::unique_ptr<AbstractViewItemDropController> 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 AbstractViewItem &item, const wmDrag &drag)
{
const std::unique_ptr<AbstractViewItemDropController> drop_controller =
item.create_drop_controller();
if (!drop_controller) {
return {};
}
return drop_controller->drop_tooltip(drag);
}
static bool drop_handle(bContext &C, const AbstractViewItem &item, const ListBase &drags)
{
std::unique_ptr<AbstractViewItemDropController> 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;
}
}; };
} // namespace blender::ui } // namespace blender::ui
@ -348,26 +321,4 @@ bool UI_view_item_drag_start(bContext *C, const uiViewItemHandle *item_)
return ViewItemAPIWrapper::drag_start(*C, item); return ViewItemAPIWrapper::drag_start(*C, item);
} }
bool UI_view_item_can_drop(const uiViewItemHandle *item_,
const wmDrag *drag,
const char **r_disabled_hint)
{
const AbstractViewItem &item = reinterpret_cast<const AbstractViewItem &>(*item_);
return ViewItemAPIWrapper::can_drop(item, *drag, r_disabled_hint);
}
char *UI_view_item_drop_tooltip(const uiViewItemHandle *item_, const wmDrag *drag)
{
const AbstractViewItem &item = reinterpret_cast<const AbstractViewItem &>(*item_);
const std::string tooltip = ViewItemAPIWrapper::drop_tooltip(item, *drag);
return tooltip.empty() ? nullptr : BLI_strdup(tooltip.c_str());
}
bool UI_view_item_drop_handle(bContext *C, const uiViewItemHandle *item_, const ListBase *drags)
{
const AbstractViewItem &item = reinterpret_cast<const AbstractViewItem &>(*item_);
return ViewItemAPIWrapper::drop_handle(*C, item, *drags);
}
/** \} */ /** \} */

View File

@ -24,6 +24,7 @@
#include "BKE_screen.h" #include "BKE_screen.h"
#include "BLI_listbase.h" #include "BLI_listbase.h"
#include "BLI_map.hh"
#include "ED_screen.h" #include "ED_screen.h"
@ -44,6 +45,8 @@ using namespace blender::ui;
struct ViewLink : public Link { struct ViewLink : public Link {
std::string idname; std::string idname;
std::unique_ptr<AbstractView> view; std::unique_ptr<AbstractView> view;
static void views_bounds_calc(const uiBlock &block);
}; };
template<class T> template<class T>
@ -81,6 +84,51 @@ void ui_block_free_views(uiBlock *block)
} }
} }
void ViewLink::views_bounds_calc(const uiBlock &block)
{
Map<AbstractView *, rcti> views_bounds;
rcti minmax;
BLI_rcti_init_minmax(&minmax);
LISTBASE_FOREACH (ViewLink *, link, &block.views) {
views_bounds.add(link->view.get(), minmax);
}
LISTBASE_FOREACH (uiBut *, but, &block.buttons) {
if (but->type != UI_BTYPE_VIEW_ITEM) {
continue;
}
uiButViewItem *view_item_but = static_cast<uiButViewItem *>(but);
if (!view_item_but->view_item) {
continue;
}
/* Get the view from the button. */
AbstractViewItem &view_item = reinterpret_cast<AbstractViewItem &>(*view_item_but->view_item);
AbstractView &view = view_item.get_view();
rcti &bounds = views_bounds.lookup(&view);
rcti but_rcti{};
BLI_rcti_rctf_copy_round(&but_rcti, &view_item_but->rect);
BLI_rcti_do_minmax_rcti(&bounds, &but_rcti);
}
for (const auto item : views_bounds.items()) {
const rcti &bounds = item.value;
if (BLI_rcti_is_empty(&bounds)) {
continue;
}
AbstractView &view = *item.key;
view.bounds_ = bounds;
}
}
void ui_block_views_bounds_calc(const uiBlock *block)
{
ViewLink::views_bounds_calc(*block);
}
void ui_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params) void ui_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params)
{ {
ARegion *region = listener_params->region; ARegion *region = listener_params->region;
@ -92,6 +140,35 @@ void ui_block_views_listen(const uiBlock *block, const wmRegionListenerParams *l
} }
} }
/* Similar to #ui_but_find_mouse_over_ex(). */
uiViewHandle *UI_region_view_find_at(const ARegion *region, const int xy[2], const int pad)
{
if (!ui_region_contains_point_px(region, xy)) {
return nullptr;
}
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
float mx = xy[0], my = xy[1];
ui_window_to_block_fl(region, block, &mx, &my);
LISTBASE_FOREACH (ViewLink *, view_link, &block->views) {
std::optional<rcti> bounds = view_link->view->get_bounds();
if (!bounds) {
continue;
}
rcti padded_bounds = *bounds;
if (pad) {
BLI_rcti_pad(&padded_bounds, pad, pad);
}
if (BLI_rcti_isect_pt(&padded_bounds, mx, my)) {
return reinterpret_cast<uiViewHandle *>(view_link->view.get());
}
}
}
return nullptr;
}
uiViewItemHandle *UI_region_views_find_item_at(const ARegion *region, const int xy[2]) uiViewItemHandle *UI_region_views_find_item_at(const ARegion *region, const int xy[2])
{ {
uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_mouse_over(region, xy); uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_mouse_over(region, xy);
@ -112,6 +189,34 @@ uiViewItemHandle *UI_region_views_find_active_item(const ARegion *region)
return item_but->view_item; return item_but->view_item;
} }
namespace blender::ui {
std::unique_ptr<DropTargetInterface> region_views_find_drop_target_at(const ARegion *region,
const int xy[2])
{
const uiViewItemHandle *hovered_view_item = UI_region_views_find_item_at(region, xy);
if (hovered_view_item) {
std::unique_ptr<DropTargetInterface> drop_target = view_item_drop_target(hovered_view_item);
if (drop_target) {
return drop_target;
}
}
/* Get style for some sensible padding around the view items. */
const uiStyle *style = UI_style_get_dpi();
const uiViewHandle *hovered_view = UI_region_view_find_at(region, xy, style->buttonspacex);
if (hovered_view) {
std::unique_ptr<DropTargetInterface> drop_target = view_drop_target(hovered_view);
if (drop_target) {
return drop_target;
}
}
return nullptr;
}
} // namespace blender::ui
static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractView &view) static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractView &view)
{ {
/* First get the idname the of the view we're looking for. */ /* First get the idname the of the view we're looking for. */

View File

@ -49,7 +49,7 @@ class AssetCatalogTreeView : public ui::AbstractTreeView {
SpaceFile &space_file_; SpaceFile &space_file_;
friend class AssetCatalogTreeViewItem; friend class AssetCatalogTreeViewItem;
friend class AssetCatalogDropController; friend class AssetCatalogDropTarget;
friend class AssetCatalogTreeViewAllItem; friend class AssetCatalogTreeViewAllItem;
public: public:
@ -90,7 +90,7 @@ class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem {
/** Add drag support for catalog items. */ /** Add drag support for catalog items. */
std::unique_ptr<ui::AbstractViewItemDragController> create_drag_controller() const override; std::unique_ptr<ui::AbstractViewItemDragController> create_drag_controller() const override;
/** Add dropping support for catalog items. */ /** Add dropping support for catalog items. */
std::unique_ptr<ui::AbstractViewItemDropController> create_drop_controller() const override; std::unique_ptr<ui::AbstractViewItemDropTarget> create_drop_target() const override;
}; };
class AssetCatalogDragController : public ui::AbstractViewItemDragController { class AssetCatalogDragController : public ui::AbstractViewItemDragController {
@ -105,15 +105,15 @@ class AssetCatalogDragController : public ui::AbstractViewItemDragController {
void on_drag_start() override; void on_drag_start() override;
}; };
class AssetCatalogDropController : public ui::AbstractViewItemDropController { class AssetCatalogDropTarget : public ui::AbstractViewItemDropTarget {
AssetCatalogTreeItem &catalog_item_; AssetCatalogTreeItem &catalog_item_;
public: public:
AssetCatalogDropController(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item); AssetCatalogDropTarget(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item);
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override;
std::string drop_tooltip(const wmDrag &drag) const override; std::string drop_tooltip(const wmDrag &drag) const override;
bool on_drop(struct bContext *C, const wmDrag &drag) override; bool on_drop(struct bContext *C, const wmDrag &drag) const override;
::AssetLibrary &get_asset_library() const; ::AssetLibrary &get_asset_library() const;
@ -146,29 +146,29 @@ class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem {
void build_row(uiLayout &row) override; void build_row(uiLayout &row) override;
struct DropController : public ui::AbstractViewItemDropController { struct DropTarget : public ui::AbstractViewItemDropTarget {
DropController(AssetCatalogTreeView &tree_view); DropTarget(AssetCatalogTreeView &tree_view);
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override;
std::string drop_tooltip(const wmDrag &drag) const override; std::string drop_tooltip(const wmDrag &drag) const override;
bool on_drop(struct bContext *C, const wmDrag &drag) override; bool on_drop(struct bContext *C, const wmDrag &drag) const override;
}; };
std::unique_ptr<ui::AbstractViewItemDropController> create_drop_controller() const override; std::unique_ptr<ui::AbstractViewItemDropTarget> create_drop_target() const override;
}; };
class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem {
using BasicTreeViewItem::BasicTreeViewItem; using BasicTreeViewItem::BasicTreeViewItem;
struct DropController : public ui::AbstractViewItemDropController { struct DropTarget : public ui::AbstractViewItemDropTarget {
DropController(AssetCatalogTreeView &tree_view); DropTarget(AssetCatalogTreeView &tree_view);
bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override;
std::string drop_tooltip(const wmDrag &drag) const override; std::string drop_tooltip(const wmDrag &drag) const override;
bool on_drop(struct bContext *C, const wmDrag &drag) override; bool on_drop(struct bContext *C, const wmDrag &drag) const override;
}; };
std::unique_ptr<ui::AbstractViewItemDropController> create_drop_controller() const override; std::unique_ptr<ui::AbstractViewItemDropTarget> create_drop_target() const override;
}; };
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
@ -339,10 +339,10 @@ bool AssetCatalogTreeViewItem::rename(StringRefNull new_name)
return true; return true;
} }
std::unique_ptr<ui::AbstractViewItemDropController> AssetCatalogTreeViewItem:: std::unique_ptr<ui::AbstractViewItemDropTarget> AssetCatalogTreeViewItem::create_drop_target()
create_drop_controller() const const
{ {
return std::make_unique<AssetCatalogDropController>( return std::make_unique<AssetCatalogDropTarget>(
static_cast<AssetCatalogTreeView &>(get_tree_view()), catalog_item_); static_cast<AssetCatalogTreeView &>(get_tree_view()), catalog_item_);
} }
@ -355,13 +355,13 @@ std::unique_ptr<ui::AbstractViewItemDragController> AssetCatalogTreeViewItem::
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
AssetCatalogDropController::AssetCatalogDropController(AssetCatalogTreeView &tree_view, AssetCatalogDropTarget::AssetCatalogDropTarget(AssetCatalogTreeView &tree_view,
AssetCatalogTreeItem &catalog_item) AssetCatalogTreeItem &catalog_item)
: ui::AbstractViewItemDropController(tree_view), catalog_item_(catalog_item) : ui::AbstractViewItemDropTarget(tree_view), catalog_item_(catalog_item)
{ {
} }
bool AssetCatalogDropController::can_drop(const wmDrag &drag, const char **r_disabled_hint) const bool AssetCatalogDropTarget::can_drop(const wmDrag &drag, const char **r_disabled_hint) const
{ {
if (drag.type == WM_DRAG_ASSET_CATALOG) { if (drag.type == WM_DRAG_ASSET_CATALOG) {
const ::AssetLibrary &library = get_asset_library(); const ::AssetLibrary &library = get_asset_library();
@ -389,7 +389,7 @@ bool AssetCatalogDropController::can_drop(const wmDrag &drag, const char **r_dis
return false; return false;
} }
std::string AssetCatalogDropController::drop_tooltip(const wmDrag &drag) const std::string AssetCatalogDropTarget::drop_tooltip(const wmDrag &drag) const
{ {
if (drag.type == WM_DRAG_ASSET_CATALOG) { if (drag.type == WM_DRAG_ASSET_CATALOG) {
return drop_tooltip_asset_catalog(drag); return drop_tooltip_asset_catalog(drag);
@ -397,7 +397,7 @@ std::string AssetCatalogDropController::drop_tooltip(const wmDrag &drag) const
return drop_tooltip_asset_list(drag); return drop_tooltip_asset_list(drag);
} }
std::string AssetCatalogDropController::drop_tooltip_asset_catalog(const wmDrag &drag) const std::string AssetCatalogDropTarget::drop_tooltip_asset_catalog(const wmDrag &drag) const
{ {
BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG);
const AssetCatalog *src_catalog = get_drag_catalog(drag, get_asset_library()); const AssetCatalog *src_catalog = get_drag_catalog(drag, get_asset_library());
@ -406,7 +406,7 @@ std::string AssetCatalogDropController::drop_tooltip_asset_catalog(const wmDrag
TIP_("into") + " '" + catalog_item_.get_name() + "'"; TIP_("into") + " '" + catalog_item_.get_name() + "'";
} }
std::string AssetCatalogDropController::drop_tooltip_asset_list(const wmDrag &drag) const std::string AssetCatalogDropTarget::drop_tooltip_asset_list(const wmDrag &drag) const
{ {
BLI_assert(drag.type == WM_DRAG_ASSET_LIST); BLI_assert(drag.type == WM_DRAG_ASSET_LIST);
@ -429,7 +429,7 @@ std::string AssetCatalogDropController::drop_tooltip_asset_list(const wmDrag &dr
return basic_tip; return basic_tip;
} }
bool AssetCatalogDropController::on_drop(struct bContext *C, const wmDrag &drag) bool AssetCatalogDropTarget::on_drop(struct bContext *C, const wmDrag &drag) const
{ {
if (drag.type == WM_DRAG_ASSET_CATALOG) { if (drag.type == WM_DRAG_ASSET_CATALOG) {
return drop_asset_catalog_into_catalog( return drop_asset_catalog_into_catalog(
@ -442,7 +442,7 @@ bool AssetCatalogDropController::on_drop(struct bContext *C, const wmDrag &drag)
catalog_item_.get_simple_name()); catalog_item_.get_simple_name());
} }
bool AssetCatalogDropController::drop_asset_catalog_into_catalog( bool AssetCatalogDropTarget::drop_asset_catalog_into_catalog(
const wmDrag &drag, const wmDrag &drag,
AssetCatalogTreeView &tree_view, AssetCatalogTreeView &tree_view,
const std::optional<CatalogID> drop_catalog_id) const std::optional<CatalogID> drop_catalog_id)
@ -456,11 +456,11 @@ bool AssetCatalogDropController::drop_asset_catalog_into_catalog(
return true; return true;
} }
bool AssetCatalogDropController::drop_assets_into_catalog(struct bContext *C, bool AssetCatalogDropTarget::drop_assets_into_catalog(struct bContext *C,
const AssetCatalogTreeView &tree_view, const AssetCatalogTreeView &tree_view,
const wmDrag &drag, const wmDrag &drag,
CatalogID catalog_id, CatalogID catalog_id,
StringRefNull simple_name) StringRefNull simple_name)
{ {
BLI_assert(drag.type == WM_DRAG_ASSET_LIST); BLI_assert(drag.type == WM_DRAG_ASSET_LIST);
const ListBase *asset_drags = WM_drag_asset_list_get(&drag); const ListBase *asset_drags = WM_drag_asset_list_get(&drag);
@ -491,8 +491,8 @@ bool AssetCatalogDropController::drop_assets_into_catalog(struct bContext *C,
return true; return true;
} }
AssetCatalog *AssetCatalogDropController::get_drag_catalog(const wmDrag &drag, AssetCatalog *AssetCatalogDropTarget::get_drag_catalog(const wmDrag &drag,
const ::AssetLibrary &asset_library) const ::AssetLibrary &asset_library)
{ {
if (drag.type != WM_DRAG_ASSET_CATALOG) { if (drag.type != WM_DRAG_ASSET_CATALOG) {
return nullptr; return nullptr;
@ -504,8 +504,7 @@ AssetCatalog *AssetCatalogDropController::get_drag_catalog(const wmDrag &drag,
return catalog_service->find_catalog(catalog_drag->drag_catalog_id); return catalog_service->find_catalog(catalog_drag->drag_catalog_id);
} }
bool AssetCatalogDropController::has_droppable_asset(const wmDrag &drag, bool AssetCatalogDropTarget::has_droppable_asset(const wmDrag &drag, const char **r_disabled_hint)
const char **r_disabled_hint)
{ {
const ListBase *asset_drags = WM_drag_asset_list_get(&drag); const ListBase *asset_drags = WM_drag_asset_list_get(&drag);
@ -521,8 +520,8 @@ bool AssetCatalogDropController::has_droppable_asset(const wmDrag &drag,
return false; return false;
} }
bool AssetCatalogDropController::can_modify_catalogs(const ::AssetLibrary &library, bool AssetCatalogDropTarget::can_modify_catalogs(const ::AssetLibrary &library,
const char **r_disabled_hint) const char **r_disabled_hint)
{ {
if (ED_asset_catalogs_read_only(library)) { if (ED_asset_catalogs_read_only(library)) {
*r_disabled_hint = "Catalogs cannot be edited in this asset library"; *r_disabled_hint = "Catalogs cannot be edited in this asset library";
@ -531,7 +530,7 @@ bool AssetCatalogDropController::can_modify_catalogs(const ::AssetLibrary &libra
return true; return true;
} }
::AssetLibrary &AssetCatalogDropController::get_asset_library() const ::AssetLibrary &AssetCatalogDropTarget::get_asset_library() const
{ {
return *get_view<AssetCatalogTreeView>().asset_library_; return *get_view<AssetCatalogTreeView>().asset_library_;
} }
@ -580,30 +579,30 @@ void AssetCatalogTreeViewAllItem::build_row(uiLayout &row)
RNA_string_set(props, "parent_path", nullptr); RNA_string_set(props, "parent_path", nullptr);
} }
std::unique_ptr<ui::AbstractViewItemDropController> AssetCatalogTreeViewAllItem:: std::unique_ptr<ui::AbstractViewItemDropTarget> AssetCatalogTreeViewAllItem::create_drop_target()
create_drop_controller() const const
{ {
return std::make_unique<AssetCatalogTreeViewAllItem::DropController>( return std::make_unique<AssetCatalogTreeViewAllItem::DropTarget>(
static_cast<AssetCatalogTreeView &>(get_tree_view())); static_cast<AssetCatalogTreeView &>(get_tree_view()));
} }
AssetCatalogTreeViewAllItem::DropController::DropController(AssetCatalogTreeView &tree_view) AssetCatalogTreeViewAllItem::DropTarget::DropTarget(AssetCatalogTreeView &tree_view)
: ui::AbstractViewItemDropController(tree_view) : ui::AbstractViewItemDropTarget(tree_view)
{ {
} }
bool AssetCatalogTreeViewAllItem::DropController::can_drop(const wmDrag &drag, bool AssetCatalogTreeViewAllItem::DropTarget::can_drop(const wmDrag &drag,
const char **r_disabled_hint) const const char **r_disabled_hint) const
{ {
if (drag.type != WM_DRAG_ASSET_CATALOG) { if (drag.type != WM_DRAG_ASSET_CATALOG) {
return false; return false;
} }
::AssetLibrary &library = *get_view<AssetCatalogTreeView>().asset_library_; ::AssetLibrary &library = *get_view<AssetCatalogTreeView>().asset_library_;
if (!AssetCatalogDropController::can_modify_catalogs(library, r_disabled_hint)) { if (!AssetCatalogDropTarget::can_modify_catalogs(library, r_disabled_hint)) {
return false; return false;
} }
const AssetCatalog *drag_catalog = AssetCatalogDropController::get_drag_catalog(drag, library); const AssetCatalog *drag_catalog = AssetCatalogDropTarget::get_drag_catalog(drag, library);
if (drag_catalog->path.parent() == "") { if (drag_catalog->path.parent() == "") {
*r_disabled_hint = "Catalog is already placed at the highest level"; *r_disabled_hint = "Catalog is already placed at the highest level";
return false; return false;
@ -612,21 +611,21 @@ bool AssetCatalogTreeViewAllItem::DropController::can_drop(const wmDrag &drag,
return true; return true;
} }
std::string AssetCatalogTreeViewAllItem::DropController::drop_tooltip(const wmDrag &drag) const std::string AssetCatalogTreeViewAllItem::DropTarget::drop_tooltip(const wmDrag &drag) const
{ {
BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG);
const AssetCatalog *drag_catalog = AssetCatalogDropController::get_drag_catalog( const AssetCatalog *drag_catalog = AssetCatalogDropTarget::get_drag_catalog(
drag, *get_view<AssetCatalogTreeView>().asset_library_); drag, *get_view<AssetCatalogTreeView>().asset_library_);
return std::string(TIP_("Move Catalog")) + " '" + drag_catalog->path.name() + "' " + return std::string(TIP_("Move Catalog")) + " '" + drag_catalog->path.name() + "' " +
TIP_("to the top level of the tree"); TIP_("to the top level of the tree");
} }
bool AssetCatalogTreeViewAllItem::DropController::on_drop(struct bContext * /*C*/, bool AssetCatalogTreeViewAllItem::DropTarget::on_drop(struct bContext * /*C*/,
const wmDrag &drag) const wmDrag &drag) const
{ {
BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG);
return AssetCatalogDropController::drop_asset_catalog_into_catalog( return AssetCatalogDropTarget::drop_asset_catalog_into_catalog(
drag, drag,
get_view<AssetCatalogTreeView>(), get_view<AssetCatalogTreeView>(),
/* No value to drop into the root level. */ /* No value to drop into the root level. */
@ -635,29 +634,28 @@ bool AssetCatalogTreeViewAllItem::DropController::on_drop(struct bContext * /*C*
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
std::unique_ptr<ui::AbstractViewItemDropController> AssetCatalogTreeViewUnassignedItem:: std::unique_ptr<ui::AbstractViewItemDropTarget> AssetCatalogTreeViewUnassignedItem::
create_drop_controller() const create_drop_target() const
{ {
return std::make_unique<AssetCatalogTreeViewUnassignedItem::DropController>( return std::make_unique<AssetCatalogTreeViewUnassignedItem::DropTarget>(
static_cast<AssetCatalogTreeView &>(get_tree_view())); static_cast<AssetCatalogTreeView &>(get_tree_view()));
} }
AssetCatalogTreeViewUnassignedItem::DropController::DropController(AssetCatalogTreeView &tree_view) AssetCatalogTreeViewUnassignedItem::DropTarget::DropTarget(AssetCatalogTreeView &tree_view)
: ui::AbstractViewItemDropController(tree_view) : ui::AbstractViewItemDropTarget(tree_view)
{ {
} }
bool AssetCatalogTreeViewUnassignedItem::DropController::can_drop( bool AssetCatalogTreeViewUnassignedItem::DropTarget::can_drop(const wmDrag &drag,
const wmDrag &drag, const char **r_disabled_hint) const const char **r_disabled_hint) const
{ {
if (drag.type != WM_DRAG_ASSET_LIST) { if (drag.type != WM_DRAG_ASSET_LIST) {
return false; return false;
} }
return AssetCatalogDropController::has_droppable_asset(drag, r_disabled_hint); return AssetCatalogDropTarget::has_droppable_asset(drag, r_disabled_hint);
} }
std::string AssetCatalogTreeViewUnassignedItem::DropController::drop_tooltip( std::string AssetCatalogTreeViewUnassignedItem::DropTarget::drop_tooltip(const wmDrag &drag) const
const wmDrag &drag) const
{ {
const ListBase *asset_drags = WM_drag_asset_list_get(&drag); const ListBase *asset_drags = WM_drag_asset_list_get(&drag);
const bool is_multiple_assets = !BLI_listbase_is_single(asset_drags); const bool is_multiple_assets = !BLI_listbase_is_single(asset_drags);
@ -666,11 +664,11 @@ std::string AssetCatalogTreeViewUnassignedItem::DropController::drop_tooltip(
TIP_("Move asset out of any catalog"); TIP_("Move asset out of any catalog");
} }
bool AssetCatalogTreeViewUnassignedItem::DropController::on_drop(struct bContext *C, bool AssetCatalogTreeViewUnassignedItem::DropTarget::on_drop(struct bContext *C,
const wmDrag &drag) const wmDrag &drag) const
{ {
/* Assign to nil catalog ID. */ /* Assign to nil catalog ID. */
return AssetCatalogDropController::drop_assets_into_catalog( return AssetCatalogDropTarget::drop_assets_into_catalog(
C, get_view<AssetCatalogTreeView>(), drag, CatalogID{}); C, get_view<AssetCatalogTreeView>(), drag, CatalogID{});
} }