Blender V5.0
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
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,
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
99 0,
100 0,
101 pup->ui_size_x,
102 0,
103 0,
104 style);
105
106 pup->layout->operator_context_set(opcontext);
107
108 if (pup->but) {
109 if (pup->but->context) {
110 pup->layout->context_copy(pup->but->context);
111 }
112 }
113}
114
116{
117 uiPopover *pup = static_cast<uiPopover *>(arg_pup);
118
119 /* Create UI block and layout now if it wasn't done between begin/end. */
120 if (!pup->layout) {
122
123 if (pup->popover_func) {
124 pup->block->handle = handle;
125 pup->popover_func(C, pup->layout, const_cast<PanelType *>(pup->panel_type));
126 pup->block->handle = nullptr;
127 }
128
129 pup->layout = nullptr;
130 }
131
132 /* Setup and resolve UI layout for block. */
133 uiBlock *block = pup->block;
134
135 /* in some cases we create the block before the region,
136 * so we set it delayed here if necessary */
137 if (BLI_findindex(&handle->region->runtime->uiblocks, block) == -1) {
138 UI_block_region_set(block, handle->region);
139 }
140
143
144 const int block_margin = U.widget_unit / 2;
145
146 if (pup->but) {
147 /* For a header menu we set the direction automatic. */
148 block->minbounds = BLI_rctf_size_x(&pup->but->rect);
149 UI_block_bounds_set_normal(block, block_margin);
150
151 /* If menu slides out of other menu, override direction. */
152 const bool slideout = ui_block_is_menu(pup->but->block);
153 if (slideout) {
155 }
156
157 /* Store the button location for positioning the popover arrow hint. */
158 if (!handle->refresh) {
159 float center[2] = {BLI_rctf_cent_x(&pup->but->rect), BLI_rctf_cent_y(&pup->but->rect)};
160 ui_block_to_window_fl(handle->ctx_region, pup->but->block, &center[0], &center[1]);
161 /* These variables aren't used for popovers,
162 * we could add new variables if there is a conflict. */
163 block->bounds_offset[0] = int(center[0]);
164 block->bounds_offset[1] = int(center[1]);
166 }
167 else {
169 }
170
171 if (!slideout) {
172 ARegion *region = CTX_wm_region(C);
173
174 if (region && region->panels.first) {
175 /* For regions with panels, prefer to open to top so we can
176 * see the values of the buttons below changing. */
178 }
179 /* Prefer popover from header to be positioned into the editor. */
180 else if (region) {
181 if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) {
184 }
185 }
186 }
187 }
188
189 /* Estimated a maximum size so we don't go off-screen for low height
190 * areas near the bottom of the window on refreshes. */
191 handle->max_size_y = UI_UNIT_Y * 16.0f;
192 }
193 else if (pup->panel_type &&
195 {
198 UI_block_direction_set(block, block->direction);
200
201 const int bounds_offset[2] = {
202 int(pup->panel_type->offset_units_xy.x * UI_UNIT_X),
203 int(pup->panel_type->offset_units_xy.y * UI_UNIT_Y),
204 };
205 UI_block_bounds_set_popup(block, block_margin, bounds_offset);
206 }
207 else {
208 /* Not attached to a button. */
209 int bounds_offset[2] = {0, 0};
212 UI_block_direction_set(block, block->direction);
214
215 if (!handle->refresh) {
216 uiBut *but = nullptr;
217 uiBut *but_first = nullptr;
218 for (const std::unique_ptr<uiBut> &but_iter : block->buttons) {
219 if ((but_first == nullptr) && ui_but_is_editable(but_iter.get())) {
220 but_first = but_iter.get();
221 }
222 if (but_iter->flag & (UI_SELECT | UI_SELECT_DRAW)) {
223 but = but_iter.get();
224 break;
225 }
226 }
227
228 if (but) {
229 bounds_offset[0] = -(but->rect.xmin + 0.8f * BLI_rctf_size_x(&but->rect));
230 bounds_offset[1] = -BLI_rctf_cent_y(&but->rect);
231 }
232 else {
233 bounds_offset[0] = -(pup->ui_size_x / 2);
234 bounds_offset[1] = but_first ? -BLI_rctf_cent_y(&but_first->rect) : (UI_UNIT_Y / 2);
235 }
236 copy_v2_v2_int(handle->prev_bounds_offset, bounds_offset);
237 }
238 else {
239 copy_v2_v2_int(bounds_offset, handle->prev_bounds_offset);
240 }
241
242 UI_block_bounds_set_popup(block, block_margin, bounds_offset);
243 }
244
245 return block;
246}
247
248static void ui_block_free_func_POPOVER(void *arg_pup)
249{
250 uiPopover *pup = static_cast<uiPopover *>(arg_pup);
251 if (pup->keymap != nullptr) {
252 wmWindow *window = pup->window;
254 }
255 MEM_delete(pup);
256}
257
259 ARegion *butregion,
260 uiBut *but,
261 uiPopoverCreateFunc popover_func,
262 const PanelType *panel_type)
263{
264 wmWindow *window = CTX_wm_window(C);
265 const uiStyle *style = UI_style_get_dpi();
266
267 /* Create popover, buttons are created from callback. */
268 uiPopover *pup = MEM_new<uiPopover>(__func__);
269 pup->but = but;
270
271 /* FIXME: maybe one day we want non panel popovers? */
272 {
273 const int ui_units_x = (panel_type->ui_units_x == 0) ? UI_POPOVER_WIDTH_UNITS :
274 panel_type->ui_units_x;
275 /* Scale width by changes to Text Style point size. */
276 pup->ui_size_x = ui_units_x * U.widget_unit * (style->widget.points / UI_DEFAULT_TEXT_POINTS);
277 }
278
279 pup->popover_func = popover_func;
280 pup->panel_type = panel_type;
281
282#ifdef USE_UI_POPOVER_ONCE
283 {
284 /* Ideally this would be passed in. */
285 const wmEvent *event = window->eventstate;
286 pup->is_once = (event->type == LEFTMOUSE) && (event->val == KM_PRESS);
287 }
288#endif
289
290 /* Create popup block. */
292 C, butregion, but, nullptr, ui_block_func_POPOVER, pup, ui_block_free_func_POPOVER, true);
293
294 /* Add handlers. If attached to a button, the button will already
295 * add a modal handler and pass on events. */
296 if (!but) {
297 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
299 handle->popup = true;
300 }
301
302 return handle;
303}
304
306
307/* -------------------------------------------------------------------- */
310
312 const char *idname,
313 bool keep_open,
314 ReportList *reports)
315{
316 uiLayout *layout;
317 PanelType *pt = WM_paneltype_find(idname, true);
318 if (pt == nullptr) {
319 BKE_reportf(reports, RPT_ERROR, "Panel \"%s\" not found", idname);
320 return OPERATOR_CANCELLED;
321 }
322
323 if (pt->poll && (pt->poll(C, pt) == false)) {
324 /* cancel but allow event to pass through, just like operators do */
326 }
327
328 uiBlock *block = nullptr;
329 if (keep_open) {
331 C, nullptr, nullptr, ui_item_paneltype_func, pt);
332 uiPopover *pup = static_cast<uiPopover *>(handle->popup_create_vars.arg);
333 block = pup->block;
334 }
335 else {
336 uiPopover *pup = UI_popover_begin(C, U.widget_unit * pt->ui_units_x, false);
337 layout = UI_popover_layout(pup);
338 UI_paneltype_draw(C, pt, layout);
339 UI_popover_end(C, pup, nullptr);
340 block = pup->block;
341 }
342
343 if (block) {
344 uiPopupBlockHandle *handle = block->handle;
346 }
347 return OPERATOR_INTERFACE;
348}
349
351
352/* -------------------------------------------------------------------- */
355
356uiPopover *UI_popover_begin(bContext *C, int ui_menu_width, bool from_active_button)
357{
358 uiPopover *pup = MEM_new<uiPopover>(__func__);
359 if (ui_menu_width == 0) {
360 ui_menu_width = U.widget_unit * UI_POPOVER_WIDTH_UNITS;
361 }
362 pup->ui_size_x = ui_menu_width;
363
364 ARegion *butregion = nullptr;
365 uiBut *but = nullptr;
366
367 if (from_active_button) {
368 butregion = CTX_wm_region(C);
369 but = UI_region_active_but_get(butregion);
370 if (but == nullptr) {
371 butregion = nullptr;
372 }
373 }
374
375 pup->but = but;
376 pup->butregion = butregion;
377
378 /* Operator context default same as menus, change if needed. */
380
381 /* Create in advance so we can let buttons point to #uiPopupBlockHandle::retvalue
382 * (and other return values) already. */
383 pup->block->handle = MEM_new<uiPopupBlockHandle>(__func__);
384
385 return pup;
386}
387
388static void popover_keymap_fn(wmKeyMap * /*keymap*/, wmKeyMapItem * /*kmi*/, void *user_data)
389{
390 uiPopover *pup = static_cast<uiPopover *>(user_data);
392}
393
395{
396 wmWindow *window = CTX_wm_window(C);
397
398 if (keymap) {
399 /* Add so we get keymaps shown in the buttons. */
401 pup->keymap = keymap;
404 }
405
406 /* Create popup block. No refresh support since the buttons were created
407 * between begin/end and we have no callback to recreate them. */
409 pup->butregion,
410 pup->but,
411 nullptr,
413 pup,
415 false);
416
417 /* Add handlers. */
418 UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
420 handle->popup = true;
421
422 /* Re-add so it gets priority. */
423 if (keymap) {
426 }
427
428 pup->window = window;
429
430 /* TODO(@ideasman42): we may want to make this configurable.
431 * The begin/end type of calling popups doesn't allow 'can_refresh' to be set.
432 * For now close this style of popovers when accessed. */
434}
435
437{
438 return pup->layout;
439}
440
441#ifdef USE_UI_POPOVER_ONCE
443{
444 pup->is_once = false;
445}
446#endif
447
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
@ RPT_ERROR
Definition BKE_report.hh:39
#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)
@ RGN_ALIGN_BOTTOM
#define RGN_TYPE_IS_HEADER_ANY(regiontype)
@ OPERATOR_CANCELLED
@ OPERATOR_INTERFACE
@ OPERATOR_PASS_THROUGH
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
#define UI_UNIT_Y
@ UI_BLOCK_POPOVER_ONCE
@ UI_BLOCK_LOOP
@ UI_BLOCK_KEEP_OPEN
@ UI_BLOCK_SHOW_SHORTCUT_ALWAYS
@ UI_BLOCK_POPOVER
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:653
uiBut * UI_region_active_but_get(const ARegion *region)
void UI_block_bounds_set_normal(uiBlock *block, int addval)
Definition interface.cc:637
const uiStyle * UI_style_get_dpi()
void UI_block_flag_disable(uiBlock *block, int flag)
void UI_popover_once_clear(uiPopover *pup)
bool UI_block_active_only_flagged_buttons(const bContext *C, ARegion *region, uiBlock *block)
@ UI_RETURN_OK
void UI_popup_handlers_add(bContext *C, ListBase *handlers, uiPopupBlockHandle *popup, char flag)
void UI_block_region_set(uiBlock *block, ARegion *region)
void UI_block_direction_set(uiBlock *block, char direction)
@ UI_BLOCK_THEME_STYLE_POPUP
#define UI_DEFAULT_TEXT_POINTS
#define UI_UNIT_X
void UI_block_flag_enable(uiBlock *block, int flag)
@ UI_DIR_CENTER_X
@ UI_DIR_DOWN
@ UI_DIR_RIGHT
@ UI_DIR_UP
void UI_paneltype_draw(bContext *C, PanelType *pt, uiLayout *layout)
@ KM_PRESS
Definition WM_types.hh:311
#define U
void ui_block_to_window_fl(const ARegion *region, const uiBlock *block, float *x, float *y)
Definition interface.cc:142
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_popover_create_block(bContext *C, ARegion *region, uiPopover *pup, blender::wm::OpCallContext opcontext)
static void ui_block_free_func_POPOVER(void *arg_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)
int2 block_layout_resolve(uiBlock *block)
uiLayout & block_layout(uiBlock *block, LayoutDirection direction, LayoutType type, int x, int y, int size, int em, int padding, const uiStyle *style)
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
void operator_context_set(blender::wm::OpCallContext opcontext)
void context_copy(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)