Blender V4.3
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
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#include "BLI_utildefines.h"
37
38#include "BKE_context.hh"
39#include "BKE_report.hh"
40#include "BKE_screen.hh"
41
42#include "WM_api.hh"
43#include "WM_types.hh"
44
45#include "UI_interface.hh"
46
47#include "interface_intern.hh"
48
49/* -------------------------------------------------------------------- */
53struct uiPopover {
58
59 /* Needed for keymap removal. */
63
66
67 /* Size in pixels (ui scale applied). */
69
70#ifdef USE_UI_POPOVER_ONCE
71 bool is_once;
72#endif
73};
74
80 ARegion *region,
81 uiPopover *pup,
82 wmOperatorCallContext opcontext)
83{
84 BLI_assert(pup->ui_size_x != 0);
85
86 const uiStyle *style = UI_style_get_dpi();
87
88 pup->block = UI_block_begin(C, region, __func__, UI_EMBOSS);
89
91#ifdef USE_UI_POPOVER_ONCE
92 if (pup->is_once) {
94 }
95#endif
96
98 pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, pup->ui_size_x, 0, 0, style);
99
100 uiLayoutSetOperatorContext(pup->layout, opcontext);
101
102 if (pup->but) {
103 if (pup->but->context) {
105 }
106 }
107}
108
109static uiBlock *ui_block_func_POPOVER(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
110{
111 uiPopover *pup = static_cast<uiPopover *>(arg_pup);
112
113 /* Create UI block and layout now if it wasn't done between begin/end. */
114 if (!pup->layout) {
115 ui_popover_create_block(C, handle->region, pup, WM_OP_INVOKE_REGION_WIN);
116
117 if (pup->popover_func) {
118 pup->block->handle = handle;
119 pup->popover_func(C, pup->layout, const_cast<PanelType *>(pup->panel_type));
120 pup->block->handle = nullptr;
121 }
122
123 pup->layout = nullptr;
124 }
125
126 /* Setup and resolve UI layout for block. */
127 uiBlock *block = pup->block;
128 int width, height;
129
130 /* in some cases we create the block before the region,
131 * so we set it delayed here if necessary */
132 if (BLI_findindex(&handle->region->uiblocks, block) == -1) {
133 UI_block_region_set(block, handle->region);
134 }
135
136 UI_block_layout_resolve(block, &width, &height);
138
139 const int block_margin = U.widget_unit / 2;
140
141 if (pup->but) {
142 /* For a header menu we set the direction automatic. */
143 block->minbounds = BLI_rctf_size_x(&pup->but->rect);
144 UI_block_bounds_set_normal(block, block_margin);
145
146 /* If menu slides out of other menu, override direction. */
147 const bool slideout = ui_block_is_menu(pup->but->block);
148 if (slideout) {
150 }
151
152 /* Store the button location for positioning the popover arrow hint. */
153 if (!handle->refresh) {
154 float center[2] = {BLI_rctf_cent_x(&pup->but->rect), BLI_rctf_cent_y(&pup->but->rect)};
155 ui_block_to_window_fl(handle->ctx_region, pup->but->block, &center[0], &center[1]);
156 /* These variables aren't used for popovers,
157 * we could add new variables if there is a conflict. */
158 block->bounds_offset[0] = int(center[0]);
159 block->bounds_offset[1] = int(center[1]);
160 copy_v2_v2_int(handle->prev_bounds_offset, block->bounds_offset);
161 }
162 else {
163 copy_v2_v2_int(block->bounds_offset, handle->prev_bounds_offset);
164 }
165
166 if (!slideout) {
167 ARegion *region = CTX_wm_region(C);
168
169 if (region && region->panels.first) {
170 /* For regions with panels, prefer to open to top so we can
171 * see the values of the buttons below changing. */
173 }
174 /* Prefer popover from header to be positioned into the editor. */
175 else if (region) {
176 if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) {
177 if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_BOTTOM) {
179 }
180 }
181 }
182 }
183
184 /* Estimated a maximum size so we don't go off-screen for low height
185 * areas near the bottom of the window on refreshes. */
186 handle->max_size_y = UI_UNIT_Y * 16.0f;
187 }
188 else if (pup->panel_type &&
190 {
193 UI_block_direction_set(block, block->direction);
195
196 const int bounds_offset[2] = {
199 };
200 UI_block_bounds_set_popup(block, block_margin, bounds_offset);
201 }
202 else {
203 /* Not attached to a button. */
204 int bounds_offset[2] = {0, 0};
207 UI_block_direction_set(block, block->direction);
209
210 if (!handle->refresh) {
211 uiBut *but = nullptr;
212 uiBut *but_first = nullptr;
213 LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) {
214 if ((but_first == nullptr) && ui_but_is_editable(but_iter)) {
215 but_first = but_iter;
216 }
217 if (but_iter->flag & (UI_SELECT | UI_SELECT_DRAW)) {
218 but = but_iter;
219 break;
220 }
221 }
222
223 if (but) {
224 bounds_offset[0] = -(but->rect.xmin + 0.8f * BLI_rctf_size_x(&but->rect));
225 bounds_offset[1] = -BLI_rctf_cent_y(&but->rect);
226 }
227 else {
228 bounds_offset[0] = -(pup->ui_size_x / 2);
229 bounds_offset[1] = but_first ? -BLI_rctf_cent_y(&but_first->rect) : (UI_UNIT_Y / 2);
230 }
231 copy_v2_v2_int(handle->prev_bounds_offset, bounds_offset);
232 }
233 else {
234 copy_v2_v2_int(bounds_offset, handle->prev_bounds_offset);
235 }
236
237 UI_block_bounds_set_popup(block, block_margin, bounds_offset);
238 }
239
240 return block;
241}
242
243static void ui_block_free_func_POPOVER(void *arg_pup)
244{
245 uiPopover *pup = static_cast<uiPopover *>(arg_pup);
246 if (pup->keymap != nullptr) {
247 wmWindow *window = pup->window;
249 }
250 MEM_delete(pup);
251}
252
254 ARegion *butregion,
255 uiBut *but,
256 uiPopoverCreateFunc popover_func,
257 const PanelType *panel_type)
258{
259 wmWindow *window = CTX_wm_window(C);
260 const uiStyle *style = UI_style_get_dpi();
261
262 /* Create popover, buttons are created from callback. */
263 uiPopover *pup = MEM_new<uiPopover>(__func__);
264 pup->but = but;
265
266 /* FIXME: maybe one day we want non panel popovers? */
267 {
268 const int ui_units_x = (panel_type->ui_units_x == 0) ? UI_POPOVER_WIDTH_UNITS :
269 panel_type->ui_units_x;
270 /* Scale width by changes to Text Style point size. */
271 pup->ui_size_x = ui_units_x * U.widget_unit *
273 }
274
275 pup->popover_func = popover_func;
276 pup->panel_type = panel_type;
277
278#ifdef USE_UI_POPOVER_ONCE
279 {
280 /* Ideally this would be passed in. */
281 const wmEvent *event = window->eventstate;
282 pup->is_once = (event->type == LEFTMOUSE) && (event->val == KM_PRESS);
283 }
284#endif
285
286 /* Create popup block. */
288 C, butregion, but, nullptr, ui_block_func_POPOVER, pup, ui_block_free_func_POPOVER, true);
289
290 /* Add handlers. If attached to a button, the button will already
291 * add a modal handler and pass on events. */
292 if (!but) {
293 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
295 handle->popup = true;
296 }
297
298 return handle;
299}
300
303/* -------------------------------------------------------------------- */
307int UI_popover_panel_invoke(bContext *C, const char *idname, bool keep_open, ReportList *reports)
308{
309 uiLayout *layout;
310 PanelType *pt = WM_paneltype_find(idname, true);
311 if (pt == nullptr) {
312 BKE_reportf(reports, RPT_ERROR, "Panel \"%s\" not found", idname);
313 return OPERATOR_CANCELLED;
314 }
315
316 if (pt->poll && (pt->poll(C, pt) == false)) {
317 /* cancel but allow event to pass through, just like operators do */
319 }
320
321 uiBlock *block = nullptr;
322 if (keep_open) {
324 C, nullptr, nullptr, ui_item_paneltype_func, pt);
325 uiPopover *pup = static_cast<uiPopover *>(handle->popup_create_vars.arg);
326 block = pup->block;
327 }
328 else {
329 uiPopover *pup = UI_popover_begin(C, U.widget_unit * pt->ui_units_x, false);
330 layout = UI_popover_layout(pup);
331 UI_paneltype_draw(C, pt, layout);
332 UI_popover_end(C, pup, nullptr);
333 block = pup->block;
334 }
335
336 if (block) {
337 uiPopupBlockHandle *handle = static_cast<uiPopupBlockHandle *>(block->handle);
338 UI_block_active_only_flagged_buttons(C, handle->region, block);
339 }
340 return OPERATOR_INTERFACE;
341}
342
345/* -------------------------------------------------------------------- */
349uiPopover *UI_popover_begin(bContext *C, int ui_menu_width, bool from_active_button)
350{
351 uiPopover *pup = MEM_new<uiPopover>(__func__);
352 if (ui_menu_width == 0) {
353 ui_menu_width = U.widget_unit * UI_POPOVER_WIDTH_UNITS;
354 }
355 pup->ui_size_x = ui_menu_width;
356
357 ARegion *butregion = nullptr;
358 uiBut *but = nullptr;
359
360 if (from_active_button) {
361 butregion = CTX_wm_region(C);
362 but = UI_region_active_but_get(butregion);
363 if (but == nullptr) {
364 butregion = nullptr;
365 }
366 }
367
368 pup->but = but;
369 pup->butregion = butregion;
370
371 /* Operator context default same as menus, change if needed. */
373
374 /* Create in advance so we can let buttons point to #uiPopupBlockHandle::retvalue
375 * (and other return values) already. */
376 pup->block->handle = MEM_cnew<uiPopupBlockHandle>(__func__);
377
378 return pup;
379}
380
381static void popover_keymap_fn(wmKeyMap * /*keymap*/, wmKeyMapItem * /*kmi*/, void *user_data)
382{
383 uiPopover *pup = static_cast<uiPopover *>(user_data);
385}
386
388{
389 wmWindow *window = CTX_wm_window(C);
390
391 if (keymap) {
392 /* Add so we get keymaps shown in the buttons. */
394 pup->keymap = keymap;
397 }
398
399 /* Create popup block. No refresh support since the buttons were created
400 * between begin/end and we have no callback to recreate them. */
402 pup->butregion,
403 pup->but,
404 nullptr,
406 pup,
408 false);
409
410 /* Add handlers. */
411 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
413 handle->popup = true;
414
415 /* Re-add so it gets priority. */
416 if (keymap) {
419 }
420
421 pup->window = window;
422
423 /* TODO(@ideasman42): we may want to make this configurable.
424 * The begin/end type of calling popups doesn't allow 'can_refresh' to be set.
425 * For now close this style of popovers when accessed. */
427}
428
430{
431 return pup->layout;
432}
433
434#ifdef USE_UI_POPOVER_ONCE
436{
437 pup->is_once = false;
438}
439#endif
440
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:50
void BLI_addhead(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:90
#define LISTBASE_FOREACH(type, var, list)
void BLI_remlink(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:130
int BLI_findindex(const struct ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
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:184
BLI_INLINE float BLI_rctf_cent_x(const struct rctf *rct)
Definition BLI_rect.h:180
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:197
#define RGN_ALIGN_ENUM_FROM_MASK(align)
@ RGN_ALIGN_BOTTOM
#define RGN_TYPE_IS_HEADER_ANY(regiontype)
@ OPERATOR_PASS_THROUGH
Read Guarded memory(de)allocation.
@ UI_RETURN_OK
#define UI_UNIT_Y
@ UI_EMBOSS
void UI_block_theme_style_set(uiBlock *block, char theme_style)
@ UI_BLOCK_POPOVER_ONCE
@ UI_BLOCK_LOOP
@ UI_BLOCK_KEEP_OPEN
@ UI_BLOCK_SHOW_SHORTCUT_ALWAYS
@ UI_BLOCK_POPOVER
void UI_block_bounds_set_popup(uiBlock *block, int addval, const int bounds_offset[2])
Definition interface.cc:590
uiBut * UI_region_active_but_get(const ARegion *region)
void UI_block_bounds_set_normal(uiBlock *block, int addval)
Definition interface.cc:574
uiLayout * UI_block_layout(uiBlock *block, int dir, int type, int x, int y, int size, int em, int padding, const uiStyle *style)
const uiStyle * UI_style_get_dpi()
void UI_block_flag_disable(uiBlock *block, int flag)
uiBlock * UI_block_begin(const bContext *C, ARegion *region, std::string name, eUIEmbossType emboss)
bool UI_block_active_only_flagged_buttons(const bContext *C, ARegion *region, uiBlock *block)
void UI_paneltype_draw(bContext *C, PanelType *pt, uiLayout *layout)
@ UI_DIR_CENTER_X
@ UI_DIR_DOWN
@ UI_DIR_RIGHT
@ UI_DIR_UP
@ UI_LAYOUT_VERTICAL
@ UI_LAYOUT_PANEL
@ UI_BLOCK_THEME_STYLE_POPUP
void UI_popup_handlers_add(bContext *C, ListBase *handlers, uiPopupBlockHandle *popup, char flag)
void UI_block_layout_resolve(uiBlock *block, int *r_x, int *r_y)
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)
void uiLayoutSetOperatorContext(uiLayout *layout, wmOperatorCallContext opcontext)
void uiLayoutContextCopy(uiLayout *layout, const bContextStore *context)
@ KM_PRESS
Definition WM_types.hh:284
wmOperatorCallContext
Definition WM_types.hh:216
@ WM_OP_INVOKE_REGION_WIN
Definition WM_types.hh:219
@ WM_OP_EXEC_REGION_WIN
Definition WM_types.hh:226
unsigned int U
Definition btGjkEpa3.h:78
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
void ui_block_to_window_fl(const ARegion *region, const uiBlock *block, float *x, float *y)
Definition interface.cc:135
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
@ UI_SELECT_DRAW
@ UI_SELECT
std::function< void(bContext *, uiLayout *, PanelType *)> uiPopoverCreateFunc
#define UI_MENU_WIDTH_MIN
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)
uiLayout * UI_popover_layout(uiPopover *pup)
uiPopover * UI_popover_begin(bContext *C, int ui_menu_width, bool from_active_button)
int UI_popover_panel_invoke(bContext *C, const char *idname, bool keep_open, ReportList *reports)
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)
bool(* poll)(const bContext *C, PanelType *pt)
blender::float2 offset_units_xy
float xmin
uiPopupBlockHandle * handle
int bounds_offset[2]
ListBase buttons
uiBlock * block
const bContextStore * context
uiPopoverCreateFunc popover_func
const PanelType * panel_type
wmEventHandler_Keymap * keymap_handler
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 char *idname, bool quiet)