UI: Improved feedback when dropping is not possible on drag 'n drop

* Allow operators to show a "disabled hint" in red text explaining why dropping
  at the current location and in current context doesn't work. Should greatly
  help users to understand what's the problem.
* Show a "stop" cursor when dropping isn't possible, like it's common on OSes.

Differential Revision: https://developer.blender.org/D10358
This commit is contained in:
Julian Eisel 2021-10-26 18:04:45 +02:00
parent ec831ce5df
commit df2e053935
4 changed files with 125 additions and 29 deletions

View File

@ -1044,6 +1044,11 @@ typedef struct wmDrag {
* triggered.
*/
struct wmDropBox *active_dropbox;
/* Text to show when the operator poll fails. Typically the message the
* operator set with CTX_wm_operator_poll_msg_set(). */
const char *disabled_info;
bool free_disabled_info;
unsigned int flags;
/** List of wmDragIDs, all are guaranteed to have the same ID type. */

View File

@ -207,6 +207,30 @@ wmDrag *WM_event_start_drag(
return drag;
}
/**
* Additional work to cleanly end dragging. Additional because this doesn't actually remove the
* drag items. Should be called whenever dragging is stopped (successful or not, also when
* canceled).
*/
void wm_drags_exit(wmWindowManager *wm, wmWindow *win)
{
bool any_active = false;
LISTBASE_FOREACH (const wmDrag *, drag, &wm->drags) {
if (drag->active_dropbox) {
any_active = true;
}
}
/* If there is no active drop-box #wm_drags_check_ops() set a stop-cursor, which needs to be
* restored. */
if (!any_active) {
WM_cursor_modal_restore(win);
/* Ensure the correct area cursor is restored. */
win->tag_cursor_refresh = true;
WM_event_add_mousemove(win);
}
}
void WM_event_drag_image(wmDrag *drag, ImBuf *imb, float scale, int sx, int sy)
{
drag->imb = imb;
@ -240,6 +264,9 @@ void WM_drag_free(wmDrag *drag)
if (drag->flags & WM_DRAG_FREE_DATA) {
WM_drag_data_free(drag->type, drag->poin);
}
if (drag->free_disabled_info) {
MEM_SAFE_FREE(drag->disabled_info);
}
BLI_freelistN(&drag->ids);
LISTBASE_FOREACH_MUTABLE (wmDragAssetListItem *, asset_item, &drag->asset_items) {
if (asset_item->is_external) {
@ -277,15 +304,35 @@ static wmDropBox *dropbox_active(bContext *C,
wmDrag *drag,
const wmEvent *event)
{
if (drag->free_disabled_info) {
MEM_SAFE_FREE(drag->disabled_info);
}
drag->disabled_info = NULL;
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
if (handler_base->type == WM_HANDLER_TYPE_DROPBOX) {
wmEventHandler_Dropbox *handler = (wmEventHandler_Dropbox *)handler_base;
if (handler->dropboxes) {
LISTBASE_FOREACH (wmDropBox *, drop, handler->dropboxes) {
if (drop->poll(C, drag, event) &&
WM_operator_poll_context(C, drop->ot, drop->opcontext)) {
if (!drop->poll(C, drag, event)) {
/* If the drop's poll fails, don't set the disabled-info. This would be too aggressive.
* Instead show it only if the drop box could be used in principle, but the operator
* can't be executed. */
continue;
}
if (WM_operator_poll_context(C, drop->ot, drop->opcontext)) {
return drop;
}
/* Attempt to set the disabled hint when the poll fails. Will always be the last hint set
* when there are multiple failing polls (could allow multiple disabled-hints too). */
bool free_disabled_info = false;
const char *disabled_hint = CTX_wm_operator_poll_msg_get(C, &free_disabled_info);
if (disabled_hint) {
drag->disabled_info = disabled_hint;
drag->free_disabled_info = free_disabled_info;
}
}
}
}
@ -309,7 +356,10 @@ static wmDropBox *wm_dropbox_active(bContext *C, wmDrag *drag, const wmEvent *ev
return drop;
}
static void wm_drop_operator_options(bContext *C, wmDrag *drag, const wmEvent *event)
/**
* Update dropping information for the current mouse position in \a event.
*/
static void wm_drop_update_active(bContext *C, wmDrag *drag, const wmEvent *event)
{
wmWindow *win = CTX_wm_window(C);
const int winsize_x = WM_window_pixels_x(win);
@ -335,13 +385,36 @@ static void wm_drop_operator_options(bContext *C, wmDrag *drag, const wmEvent *e
}
}
void wm_drop_prepare(bContext *C, wmDrag *drag, wmDropBox *drop)
{
/* Optionally copy drag information to operator properties. Don't call it if the
* operator fails anyway, it might do more than just set properties (e.g.
* typically import an asset). */
if (drop->copy && WM_operator_poll_context(C, drop->ot, drop->opcontext)) {
drop->copy(drag, drop);
}
wm_drags_exit(CTX_wm_manager(C), CTX_wm_window(C));
}
/* called in inner handler loop, region context */
void wm_drags_check_ops(bContext *C, const wmEvent *event)
{
wmWindowManager *wm = CTX_wm_manager(C);
bool any_active = false;
LISTBASE_FOREACH (wmDrag *, drag, &wm->drags) {
wm_drop_operator_options(C, drag, event);
wm_drop_update_active(C, drag, event);
if (drag->active_dropbox) {
any_active = true;
}
}
/* Change the cursor to display that dropping isn't possible here. But only if there is something
* being dragged actually. Cursor will be restored in #wm_drags_exit(). */
if (!BLI_listbase_is_empty(&wm->drags)) {
WM_cursor_modal_set(CTX_wm_window(C), any_active ? WM_CURSOR_DEFAULT : WM_CURSOR_STOP);
}
}
@ -622,6 +695,17 @@ static void wm_drop_operator_draw(const char *name, int x, int y)
UI_fontstyle_draw_simple_backdrop(fstyle, x, y, name, col_fg, col_bg);
}
static void wm_drop_redalert_draw(const char *redalert_str, int x, int y)
{
const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
const float col_bg[4] = {0.0f, 0.0f, 0.0f, 0.2f};
float col_fg[4];
UI_GetThemeColor4fv(TH_REDALERT, col_fg);
UI_fontstyle_draw_simple_backdrop(fstyle, x, y, redalert_str, col_fg, col_bg);
}
const char *WM_drag_get_item_name(wmDrag *drag)
{
switch (drag->type) {
@ -721,35 +805,42 @@ static void wm_drag_draw_tooltip(bContext *C, wmWindow *win, wmDrag *drag, const
free_tooltip = true;
}
if (tooltip) {
const int winsize_y = WM_window_pixels_y(win);
int x, y;
if (drag->imb) {
x = xy[0] - drag->sx / 2;
if (!tooltip && !drag->disabled_info) {
return;
}
if (xy[1] + drag->sy / 2 + padding + iconsize < winsize_y) {
y = xy[1] + drag->sy / 2 + padding;
}
else {
y = xy[1] - drag->sy / 2 - padding - iconsize - padding - iconsize;
}
const int winsize_y = WM_window_pixels_y(win);
int x, y;
if (drag->imb) {
x = xy[0] - drag->sx / 2;
if (xy[1] + drag->sy / 2 + padding + iconsize < winsize_y) {
y = xy[1] + drag->sy / 2 + padding;
}
else {
x = xy[0] - 2 * padding;
if (xy[1] + iconsize + iconsize < winsize_y) {
y = (xy[1] + iconsize) + padding;
}
else {
y = (xy[1] - iconsize) - padding;
}
y = xy[1] - drag->sy / 2 - padding - iconsize - padding - iconsize;
}
}
else {
x = xy[0] - 2 * padding;
if (xy[1] + iconsize + iconsize < winsize_y) {
y = (xy[1] + iconsize) + padding;
}
else {
y = (xy[1] - iconsize) - padding;
}
}
if (tooltip) {
wm_drop_operator_draw(tooltip, x, y);
if (free_tooltip) {
MEM_freeN((void *)tooltip);
}
}
else if (drag->disabled_info) {
wm_drop_redalert_draw(drag->disabled_info, x, y);
}
}
static void wm_drag_draw_default(bContext *C, wmWindow *win, wmDrag *drag, const int xy[2])

View File

@ -3055,12 +3055,7 @@ static int wm_handlers_do_intern(bContext *C, wmWindow *win, wmEvent *event, Lis
ListBase *lb = (ListBase *)event->customdata;
LISTBASE_FOREACH_MUTABLE (wmDrag *, drag, lb) {
if (drop->poll(C, drag, event)) {
/* Optionally copy drag information to operator properties. Don't call it if the
* operator fails anyway, it might do more than just set properties (e.g.
* typically import an asset). */
if (drop->copy && WM_operator_poll_context(C, drop->ot, drop->opcontext)) {
drop->copy(drag, drop);
}
wm_drop_prepare(C, drag, drop);
/* Pass single matched wmDrag onto the operator. */
BLI_remlink(lb, drag);
@ -3094,6 +3089,8 @@ static int wm_handlers_do_intern(bContext *C, wmWindow *win, wmEvent *event, Lis
break;
}
}
/* Always exit all drags on a drop event, even if poll didn't succeed. */
wm_drags_exit(wm, win);
}
}
}
@ -3390,6 +3387,7 @@ static void wm_event_drag_and_drop_test(wmWindowManager *wm, wmWindow *win, wmEv
screen->do_draw_drag = true;
}
else if (event->type == EVT_ESCKEY) {
wm_drags_exit(wm, win);
WM_drag_free_list(&wm->drags);
screen->do_draw_drag = true;

View File

@ -172,6 +172,8 @@ void wm_tablet_data_from_ghost(const struct GHOST_TabletData *tablet_data, wmTab
/* wm_dropbox.c */
void wm_dropbox_free(void);
void wm_drags_exit(wmWindowManager *wm, wmWindow *win);
void wm_drop_prepare(bContext *C, wmDrag *drag, wmDropBox *drop);
void wm_drags_check_ops(bContext *C, const wmEvent *event);
void wm_drags_draw(bContext *C, wmWindow *win);