Blender V4.5
interface_panel.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9/* a full doc with API notes can be found in
10 * bf-blender/trunk/blender/doc/guides/interface_API.txt */
11
12#include <algorithm>
13#include <cctype>
14#include <cmath>
15#include <cstdlib>
16#include <cstring>
17
18#include "MEM_guardedalloc.h"
19
20#include "BLI_listbase.h"
21#include "BLI_math_vector.h"
22#include "BLI_string.h"
23#include "BLI_time.h"
24#include "BLI_utildefines.h"
25
26#include "BLT_translation.hh"
27
28#include "DNA_screen_types.h"
29#include "DNA_userdef_types.h"
30
31#include "BKE_context.hh"
32#include "BKE_screen.hh"
33
34#include "RNA_access.hh"
35
36#include "BLF_api.hh"
37
38#include "WM_api.hh"
39#include "WM_types.hh"
40
41#include "ED_screen.hh"
42
43#include "UI_interface.hh"
44#include "UI_interface_c.hh"
45#include "UI_interface_icons.hh"
46#include "UI_resources.hh"
47#include "UI_view2d.hh"
48
49#include "GPU_batch_presets.hh"
50#include "GPU_immediate.hh"
51#include "GPU_matrix.hh"
52#include "GPU_state.hh"
53
54#include "interface_intern.hh"
55
56/* -------------------------------------------------------------------- */
59
60#define ANIMATION_TIME 0.30
61#define ANIMATION_INTERVAL 0.02
62
85
86/* The state of the mouse position relative to the panel. */
93
99
102
103 /* Animation. */
105 double starttime;
106
107 /* Dragging. */
111};
112
118
119static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel);
120static int get_panel_real_size_y(const Panel *panel);
121static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state);
122static int compare_panel(const void *a, const void *b);
123static bool panel_type_context_poll(ARegion *region,
124 const PanelType *panel_type,
125 const char *context);
126
128
129/* -------------------------------------------------------------------- */
132
134 Panel **r_panel_animation,
135 bool *r_no_animation)
136{
137 LISTBASE_FOREACH (Panel *, panel, lb) {
138 /* Detect panel active flag changes. */
139 if (!(panel->type && panel->type->parent)) {
140 if ((panel->runtime_flag & PANEL_WAS_ACTIVE) && !(panel->runtime_flag & PANEL_ACTIVE)) {
141 return true;
142 }
143 if (!(panel->runtime_flag & PANEL_WAS_ACTIVE) && (panel->runtime_flag & PANEL_ACTIVE)) {
144 return true;
145 }
146 }
147
148 /* Detect changes in panel expansions. */
149 if (bool(panel->runtime_flag & PANEL_WAS_CLOSED) != UI_panel_is_closed(panel)) {
150 *r_panel_animation = panel;
151 return false;
152 }
153
154 if ((panel->runtime_flag & PANEL_ACTIVE) && !UI_panel_is_closed(panel)) {
155 if (panel_active_animation_changed(&panel->children, r_panel_animation, r_no_animation)) {
156 return true;
157 }
158 }
159
160 /* Detect animation. */
161 if (panel->activedata) {
162 uiHandlePanelData *data = static_cast<uiHandlePanelData *>(panel->activedata);
163 if (data->state == PANEL_STATE_ANIMATION) {
164 *r_panel_animation = panel;
165 }
166 else {
167 /* Don't animate while handling other interaction. */
168 *r_no_animation = true;
169 }
170 }
171 if ((panel->runtime_flag & PANEL_ANIM_ALIGN) && !(*r_panel_animation)) {
172 *r_panel_animation = panel;
173 }
174 }
175
176 return false;
177}
178
182static bool properties_space_needs_realign(const ScrArea *area, const ARegion *region)
183{
184 if (area->spacetype == SPACE_PROPERTIES && region->regiontype == RGN_TYPE_WINDOW) {
185 const SpaceProperties *sbuts = static_cast<SpaceProperties *>(area->spacedata.first);
186
187 if (sbuts->mainbo != sbuts->mainb) {
188 return true;
189 }
190 }
191
192 return false;
193}
194
195static bool panels_need_realign(const ScrArea *area, ARegion *region, Panel **r_panel_animation)
196{
197 *r_panel_animation = nullptr;
198
199 if (properties_space_needs_realign(area, region)) {
200 return true;
201 }
202
203 /* Detect if a panel was added or removed. */
204 Panel *panel_animation = nullptr;
205 bool no_animation = false;
206 if (panel_active_animation_changed(&region->panels, &panel_animation, &no_animation)) {
207 return true;
208 }
209
210 /* Detect panel marked for animation, if we're not already animating. */
211 if (panel_animation) {
212 if (!no_animation) {
213 *r_panel_animation = panel_animation;
214 }
215 return true;
216 }
217
218 return false;
219}
220
222
223/* -------------------------------------------------------------------- */
226
227static Panel *panel_add_instanced(ListBase *panels, PanelType *panel_type, PointerRNA *custom_data)
228{
229 Panel *panel = BKE_panel_new(panel_type);
230
231 panel->runtime->custom_data_ptr = custom_data;
233
234 /* Add the panel's children too. Although they aren't instanced panels, we can still use this
235 * function to create them, as UI_panel_begin does other things we don't need to do. */
236 LISTBASE_FOREACH (LinkData *, child, &panel_type->children) {
237 PanelType *child_type = static_cast<PanelType *>(child->data);
238 panel_add_instanced(&panel->children, child_type, custom_data);
239 }
240
241 /* Make sure the panel is added to the end of the display-order as well. This is needed for
242 * loading existing files.
243 *
244 * NOTE: We could use special behavior to place it after the panel that starts the list of
245 * instanced panels, but that would add complexity that isn't needed for now. */
246 int max_sortorder = 0;
247 LISTBASE_FOREACH (Panel *, existing_panel, panels) {
248 max_sortorder = std::max(existing_panel->sortorder, max_sortorder);
249 }
250 panel->sortorder = max_sortorder + 1;
251
252 BLI_addtail(panels, panel);
253
254 return panel;
255}
256
258 ARegion *region,
259 ListBase *panels,
260 const char *panel_idname,
261 PointerRNA *custom_data)
262{
263 ARegionType *region_type = region->runtime->type;
264
265 PanelType *panel_type = static_cast<PanelType *>(
266 BLI_findstring(&region_type->paneltypes, panel_idname, offsetof(PanelType, idname)));
267
268 if (panel_type == nullptr) {
269 printf("Panel type '%s' not found.\n", panel_idname);
270 return nullptr;
271 }
272
273 Panel *new_panel = panel_add_instanced(panels, panel_type, custom_data);
274
275 /* Do this after #panel_add_instatnced so all sub-panels are added. */
277
278 return new_panel;
279}
280
281void UI_list_panel_unique_str(Panel *panel, char *r_name)
282{
283 /* The panel sort-order will be unique for a specific panel type because the instanced
284 * panel list is regenerated for every change in the data order / length. */
286}
287
295static void panel_delete(ARegion *region, ListBase *panels, Panel *panel)
296{
297 /* Recursively delete children. */
298 LISTBASE_FOREACH_MUTABLE (Panel *, child, &panel->children) {
299 panel_delete(region, &panel->children, child);
300 }
301 BLI_freelistN(&panel->children);
302
303 BLI_remlink(panels, panel);
304 BKE_panel_free(panel);
305}
306
308{
309 if (panel->activedata != nullptr) {
311 }
312 LISTBASE_FOREACH (Panel *, child, &panel->children) {
314 }
315}
316
318{
319 /* Delete panels with the instanced flag. */
320 LISTBASE_FOREACH_MUTABLE (Panel *, panel, &region->panels) {
321 if (!panel->type) {
322 continue;
323 }
324 if ((panel->type->flag & PANEL_TYPE_INSTANCED) == 0) {
325 continue;
326 }
327 /* Make sure any active handler is removed from this this panel or its children before deleting
328 * them. */
329 if (C != nullptr) {
331 }
332
333 /* Free panel's custom data. */
334 if (panel->runtime->custom_data_ptr != nullptr) {
335 MEM_delete(panel->runtime->custom_data_ptr);
336 }
337
338 /* Free the panel and its sub-panels. */
339 panel_delete(region, &region->panels, panel);
340 }
341}
342
344 ListBase *data,
345 uiListPanelIDFromDataFunc panel_idname_func)
346{
347 /* Check for nullptr data. */
348 int data_len = 0;
349 Link *data_link = nullptr;
350 if (data == nullptr) {
351 data_len = 0;
352 data_link = nullptr;
353 }
354 else {
355 data_len = BLI_listbase_count(data);
356 data_link = static_cast<Link *>(data->first);
357 }
358
359 int i = 0;
360 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
361 if (panel->type != nullptr && panel->type->flag & PANEL_TYPE_INSTANCED) {
362 /* The panels were reordered by drag and drop. */
363 if (panel->flag & PNL_INSTANCED_LIST_ORDER_CHANGED) {
364 return false;
365 }
366
367 /* We reached the last data item before the last instanced panel. */
368 if (data_link == nullptr) {
369 return false;
370 }
371
372 /* Check if the panel type matches the panel type from the data item. */
373 char panel_idname[MAX_NAME];
374 panel_idname_func(data_link, panel_idname);
375 if (!STREQ(panel_idname, panel->type->idname)) {
376 return false;
377 }
378
379 data_link = data_link->next;
380 i++;
381 }
382 }
383
384 /* If we didn't make it to the last list item, the panel list isn't complete. */
385 if (i != data_len) {
386 return false;
387 }
388
389 return true;
390}
391
392static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel)
393{
394 /* Without a type we cannot access the reorder callback. */
395 if (drag_panel->type == nullptr) {
396 return;
397 }
398 /* Don't reorder if this instanced panel doesn't support drag and drop reordering. */
399 if (drag_panel->type->reorder == nullptr) {
400 return;
401 }
402
403 char *context = nullptr;
404 if (!UI_panel_category_is_visible(region)) {
405 context = drag_panel->type->context;
406 }
407
408 /* Find how many instanced panels with this context string. */
409 int list_panels_len = 0;
410 int start_index = -1;
411 LISTBASE_FOREACH (const Panel *, panel, &region->panels) {
412 if (panel->type) {
413 if (panel->type->flag & PANEL_TYPE_INSTANCED) {
414 if (panel_type_context_poll(region, panel->type, context)) {
415 if (panel == drag_panel) {
416 BLI_assert(start_index == -1); /* This panel should only appear once. */
417 start_index = list_panels_len;
418 }
419 list_panels_len++;
420 }
421 }
422 }
423 }
424 BLI_assert(start_index != -1); /* The drag panel should definitely be in the list. */
425
426 /* Sort the matching instanced panels by their display order. */
427 PanelSort *panel_sort = static_cast<PanelSort *>(
428 MEM_callocN(list_panels_len * sizeof(*panel_sort), __func__));
429 PanelSort *sort_index = panel_sort;
430 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
431 if (panel->type) {
432 if (panel->type->flag & PANEL_TYPE_INSTANCED) {
433 if (panel_type_context_poll(region, panel->type, context)) {
434 sort_index->panel = panel;
435 sort_index++;
436 }
437 }
438 }
439 }
440 qsort(panel_sort, list_panels_len, sizeof(*panel_sort), compare_panel);
441
442 /* Find how many of those panels are above this panel. */
443 int move_to_index = 0;
444 for (; move_to_index < list_panels_len; move_to_index++) {
445 if (panel_sort[move_to_index].panel == drag_panel) {
446 break;
447 }
448 }
449
450 MEM_freeN(panel_sort);
451
452 if (move_to_index == start_index) {
453 /* In this case, the reorder was not changed, so don't do any updates or call the callback. */
454 return;
455 }
456
457 /* Set the bit to tell the interface to instanced the list. */
459
460 CTX_store_set(C, drag_panel->runtime->context);
461
462 /* Finally, move this panel's list item to the new index in its list. */
463 drag_panel->type->reorder(C, drag_panel, move_to_index);
464
465 CTX_store_set(C, nullptr);
466}
467
473static bool panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index)
474{
475 const bool open = (flag & (1 << *flag_index));
476 bool changed = (open == UI_panel_is_closed(panel));
477
478 SET_FLAG_FROM_TEST(panel->flag, !open, PNL_CLOSED);
479
480 LISTBASE_FOREACH (Panel *, child, &panel->children) {
481 *flag_index = *flag_index + 1;
482 changed |= panel_set_expand_from_list_data_recursive(child, flag, flag_index);
483 }
484 return changed;
485}
486
492{
493 BLI_assert(panel->type != nullptr);
495 if (panel->type->get_list_data_expand_flag == nullptr) {
496 /* Instanced panel doesn't support loading expansion. */
497 return;
498 }
499
500 const short expand_flag = panel->type->get_list_data_expand_flag(C, panel);
501 short flag_index = 0;
502
503 /* Start panel animation if the open state was changed. */
504 if (panel_set_expand_from_list_data_recursive(panel, expand_flag, &flag_index)) {
506 }
507}
508
513{
514 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
515 if (panel->runtime_flag & PANEL_ACTIVE) {
516 PanelType *panel_type = panel->type;
517 if (panel_type != nullptr && panel->type->flag & PANEL_TYPE_INSTANCED) {
519 }
520 }
521 }
522}
523
527static void get_panel_expand_flag(const Panel *panel, short *flag, short *flag_index)
528{
529 const bool open = !(panel->flag & PNL_CLOSED);
530 SET_FLAG_FROM_TEST(*flag, open, (1 << *flag_index));
531
532 LISTBASE_FOREACH (const Panel *, child, &panel->children) {
533 *flag_index = *flag_index + 1;
534 get_panel_expand_flag(child, flag, flag_index);
535 }
536}
537
546static void set_panels_list_data_expand_flag(const bContext *C, const ARegion *region)
547{
548 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
549 PanelType *panel_type = panel->type;
550 if (panel_type == nullptr) {
551 continue;
552 }
553
554 /* Check for #PANEL_ACTIVE so we only set the expand flag for active panels. */
555 if (panel_type->flag & PANEL_TYPE_INSTANCED && panel->runtime_flag & PANEL_ACTIVE) {
556 short expand_flag;
557 short flag_index = 0;
558 get_panel_expand_flag(panel, &expand_flag, &flag_index);
559 if (panel->type->set_list_data_expand_flag) {
560 panel->type->set_list_data_expand_flag(C, panel, expand_flag);
561 }
562 }
563 }
564}
565
567
568/* -------------------------------------------------------------------- */
571
572static bool panel_custom_pin_to_last_get(const Panel *panel)
573{
574 if (panel->type->pin_to_last_property[0] != '\0') {
576 if (ptr != nullptr && !RNA_pointer_is_null(ptr)) {
578 }
579 }
580
581 return false;
582}
583
584static void panel_custom_pin_to_last_set(const bContext *C, const Panel *panel, const bool value)
585{
586 if (panel->type->pin_to_last_property[0] != '\0') {
588 if (ptr != nullptr && !RNA_pointer_is_null(ptr)) {
591 RNA_property_update(const_cast<bContext *>(C), ptr, prop);
592 }
593 }
594}
595
596static bool panel_custom_data_active_get(const Panel *panel)
597{
598 /* The caller should make sure the panel is active and has a type. */
600 BLI_assert(panel->type != nullptr);
601
602 if (panel->type->active_property[0] != '\0') {
604 if (ptr != nullptr && !RNA_pointer_is_null(ptr)) {
605 return RNA_boolean_get(ptr, panel->type->active_property);
606 }
607 }
608
609 return false;
610}
611
613{
614 /* Since the panel is interacted with, it should be active and have a type. */
616 BLI_assert(panel->type != nullptr);
617
618 if (panel->type->active_property[0] != '\0') {
621 if (ptr != nullptr && !RNA_pointer_is_null(ptr)) {
622 RNA_boolean_set(ptr, panel->type->active_property, true);
623 }
624 }
625}
626
630static void panel_set_flag_recursive(Panel *panel, short flag, bool value)
631{
632 SET_FLAG_FROM_TEST(panel->flag, value, flag);
633
634 LISTBASE_FOREACH (Panel *, child, &panel->children) {
635 panel_set_flag_recursive(child, flag, value);
636 }
637}
638
642static void panel_set_runtime_flag_recursive(Panel *panel, short flag, bool value)
643{
644 SET_FLAG_FROM_TEST(panel->runtime_flag, value, flag);
645
646 LISTBASE_FOREACH (Panel *, sub_panel, &panel->children) {
647 panel_set_runtime_flag_recursive(sub_panel, flag, value);
648 }
649}
650
651static void panels_collapse_all(ARegion *region, const Panel *from_panel)
652{
653 const bool has_category_tabs = UI_panel_category_is_visible(region);
654 const char *category = has_category_tabs ? UI_panel_category_active_get(region, false) : nullptr;
655 const PanelType *from_pt = from_panel->type;
656
657 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
658 PanelType *pt = panel->type;
659
660 /* Close panels with headers in the same context. */
661 if (pt && from_pt && !(pt->flag & PANEL_TYPE_NO_HEADER)) {
662 if (!pt->context[0] || !from_pt->context[0] || STREQ(pt->context, from_pt->context)) {
663 if ((panel->flag & PNL_PIN) || !category || !pt->category[0] ||
664 STREQ(pt->category, category))
665 {
666 panel->flag |= PNL_CLOSED;
667 }
668 }
669 }
670 }
671}
672
674 const PanelType *panel_type,
675 const char *context)
676{
677 if (!BLI_listbase_is_empty(&region->runtime->panels_category)) {
678 return STREQ(panel_type->category, UI_panel_category_active_get(region, false));
679 }
680
681 if (panel_type->context[0] && STREQ(panel_type->context, context)) {
682 return true;
683 }
684
685 return false;
686}
687
689{
690 const char *idname = pt->idname;
691
692 LISTBASE_FOREACH (Panel *, panel, lb) {
693 if (STREQLEN(panel->panelname, idname, sizeof(panel->panelname))) {
694 return panel;
695 }
696 }
697 return nullptr;
698}
699
701 ARegion *region, ListBase *lb, uiBlock *block, PanelType *pt, Panel *panel, bool *r_open)
702{
703 Panel *panel_last;
704 const char *drawname = CTX_IFACE_(pt->translation_context, pt->label);
705 const bool newpanel = (panel == nullptr);
706
707 if (newpanel) {
708 panel = BKE_panel_new(pt);
709
711 panel->flag |= PNL_CLOSED;
713 }
714
715 panel->ofsx = 0;
716 panel->ofsy = 0;
717 panel->sizex = 0;
718 panel->sizey = 0;
719 panel->blocksizex = 0;
720 panel->blocksizey = 0;
722
723 BLI_addtail(lb, panel);
724 }
725 else {
726 /* Panel already exists. */
727 panel->type = pt;
728 }
729
730 panel->runtime->block = block;
731
732 UI_panel_drawname_set(panel, drawname);
733
734 /* If a new panel is added, we insert it right after the panel that was last added.
735 * This way new panels are inserted in the right place between versions. */
736 for (panel_last = static_cast<Panel *>(lb->first); panel_last; panel_last = panel_last->next) {
737 if (panel_last->runtime_flag & PANEL_LAST_ADDED) {
738 BLI_remlink(lb, panel);
739 BLI_insertlinkafter(lb, panel_last, panel);
740 break;
741 }
742 }
743
744 if (newpanel) {
745 panel->sortorder = (panel_last) ? panel_last->sortorder + 1 : 0;
746
747 LISTBASE_FOREACH (Panel *, panel_next, lb) {
748 if (panel_next != panel && panel_next->sortorder >= panel->sortorder) {
749 panel_next->sortorder++;
750 }
751 }
752 }
753
754 if (panel_last) {
755 panel_last->runtime_flag &= ~PANEL_LAST_ADDED;
756 }
757
758 /* Assign the new panel to the block. */
759 block->panel = panel;
761 if (region->alignment == RGN_ALIGN_FLOAT) {
763 }
764
765 *r_open = !UI_panel_is_closed(panel);
766
767 return panel;
768}
769
776
778{
779 uiBlock *block = panel->runtime->block;
780
781 /* A button group should always be created in #UI_panel_header_buttons_begin. */
783
784 uiButtonGroup &button_group = block->button_groups.last();
785 button_group.flag &= ~UI_BUTTON_GROUP_LOCK;
786
787 /* Repurpose the first header button group if it is empty, in case the first button added to
788 * the panel doesn't add a new group (if the button is created directly rather than through an
789 * interface layout call). */
790 if (block->button_groups.size() == 1 && button_group.buttons.is_empty()) {
791 button_group.flag &= ~UI_BUTTON_GROUP_PANEL_HEADER;
792 }
793 else {
794 /* Always add a new button group. Although this may result in many empty groups, without it,
795 * new buttons in the panel body not protected with a #ui_block_new_button_group call would
796 * end up in the panel header group. */
798 }
799}
800
801static float panel_region_offset_x_get(const ARegion *region)
802{
803 if (UI_panel_category_is_visible(region)) {
806 }
807 }
808
809 return 0.0f;
810}
811
816static void panel_calculate_size_recursive(ARegion *region, Panel *panel)
817{
818 int width = panel->blocksizex;
819 int height = panel->blocksizey;
820
821 LISTBASE_FOREACH (Panel *, child_panel, &panel->children) {
822 if (child_panel->runtime_flag & PANEL_ACTIVE) {
823 panel_calculate_size_recursive(region, child_panel);
824 width = max_ii(width, child_panel->sizex);
825 height += get_panel_real_size_y(child_panel);
826 }
827 }
828
829 /* Update total panel size. */
830 if (panel->runtime_flag & PANEL_NEW_ADDED) {
832 panel->sizex = width;
833 panel->sizey = height;
834 }
835 else {
836 const int old_sizex = panel->sizex, old_sizey = panel->sizey;
837 const int old_region_ofsx = panel->runtime->region_ofsx;
838
839 /* Update width/height if non-zero. */
840 if (width != 0) {
841 panel->sizex = width;
842 }
843 if (height != 0 || !UI_panel_is_closed(panel)) {
844 panel->sizey = height;
845 }
846
847 /* Check if we need to do an animation. */
848 if (panel->sizex != old_sizex || panel->sizey != old_sizey) {
850 panel->ofsy += old_sizey - panel->sizey;
851 }
852
854 if (old_region_ofsx != panel->runtime->region_ofsx) {
856 }
857 }
858}
859
860void UI_panel_end(Panel *panel, int width, int height)
861{
862 /* Store the size of the buttons layout in the panel. The actual panel size
863 * (including sub-panels) is calculated in #UI_panels_end. */
864 panel->blocksizex = width;
865 panel->blocksizey = height;
866}
867
869{
870 MEM_SAFE_FREE(panel->drawname);
871 panel->drawname = BLI_strdupn(name.data(), name.size());
872}
873
875{
876 const uiStyle *style = UI_style_get_dpi();
877
878 /* Compute bounds and offset. */
880
881 const int ofsy = block->panel->sizey - style->panelspace;
882
883 for (const std::unique_ptr<uiBut> &but : block->buttons) {
884 but->rect.ymin += ofsy;
885 but->rect.ymax += ofsy;
886 }
887
888 block->rect.xmax = block->panel->sizex;
889 block->rect.ymax = block->panel->sizey;
890 block->rect.xmin = block->rect.ymin = 0.0;
891}
892
897
898static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches)
899{
900 *filter_matches |= bool(panel->runtime_flag & PANEL_SEARCH_FILTER_MATCH);
901
902 /* If the panel has no match we need to make sure that its children are too. */
903 if (!*filter_matches) {
904 LISTBASE_FOREACH (const Panel *, child_panel, &panel->children) {
905 panel_matches_search_filter_recursive(child_panel, filter_matches);
906 }
907 }
908}
909
911{
912 bool search_filter_matches = false;
913 panel_matches_search_filter_recursive(panel, &search_filter_matches);
914 return search_filter_matches;
915}
916
921 Panel *panel,
922 const bool use_search_closed)
923{
924 /* This has to run on inactive panels that may not have a type,
925 * but we can prevent running on header-less panels in some cases. */
926 if (panel->type == nullptr || !(panel->type->flag & PANEL_TYPE_NO_HEADER)) {
928 }
929
930 LISTBASE_FOREACH (Panel *, child_panel, &panel->children) {
931 /* Don't check if the sub-panel is active, otherwise the
932 * expansion won't be reset when the parent is closed. */
933 panel_set_expansion_from_search_filter_recursive(C, child_panel, use_search_closed);
934 }
935}
936
941 ARegion *region,
942 const bool use_search_closed)
943{
944 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
945 /* Don't check if the panel is active, otherwise the expansion won't
946 * be correct when switching back to tab after exiting search. */
947 panel_set_expansion_from_search_filter_recursive(C, panel, use_search_closed);
948 }
950}
951
956static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel)
957{
958 uiBlock *block = panel->runtime->block;
959 BLI_assert(block != nullptr);
960 BLI_assert(block->active);
961 if (parent_panel != nullptr && UI_panel_is_closed(parent_panel)) {
962 /* The parent panel is closed, so this panel can be completely removed. */
963 UI_block_set_search_only(block, true);
964 for (const std::unique_ptr<uiBut> &but : block->buttons) {
965 but->flag |= UI_HIDDEN;
966 }
967 }
968 else if (UI_panel_is_closed(panel)) {
969 /* If sub-panels have no search results but the parent panel does, then the parent panel open
970 * and the sub-panels will close. In that case there must be a way to hide the buttons in the
971 * panel but keep the header buttons. */
972 for (const uiButtonGroup &button_group : block->button_groups) {
973 if (button_group.flag & UI_BUTTON_GROUP_PANEL_HEADER) {
974 continue;
975 }
976 for (uiBut *but : button_group.buttons) {
977 but->flag |= UI_HIDDEN;
978 }
979 }
980 }
981
982 LISTBASE_FOREACH (Panel *, child_panel, &panel->children) {
983 if (child_panel->runtime_flag & PANEL_ACTIVE) {
984 BLI_assert(child_panel->runtime->block != nullptr);
986 }
987 }
988}
989
991{
992 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
993 if (panel->runtime_flag & PANEL_ACTIVE) {
994 BLI_assert(panel->runtime->block != nullptr);
996 }
997 }
998}
999
1000bool UI_panel_is_closed(const Panel *panel)
1001{
1002 /* Header-less panels can never be closed, otherwise they could disappear. */
1003 if (panel->type && panel->type->flag & PANEL_TYPE_NO_HEADER) {
1004 return false;
1005 }
1006
1008 return !UI_panel_matches_search_filter(panel);
1009 }
1010
1011 return panel->flag & PNL_CLOSED;
1012}
1013
1014bool UI_panel_is_active(const Panel *panel)
1015{
1016 return panel->runtime_flag & PANEL_ACTIVE;
1017}
1018
1020
1021/* -------------------------------------------------------------------- */
1024
1025void UI_panels_draw(const bContext *C, ARegion *region)
1026{
1027 /* Draw in reverse order, because #uiBlocks are added in reverse order
1028 * and we need child panels to draw on top. */
1029 LISTBASE_FOREACH_BACKWARD (uiBlock *, block, &region->runtime->uiblocks) {
1030 if (block->active && block->panel && !UI_panel_is_dragging(block->panel) &&
1032 {
1033 UI_block_draw(C, block);
1034 }
1035 }
1036
1037 LISTBASE_FOREACH_BACKWARD (uiBlock *, block, &region->runtime->uiblocks) {
1038 if (block->active && block->panel && UI_panel_is_dragging(block->panel) &&
1040 {
1041 UI_block_draw(C, block);
1042 }
1043 }
1044}
1045
1046#define PNL_ICON UI_UNIT_X /* Could be UI_UNIT_Y too. */
1047
1048void UI_panel_label_offset(const uiBlock *block, int *r_x, int *r_y)
1049{
1050 Panel *panel = block->panel;
1051 const bool is_subpanel = (panel->type && panel->type->parent);
1052
1053 *r_x = UI_UNIT_X * 1.0f;
1054 *r_y = UI_UNIT_Y * 1.5f;
1055
1056 if (is_subpanel) {
1057 *r_x += (0.7f * UI_UNIT_X);
1058 }
1059}
1060
1061static void panel_title_color_get(const Panel *panel,
1062 const bool show_background,
1063 const bool region_search_filter_active,
1064 uchar r_color[4])
1065{
1066 if (!show_background) {
1067 /* Use menu colors for floating panels. */
1068 bTheme *btheme = UI_GetTheme();
1069 const uiWidgetColors *wcol = &btheme->tui.wcol_menu_back;
1070 copy_v4_v4_uchar(r_color, (const uchar *)wcol->text);
1071 return;
1072 }
1073
1074 const bool search_match = UI_panel_matches_search_filter(panel);
1075
1077 if (region_search_filter_active && !search_match) {
1078 r_color[0] *= 0.5;
1079 r_color[1] *= 0.5;
1080 r_color[2] *= 0.5;
1081 }
1082}
1083
1084static void panel_draw_highlight_border(const Panel *panel,
1085 const rcti *rect,
1086 const rcti *header_rect)
1087{
1088 const bool is_subpanel = panel->type->parent != nullptr;
1089 if (is_subpanel) {
1090 return;
1091 }
1092
1093 const bTheme *btheme = UI_GetTheme();
1094 const float aspect = panel->runtime->block->aspect;
1095 const float radius = (btheme->tui.panel_roundness * U.widget_unit * 0.5f) / aspect;
1097
1098 rctf box_rect;
1099 box_rect.xmin = rect->xmin;
1100 box_rect.xmax = rect->xmax;
1101 box_rect.ymin = UI_panel_is_closed(panel) ? header_rect->ymin : rect->ymin;
1102 box_rect.ymax = header_rect->ymax;
1103
1104 float color[4];
1106 UI_draw_roundbox_4fv(&box_rect, false, radius, color);
1107}
1108
1109static void panel_draw_aligned_widgets(const uiStyle *style,
1110 const Panel *panel,
1111 const rcti *header_rect,
1112 const float aspect,
1113 const bool show_pin,
1114 const bool show_background,
1115 const bool region_search_filter_active)
1116{
1117 const bool is_subpanel = panel->type->parent != nullptr;
1118 const uiFontStyle *fontstyle = (is_subpanel) ? &style->widget : &style->paneltitle;
1119
1120 const int header_height = BLI_rcti_size_y(header_rect);
1121 const int scaled_unit = round_fl_to_int(UI_UNIT_X / aspect);
1122
1123 /* Offset triangle and text to the right for sub-panels. */
1124 rcti widget_rect;
1125 widget_rect.xmin = header_rect->xmin + (is_subpanel ? scaled_unit * 0.7f : 0);
1126 widget_rect.xmax = header_rect->xmax;
1127 widget_rect.ymin = header_rect->ymin;
1128 widget_rect.ymax = header_rect->ymax;
1129
1130 uchar title_color[4];
1131 panel_title_color_get(panel, show_background, region_search_filter_active, title_color);
1132 title_color[3] = 255;
1133
1134 /* Draw collapse icon. */
1135 {
1136 const float size_y = BLI_rcti_size_y(&widget_rect);
1138 UI_icon_draw_ex(widget_rect.xmin + size_y * 0.2f,
1139 widget_rect.ymin + size_y * (UI_panel_is_closed(panel) ? 0.17f : 0.14f),
1140 UI_panel_is_closed(panel) ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT,
1141 aspect * UI_INV_SCALE_FAC,
1142 0.8f,
1143 0.0f,
1144 title_color,
1145 false,
1148 }
1149
1150 /* Draw text label. */
1151 if (panel->drawname && panel->drawname[0] != '\0') {
1152 rcti title_rect;
1153 title_rect.xmin = widget_rect.xmin + (panel->labelofs / aspect) + scaled_unit * 1.1f;
1154 title_rect.xmax = widget_rect.xmax;
1155 title_rect.ymin = widget_rect.ymin - 2.0f / aspect;
1156 title_rect.ymax = widget_rect.ymax;
1157
1159 params.align = UI_STYLE_TEXT_LEFT;
1161 fontstyle, &title_rect, panel->drawname, strlen(panel->drawname), title_color, &params);
1162 }
1163
1164 /* Draw the pin icon. */
1165 if (show_pin && (panel->flag & PNL_PIN)) {
1167 UI_icon_draw_ex(widget_rect.xmax - scaled_unit * 2.2f,
1168 widget_rect.ymin + 5.0f / aspect,
1169 ICON_PINNED,
1170 aspect * UI_INV_SCALE_FAC,
1171 1.0f,
1172 0.0f,
1173 title_color,
1174 false,
1177 }
1178
1179 /* Draw drag widget. */
1180 if (!is_subpanel && show_background) {
1181 const float x = widget_rect.xmax - scaled_unit * 1.15;
1182 const float y = widget_rect.ymin + (header_height - (header_height * 0.7f)) * 0.5f;
1183 const bool is_pin = panel_custom_pin_to_last_get(panel);
1184 const int icon = is_pin ? ICON_PINNED : ICON_GRIP;
1185 const float size = aspect * UI_INV_SCALE_FAC;
1186 const float alpha = is_pin ? 1.0f : 0.5f;
1187 UI_icon_draw_ex(x, y, icon, size, alpha, 0.0f, title_color, false, UI_NO_ICON_OVERLAY_TEXT);
1188 }
1189}
1190
1192 const Panel *panel,
1193 const float radius,
1194 float subpanel_backcolor[4])
1195{
1196 /* Draw backdrops for layout panels. */
1197 const float aspect = ui_block_is_popup_any(panel->runtime->block) ?
1198 panel->runtime->block->aspect :
1199 1.0f;
1200
1201 for (const LayoutPanelBody &body : panel->runtime->layout_panels.bodies) {
1202
1203 rctf panel_blockspace = panel->runtime->block->rect;
1204 panel_blockspace.ymax = panel->runtime->block->rect.ymax + body.end_y;
1205 panel_blockspace.ymin = panel->runtime->block->rect.ymax + body.start_y;
1206
1207 if (panel_blockspace.ymax <= panel->runtime->block->rect.ymin) {
1208 /* Layout panels no longer fits in block rectangle, stop drawing backdrops. */
1209 break;
1210 }
1211 if (panel_blockspace.ymin >= panel->runtime->block->rect.ymax) {
1212 /* Skip layout panels that scrolled to the top of the block rectangle. */
1213 continue;
1214 }
1215 /* If the layout panel is at the end of the root panel, it's bottom corners are rounded. */
1216 const bool is_main_panel_end = panel_blockspace.ymin - panel->runtime->block->rect.ymin <
1217 (10.0f / aspect);
1218 if (is_main_panel_end) {
1219 panel_blockspace.ymin = panel->runtime->block->rect.ymin;
1221 }
1222 else {
1224 }
1225 panel_blockspace.ymax = std::min(panel_blockspace.ymax, panel->runtime->block->rect.ymax);
1226
1227 rcti panel_pixelspace = ui_to_pixelrect(region, panel->runtime->block, &panel_blockspace);
1228 rctf panel_pixelspacef;
1229 BLI_rctf_rcti_copy(&panel_pixelspacef, &panel_pixelspace);
1230 UI_draw_roundbox_4fv(&panel_pixelspacef, true, radius, subpanel_backcolor);
1231 }
1232}
1233
1234static void panel_draw_aligned_backdrop(const ARegion *region,
1235 const Panel *panel,
1236 const rcti *rect,
1237 const rcti *header_rect)
1238{
1239 const bool is_open = !UI_panel_is_closed(panel);
1240 const bool is_subpanel = panel->type->parent != nullptr;
1241 const bool has_header = (panel->type->flag & PANEL_TYPE_NO_HEADER) == 0;
1242
1243 if (is_subpanel && !is_open) {
1244 return;
1245 }
1246
1247 const bTheme *btheme = UI_GetTheme();
1248 const float aspect = panel->runtime->block->aspect;
1249 const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f / aspect;
1250
1253
1254 /* Panel backdrop. */
1255 if (is_open || !has_header) {
1256 float panel_backcolor[4];
1258 if (!has_header) {
1259 UI_GetThemeColor4fv(TH_BACK, panel_backcolor);
1260 }
1261 else {
1262 UI_GetThemeColor4fv((is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK), panel_backcolor);
1263 }
1264
1265 rctf box_rect;
1266 box_rect.xmin = rect->xmin;
1267 box_rect.xmax = rect->xmax;
1268 box_rect.ymin = rect->ymin;
1269 box_rect.ymax = rect->ymax;
1270 UI_draw_roundbox_4fv(&box_rect, true, radius, panel_backcolor);
1271
1272 float subpanel_backcolor[4];
1273 UI_GetThemeColor4fv(TH_PANEL_SUB_BACK, subpanel_backcolor);
1274 ui_draw_layout_panels_backdrop(region, panel, radius, subpanel_backcolor);
1275 }
1276
1277 /* Panel header backdrops for non sub-panels. */
1278 if (!is_subpanel && has_header) {
1279 float panel_headercolor[4];
1281 panel_headercolor);
1283
1284 /* Change the width a little bit to line up with the sides. */
1285 rctf box_rect;
1286 box_rect.xmin = rect->xmin;
1287 box_rect.xmax = rect->xmax;
1288 box_rect.ymin = header_rect->ymin;
1289 box_rect.ymax = header_rect->ymax;
1290 UI_draw_roundbox_4fv(&box_rect, true, radius, panel_headercolor);
1291 }
1292
1295}
1296
1298 const uiStyle *style,
1299 const uiBlock *block,
1300 const rcti *rect,
1301 const bool show_pin,
1302 const bool show_background,
1303 const bool region_search_filter_active)
1304{
1305 const Panel *panel = block->panel;
1306
1307 /* Add 0.001f to prevent flicker from float inaccuracy. */
1308 const rcti header_rect = {
1309 rect->xmin,
1310 rect->xmax,
1311 rect->ymax,
1312 rect->ymax + int(floor(PNL_HEADER / block->aspect + 0.001f)),
1313 };
1314
1315 if (show_background || (panel->type->flag & PANEL_TYPE_NO_HEADER)) {
1316 panel_draw_aligned_backdrop(region, panel, rect, &header_rect);
1317 }
1318
1319 /* Draw the widgets and text in the panel header. */
1320 if (!(panel->type->flag & PANEL_TYPE_NO_HEADER)) {
1322 panel,
1323 &header_rect,
1324 block->aspect,
1325 show_pin,
1326 show_background,
1327 region_search_filter_active);
1328 }
1329
1330 if (panel_custom_data_active_get(panel)) {
1331 panel_draw_highlight_border(panel, rect, &header_rect);
1332 }
1333}
1334
1335bool UI_panel_should_show_background(const ARegion *region, const PanelType *panel_type)
1336{
1337 if (region->alignment == RGN_ALIGN_FLOAT) {
1338 return false;
1339 }
1340
1341 if (panel_type && panel_type->flag & PANEL_TYPE_NO_HEADER) {
1342 if (region->regiontype == RGN_TYPE_TOOLS) {
1343 /* We never want a background around active tools. */
1344 return false;
1345 }
1346 /* Without a header there is no background except for region overlap. */
1347 return region->overlap != 0;
1348 }
1349
1350 return true;
1351}
1352
1354
1355/* -------------------------------------------------------------------- */
1358
1359#define TABS_PADDING_BETWEEN_FACTOR 4.0f
1360#define TABS_PADDING_TEXT_FACTOR 6.0f
1361
1362void UI_panel_category_draw_all(ARegion *region, const char *category_id_active)
1363{
1364 // #define USE_FLAT_INACTIVE
1366 View2D *v2d = &region->v2d;
1367 const uiStyle *style = UI_style_get();
1368 const uiFontStyle *fstyle = &style->widget;
1369 UI_fontstyle_set(fstyle);
1370 const int fontid = fstyle->uifont_id;
1371 float fstyle_points = fstyle->points;
1372 const float aspect = BLI_listbase_is_empty(&region->runtime->uiblocks) ?
1373 1.0f :
1374 ((uiBlock *)region->runtime->uiblocks.first)->aspect;
1375 const float zoom = 1.0f / aspect;
1376 const int px = U.pixelsize;
1377 const int category_tabs_width = round_fl_to_int(UI_PANEL_CATEGORY_MARGIN_WIDTH * zoom);
1378 const float dpi_fac = UI_SCALE_FAC;
1379 /* Padding of tabs around text. */
1380 const int tab_v_pad_text = round_fl_to_int(TABS_PADDING_TEXT_FACTOR * dpi_fac * zoom) + 2 * px;
1381 /* Padding between tabs. */
1382 const int tab_v_pad = round_fl_to_int(TABS_PADDING_BETWEEN_FACTOR * dpi_fac * zoom);
1383 bTheme *btheme = UI_GetTheme();
1384 const float tab_curve_radius = btheme->tui.wcol_tab.roundness * U.widget_unit * zoom;
1387 bool is_alpha;
1388#ifdef USE_FLAT_INACTIVE
1389 bool is_active_prev = false;
1390#endif
1391 /* Same for all tabs. */
1392 /* Intentionally don't scale by 'px'. */
1393 const int rct_xmin = is_left ? v2d->mask.xmin + 3 : (v2d->mask.xmax - category_tabs_width);
1394 const int rct_xmax = is_left ? v2d->mask.xmin + category_tabs_width : (v2d->mask.xmax - 3);
1395 int y_ofs = tab_v_pad;
1396
1397 /* Primary theme colors. */
1398 uchar theme_col_back[4];
1399 uchar theme_col_text[3];
1400 uchar theme_col_text_hi[3];
1401
1402 /* Tab colors. */
1403 uchar theme_col_tab_bg[4];
1404 float theme_col_tab_active[4];
1405 float theme_col_tab_inactive[4];
1406 float theme_col_tab_outline[4];
1407
1408 UI_GetThemeColor4ubv(TH_BACK, theme_col_back);
1409 UI_GetThemeColor3ubv(TH_TEXT, theme_col_text);
1410 UI_GetThemeColor3ubv(TH_TEXT_HI, theme_col_text_hi);
1411
1412 UI_GetThemeColor4ubv(TH_TAB_BACK, theme_col_tab_bg);
1413 UI_GetThemeColor4fv(TH_TAB_ACTIVE, theme_col_tab_active);
1414 UI_GetThemeColor4fv(TH_TAB_INACTIVE, theme_col_tab_inactive);
1415 UI_GetThemeColor4fv(TH_TAB_OUTLINE, theme_col_tab_outline);
1416
1417 is_alpha = (region->overlap && (theme_col_back[3] != 255));
1418
1419 BLF_enable(fontid, BLF_ROTATION);
1420 BLF_rotation(fontid, is_left ? M_PI_2 : -M_PI_2);
1421 ui_fontscale(&fstyle_points, aspect);
1422 BLF_size(fontid, fstyle_points * UI_SCALE_FAC);
1423
1424 /* Check the region type supports categories to avoid an assert
1425 * for showing 3D view panels in the properties space. */
1426 if ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) {
1428 }
1429
1430 /* Calculate tab rectangle for each panel. */
1431 LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, &region->runtime->panels_category) {
1432 rcti *rct = &pc_dyn->rect;
1433 const char *category_id = pc_dyn->idname;
1434 const char *category_id_draw = IFACE_(category_id);
1435 const int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX);
1436
1437 rct->xmin = rct_xmin;
1438 rct->xmax = rct_xmax;
1439
1440 rct->ymin = v2d->mask.ymax - (y_ofs + category_width + (tab_v_pad_text * 2));
1441 rct->ymax = v2d->mask.ymax - (y_ofs);
1442
1443 y_ofs += category_width + tab_v_pad + (tab_v_pad_text * 2);
1444 }
1445
1446 const int max_scroll = max_ii(y_ofs - BLI_rcti_size_y(&v2d->mask), 0);
1447 const int scroll = clamp_i(region->category_scroll, 0, max_scroll);
1448 region->category_scroll = scroll;
1449 LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, &region->runtime->panels_category) {
1450 rcti *rct = &pc_dyn->rect;
1451 rct->ymin += scroll;
1452 rct->ymax += scroll;
1453 }
1454
1455 /* Begin drawing. */
1456 GPU_line_smooth(true);
1457
1460
1461 /* Draw the background. */
1462 if (is_alpha) {
1464 immUniformColor4ubv(theme_col_tab_bg);
1465 }
1466 else {
1467 immUniformColor3ubv(theme_col_tab_bg);
1468 }
1469
1470 if (is_left) {
1471 immRectf(
1472 pos, v2d->mask.xmin, v2d->mask.ymin, v2d->mask.xmin + category_tabs_width, v2d->mask.ymax);
1473 }
1474 else {
1475 immRectf(pos,
1476 v2d->mask.xmax - category_tabs_width,
1477 v2d->mask.ymin,
1478 v2d->mask.xmax + 1,
1479 v2d->mask.ymax);
1480 }
1481
1482 if (is_alpha) {
1484 }
1485
1487
1488 LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, &region->runtime->panels_category) {
1489 const rcti *rct = &pc_dyn->rect;
1490 if (rct->ymin > v2d->mask.ymax) {
1491 /* Scrolled outside the top of the view, check the next tab. */
1492 continue;
1493 }
1494 if (rct->ymax < v2d->mask.ymin) {
1495 /* Scrolled past visible bounds, no need to draw other tabs. */
1496 break;
1497 }
1498 const char *category_id = pc_dyn->idname;
1499 const char *category_id_draw = IFACE_(category_id);
1500 size_t category_draw_len = BLF_DRAW_STR_DUMMY_MAX;
1501 const bool is_active = STREQ(category_id, category_id_active);
1502
1504
1505#ifdef USE_FLAT_INACTIVE
1506 /* Draw line between inactive tabs. */
1507 if (is_active == false && is_active_prev == false && pc_dyn->prev) {
1510 immUniformColor3fvAlpha(theme_col_tab_outline, 0.3f);
1511 immRectf(pos,
1512 is_left ? v2d->mask.xmin + (category_tabs_width / 5) :
1513 v2d->mask.xmax - (category_tabs_width / 5),
1514 rct->ymax + px,
1515 is_left ? (v2d->mask.xmin + category_tabs_width) - (category_tabs_width / 5) :
1516 (v2d->mask.xmax - category_tabs_width) + (category_tabs_width / 5),
1517 rct->ymax + (px * 3));
1519 }
1520
1521 is_active_prev = is_active;
1522
1523 if (is_active)
1524#endif
1525 {
1526 /* Draw filled rectangle and outline for tab. */
1528 rctf box_rect;
1529 box_rect.xmin = rct->xmin;
1530 box_rect.xmax = rct->xmax;
1531 box_rect.ymin = rct->ymin;
1532 box_rect.ymax = rct->ymax;
1533
1534 UI_draw_roundbox_4fv(&box_rect,
1535 true,
1536 tab_curve_radius,
1537 is_active ? theme_col_tab_active : theme_col_tab_inactive);
1538 UI_draw_roundbox_4fv(&box_rect, false, tab_curve_radius, theme_col_tab_outline);
1539
1540 /* Disguise the outline on one side to join the tab to the panel. */
1543
1544 immUniformColor4fv(is_active ? theme_col_tab_active : theme_col_tab_inactive);
1545 immRectf(pos,
1546 is_left ? rct->xmax - px : rct->xmin,
1547 rct->ymin + px,
1548 is_left ? rct->xmax : rct->xmin + px,
1549 rct->ymax - px);
1551 }
1552
1553 /* Tab titles. */
1554
1555 /* Offset toward the middle of the rect. */
1556 const int text_v_ofs = (rct_xmax - rct_xmin) * 0.5f;
1557 /* Offset down as the font size increases. */
1558 const int text_size_offset = int(fstyle_points * UI_SCALE_FAC * 0.35f);
1559
1560 BLF_position(fontid,
1561 is_left ? rct->xmax - text_v_ofs + text_size_offset :
1562 rct->xmin + text_v_ofs - text_size_offset,
1563 is_left ? rct->ymin + tab_v_pad_text : rct->ymax - tab_v_pad_text,
1564 0.0f);
1565 BLF_color3ubv(fontid, is_active ? theme_col_text_hi : theme_col_text);
1566 BLF_draw(fontid, category_id_draw, category_draw_len);
1567
1569
1570 /* Not essential, but allows events to be handled right up to the region edge (#38171). */
1571 if (is_left) {
1572 pc_dyn->rect.xmin = v2d->mask.xmin;
1573 }
1574 else {
1575 pc_dyn->rect.xmax = v2d->mask.xmax;
1576 }
1577 }
1578
1579 GPU_line_smooth(false);
1580
1581 BLF_disable(fontid, BLF_ROTATION);
1582}
1583
1584#undef TABS_PADDING_BETWEEN_FACTOR
1585#undef TABS_PADDING_TEXT_FACTOR
1586
1588
1589static int ui_panel_category_show_active_tab(ARegion *region, const int mval[2])
1590{
1591 if (!ED_region_panel_category_gutter_isect_xy(region, mval)) {
1593 }
1594 const View2D *v2d = &region->v2d;
1595 LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, &region->runtime->panels_category) {
1596 const bool is_active = STREQ(pc_dyn->idname, region->runtime->category);
1597 if (!is_active) {
1598 continue;
1599 }
1600 const rcti *rct = &pc_dyn->rect;
1601 region->category_scroll = v2d->mask.ymax - (rct->ymax - region->category_scroll);
1602
1603 if (pc_dyn->next) {
1604 const PanelCategoryDyn *pc_dyn_next = static_cast<PanelCategoryDyn *>(pc_dyn->next);
1605 const int tab_v_pad = rct->ymin - pc_dyn_next->rect.ymax;
1606 region->category_scroll -= tab_v_pad;
1607 }
1608 break;
1609 }
1610 ED_region_tag_redraw(region);
1611 return WM_UI_HANDLER_BREAK;
1612}
1613/* -------------------------------------------------------------------- */
1616
1617static int get_panel_size_y(const Panel *panel)
1618{
1619 if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) {
1620 return panel->sizey;
1621 }
1622
1623 return PNL_HEADER + panel->sizey;
1624}
1625
1626static int get_panel_real_size_y(const Panel *panel)
1627{
1628 const int sizey = UI_panel_is_closed(panel) ? 0 : panel->sizey;
1629
1630 if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) {
1631 return sizey;
1632 }
1633
1634 return PNL_HEADER + sizey;
1635}
1636
1637int UI_panel_size_y(const Panel *panel)
1638{
1639 return get_panel_real_size_y(panel);
1640}
1641
1646static int get_panel_real_ofsy(Panel *panel)
1647{
1648 if (UI_panel_is_closed(panel)) {
1649 return panel->ofsy + panel->sizey;
1650 }
1651 return panel->ofsy;
1652}
1653
1654bool UI_panel_is_dragging(const Panel *panel)
1655{
1656 return panel->runtime_flag & PANEL_IS_DRAG_DROP;
1657}
1658
1666
1667static int find_highest_panel(const void *a, const void *b)
1668{
1669 const Panel *panel_a = ((PanelSort *)a)->panel;
1670 const Panel *panel_b = ((PanelSort *)b)->panel;
1671
1672 /* Stick uppermost header-less panels to the top of the region -
1673 * prevent them from being sorted (multiple header-less panels have to be sorted though). */
1674 if (panel_a->type->flag & PANEL_TYPE_NO_HEADER && panel_b->type->flag & PANEL_TYPE_NO_HEADER) {
1675 /* Pass the no-header checks and check for `ofsy` and #Panel.sortorder below. */
1676 }
1677 else if (panel_a->type->flag & PANEL_TYPE_NO_HEADER) {
1678 return -1;
1679 }
1680 else if (panel_b->type->flag & PANEL_TYPE_NO_HEADER) {
1681 return 1;
1682 }
1683
1684 const bool pin_last_a = panel_custom_pin_to_last_get(panel_a);
1685 const bool pin_last_b = panel_custom_pin_to_last_get(panel_b);
1686 if (pin_last_a && !pin_last_b) {
1687 return 1;
1688 }
1689 if (!pin_last_a && pin_last_b) {
1690 return -1;
1691 }
1692
1693 if (panel_a->ofsy + panel_a->sizey < panel_b->ofsy + panel_b->sizey) {
1694 return 1;
1695 }
1696 if (panel_a->ofsy + panel_a->sizey > panel_b->ofsy + panel_b->sizey) {
1697 return -1;
1698 }
1699 if (panel_a->sortorder > panel_b->sortorder) {
1700 return 1;
1701 }
1702 if (panel_a->sortorder < panel_b->sortorder) {
1703 return -1;
1704 }
1705
1706 return 0;
1707}
1708
1709static int compare_panel(const void *a, const void *b)
1710{
1711 const Panel *panel_a = ((PanelSort *)a)->panel;
1712 const Panel *panel_b = ((PanelSort *)b)->panel;
1713
1714 if (panel_a->sortorder > panel_b->sortorder) {
1715 return 1;
1716 }
1717 if (panel_a->sortorder < panel_b->sortorder) {
1718 return -1;
1719 }
1720
1721 return 0;
1722}
1723
1724static void align_sub_panels(Panel *panel)
1725{
1726 /* Position sub panels. */
1727 int ofsy = panel->ofsy + panel->sizey - panel->blocksizey;
1728
1729 LISTBASE_FOREACH (Panel *, pachild, &panel->children) {
1730 if (pachild->runtime_flag & PANEL_ACTIVE) {
1731 pachild->ofsx = panel->ofsx;
1732 pachild->ofsy = ofsy - get_panel_size_y(pachild);
1733 ofsy -= get_panel_real_size_y(pachild);
1734
1735 if (pachild->children.first) {
1736 align_sub_panels(pachild);
1737 }
1738 }
1739 }
1740}
1741
1745static bool uiAlignPanelStep(ARegion *region, const float factor, const bool drag)
1746{
1747 /* Count active panels. */
1748 int active_panels_len = 0;
1749 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
1750 if (panel->runtime_flag & PANEL_ACTIVE) {
1751 /* These panels should have types since they are currently displayed to the user. */
1752 BLI_assert(panel->type != nullptr);
1753 active_panels_len++;
1754 }
1755 }
1756 if (active_panels_len == 0) {
1757 return false;
1758 }
1759
1760 /* Sort panels. */
1761 PanelSort *panel_sort = MEM_malloc_arrayN<PanelSort>(active_panels_len, __func__);
1762 {
1763 PanelSort *ps = panel_sort;
1764 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
1765 if (panel->runtime_flag & PANEL_ACTIVE) {
1766 ps->panel = panel;
1767 ps++;
1768 }
1769 }
1770 }
1771
1772 if (drag) {
1773 /* While dragging, sort based on location and update #Panel.sortorder. */
1774 qsort(panel_sort, active_panels_len, sizeof(PanelSort), find_highest_panel);
1775 for (int i = 0; i < active_panels_len; i++) {
1776 panel_sort[i].panel->sortorder = i;
1777 }
1778 }
1779 else {
1780 /* Otherwise use #Panel.sortorder. */
1781 qsort(panel_sort, active_panels_len, sizeof(PanelSort), compare_panel);
1782 }
1783
1784 /* X offset. */
1785 const int region_offset_x = panel_region_offset_x_get(region);
1786 for (int i = 0; i < active_panels_len; i++) {
1787 PanelSort *ps = &panel_sort[i];
1788 const bool show_background = UI_panel_should_show_background(region, ps->panel->type);
1789 ps->panel->runtime->region_ofsx = region_offset_x;
1790 ps->new_offset_x = region_offset_x + (show_background ? UI_PANEL_MARGIN_X : 0);
1791 }
1792
1793 /* Y offset. */
1794 for (int i = 0, y = 0; i < active_panels_len; i++) {
1795 PanelSort *ps = &panel_sort[i];
1796 const bool show_background = UI_panel_should_show_background(region, ps->panel->type);
1797
1799
1800 /* Separate panel boxes a bit further (if they are drawn). */
1801 if (show_background) {
1803 }
1804 ps->new_offset_y = y;
1805 /* The header still draws offset by the size of closed panels, so apply the offset here. */
1806 if (UI_panel_is_closed(ps->panel)) {
1807 panel_sort[i].new_offset_y -= ps->panel->sizey;
1808 }
1809 }
1810
1811 /* Interpolate based on the input factor. */
1812 bool changed = false;
1813 for (int i = 0; i < active_panels_len; i++) {
1814 PanelSort *ps = &panel_sort[i];
1815 if (ps->panel->flag & PNL_SELECT) {
1816 continue;
1817 }
1818
1819 if (ps->new_offset_x != ps->panel->ofsx) {
1820 const float x = interpf(float(ps->new_offset_x), float(ps->panel->ofsx), factor);
1821 ps->panel->ofsx = round_fl_to_int(x);
1822 changed = true;
1823 }
1824 if (ps->new_offset_y != ps->panel->ofsy) {
1825 const float y = interpf(float(ps->new_offset_y), float(ps->panel->ofsy), factor);
1826 ps->panel->ofsy = round_fl_to_int(y);
1827 changed = true;
1828 }
1829 }
1830
1831 /* Set locations for tabbed and sub panels. */
1832 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
1833 if (panel->runtime_flag & PANEL_ACTIVE) {
1834 if (panel->children.first) {
1835 align_sub_panels(panel);
1836 }
1837 }
1838 }
1839
1840 MEM_freeN(panel_sort);
1841
1842 return changed;
1843}
1844
1845static void ui_panels_size(ARegion *region, int *r_x, int *r_y)
1846{
1847 int sizex = 0;
1848 int sizey = 0;
1849 bool has_panel_with_background = false;
1850
1851 /* Compute size taken up by panels, for setting in view2d. */
1852 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
1853 if (panel->runtime_flag & PANEL_ACTIVE) {
1854 const int pa_sizex = panel->ofsx + panel->sizex;
1855 const int pa_sizey = get_panel_real_ofsy(panel);
1856
1857 sizex = max_ii(sizex, pa_sizex);
1858 sizey = min_ii(sizey, pa_sizey);
1859 if (UI_panel_should_show_background(region, panel->type)) {
1860 has_panel_with_background = true;
1861 }
1862 }
1863 }
1864
1865 if (sizex == 0) {
1866 sizex = UI_PANEL_WIDTH;
1867 }
1868 if (sizey == 0) {
1869 sizey = -UI_PANEL_WIDTH;
1870 }
1871 /* Extra margin after the list so the view scrolls a few pixels further than the panel border.
1872 * Also makes the bottom match the top margin. */
1873 if (has_panel_with_background) {
1874 sizey -= UI_PANEL_MARGIN_Y;
1875 }
1876
1877 *r_x = sizex;
1878 *r_y = sizey;
1879}
1880
1881static void ui_do_animate(bContext *C, Panel *panel)
1882{
1883 uiHandlePanelData *data = static_cast<uiHandlePanelData *>(panel->activedata);
1884 ARegion *region = CTX_wm_region(C);
1885
1886 float fac = (BLI_time_now_seconds() - data->starttime) / ANIMATION_TIME;
1887 fac = min_ff(sqrtf(fac), 1.0f);
1888
1889 if (uiAlignPanelStep(region, fac, false)) {
1890 ED_region_tag_redraw(region);
1891 }
1892 else {
1893 if (UI_panel_is_dragging(panel)) {
1894 /* NOTE: doing this in #panel_activate_state would require
1895 * removing `const` for context in many other places. */
1896 reorder_instanced_panel_list(C, region, panel);
1897 }
1898
1900 }
1901}
1902
1904{
1905 LISTBASE_FOREACH (Panel *, panel, lb) {
1906 /* Flags to copy over to the next layout pass. */
1907 const short flag_copy = PANEL_USE_CLOSED_FROM_SEARCH | PANEL_IS_DRAG_DROP;
1908
1909 const bool was_active = panel->runtime_flag & PANEL_ACTIVE;
1910 const bool was_closed = UI_panel_is_closed(panel);
1911 panel->runtime_flag &= flag_copy;
1912 SET_FLAG_FROM_TEST(panel->runtime_flag, was_active, PANEL_WAS_ACTIVE);
1913 SET_FLAG_FROM_TEST(panel->runtime_flag, was_closed, PANEL_WAS_CLOSED);
1914
1915 panels_layout_begin_clear_flags(&panel->children);
1916 }
1917}
1918
1919void UI_panels_begin(const bContext * /*C*/, ARegion *region)
1920{
1921 /* Set all panels as inactive, so that at the end we know which ones were used. Also
1922 * clear other flags so we know later that their values were set for the current redraw. */
1924}
1925
1926void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y)
1927{
1928 ScrArea *area = CTX_wm_area(C);
1929
1931
1932 const bool region_search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE;
1933
1934 if (properties_space_needs_realign(area, region)) {
1935 region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active);
1936 }
1937 else if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) {
1938 region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active);
1939 }
1940
1941 if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) {
1942 /* Clean up the extra panels and buttons created for searching. */
1944 }
1945
1946 LISTBASE_FOREACH (Panel *, panel, &region->panels) {
1947 if (panel->runtime_flag & PANEL_ACTIVE) {
1948 BLI_assert(panel->runtime->block != nullptr);
1949 panel_calculate_size_recursive(region, panel);
1950 }
1951 }
1952
1953 /* Offset contents. */
1954 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
1955 if (block->active && block->panel) {
1956 ui_offset_panel_block(block);
1957
1958 /* Update bounds for all "views" in this block. Usually this is done in #UI_block_end(), but
1959 * that wouldn't work because of the offset applied above. */
1960 ui_block_views_end(region, block);
1961 }
1962 }
1963
1964 /* Re-align, possibly with animation. */
1965 Panel *panel;
1966 if (panels_need_realign(area, region, &panel)) {
1967 if (panel) {
1969 }
1970 else {
1971 uiAlignPanelStep(region, 1.0, false);
1972 }
1973 }
1974
1975 /* Compute size taken up by panels. */
1976 ui_panels_size(region, r_x, r_y);
1977}
1978
1980
1981/* -------------------------------------------------------------------- */
1984
1985#define DRAG_REGION_PAD (PNL_HEADER * 0.5)
1986static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel)
1987{
1988 uiHandlePanelData *data = static_cast<uiHandlePanelData *>(panel->activedata);
1989 ARegion *region = CTX_wm_region(C);
1990
1991 /* Keep the drag position in the region with a small pad to keep the panel visible. */
1992 const int y = clamp_i(event->xy[1], region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD);
1993
1994 float dy = float(y - data->starty);
1995
1996 /* Adjust for region zoom. */
1997 dy *= BLI_rctf_size_y(&region->v2d.cur) / float(BLI_rcti_size_y(&region->winrct));
1998
1999 /* Add the movement of the view due to edge scrolling while dragging. */
2000 dy += (region->v2d.cur.ymin - data->start_cur_ymin);
2001
2002 panel->ofsy = data->startofsy + round_fl_to_int(dy);
2003
2004 uiAlignPanelStep(region, 0.2f, true);
2005
2006 ED_region_tag_redraw(region);
2007}
2008#undef DRAG_REGION_PAD
2009
2011
2012/* -------------------------------------------------------------------- */
2015
2017{
2018 for (LayoutPanelHeader &header : panel.runtime->layout_panels.headers) {
2019 if (IN_RANGE(float(my - panel.runtime->block->rect.ymax), header.start_y, header.end_y)) {
2020 return &header;
2021 }
2022 }
2023 return nullptr;
2024}
2025
2027 const Panel *panel,
2028 const int mx,
2029 const int my)
2030{
2031 if (!IN_RANGE(float(mx), block->rect.xmin, block->rect.xmax)) {
2032 return PANEL_MOUSE_OUTSIDE;
2033 }
2034
2035 if (IN_RANGE(float(my), block->rect.ymax, block->rect.ymax + PNL_HEADER)) {
2037 }
2038 if (ui_layout_panel_header_under_mouse(*panel, my) != nullptr) {
2040 }
2041
2042 if (!UI_panel_is_closed(panel)) {
2043 if (IN_RANGE(float(my), block->rect.ymin, block->rect.ymax + PNL_HEADER)) {
2045 }
2046 }
2047
2048 return PANEL_MOUSE_OUTSIDE;
2049}
2050
2055
2056static void ui_panel_drag_collapse_handler_remove(bContext * /*C*/, void *userdata)
2057{
2058 uiPanelDragCollapseHandle *dragcol_data = static_cast<uiPanelDragCollapseHandle *>(userdata);
2059 MEM_freeN(dragcol_data);
2060}
2061
2063 const uiPanelDragCollapseHandle *dragcol_data,
2064 const int xy_dst[2])
2065{
2066 ARegion *region = CTX_wm_region_popup(C);
2067 if (!region) {
2068 region = CTX_wm_region(C);
2069 }
2070 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
2071 float xy_a_block[2] = {float(dragcol_data->xy_init[0]), float(dragcol_data->xy_init[1])};
2072 float xy_b_block[2] = {float(xy_dst[0]), float(xy_dst[1])};
2073 Panel *panel = block->panel;
2074
2075 if (panel == nullptr) {
2076 continue;
2077 }
2078
2079 /* Lock axis. */
2080 xy_b_block[0] = dragcol_data->xy_init[0];
2081
2082 /* Use cursor coords in block space. */
2083 ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]);
2084 ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]);
2085
2086 for (LayoutPanelHeader &header : panel->runtime->layout_panels.headers) {
2087 rctf rect = block->rect;
2088 rect.ymin = block->rect.ymax + header.start_y;
2089 rect.ymax = block->rect.ymax + header.end_y;
2090
2091 if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) {
2093 &header.open_owner_ptr, header.open_prop_name.c_str(), !dragcol_data->was_first_open);
2095 const_cast<bContext *>(C),
2096 &header.open_owner_ptr,
2097 RNA_struct_find_property(&header.open_owner_ptr, header.open_prop_name.c_str()));
2098 ED_region_tag_redraw(region);
2100 }
2101 }
2102
2103 if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) {
2104 continue;
2105 }
2106 const int oldflag = panel->flag;
2107
2108 /* Set up `rect` to match header size. */
2109 rctf rect = block->rect;
2110 rect.ymin = rect.ymax;
2111 rect.ymax = rect.ymin + PNL_HEADER;
2112
2113 /* Touch all panels between last mouse coordinate and the current one. */
2114 if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) {
2115 /* Force panel to open or close. */
2117 SET_FLAG_FROM_TEST(panel->flag, dragcol_data->was_first_open, PNL_CLOSED);
2118
2119 /* If panel->flag has changed this means a panel was opened/closed here. */
2120 if (panel->flag != oldflag) {
2122 }
2123 }
2124 }
2125 /* Update the instanced panel data expand flags with the changes made here. */
2127}
2128
2135static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata)
2136{
2137 wmWindow *win = CTX_wm_window(C);
2138 uiPanelDragCollapseHandle *dragcol_data = static_cast<uiPanelDragCollapseHandle *>(userdata);
2139 short retval = WM_UI_HANDLER_CONTINUE;
2140
2141 switch (event->type) {
2142 case MOUSEMOVE:
2143 ui_panel_drag_collapse(C, dragcol_data, event->xy);
2144
2145 retval = WM_UI_HANDLER_BREAK;
2146 break;
2147 case LEFTMOUSE:
2148 if (event->val == KM_RELEASE) {
2149 /* Done! */
2153 dragcol_data,
2154 true);
2156 }
2157 /* Don't let any left-mouse event fall through! */
2158 retval = WM_UI_HANDLER_BREAK;
2159 break;
2160 default: {
2161 break;
2162 }
2163 }
2164
2165 return retval;
2166}
2167
2168void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open)
2169{
2170 wmWindow *win = CTX_wm_window(C);
2171 const wmEvent *event = win->eventstate;
2173
2174 dragcol_data->was_first_open = was_open;
2175 copy_v2_v2_int(dragcol_data->xy_init, event->xy);
2176
2178 &win->modalhandlers,
2181 dragcol_data,
2183}
2184
2186{
2187 const bool is_open = RNA_boolean_get(&header->open_owner_ptr, header->open_prop_name.c_str());
2188 RNA_boolean_set(&header->open_owner_ptr, header->open_prop_name.c_str(), !is_open);
2190 const_cast<bContext *>(C),
2191 &header->open_owner_ptr,
2192 RNA_struct_find_property(&header->open_owner_ptr, header->open_prop_name.c_str()));
2193 return !is_open;
2194}
2195
2197 bContext *C, const uiBlock *block, const int /*mx*/, const int my, const int event_type)
2198{
2199 Panel *panel = block->panel;
2200 BLI_assert(panel->type != nullptr);
2201
2203 if (header == nullptr) {
2204 return;
2205 }
2206 const bool new_state = ui_layout_panel_toggle_open(C, header);
2209
2210 if (event_type == LEFTMOUSE) {
2212 }
2213}
2214
2222 const uiBlock *block,
2223 const int mx,
2224 const int event_type,
2225 const bool ctrl,
2226 const bool shift)
2227{
2228 Panel *panel = block->panel;
2229 ARegion *region = CTX_wm_region(C);
2230
2231 BLI_assert(panel->type != nullptr);
2233
2234 const bool is_subpanel = (panel->type->parent != nullptr);
2235 const bool use_pin = UI_panel_category_is_visible(region) && UI_panel_can_be_pinned(panel);
2236 const bool show_pin = use_pin && (panel->flag & PNL_PIN);
2237 const bool show_drag = !is_subpanel;
2238
2239 /* Handle panel pinning. */
2240 if (use_pin && ELEM(event_type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE) && shift) {
2241 panel->flag ^= PNL_PIN;
2242 ED_region_tag_redraw(region);
2243 return;
2244 }
2245
2246 float expansion_area_xmax = block->rect.xmax;
2247 if (show_drag) {
2248 expansion_area_xmax -= (PNL_ICON * 1.5f);
2249 }
2250 if (show_pin) {
2251 expansion_area_xmax -= PNL_ICON;
2252 }
2253
2254 /* Collapse and expand panels. */
2255 if (ELEM(event_type, EVT_RETKEY, EVT_PADENTER, EVT_AKEY) || mx < expansion_area_xmax) {
2256 if (ctrl && !is_subpanel) {
2257 /* For parent panels, collapse all other panels or toggle children. */
2258 if (UI_panel_is_closed(panel) || BLI_listbase_is_empty(&panel->children)) {
2259 panels_collapse_all(region, panel);
2260
2261 /* Reset the view - we don't want to display a view without content. */
2262 UI_view2d_offset(&region->v2d, 0.0f, 1.0f);
2263 }
2264 else {
2265 /* If a panel has sub-panels and it's open, toggle the expansion
2266 * of the sub-panels (based on the expansion of the first sub-panel). */
2267 Panel *first_child = static_cast<Panel *>(panel->children.first);
2268 BLI_assert(first_child != nullptr);
2270 panel->flag |= PNL_CLOSED;
2271 }
2272 }
2273
2275
2276 if (event_type == LEFTMOUSE) {
2278 }
2279
2280 /* Set panel custom data (modifier) active when expanding sub-panels, but not top-level
2281 * panels to allow collapsing and expanding without setting the active element. */
2282 if (is_subpanel) {
2284 }
2285
2288 return;
2289 }
2290
2291 /* Handle panel dragging. For now don't allow dragging in floating regions. */
2292 if (show_drag && !(region->alignment == RGN_ALIGN_FLOAT)) {
2293 const float drag_area_xmin = block->rect.xmax - (PNL_ICON * 1.5f);
2294 const float drag_area_xmax = block->rect.xmax;
2295 if (IN_RANGE(mx, drag_area_xmin, drag_area_xmax)) {
2296 if (panel_custom_pin_to_last_get(panel)) {
2297 panel_custom_pin_to_last_set(C, panel, false);
2298 return;
2299 }
2301 return;
2302 }
2303 }
2304
2305 /* Handle panel unpinning. */
2306 if (show_pin) {
2307 const float pin_area_xmin = expansion_area_xmax;
2308 const float pin_area_xmax = pin_area_xmin + PNL_ICON;
2309 if (IN_RANGE(mx, pin_area_xmin, pin_area_xmax)) {
2310 panel->flag ^= PNL_PIN;
2311 ED_region_tag_redraw(region);
2312 return;
2313 }
2314 }
2315}
2316
2318{
2319 /* Check for more than one category. */
2320 return region->runtime->panels_category.first &&
2321 region->runtime->panels_category.first != region->runtime->panels_category.last;
2322}
2323
2324PanelCategoryDyn *UI_panel_category_find(const ARegion *region, const char *idname)
2325{
2326 return static_cast<PanelCategoryDyn *>(BLI_findstring(
2327 &region->runtime->panels_category, idname, offsetof(PanelCategoryDyn, idname)));
2328}
2329
2330int UI_panel_category_index_find(ARegion *region, const char *idname)
2331{
2332 return BLI_findstringindex(
2333 &region->runtime->panels_category, idname, offsetof(PanelCategoryDyn, idname));
2334}
2335
2337{
2338 return static_cast<PanelCategoryStack *>(BLI_findstring(
2339 &region->panels_category_active, idname, offsetof(PanelCategoryStack, idname)));
2340}
2341
2342static void ui_panel_category_active_set(ARegion *region, const char *idname, bool fallback)
2343{
2344 ListBase *lb = &region->panels_category_active;
2345 PanelCategoryStack *pc_act = UI_panel_category_active_find(region, idname);
2346
2347 if (pc_act) {
2348 BLI_remlink(lb, pc_act);
2349 }
2350 else {
2351 pc_act = MEM_callocN<PanelCategoryStack>(__func__);
2352 STRNCPY(pc_act->idname, idname);
2353 }
2354
2355 if (fallback) {
2356 /* For fall-backs, add at the end so explicitly chosen categories have priority. */
2357 BLI_addtail(lb, pc_act);
2358 }
2359 else {
2360 BLI_addhead(lb, pc_act);
2361 }
2362
2363 /* Validate all active panels. We could do this on load, they are harmless -
2364 * but we should remove them somewhere.
2365 * (Add-ons could define panels and gather cruft over time). */
2366 {
2367 PanelCategoryStack *pc_act_next;
2368 /* intentionally skip first */
2369 pc_act_next = pc_act->next;
2370 while ((pc_act = pc_act_next)) {
2371 pc_act_next = pc_act->next;
2372 if (!BLI_findstring(
2373 &region->runtime->type->paneltypes, pc_act->idname, offsetof(PanelType, category)))
2374 {
2375 BLI_remlink(lb, pc_act);
2376 MEM_freeN(pc_act);
2377 }
2378 }
2379 }
2380}
2381
2382void UI_panel_category_active_set(ARegion *region, const char *idname)
2383{
2384 ui_panel_category_active_set(region, idname, false);
2385}
2386
2387void UI_panel_category_index_active_set(ARegion *region, const int index)
2388{
2389 PanelCategoryDyn *pc_dyn = static_cast<PanelCategoryDyn *>(
2390 BLI_findlink(&region->runtime->panels_category, index));
2391 if (!pc_dyn) {
2392 return;
2393 }
2394
2395 ui_panel_category_active_set(region, pc_dyn->idname, false);
2396}
2397
2398void UI_panel_category_active_set_default(ARegion *region, const char *idname)
2399{
2400 if (!UI_panel_category_active_find(region, idname)) {
2401 ui_panel_category_active_set(region, idname, true);
2402 }
2403}
2404
2405const char *UI_panel_category_active_get(ARegion *region, bool set_fallback)
2406{
2408 if (UI_panel_category_find(region, pc_act->idname)) {
2409 return pc_act->idname;
2410 }
2411 }
2412
2413 if (set_fallback) {
2414 PanelCategoryDyn *pc_dyn = static_cast<PanelCategoryDyn *>(
2415 region->runtime->panels_category.first);
2416 if (pc_dyn) {
2417 ui_panel_category_active_set(region, pc_dyn->idname, true);
2418 return pc_dyn->idname;
2419 }
2420 }
2421
2422 return nullptr;
2423}
2424
2426{
2427 LISTBASE_FOREACH (PanelCategoryDyn *, ptd, &region->runtime->panels_category) {
2428 if (BLI_rcti_isect_pt(&ptd->rect, event->mval[0], event->mval[1])) {
2429 return ptd;
2430 }
2431 }
2432
2433 return nullptr;
2434}
2435
2436void UI_panel_category_add(ARegion *region, const char *name)
2437{
2439 BLI_addtail(&region->runtime->panels_category, pc_dyn);
2440
2441 STRNCPY(pc_dyn->idname, name);
2442
2443 /* 'pc_dyn->rect' must be set on draw. */
2444}
2445
2447{
2448 BLI_freelistN(&region->runtime->panels_category);
2449}
2450
2452 ARegion *region,
2453 const uiBut *active_but)
2454{
2455 const bool is_mousewheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE);
2456 const bool inside_tabregion =
2458 (event->mval[0] <
2459 ((PanelCategoryDyn *)region->runtime->panels_category.first)->rect.xmax) :
2460 (event->mval[0] >
2461 ((PanelCategoryDyn *)region->runtime->panels_category.first)->rect.xmin));
2462
2463 /* If mouse is inside non-tab region, ctrl key is required. */
2464 if (is_mousewheel && (event->modifier & KM_CTRL) == 0 && !inside_tabregion) {
2466 }
2467
2468 if (active_but && ui_but_supports_cycling(active_but)) {
2469 /* Skip - exception to make cycling buttons using ctrl+mousewheel work in tabbed regions. */
2470 }
2471 else {
2472 const char *category = UI_panel_category_active_get(region, false);
2473 if (LIKELY(category)) {
2474 PanelCategoryDyn *pc_dyn = UI_panel_category_find(region, category);
2475 /* Cyclic behavior between categories
2476 * using Ctrl+Tab (+Shift for backwards) or Ctrl+Wheel Up/Down. */
2477 if (LIKELY(pc_dyn) && (event->modifier & KM_CTRL)) {
2478 if (is_mousewheel) {
2479 /* We can probably get rid of this and only allow Ctrl-Tabbing. */
2480 pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev;
2481 }
2482 else {
2483 const bool backwards = event->modifier & KM_SHIFT;
2484 pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next;
2485 if (!pc_dyn) {
2486 /* Proper cyclic behavior, back to first/last category (only used for ctrl+tab). */
2487 pc_dyn = backwards ?
2488 static_cast<PanelCategoryDyn *>(region->runtime->panels_category.last) :
2489 static_cast<PanelCategoryDyn *>(region->runtime->panels_category.first);
2490 }
2491 }
2492
2493 if (pc_dyn) {
2494 /* Intentionally don't reset scroll in this case,
2495 * allowing for quick browsing between tabs. */
2496 UI_panel_category_active_set(region, pc_dyn->idname);
2497 ED_region_tag_redraw(region);
2498 }
2499 return WM_UI_HANDLER_BREAK;
2500 }
2501 }
2502 }
2503
2505}
2506
2508 const wmEvent *event,
2509 ARegion *region,
2510 const uiBut *active_but)
2511{
2512 /* Mouse-move events are handled by separate handlers for dragging and drag collapsing. */
2513 if (ISMOUSE_MOTION(event->type)) {
2515 }
2516
2517 /* We only use KM_PRESS events in this function, so it's simpler to return early. */
2518 if (event->val != KM_PRESS) {
2520 }
2521
2522 /* Scroll-bars can overlap panels now, they have handling priority. */
2523 if (UI_view2d_mouse_in_scrollers(region, &region->v2d, event->xy)) {
2525 }
2526
2527 int retval = WM_UI_HANDLER_CONTINUE;
2528
2529 /* Handle category tabs. */
2530 if (UI_panel_category_is_visible(region)) {
2531 if (event->type == LEFTMOUSE) {
2532 PanelCategoryDyn *pc_dyn = panel_categories_find_mouse_over(region, event);
2533 if (pc_dyn) {
2534 UI_panel_category_active_set(region, pc_dyn->idname);
2535 ED_region_tag_redraw(region);
2536
2537 /* Reset scroll to the top (#38348). */
2538 UI_view2d_offset(&region->v2d, -1.0f, 1.0f);
2539
2540 retval = WM_UI_HANDLER_BREAK;
2541 }
2542 }
2543 else if (((event->type == EVT_TABKEY) && (event->modifier & KM_CTRL)) ||
2545 {
2546 /* Cycle tabs. */
2547 retval = ui_handle_panel_category_cycling(event, region, active_but);
2548 }
2549 if (event->type == EVT_PADPERIOD) {
2550 retval = ui_panel_category_show_active_tab(region, event->xy);
2551 }
2552 }
2553
2554 if (retval == WM_UI_HANDLER_BREAK) {
2555 return retval;
2556 }
2557
2558 const uiBut *region_active_but = ui_region_find_active_but(region);
2559 const bool region_has_active_button = region_active_but &&
2560 region_active_but->type != UI_BTYPE_LABEL;
2561
2562 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
2563 Panel *panel = block->panel;
2564 if (panel == nullptr || panel->type == nullptr) {
2565 continue;
2566 }
2567 /* We can't expand or collapse panels without headers, they would disappear. Layout panels can
2568 * be expanded and collapsed though. */
2569 const bool has_panel_header = !(panel->type->flag & PANEL_TYPE_NO_HEADER);
2570
2571 int mx = event->xy[0];
2572 int my = event->xy[1];
2573 ui_window_to_block(region, block, &mx, &my);
2574
2575 const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my);
2576
2577 if (has_panel_header && mouse_state != PANEL_MOUSE_OUTSIDE) {
2578 /* Mark panels that have been interacted with so their expansion
2579 * doesn't reset when property search finishes. */
2582
2583 /* The panel collapse / expand key "A" is special as it takes priority over
2584 * active button handling. */
2585 if ((event->type == EVT_AKEY) && (event->modifier == 0)) {
2586 retval = WM_UI_HANDLER_BREAK;
2588 C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT);
2589 break;
2590 }
2591 }
2592
2593 /* Don't do any other panel handling with an active button. */
2594 if (region_has_active_button) {
2595 continue;
2596 }
2597
2598 if (has_panel_header && mouse_state == PANEL_MOUSE_INSIDE_HEADER) {
2599 /* All mouse clicks inside panel headers should return in break. */
2600 if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) {
2601 retval = WM_UI_HANDLER_BREAK;
2603 C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT);
2604 }
2605 else if (event->type == RIGHTMOUSE) {
2606 retval = WM_UI_HANDLER_BREAK;
2607 ui_popup_context_menu_for_panel(C, region, block->panel);
2608 }
2609 break;
2610 }
2611 if (mouse_state == PANEL_MOUSE_INSIDE_LAYOUT_PANEL_HEADER) {
2612 if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) {
2613 retval = WM_UI_HANDLER_BREAK;
2614 ui_handle_layout_panel_header(C, block, mx, my, event->type);
2615 }
2616 }
2617 }
2618
2619 return retval;
2620}
2621
2622static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data)
2623{
2624 panel->runtime->custom_data_ptr = custom_data;
2625
2626 LISTBASE_FOREACH (Panel *, child_panel, &panel->children) {
2627 ui_panel_custom_data_set_recursive(child_panel, custom_data);
2628 }
2629}
2630
2631void UI_panel_context_pointer_set(Panel *panel, const char *name, PointerRNA *ptr)
2632{
2633 uiLayoutSetContextPointer(panel->layout, name, ptr);
2635}
2636
2638{
2639 BLI_assert(panel->type != nullptr);
2640
2641 /* Free the old custom data, which should be shared among all of the panel's sub-panels. */
2642 if (panel->runtime->custom_data_ptr != nullptr) {
2643 MEM_delete(panel->runtime->custom_data_ptr);
2644 }
2645
2646 ui_panel_custom_data_set_recursive(panel, custom_data);
2647}
2648
2650{
2651 return panel->runtime->custom_data_ptr;
2652}
2653
2655{
2656 ARegion *region = CTX_wm_region(C);
2657 if (region) {
2658 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
2659 Panel *panel = block->panel;
2660 if (panel == nullptr) {
2661 continue;
2662 }
2663
2664 int mx = event->xy[0];
2665 int my = event->xy[1];
2666 ui_window_to_block(region, block, &mx, &my);
2667 const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my);
2669 return UI_panel_custom_data_get(panel);
2670 }
2671 }
2672 }
2673
2674 return nullptr;
2675}
2676
2678{
2679 return (panel->type->parent == nullptr) && !(panel->type->flag & PANEL_TYPE_INSTANCED);
2680}
2681
2683
2684/* -------------------------------------------------------------------- */
2687
2688/* NOTE: this is modal handler and should not swallow events for animation. */
2689static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata)
2690{
2691 Panel *panel = static_cast<Panel *>(userdata);
2692 uiHandlePanelData *data = static_cast<uiHandlePanelData *>(panel->activedata);
2693
2694 /* Verify if we can stop. */
2695 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
2697 }
2698 else if (event->type == MOUSEMOVE) {
2699 if (data->state == PANEL_STATE_DRAG) {
2700 ui_do_drag(C, event, panel);
2701 }
2702 }
2703 else if (event->type == TIMER && event->customdata == data->animtimer) {
2704 if (data->state == PANEL_STATE_ANIMATION) {
2705 ui_do_animate(C, panel);
2706 }
2707 else if (data->state == PANEL_STATE_DRAG) {
2708 ui_do_drag(C, event, panel);
2709 }
2710 }
2711
2712 data = static_cast<uiHandlePanelData *>(panel->activedata);
2713
2714 if (data && data->state == PANEL_STATE_ANIMATION) {
2716 }
2717 return WM_UI_HANDLER_BREAK;
2718}
2719
2720static void ui_handler_remove_panel(bContext *C, void *userdata)
2721{
2722 Panel *panel = static_cast<Panel *>(userdata);
2723
2725}
2726
2728 wmWindow *win,
2729 const ARegion *region,
2730 Panel *panel,
2732{
2734
2735 if (panel->activedata == nullptr) {
2736 panel->activedata = MEM_callocN(sizeof(uiHandlePanelData), __func__);
2738 &win->modalhandlers,
2741 panel,
2743 }
2744
2745 uiHandlePanelData *data = static_cast<uiHandlePanelData *>(panel->activedata);
2746
2747 /* Only create a new timer if necessary. Reuse can occur when PANEL_STATE_ANIMATION follows
2748 * PANEL_STATE_DRAG for example (i.e. panel->activedata was present already). */
2749 if (!data->animtimer) {
2751 }
2752
2753 data->state = state;
2754 data->startx = win->eventstate->xy[0];
2755 data->starty = win->eventstate->xy[1];
2756 data->startofsx = panel->ofsx;
2757 data->startofsy = panel->ofsy;
2758 data->start_cur_xmin = region->v2d.cur.xmin;
2759 data->start_cur_ymin = region->v2d.cur.ymin;
2760 data->starttime = BLI_time_now_seconds();
2761}
2762
2769{
2770 uiHandlePanelData *data = static_cast<uiHandlePanelData *>(panel->activedata);
2771 wmWindow *win = CTX_wm_window(C);
2772 ARegion *region = CTX_wm_region(C);
2773
2774 if (data != nullptr && data->state == state) {
2775 return;
2776 }
2777
2778 if (state == PANEL_STATE_DRAG) {
2780
2783
2784 panel_handle_data_ensure(C, win, region, panel, state);
2785
2786 /* Initiate edge panning during drags for scrolling beyond the initial region view. */
2787 wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true);
2789 }
2790 else if (state == PANEL_STATE_ANIMATION) {
2791 panel_set_flag_recursive(panel, PNL_SELECT, false);
2792
2793 panel_handle_data_ensure(C, win, region, panel, state);
2794 }
2795 else if (state == PANEL_STATE_EXIT) {
2797
2798 BLI_assert(data != nullptr);
2799
2800 if (data->animtimer) {
2801 WM_event_timer_remove(CTX_wm_manager(C), win, data->animtimer);
2802 data->animtimer = nullptr;
2803 }
2804
2805 MEM_freeN(data);
2806 panel->activedata = nullptr;
2807
2810 }
2811
2812 ED_region_tag_redraw(region);
2813}
2814
2816{
2817 if (panel->activedata) {
2819 }
2820}
2821
ARegion * CTX_wm_region_popup(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
void CTX_store_set(bContext *C, const bContextStore *store)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
void BKE_panel_free(Panel *panel)
Definition screen.cc:557
@ PANEL_TYPE_NO_HEADER
@ PANEL_TYPE_INSTANCED
@ PANEL_TYPE_DEFAULT_CLOSED
Panel * BKE_panel_new(PanelType *panel_type)
Definition screen.cc:540
void BLF_size(int fontid, float size)
Definition blf.cc:440
void BLF_color3ubv(int fontid, const unsigned char rgb[3])
Definition blf.cc:473
@ BLF_ROTATION
Definition BLF_api.hh:433
#define BLF_DRAW_STR_DUMMY_MAX
Definition BLF_api.hh:468
void BLF_disable(int fontid, int option)
Definition blf.cc:329
void BLF_rotation(int fontid, float angle)
Definition blf.cc:897
void BLF_draw(int fontid, const char *str, size_t str_len, ResultBLF *r_info=nullptr) ATTR_NONNULL(2)
Definition blf.cc:582
void BLF_enable(int fontid, int option)
Definition blf.cc:320
float BLF_width(int fontid, const char *str, size_t str_len, ResultBLF *r_info=nullptr) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(2)
Definition blf.cc:805
void BLF_position(int fontid, float x, float y, float z)
Definition blf.cc:385
#define BLI_assert(a)
Definition BLI_assert.h:46
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
#define LISTBASE_FOREACH(type, var, list)
void * BLI_findstring(const ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:608
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
#define LISTBASE_FOREACH_BACKWARD(type, var, list)
void void BLI_freelistN(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:497
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void BLI_insertlinkafter(ListBase *listbase, void *vprevlink, void *vnewlink) ATTR_NONNULL(1)
Definition listbase.cc:332
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
int BLI_findstringindex(const ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:780
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
MINLINE int round_fl_to_int(float a)
MINLINE int min_ii(int a, int b)
MINLINE float min_ff(float a, float b)
MINLINE int max_ii(int a, int b)
MINLINE float interpf(float target, float origin, float t)
MINLINE int clamp_i(int value, int min, int max)
#define M_PI_2
MINLINE void copy_v4_v4_uchar(unsigned char r[4], const unsigned char a[4])
MINLINE void copy_v2_v2_int(int r[2], const int a[2])
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:198
bool BLI_rcti_isect_pt(const struct rcti *rect, int x, int y)
bool BLI_rctf_isect_segment(const struct rctf *rect, const float s1[2], const float s2[2])
void BLI_rctf_rcti_copy(struct rctf *dst, const struct rcti *src)
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
char * BLI_strdupn(const char *str, size_t len) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition string.cc:30
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
size_t BLI_snprintf(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format,...) ATTR_NONNULL(1
unsigned char uchar
unsigned int uint
Platform independent time functions.
double BLI_time_now_seconds(void)
Definition time.cc:65
#define STREQLEN(a, b, n)
#define IN_RANGE(a, b, c)
#define SET_FLAG_FROM_TEST(value, test, flag)
#define ELEM(...)
#define STREQ(a, b)
#define LIKELY(x)
#define CTX_IFACE_(context, msgid)
#define IFACE_(msgid)
#define RGN_ALIGN_ENUM_FROM_MASK(align)
@ PNL_SELECT
@ PNL_PIN
@ PNL_CLOSED
@ PNL_INSTANCED_LIST_ORDER_CHANGED
@ RGN_TYPE_WINDOW
@ RGN_TYPE_TOOLS
#define RGN_TYPE_HAS_CATEGORY_MASK
@ RGN_FLAG_SEARCH_FILTER_UPDATE
@ RGN_FLAG_SEARCH_FILTER_ACTIVE
@ RGN_ALIGN_RIGHT
@ RGN_ALIGN_FLOAT
@ SPACE_PROPERTIES
#define UI_SCALE_FAC
#define UI_INV_SCALE_FAC
void(*)(void *data_link, char *r_idname) uiListPanelIDFromDataFunc
void ED_region_tag_refresh_ui(ARegion *region)
Definition area.cc:668
bool ED_region_panel_category_gutter_isect_xy(const ARegion *region, const int event_xy[2])
Definition area_query.cc:88
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:639
void immUniformColor4ubv(const unsigned char rgba[4])
void immUnbindProgram()
void immBindBuiltinProgram(eGPUBuiltinShader shader_id)
void immUniformColor3ubv(const unsigned char rgb[3])
GPUVertFormat * immVertexFormat()
void immUniformColor4fv(const float rgba[4])
void immUniformColor3fvAlpha(const float rgb[3], float a)
void immRectf(uint pos, float x1, float y1, float x2, float y2)
@ GPU_SHADER_3D_UNIFORM_COLOR
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(eGPUBlend blend)
Definition gpu_state.cc:42
void GPU_line_smooth(bool enable)
Definition gpu_state.cc:78
@ GPU_FETCH_FLOAT
uint GPU_vertformat_attr_add(GPUVertFormat *, blender::StringRef name, GPUVertCompType, uint comp_len, GPUVertFetchMode)
@ GPU_COMP_F32
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
bool UI_panel_is_closed(const Panel *panel)
#define UI_UNIT_Y
void UI_draw_roundbox_4fv(const rctf *rect, bool filled, float rad, const float col[4])
void UI_block_theme_style_set(uiBlock *block, char theme_style)
#define INSTANCED_PANEL_UNIQUE_STR_SIZE
#define UI_PANEL_WIDTH
const uiStyle * UI_style_get_dpi()
void UI_draw_roundbox_corner_set(int type)
#define UI_PANEL_MARGIN_X
@ UI_CNR_BOTTOM_LEFT
@ UI_CNR_BOTTOM_RIGHT
@ UI_CNR_ALL
@ UI_CNR_TOP_LEFT
@ UI_CNR_TOP_RIGHT
@ UI_CNR_NONE
@ UI_BLOCK_THEME_STYLE_POPUP
@ UI_STYLE_TEXT_LEFT
const uiStyle * UI_style_get()
bool UI_block_is_search_only(const uiBlock *block)
void UI_fontstyle_set(const uiFontStyle *fs)
void UI_block_draw(const bContext *C, uiBlock *block)
#define UI_PANEL_CATEGORY_MARGIN_WIDTH
void UI_fontstyle_draw(const uiFontStyle *fs, const rcti *rect, const char *str, size_t str_len, const uchar col[4], const uiFontStyleDraw_Params *fs_params)
#define UI_PANEL_MARGIN_Y
bool UI_panel_is_dragging(const Panel *panel)
#define UI_UNIT_X
@ UI_BTYPE_LABEL
void UI_block_set_search_only(uiBlock *block, bool search_only)
const char * UI_panel_category_active_get(ARegion *region, bool set_fallback)
#define UI_NO_ICON_OVERLAY_TEXT
void UI_icon_draw_ex(float x, float y, int icon_id, float aspect, float alpha, float desaturate, const uchar mono_color[4], bool mono_border, const IconTextOverlay *text_overlay, const bool inverted=false)
void uiLayoutSetContextPointer(uiLayout *layout, blender::StringRef name, PointerRNA *ptr)
bContextStore * uiLayoutGetContextStore(uiLayout *layout)
@ TH_SELECT_ACTIVE
@ TH_TAB_OUTLINE
@ TH_PANEL_HEADER
@ TH_TAB_ACTIVE
@ TH_BACK
@ TH_PANEL_SUB_BACK
@ TH_TITLE
@ TH_TAB_BACK
@ TH_TAB_INACTIVE
@ TH_MATCH
@ TH_PANEL_BACK
@ TH_TEXT
@ TH_TEXT_HI
void UI_GetThemeColor3ubv(int colorid, unsigned char col[3])
void UI_GetThemeColor4fv(int colorid, float col[4])
bTheme * UI_GetTheme()
void UI_GetThemeColor4ubv(int colorid, unsigned char col[4])
char char UI_view2d_mouse_in_scrollers(const ARegion *region, const View2D *v2d, const int xy[2]) ATTR_NONNULL(1
void UI_view2d_offset(View2D *v2d, float xfac, float yfac)
Definition view2d.cc:1956
eWM_EventHandlerFlag
Definition WM_api.hh:541
#define WM_UI_HANDLER_CONTINUE
Definition WM_types.hh:345
@ KM_CTRL
Definition WM_types.hh:276
@ KM_SHIFT
Definition WM_types.hh:275
@ KM_PRESS
Definition WM_types.hh:308
@ KM_RELEASE
Definition WM_types.hh:309
@ WM_OP_INVOKE_DEFAULT
Definition WM_types.hh:238
#define WM_UI_HANDLER_BREAK
Definition WM_types.hh:346
#define U
BMesh const char void * data
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
int64_t size() const
const T & last(const int64_t n=0) const
bool is_empty() const
constexpr int64_t size() const
constexpr const char * data() const
static float is_left(const float p0[2], const float p1[2], const float p2[2])
#define offsetof(t, d)
#define sqrtf(x)
uint pos
#define floor
#define printf(...)
#define MEM_SAFE_FREE(v)
#define MAX_NAME
void ui_window_to_block(const ARegion *region, const uiBlock *block, int *x, int *y)
Definition interface.cc:223
void ui_block_bounds_calc(uiBlock *block)
Definition interface.cc:478
rcti ui_to_pixelrect(const ARegion *region, const uiBlock *block, const rctf *src_rect)
void ui_window_to_block_fl(const ARegion *region, const uiBlock *block, float *x, float *y)
Definition interface.cc:186
void ui_fontscale(float *points, float aspect)
bool ui_but_supports_cycling(const uiBut *but)
void ui_block_new_button_group(uiBlock *block, uiButtonGroupFlag flag)
void ui_popup_context_menu_for_panel(bContext *C, ARegion *region, Panel *panel)
static int roundboxtype
void ui_handle_afterfunc_add_operator(wmOperatorType *ot, wmOperatorCallContext opcontext)
void ui_block_views_end(ARegion *region, const uiBlock *block)
uiButtonGroupFlag
@ UI_BUTTON_GROUP_LOCK
@ UI_BUTTON_GROUP_PANEL_HEADER
bool ui_block_is_popup_any(const uiBlock *block) ATTR_WARN_UNUSED_RESULT
#define PNL_HEADER
uiBut * ui_region_find_active_but(ARegion *region) ATTR_WARN_UNUSED_RESULT
@ UI_HIDDEN
void UI_panel_stop_animation(const bContext *C, Panel *panel)
bool UI_panel_is_closed(const Panel *panel)
static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state)
static int get_panel_real_ofsy(Panel *panel)
static void panel_title_color_get(const Panel *panel, const bool show_background, const bool region_search_filter_active, uchar r_color[4])
static void panel_set_expansion_from_search_filter_recursive(const bContext *C, Panel *panel, const bool use_search_closed)
static void panel_set_runtime_flag_recursive(Panel *panel, short flag, bool value)
static void region_panels_set_expansion_from_search_filter(const bContext *C, ARegion *region, const bool use_search_closed)
void UI_panel_category_draw_all(ARegion *region, const char *category_id_active)
static int ui_panel_category_show_active_tab(ARegion *region, const int mval[2])
static void ui_panels_size(ARegion *region, int *r_x, int *r_y)
PanelCategoryDyn * UI_panel_category_find(const ARegion *region, const char *idname)
bool UI_panel_can_be_pinned(const Panel *panel)
static void ui_panel_drag_collapse_handler_remove(bContext *, void *userdata)
static void ui_do_animate(bContext *C, Panel *panel)
static int get_panel_real_size_y(const Panel *panel)
static void set_panels_list_data_expand_flag(const bContext *C, const ARegion *region)
void UI_panel_drawname_set(Panel *panel, blender::StringRef name)
Panel * UI_panel_find_by_type(ListBase *lb, const PanelType *pt)
uiPanelRuntimeFlag
@ PANEL_NEW_ADDED
@ PANEL_WAS_CLOSED
@ PANEL_ANIM_ALIGN
@ PANEL_ACTIVE_BORDER
@ PANEL_WAS_ACTIVE
@ PANEL_SEARCH_FILTER_MATCH
@ PANEL_USE_CLOSED_FROM_SEARCH
@ PANEL_ACTIVE
@ PANEL_LAST_ADDED
@ PANEL_IS_DRAG_DROP
static void region_panels_set_expansion_from_list_data(const bContext *C, ARegion *region)
static void ui_panel_drag_collapse(const bContext *C, const uiPanelDragCollapseHandle *dragcol_data, const int xy_dst[2])
bool UI_panel_matches_search_filter(const Panel *panel)
static void panel_set_flag_recursive(Panel *panel, short flag, bool value)
static void region_panels_remove_invisible_layouts(ARegion *region)
void UI_panel_header_buttons_begin(Panel *panel)
void UI_panels_free_instanced(const bContext *C, ARegion *region)
static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata)
static void panel_delete(ARegion *region, ListBase *panels, Panel *panel)
void ui_draw_layout_panels_backdrop(const ARegion *region, const Panel *panel, const float radius, float subpanel_backcolor[4])
bool UI_panel_category_is_visible(const ARegion *region)
static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data)
#define ANIMATION_TIME
bool ui_layout_panel_toggle_open(const bContext *C, LayoutPanelHeader *header)
static void get_panel_expand_flag(const Panel *panel, short *flag, short *flag_index)
static bool panel_type_context_poll(ARegion *region, const PanelType *panel_type, const char *context)
Panel * UI_panel_begin(ARegion *region, ListBase *lb, uiBlock *block, PanelType *pt, Panel *panel, bool *r_open)
static bool panel_custom_data_active_get(const Panel *panel)
static void ui_panel_exit_state_recursive(const bContext *C, Panel *panel)
int ui_handler_panel_region(bContext *C, const wmEvent *event, ARegion *region, const uiBut *active_but)
int UI_panel_size_y(const Panel *panel)
void UI_panels_begin(const bContext *, ARegion *region)
static bool panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index)
void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y)
void UI_panel_category_active_set(ARegion *region, const char *idname)
void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data)
static void ui_handler_remove_panel(bContext *C, void *userdata)
static bool panel_custom_pin_to_last_get(const Panel *panel)
void ui_panel_tag_search_filter_match(Panel *panel)
#define DRAG_REGION_PAD
bool UI_panel_is_active(const Panel *panel)
static int get_panel_size_y(const Panel *panel)
bool UI_panel_should_show_background(const ARegion *region, const PanelType *panel_type)
static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel)
static void panel_draw_aligned_widgets(const uiStyle *style, const Panel *panel, const rcti *header_rect, const float aspect, const bool show_pin, const bool show_background, const bool region_search_filter_active)
static PanelCategoryDyn * panel_categories_find_mouse_over(ARegion *region, const wmEvent *event)
void UI_panel_category_clear_all(ARegion *region)
PanelCategoryStack * UI_panel_category_active_find(ARegion *region, const char *idname)
static bool panels_need_realign(const ScrArea *area, ARegion *region, Panel **r_panel_animation)
LayoutPanelHeader * ui_layout_panel_header_under_mouse(const Panel &panel, const int my)
#define PNL_ICON
static float panel_region_offset_x_get(const ARegion *region)
static void align_sub_panels(Panel *panel)
uiPanelMouseState
@ PANEL_MOUSE_INSIDE_HEADER
@ PANEL_MOUSE_INSIDE_LAYOUT_PANEL_HEADER
@ PANEL_MOUSE_INSIDE_CONTENT
@ PANEL_MOUSE_OUTSIDE
static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches)
static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel)
static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel)
#define TABS_PADDING_BETWEEN_FACTOR
int UI_panel_category_index_find(ARegion *region, const char *idname)
static Panel * panel_add_instanced(ListBase *panels, PanelType *panel_type, PointerRNA *custom_data)
static void panel_handle_data_ensure(const bContext *C, wmWindow *win, const ARegion *region, Panel *panel, const uiHandlePanelState state)
void UI_panel_category_active_set_default(ARegion *region, const char *idname)
#define ANIMATION_INTERVAL
void UI_panel_label_offset(const uiBlock *block, int *r_x, int *r_y)
#define TABS_PADDING_TEXT_FACTOR
static int compare_panel(const void *a, const void *b)
static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata)
static void panels_collapse_all(ARegion *region, const Panel *from_panel)
static void panels_layout_begin_clear_flags(ListBase *lb)
static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel)
void UI_panel_end(Panel *panel, int width, int height)
void UI_panel_category_index_active_set(ARegion *region, const int index)
bool UI_panel_list_matches_data(ARegion *region, ListBase *data, uiListPanelIDFromDataFunc panel_idname_func)
void UI_panel_header_buttons_end(Panel *panel)
bool UI_panel_is_dragging(const Panel *panel)
static void ui_offset_panel_block(uiBlock *block)
void UI_panels_draw(const bContext *C, ARegion *region)
static void panel_custom_data_active_set(Panel *panel)
static int ui_handle_panel_category_cycling(const wmEvent *event, ARegion *region, const uiBut *active_but)
PointerRNA * UI_panel_custom_data_get(const Panel *panel)
static bool panel_active_animation_changed(ListBase *lb, Panel **r_panel_animation, bool *r_no_animation)
static bool uiAlignPanelStep(ARegion *region, const float factor, const bool drag)
void UI_list_panel_unique_str(Panel *panel, char *r_name)
void UI_panel_context_pointer_set(Panel *panel, const char *name, PointerRNA *ptr)
void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open)
Panel * UI_panel_add_instanced(const bContext *C, ARegion *region, ListBase *panels, const char *panel_idname, PointerRNA *custom_data)
void UI_panel_category_add(ARegion *region, const char *name)
static void panel_draw_aligned_backdrop(const ARegion *region, const Panel *panel, const rcti *rect, const rcti *header_rect)
static int find_highest_panel(const void *a, const void *b)
static void ui_handle_panel_header(const bContext *C, const uiBlock *block, const int mx, const int event_type, const bool ctrl, const bool shift)
static void ui_handle_layout_panel_header(bContext *C, const uiBlock *block, const int, const int my, const int event_type)
static void panel_calculate_size_recursive(ARegion *region, Panel *panel)
PointerRNA * UI_region_panel_custom_data_under_cursor(const bContext *C, const wmEvent *event)
const char * UI_panel_category_active_get(ARegion *region, bool set_fallback)
static void ui_panel_category_active_set(ARegion *region, const char *idname, bool fallback)
void ui_draw_aligned_panel(const ARegion *region, const uiStyle *style, const uiBlock *block, const rcti *rect, const bool show_pin, const bool show_background, const bool region_search_filter_active)
static void panel_draw_highlight_border(const Panel *panel, const rcti *rect, const rcti *header_rect)
static void panel_custom_pin_to_last_set(const bContext *C, const Panel *panel, const bool value)
static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block, const Panel *panel, const int mx, const int my)
static bool properties_space_needs_realign(const ScrArea *area, const ARegion *region)
uiHandlePanelState
@ PANEL_STATE_DRAG
@ PANEL_STATE_ANIMATION
@ PANEL_STATE_EXIT
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static ulong state[N]
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
void RNA_boolean_set(PointerRNA *ptr, const char *name, bool value)
void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
bool RNA_pointer_is_null(const PointerRNA *ptr)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
ListBase paneltypes
ListBase panels_category_active
ARegionRuntimeHandle * runtime
ListBase panels
PointerRNA open_owner_ptr
std::string open_prop_name
blender::Vector< LayoutPanelBody > bodies
blender::Vector< LayoutPanelHeader > headers
void * first
struct PanelCategoryDyn * next
struct PanelCategoryDyn * prev
struct PanelCategoryStack * next
void(* reorder)(bContext *C, Panel *pa, int new_index)
char idname[BKE_ST_MAXNAME]
char pin_to_last_property[BKE_ST_MAXNAME]
char context[BKE_ST_MAXNAME]
char translation_context[BKE_ST_MAXNAME]
char active_property[BKE_ST_MAXNAME]
ListBase children
char category[BKE_ST_MAXNAME]
char label[BKE_ST_MAXNAME]
short(* get_list_data_expand_flag)(const bContext *C, Panel *pa)
PanelType * parent
bContextStore * context
uiBlock * block
PointerRNA * custom_data_ptr
LayoutPanels layout_panels
struct PanelType * type
short labelofs
struct Panel_Runtime * runtime
struct uiLayout * layout
void * activedata
short runtime_flag
char * drawname
struct Panel * next
ListBase children
ListBase spacedata
uiWidgetColors wcol_menu_back
float panel_roundness
uiWidgetColors wcol_tab
ThemeUI tui
float xmax
float xmin
float ymax
float ymin
int ymin
int ymax
int xmin
int xmax
blender::Vector< std::unique_ptr< uiBut > > buttons
blender::Vector< uiButtonGroup > button_groups
eButType type
uiButtonGroupFlag flag
blender::Vector< uiBut * > buttons
uiHandlePanelState state
uiFontStyle paneltitle
short panelspace
uiFontStyle widget
unsigned char text[4]
wmEventModifierFlag modifier
Definition WM_types.hh:771
wmEventType type
Definition WM_types.hh:754
short val
Definition WM_types.hh:756
int xy[2]
Definition WM_types.hh:758
int mval[2]
Definition WM_types.hh:760
void * customdata
Definition WM_types.hh:804
struct wmEvent * eventstate
i
Definition text_draw.cc:230
wmEventHandler_UI * WM_event_add_ui_handler(const bContext *C, ListBase *handlers, wmUIHandlerFunc handle_fn, wmUIHandlerRemoveFunc remove_fn, void *user_data, const eWM_EventHandlerFlag flag)
void WM_event_remove_ui_handler(ListBase *handlers, wmUIHandlerFunc handle_fn, wmUIHandlerRemoveFunc remove_fn, void *user_data, const bool postpone)
#define ISMOUSE_MOTION(event_type)
@ RIGHTMOUSE
@ EVT_PADPERIOD
@ TIMER
@ EVT_TABKEY
@ EVT_AKEY
@ WHEELUPMOUSE
@ EVT_PADENTER
@ WHEELDOWNMOUSE
@ MOUSEMOVE
@ LEFTMOUSE
@ EVT_RETKEY
PointerRNA * ptr
Definition wm_files.cc:4227
wmOperatorType * ot
Definition wm_files.cc:4226
wmOperatorType * WM_operatortype_find(const char *idname, bool quiet)
void WM_tooltip_clear(bContext *C, wmWindow *win)
Definition wm_tooltip.cc:82
wmTimer * WM_event_timer_add(wmWindowManager *wm, wmWindow *win, const wmEventType event_type, const double time_step)
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *, wmTimer *timer)
uint8_t flag
Definition wm_window.cc:139