Blender V4.5
interface_region_popover.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2008 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
27
28#include "MEM_guardedalloc.h"
29
30#include "DNA_userdef_types.h"
31
32#include "BLI_listbase.h"
33
34#include "BLI_math_vector.h"
35#include "BLI_rect.h"
36
37#include "BKE_context.hh"
38#include "BKE_report.hh"
39#include "BKE_screen.hh"
40
41#include "WM_api.hh"
42#include "WM_types.hh"
43
44#include "UI_interface.hh"
45
46#include "interface_intern.hh"
47
48/* -------------------------------------------------------------------- */
51
52struct uiPopover {
57
58 /* Needed for keymap removal. */
62
65
66 /* Size in pixels (ui scale applied). */
68
69#ifdef USE_UI_POPOVER_ONCE
70 bool is_once;
71#endif
72};
73
79 ARegion *region,
80 uiPopover *pup,
81 wmOperatorCallContext opcontext)
82{
83 BLI_assert(pup->ui_size_x != 0);
84
85 const uiStyle *style = UI_style_get_dpi();
86
87 pup->block = UI_block_begin(C, region, __func__, blender::ui::EmbossType::Emboss);
88
90#ifdef USE_UI_POPOVER_ONCE
91 if (pup->is_once) {
93 }
94#endif
95
97 pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, pup->ui_size_x, 0, 0, style);
98
99 uiLayoutSetOperatorContext(pup->layout, opcontext);
100
101 if (pup->but) {
102 if (pup->but->context) {
104 }
105 }
106}
107
109{
110 uiPopover *pup = static_cast<uiPopover *>(arg_pup);
111
112 /* Create UI block and layout now if it wasn't done between begin/end. */
113 if (!pup->layout) {
115
116 if (pup->popover_func) {
117 pup->block->handle = handle;
118 pup->popover_func(C, pup->layout, const_cast<PanelType *>(pup->panel_type));
119 pup->block->handle = nullptr;
120 }
121
122 pup->layout = nullptr;
123 }
124
125 /* Setup and resolve UI layout for block. */
126 uiBlock *block = pup->block;
127 int width, height;
128
129 /* in some cases we create the block before the region,
130 * so we set it delayed here if necessary */
131 if (BLI_findindex(&handle->region->runtime->uiblocks, block) == -1) {
132 UI_block_region_set(block, handle->region);
133 }
134
135 UI_block_layout_resolve(block, &width, &height);
137
138 const int block_margin = U.widget_unit / 2;
139
140 if (pup->but) {
141 /* For a header menu we set the direction automatic. */
142 block->minbounds = BLI_rctf_size_x(&pup->but->rect);
143 UI_block_bounds_set_normal(block, block_margin);
144
145 /* If menu slides out of other menu, override direction. */
146 const bool slideout = ui_block_is_menu(pup->but->block);
147 if (slideout) {
149 }
150
151 /* Store the button location for positioning the popover arrow hint. */
152 if (!handle->refresh) {
153 float center[2] = {BLI_rctf_cent_x(&pup->but->rect), BLI_rctf_cent_y(&pup->but->rect)};
154 ui_block_to_window_fl(handle->ctx_region, pup->but->block, &center[0], &center[1]);
155 /* These variables aren't used for popovers,
156 * we could add new variables if there is a conflict. */
157 block->bounds_offset[0] = int(center[0]);
158 block->bounds_offset[1] = int(center[1]);
160 }
161 else {
163 }
164
165 if (!slideout) {
166 ARegion *region = CTX_wm_region(C);
167
168 if (region && region->panels.first) {
169 /* For regions with panels, prefer to open to top so we can
170 * see the values of the buttons below changing. */
172 }
173 /* Prefer popover from header to be positioned into the editor. */
174 else if (region) {
175 if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) {
178 }
179 }
180 }
181 }
182
183 /* Estimated a maximum size so we don't go off-screen for low height
184 * areas near the bottom of the window on refreshes. */
185 handle->max_size_y = UI_UNIT_Y * 16.0f;
186 }
187 else if (pup->panel_type &&
189 {
192 UI_block_direction_set(block, block->direction);
194
195 const int bounds_offset[2] = {
196 int(pup->panel_type->offset_units_xy.x * UI_UNIT_X),
197 int(pup->panel_type->offset_units_xy.y * UI_UNIT_Y),
198 };
199 UI_block_bounds_set_popup(block, block_margin, bounds_offset);
200 }
201 else {
202 /* Not attached to a button. */
203 int bounds_offset[2] = {0, 0};
206 UI_block_direction_set(block, block->direction);
208
209 if (!handle->refresh) {
210 uiBut *but = nullptr;
211 uiBut *but_first = nullptr;
212 for (const std::unique_ptr<uiBut> &but_iter : block->buttons) {
213 if ((but_first == nullptr) && ui_but_is_editable(but_iter.get())) {
214 but_first = but_iter.get();
215 }
216 if (but_iter->flag & (UI_SELECT | UI_SELECT_DRAW)) {
217 but = but_iter.get();
218 break;
219 }
220 }
221
222 if (but) {
223 bounds_offset[0] = -(but->rect.xmin + 0.8f * BLI_rctf_size_x(&but->rect));
224 bounds_offset[1] = -BLI_rctf_cent_y(&but->rect);
225 }
226 else {
227 bounds_offset[0] = -(pup->ui_size_x / 2);
228 bounds_offset[1] = but_first ? -BLI_rctf_cent_y(&but_first->rect) : (UI_UNIT_Y / 2);
229 }
230 copy_v2_v2_int(handle->prev_bounds_offset, bounds_offset);
231 }
232 else {
233 copy_v2_v2_int(bounds_offset, handle->prev_bounds_offset);
234 }
235
236 UI_block_bounds_set_popup(block, block_margin, bounds_offset);
237 }
238
239 return block;
240}
241
242static void ui_block_free_func_POPOVER(void *arg_pup)
243{
244 uiPopover *pup = static_cast<uiPopover *>(arg_pup);
245 if (pup->keymap != nullptr) {
246 wmWindow *window = pup->window;
248 }
249 MEM_delete(pup);
250}
251
253 ARegion *butregion,
254 uiBut *but,
255 uiPopoverCreateFunc popover_func,
256 const PanelType *panel_type)
257{
258 wmWindow *window = CTX_wm_window(C);
259 const uiStyle *style = UI_style_get_dpi();
260
261 /* Create popover, buttons are created from callback. */
262 uiPopover *pup = MEM_new<uiPopover>(__func__);
263 pup->but = but;
264
265 /* FIXME: maybe one day we want non panel popovers? */
266 {
267 const int ui_units_x = (panel_type->ui_units_x == 0) ? UI_POPOVER_WIDTH_UNITS :
268 panel_type->ui_units_x;
269 /* Scale width by changes to Text Style point size. */
270 pup->ui_size_x = ui_units_x * U.widget_unit * (style->widget.points / UI_DEFAULT_TEXT_POINTS);
271 }
272
273 pup->popover_func = popover_func;
274 pup->panel_type = panel_type;
275
276#ifdef USE_UI_POPOVER_ONCE
277 {
278 /* Ideally this would be passed in. */
279 const wmEvent *event = window->eventstate;
280 pup->is_once = (event->type == LEFTMOUSE) && (event->val == KM_PRESS);
281 }
282#endif
283
284 /* Create popup block. */
286 C, butregion, but, nullptr, ui_block_func_POPOVER, pup, ui_block_free_func_POPOVER, true);
287
288 /* Add handlers. If attached to a button, the button will already
289 * add a modal handler and pass on events. */
290 if (!but) {
291 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
293 handle->popup = true;
294 }
295
296 return handle;
297}
298
300
301/* -------------------------------------------------------------------- */
304
306 const char *idname,
307 bool keep_open,
309{
310 uiLayout *layout;
311 PanelType *pt = WM_paneltype_find(idname, true);
312 if (pt == nullptr) {
313 BKE_reportf(reports, RPT_ERROR, "Panel \"%s\" not found", idname);
314 return OPERATOR_CANCELLED;
315 }
316
317 if (pt->poll && (pt->poll(C, pt) == false)) {
318 /* cancel but allow event to pass through, just like operators do */
320 }
321
322 uiBlock *block = nullptr;
323 if (keep_open) {
325 C, nullptr, nullptr, ui_item_paneltype_func, pt);
326 uiPopover *pup = static_cast<uiPopover *>(handle->popup_create_vars.arg);
327 block = pup->block;
328 }
329 else {
330 uiPopover *pup = UI_popover_begin(C, U.widget_unit * pt->ui_units_x, false);
331 layout = UI_popover_layout(pup);
332 UI_paneltype_draw(C, pt, layout);
333 UI_popover_end(C, pup, nullptr);
334 block = pup->block;
335 }
336
337 if (block) {
338 uiPopupBlockHandle *handle = block->handle;
340 }
341 return OPERATOR_INTERFACE;
342}
343
345
346/* -------------------------------------------------------------------- */
349
350uiPopover *UI_popover_begin(bContext *C, int ui_menu_width, bool from_active_button)
351{
352 uiPopover *pup = MEM_new<uiPopover>(__func__);
353 if (ui_menu_width == 0) {
354 ui_menu_width = U.widget_unit * UI_POPOVER_WIDTH_UNITS;
355 }
356 pup->ui_size_x = ui_menu_width;
357
358 ARegion *butregion = nullptr;
359 uiBut *but = nullptr;
360
361 if (from_active_button) {
362 butregion = CTX_wm_region(C);
363 but = UI_region_active_but_get(butregion);
364 if (but == nullptr) {
365 butregion = nullptr;
366 }
367 }
368
369 pup->but = but;
370 pup->butregion = butregion;
371
372 /* Operator context default same as menus, change if needed. */
374
375 /* Create in advance so we can let buttons point to #uiPopupBlockHandle::retvalue
376 * (and other return values) already. */
377 pup->block->handle = MEM_new<uiPopupBlockHandle>(__func__);
378
379 return pup;
380}
381
382static void popover_keymap_fn(wmKeyMap * /*keymap*/, wmKeyMapItem * /*kmi*/, void *user_data)
383{
384 uiPopover *pup = static_cast<uiPopover *>(user_data);
386}
387
389{
390 wmWindow *window = CTX_wm_window(C);
391
392 if (keymap) {
393 /* Add so we get keymaps shown in the buttons. */
395 pup->keymap = keymap;
398 }
399
400 /* Create popup block. No refresh support since the buttons were created
401 * between begin/end and we have no callback to recreate them. */
403 pup->butregion,
404 pup->but,
405 nullptr,
407 pup,
409 false);
410
411 /* Add handlers. */
412 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
414 handle->popup = true;
415
416 /* Re-add so it gets priority. */
417 if (keymap) {
420 }
421
422 pup->window = window;
423
424 /* TODO(@ideasman42): we may want to make this configurable.
425 * The begin/end type of calling popups doesn't allow 'can_refresh' to be set.
426 * For now close this style of popovers when accessed. */
428}
429
431{
432 return pup->layout;
433}
434
435#ifdef USE_UI_POPOVER_ONCE
437{
438 pup->is_once = false;
439}
440#endif
441
wmWindow * CTX_wm_window(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
#define BLI_assert(a)
Definition BLI_assert.h:46
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
void BLI_addhead(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:91
MINLINE void copy_v2_v2_int(int r[2], const int a[2])
BLI_INLINE float BLI_rctf_cent_y(const struct rctf *rct)
Definition BLI_rect.h:189
BLI_INLINE float BLI_rctf_cent_x(const struct rctf *rct)
Definition BLI_rect.h:185
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
#define RGN_ALIGN_ENUM_FROM_MASK(align)
#define RGN_TYPE_IS_HEADER_ANY(regiontype)
@ RGN_ALIGN_BOTTOM
@ OPERATOR_CANCELLED
@ OPERATOR_INTERFACE
@ OPERATOR_PASS_THROUGH
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
#define UI_UNIT_Y
void UI_block_theme_style_set(uiBlock *block, char theme_style)
uiBlock * UI_block_begin(const bContext *C, ARegion *region, std::string name, blender::ui::EmbossType emboss)
void UI_block_bounds_set_popup(uiBlock *block, int addval, const int bounds_offset[2])
Definition interface.cc:641
@ UI_RETURN_OK
uiBut * UI_region_active_but_get(const ARegion *region)
void UI_block_bounds_set_normal(uiBlock *block, int addval)
Definition interface.cc:625
const uiStyle * UI_style_get_dpi()
void UI_block_flag_disable(uiBlock *block, int flag)
bool UI_block_active_only_flagged_buttons(const bContext *C, ARegion *region, uiBlock *block)
@ UI_BLOCK_THEME_STYLE_POPUP
void UI_popup_handlers_add(bContext *C, ListBase *handlers, uiPopupBlockHandle *popup, char flag)
@ UI_DIR_CENTER_X
@ UI_DIR_DOWN
@ UI_DIR_RIGHT
@ UI_DIR_UP
void UI_block_region_set(uiBlock *block, ARegion *region)
void UI_block_direction_set(uiBlock *block, char direction)
#define UI_DEFAULT_TEXT_POINTS
#define UI_UNIT_X
void UI_block_flag_enable(uiBlock *block, int flag)
@ UI_BLOCK_POPOVER_ONCE
@ UI_BLOCK_LOOP
@ UI_BLOCK_KEEP_OPEN
@ UI_BLOCK_SHOW_SHORTCUT_ALWAYS
@ UI_BLOCK_POPOVER
@ UI_LAYOUT_VERTICAL
uiLayout * UI_block_layout(uiBlock *block, int dir, int type, int x, int y, int size, int em, int padding, const uiStyle *style)
@ UI_LAYOUT_PANEL
void UI_paneltype_draw(bContext *C, PanelType *pt, uiLayout *layout)
void UI_block_layout_resolve(uiBlock *block, int *r_x, int *r_y)
void uiLayoutSetOperatorContext(uiLayout *layout, wmOperatorCallContext opcontext)
void uiLayoutContextCopy(uiLayout *layout, const bContextStore *context)
@ KM_PRESS
Definition WM_types.hh:308
ReportList * reports
Definition WM_types.hh:1025
wmOperatorCallContext
Definition WM_types.hh:236
@ WM_OP_INVOKE_REGION_WIN
Definition WM_types.hh:239
@ WM_OP_EXEC_REGION_WIN
Definition WM_types.hh:246
#define U
void ui_block_to_window_fl(const ARegion *region, const uiBlock *block, float *x, float *y)
Definition interface.cc:137
uiPopupBlockHandle * ui_popup_block_create(bContext *C, ARegion *butregion, uiBut *but, uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func, void *arg, uiFreeArgFunc arg_free, bool can_refresh)
void ui_item_paneltype_func(bContext *C, uiLayout *layout, void *arg_pt)
#define UI_POPOVER_WIDTH_UNITS
bool ui_block_is_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT
bool ui_but_is_editable(const uiBut *but) ATTR_WARN_UNUSED_RESULT
std::function< void(bContext *, uiLayout *, PanelType *)> uiPopoverCreateFunc
#define UI_MENU_WIDTH_MIN
@ UI_SELECT_DRAW
@ UI_SELECT
void UI_popover_end(bContext *C, uiPopover *pup, wmKeyMap *keymap)
static void ui_block_free_func_POPOVER(void *arg_pup)
void UI_popover_once_clear(uiPopover *pup)
static uiBlock * ui_block_func_POPOVER(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
wmOperatorStatus UI_popover_panel_invoke(bContext *C, const char *idname, bool keep_open, ReportList *reports)
uiLayout * UI_popover_layout(uiPopover *pup)
uiPopover * UI_popover_begin(bContext *C, int ui_menu_width, bool from_active_button)
uiPopupBlockHandle * ui_popover_panel_create(bContext *C, ARegion *butregion, uiBut *but, uiPopoverCreateFunc popover_func, const PanelType *panel_type)
static void popover_keymap_fn(wmKeyMap *, wmKeyMapItem *, void *user_data)
static void ui_popover_create_block(bContext *C, ARegion *region, uiPopover *pup, wmOperatorCallContext opcontext)
ARegionRuntimeHandle * runtime
ListBase panels
void * first
bool(* poll)(const bContext *C, PanelType *pt)
blender::float2 offset_units_xy
float xmin
blender::Vector< std::unique_ptr< uiBut > > buttons
uiPopupBlockHandle * handle
int bounds_offset[2]
uiBlock * block
const bContextStore * context
uiPopoverCreateFunc popover_func
const PanelType * panel_type
wmEventHandler_Keymap * keymap_handler
uiPopupBlockCreate popup_create_vars
uiFontStyle widget
struct wmEvent * eventstate
void WM_event_remove_keymap_handler(ListBase *handlers, wmKeyMap *keymap)
wmEventHandler_Keymap * WM_event_add_keymap_handler_priority(ListBase *handlers, wmKeyMap *keymap, int)
void WM_event_set_keymap_handler_post_callback(wmEventHandler_Keymap *handler, void(keymap_tag)(wmKeyMap *keymap, wmKeyMapItem *kmi, void *user_data), void *user_data)
void WM_event_add_mousemove(wmWindow *win)
@ LEFTMOUSE
PanelType * WM_paneltype_find(const StringRef idname, bool quiet)