From df2e05393533bff6b14751deb890ef0fc4064c8f Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Tue, 26 Oct 2021 18:04:45 +0200 Subject: 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 --- source/blender/windowmanager/WM_types.h | 5 + source/blender/windowmanager/intern/wm_dragdrop.c | 135 +++++++++++++++++---- .../blender/windowmanager/intern/wm_event_system.c | 10 +- source/blender/windowmanager/wm_event_system.h | 2 + 4 files changed, 124 insertions(+), 28 deletions(-) (limited to 'source') diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 2813047f0e4..1f1b1a70685 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -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. */ diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c index c60e47a3b0f..3206d0c044e 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.c +++ b/source/blender/windowmanager/intern/wm_dragdrop.c @@ -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; + 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 (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]) diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index df976d9a4cd..2aa9a4d8676 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -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; diff --git a/source/blender/windowmanager/wm_event_system.h b/source/blender/windowmanager/wm_event_system.h index dfc8c578420..40e4d905fcd 100644 --- a/source/blender/windowmanager/wm_event_system.h +++ b/source/blender/windowmanager/wm_event_system.h @@ -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); -- cgit v1.2.3