Blender V4.3
interface_region_popup.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
11#include <cstdarg>
12#include <cstdlib>
13#include <cstring>
14
15#include "MEM_guardedalloc.h"
16
17#include "DNA_userdef_types.h"
18
19#include "BLI_listbase.h"
20#include "BLI_math_vector.h"
21#include "BLI_rect.h"
22#include "BLI_utildefines.h"
23
24#include "BKE_context.hh"
25#include "BKE_screen.hh"
26
27#include "WM_api.hh"
28#include "WM_types.hh"
29
30#include "UI_interface.hh"
31
32#include "ED_screen.hh"
33
34#include "interface_intern.hh"
36
37/* -------------------------------------------------------------------- */
41void ui_popup_translate(ARegion *region, const int mdiff[2])
42{
43 BLI_rcti_translate(&region->winrct, UNPACK2(mdiff));
44
46
48
49 /* update blocks */
50 LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
51 uiPopupBlockHandle *handle = block->handle;
52 /* Make empty, will be initialized on next use, see #60608. */
53 BLI_rctf_init(&handle->prev_block_rect, 0, 0, 0, 0);
54
55 LISTBASE_FOREACH (uiSafetyRct *, saferct, &block->saferct) {
56 BLI_rctf_translate(&saferct->parent, UNPACK2(mdiff));
57 BLI_rctf_translate(&saferct->safety, UNPACK2(mdiff));
58 }
59 }
60}
61
62/* position block relative to but, result is in window space */
64 ARegion *butregion,
65 uiBut *but,
66 uiBlock *block)
67{
68 uiPopupBlockHandle *handle = block->handle;
69
70 /* Compute button position in window coordinates using the source
71 * button region/block, to position the popup attached to it. */
72 rctf butrct;
73 if (!handle->refresh) {
74 ui_block_to_window_rctf(butregion, but->block, &butrct, &but->rect);
75
76 /* widget_roundbox_set has this correction too, keep in sync */
77 if (but->type != UI_BTYPE_PULLDOWN) {
78 if (but->drawflag & UI_BUT_ALIGN_TOP) {
79 butrct.ymax += U.pixelsize;
80 }
81 if (but->drawflag & UI_BUT_ALIGN_LEFT) {
82 butrct.xmin -= U.pixelsize;
83 }
84 }
85
86 handle->prev_butrct = butrct;
87 }
88 else {
89 /* For refreshes, keep same button position so popup doesn't move. */
90 butrct = handle->prev_butrct;
91 }
92
93 /* Compute block size in window space, based on buttons contained in it. */
94 if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) {
95 if (block->buttons.first) {
97
98 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
100 bt->rect.xmax += UI_MENU_SUBMENU_PADDING;
101 }
102 BLI_rctf_union(&block->rect, &bt->rect);
103 }
104 }
105 else {
106 /* we're nice and allow empty blocks too */
107 block->rect.xmin = block->rect.ymin = 0;
108 block->rect.xmax = block->rect.ymax = 20;
109 }
110 }
111
112 /* Trim the popup and its contents to the width of the button if the size difference
113 * is small. This avoids cases where the rounded corner clips underneath the button. */
114 const int delta = BLI_rctf_size_x(&block->rect) - BLI_rctf_size_x(&butrct);
115 const float max_radius = (0.5f * U.widget_unit);
116
117 if (delta >= 0 && delta < max_radius) {
118 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
119 /* Only trim the right most buttons in multi-column popovers. */
120 if (bt->rect.xmax == block->rect.xmax) {
121 bt->rect.xmax -= delta;
122 }
123 }
124 block->rect.xmax -= delta;
125 }
126
127 ui_block_to_window_rctf(butregion, but->block, &block->rect, &block->rect);
128
129 /* `block->rect` is already scaled with `butregion->winrct`,
130 * apply this scale to layout panels too. */
131 if (Panel *panel = block->panel) {
132 for (LayoutPanelBody &body : panel->runtime->layout_panels.bodies) {
133 body.start_y /= block->aspect;
134 body.end_y /= block->aspect;
135 }
136 for (LayoutPanelHeader &header : panel->runtime->layout_panels.headers) {
137 header.start_y /= block->aspect;
138 header.end_y /= block->aspect;
139 }
140 }
141
142 /* Compute direction relative to button, based on available space. */
143 const int size_x = BLI_rctf_size_x(&block->rect) + 0.2f * UI_UNIT_X; /* 4 for shadow */
144 const int size_y = BLI_rctf_size_y(&block->rect) + 0.2f * UI_UNIT_Y;
145 const int center_x = (block->direction & UI_DIR_CENTER_X) ? size_x / 2 : 0;
146 const int center_y = (block->direction & UI_DIR_CENTER_Y) ? size_y / 2 : 0;
147
148 const blender::int2 win_size = WM_window_native_pixel_size(window);
149
150 /* Take into account maximum size so we don't have to flip on refresh. */
151 const blender::float2 max_size = {
152 max_ff(size_x, handle->max_size_x),
153 max_ff(size_y, handle->max_size_y),
154 };
155
156 short dir1 = 0, dir2 = 0;
157
158 if (!handle->refresh) {
159 bool left = false, right = false, top = false, down = false;
160
161 /* check if there's space at all */
162 if (butrct.xmin - max_size[0] + center_x > 0.0f) {
163 left = true;
164 }
165 if (butrct.xmax + max_size[0] - center_x < win_size[0]) {
166 right = true;
167 }
168 if (butrct.ymin - max_size[1] + center_y > 0.0f) {
169 down = true;
170 }
171 if (butrct.ymax + max_size[1] - center_y < win_size[1]) {
172 top = true;
173 }
174
175 if (top == 0 && down == 0) {
176 if (butrct.ymin - max_size[1] < win_size[1] - butrct.ymax - max_size[1]) {
177 top = true;
178 }
179 else {
180 down = true;
181 }
182 }
183
184 dir1 = (block->direction & UI_DIR_ALL);
185
186 /* Secondary directions. */
187 if (dir1 & (UI_DIR_UP | UI_DIR_DOWN)) {
188 if (dir1 & UI_DIR_LEFT) {
189 dir2 = UI_DIR_LEFT;
190 }
191 else if (dir1 & UI_DIR_RIGHT) {
192 dir2 = UI_DIR_RIGHT;
193 }
194 dir1 &= (UI_DIR_UP | UI_DIR_DOWN);
195 }
196
197 if ((dir2 == 0) && ELEM(dir1, UI_DIR_LEFT, UI_DIR_RIGHT)) {
198 dir2 = UI_DIR_DOWN;
199 }
200 if ((dir2 == 0) && ELEM(dir1, UI_DIR_UP, UI_DIR_DOWN)) {
201 dir2 = UI_DIR_LEFT;
202 }
203
204 /* no space at all? don't change */
205 if (left || right) {
206 if (dir1 == UI_DIR_LEFT && left == 0) {
207 dir1 = UI_DIR_RIGHT;
208 }
209 if (dir1 == UI_DIR_RIGHT && right == 0) {
210 dir1 = UI_DIR_LEFT;
211 }
212 /* this is aligning, not append! */
213 if (dir2 == UI_DIR_LEFT && right == 0) {
214 dir2 = UI_DIR_RIGHT;
215 }
216 if (dir2 == UI_DIR_RIGHT && left == 0) {
217 dir2 = UI_DIR_LEFT;
218 }
219 }
220 if (down || top) {
221 if (dir1 == UI_DIR_UP && top == 0) {
222 dir1 = UI_DIR_DOWN;
223 }
224 if (dir1 == UI_DIR_DOWN && down == 0) {
225 dir1 = UI_DIR_UP;
226 }
227 BLI_assert(dir2 != UI_DIR_UP);
228 // if (dir2 == UI_DIR_UP && top == 0) { dir2 = UI_DIR_DOWN; }
229 if (dir2 == UI_DIR_DOWN && down == 0) {
230 dir2 = UI_DIR_UP;
231 }
232 }
233
234 handle->prev_dir1 = dir1;
235 handle->prev_dir2 = dir2;
236 }
237 else {
238 /* For refreshes, keep same popup direct so popup doesn't move
239 * to a totally different position while editing in it. */
240 dir1 = handle->prev_dir1;
241 dir2 = handle->prev_dir2;
242 }
243
244 /* Compute offset based on direction. */
245 float offset_x = 0, offset_y = 0;
246
247 /* Ensure buttons don't come between the parent button and the popup, see: #63566. */
248 const float offset_overlap = max_ff(U.pixelsize, 1.0f);
249
250 if (dir1 == UI_DIR_LEFT) {
251 offset_x = (butrct.xmin - block->rect.xmax) + offset_overlap;
252 if (dir2 == UI_DIR_UP) {
253 offset_y = butrct.ymin - block->rect.ymin - center_y - UI_MENU_PADDING;
254 }
255 else {
256 offset_y = butrct.ymax - block->rect.ymax + center_y + UI_MENU_PADDING;
257 }
258 }
259 else if (dir1 == UI_DIR_RIGHT) {
260 offset_x = (butrct.xmax - block->rect.xmin) - offset_overlap;
261 if (dir2 == UI_DIR_UP) {
262 offset_y = butrct.ymin - block->rect.ymin - center_y - UI_MENU_PADDING;
263 }
264 else {
265 offset_y = butrct.ymax - block->rect.ymax + center_y + UI_MENU_PADDING;
266 }
267 }
268 else if (dir1 == UI_DIR_UP) {
269 offset_y = (butrct.ymax - block->rect.ymin) - offset_overlap;
270
271 if (but->type == UI_BTYPE_COLOR &&
272 block->rect.ymax + offset_y > win_size[1] - UI_POPUP_MENU_TOP)
273 {
274 /* Shift this down, aligning the top edge close to the window top. */
275 offset_y = win_size[1] - block->rect.ymax - UI_POPUP_MENU_TOP;
276 /* All four corners should be rounded since this no longer button-aligned. */
277 block->direction = UI_DIR_CENTER_Y;
278 dir1 = UI_DIR_CENTER_Y;
279 }
280
281 if (dir2 == UI_DIR_RIGHT) {
282 offset_x = butrct.xmax - block->rect.xmax + center_x;
283 }
284 else {
285 offset_x = butrct.xmin - block->rect.xmin - center_x;
286 }
287 }
288 else if (dir1 == UI_DIR_DOWN) {
289 offset_y = (butrct.ymin - block->rect.ymax) + offset_overlap;
290
291 if (but->type == UI_BTYPE_COLOR && block->rect.ymin + offset_y < UI_SCREEN_MARGIN) {
292 /* Shift this up, aligning the bottom edge close to the window bottom. */
293 offset_y = -block->rect.ymin + UI_SCREEN_MARGIN;
294 /* All four corners should be rounded since this no longer button-aligned. */
295 block->direction = UI_DIR_CENTER_Y;
296 dir1 = UI_DIR_CENTER_Y;
297 }
298
299 if (dir2 == UI_DIR_RIGHT) {
300 offset_x = butrct.xmax - block->rect.xmax + center_x;
301 }
302 else {
303 offset_x = butrct.xmin - block->rect.xmin - center_x;
304 }
305 }
306
307 /* Center over popovers for eg. */
308 if (block->direction & UI_DIR_CENTER_X) {
309 offset_x += BLI_rctf_size_x(&butrct) / ((dir2 == UI_DIR_LEFT) ? 2 : -2);
310 }
311
312 /* Apply offset, buttons in window coords. */
313 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
314 ui_block_to_window_rctf(butregion, but->block, &bt->rect, &bt->rect);
315
316 BLI_rctf_translate(&bt->rect, offset_x, offset_y);
317
318 /* ui_but_update recalculates drawstring size in pixels */
319 ui_but_update(bt);
320 }
321
322 BLI_rctf_translate(&block->rect, offset_x, offset_y);
323
324 /* Safety calculus. */
325 {
326 const float midx = BLI_rctf_cent_x(&butrct);
327 const float midy = BLI_rctf_cent_y(&butrct);
328
329 /* when you are outside parent button, safety there should be smaller */
330
331 const int s1 = 40 * UI_SCALE_FAC;
332 const int s2 = 3 * UI_SCALE_FAC;
333
334 /* parent button to left */
335 if (midx < block->rect.xmin) {
336 block->safety.xmin = block->rect.xmin - s2;
337 }
338 else {
339 block->safety.xmin = block->rect.xmin - s1;
340 }
341 /* parent button to right */
342 if (midx > block->rect.xmax) {
343 block->safety.xmax = block->rect.xmax + s2;
344 }
345 else {
346 block->safety.xmax = block->rect.xmax + s1;
347 }
348
349 /* parent button on bottom */
350 if (midy < block->rect.ymin) {
351 block->safety.ymin = block->rect.ymin - s2;
352 }
353 else {
354 block->safety.ymin = block->rect.ymin - s1;
355 }
356 /* parent button on top */
357 if (midy > block->rect.ymax) {
358 block->safety.ymax = block->rect.ymax + s2;
359 }
360 else {
361 block->safety.ymax = block->rect.ymax + s1;
362 }
363
364 /* Exception for switched pull-downs. */
365 if (dir1 && (dir1 & block->direction) == 0) {
366 if (dir2 == UI_DIR_RIGHT) {
367 block->safety.xmax = block->rect.xmax + s2;
368 }
369 if (dir2 == UI_DIR_LEFT) {
370 block->safety.xmin = block->rect.xmin - s2;
371 }
372 }
373
374 const bool fully_aligned_with_button = BLI_rctf_size_x(&block->rect) <=
375 BLI_rctf_size_x(&butrct) + 1;
376 const bool off_screen_left = (block->rect.xmin < 0);
377 const bool off_screen_right = (block->rect.xmax > win_size[0]);
378
379 if (fully_aligned_with_button) {
380 /* Popup is neither left or right from the button. */
381 dir2 &= ~(UI_DIR_LEFT | UI_DIR_RIGHT);
382 }
383 else if (off_screen_left || off_screen_right) {
384 /* Popup is both left and right from the button. */
385 dir2 |= (UI_DIR_LEFT | UI_DIR_RIGHT);
386 }
387
388 /* Popovers don't need secondary direction. Pull-downs to
389 * the left or right are currently not supported. */
390 const bool no_2nd_dir = (but->type == UI_BTYPE_POPOVER || ui_but_menu_draw_as_popover(but) ||
391 dir1 & (UI_DIR_RIGHT | UI_DIR_LEFT));
392 block->direction = no_2nd_dir ? dir1 : (dir1 | dir2);
393 }
394
395 /* Keep a list of these, needed for pull-down menus. */
396 uiSafetyRct *saferct = MEM_cnew<uiSafetyRct>(__func__);
397 saferct->parent = butrct;
398 saferct->safety = block->safety;
399 BLI_freelistN(&block->saferct);
400 BLI_duplicatelist(&block->saferct, &but->block->saferct);
401 BLI_addhead(&block->saferct, saferct);
402}
403
406/* -------------------------------------------------------------------- */
410static void ui_block_region_refresh(const bContext *C, ARegion *region)
411{
412 BLI_assert(region->regiontype == RGN_TYPE_TEMPORARY);
413
414 ScrArea *ctx_area = CTX_wm_area(C);
415 ARegion *ctx_region = CTX_wm_region(C);
416
417 if (region->do_draw & RGN_REFRESH_UI) {
418 ScrArea *handle_ctx_area;
419 ARegion *handle_ctx_region;
420
421 region->do_draw &= ~RGN_REFRESH_UI;
422 LISTBASE_FOREACH_MUTABLE (uiBlock *, block, &region->uiblocks) {
423 uiPopupBlockHandle *handle = block->handle;
424
425 if (handle->can_refresh) {
426 handle_ctx_area = handle->ctx_area;
427 handle_ctx_region = handle->ctx_region;
428
429 if (handle_ctx_area) {
430 CTX_wm_area_set((bContext *)C, handle_ctx_area);
431 }
432 if (handle_ctx_region) {
433 CTX_wm_region_set((bContext *)C, handle_ctx_region);
434 }
435
436 uiBut *but = handle->popup_create_vars.but;
437 ARegion *butregion = handle->popup_create_vars.butregion;
438 ui_popup_block_refresh((bContext *)C, handle, butregion, but);
439 }
440 }
441 }
442
443 CTX_wm_area_set((bContext *)C, ctx_area);
444 CTX_wm_region_set((bContext *)C, ctx_region);
445}
446
447static void ui_block_region_draw(const bContext *C, ARegion *region)
448{
449 LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
450 UI_block_draw(C, block);
451 }
452}
453
458{
459 ARegion *region = params->region;
460 const wmNotifier *wmn = params->notifier;
461
462 switch (wmn->category) {
463 case NC_WINDOW: {
464 switch (wmn->action) {
465 case NA_EDITED: {
466 /* window resize */
468 break;
469 }
470 }
471 break;
472 }
473 }
474}
475
476static void ui_popup_block_clip(wmWindow *window, uiBlock *block)
477{
478 const float xmin_orig = block->rect.xmin;
479 const int margin = UI_SCREEN_MARGIN;
480
481 if (block->flag & UI_BLOCK_NO_WIN_CLIP) {
482 return;
483 }
484
485 const blender::int2 win_size = WM_window_native_pixel_size(window);
486
487 /* shift to left if outside of view */
488 if (block->rect.xmax > win_size[0] - margin) {
489 const float xofs = win_size[0] - margin - block->rect.xmax;
490 block->rect.xmin += xofs;
491 block->rect.xmax += xofs;
492 }
493 /* shift menus to right if outside of view */
494 if (block->rect.xmin < margin) {
495 const float xofs = (margin - block->rect.xmin);
496 block->rect.xmin += xofs;
497 block->rect.xmax += xofs;
498 }
499
500 if (block->rect.ymin < margin) {
501 block->rect.ymin = margin;
502 }
503 if (block->rect.ymax > win_size[1] - UI_POPUP_MENU_TOP) {
504 block->rect.ymax = win_size[1] - UI_POPUP_MENU_TOP;
505 }
506
507 /* ensure menu items draw inside left/right boundary */
508 const float xofs = block->rect.xmin - xmin_orig;
509 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
510 bt->rect.xmin += xofs;
511 bt->rect.xmax += xofs;
512 }
513}
514
516{
518
519 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
520 bt->flag &= ~UI_SCROLLED;
521 }
522
523 if (block->buttons.first == block->buttons.last) {
524 return;
525 }
526
527 /* mark buttons that are outside boundary */
528 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
529 if (bt->rect.ymin < block->rect.ymin) {
530 bt->flag |= UI_SCROLLED;
531 block->flag |= UI_BLOCK_CLIPBOTTOM;
532 }
533 if (bt->rect.ymax > block->rect.ymax) {
534 bt->flag |= UI_SCROLLED;
535 block->flag |= UI_BLOCK_CLIPTOP;
536 }
537 }
538
539 /* mark buttons overlapping arrows, if we have them */
540 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
541 if (block->flag & UI_BLOCK_CLIPBOTTOM) {
542 if (bt->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW) {
543 bt->flag |= UI_SCROLLED;
544 }
545 }
546 if (block->flag & UI_BLOCK_CLIPTOP) {
547 if (bt->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW) {
548 bt->flag |= UI_SCROLLED;
549 }
550 }
551 }
552}
553
555{
556 wmWindow *ctx_win = CTX_wm_window(C);
557 ScrArea *ctx_area = CTX_wm_area(C);
558 ARegion *ctx_region = CTX_wm_region(C);
559
561 wmWindow *win = ctx_win;
562 bScreen *screen = CTX_wm_screen(C);
563
564 /* There may actually be a different window active than the one showing the popup, so lookup real
565 * one. */
566 if (BLI_findindex(&screen->regionbase, handle->region) == -1) {
567 LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) {
568 screen = WM_window_get_active_screen(win_iter);
569 if (BLI_findindex(&screen->regionbase, handle->region) != -1) {
570 win = win_iter;
571 break;
572 }
573 }
574 }
575
576 BLI_assert(win && screen);
577
578 CTX_wm_window_set(C, win);
579 ui_region_temp_remove(C, screen, handle->region);
580
581 /* Reset context (area and region were null'ed when changing context window). */
582 CTX_wm_window_set(C, ctx_win);
583 CTX_wm_area_set(C, ctx_area);
584 CTX_wm_region_set(C, ctx_region);
585
586 /* reset to region cursor (only if there's not another menu open) */
587 if (BLI_listbase_is_empty(&screen->regionbase)) {
588 win->tag_cursor_refresh = true;
589 }
590
591 if (handle->scrolltimer) {
592 WM_event_timer_remove(wm, win, handle->scrolltimer);
593 }
594}
595
596void ui_layout_panel_popup_scroll_apply(Panel *panel, const float dy)
597{
598 if (!panel || dy == 0.0f) {
599 return;
600 }
601 for (LayoutPanelBody &body : panel->runtime->layout_panels.bodies) {
602 body.start_y += dy;
603 body.end_y += dy;
604 }
605 for (LayoutPanelHeader &headcer : panel->runtime->layout_panels.headers) {
606 headcer.start_y += dy;
607 headcer.end_y += dy;
608 }
609}
610
612{
613 Panel *&panel = region->runtime.popup_block_panel;
614 if (!panel) {
615 /* Dummy popup panel type. */
616 static PanelType panel_type = []() {
617 PanelType type{};
619 return type;
620 }();
621 panel = BKE_panel_new(&panel_type);
622 }
623 panel->runtime->layout_panels.clear();
624 block->panel = panel;
625 panel->runtime->block = block;
626}
627
629 uiPopupBlockHandle *handle,
630 ARegion *butregion,
631 uiBut *but)
632{
633 const int margin = UI_POPUP_MARGIN;
634 wmWindow *window = CTX_wm_window(C);
635 ARegion *region = handle->region;
636
637 const uiBlockCreateFunc create_func = handle->popup_create_vars.create_func;
638 const uiBlockHandleCreateFunc handle_create_func = handle->popup_create_vars.handle_create_func;
639 void *arg = handle->popup_create_vars.arg;
640
641 uiBlock *block_old = static_cast<uiBlock *>(region->uiblocks.first);
642
643 handle->refresh = (block_old != nullptr);
644
645 BLI_assert(!handle->refresh || handle->can_refresh);
646
647#ifndef NDEBUG
648 wmEvent *event_back = window->eventstate;
649 wmEvent *event_last_back = window->event_last_handled;
650#endif
651
652 /* create ui block */
653 uiBlock *block;
654 if (create_func) {
655 block = create_func(C, region, arg);
656 }
657 else {
658 block = handle_create_func(C, handle, arg);
659 }
660
661 /* Don't create accelerator keys if the parent menu does not have them. */
662 if (but && but->block->flag & UI_BLOCK_NO_ACCELERATOR_KEYS) {
664 }
665
666 /* callbacks _must_ leave this for us, otherwise we can't call UI_block_update_from_old */
667 BLI_assert(!block->endblock);
668
669 /* Ensure we don't use mouse coords here.
670 *
671 * NOTE(@ideasman42): Important because failing to do will cause glitches refreshing the popup.
672 *
673 * - Many popups use #wmEvent::xy to position them.
674 * - Refreshing a pop-up must only ever change it's contents. Consider that refreshing
675 * might be used to show a menu item as grayed out, or change a text label,
676 * we *never* want the popup to move based on the cursor location while refreshing.
677 * - The location of the cursor at the time of creation is stored in:
678 * `handle->popup_create_vars.event_xy` which must be used instead.
679 *
680 * Since it's difficult to control logic which is called indirectly here,
681 * clear the `eventstate` entirely to ensure it's never used when refreshing a popup. */
682#ifndef NDEBUG
683 window->eventstate = nullptr;
684#endif
685
686 if (block->handle) {
687 memcpy(block->handle, handle, sizeof(uiPopupBlockHandle));
688 MEM_freeN(handle);
689 handle = block->handle;
690 }
691 else {
692 block->handle = handle;
693 }
694
695 region->regiondata = handle;
696
697 /* set UI_BLOCK_NUMSELECT before UI_block_end() so we get alphanumeric keys assigned */
698 if (but == nullptr) {
699 block->flag |= UI_BLOCK_POPUP;
700 }
701
702 block->flag |= UI_BLOCK_LOOP;
704
705 /* defer this until blocks are translated (below) */
706 block->oldblock = nullptr;
707
708 if (!block->endblock) {
710 C, block, handle->popup_create_vars.event_xy, handle->popup_create_vars.event_xy);
711 }
712
713 /* if this is being created from a button */
714 if (but) {
715 block->aspect = but->block->aspect;
716 ui_popup_block_position(window, butregion, but, block);
717 handle->direction = block->direction;
718 }
719 else {
720 /* Keep a list of these, needed for pull-down menus. */
721 uiSafetyRct *saferct = MEM_cnew<uiSafetyRct>(__func__);
722 saferct->safety = block->safety;
723 BLI_addhead(&block->saferct, saferct);
724 }
725
726 if (block->flag & UI_BLOCK_PIE_MENU) {
727 const int win_width = UI_SCREEN_MARGIN;
728
729 const blender::int2 win_size = WM_window_native_pixel_size(window);
730
732
733 /* only try translation if area is large enough */
734 int x_offset = 0;
735 if (BLI_rctf_size_x(&block->rect) < win_size[0] - (2.0f * win_width)) {
736 if (block->rect.xmin < win_width) {
737 x_offset += win_width - block->rect.xmin;
738 }
739 if (block->rect.xmax > win_size[0] - win_width) {
740 x_offset += win_size[0] - win_width - block->rect.xmax;
741 }
742 }
743
744 int y_offset = 0;
745 if (BLI_rctf_size_y(&block->rect) < win_size[1] - (2.0f * win_width)) {
746 if (block->rect.ymin < win_width) {
747 y_offset += win_width - block->rect.ymin;
748 }
749 if (block->rect.ymax > win_size[1] - win_width) {
750 y_offset += win_size[1] - win_width - block->rect.ymax;
751 }
752 }
753 /* if we are offsetting set up initial data for timeout functionality */
754
755 if ((x_offset != 0) || (y_offset != 0)) {
756 block->pie_data.pie_center_spawned[0] += x_offset;
757 block->pie_data.pie_center_spawned[1] += y_offset;
758
759 UI_block_translate(block, x_offset, y_offset);
760
761 if (U.pie_initial_timeout > 0) {
763 }
764 }
765
766 region->winrct.xmin = 0;
767 region->winrct.xmax = win_size[0];
768 region->winrct.ymin = 0;
769 region->winrct.ymax = win_size[1];
770
772
773 /* lastly set the buttons at the center of the pie menu, ready for animation */
774 if (U.pie_animation_timeout > 0) {
775 LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) {
776 if (but_iter->pie_dir != UI_RADIAL_NONE) {
777 BLI_rctf_recenter(&but_iter->rect, UNPACK2(block->pie_data.pie_center_spawned));
778 }
779 }
780 }
781 }
782 else {
783 /* Add an offset to draw the popover arrow. */
784 if ((block->flag & UI_BLOCK_POPOVER) && ELEM(block->direction, UI_DIR_UP, UI_DIR_DOWN)) {
785 /* Keep sync with 'ui_draw_popover_back_impl'. */
786 const float unit_size = U.widget_unit / block->aspect;
787 const float unit_half = unit_size * (block->direction == UI_DIR_DOWN ? 0.5 : -0.5);
788
789 UI_block_translate(block, 0, -unit_half);
790 }
791
792 /* clip block with window boundary */
793 ui_popup_block_clip(window, block);
794
795 /* Avoid menu moving down and losing cursor focus by keeping it at
796 * the same height. */
797 if (handle->refresh && handle->prev_block_rect.ymax > block->rect.ymax) {
799 const float offset = handle->prev_block_rect.ymax - block->rect.ymax;
800 UI_block_translate(block, 0, offset);
801 block->rect.ymin = handle->prev_block_rect.ymin;
802 }
803 }
804
805 handle->prev_block_rect = block->rect;
806
807 /* the block and buttons were positioned in window space as in 2.4x, now
808 * these menu blocks are regions so we bring it back to region space.
809 * additionally we add some padding for the menu shadow or rounded menus */
810 region->winrct.xmin = block->rect.xmin - margin;
811 region->winrct.xmax = block->rect.xmax + margin;
812 region->winrct.ymin = block->rect.ymin - margin;
813 region->winrct.ymax = block->rect.ymax + UI_POPUP_MENU_TOP;
814
815 UI_block_translate(block, -region->winrct.xmin, -region->winrct.ymin);
816 /* Popups can change size, fix scroll offset if a panel was closed. */
817 float ymin = FLT_MAX;
818 float ymax = -FLT_MAX;
819 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
820 ymin = min_ff(ymin, bt->rect.ymin);
821 ymax = max_ff(ymax, bt->rect.ymax);
822 }
823 const int scroll_pad = ui_block_is_menu(block) ? UI_MENU_SCROLL_PAD : UI_UNIT_Y * 0.5f;
824 const float scroll_min = std::min(block->rect.ymax - ymax - scroll_pad, 0.0f);
825 const float scroll_max = std::max(block->rect.ymin - ymin + scroll_pad, 0.0f);
826 handle->scrolloffset = std::clamp(handle->scrolloffset, scroll_min, scroll_max);
827 /* apply scroll offset */
828 if (handle->scrolloffset != 0.0f) {
829 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
830 bt->rect.ymin += handle->scrolloffset;
831 bt->rect.ymax += handle->scrolloffset;
832 }
833 }
834 /* Layout panels are relative to `block->rect.ymax`. Rather than a
835 * scroll, this is a offset applied due to the overflow at the top. */
836 ui_layout_panel_popup_scroll_apply(block->panel, -scroll_min);
837 }
838 /* Apply popup scroll offset to layout panels. */
839 ui_layout_panel_popup_scroll_apply(block->panel, handle->scrolloffset);
840
841 if (block_old) {
842 block->oldblock = block_old;
843 UI_block_update_from_old(C, block);
845 }
846
847 /* checks which buttons are visible, sets flags to prevent draw (do after region init) */
849
850 /* Adds sub-window. */
852
853 /* Get `winmat` now that we actually have the sub-window. */
854 wmGetProjectionMatrix(block->winmat, &region->winrct);
855
856 /* notify change and redraw */
857 ED_region_tag_redraw(region);
858
859 ED_region_update_rect(region);
860
861#ifndef NDEBUG
862 window->eventstate = event_back;
863 window->event_last_handled = event_last_back;
864#endif
865
866 return block;
867}
868
870 ARegion *butregion,
871 uiBut *but,
873 uiBlockHandleCreateFunc handle_create_func,
874 void *arg,
875 uiFreeArgFunc arg_free,
876 const bool can_refresh)
877{
878 wmWindow *window = CTX_wm_window(C);
879 uiBut *activebut = UI_context_active_but_get(C);
880
881 /* disable tooltips from buttons below */
882 if (activebut) {
883 UI_but_tooltip_timer_remove(C, activebut);
884 }
885 /* standard cursor by default */
887
888 /* create handle */
889 uiPopupBlockHandle *handle = MEM_cnew<uiPopupBlockHandle>(__func__);
890
891 /* store context for operator */
892 handle->ctx_area = CTX_wm_area(C);
893 handle->ctx_region = CTX_wm_region(C);
894 handle->can_refresh = can_refresh;
895
896 /* store vars to refresh popup (RGN_REFRESH_UI) */
897 handle->popup_create_vars.create_func = create_func;
898 handle->popup_create_vars.handle_create_func = handle_create_func;
899 handle->popup_create_vars.arg = arg;
900 handle->popup_create_vars.arg_free = arg_free;
901 handle->popup_create_vars.but = but;
902 handle->popup_create_vars.butregion = but ? butregion : nullptr;
903 copy_v2_v2_int(handle->popup_create_vars.event_xy, window->eventstate->xy);
904
905 /* create area region */
907 handle->region = region;
908
909 static ARegionType type;
910 memset(&type, 0, sizeof(ARegionType));
911 type.draw = ui_block_region_draw;
912 type.layout = ui_block_region_refresh;
913 type.regionid = RGN_TYPE_TEMPORARY;
914 region->type = &type;
915
916 UI_region_handlers_add(&region->handlers);
917
918 /* Note that this will be set in the code-path that typically calls refreshing
919 * (that loops over #Screen::regionbase and refreshes regions tagged with #RGN_REFRESH_UI).
920 * Whereas this only runs on initial creation.
921 * Set the region here so drawing logic can rely on it being set.
922 * Note that restoring the previous value may not be needed, it just avoids potential
923 * problems caused by popups manipulating the context which created them.
924 *
925 * The check for `can_refresh` exists because the context when refreshing sets the "region_popup"
926 * so failing to do so here would cause callbacks draw function to have a different context
927 * the first time it's called. Setting this in every context causes button context menus to
928 * fail because setting the "region_popup" causes poll functions to reference the popup region
929 * instead of the region where the button was created, see #121728.
930 *
931 * NOTE(@ideasman42): the logic for which popups run with their region set to
932 * #bContext::wm::region_popup could be adjusted, making this context member depend on
933 * the ability to refresh seems somewhat arbitrary although it does make *some* sense
934 * because accessing the region later (to tag for refreshing for example)
935 * only makes sense if that region supports refreshing. */
936 ARegion *region_popup_prev = nullptr;
937 if (can_refresh) {
938 region_popup_prev = CTX_wm_region_popup(C);
939 CTX_wm_region_popup_set(C, region);
940 }
941
942 uiBlock *block = ui_popup_block_refresh(C, handle, butregion, but);
943 handle = block->handle;
944
945 /* Wait with tooltips until the mouse is moved, button handling will re-enable them on the first
946 * actual mouse move. */
947 block->tooltipdisabled = true;
948
949 if (can_refresh) {
950 CTX_wm_region_popup_set(C, region_popup_prev);
951 }
952
953 /* keep centered on window resizing */
956 }
957
958 return handle;
959}
960
962{
963 bool is_submenu = false;
964
965 /* If this popup is created from a popover which does NOT have keep-open flag set,
966 * then close the popover too. We could extend this to other popup types too. */
967 ARegion *region = handle->popup_create_vars.butregion;
968 if (region != nullptr) {
969 LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
970 if (block->handle && (block->flag & UI_BLOCK_POPOVER) &&
971 (block->flag & UI_BLOCK_KEEP_OPEN) == 0)
972 {
973 uiPopupBlockHandle *menu = block->handle;
974 menu->menuretval = UI_RETURN_OK;
975 }
976
977 if (ui_block_is_menu(block)) {
978 is_submenu = true;
979 }
980 }
981 }
982
983 /* Clear the status bar text that is set when opening a menu. */
984 if (!is_submenu) {
985 ED_workspace_status_text(C, nullptr);
986 }
987
988 if (handle->popup_create_vars.arg_free) {
989 handle->popup_create_vars.arg_free(handle->popup_create_vars.arg);
990 }
991
992 if (handle->region->runtime.popup_block_panel) {
993 BKE_panel_free(handle->region->runtime.popup_block_panel);
994 }
995
996 ui_popup_block_remove(C, handle);
997
998 MEM_freeN(handle);
999}
1000
bScreen * CTX_wm_screen(const bContext *C)
ARegion * CTX_wm_region_popup(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
void CTX_wm_window_set(bContext *C, wmWindow *win)
void CTX_wm_area_set(bContext *C, ScrArea *area)
void CTX_wm_region_set(bContext *C, ARegion *region)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
void CTX_wm_region_popup_set(bContext *C, ARegion *region_popup)
void BKE_panel_free(Panel *panel)
Definition screen.cc:529
Panel * BKE_panel_new(PanelType *panel_type)
Definition screen.cc:518
@ PANEL_TYPE_NO_HEADER
#define BLI_assert(a)
Definition BLI_assert.h:50
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
void BLI_addhead(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:90
#define LISTBASE_FOREACH(type, var, list)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
void void void void void void BLI_duplicatelist(struct ListBase *dst, const struct ListBase *src) ATTR_NONNULL(1
void void BLI_freelistN(struct ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:496
int BLI_findindex(const struct ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
MINLINE float max_ff(float a, float b)
MINLINE float min_ff(float a, float b)
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void copy_v2_v2_int(int r[2], const int a[2])
void BLI_rctf_translate(struct rctf *rect, float x, float y)
Definition rct.c:567
void BLI_rctf_union(struct rctf *rct_a, const struct rctf *rct_b)
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
void BLI_rcti_translate(struct rcti *rect, int x, int y)
Definition rct.c:560
void BLI_rctf_init(struct rctf *rect, float xmin, float xmax, float ymin, float ymax)
Definition rct.c:408
void BLI_rctf_recenter(struct rctf *rect, float x, float y)
Definition rct.c:596
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:197
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:201
void BLI_rctf_init_minmax(struct rctf *rect)
Definition rct.c:484
#define UNPACK2(a)
#define ELEM(...)
@ RGN_TYPE_TEMPORARY
@ RGN_REFRESH_UI
#define UI_SCALE_FAC
void ED_region_tag_refresh_ui(ARegion *region)
Definition area.cc:662
void ED_region_update_rect(ARegion *region)
Definition area.cc:2201
void ED_region_floating_init(ARegion *region)
Definition area.cc:2206
void ED_workspace_status_text(bContext *C, const char *str)
Definition area.cc:966
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:634
Read Guarded memory(de)allocation.
void UI_block_update_from_old(const bContext *C, uiBlock *block)
@ UI_RETURN_OK
void(*)(void *arg) uiFreeArgFunc
#define UI_UNIT_Y
void UI_block_theme_style_set(uiBlock *block, char theme_style)
@ UI_BLOCK_CLIPBOTTOM
@ UI_BLOCK_LOOP
@ UI_BLOCK_NO_ACCELERATOR_KEYS
@ UI_BLOCK_PIE_MENU
@ UI_BLOCK_KEEP_OPEN
@ UI_BLOCK_POPUP
@ UI_BLOCK_CLIPTOP
@ UI_BLOCK_POPOVER
@ UI_BLOCK_NO_WIN_CLIP
void UI_block_translate(uiBlock *block, float x, float y)
Definition interface.cc:348
void UI_but_tooltip_timer_remove(bContext *C, uiBut *but)
uiBut * UI_context_active_but_get(const bContext *C)
@ UI_BLOCK_BOUNDS_POPUP_CENTER
@ UI_DIR_CENTER_X
@ UI_DIR_CENTER_Y
@ UI_DIR_ALL
@ UI_DIR_DOWN
@ UI_DIR_RIGHT
@ UI_DIR_LEFT
@ UI_DIR_UP
void UI_block_draw(const bContext *C, uiBlock *block)
void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_xy[2])
@ UI_BLOCK_THEME_STYLE_POPUP
void UI_blocklist_free_inactive(const bContext *C, ARegion *region)
#define UI_SCREEN_MARGIN
#define UI_UNIT_X
uiBlock *(*)(bContext *C, ARegion *region, void *arg1) uiBlockCreateFunc
@ UI_BTYPE_POPOVER
@ UI_BTYPE_PULLDOWN
@ UI_BTYPE_COLOR
void UI_region_handlers_add(ListBase *handlers)
@ UI_BUT_ALIGN_TOP
@ UI_BUT_ALIGN_LEFT
#define NC_WINDOW
Definition WM_types.hh:342
#define NA_EDITED
Definition WM_types.hh:550
unsigned int U
Definition btGjkEpa3.h:78
void ui_but_update(uiBut *but)
bool ui_but_menu_draw_as_popover(const uiBut *but)
void ui_block_to_window_rctf(const ARegion *region, const uiBlock *block, rctf *rct_dst, const rctf *rct_src)
Definition interface.cc:163
float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2])
#define UI_POPUP_MENU_TOP
uiBlock *(*)(bContext *C, uiPopupBlockHandle *handle, void *arg1) uiBlockHandleCreateFunc
@ UI_RADIAL_NONE
#define UI_MENU_SUBMENU_PADDING
@ UI_PIE_INITIAL_DIRECTION
#define UI_POPUP_MARGIN
bool ui_block_is_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT
#define UI_MENU_SCROLL_PAD
#define UI_MENU_PADDING
#define UI_MENU_SCROLL_ARROW
@ UI_BLOCK_CONTAINS_SUBMENU_BUT
@ UI_SCROLLED
static void ui_popup_block_position(wmWindow *window, ARegion *butregion, uiBut *but, uiBlock *block)
static void ui_block_region_refresh(const bContext *C, ARegion *region)
static void ui_block_region_popup_window_listener(const wmRegionListenerParams *params)
uiPopupBlockHandle * ui_popup_block_create(bContext *C, ARegion *butregion, uiBut *but, uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func, void *arg, uiFreeArgFunc arg_free, const bool can_refresh)
void UI_popup_dummy_panel_set(ARegion *region, uiBlock *block)
static void ui_popup_block_clip(wmWindow *window, uiBlock *block)
void ui_popup_block_scrolltest(uiBlock *block)
void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle)
void ui_popup_translate(ARegion *region, const int mdiff[2])
uiBlock * ui_popup_block_refresh(bContext *C, uiPopupBlockHandle *handle, ARegion *butregion, uiBut *but)
static void ui_block_region_draw(const bContext *C, ARegion *region)
static void ui_popup_block_remove(bContext *C, uiPopupBlockHandle *handle)
void ui_layout_panel_popup_scroll_apply(Panel *panel, const float dy)
ARegion * ui_region_temp_add(bScreen *screen)
void ui_region_temp_remove(bContext *C, bScreen *screen, ARegion *region)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
static PyObject * create_func(PyObject *, PyObject *args)
Definition python.cpp:161
#define FLT_MAX
Definition stdcycles.h:14
blender::Vector< LayoutPanelBody > bodies
blender::Vector< LayoutPanelHeader > headers
void * last
void * first
uiBlock * block
LayoutPanels layout_panels
struct Panel_Runtime * runtime
float pie_center_spawned[2]
float pie_center_init[2]
float xmax
float xmin
float ymax
float ymin
float winmat[4][4]
PieMenuData pie_data
ListBase saferct
uiBlock * oldblock
uiPopupBlockHandle * handle
ListBase buttons
eBlockBoundsCalc bounds_type
short content_hints
eButType type
uiBlock * block
int xy[2]
Definition WM_types.hh:726
unsigned int action
Definition WM_types.hh:325
unsigned int category
Definition WM_types.hh:325
struct wmEvent * eventstate
struct wmEvent * event_last_handled
void WM_cursor_set(wmWindow *win, int curs)
@ WM_CURSOR_DEFAULT
Definition wm_cursors.hh:15
void wmGetProjectionMatrix(float mat[4][4], const rcti *winrct)
blender::int2 WM_window_native_pixel_size(const wmWindow *win)
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *, wmTimer *timer)
bScreen * WM_window_get_active_screen(const wmWindow *win)