Fix T92501: Crash when dragging material assets over 3D View regions

Issue was that the context used for dropbox handling and polling didn't
match the one used for drawing the dropbox and generating the tooltip
text (which would determine the material slot under the cursor,
requiring context). The mismatch would happen with overlapping regions.

Actually, this patch includes two fixes, each fixing the crash itself:
* Store the context from handling & polling and restore it for drawing.
* Correct the hovered region lookup for drawing to account for overlayed
  regions.

Note that to properly set up context for drawing, we should also account
for the operator context, which isn't done here, see
https://developer.blender.org/T92501#1247581.
This commit is contained in:
Julian Eisel 2021-11-04 12:10:58 +01:00
parent bfb664b65d
commit 80a46955d8
Notes: blender-bot 2023-02-14 10:43:47 +01:00
Referenced by issue #92501, Blender crashes when dragging Material-Assets into the 3D Viewport
5 changed files with 116 additions and 46 deletions

View File

@ -439,6 +439,7 @@ bool ED_region_panel_category_gutter_calc_rect(const ARegion *region, rcti *r_re
bool ED_region_panel_category_gutter_isect_xy(const ARegion *region, const int event_xy[2]);
bool ED_region_contains_xy(const struct ARegion *region, const int event_xy[2]);
ARegion *ED_area_find_region_xy_visual(const ScrArea *area, int regiontype, const int event_xy[2]);
/* interface_region_hud.c */
struct ARegionType *ED_area_type_hud(int space_type);

View File

@ -39,12 +39,12 @@ static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *eve
return false;
}
if (drag->free_disabled_info) {
MEM_SAFE_FREE(drag->disabled_info);
if (drag->drop_state.free_disabled_info) {
MEM_SAFE_FREE(drag->drop_state.disabled_info);
}
drag->free_disabled_info = false;
return UI_tree_view_item_can_drop(hovered_tree_item, drag, &drag->disabled_info);
drag->drop_state.free_disabled_info = false;
return UI_tree_view_item_can_drop(hovered_tree_item, drag, &drag->drop_state.disabled_info);
}
static char *ui_tree_view_drop_tooltip(bContext *C,

View File

@ -140,6 +140,10 @@ bool ED_region_overlap_isect_xy_with_margin(const ARegion *region,
ED_region_overlap_isect_y_with_margin(region, event_xy[1], margin));
}
/**
* \note: This may return true for multiple overlapping regions. If it matters, check overlapped
* regions first (#ARegion.overlap).
*/
bool ED_region_contains_xy(const ARegion *region, const int event_xy[2])
{
/* Only use the margin when inside the region. */
@ -188,3 +192,44 @@ bool ED_region_contains_xy(const ARegion *region, const int event_xy[2])
}
return false;
}
/**
* Similar to #BKE_area_find_region_xy() but when \a event_xy intersects an overlapping region,
* this returns the region that is visually under the cursor. E.g. when over the
* transparent part of the region, it returns the region underneath.
*
* The overlapping region is determined using the #ED_region_contains_xy() query.
*/
ARegion *ED_area_find_region_xy_visual(const ScrArea *area,
const int regiontype,
const int event_xy[2])
{
if (!area) {
return NULL;
}
/* Check overlapped regions first. */
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
if (!region->overlap) {
continue;
}
if (ELEM(regiontype, RGN_TYPE_ANY, region->regiontype)) {
if (ED_region_contains_xy(region, event_xy)) {
return region;
}
}
}
/* Now non-overlapping ones. */
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
if (region->overlap) {
continue;
}
if (ELEM(regiontype, RGN_TYPE_ANY, region->regiontype)) {
if (ED_region_contains_xy(region, event_xy)) {
return region;
}
}
}
return NULL;
}

View File

@ -1030,6 +1030,27 @@ typedef char *(*WMDropboxTooltipFunc)(struct bContext *,
const int xy[2],
struct wmDropBox *drop);
typedef struct wmDragActiveDropState {
/** Informs which dropbox is activated with the drag item.
* When this value changes, the #draw_activate and #draw_deactivate dropbox callbacks are
* triggered.
*/
struct wmDropBox *active_dropbox;
/** If `active_dropbox` is set, the area it successfully polled in. To restore the context of it
* as needed. */
struct ScrArea *area_from;
/** If `active_dropbox` is set, the region it successfully polled in. To restore the context of
* it as needed. */
struct ARegion *region_from;
/** Text to show when a dropbox poll succeeds (so the dropbox itself is available) but 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;
} wmDragActiveDropState;
typedef struct wmDrag {
struct wmDrag *next, *prev;
@ -1045,15 +1066,7 @@ typedef struct wmDrag {
float scale;
int sx, sy;
/** Informs which dropbox is activated with the drag item.
* When this value changes, the #draw_activate and #draw_deactivate dropbox callbacks are
* 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;
wmDragActiveDropState drop_state;
unsigned int flags;

View File

@ -51,6 +51,7 @@
#include "BLO_readfile.h"
#include "ED_asset.h"
#include "ED_screen.h"
#include "GPU_shader.h"
#include "GPU_state.h"
@ -217,7 +218,7 @@ void wm_drags_exit(wmWindowManager *wm, wmWindow *win)
{
bool any_active = false;
LISTBASE_FOREACH (const wmDrag *, drag, &wm->drags) {
if (drag->active_dropbox) {
if (drag->drop_state.active_dropbox) {
any_active = true;
break;
}
@ -260,14 +261,14 @@ void WM_drag_data_free(int dragtype, void *poin)
void WM_drag_free(wmDrag *drag)
{
if (drag->active_dropbox && drag->active_dropbox->draw_deactivate) {
drag->active_dropbox->draw_deactivate(drag->active_dropbox, drag);
if (drag->drop_state.active_dropbox && drag->drop_state.active_dropbox->draw_deactivate) {
drag->drop_state.active_dropbox->draw_deactivate(drag->drop_state.active_dropbox, 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);
if (drag->drop_state.free_disabled_info) {
MEM_SAFE_FREE(drag->drop_state.disabled_info);
}
BLI_freelistN(&drag->ids);
LISTBASE_FOREACH_MUTABLE (wmDragAssetListItem *, asset_item, &drag->asset_items) {
@ -306,10 +307,10 @@ static wmDropBox *dropbox_active(bContext *C,
wmDrag *drag,
const wmEvent *event)
{
if (drag->free_disabled_info) {
MEM_SAFE_FREE(drag->disabled_info);
if (drag->drop_state.free_disabled_info) {
MEM_SAFE_FREE(drag->drop_state.disabled_info);
}
drag->disabled_info = NULL;
drag->drop_state.disabled_info = NULL;
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
if (handler_base->type == WM_HANDLER_TYPE_DROPBOX) {
@ -332,8 +333,8 @@ static wmDropBox *dropbox_active(bContext *C,
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;
drag->drop_state.disabled_info = disabled_hint;
drag->drop_state.free_disabled_info = free_disabled_info;
}
}
}
@ -373,7 +374,7 @@ static void wm_drop_update_active(bContext *C, wmDrag *drag, const wmEvent *even
return;
}
wmDropBox *drop_prev = drag->active_dropbox;
wmDropBox *drop_prev = drag->drop_state.active_dropbox;
wmDropBox *drop = wm_dropbox_active(C, drag, event);
if (drop != drop_prev) {
if (drop_prev && drop_prev->draw_deactivate) {
@ -383,7 +384,9 @@ static void wm_drop_update_active(bContext *C, wmDrag *drag, const wmEvent *even
if (drop && drop->draw_activate) {
drop->draw_activate(drop, drag);
}
drag->active_dropbox = drop;
drag->drop_state.active_dropbox = drop;
drag->drop_state.area_from = drop ? CTX_wm_area(C) : NULL;
drag->drop_state.region_from = drop ? CTX_wm_region(C) : NULL;
}
}
@ -408,7 +411,7 @@ void wm_drags_check_ops(bContext *C, const wmEvent *event)
LISTBASE_FOREACH (wmDrag *, drag, &wm->drags) {
wm_drop_update_active(C, drag, event);
if (drag->active_dropbox) {
if (drag->drop_state.active_dropbox) {
any_active = true;
}
}
@ -819,14 +822,14 @@ static void wm_drag_draw_tooltip(bContext *C, wmWindow *win, wmDrag *drag, const
int iconsize = UI_DPI_ICON_SIZE;
int padding = 4 * UI_DPI_FAC;
const char *tooltip = NULL;
bool free_tooltip = false;
if (drag->active_dropbox) {
tooltip = dropbox_tooltip(C, drag, xy, drag->active_dropbox);
free_tooltip = true;
char *tooltip = NULL;
if (drag->drop_state.active_dropbox) {
tooltip = dropbox_tooltip(C, drag, xy, drag->drop_state.active_dropbox);
}
if (!tooltip && !drag->disabled_info) {
const bool has_disabled_info = drag->drop_state.disabled_info &&
drag->drop_state.disabled_info[0];
if (!tooltip && !has_disabled_info) {
return;
}
@ -855,12 +858,10 @@ static void wm_drag_draw_tooltip(bContext *C, wmWindow *win, wmDrag *drag, const
if (tooltip) {
wm_drop_operator_draw(tooltip, x, y);
if (free_tooltip) {
MEM_freeN((void *)tooltip);
}
MEM_freeN(tooltip);
}
else if (drag->disabled_info) {
wm_drop_redalert_draw(drag->disabled_info, x, y);
else if (has_disabled_info) {
wm_drop_redalert_draw(drag->drop_state.disabled_info, x, y);
}
}
@ -905,22 +906,32 @@ void wm_drags_draw(bContext *C, wmWindow *win)
}
bScreen *screen = CTX_wm_screen(C);
/* To start with, use the area and region under the mouse cursor, just like event handling. The
* operator context may still override it. */
ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, UNPACK2(xy));
ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, UNPACK2(xy));
if (region) {
BLI_assert(!CTX_wm_area(C) && !CTX_wm_region(C));
CTX_wm_area_set(C, area);
CTX_wm_region_set(C, region);
}
ARegion *region = ED_area_find_region_xy_visual(area, RGN_TYPE_ANY, xy);
/* Will be overridden and unset eventually. */
BLI_assert(!CTX_wm_area(C) && !CTX_wm_region(C));
wmWindowManager *wm = CTX_wm_manager(C);
/* Should we support multi-line drag draws? Maybe not, more types mixed won't work well. */
GPU_blend(GPU_BLEND_ALPHA);
LISTBASE_FOREACH (wmDrag *, drag, &wm->drags) {
if (drag->active_dropbox && drag->active_dropbox->draw) {
drag->active_dropbox->draw(C, win, drag, xy);
continue;
if (drag->drop_state.active_dropbox) {
CTX_wm_area_set(C, drag->drop_state.area_from);
CTX_wm_region_set(C, drag->drop_state.region_from);
/* Drawing should be allowed to assume the context from handling and polling (that's why we
* restore it above). */
if (drag->drop_state.active_dropbox->draw) {
drag->drop_state.active_dropbox->draw(C, win, drag, xy);
continue;
}
}
else if (region) {
CTX_wm_area_set(C, area);
CTX_wm_region_set(C, region);
}
wm_drag_draw_default(C, win, drag, xy);