diff options
Diffstat (limited to 'source/blender/editors/interface/interface_region_popup.cc')
-rw-r--r-- | source/blender/editors/interface/interface_region_popup.cc | 836 |
1 files changed, 836 insertions, 0 deletions
diff --git a/source/blender/editors/interface/interface_region_popup.cc b/source/blender/editors/interface/interface_region_popup.cc new file mode 100644 index 00000000000..74c228e3338 --- /dev/null +++ b/source/blender/editors/interface/interface_region_popup.cc @@ -0,0 +1,836 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * PopUp Region (Generic) + */ + +#include <cstdarg> +#include <cstdlib> +#include <cstring> + +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" + +#include "ED_screen.h" + +#include "interface_intern.h" +#include "interface_regions_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Utility Functions + * \{ */ + +void ui_popup_translate(ARegion *region, const int mdiff[2]) +{ + BLI_rcti_translate(®ion->winrct, UNPACK2(mdiff)); + + ED_region_update_rect(region); + + ED_region_tag_redraw(region); + + /* update blocks */ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + uiPopupBlockHandle *handle = block->handle; + /* Make empty, will be initialized on next use, see T60608. */ + BLI_rctf_init(&handle->prev_block_rect, 0, 0, 0, 0); + + LISTBASE_FOREACH (uiSafetyRct *, saferct, &block->saferct) { + BLI_rctf_translate(&saferct->parent, UNPACK2(mdiff)); + BLI_rctf_translate(&saferct->safety, UNPACK2(mdiff)); + } + } +} + +/* position block relative to but, result is in window space */ +static void ui_popup_block_position(wmWindow *window, + ARegion *butregion, + uiBut *but, + uiBlock *block) +{ + uiPopupBlockHandle *handle = block->handle; + + /* Compute button position in window coordinates using the source + * button region/block, to position the popup attached to it. */ + rctf butrct; + + if (!handle->refresh) { + ui_block_to_window_rctf(butregion, but->block, &butrct, &but->rect); + + /* widget_roundbox_set has this correction too, keep in sync */ + if (but->type != UI_BTYPE_PULLDOWN) { + if (but->drawflag & UI_BUT_ALIGN_TOP) { + butrct.ymax += U.pixelsize; + } + if (but->drawflag & UI_BUT_ALIGN_LEFT) { + butrct.xmin -= U.pixelsize; + } + } + + handle->prev_butrct = butrct; + } + else { + /* For refreshes, keep same button position so popup doesn't move. */ + butrct = handle->prev_butrct; + } + + /* Compute block size in window space, based on buttons contained in it. */ + if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) { + if (block->buttons.first) { + BLI_rctf_init_minmax(&block->rect); + + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (block->content_hints & UI_BLOCK_CONTAINS_SUBMENU_BUT) { + bt->rect.xmax += UI_MENU_SUBMENU_PADDING; + } + BLI_rctf_union(&block->rect, &bt->rect); + } + } + else { + /* we're nice and allow empty blocks too */ + block->rect.xmin = block->rect.ymin = 0; + block->rect.xmax = block->rect.ymax = 20; + } + } + + ui_block_to_window_rctf(butregion, but->block, &block->rect, &block->rect); + + /* Compute direction relative to button, based on available space. */ + const int size_x = BLI_rctf_size_x(&block->rect) + 0.2f * UI_UNIT_X; /* 4 for shadow */ + const int size_y = BLI_rctf_size_y(&block->rect) + 0.2f * UI_UNIT_Y; + const int center_x = (block->direction & UI_DIR_CENTER_X) ? size_x / 2 : 0; + const int center_y = (block->direction & UI_DIR_CENTER_Y) ? size_y / 2 : 0; + + short dir1 = 0, dir2 = 0; + + if (!handle->refresh) { + bool left = false, right = false, top = false, down = false; + + const int win_x = WM_window_pixels_x(window); + const int win_y = WM_window_pixels_y(window); + + /* Take into account maximum size so we don't have to flip on refresh. */ + const float max_size_x = max_ff(size_x, handle->max_size_x); + const float max_size_y = max_ff(size_y, handle->max_size_y); + + /* check if there's space at all */ + if (butrct.xmin - max_size_x + center_x > 0.0f) { + left = true; + } + if (butrct.xmax + max_size_x - center_x < win_x) { + right = true; + } + if (butrct.ymin - max_size_y + center_y > 0.0f) { + down = true; + } + if (butrct.ymax + max_size_y - center_y < win_y) { + top = true; + } + + if (top == 0 && down == 0) { + if (butrct.ymin - max_size_y < win_y - butrct.ymax - max_size_y) { + top = true; + } + else { + down = true; + } + } + + dir1 = (block->direction & UI_DIR_ALL); + + /* Secondary directions. */ + if (dir1 & (UI_DIR_UP | UI_DIR_DOWN)) { + if (dir1 & UI_DIR_LEFT) { + dir2 = UI_DIR_LEFT; + } + else if (dir1 & UI_DIR_RIGHT) { + dir2 = UI_DIR_RIGHT; + } + dir1 &= (UI_DIR_UP | UI_DIR_DOWN); + } + + if ((dir2 == 0) && (ELEM(dir1, UI_DIR_LEFT, UI_DIR_RIGHT))) { + dir2 = UI_DIR_DOWN; + } + if ((dir2 == 0) && (ELEM(dir1, UI_DIR_UP, UI_DIR_DOWN))) { + dir2 = UI_DIR_LEFT; + } + + /* no space at all? don't change */ + if (left || right) { + if (dir1 == UI_DIR_LEFT && left == 0) { + dir1 = UI_DIR_RIGHT; + } + if (dir1 == UI_DIR_RIGHT && right == 0) { + dir1 = UI_DIR_LEFT; + } + /* this is aligning, not append! */ + if (dir2 == UI_DIR_LEFT && right == 0) { + dir2 = UI_DIR_RIGHT; + } + if (dir2 == UI_DIR_RIGHT && left == 0) { + dir2 = UI_DIR_LEFT; + } + } + if (down || top) { + if (dir1 == UI_DIR_UP && top == 0) { + dir1 = UI_DIR_DOWN; + } + if (dir1 == UI_DIR_DOWN && down == 0) { + dir1 = UI_DIR_UP; + } + BLI_assert(dir2 != UI_DIR_UP); + // if (dir2 == UI_DIR_UP && top == 0) { dir2 = UI_DIR_DOWN; } + if (dir2 == UI_DIR_DOWN && down == 0) { + dir2 = UI_DIR_UP; + } + } + + handle->prev_dir1 = dir1; + handle->prev_dir2 = dir2; + } + else { + /* For refreshes, keep same popup direct so popup doesn't move + * to a totally different position while editing in it. */ + dir1 = handle->prev_dir1; + dir2 = handle->prev_dir2; + } + + /* Compute offset based on direction. */ + float offset_x = 0, offset_y = 0; + + /* Ensure buttons don't come between the parent button and the popup, see: T63566. */ + const float offset_overlap = max_ff(U.pixelsize, 1.0f); + + if (dir1 == UI_DIR_LEFT) { + offset_x = (butrct.xmin - block->rect.xmax) + offset_overlap; + if (dir2 == UI_DIR_UP) { + offset_y = butrct.ymin - block->rect.ymin - center_y - UI_MENU_PADDING; + } + else { + offset_y = butrct.ymax - block->rect.ymax + center_y + UI_MENU_PADDING; + } + } + else if (dir1 == UI_DIR_RIGHT) { + offset_x = (butrct.xmax - block->rect.xmin) - offset_overlap; + if (dir2 == UI_DIR_UP) { + offset_y = butrct.ymin - block->rect.ymin - center_y - UI_MENU_PADDING; + } + else { + offset_y = butrct.ymax - block->rect.ymax + center_y + UI_MENU_PADDING; + } + } + else if (dir1 == UI_DIR_UP) { + offset_y = (butrct.ymax - block->rect.ymin) - offset_overlap; + if (dir2 == UI_DIR_RIGHT) { + offset_x = butrct.xmax - block->rect.xmax + center_x; + } + else { + offset_x = butrct.xmin - block->rect.xmin - center_x; + } + /* changed direction? */ + if ((dir1 & block->direction) == 0) { + /* TODO: still do */ + UI_block_order_flip(block); + } + } + else if (dir1 == UI_DIR_DOWN) { + offset_y = (butrct.ymin - block->rect.ymax) + offset_overlap; + if (dir2 == UI_DIR_RIGHT) { + offset_x = butrct.xmax - block->rect.xmax + center_x; + } + else { + offset_x = butrct.xmin - block->rect.xmin - center_x; + } + /* changed direction? */ + if ((dir1 & block->direction) == 0) { + /* TODO: still do */ + UI_block_order_flip(block); + } + } + + /* Center over popovers for eg. */ + if (block->direction & UI_DIR_CENTER_X) { + offset_x += BLI_rctf_size_x(&butrct) / ((dir2 == UI_DIR_LEFT) ? 2 : -2); + } + + /* Apply offset, buttons in window coords. */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + ui_block_to_window_rctf(butregion, but->block, &bt->rect, &bt->rect); + + BLI_rctf_translate(&bt->rect, offset_x, offset_y); + + /* ui_but_update recalculates drawstring size in pixels */ + ui_but_update(bt); + } + + BLI_rctf_translate(&block->rect, offset_x, offset_y); + + /* Safety calculus. */ + { + const float midx = BLI_rctf_cent_x(&butrct); + const float midy = BLI_rctf_cent_y(&butrct); + + /* when you are outside parent button, safety there should be smaller */ + + const int s1 = 40 * U.dpi_fac; + const int s2 = 3 * U.dpi_fac; + + /* parent button to left */ + if (midx < block->rect.xmin) { + block->safety.xmin = block->rect.xmin - s2; + } + else { + block->safety.xmin = block->rect.xmin - s1; + } + /* parent button to right */ + if (midx > block->rect.xmax) { + block->safety.xmax = block->rect.xmax + s2; + } + else { + block->safety.xmax = block->rect.xmax + s1; + } + + /* parent button on bottom */ + if (midy < block->rect.ymin) { + block->safety.ymin = block->rect.ymin - s2; + } + else { + block->safety.ymin = block->rect.ymin - s1; + } + /* parent button on top */ + if (midy > block->rect.ymax) { + block->safety.ymax = block->rect.ymax + s2; + } + else { + block->safety.ymax = block->rect.ymax + s1; + } + + /* Exception for switched pull-downs. */ + if (dir1 && (dir1 & block->direction) == 0) { + if (dir2 == UI_DIR_RIGHT) { + block->safety.xmax = block->rect.xmax + s2; + } + if (dir2 == UI_DIR_LEFT) { + block->safety.xmin = block->rect.xmin - s2; + } + } + block->direction = dir1; + } + + /* Keep a list of these, needed for pull-down menus. */ + uiSafetyRct *saferct = MEM_cnew<uiSafetyRct>(__func__); + saferct->parent = butrct; + saferct->safety = block->safety; + BLI_freelistN(&block->saferct); + BLI_duplicatelist(&block->saferct, &but->block->saferct); + BLI_addhead(&block->saferct, saferct); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Menu Block Creation + * \{ */ + +static void ui_block_region_refresh(const bContext *C, ARegion *region) +{ + ScrArea *ctx_area = CTX_wm_area(C); + ARegion *ctx_region = CTX_wm_region(C); + + if (region->do_draw & RGN_REFRESH_UI) { + ScrArea *handle_ctx_area; + ARegion *handle_ctx_region; + + region->do_draw &= ~RGN_REFRESH_UI; + LISTBASE_FOREACH_MUTABLE (uiBlock *, block, ®ion->uiblocks) { + uiPopupBlockHandle *handle = block->handle; + + if (handle->can_refresh) { + handle_ctx_area = handle->ctx_area; + handle_ctx_region = handle->ctx_region; + + if (handle_ctx_area) { + CTX_wm_area_set((bContext *)C, handle_ctx_area); + } + if (handle_ctx_region) { + CTX_wm_region_set((bContext *)C, handle_ctx_region); + } + + uiBut *but = handle->popup_create_vars.but; + ARegion *butregion = handle->popup_create_vars.butregion; + ui_popup_block_refresh((bContext *)C, handle, butregion, but); + } + } + } + + CTX_wm_area_set((bContext *)C, ctx_area); + CTX_wm_region_set((bContext *)C, ctx_region); +} + +static void ui_block_region_draw(const bContext *C, ARegion *region) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + UI_block_draw(C, block); + } +} + +/** + * Use to refresh centered popups on screen resizing (for splash). + */ +static void ui_block_region_popup_window_listener(const wmRegionListenerParams *params) +{ + ARegion *region = params->region; + wmNotifier *wmn = params->notifier; + + switch (wmn->category) { + case NC_WINDOW: { + switch (wmn->action) { + case NA_EDITED: { + /* window resize */ + ED_region_tag_refresh_ui(region); + break; + } + } + break; + } + } +} + +static void ui_popup_block_clip(wmWindow *window, uiBlock *block) +{ + const float xmin_orig = block->rect.xmin; + const int margin = UI_SCREEN_MARGIN; + int winx, winy; + + if (block->flag & UI_BLOCK_NO_WIN_CLIP) { + return; + } + + winx = WM_window_pixels_x(window); + winy = WM_window_pixels_y(window); + + /* shift to left if outside of view */ + if (block->rect.xmax > winx - margin) { + const float xofs = winx - margin - block->rect.xmax; + block->rect.xmin += xofs; + block->rect.xmax += xofs; + } + /* shift menus to right if outside of view */ + if (block->rect.xmin < margin) { + const float xofs = (margin - block->rect.xmin); + block->rect.xmin += xofs; + block->rect.xmax += xofs; + } + + if (block->rect.ymin < margin) { + block->rect.ymin = margin; + } + if (block->rect.ymax > winy - UI_POPUP_MENU_TOP) { + block->rect.ymax = winy - UI_POPUP_MENU_TOP; + } + + /* ensure menu items draw inside left/right boundary */ + const float xofs = block->rect.xmin - xmin_orig; + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + bt->rect.xmin += xofs; + bt->rect.xmax += xofs; + } +} + +void ui_popup_block_scrolltest(uiBlock *block) +{ + block->flag &= ~(UI_BLOCK_CLIPBOTTOM | UI_BLOCK_CLIPTOP); + + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + bt->flag &= ~UI_SCROLLED; + } + + if (block->buttons.first == block->buttons.last) { + return; + } + + /* mark buttons that are outside boundary */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (bt->rect.ymin < block->rect.ymin) { + bt->flag |= UI_SCROLLED; + block->flag |= UI_BLOCK_CLIPBOTTOM; + } + if (bt->rect.ymax > block->rect.ymax) { + bt->flag |= UI_SCROLLED; + block->flag |= UI_BLOCK_CLIPTOP; + } + } + + /* mark buttons overlapping arrows, if we have them */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (block->flag & UI_BLOCK_CLIPBOTTOM) { + if (bt->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW) { + bt->flag |= UI_SCROLLED; + } + } + if (block->flag & UI_BLOCK_CLIPTOP) { + if (bt->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW) { + bt->flag |= UI_SCROLLED; + } + } + } +} + +static void ui_popup_block_remove(bContext *C, uiPopupBlockHandle *handle) +{ + wmWindow *ctx_win = CTX_wm_window(C); + ScrArea *ctx_area = CTX_wm_area(C); + ARegion *ctx_region = CTX_wm_region(C); + + wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win = ctx_win; + bScreen *screen = CTX_wm_screen(C); + + /* There may actually be a different window active than the one showing the popup, so lookup real + * one. */ + if (BLI_findindex(&screen->regionbase, handle->region) == -1) { + LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) { + screen = WM_window_get_active_screen(win_iter); + if (BLI_findindex(&screen->regionbase, handle->region) != -1) { + win = win_iter; + break; + } + } + } + + BLI_assert(win && screen); + + CTX_wm_window_set(C, win); + ui_region_temp_remove(C, screen, handle->region); + + /* Reset context (area and region were nullptr'ed when changing context window). */ + CTX_wm_window_set(C, ctx_win); + CTX_wm_area_set(C, ctx_area); + CTX_wm_region_set(C, ctx_region); + + /* reset to region cursor (only if there's not another menu open) */ + if (BLI_listbase_is_empty(&screen->regionbase)) { + win->tag_cursor_refresh = true; + } + + if (handle->scrolltimer) { + WM_event_remove_timer(wm, win, handle->scrolltimer); + } +} + +uiBlock *ui_popup_block_refresh(bContext *C, + uiPopupBlockHandle *handle, + ARegion *butregion, + uiBut *but) +{ + const int margin = UI_POPUP_MARGIN; + wmWindow *window = CTX_wm_window(C); + ARegion *region = handle->region; + + const uiBlockCreateFunc create_func = handle->popup_create_vars.create_func; + const uiBlockHandleCreateFunc handle_create_func = handle->popup_create_vars.handle_create_func; + void *arg = handle->popup_create_vars.arg; + + uiBlock *block_old = static_cast<uiBlock *>(region->uiblocks.first); + uiBlock *block; + + handle->refresh = (block_old != nullptr); + + BLI_assert(!handle->refresh || handle->can_refresh); + +#ifdef DEBUG + wmEvent *event_back = window->eventstate; + wmEvent *event_last_back = window->event_last_handled; +#endif + + /* create ui block */ + if (create_func) { + block = create_func(C, region, arg); + } + else { + block = handle_create_func(C, handle, arg); + } + + /* callbacks _must_ leave this for us, otherwise we can't call UI_block_update_from_old */ + BLI_assert(!block->endblock); + + /* ensure we don't use mouse coords here! */ +#ifdef DEBUG + window->eventstate = nullptr; +#endif + + if (block->handle) { + memcpy(block->handle, handle, sizeof(uiPopupBlockHandle)); + MEM_freeN(handle); + handle = block->handle; + } + else { + block->handle = handle; + } + + region->regiondata = handle; + + /* set UI_BLOCK_NUMSELECT before UI_block_end() so we get alphanumeric keys assigned */ + if (but == nullptr) { + block->flag |= UI_BLOCK_POPUP; + } + + block->flag |= UI_BLOCK_LOOP; + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + + /* defer this until blocks are translated (below) */ + block->oldblock = nullptr; + + if (!block->endblock) { + UI_block_end_ex( + C, block, handle->popup_create_vars.event_xy, handle->popup_create_vars.event_xy); + } + + /* if this is being created from a button */ + if (but) { + block->aspect = but->block->aspect; + ui_popup_block_position(window, butregion, but, block); + handle->direction = block->direction; + } + else { + /* Keep a list of these, needed for pull-down menus. */ + uiSafetyRct *saferct = MEM_cnew<uiSafetyRct>(__func__); + saferct->safety = block->safety; + BLI_addhead(&block->saferct, saferct); + } + + if (block->flag & UI_BLOCK_RADIAL) { + const int win_width = UI_SCREEN_MARGIN; + int winx, winy; + + int x_offset = 0, y_offset = 0; + + winx = WM_window_pixels_x(window); + winy = WM_window_pixels_y(window); + + copy_v2_v2(block->pie_data.pie_center_init, block->pie_data.pie_center_spawned); + + /* only try translation if area is large enough */ + if (BLI_rctf_size_x(&block->rect) < winx - (2.0f * win_width)) { + if (block->rect.xmin < win_width) { + x_offset += win_width - block->rect.xmin; + } + if (block->rect.xmax > winx - win_width) { + x_offset += winx - win_width - block->rect.xmax; + } + } + + if (BLI_rctf_size_y(&block->rect) < winy - (2.0f * win_width)) { + if (block->rect.ymin < win_width) { + y_offset += win_width - block->rect.ymin; + } + if (block->rect.ymax > winy - win_width) { + y_offset += winy - win_width - block->rect.ymax; + } + } + /* if we are offsetting set up initial data for timeout functionality */ + + if ((x_offset != 0) || (y_offset != 0)) { + block->pie_data.pie_center_spawned[0] += x_offset; + block->pie_data.pie_center_spawned[1] += y_offset; + + UI_block_translate(block, x_offset, y_offset); + + if (U.pie_initial_timeout > 0) { + block->pie_data.flags |= UI_PIE_INITIAL_DIRECTION; + } + } + + region->winrct.xmin = 0; + region->winrct.xmax = winx; + region->winrct.ymin = 0; + region->winrct.ymax = winy; + + ui_block_calc_pie_segment(block, block->pie_data.pie_center_init); + + /* lastly set the buttons at the center of the pie menu, ready for animation */ + if (U.pie_animation_timeout > 0) { + LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { + if (but_iter->pie_dir != UI_RADIAL_NONE) { + BLI_rctf_recenter(&but_iter->rect, UNPACK2(block->pie_data.pie_center_spawned)); + } + } + } + } + else { + /* Add an offset to draw the popover arrow. */ + if ((block->flag & UI_BLOCK_POPOVER) && ELEM(block->direction, UI_DIR_UP, UI_DIR_DOWN)) { + /* Keep sync with 'ui_draw_popover_back_impl'. */ + const float unit_size = U.widget_unit / block->aspect; + const float unit_half = unit_size * (block->direction == UI_DIR_DOWN ? 0.5 : -0.5); + + UI_block_translate(block, 0, -unit_half); + } + + /* clip block with window boundary */ + ui_popup_block_clip(window, block); + + /* Avoid menu moving down and losing cursor focus by keeping it at + * the same height. */ + if (handle->refresh && handle->prev_block_rect.ymax > block->rect.ymax) { + if (block->bounds_type != UI_BLOCK_BOUNDS_POPUP_CENTER) { + const float offset = handle->prev_block_rect.ymax - block->rect.ymax; + UI_block_translate(block, 0, offset); + block->rect.ymin = handle->prev_block_rect.ymin; + } + } + + handle->prev_block_rect = block->rect; + + /* the block and buttons were positioned in window space as in 2.4x, now + * these menu blocks are regions so we bring it back to region space. + * additionally we add some padding for the menu shadow or rounded menus */ + region->winrct.xmin = block->rect.xmin - margin; + region->winrct.xmax = block->rect.xmax + margin; + region->winrct.ymin = block->rect.ymin - margin; + region->winrct.ymax = block->rect.ymax + UI_POPUP_MENU_TOP; + + UI_block_translate(block, -region->winrct.xmin, -region->winrct.ymin); + + /* apply scroll offset */ + if (handle->scrolloffset != 0.0f) { + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + bt->rect.ymin += handle->scrolloffset; + bt->rect.ymax += handle->scrolloffset; + } + } + } + + if (block_old) { + block->oldblock = block_old; + UI_block_update_from_old(C, block); + UI_blocklist_free_inactive(C, region); + } + + /* checks which buttons are visible, sets flags to prevent draw (do after region init) */ + ui_popup_block_scrolltest(block); + + /* adds subwindow */ + ED_region_floating_init(region); + + /* get winmat now that we actually have the subwindow */ + wmGetProjectionMatrix(block->winmat, ®ion->winrct); + + /* notify change and redraw */ + ED_region_tag_redraw(region); + + ED_region_update_rect(region); + +#ifdef DEBUG + window->eventstate = event_back; + window->event_last_handled = event_last_back; +#endif + + return block; +} + +uiPopupBlockHandle *ui_popup_block_create(bContext *C, + ARegion *butregion, + uiBut *but, + uiBlockCreateFunc create_func, + uiBlockHandleCreateFunc handle_create_func, + void *arg, + uiFreeArgFunc arg_free) +{ + wmWindow *window = CTX_wm_window(C); + uiBut *activebut = UI_context_active_but_get(C); + static ARegionType type; + ARegion *region; + uiBlock *block; + + /* disable tooltips from buttons below */ + if (activebut) { + UI_but_tooltip_timer_remove(C, activebut); + } + /* standard cursor by default */ + WM_cursor_set(window, WM_CURSOR_DEFAULT); + + /* create handle */ + uiPopupBlockHandle *handle = MEM_cnew<uiPopupBlockHandle>(__func__); + + /* store context for operator */ + handle->ctx_area = CTX_wm_area(C); + handle->ctx_region = CTX_wm_region(C); + + /* store vars to refresh popup (RGN_REFRESH_UI) */ + handle->popup_create_vars.create_func = create_func; + handle->popup_create_vars.handle_create_func = handle_create_func; + handle->popup_create_vars.arg = arg; + handle->popup_create_vars.arg_free = arg_free; + handle->popup_create_vars.but = but; + handle->popup_create_vars.butregion = but ? butregion : nullptr; + copy_v2_v2_int(handle->popup_create_vars.event_xy, window->eventstate->xy); + + /* don't allow by default, only if popup type explicitly supports it */ + handle->can_refresh = false; + + /* create area region */ + region = ui_region_temp_add(CTX_wm_screen(C)); + handle->region = region; + + memset(&type, 0, sizeof(ARegionType)); + type.draw = ui_block_region_draw; + type.layout = ui_block_region_refresh; + type.regionid = RGN_TYPE_TEMPORARY; + region->type = &type; + + UI_region_handlers_add(®ion->handlers); + + block = ui_popup_block_refresh(C, handle, butregion, but); + handle = block->handle; + + /* keep centered on window resizing */ + if (block->bounds_type == UI_BLOCK_BOUNDS_POPUP_CENTER) { + type.listener = ui_block_region_popup_window_listener; + } + + return handle; +} + +void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle) +{ + /* If this popup is created from a popover which does NOT have keep-open flag set, + * then close the popover too. We could extend this to other popup types too. */ + ARegion *region = handle->popup_create_vars.butregion; + if (region != nullptr) { + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + if (block->handle && (block->flag & UI_BLOCK_POPOVER) && + (block->flag & UI_BLOCK_KEEP_OPEN) == 0) { + uiPopupBlockHandle *menu = block->handle; + menu->menuretval = UI_RETURN_OK; + } + } + } + + if (handle->popup_create_vars.arg_free) { + handle->popup_create_vars.arg_free(handle->popup_create_vars.arg); + } + + ui_popup_block_remove(C, handle); + + MEM_freeN(handle); +} + +/** \} */ |