diff options
Diffstat (limited to 'source/blender/editors/interface/interface_region_popover.cc')
-rw-r--r-- | source/blender/editors/interface/interface_region_popover.cc | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/source/blender/editors/interface/interface_region_popover.cc b/source/blender/editors/interface/interface_region_popover.cc new file mode 100644 index 00000000000..2e10261a4f7 --- /dev/null +++ b/source/blender/editors/interface/interface_region_popover.cc @@ -0,0 +1,421 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Pop-Over Region + * + * \note This is very close to 'interface_region_menu_popup.c' + * + * We could even merge them, however menu logic is already over-loaded. + * PopOver's have the following differences. + * + * - UI is not constrained to a list. + * - Pressing a button won't close the pop-over. + * - Different draw style (to show this is has different behavior from a menu). + * - #PanelType are used instead of #MenuType. + * - No menu flipping support. + * - No moving the menu to fit the mouse cursor. + * - No key accelerators to access menu items + * (if we add support they would work differently). + * - No arrow key navigation. + * - No menu memory. + * - No title. + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" + +#include "BLI_listbase.h" + +#include "BLI_math_vector.h" +#include "BLI_rect.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" +#include "BKE_report.h" +#include "BKE_screen.h" + +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" + +#include "interface_intern.h" +#include "interface_regions_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Popup Menu with Callback or String + * \{ */ + +struct uiPopover { + uiBlock *block; + uiLayout *layout; + uiBut *but; + ARegion *butregion; + + /* Needed for keymap removal. */ + wmWindow *window; + wmKeyMap *keymap; + struct wmEventHandler_Keymap *keymap_handler; + + uiMenuCreateFunc menu_func; + void *menu_arg; + + /* Size in pixels (ui scale applied). */ + int ui_size_x; + +#ifdef USE_UI_POPOVER_ONCE + bool is_once; +#endif +}; + +static void ui_popover_create_block(bContext *C, uiPopover *pup, wmOperatorCallContext opcontext) +{ + BLI_assert(pup->ui_size_x != 0); + + const uiStyle *style = UI_style_get_dpi(); + + pup->block = UI_block_begin(C, nullptr, __func__, UI_EMBOSS); + UI_block_flag_enable(pup->block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_POPOVER); +#ifdef USE_UI_POPOVER_ONCE + if (pup->is_once) { + UI_block_flag_enable(pup->block, UI_BLOCK_POPOVER_ONCE); + } +#endif + + pup->layout = UI_block_layout( + pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, pup->ui_size_x, 0, 0, style); + + uiLayoutSetOperatorContext(pup->layout, opcontext); + + if (pup->but) { + if (pup->but->context) { + uiLayoutContextCopy(pup->layout, pup->but->context); + } + } + + pup->block->flag |= UI_BLOCK_NO_FLIP; +} + +static uiBlock *ui_block_func_POPOVER(bContext *C, uiPopupBlockHandle *handle, void *arg_pup) +{ + uiPopover *pup = static_cast<uiPopover *>(arg_pup); + + /* Create UI block and layout now if it wasn't done between begin/end. */ + if (!pup->layout) { + ui_popover_create_block(C, pup, WM_OP_INVOKE_REGION_WIN); + + if (pup->menu_func) { + pup->block->handle = handle; + pup->menu_func(C, pup->layout, pup->menu_arg); + pup->block->handle = nullptr; + } + + pup->layout = nullptr; + } + + /* Setup and resolve UI layout for block. */ + uiBlock *block = pup->block; + int width, height; + + UI_block_region_set(block, handle->region); + UI_block_layout_resolve(block, &width, &height); + UI_block_direction_set(block, UI_DIR_DOWN | UI_DIR_CENTER_X); + + const int block_margin = U.widget_unit / 2; + + if (pup->but) { + /* For a header menu we set the direction automatic. */ + block->minbounds = BLI_rctf_size_x(&pup->but->rect); + UI_block_bounds_set_normal(block, block_margin); + + /* If menu slides out of other menu, override direction. */ + const bool slideout = ui_block_is_menu(pup->but->block); + if (slideout) { + UI_block_direction_set(block, UI_DIR_RIGHT); + } + + /* Store the button location for positioning the popover arrow hint. */ + if (!handle->refresh) { + float center[2] = {BLI_rctf_cent_x(&pup->but->rect), BLI_rctf_cent_y(&pup->but->rect)}; + ui_block_to_window_fl(handle->ctx_region, pup->but->block, ¢er[0], ¢er[1]); + /* These variables aren't used for popovers, + * we could add new variables if there is a conflict. */ + block->bounds_offset[0] = (int)center[0]; + block->bounds_offset[1] = (int)center[1]; + copy_v2_v2_int(handle->prev_bounds_offset, block->bounds_offset); + } + else { + copy_v2_v2_int(block->bounds_offset, handle->prev_bounds_offset); + } + + if (!slideout) { + ARegion *region = CTX_wm_region(C); + + if (region && region->panels.first) { + /* For regions with panels, prefer to open to top so we can + * see the values of the buttons below changing. */ + UI_block_direction_set(block, UI_DIR_UP | UI_DIR_CENTER_X); + } + /* Prefer popover from header to be positioned into the editor. */ + else if (region) { + if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) { + if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_BOTTOM) { + UI_block_direction_set(block, UI_DIR_UP | UI_DIR_CENTER_X); + } + } + } + } + + /* Estimated a maximum size so we don't go off-screen for low height + * areas near the bottom of the window on refreshes. */ + handle->max_size_y = UI_UNIT_Y * 16.0f; + } + else { + /* Not attached to a button. */ + int bounds_offset[2] = {0, 0}; + UI_block_flag_enable(block, UI_BLOCK_LOOP); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + UI_block_direction_set(block, block->direction); + block->minbounds = UI_MENU_WIDTH_MIN; + + if (!handle->refresh) { + uiBut *but = nullptr; + uiBut *but_first = nullptr; + LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { + if ((but_first == nullptr) && ui_but_is_editable(but_iter)) { + but_first = but_iter; + } + if (but_iter->flag & (UI_SELECT | UI_SELECT_DRAW)) { + but = but_iter; + break; + } + } + + if (but) { + bounds_offset[0] = -(but->rect.xmin + 0.8f * BLI_rctf_size_x(&but->rect)); + bounds_offset[1] = -BLI_rctf_cent_y(&but->rect); + } + else { + bounds_offset[0] = -(pup->ui_size_x / 2); + bounds_offset[1] = but_first ? -BLI_rctf_cent_y(&but_first->rect) : (UI_UNIT_Y / 2); + } + copy_v2_v2_int(handle->prev_bounds_offset, bounds_offset); + } + else { + copy_v2_v2_int(bounds_offset, handle->prev_bounds_offset); + } + + UI_block_bounds_set_popup(block, block_margin, bounds_offset); + } + + return block; +} + +static void ui_block_free_func_POPOVER(void *arg_pup) +{ + uiPopover *pup = static_cast<uiPopover *>(arg_pup); + if (pup->keymap != nullptr) { + wmWindow *window = pup->window; + WM_event_remove_keymap_handler(&window->modalhandlers, pup->keymap); + } + MEM_freeN(pup); +} + +uiPopupBlockHandle *ui_popover_panel_create( + bContext *C, ARegion *butregion, uiBut *but, uiMenuCreateFunc menu_func, void *arg) +{ + wmWindow *window = CTX_wm_window(C); + const uiStyle *style = UI_style_get_dpi(); + const PanelType *panel_type = (PanelType *)arg; + + /* Create popover, buttons are created from callback. */ + uiPopover *pup = MEM_cnew<uiPopover>(__func__); + pup->but = but; + + /* FIXME: maybe one day we want non panel popovers? */ + { + const int ui_units_x = (panel_type->ui_units_x == 0) ? UI_POPOVER_WIDTH_UNITS : + panel_type->ui_units_x; + /* Scale width by changes to Text Style point size. */ + const int text_points_max = MAX2(style->widget.points, style->widgetlabel.points); + pup->ui_size_x = ui_units_x * U.widget_unit * + (text_points_max / (float)UI_DEFAULT_TEXT_POINTS); + } + + pup->menu_func = menu_func; + pup->menu_arg = arg; + +#ifdef USE_UI_POPOVER_ONCE + { + /* Ideally this would be passed in. */ + const wmEvent *event = window->eventstate; + pup->is_once = (event->type == LEFTMOUSE) && (event->val == KM_PRESS); + } +#endif + + /* Create popup block. */ + uiPopupBlockHandle *handle; + handle = ui_popup_block_create( + C, butregion, but, nullptr, ui_block_func_POPOVER, pup, ui_block_free_func_POPOVER); + handle->can_refresh = true; + + /* Add handlers. If attached to a button, the button will already + * add a modal handler and pass on events. */ + if (!but) { + UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); + WM_event_add_mousemove(window); + handle->popup = true; + } + + return handle; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Standard Popover Panels + * \{ */ + +int UI_popover_panel_invoke(bContext *C, const char *idname, bool keep_open, ReportList *reports) +{ + uiLayout *layout; + PanelType *pt = WM_paneltype_find(idname, true); + if (pt == nullptr) { + BKE_reportf(reports, RPT_ERROR, "Panel \"%s\" not found", idname); + return OPERATOR_CANCELLED; + } + + if (pt->poll && (pt->poll(C, pt) == false)) { + /* cancel but allow event to pass through, just like operators do */ + return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); + } + + uiBlock *block = nullptr; + if (keep_open) { + uiPopupBlockHandle *handle = ui_popover_panel_create( + C, nullptr, nullptr, ui_item_paneltype_func, pt); + uiPopover *pup = static_cast<uiPopover *>(handle->popup_create_vars.arg); + block = pup->block; + } + else { + uiPopover *pup = UI_popover_begin(C, U.widget_unit * pt->ui_units_x, false); + layout = UI_popover_layout(pup); + UI_paneltype_draw(C, pt, layout); + UI_popover_end(C, pup, nullptr); + block = pup->block; + } + + if (block) { + uiPopupBlockHandle *handle = static_cast<uiPopupBlockHandle *>(block->handle); + UI_block_active_only_flagged_buttons(C, handle->region, block); + } + return OPERATOR_INTERFACE; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Popup Menu API with begin & end + * \{ */ + +uiPopover *UI_popover_begin(bContext *C, int ui_menu_width, bool from_active_button) +{ + uiPopover *pup = MEM_cnew<uiPopover>(__func__); + if (ui_menu_width == 0) { + ui_menu_width = U.widget_unit * UI_POPOVER_WIDTH_UNITS; + } + pup->ui_size_x = ui_menu_width; + + ARegion *butregion = nullptr; + uiBut *but = nullptr; + + if (from_active_button) { + butregion = CTX_wm_region(C); + but = UI_region_active_but_get(butregion); + if (but == nullptr) { + butregion = nullptr; + } + } + + pup->but = but; + pup->butregion = butregion; + + /* Operator context default same as menus, change if needed. */ + ui_popover_create_block(C, pup, WM_OP_EXEC_REGION_WIN); + + /* create in advance so we can let buttons point to retval already */ + pup->block->handle = MEM_cnew<uiPopupBlockHandle>(__func__); + + return pup; +} + +static void popover_keymap_fn(wmKeyMap *UNUSED(keymap), wmKeyMapItem *UNUSED(kmi), void *user_data) +{ + uiPopover *pup = static_cast<uiPopover *>(user_data); + pup->block->handle->menuretval = UI_RETURN_OK; +} + +void UI_popover_end(bContext *C, uiPopover *pup, wmKeyMap *keymap) +{ + wmWindow *window = CTX_wm_window(C); + /* Create popup block. No refresh support since the buttons were created + * between begin/end and we have no callback to recreate them. */ + uiPopupBlockHandle *handle; + + if (keymap) { + /* Add so we get keymaps shown in the buttons. */ + UI_block_flag_enable(pup->block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS); + pup->keymap = keymap; + pup->keymap_handler = WM_event_add_keymap_handler_priority(&window->modalhandlers, keymap, 0); + WM_event_set_keymap_handler_post_callback(pup->keymap_handler, popover_keymap_fn, pup); + } + + handle = ui_popup_block_create(C, + pup->butregion, + pup->but, + nullptr, + ui_block_func_POPOVER, + pup, + ui_block_free_func_POPOVER); + + /* Add handlers. */ + UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); + WM_event_add_mousemove(window); + handle->popup = true; + + /* Re-add so it gets priority. */ + if (keymap) { + BLI_remlink(&window->modalhandlers, pup->keymap_handler); + BLI_addhead(&window->modalhandlers, pup->keymap_handler); + } + + pup->window = window; + + /* TODO(campbell): we may want to make this configurable. + * The begin/end stype of calling popups doesn't allow 'can_refresh' to be set. + * For now close this style of popovers when accessed. */ + UI_block_flag_disable(pup->block, UI_BLOCK_KEEP_OPEN); + + /* Panels are created flipped (from event handling POV). */ + pup->block->flag ^= UI_BLOCK_IS_FLIP; +} + +uiLayout *UI_popover_layout(uiPopover *pup) +{ + return pup->layout; +} + +#ifdef USE_UI_POPOVER_ONCE +void UI_popover_once_clear(uiPopover *pup) +{ + pup->is_once = false; +} +#endif + +/** \} */ |