1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 *
16 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
17 * All rights reserved.
18 */
19
20 /** \file
21 * \ingroup edinterface
22 */
23
24 /* a full doc with API notes can be found in
25 * bf-blender/trunk/blender/doc/guides/interface_API.txt */
26
27 #include <ctype.h>
28 #include <math.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "MEM_guardedalloc.h"
33
34 #include "PIL_time.h"
35
36 #include "BLI_blenlib.h"
37 #include "BLI_math.h"
38 #include "BLI_utildefines.h"
39
40 #include "BLT_translation.h"
41
42 #include "DNA_screen_types.h"
43 #include "DNA_userdef_types.h"
44
45 #include "BKE_context.h"
46 #include "BKE_screen.h"
47
48 #include "BLF_api.h"
49
50 #include "WM_api.h"
51 #include "WM_types.h"
52
53 #include "ED_screen.h"
54
55 #include "UI_interface.h"
56 #include "UI_interface_icons.h"
57 #include "UI_resources.h"
58 #include "UI_view2d.h"
59
60 #include "GPU_batch_presets.h"
61 #include "GPU_immediate.h"
62 #include "GPU_matrix.h"
63 #include "GPU_state.h"
64
65 #include "interface_intern.h"
66
67 /* -------------------------------------------------------------------- */
68 /** \name Defines & Structs
69 * \{ */
70
71 #define ANIMATION_TIME 0.30
72 #define ANIMATION_INTERVAL 0.02
73
74 typedef enum uiPanelRuntimeFlag {
75 PANEL_LAST_ADDED = (1 << 0),
76 PANEL_ACTIVE = (1 << 2),
77 PANEL_WAS_ACTIVE = (1 << 3),
78 PANEL_ANIM_ALIGN = (1 << 4),
79 PANEL_NEW_ADDED = (1 << 5),
80 PANEL_SEARCH_FILTER_MATCH = (1 << 7),
81 /**
82 * Use the status set by property search (#PANEL_SEARCH_FILTER_MATCH)
83 * instead of #PNL_CLOSED. Set to true on every property search update.
84 */
85 PANEL_USE_CLOSED_FROM_SEARCH = (1 << 8),
86 /** The Panel was before the start of the current / latest layout pass. */
87 PANEL_WAS_CLOSED = (1 << 9),
88 } uiPanelRuntimeFlag;
89
90 /* The state of the mouse position relative to the panel. */
91 typedef enum uiPanelMouseState {
92 PANEL_MOUSE_OUTSIDE, /** Mouse is not in the panel. */
93 PANEL_MOUSE_INSIDE_CONTENT, /** Mouse is in the actual panel content. */
94 PANEL_MOUSE_INSIDE_HEADER, /** Mouse is in the panel header. */
95 } uiPanelMouseState;
96
97 typedef enum uiHandlePanelState {
98 PANEL_STATE_DRAG,
99 PANEL_STATE_DRAG_SCALE,
100 PANEL_STATE_WAIT_UNTAB,
101 PANEL_STATE_ANIMATION,
102 PANEL_STATE_EXIT,
103 } uiHandlePanelState;
104
105 typedef struct uiHandlePanelData {
106 uiHandlePanelState state;
107
108 /* Animation. */
109 wmTimer *animtimer;
110 double starttime;
111
112 /* Dragging. */
113 bool is_drag_drop;
114 int startx, starty;
115 int startofsx, startofsy;
116 int startsizex, startsizey;
117 float start_cur_xmin, start_cur_ymin;
118 } uiHandlePanelData;
119
120 typedef struct PanelSort {
121 Panel *panel;
122 int new_offset_x;
123 int new_offset_y;
124 } PanelSort;
125
126 static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel);
127 static int get_panel_real_size_y(const Panel *panel);
128 static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelState state);
129 static int compare_panel(const void *a, const void *b);
130 static bool panel_type_context_poll(ARegion *region,
131 const PanelType *panel_type,
132 const char *context);
133
134 /** \} */
135
136 /* -------------------------------------------------------------------- */
137 /** \name Local Functions
138 * \{ */
139
panel_title_color_get(const Panel * panel,const bool show_background,const bool region_search_filter_active,uchar r_color[4])140 static void panel_title_color_get(const Panel *panel,
141 const bool show_background,
142 const bool region_search_filter_active,
143 uchar r_color[4])
144 {
145 if (!show_background) {
146 /* Use menu colors for floating panels. */
147 bTheme *btheme = UI_GetTheme();
148 const uiWidgetColors *wcol = &btheme->tui.wcol_menu_back;
149 copy_v4_v4_uchar(r_color, (const uchar *)wcol->text);
150 return;
151 }
152
153 const bool search_match = UI_panel_matches_search_filter(panel);
154
155 UI_GetThemeColor4ubv(TH_TITLE, r_color);
156 if (region_search_filter_active && !search_match) {
157 r_color[0] *= 0.5;
158 r_color[1] *= 0.5;
159 r_color[2] *= 0.5;
160 }
161 }
162
panel_active_animation_changed(ListBase * lb,Panel ** r_panel_animation,bool * r_no_animation)163 static bool panel_active_animation_changed(ListBase *lb,
164 Panel **r_panel_animation,
165 bool *r_no_animation)
166 {
167 LISTBASE_FOREACH (Panel *, panel, lb) {
168 /* Detect panel active flag changes. */
169 if (!(panel->type && panel->type->parent)) {
170 if ((panel->runtime_flag & PANEL_WAS_ACTIVE) && !(panel->runtime_flag & PANEL_ACTIVE)) {
171 return true;
172 }
173 if (!(panel->runtime_flag & PANEL_WAS_ACTIVE) && (panel->runtime_flag & PANEL_ACTIVE)) {
174 return true;
175 }
176 }
177
178 /* Detect changes in panel expansions. */
179 if ((bool)(panel->runtime_flag & PANEL_WAS_CLOSED) != UI_panel_is_closed(panel)) {
180 *r_panel_animation = panel;
181 return false;
182 }
183
184 if ((panel->runtime_flag & PANEL_ACTIVE) && !UI_panel_is_closed(panel)) {
185 if (panel_active_animation_changed(&panel->children, r_panel_animation, r_no_animation)) {
186 return true;
187 }
188 }
189
190 /* Detect animation. */
191 if (panel->activedata) {
192 uiHandlePanelData *data = panel->activedata;
193 if (data->state == PANEL_STATE_ANIMATION) {
194 *r_panel_animation = panel;
195 }
196 else {
197 /* Don't animate while handling other interaction. */
198 *r_no_animation = true;
199 }
200 }
201 if ((panel->runtime_flag & PANEL_ANIM_ALIGN) && !(*r_panel_animation)) {
202 *r_panel_animation = panel;
203 }
204 }
205
206 return false;
207 }
208
209 /**
210 * \return True if the properties editor switch tabs since the last layout pass.
211 */
properties_space_needs_realign(ScrArea * area,ARegion * region)212 static bool properties_space_needs_realign(ScrArea *area, ARegion *region)
213 {
214 if (area->spacetype == SPACE_PROPERTIES && region->regiontype == RGN_TYPE_WINDOW) {
215 SpaceProperties *sbuts = area->spacedata.first;
216
217 if (sbuts->mainbo != sbuts->mainb) {
218 return true;
219 }
220 }
221
222 return false;
223 }
224
panels_need_realign(ScrArea * area,ARegion * region,Panel ** r_panel_animation)225 static bool panels_need_realign(ScrArea *area, ARegion *region, Panel **r_panel_animation)
226 {
227 *r_panel_animation = NULL;
228
229 if (properties_space_needs_realign(area, region)) {
230 return true;
231 }
232
233 /* Detect if a panel was added or removed. */
234 Panel *panel_animation = NULL;
235 bool no_animation = false;
236 if (panel_active_animation_changed(®ion->panels, &panel_animation, &no_animation)) {
237 return true;
238 }
239
240 /* Detect panel marked for animation, if we're not already animating. */
241 if (panel_animation) {
242 if (!no_animation) {
243 *r_panel_animation = panel_animation;
244 }
245 return true;
246 }
247
248 return false;
249 }
250
251 /** \} */
252
253 /* -------------------------------------------------------------------- */
254 /** \name Functions for Instanced Panels
255 * \{ */
256
panel_add_instanced(ARegion * region,ListBase * panels,PanelType * panel_type,PointerRNA * custom_data)257 static Panel *panel_add_instanced(ARegion *region,
258 ListBase *panels,
259 PanelType *panel_type,
260 PointerRNA *custom_data)
261 {
262 Panel *panel = MEM_callocN(sizeof(Panel), "instanced panel");
263 panel->type = panel_type;
264 BLI_strncpy(panel->panelname, panel_type->idname, sizeof(panel->panelname));
265
266 panel->runtime.custom_data_ptr = custom_data;
267 panel->runtime_flag |= PANEL_NEW_ADDED;
268
269 /* Add the panel's children too. Although they aren't instanced panels, we can still use this
270 * function to create them, as UI_panel_begin does other things we don't need to do. */
271 LISTBASE_FOREACH (LinkData *, child, &panel_type->children) {
272 PanelType *child_type = child->data;
273 panel_add_instanced(region, &panel->children, child_type, custom_data);
274 }
275
276 /* Make sure the panel is added to the end of the display-order as well. This is needed for
277 * loading existing files.
278 *
279 * Note: We could use special behavior to place it after the panel that starts the list of
280 * instanced panels, but that would add complexity that isn't needed for now. */
281 int max_sortorder = 0;
282 LISTBASE_FOREACH (Panel *, existing_panel, panels) {
283 if (existing_panel->sortorder > max_sortorder) {
284 max_sortorder = existing_panel->sortorder;
285 }
286 }
287 panel->sortorder = max_sortorder + 1;
288
289 BLI_addtail(panels, panel);
290
291 return panel;
292 }
293
294 /**
295 * Called in situations where panels need to be added dynamically rather than
296 * having only one panel corresponding to each #PanelType.
297 */
UI_panel_add_instanced(const bContext * C,ARegion * region,ListBase * panels,char * panel_idname,PointerRNA * custom_data)298 Panel *UI_panel_add_instanced(const bContext *C,
299 ARegion *region,
300 ListBase *panels,
301 char *panel_idname,
302 PointerRNA *custom_data)
303 {
304 ARegionType *region_type = region->type;
305
306 PanelType *panel_type = BLI_findstring(
307 ®ion_type->paneltypes, panel_idname, offsetof(PanelType, idname));
308
309 if (panel_type == NULL) {
310 printf("Panel type '%s' not found.\n", panel_idname);
311 return NULL;
312 }
313
314 Panel *new_panel = panel_add_instanced(region, panels, panel_type, custom_data);
315
316 /* Do this after #panel_add_instatnced so all subpanels are added. */
317 panel_set_expansion_from_list_data(C, new_panel);
318
319 return new_panel;
320 }
321
322 /**
323 * Find a unique key to append to the #PanelTyype.idname for the lookup to the panel's #uiBlock.
324 * Needed for instanced panels, where there can be multiple with the same type and identifier.
325 */
UI_list_panel_unique_str(Panel * panel,char * r_name)326 void UI_list_panel_unique_str(Panel *panel, char *r_name)
327 {
328 /* The panel sortorder will be unique for a specific panel type because the instanced
329 * panel list is regenerated for every change in the data order / length. */
330 snprintf(r_name, INSTANCED_PANEL_UNIQUE_STR_LEN, "%d", panel->sortorder);
331 }
332
333 /**
334 * Free a panel and its children. Custom data is shared by the panel and its children
335 * and is freed by #UI_panels_free_instanced.
336 *
337 * \note The only panels that should need to be deleted at runtime are panels with the
338 * #PNL_INSTANCED flag set.
339 */
panel_delete(const bContext * C,ARegion * region,ListBase * panels,Panel * panel)340 static void panel_delete(const bContext *C, ARegion *region, ListBase *panels, Panel *panel)
341 {
342 /* Recursively delete children. */
343 LISTBASE_FOREACH_MUTABLE (Panel *, child, &panel->children) {
344 panel_delete(C, region, &panel->children, child);
345 }
346 BLI_freelistN(&panel->children);
347
348 BLI_remlink(panels, panel);
349 if (panel->activedata) {
350 MEM_freeN(panel->activedata);
351 }
352 MEM_freeN(panel);
353 }
354
355 /**
356 * Remove instanced panels from the region's panel list.
357 *
358 * \note Can be called with NULL \a C, but it should be avoided because
359 * handlers might not be removed.
360 */
UI_panels_free_instanced(const bContext * C,ARegion * region)361 void UI_panels_free_instanced(const bContext *C, ARegion *region)
362 {
363 /* Delete panels with the instanced flag. */
364 LISTBASE_FOREACH_MUTABLE (Panel *, panel, ®ion->panels) {
365 if ((panel->type != NULL) && (panel->type->flag & PNL_INSTANCED)) {
366 /* Make sure the panel's handler is removed before deleting it. */
367 if (C != NULL && panel->activedata != NULL) {
368 panel_activate_state(C, panel, PANEL_STATE_EXIT);
369 }
370
371 /* Free panel's custom data. */
372 if (panel->runtime.custom_data_ptr != NULL) {
373 MEM_freeN(panel->runtime.custom_data_ptr);
374 }
375
376 /* Free the panel and its sub-panels. */
377 panel_delete(C, region, ®ion->panels, panel);
378 }
379 }
380 }
381
382 /**
383 * Check if the instanced panels in the region's panels correspond to the list of data the panels
384 * represent. Returns false if the panels have been reordered or if the types from the list data
385 * don't match in any way.
386 *
387 * \param data: The list of data to check against the instanced panels.
388 * \param panel_idname_func: Function to find the #PanelType.idname for each item in the data list.
389 * For a readability and generality, this lookup happens separately for each type of panel list.
390 */
UI_panel_list_matches_data(ARegion * region,ListBase * data,uiListPanelIDFromDataFunc panel_idname_func)391 bool UI_panel_list_matches_data(ARegion *region,
392 ListBase *data,
393 uiListPanelIDFromDataFunc panel_idname_func)
394 {
395 /* Check for NULL data. */
396 int data_len = 0;
397 Link *data_link = NULL;
398 if (data == NULL) {
399 data_len = 0;
400 data_link = NULL;
401 }
402 else {
403 data_len = BLI_listbase_count(data);
404 data_link = data->first;
405 }
406
407 int i = 0;
408 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
409 if (panel->type != NULL && panel->type->flag & PNL_INSTANCED) {
410 /* The panels were reordered by drag and drop. */
411 if (panel->flag & PNL_INSTANCED_LIST_ORDER_CHANGED) {
412 return false;
413 }
414
415 /* We reached the last data item before the last instanced panel. */
416 if (data_link == NULL) {
417 return false;
418 }
419
420 /* Check if the panel type matches the panel type from the data item. */
421 char panel_idname[MAX_NAME];
422 panel_idname_func(data_link, panel_idname);
423 if (!STREQ(panel_idname, panel->type->idname)) {
424 return false;
425 }
426
427 data_link = data_link->next;
428 i++;
429 }
430 }
431
432 /* If we didn't make it to the last list item, the panel list isn't complete. */
433 if (i != data_len) {
434 return false;
435 }
436
437 return true;
438 }
439
reorder_instanced_panel_list(bContext * C,ARegion * region,Panel * drag_panel)440 static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel)
441 {
442 /* Without a type we cannot access the reorder callback. */
443 if (drag_panel->type == NULL) {
444 return;
445 }
446 /* Don't reorder if this instanced panel doesn't support drag and drop reordering. */
447 if (drag_panel->type->reorder == NULL) {
448 return;
449 }
450
451 char *context = NULL;
452 if (!UI_panel_category_is_visible(region)) {
453 context = drag_panel->type->context;
454 }
455
456 /* Find how many instanced panels with this context string. */
457 int list_panels_len = 0;
458 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
459 if (panel->type) {
460 if (panel->type->flag & PNL_INSTANCED) {
461 if (panel_type_context_poll(region, panel->type, context)) {
462 list_panels_len++;
463 }
464 }
465 }
466 }
467
468 /* Sort the matching instanced panels by their display order. */
469 PanelSort *panel_sort = MEM_callocN(list_panels_len * sizeof(*panel_sort), "instancedpanelsort");
470 PanelSort *sort_index = panel_sort;
471 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
472 if (panel->type) {
473 if (panel->type->flag & PNL_INSTANCED) {
474 if (panel_type_context_poll(region, panel->type, context)) {
475 sort_index->panel = panel;
476 sort_index++;
477 }
478 }
479 }
480 }
481 qsort(panel_sort, list_panels_len, sizeof(*panel_sort), compare_panel);
482
483 /* Find how many of those panels are above this panel. */
484 int move_to_index = 0;
485 for (; move_to_index < list_panels_len; move_to_index++) {
486 if (panel_sort[move_to_index].panel == drag_panel) {
487 break;
488 }
489 }
490
491 MEM_freeN(panel_sort);
492
493 /* Set the bit to tell the interface to instanced the list. */
494 drag_panel->flag |= PNL_INSTANCED_LIST_ORDER_CHANGED;
495
496 /* Finally, move this panel's list item to the new index in its list. */
497 drag_panel->type->reorder(C, drag_panel, move_to_index);
498 }
499
500 /**
501 * Recursive implementation for #panel_set_expansion_from_list_data.
502 *
503 * \return Whether the closed flag for the panel or any sub-panels changed.
504 */
panel_set_expand_from_list_data_recursive(Panel * panel,short flag,short * flag_index)505 static bool panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index)
506 {
507 const bool open = (flag & (1 << *flag_index));
508 bool changed = (open == UI_panel_is_closed(panel));
509
510 SET_FLAG_FROM_TEST(panel->flag, !open, PNL_CLOSED);
511
512 LISTBASE_FOREACH (Panel *, child, &panel->children) {
513 *flag_index = *flag_index + 1;
514 changed |= panel_set_expand_from_list_data_recursive(child, flag, flag_index);
515 }
516 return changed;
517 }
518
519 /**
520 * Set the expansion of the panel and its sub-panels from the flag stored in the
521 * corresponding list data. The flag has expansion stored in each bit in depth first order.
522 */
panel_set_expansion_from_list_data(const bContext * C,Panel * panel)523 static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel)
524 {
525 BLI_assert(panel->type != NULL);
526 BLI_assert(panel->type->flag & PNL_INSTANCED);
527 if (panel->type->get_list_data_expand_flag == NULL) {
528 /* Instanced panel doesn't support loading expansion. */
529 return;
530 }
531
532 const short expand_flag = panel->type->get_list_data_expand_flag(C, panel);
533 short flag_index = 0;
534
535 /* Start panel animation if the open state was changed. */
536 if (panel_set_expand_from_list_data_recursive(panel, expand_flag, &flag_index)) {
537 panel_activate_state(C, panel, PANEL_STATE_ANIMATION);
538 }
539 }
540
541 /**
542 * Set expansion based on the data for instanced panels.
543 */
region_panels_set_expansion_from_list_data(const bContext * C,ARegion * region)544 static void region_panels_set_expansion_from_list_data(const bContext *C, ARegion *region)
545 {
546 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
547 if (panel->runtime_flag & PANEL_ACTIVE) {
548 PanelType *panel_type = panel->type;
549 if (panel_type != NULL && panel->type->flag & PNL_INSTANCED) {
550 panel_set_expansion_from_list_data(C, panel);
551 }
552 }
553 }
554 }
555
556 /**
557 * Recursive implementation for #set_panels_list_data_expand_flag.
558 */
get_panel_expand_flag(Panel * panel,short * flag,short * flag_index)559 static void get_panel_expand_flag(Panel *panel, short *flag, short *flag_index)
560 {
561 const bool open = !(panel->flag & PNL_CLOSED);
562 SET_FLAG_FROM_TEST(*flag, open, (1 << *flag_index));
563
564 LISTBASE_FOREACH (Panel *, child, &panel->children) {
565 *flag_index = *flag_index + 1;
566 get_panel_expand_flag(child, flag, flag_index);
567 }
568 }
569
570 /**
571 * Call the callback to store the panel and sub-panel expansion settings in the list item that
572 * corresponds to each instanced panel.
573 *
574 * \note This needs to iterate through all of the regions panels because the panel with changed
575 * expansion could have been the sub-panel of a instanced panel, meaning it might not know
576 * which list item it corresponds to.
577 */
set_panels_list_data_expand_flag(const bContext * C,const ARegion * region)578 static void set_panels_list_data_expand_flag(const bContext *C, const ARegion *region)
579 {
580 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
581 PanelType *panel_type = panel->type;
582 if (panel_type == NULL) {
583 continue;
584 }
585
586 /* Check for #PANEL_ACTIVE so we only set the expand flag for active panels. */
587 if (panel_type->flag & PNL_INSTANCED && panel->runtime_flag & PANEL_ACTIVE) {
588 short expand_flag;
589 short flag_index = 0;
590 get_panel_expand_flag(panel, &expand_flag, &flag_index);
591 if (panel->type->set_list_data_expand_flag) {
592 panel->type->set_list_data_expand_flag(C, panel, expand_flag);
593 }
594 }
595 }
596 }
597
598 /** \} */
599
600 /* -------------------------------------------------------------------- */
601 /** \name Panels
602 * \{ */
603
604 /**
605 * Set flag state for a panel and its sub-panels.
606 *
607 * \return True if this function changed any of the flags, false if it didn't.
608 */
panel_set_flag_recursive(Panel * panel,int flag,bool value)609 static bool panel_set_flag_recursive(Panel *panel, int flag, bool value)
610 {
611 const short flag_original = panel->flag;
612
613 SET_FLAG_FROM_TEST(panel->flag, value, flag);
614
615 bool changed = (flag_original != panel->flag);
616
617 LISTBASE_FOREACH (Panel *, child, &panel->children) {
618 changed |= panel_set_flag_recursive(child, flag, value);
619 }
620
621 return changed;
622 }
623
panels_collapse_all(ARegion * region,const Panel * from_panel)624 static void panels_collapse_all(ARegion *region, const Panel *from_panel)
625 {
626 const bool has_category_tabs = UI_panel_category_is_visible(region);
627 const char *category = has_category_tabs ? UI_panel_category_active_get(region, false) : NULL;
628 const PanelType *from_pt = from_panel->type;
629
630 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
631 PanelType *pt = panel->type;
632
633 /* Close panels with headers in the same context. */
634 if (pt && from_pt && !(pt->flag & PNL_NO_HEADER)) {
635 if (!pt->context[0] || !from_pt->context[0] || STREQ(pt->context, from_pt->context)) {
636 if ((panel->flag & PNL_PIN) || !category || !pt->category[0] ||
637 STREQ(pt->category, category)) {
638 panel->flag |= PNL_CLOSED;
639 }
640 }
641 }
642 }
643 }
644
panel_type_context_poll(ARegion * region,const PanelType * panel_type,const char * context)645 static bool panel_type_context_poll(ARegion *region,
646 const PanelType *panel_type,
647 const char *context)
648 {
649 if (UI_panel_category_is_visible(region)) {
650 return STREQ(panel_type->category, UI_panel_category_active_get(region, false));
651 }
652
653 if (panel_type->context[0] && STREQ(panel_type->context, context)) {
654 return true;
655 }
656
657 return false;
658 }
659
UI_panel_find_by_type(ListBase * lb,PanelType * pt)660 Panel *UI_panel_find_by_type(ListBase *lb, PanelType *pt)
661 {
662 const char *idname = pt->idname;
663
664 LISTBASE_FOREACH (Panel *, panel, lb) {
665 if (STREQLEN(panel->panelname, idname, sizeof(panel->panelname))) {
666 return panel;
667 }
668 }
669 return NULL;
670 }
671
672 /**
673 * \note \a panel should be return value from #UI_panel_find_by_type and can be NULL.
674 */
UI_panel_begin(ARegion * region,ListBase * lb,uiBlock * block,PanelType * pt,Panel * panel,bool * r_open)675 Panel *UI_panel_begin(
676 ARegion *region, ListBase *lb, uiBlock *block, PanelType *pt, Panel *panel, bool *r_open)
677 {
678 Panel *panel_last;
679 const char *drawname = CTX_IFACE_(pt->translation_context, pt->label);
680 const char *idname = pt->idname;
681 const bool newpanel = (panel == NULL);
682
683 if (newpanel) {
684 panel = MEM_callocN(sizeof(Panel), "new panel");
685 panel->type = pt;
686 BLI_strncpy(panel->panelname, idname, sizeof(panel->panelname));
687
688 if (pt->flag & PNL_DEFAULT_CLOSED) {
689 panel->flag |= PNL_CLOSED;
690 panel->runtime_flag |= PANEL_WAS_CLOSED;
691 }
692
693 panel->ofsx = 0;
694 panel->ofsy = 0;
695 panel->sizex = 0;
696 panel->sizey = 0;
697 panel->blocksizex = 0;
698 panel->blocksizey = 0;
699 panel->runtime_flag |= PANEL_NEW_ADDED;
700
701 BLI_addtail(lb, panel);
702 }
703 else {
704 /* Panel already exists. */
705 panel->type = pt;
706 }
707
708 panel->runtime.block = block;
709
710 BLI_strncpy(panel->drawname, drawname, sizeof(panel->drawname));
711
712 /* If a new panel is added, we insert it right after the panel that was last added.
713 * This way new panels are inserted in the right place between versions. */
714 for (panel_last = lb->first; panel_last; panel_last = panel_last->next) {
715 if (panel_last->runtime_flag & PANEL_LAST_ADDED) {
716 BLI_remlink(lb, panel);
717 BLI_insertlinkafter(lb, panel_last, panel);
718 break;
719 }
720 }
721
722 if (newpanel) {
723 panel->sortorder = (panel_last) ? panel_last->sortorder + 1 : 0;
724
725 LISTBASE_FOREACH (Panel *, panel_next, lb) {
726 if (panel_next != panel && panel_next->sortorder >= panel->sortorder) {
727 panel_next->sortorder++;
728 }
729 }
730 }
731
732 if (panel_last) {
733 panel_last->runtime_flag &= ~PANEL_LAST_ADDED;
734 }
735
736 /* Assign the new panel to the block. */
737 block->panel = panel;
738 panel->runtime_flag |= PANEL_ACTIVE | PANEL_LAST_ADDED;
739 if (region->alignment == RGN_ALIGN_FLOAT) {
740 UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
741 }
742
743 *r_open = false;
744
745 if (UI_panel_is_closed(panel)) {
746 return panel;
747 }
748
749 *r_open = true;
750
751 return panel;
752 }
753
754 /**
755 * Create the panel header button group, used to mark which buttons are part of
756 * panel headers for later panel search handling. Should be called before adding
757 * buttons for the panel's header layout.
758 */
UI_panel_header_buttons_begin(Panel * panel)759 void UI_panel_header_buttons_begin(Panel *panel)
760 {
761 uiBlock *block = panel->runtime.block;
762
763 ui_block_new_button_group(block, UI_BUTTON_GROUP_LOCK | UI_BUTTON_GROUP_PANEL_HEADER);
764 }
765
766 /**
767 * Allow new button groups to be created after the header group.
768 */
UI_panel_header_buttons_end(Panel * panel)769 void UI_panel_header_buttons_end(Panel *panel)
770 {
771 uiBlock *block = panel->runtime.block;
772
773 /* There should always be the button group created in #UI_panel_header_buttons_begin. */
774 BLI_assert(!BLI_listbase_is_empty(&block->button_groups));
775
776 uiButtonGroup *button_group = block->button_groups.last;
777
778 button_group->flag &= ~UI_BUTTON_GROUP_LOCK;
779
780 /* Repurpose the first "header" button group if it is empty, in case the first button added to
781 * the panel doesn't add a new group (if the button is created directly rather than through an
782 * interface layout call). */
783 if (BLI_listbase_is_single(&block->button_groups) &&
784 BLI_listbase_is_empty(&button_group->buttons)) {
785 button_group->flag &= ~UI_BUTTON_GROUP_PANEL_HEADER;
786 }
787 else {
788 /* We should still always add a new button group. Although this results in many empty groups,
789 * without it, new buttons not protected with a #ui_block_new_button_group call would end up
790 * in the panel header group. */
791 ui_block_new_button_group(block, 0);
792 }
793 }
794
panel_region_offset_x_get(const ARegion * region)795 static float panel_region_offset_x_get(const ARegion *region)
796 {
797 if (UI_panel_category_is_visible(region)) {
798 if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) {
799 return UI_PANEL_CATEGORY_MARGIN_WIDTH;
800 }
801 }
802
803 return 0.0f;
804 }
805
806 /**
807 * Starting from the "block size" set in #UI_panel_end, calculate the full size
808 * of the panel including the subpanel headers and buttons.
809 */
panel_calculate_size_recursive(ARegion * region,Panel * panel)810 static void panel_calculate_size_recursive(ARegion *region, Panel *panel)
811 {
812 int width = panel->blocksizex;
813 int height = panel->blocksizey;
814
815 LISTBASE_FOREACH (Panel *, child_panel, &panel->children) {
816 if (child_panel->runtime_flag & PANEL_ACTIVE) {
817 panel_calculate_size_recursive(region, child_panel);
818 width = max_ii(width, child_panel->sizex);
819 height += get_panel_real_size_y(child_panel);
820 }
821 }
822
823 /* Update total panel size. */
824 if (panel->runtime_flag & PANEL_NEW_ADDED) {
825 panel->runtime_flag &= ~PANEL_NEW_ADDED;
826 panel->sizex = width;
827 panel->sizey = height;
828 }
829 else {
830 const int old_sizex = panel->sizex, old_sizey = panel->sizey;
831 const int old_region_ofsx = panel->runtime.region_ofsx;
832
833 /* Update width/height if non-zero. */
834 if (width != 0) {
835 panel->sizex = width;
836 }
837 if (height != 0 || !UI_panel_is_closed(panel)) {
838 panel->sizey = height;
839 }
840
841 /* Check if we need to do an animation. */
842 if (panel->sizex != old_sizex || panel->sizey != old_sizey) {
843 panel->runtime_flag |= PANEL_ANIM_ALIGN;
844 panel->ofsy += old_sizey - panel->sizey;
845 }
846
847 panel->runtime.region_ofsx = panel_region_offset_x_get(region);
848 if (old_region_ofsx != panel->runtime.region_ofsx) {
849 panel->runtime_flag |= PANEL_ANIM_ALIGN;
850 }
851 }
852 }
853
UI_panel_end(Panel * panel,int width,int height)854 void UI_panel_end(Panel *panel, int width, int height)
855 {
856 /* Store the size of the buttons layout in the panel. The actual panel size
857 * (including subpanels) is calculated in #UI_panels_end. */
858 panel->blocksizex = width;
859 panel->blocksizey = height;
860 }
861
ui_offset_panel_block(uiBlock * block)862 static void ui_offset_panel_block(uiBlock *block)
863 {
864 const uiStyle *style = UI_style_get_dpi();
865
866 /* Compute bounds and offset. */
867 ui_block_bounds_calc(block);
868
869 const int ofsy = block->panel->sizey - style->panelspace;
870
871 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
872 but->rect.ymin += ofsy;
873 but->rect.ymax += ofsy;
874 }
875
876 block->rect.xmax = block->panel->sizex;
877 block->rect.ymax = block->panel->sizey;
878 block->rect.xmin = block->rect.ymin = 0.0;
879 }
880
ui_panel_tag_search_filter_match(struct Panel * panel)881 void ui_panel_tag_search_filter_match(struct Panel *panel)
882 {
883 panel->runtime_flag |= PANEL_SEARCH_FILTER_MATCH;
884 }
885
panel_matches_search_filter_recursive(const Panel * panel,bool * filter_matches)886 static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches)
887 {
888 *filter_matches |= panel->runtime_flag & PANEL_SEARCH_FILTER_MATCH;
889
890 /* If the panel has no match we need to make sure that its children are too. */
891 if (!*filter_matches) {
892 LISTBASE_FOREACH (const Panel *, child_panel, &panel->children) {
893 panel_matches_search_filter_recursive(child_panel, filter_matches);
894 }
895 }
896 }
897
898 /**
899 * Find whether a panel or any of its sub-panels contain a property that matches the search filter,
900 * depending on the search process running in #UI_block_apply_search_filter earlier.
901 */
UI_panel_matches_search_filter(const Panel * panel)902 bool UI_panel_matches_search_filter(const Panel *panel)
903 {
904 bool search_filter_matches = false;
905 panel_matches_search_filter_recursive(panel, &search_filter_matches);
906 return search_filter_matches;
907 }
908
909 /**
910 * Set the flag telling the panel to use its search result status for
911 * its expansion. Also activate animation if that changes the expansion.
912 */
panel_set_expansion_from_seach_filter_recursive(const bContext * C,Panel * panel,const bool use_search_closed,const bool use_animation)913 static void panel_set_expansion_from_seach_filter_recursive(const bContext *C,
914 Panel *panel,
915 const bool use_search_closed,
916 const bool use_animation)
917 {
918 /* This has to run on inactive panels that may not have a type,
919 * but we can prevent running on headerless panels in some cases. */
920 if (panel->type == NULL || !(panel->type->flag & PNL_NO_HEADER)) {
921 SET_FLAG_FROM_TEST(panel->runtime_flag, use_search_closed, PANEL_USE_CLOSED_FROM_SEARCH);
922 }
923
924 LISTBASE_FOREACH (Panel *, child_panel, &panel->children) {
925 /* Don't check if the subpanel is active, otherwise the
926 * expansion won't be reset when the parent is closed. */
927 panel_set_expansion_from_seach_filter_recursive(
928 C, child_panel, use_search_closed, use_animation);
929 }
930 }
931
932 /**
933 * Set the flag telling every panel to override its expansion with its search result status.
934 */
region_panels_set_expansion_from_seach_filter(const bContext * C,ARegion * region,const bool use_search_closed,const bool use_animation)935 static void region_panels_set_expansion_from_seach_filter(const bContext *C,
936 ARegion *region,
937 const bool use_search_closed,
938 const bool use_animation)
939 {
940 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
941 panel_set_expansion_from_seach_filter_recursive(C, panel, use_search_closed, use_animation);
942 }
943 set_panels_list_data_expand_flag(C, region);
944 }
945
946 /**
947 * Hide buttons in invisible layouts, which are created because in order to search,
948 * buttons must be added for all panels, even panels that will end up closed.
949 */
panel_remove_invisible_layouts_recursive(Panel * panel,const Panel * parent_panel)950 static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel)
951 {
952 uiBlock *block = panel->runtime.block;
953 BLI_assert(block != NULL);
954 BLI_assert(block->active);
955 if (parent_panel != NULL && UI_panel_is_closed(parent_panel)) {
956 /* The parent panel is closed, so this panel can be completely removed. */
957 UI_block_set_search_only(block, true);
958 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
959 but->flag |= UI_HIDDEN;
960 }
961 }
962 else if (UI_panel_is_closed(panel)) {
963 /* If subpanels have no search results but the parent panel does, then the parent panel open
964 * and the subpanels will close. In that case there must be a way to hide the buttons in the
965 * panel but keep the header buttons. */
966 LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) {
967 if (button_group->flag & UI_BUTTON_GROUP_PANEL_HEADER) {
968 continue;
969 }
970 LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) {
971 uiBut *but = link->data;
972 but->flag |= UI_HIDDEN;
973 }
974 }
975 }
976
977 LISTBASE_FOREACH (Panel *, child_panel, &panel->children) {
978 if (child_panel->runtime_flag & PANEL_ACTIVE) {
979 BLI_assert(child_panel->runtime.block != NULL);
980 panel_remove_invisible_layouts_recursive(child_panel, panel);
981 }
982 }
983 }
984
region_panels_remove_invisible_layouts(ARegion * region)985 static void region_panels_remove_invisible_layouts(ARegion *region)
986 {
987 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
988 if (panel->runtime_flag & PANEL_ACTIVE) {
989 BLI_assert(panel->runtime.block != NULL);
990 panel_remove_invisible_layouts_recursive(panel, NULL);
991 }
992 }
993 }
994
995 /**
996 * Get the panel's expansion state, taking into account
997 * expansion set from property search if it applies.
998 */
UI_panel_is_closed(const Panel * panel)999 bool UI_panel_is_closed(const Panel *panel)
1000 {
1001 /* Header-less panels can never be closed, otherwise they could disappear. */
1002 if (panel->type && panel->type->flag & PNL_NO_HEADER) {
1003 return false;
1004 }
1005
1006 if (panel->runtime_flag & PANEL_USE_CLOSED_FROM_SEARCH) {
1007 return !UI_panel_matches_search_filter(panel);
1008 }
1009
1010 return panel->flag & PNL_CLOSED;
1011 }
1012
UI_panel_is_active(const Panel * panel)1013 bool UI_panel_is_active(const Panel *panel)
1014 {
1015 return panel->runtime_flag & PANEL_ACTIVE;
1016 }
1017
1018 /** \} */
1019
1020 /* -------------------------------------------------------------------- */
1021 /** \name Drawing
1022 * \{ */
1023
1024 /**
1025 * Draw panels, selected (panels currently being dragged) on top.
1026 */
UI_panels_draw(const bContext * C,ARegion * region)1027 void UI_panels_draw(const bContext *C, ARegion *region)
1028 {
1029 /* Draw in reverse order, because #uiBlocks are added in reverse order
1030 * and we need child panels to draw on top. */
1031 LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) {
1032 if (block->active && block->panel && !(block->panel->flag & PNL_SELECT) &&
1033 !UI_block_is_search_only(block)) {
1034 UI_block_draw(C, block);
1035 }
1036 }
1037
1038 LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) {
1039 if (block->active && block->panel && (block->panel->flag & PNL_SELECT) &&
1040 !UI_block_is_search_only(block)) {
1041 UI_block_draw(C, block);
1042 }
1043 }
1044 }
1045
1046 /* Triangle 'icon' for panel header. */
UI_draw_icon_tri(float x,float y,char dir,const float color[4])1047 void UI_draw_icon_tri(float x, float y, char dir, const float color[4])
1048 {
1049 const float f3 = 0.05 * U.widget_unit;
1050 const float f5 = 0.15 * U.widget_unit;
1051 const float f7 = 0.25 * U.widget_unit;
1052
1053 if (dir == 'h') {
1054 UI_draw_anti_tria(x - f3, y - f5, x - f3, y + f5, x + f7, y, color);
1055 }
1056 else if (dir == 't') {
1057 UI_draw_anti_tria(x - f5, y - f7, x + f5, y - f7, x, y + f3, color);
1058 }
1059 else { /* 'v' = vertical, down. */
1060 UI_draw_anti_tria(x - f5, y + f3, x + f5, y + f3, x, y - f7, color);
1061 }
1062 }
1063
1064 #define PNL_ICON UI_UNIT_X /* Could be UI_UNIT_Y too. */
1065
1066 /* For button layout next to label. */
UI_panel_label_offset(uiBlock * block,int * r_x,int * r_y)1067 void UI_panel_label_offset(uiBlock *block, int *r_x, int *r_y)
1068 {
1069 Panel *panel = block->panel;
1070 const bool is_subpanel = (panel->type && panel->type->parent);
1071
1072 *r_x = UI_UNIT_X * 1.0f;
1073 *r_y = UI_UNIT_Y * 1.5f;
1074
1075 if (is_subpanel) {
1076 *r_x += (0.7f * UI_UNIT_X);
1077 }
1078 }
1079
ui_draw_aligned_panel_header(const uiStyle * style,const uiBlock * block,const rcti * rect,const bool show_background,const bool region_search_filter_active)1080 static void ui_draw_aligned_panel_header(const uiStyle *style,
1081 const uiBlock *block,
1082 const rcti *rect,
1083 const bool show_background,
1084 const bool region_search_filter_active)
1085 {
1086 const Panel *panel = block->panel;
1087 const bool is_subpanel = (panel->type && panel->type->parent);
1088 const uiFontStyle *fontstyle = (is_subpanel) ? &style->widgetlabel : &style->paneltitle;
1089
1090 /* + 0.001f to avoid flirting with float inaccuracy .*/
1091 const int pnl_icons = (panel->labelofs + (1.1f * PNL_ICON)) / block->aspect + 0.001f;
1092
1093 /* Draw text labels. */
1094 uchar col_title[4];
1095 panel_title_color_get(panel, show_background, region_search_filter_active, col_title);
1096 col_title[3] = 255;
1097
1098 rcti hrect = *rect;
1099 hrect.xmin = rect->xmin + pnl_icons;
1100 hrect.ymin -= 2.0f / block->aspect;
1101 UI_fontstyle_draw(fontstyle,
1102 &hrect,
1103 panel->drawname,
1104 col_title,
1105 &(struct uiFontStyleDraw_Params){
1106 .align = UI_STYLE_TEXT_LEFT,
1107 });
1108 }
1109
1110 /**
1111 * Draw a panel integrated in buttons-window, tool/property lists etc.
1112 */
ui_draw_aligned_panel(const uiStyle * style,const uiBlock * block,const rcti * rect,const bool show_pin,const bool show_background,const bool region_search_filter_active)1113 void ui_draw_aligned_panel(const uiStyle *style,
1114 const uiBlock *block,
1115 const rcti *rect,
1116 const bool show_pin,
1117 const bool show_background,
1118 const bool region_search_filter_active)
1119 {
1120 const Panel *panel = block->panel;
1121 float color[4];
1122 const bool is_subpanel = (panel->type && panel->type->parent);
1123 const bool show_drag = (!is_subpanel &&
1124 /* FIXME(campbell): currently no background means floating panel which
1125 * can't be dragged. This may be changed in future. */
1126 show_background);
1127 const int panel_col = is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK;
1128 const bool draw_box_style = (panel->type && panel->type->flag & PNL_DRAW_BOX);
1129
1130 /* Use the theme for box widgets for box-style panels. */
1131 uiWidgetColors *box_wcol = NULL;
1132 if (draw_box_style) {
1133 bTheme *btheme = UI_GetTheme();
1134 box_wcol = &btheme->tui.wcol_box;
1135 }
1136
1137 const uint pos = GPU_vertformat_attr_add(
1138 immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
1139
1140 if (panel->type && (panel->type->flag & PNL_NO_HEADER)) {
1141 if (show_background) {
1142 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1143 immUniformThemeColor(panel_col);
1144 immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
1145 immUnbindProgram();
1146 }
1147 return;
1148 }
1149
1150 /* Calculate header rectangle with + 0.001f to prevent flicker due to float inaccuracy. */
1151 rcti headrect = {
1152 rect->xmin, rect->xmax, rect->ymax, rect->ymax + floor(PNL_HEADER / block->aspect + 0.001f)};
1153
1154 /* Draw a panel and header backdrops with an opaque box backdrop for box style panels. */
1155 if (draw_box_style && !is_subpanel) {
1156 /* Expand the top a tiny bit to give header buttons equal size above and below. */
1157 rcti box_rect = {rect->xmin,
1158 rect->xmax,
1159 UI_panel_is_closed(panel) ? headrect.ymin : rect->ymin,
1160 headrect.ymax + U.pixelsize};
1161 ui_draw_box_opaque(&box_rect, UI_CNR_ALL);
1162
1163 /* Mimic the border between aligned box widgets for the bottom of the header. */
1164 if (!UI_panel_is_closed(panel)) {
1165 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1166 GPU_blend(GPU_BLEND_ALPHA);
1167
1168 immUniformColor4ubv(box_wcol->outline);
1169 immRectf(pos, rect->xmin, headrect.ymin - U.pixelsize, rect->xmax, headrect.ymin);
1170 uchar emboss_col[4];
1171 UI_GetThemeColor4ubv(TH_WIDGET_EMBOSS, emboss_col);
1172 immUniformColor4ubv(emboss_col);
1173 immRectf(pos,
1174 rect->xmin,
1175 headrect.ymin - U.pixelsize,
1176 rect->xmax,
1177 headrect.ymin - U.pixelsize - 1);
1178
1179 GPU_blend(GPU_BLEND_NONE);
1180 immUnbindProgram();
1181 }
1182 }
1183
1184 /* Draw the header backdrop. */
1185 if (show_background && !is_subpanel && !draw_box_style) {
1186 const float minx = rect->xmin;
1187 const float y = headrect.ymax;
1188
1189 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1190 GPU_blend(GPU_BLEND_ALPHA);
1191
1192 /* Draw with background color. */
1193 immUniformThemeColor(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER);
1194 immRectf(pos, minx, headrect.ymin, rect->xmax, y);
1195
1196 immBegin(GPU_PRIM_LINES, 4);
1197
1198 immVertex2f(pos, minx, y);
1199 immVertex2f(pos, rect->xmax, y);
1200
1201 immVertex2f(pos, minx, y);
1202 immVertex2f(pos, rect->xmax, y);
1203
1204 immEnd();
1205
1206 GPU_blend(GPU_BLEND_NONE);
1207 immUnbindProgram();
1208 }
1209
1210 /* draw optional pin icon */
1211 if (show_pin && (block->panel->flag & PNL_PIN)) {
1212 uchar col_title[4];
1213 panel_title_color_get(panel, show_background, region_search_filter_active, col_title);
1214
1215 GPU_blend(GPU_BLEND_ALPHA);
1216 UI_icon_draw_ex(headrect.xmax - ((PNL_ICON * 2.2f) / block->aspect),
1217 headrect.ymin + (5.0f / block->aspect),
1218 (panel->flag & PNL_PIN) ? ICON_PINNED : ICON_UNPINNED,
1219 (block->aspect * U.inv_dpi_fac),
1220 1.0f,
1221 0.0f,
1222 col_title,
1223 false);
1224 GPU_blend(GPU_BLEND_NONE);
1225 }
1226
1227 /* Draw the title. */
1228 rcti titlerect = headrect;
1229 if (is_subpanel) {
1230 titlerect.xmin += (0.7f * UI_UNIT_X) / block->aspect + 0.001f;
1231 }
1232 ui_draw_aligned_panel_header(
1233 style, block, &titlerect, show_background, region_search_filter_active);
1234
1235 if (show_drag) {
1236 /* Make `itemrect` smaller. */
1237 const float scale = 0.7;
1238 rctf itemrect;
1239 itemrect.xmax = headrect.xmax - (0.2f * UI_UNIT_X);
1240 itemrect.xmin = itemrect.xmax - BLI_rcti_size_y(&headrect);
1241 itemrect.ymin = headrect.ymin;
1242 itemrect.ymax = headrect.ymax;
1243 BLI_rctf_scale(&itemrect, scale);
1244
1245 GPU_matrix_push();
1246 GPU_matrix_translate_2f(itemrect.xmin, itemrect.ymin);
1247
1248 const int col_tint = 84;
1249 float col_high[4], col_dark[4];
1250 UI_GetThemeColorShade4fv(TH_PANEL_HEADER, col_tint, col_high);
1251 UI_GetThemeColorShade4fv(TH_PANEL_BACK, -col_tint, col_dark);
1252
1253 GPUBatch *batch = GPU_batch_preset_panel_drag_widget(
1254 U.pixelsize, col_high, col_dark, BLI_rcti_size_y(&headrect) * scale);
1255 GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_FLAT_COLOR);
1256 GPU_batch_draw(batch);
1257 GPU_matrix_pop();
1258 }
1259
1260 /* Draw panel backdrop. */
1261 if (!UI_panel_is_closed(panel)) {
1262 /* in some occasions, draw a border */
1263 if (panel->flag & PNL_SELECT && !is_subpanel) {
1264 float radius;
1265 if (draw_box_style) {
1266 UI_draw_roundbox_corner_set(UI_CNR_ALL);
1267 radius = box_wcol->roundness * U.widget_unit;
1268 }
1269 else {
1270 UI_draw_roundbox_corner_set(UI_CNR_NONE);
1271 radius = 0.0f;
1272 }
1273
1274 UI_GetThemeColorShade4fv(TH_BACK, -120, color);
1275 UI_draw_roundbox_aa(false,
1276 0.5f + rect->xmin,
1277 0.5f + rect->ymin,
1278 0.5f + rect->xmax,
1279 0.5f + headrect.ymax + 1,
1280 radius,
1281 color);
1282 }
1283
1284 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1285 GPU_blend(GPU_BLEND_ALPHA);
1286
1287 /* Draw panel backdrop if it wasn't already been drawn by the single opaque round box earlier.
1288 * Note: Sub-panels blend with panels, so they can't be opaque. */
1289 if (show_background && !(draw_box_style && !is_subpanel)) {
1290 /* Draw the bottom sub-panels. */
1291 if (draw_box_style) {
1292 if (panel->next) {
1293 immUniformThemeColor(panel_col);
1294 immRectf(
1295 pos, rect->xmin + U.pixelsize, rect->ymin, rect->xmax - U.pixelsize, rect->ymax);
1296 }
1297 else {
1298 /* Change the width a little bit to line up with sides. */
1299 UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT);
1300 UI_GetThemeColor4fv(panel_col, color);
1301 UI_draw_roundbox_aa(true,
1302 rect->xmin + U.pixelsize,
1303 rect->ymin + U.pixelsize,
1304 rect->xmax - U.pixelsize,
1305 rect->ymax,
1306 box_wcol->roundness * U.widget_unit,
1307 color);
1308 }
1309 }
1310 else {
1311 immUniformThemeColor(panel_col);
1312 immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
1313 }
1314 }
1315
1316 immUnbindProgram();
1317 }
1318
1319 /* Draw collapse icon. */
1320 {
1321 rctf itemrect = {.xmin = titlerect.xmin,
1322 .xmax = itemrect.xmin + BLI_rcti_size_y(&titlerect),
1323 .ymin = titlerect.ymin,
1324 .ymax = titlerect.ymax};
1325 BLI_rctf_scale(&itemrect, 0.25f);
1326
1327 uchar col_title[4];
1328 panel_title_color_get(panel, show_background, region_search_filter_active, col_title);
1329 float tria_color[4];
1330 rgb_uchar_to_float(tria_color, col_title);
1331 tria_color[3] = 1.0f;
1332
1333 if (UI_panel_is_closed(panel)) {
1334 ui_draw_anti_tria_rect(&itemrect, 'h', tria_color);
1335 }
1336 else {
1337 ui_draw_anti_tria_rect(&itemrect, 'v', tria_color);
1338 }
1339 }
1340 }
1341
1342 /** \} */
1343
1344 /* -------------------------------------------------------------------- */
1345 /** \name Category Drawing (Tabs)
1346 * \{ */
1347
1348 #define TABS_PADDING_BETWEEN_FACTOR 4.0f
1349 #define TABS_PADDING_TEXT_FACTOR 6.0f
1350
1351 /**
1352 * Draw vertical tabs on the left side of the region, one tab per category.
1353 */
UI_panel_category_draw_all(ARegion * region,const char * category_id_active)1354 void UI_panel_category_draw_all(ARegion *region, const char *category_id_active)
1355 {
1356 // #define USE_FLAT_INACTIVE
1357 const bool is_left = RGN_ALIGN_ENUM_FROM_MASK(region->alignment != RGN_ALIGN_RIGHT);
1358 View2D *v2d = ®ion->v2d;
1359 const uiStyle *style = UI_style_get();
1360 const uiFontStyle *fstyle = &style->widget;
1361 const int fontid = fstyle->uifont_id;
1362 short fstyle_points = fstyle->points;
1363 const float aspect = ((uiBlock *)region->uiblocks.first)->aspect;
1364 const float zoom = 1.0f / aspect;
1365 const int px = U.pixelsize;
1366 const int category_tabs_width = round_fl_to_int(UI_PANEL_CATEGORY_MARGIN_WIDTH * zoom);
1367 const float dpi_fac = UI_DPI_FAC;
1368 /* Padding of tabs around text. */
1369 const int tab_v_pad_text = round_fl_to_int(TABS_PADDING_TEXT_FACTOR * dpi_fac * zoom) + 2 * px;
1370 /* Padding between tabs. */
1371 const int tab_v_pad = round_fl_to_int(TABS_PADDING_BETWEEN_FACTOR * dpi_fac * zoom);
1372 bTheme *btheme = UI_GetTheme();
1373 const float tab_curve_radius = btheme->tui.wcol_tab.roundness * U.widget_unit * zoom;
1374 const int roundboxtype = is_left ? (UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT) :
1375 (UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT);
1376 bool is_alpha;
1377 bool do_scaletabs = false;
1378 #ifdef USE_FLAT_INACTIVE
1379 bool is_active_prev = false;
1380 #endif
1381 float scaletabs = 1.0f;
1382 /* Same for all tabs. */
1383 /* Intentionally don't scale by 'px'. */
1384 const int rct_xmin = is_left ? v2d->mask.xmin + 3 : (v2d->mask.xmax - category_tabs_width);
1385 const int rct_xmax = is_left ? v2d->mask.xmin + category_tabs_width : (v2d->mask.xmax - 3);
1386 const int text_v_ofs = (rct_xmax - rct_xmin) * 0.3f;
1387
1388 int y_ofs = tab_v_pad;
1389
1390 /* Primary theme colors. */
1391 uchar theme_col_back[4];
1392 uchar theme_col_text[3];
1393 uchar theme_col_text_hi[3];
1394
1395 /* Tab colors. */
1396 uchar theme_col_tab_bg[4];
1397 float theme_col_tab_active[4];
1398 float theme_col_tab_inactive[4];
1399 float theme_col_tab_outline[4];
1400
1401 UI_GetThemeColor4ubv(TH_BACK, theme_col_back);
1402 UI_GetThemeColor3ubv(TH_TEXT, theme_col_text);
1403 UI_GetThemeColor3ubv(TH_TEXT_HI, theme_col_text_hi);
1404
1405 UI_GetThemeColor4ubv(TH_TAB_BACK, theme_col_tab_bg);
1406 UI_GetThemeColor4fv(TH_TAB_ACTIVE, theme_col_tab_active);
1407 UI_GetThemeColor4fv(TH_TAB_INACTIVE, theme_col_tab_inactive);
1408 UI_GetThemeColor4fv(TH_TAB_OUTLINE, theme_col_tab_outline);
1409
1410 is_alpha = (region->overlap && (theme_col_back[3] != 255));
1411
1412 if (fstyle->kerning == 1) {
1413 BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
1414 }
1415
1416 BLF_enable(fontid, BLF_ROTATION);
1417 BLF_rotation(fontid, M_PI_2);
1418 // UI_fontstyle_set(&style->widget);
1419 ui_fontscale(&fstyle_points, aspect / (U.pixelsize * 1.1f));
1420 BLF_size(fontid, fstyle_points, U.dpi);
1421
1422 /* Check the region type supports categories to avoid an assert
1423 * for showing 3D view panels in the properties space. */
1424 if ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) {
1425 BLI_assert(UI_panel_category_is_visible(region));
1426 }
1427
1428 /* Calculate tab rectangle and check if we need to scale down. */
1429 LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) {
1430 rcti *rct = &pc_dyn->rect;
1431 const char *category_id = pc_dyn->idname;
1432 const char *category_id_draw = IFACE_(category_id);
1433 const int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX);
1434
1435 rct->xmin = rct_xmin;
1436 rct->xmax = rct_xmax;
1437
1438 rct->ymin = v2d->mask.ymax - (y_ofs + category_width + (tab_v_pad_text * 2));
1439 rct->ymax = v2d->mask.ymax - (y_ofs);
1440
1441 y_ofs += category_width + tab_v_pad + (tab_v_pad_text * 2);
1442 }
1443
1444 if (y_ofs > BLI_rcti_size_y(&v2d->mask)) {
1445 scaletabs = (float)BLI_rcti_size_y(&v2d->mask) / (float)y_ofs;
1446
1447 LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) {
1448 rcti *rct = &pc_dyn->rect;
1449 rct->ymin = ((rct->ymin - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax;
1450 rct->ymax = ((rct->ymax - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax;
1451 }
1452
1453 do_scaletabs = true;
1454 }
1455
1456 /* Begin drawing. */
1457 GPU_line_smooth(true);
1458
1459 uint pos = GPU_vertformat_attr_add(
1460 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
1461 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1462
1463 /* Draw the background. */
1464 if (is_alpha) {
1465 GPU_blend(GPU_BLEND_ALPHA);
1466 immUniformColor4ubv(theme_col_tab_bg);
1467 }
1468 else {
1469 immUniformColor3ubv(theme_col_tab_bg);
1470 }
1471
1472 if (is_left) {
1473 immRecti(
1474 pos, v2d->mask.xmin, v2d->mask.ymin, v2d->mask.xmin + category_tabs_width, v2d->mask.ymax);
1475 }
1476 else {
1477 immRecti(
1478 pos, v2d->mask.xmax - category_tabs_width, v2d->mask.ymin, v2d->mask.xmax, v2d->mask.ymax);
1479 }
1480
1481 if (is_alpha) {
1482 GPU_blend(GPU_BLEND_NONE);
1483 }
1484
1485 immUnbindProgram();
1486
1487 LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) {
1488 const rcti *rct = &pc_dyn->rect;
1489 const char *category_id = pc_dyn->idname;
1490 const char *category_id_draw = IFACE_(category_id);
1491 const int category_width = BLI_rcti_size_y(rct) - (tab_v_pad_text * 2);
1492 size_t category_draw_len = BLF_DRAW_STR_DUMMY_MAX;
1493 #if 0
1494 int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX);
1495 #endif
1496
1497 const bool is_active = STREQ(category_id, category_id_active);
1498
1499 GPU_blend(GPU_BLEND_ALPHA);
1500
1501 #ifdef USE_FLAT_INACTIVE
1502 /* Draw line between inactive tabs. */
1503 if (is_active == false && is_active_prev == false && pc_dyn->prev) {
1504 pos = GPU_vertformat_attr_add(
1505 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
1506 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1507 immUniformColor3fvAlpha(theme_col_tab_outline, 0.3f);
1508 immRecti(pos,
1509 is_left ? v2d->mask.xmin + (category_tabs_width / 5) :
1510 v2d->mask.xmax - (category_tabs_width / 5),
1511 rct->ymax + px,
1512 is_left ? (v2d->mask.xmin + category_tabs_width) - (category_tabs_width / 5) :
1513 (v2d->mask.xmax - category_tabs_width) + (category_tabs_width / 5),
1514 rct->ymax + (px * 3));
1515 immUnbindProgram();
1516 }
1517
1518 is_active_prev = is_active;
1519
1520 if (is_active)
1521 #endif
1522 {
1523 /* Draw filled rectangle and outline for tab. */
1524 UI_draw_roundbox_corner_set(roundboxtype);
1525 UI_draw_roundbox_4fv(true,
1526 rct->xmin,
1527 rct->ymin,
1528 rct->xmax,
1529 rct->ymax,
1530 tab_curve_radius,
1531 is_active ? theme_col_tab_active : theme_col_tab_inactive);
1532 UI_draw_roundbox_4fv(false,
1533 rct->xmin,
1534 rct->ymin,
1535 rct->xmax,
1536 rct->ymax,
1537 tab_curve_radius,
1538 theme_col_tab_outline);
1539
1540 /* Disguise the outline on one side to join the tab to the panel. */
1541 pos = GPU_vertformat_attr_add(
1542 immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
1543 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
1544
1545 immUniformColor4fv(is_active ? theme_col_tab_active : theme_col_tab_inactive);
1546 immRecti(pos,
1547 is_left ? rct->xmax - px : rct->xmin,
1548 rct->ymin + px,
1549 is_left ? rct->xmax : rct->xmin + px,
1550 rct->ymax - px);
1551 immUnbindProgram();
1552 }
1553
1554 /* Tab titles. */
1555
1556 if (do_scaletabs) {
1557 category_draw_len = BLF_width_to_strlen(
1558 fontid, category_id_draw, category_draw_len, category_width, NULL);
1559 }
1560
1561 BLF_position(fontid, rct->xmax - text_v_ofs, rct->ymin + tab_v_pad_text, 0.0f);
1562 BLF_color3ubv(fontid, theme_col_text);
1563 BLF_draw(fontid, category_id_draw, category_draw_len);
1564
1565 GPU_blend(GPU_BLEND_NONE);
1566
1567 /* Not essential, but allows events to be handled right up to the region edge (T38171). */
1568 if (is_left) {
1569 pc_dyn->rect.xmin = v2d->mask.xmin;
1570 }
1571 else {
1572 pc_dyn->rect.xmax = v2d->mask.xmax;
1573 }
1574 }
1575
1576 GPU_line_smooth(false);
1577
1578 BLF_disable(fontid, BLF_ROTATION);
1579
1580 if (fstyle->kerning == 1) {
1581 BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
1582 }
1583 }
1584
1585 #undef TABS_PADDING_BETWEEN_FACTOR
1586 #undef TABS_PADDING_TEXT_FACTOR
1587
1588 /** \} */
1589
1590 /* -------------------------------------------------------------------- */
1591 /** \name Panel Alignment
1592 * \{ */
1593
get_panel_size_y(const Panel * panel)1594 static int get_panel_size_y(const Panel *panel)
1595 {
1596 if (panel->type && (panel->type->flag & PNL_NO_HEADER)) {
1597 return panel->sizey;
1598 }
1599
1600 return PNL_HEADER + panel->sizey;
1601 }
1602
get_panel_real_size_y(const Panel * panel)1603 static int get_panel_real_size_y(const Panel *panel)
1604 {
1605 const int sizey = UI_panel_is_closed(panel) ? 0 : panel->sizey;
1606
1607 if (panel->type && (panel->type->flag & PNL_NO_HEADER)) {
1608 return sizey;
1609 }
1610
1611 return PNL_HEADER + sizey;
1612 }
1613
UI_panel_size_y(const Panel * panel)1614 int UI_panel_size_y(const Panel *panel)
1615 {
1616 return get_panel_real_size_y(panel);
1617 }
1618
1619 /**
1620 * This function is needed because #uiBlock and Panel itself don't
1621 * change #Panel.sizey or location when closed.
1622 */
get_panel_real_ofsy(Panel * panel)1623 static int get_panel_real_ofsy(Panel *panel)
1624 {
1625 if (UI_panel_is_closed(panel)) {
1626 return panel->ofsy + panel->sizey;
1627 }
1628 return panel->ofsy;
1629 }
1630
UI_panel_is_dragging(const struct Panel * panel)1631 bool UI_panel_is_dragging(const struct Panel *panel)
1632 {
1633 uiHandlePanelData *data = panel->activedata;
1634 if (!data) {
1635 return false;
1636 }
1637
1638 return data->is_drag_drop;
1639 }
1640
1641 /**
1642 * \note about sorting:
1643 * The #Panel.sortorder has a lower value for new panels being added.
1644 * however, that only works to insert a single panel, when more new panels get
1645 * added the coordinates of existing panels and the previously stored to-be-inserted
1646 * panels do not match for sorting.
1647 */
1648
find_highest_panel(const void * a,const void * b)1649 static int find_highest_panel(const void *a, const void *b)
1650 {
1651 const Panel *panel_a = ((PanelSort *)a)->panel;
1652 const Panel *panel_b = ((PanelSort *)b)->panel;
1653
1654 /* Stick uppermost header-less panels to the top of the region -
1655 * prevent them from being sorted (multiple header-less panels have to be sorted though). */
1656 if (panel_a->type->flag & PNL_NO_HEADER && panel_b->type->flag & PNL_NO_HEADER) {
1657 /* Pass the no-header checks and check for `ofsy` and #Panel.sortorder below. */
1658 }
1659 else if (panel_a->type->flag & PNL_NO_HEADER) {
1660 return -1;
1661 }
1662 else if (panel_b->type->flag & PNL_NO_HEADER) {
1663 return 1;
1664 }
1665
1666 if (panel_a->ofsy + panel_a->sizey < panel_b->ofsy + panel_b->sizey) {
1667 return 1;
1668 }
1669 if (panel_a->ofsy + panel_a->sizey > panel_b->ofsy + panel_b->sizey) {
1670 return -1;
1671 }
1672 if (panel_a->sortorder > panel_b->sortorder) {
1673 return 1;
1674 }
1675 if (panel_a->sortorder < panel_b->sortorder) {
1676 return -1;
1677 }
1678
1679 return 0;
1680 }
1681
compare_panel(const void * a,const void * b)1682 static int compare_panel(const void *a, const void *b)
1683 {
1684 const Panel *panel_a = ((PanelSort *)a)->panel;
1685 const Panel *panel_b = ((PanelSort *)b)->panel;
1686
1687 if (panel_a->sortorder > panel_b->sortorder) {
1688 return 1;
1689 }
1690 if (panel_a->sortorder < panel_b->sortorder) {
1691 return -1;
1692 }
1693
1694 return 0;
1695 }
1696
align_sub_panels(Panel * panel)1697 static void align_sub_panels(Panel *panel)
1698 {
1699 /* Position sub panels. */
1700 int ofsy = panel->ofsy + panel->sizey - panel->blocksizey;
1701
1702 LISTBASE_FOREACH (Panel *, pachild, &panel->children) {
1703 if (pachild->runtime_flag & PANEL_ACTIVE) {
1704 pachild->ofsx = panel->ofsx;
1705 pachild->ofsy = ofsy - get_panel_size_y(pachild);
1706 ofsy -= get_panel_real_size_y(pachild);
1707
1708 if (pachild->children.first) {
1709 align_sub_panels(pachild);
1710 }
1711 }
1712 }
1713 }
1714
1715 /**
1716 * Calculate the position and order of panels as they are opened, closed, and dragged.
1717 */
uiAlignPanelStep(ARegion * region,const float factor,const bool drag)1718 static bool uiAlignPanelStep(ARegion *region, const float factor, const bool drag)
1719 {
1720 /* Count active panels. */
1721 int active_panels_len = 0;
1722 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
1723 if (panel->runtime_flag & PANEL_ACTIVE) {
1724 /* These panels should have types since they are currently displayed to the user. */
1725 BLI_assert(panel->type != NULL);
1726 active_panels_len++;
1727 }
1728 }
1729 if (active_panels_len == 0) {
1730 return false;
1731 }
1732
1733 /* Sort panels. */
1734 PanelSort *panel_sort = MEM_mallocN(sizeof(PanelSort) * active_panels_len, __func__);
1735 {
1736 PanelSort *ps = panel_sort;
1737 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
1738 if (panel->runtime_flag & PANEL_ACTIVE) {
1739 ps->panel = panel;
1740 ps++;
1741 }
1742 }
1743 }
1744
1745 if (drag) {
1746 /* While dragging, sort based on location and update #Panel.sortorder. */
1747 qsort(panel_sort, active_panels_len, sizeof(PanelSort), find_highest_panel);
1748 for (int i = 0; i < active_panels_len; i++) {
1749 panel_sort[i].panel->sortorder = i;
1750 }
1751 }
1752 else {
1753 /* Otherwise use #Panel.sortorder. */
1754 qsort(panel_sort, active_panels_len, sizeof(PanelSort), compare_panel);
1755 }
1756
1757 /* X offset. */
1758 const int region_offset_x = panel_region_offset_x_get(region);
1759 for (int i = 0; i < active_panels_len; i++) {
1760 PanelSort *ps = &panel_sort[i];
1761 const bool use_box = ps->panel->type->flag & PNL_DRAW_BOX;
1762 ps->panel->runtime.region_ofsx = region_offset_x;
1763 ps->new_offset_x = region_offset_x + ((use_box) ? UI_PANEL_BOX_STYLE_MARGIN : 0);
1764 }
1765
1766 /* Y offset. */
1767 for (int i = 0, y = 0; i < active_panels_len; i++) {
1768 PanelSort *ps = &panel_sort[i];
1769 y -= get_panel_real_size_y(ps->panel);
1770
1771 const bool use_box = ps->panel->type->flag & PNL_DRAW_BOX;
1772 if (use_box) {
1773 y -= UI_PANEL_BOX_STYLE_MARGIN;
1774 }
1775 ps->new_offset_y = y;
1776 /* The header still draws offset by the size of closed panels, so apply the offset here. */
1777 if (UI_panel_is_closed(ps->panel)) {
1778 panel_sort[i].new_offset_y -= ps->panel->sizey;
1779 }
1780 }
1781
1782 /* Interpolate based on the input factor. */
1783 bool changed = false;
1784 for (int i = 0; i < active_panels_len; i++) {
1785 PanelSort *ps = &panel_sort[i];
1786 if (ps->panel->flag & PNL_SELECT) {
1787 continue;
1788 }
1789
1790 if (ps->new_offset_x != ps->panel->ofsx) {
1791 const float x = interpf((float)ps->new_offset_x, (float)ps->panel->ofsx, factor);
1792 ps->panel->ofsx = round_fl_to_int(x);
1793 changed = true;
1794 }
1795 if (ps->new_offset_y != ps->panel->ofsy) {
1796 const float y = interpf((float)ps->new_offset_y, (float)ps->panel->ofsy, factor);
1797 ps->panel->ofsy = round_fl_to_int(y);
1798 changed = true;
1799 }
1800 }
1801
1802 /* Set locations for tabbed and sub panels. */
1803 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
1804 if (panel->runtime_flag & PANEL_ACTIVE) {
1805 if (panel->children.first) {
1806 align_sub_panels(panel);
1807 }
1808 }
1809 }
1810
1811 MEM_freeN(panel_sort);
1812
1813 return changed;
1814 }
1815
ui_panels_size(ARegion * region,int * r_x,int * r_y)1816 static void ui_panels_size(ARegion *region, int *r_x, int *r_y)
1817 {
1818 int sizex = 0;
1819 int sizey = 0;
1820
1821 /* Compute size taken up by panels, for setting in view2d. */
1822 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
1823 if (panel->runtime_flag & PANEL_ACTIVE) {
1824 const int pa_sizex = panel->ofsx + panel->sizex;
1825 const int pa_sizey = get_panel_real_ofsy(panel);
1826
1827 sizex = max_ii(sizex, pa_sizex);
1828 sizey = min_ii(sizey, pa_sizey);
1829 }
1830 }
1831
1832 if (sizex == 0) {
1833 sizex = UI_PANEL_WIDTH;
1834 }
1835 if (sizey == 0) {
1836 sizey = -UI_PANEL_WIDTH;
1837 }
1838
1839 *r_x = sizex;
1840 *r_y = sizey;
1841 }
1842
ui_do_animate(bContext * C,Panel * panel)1843 static void ui_do_animate(bContext *C, Panel *panel)
1844 {
1845 uiHandlePanelData *data = panel->activedata;
1846 ARegion *region = CTX_wm_region(C);
1847
1848 float fac = (PIL_check_seconds_timer() - data->starttime) / ANIMATION_TIME;
1849 fac = min_ff(sqrtf(fac), 1.0f);
1850
1851 /* For max 1 second, interpolate positions. */
1852 if (uiAlignPanelStep(region, fac, false)) {
1853 ED_region_tag_redraw(region);
1854 }
1855 else {
1856 fac = 1.0f;
1857 }
1858
1859 if (fac >= 1.0f) {
1860 /* Store before data is freed. */
1861 const bool is_drag_drop = data->is_drag_drop;
1862
1863 panel_activate_state(C, panel, PANEL_STATE_EXIT);
1864 if (is_drag_drop) {
1865 /* Note: doing this in #panel_activate_state would require removing `const` for context in
1866 * many other places. */
1867 reorder_instanced_panel_list(C, region, panel);
1868 }
1869 return;
1870 }
1871 }
1872
panels_layout_begin_clear_flags(ListBase * lb)1873 static void panels_layout_begin_clear_flags(ListBase *lb)
1874 {
1875 LISTBASE_FOREACH (Panel *, panel, lb) {
1876 /* Flags to copy over to the next layout pass. */
1877 const short flag_copy = PANEL_USE_CLOSED_FROM_SEARCH;
1878
1879 const bool was_active = panel->runtime_flag & PANEL_ACTIVE;
1880 const bool was_closed = UI_panel_is_closed(panel);
1881 panel->runtime_flag &= flag_copy;
1882 SET_FLAG_FROM_TEST(panel->runtime_flag, was_active, PANEL_WAS_ACTIVE);
1883 SET_FLAG_FROM_TEST(panel->runtime_flag, was_closed, PANEL_WAS_CLOSED);
1884
1885 panels_layout_begin_clear_flags(&panel->children);
1886 }
1887 }
1888
UI_panels_begin(const bContext * UNUSED (C),ARegion * region)1889 void UI_panels_begin(const bContext *UNUSED(C), ARegion *region)
1890 {
1891 /* Set all panels as inactive, so that at the end we know which ones were used. Also
1892 * clear other flags so we know later that their values were set for the current redraw. */
1893 panels_layout_begin_clear_flags(®ion->panels);
1894 }
1895
UI_panels_end(const bContext * C,ARegion * region,int * r_x,int * r_y)1896 void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y)
1897 {
1898 ScrArea *area = CTX_wm_area(C);
1899
1900 region_panels_set_expansion_from_list_data(C, region);
1901
1902 const bool region_search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE;
1903
1904 if (properties_space_needs_realign(area, region)) {
1905 region_panels_set_expansion_from_seach_filter(C, region, region_search_filter_active, false);
1906 }
1907 else if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) {
1908 region_panels_set_expansion_from_seach_filter(C, region, region_search_filter_active, true);
1909 }
1910
1911 if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) {
1912 /* Clean up the extra panels and buttons created for searching. */
1913 region_panels_remove_invisible_layouts(region);
1914 }
1915
1916 LISTBASE_FOREACH (Panel *, panel, ®ion->panels) {
1917 if (panel->runtime_flag & PANEL_ACTIVE) {
1918 BLI_assert(panel->runtime.block != NULL);
1919 panel_calculate_size_recursive(region, panel);
1920 }
1921 }
1922
1923 /* Offset contents. */
1924 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
1925 if (block->active && block->panel) {
1926 ui_offset_panel_block(block);
1927 }
1928 }
1929
1930 /* Re-align, possibly with animation. */
1931 Panel *panel;
1932 if (panels_need_realign(area, region, &panel)) {
1933 if (panel) {
1934 panel_activate_state(C, panel, PANEL_STATE_ANIMATION);
1935 }
1936 else {
1937 uiAlignPanelStep(region, 1.0, false);
1938 }
1939 }
1940
1941 /* Compute size taken up by panels. */
1942 ui_panels_size(region, r_x, r_y);
1943 }
1944
1945 /** \} */
1946
1947 /* -------------------------------------------------------------------- */
1948 /** \name Panel Dragging
1949 * \{ */
1950
1951 #define DRAG_REGION_PAD (PNL_HEADER * 0.5)
ui_do_drag(const bContext * C,const wmEvent * event,Panel * panel)1952 static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel)
1953 {
1954 uiHandlePanelData *data = panel->activedata;
1955 ARegion *region = CTX_wm_region(C);
1956
1957 /* Keep the drag position in the region with a small pad to keep the panel visible. */
1958 const int x = clamp_i(event->x, region->winrct.xmin, region->winrct.xmax + DRAG_REGION_PAD);
1959 const int y = clamp_i(event->y, region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD);
1960
1961 float dx = (float)(x - data->startx);
1962 float dy = (float)(y - data->starty);
1963
1964 /* Adjust for region zoom. */
1965 dx *= BLI_rctf_size_x(®ion->v2d.cur) / (float)BLI_rcti_size_x(®ion->winrct);
1966 dy *= BLI_rctf_size_y(®ion->v2d.cur) / (float)BLI_rcti_size_y(®ion->winrct);
1967
1968 if (data->state == PANEL_STATE_DRAG_SCALE) {
1969 panel->sizex = MAX2(data->startsizex + dx, UI_PANEL_MINX);
1970
1971 if (data->startsizey - dy < UI_PANEL_MINY) {
1972 dy = -UI_PANEL_MINY + data->startsizey;
1973 }
1974
1975 panel->sizey = data->startsizey - dy;
1976 panel->ofsy = data->startofsy + dy;
1977 }
1978 else {
1979 /* Reset the panel snapping, to allow dragging away from snapped edges. */
1980 panel->snap = PNL_SNAP_NONE;
1981
1982 /* Add the movement of the view due to edge scrolling while dragging. */
1983 dx += ((float)region->v2d.cur.xmin - data->start_cur_xmin);
1984 dy += ((float)region->v2d.cur.ymin - data->start_cur_ymin);
1985 panel->ofsx = data->startofsx + round_fl_to_int(dx);
1986 panel->ofsy = data->startofsy + round_fl_to_int(dy);
1987
1988 uiAlignPanelStep(region, 0.2f, true);
1989 }
1990
1991 ED_region_tag_redraw(region);
1992 }
1993 #undef DRAG_REGION_PAD
1994
1995 /** \} */
1996
1997 /* -------------------------------------------------------------------- */
1998 /** \name Region Level Panel Interaction
1999 * \{ */
2000
ui_panel_mouse_state_get(const uiBlock * block,const Panel * panel,const int mx,const int my)2001 static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block,
2002 const Panel *panel,
2003 const int mx,
2004 const int my)
2005 {
2006 if (!IN_RANGE((float)mx, block->rect.xmin, block->rect.xmax)) {
2007 return PANEL_MOUSE_OUTSIDE;
2008 }
2009
2010 if (IN_RANGE((float)my, block->rect.ymax, block->rect.ymax + PNL_HEADER)) {
2011 return PANEL_MOUSE_INSIDE_HEADER;
2012 }
2013
2014 if (!UI_panel_is_closed(panel)) {
2015 if (IN_RANGE((float)my, block->rect.ymin, block->rect.ymax + PNL_HEADER)) {
2016 return PANEL_MOUSE_INSIDE_CONTENT;
2017 }
2018 }
2019
2020 return PANEL_MOUSE_OUTSIDE;
2021 }
2022
2023 typedef struct uiPanelDragCollapseHandle {
2024 bool was_first_open;
2025 int xy_init[2];
2026 } uiPanelDragCollapseHandle;
2027
ui_panel_drag_collapse_handler_remove(bContext * UNUSED (C),void * userdata)2028 static void ui_panel_drag_collapse_handler_remove(bContext *UNUSED(C), void *userdata)
2029 {
2030 uiPanelDragCollapseHandle *dragcol_data = userdata;
2031 MEM_freeN(dragcol_data);
2032 }
2033
ui_panel_drag_collapse(const bContext * C,const uiPanelDragCollapseHandle * dragcol_data,const int xy_dst[2])2034 static void ui_panel_drag_collapse(const bContext *C,
2035 const uiPanelDragCollapseHandle *dragcol_data,
2036 const int xy_dst[2])
2037 {
2038 ARegion *region = CTX_wm_region(C);
2039
2040 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
2041 float xy_a_block[2] = {UNPACK2(dragcol_data->xy_init)};
2042 float xy_b_block[2] = {UNPACK2(xy_dst)};
2043 Panel *panel = block->panel;
2044
2045 if (panel == NULL || (panel->type && (panel->type->flag & PNL_NO_HEADER))) {
2046 continue;
2047 }
2048 const int oldflag = panel->flag;
2049
2050 /* Lock axis. */
2051 xy_b_block[0] = dragcol_data->xy_init[0];
2052
2053 /* Use cursor coords in block space. */
2054 ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]);
2055 ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]);
2056
2057 /* Set up `rect` to match header size. */
2058 rctf rect = block->rect;
2059 rect.ymin = rect.ymax;
2060 rect.ymax = rect.ymin + PNL_HEADER;
2061
2062 /* Touch all panels between last mouse coordinate and the current one. */
2063 if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) {
2064 /* Force panel to open or close. */
2065 panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH;
2066 SET_FLAG_FROM_TEST(panel->flag, dragcol_data->was_first_open, PNL_CLOSED);
2067
2068 /* If panel->flag has changed this means a panel was opened/closed here. */
2069 if (panel->flag != oldflag) {
2070 panel_activate_state(C, panel, PANEL_STATE_ANIMATION);
2071 }
2072 }
2073 }
2074 /* Update the instanced panel data expand flags with the changes made here. */
2075 set_panels_list_data_expand_flag(C, region);
2076 }
2077
2078 /**
2079 * Panel drag-collapse (modal handler).
2080 * Clicking and dragging over panels toggles their collapse state based on the panel
2081 * that was first dragged over. If it was open all affected panels including the initial
2082 * one are closed and vice versa.
2083 */
ui_panel_drag_collapse_handler(bContext * C,const wmEvent * event,void * userdata)2084 static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata)
2085 {
2086 wmWindow *win = CTX_wm_window(C);
2087 uiPanelDragCollapseHandle *dragcol_data = userdata;
2088 short retval = WM_UI_HANDLER_CONTINUE;
2089
2090 switch (event->type) {
2091 case MOUSEMOVE:
2092 ui_panel_drag_collapse(C, dragcol_data, &event->x);
2093
2094 retval = WM_UI_HANDLER_BREAK;
2095 break;
2096 case LEFTMOUSE:
2097 if (event->val == KM_RELEASE) {
2098 /* Done! */
2099 WM_event_remove_ui_handler(&win->modalhandlers,
2100 ui_panel_drag_collapse_handler,
2101 ui_panel_drag_collapse_handler_remove,
2102 dragcol_data,
2103 true);
2104 ui_panel_drag_collapse_handler_remove(C, dragcol_data);
2105 }
2106 /* Don't let any left-mouse event fall through! */
2107 retval = WM_UI_HANDLER_BREAK;
2108 break;
2109 }
2110
2111 return retval;
2112 }
2113
ui_panel_drag_collapse_handler_add(const bContext * C,const bool was_open)2114 static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open)
2115 {
2116 wmWindow *win = CTX_wm_window(C);
2117 const wmEvent *event = win->eventstate;
2118 uiPanelDragCollapseHandle *dragcol_data = MEM_mallocN(sizeof(*dragcol_data), __func__);
2119
2120 dragcol_data->was_first_open = was_open;
2121 copy_v2_v2_int(dragcol_data->xy_init, &event->x);
2122
2123 WM_event_add_ui_handler(C,
2124 &win->modalhandlers,
2125 ui_panel_drag_collapse_handler,
2126 ui_panel_drag_collapse_handler_remove,
2127 dragcol_data,
2128 0);
2129 }
2130
2131 /**
2132 * Supposing the block has a panel and isn't a menu, handle opening, closing, pinning, etc.
2133 * Code currently assumes layout style for location of widgets
2134 *
2135 * \param mx: The mouse x coordinate, in panel space.
2136 */
ui_handle_panel_header(const bContext * C,uiBlock * block,const int mx,short int event_type,const short ctrl,const short shift)2137 static void ui_handle_panel_header(const bContext *C,
2138 uiBlock *block,
2139 const int mx,
2140 short int event_type,
2141 const short ctrl,
2142 const short shift)
2143 {
2144 Panel *panel = block->panel;
2145 ARegion *region = CTX_wm_region(C);
2146
2147 BLI_assert(panel->type != NULL);
2148 BLI_assert(!(panel->type->flag & PNL_NO_HEADER));
2149
2150 const bool is_subpanel = (panel->type->parent != NULL);
2151 const bool use_pin = UI_panel_category_is_visible(region) && !is_subpanel;
2152 const bool show_pin = use_pin && (panel->flag & PNL_PIN);
2153 const bool show_drag = !is_subpanel;
2154
2155 /* Handle panel pinning. */
2156 if (use_pin && ELEM(event_type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE) && shift) {
2157 panel->flag ^= PNL_PIN;
2158 ED_region_tag_redraw(region);
2159 return;
2160 }
2161
2162 float expansion_area_xmax = block->rect.xmax;
2163 if (show_drag) {
2164 expansion_area_xmax -= (PNL_ICON * 1.5f);
2165 }
2166 if (show_pin) {
2167 expansion_area_xmax -= PNL_ICON;
2168 }
2169
2170 /* Collapse and expand panels. */
2171 if (ELEM(event_type, EVT_RETKEY, EVT_PADENTER, EVT_AKEY) || mx < expansion_area_xmax) {
2172 if (ctrl && !is_subpanel) {
2173 /* For parent panels, collapse all other panels or toggle children. */
2174 if (UI_panel_is_closed(panel) || BLI_listbase_is_empty(&panel->children)) {
2175 panels_collapse_all(region, panel);
2176
2177 /* Reset the view - we don't want to display a view without content. */
2178 UI_view2d_offset(®ion->v2d, 0.0f, 1.0f);
2179 }
2180 else {
2181 /* If a panel has sub-panels and it's open, toggle the expansion
2182 * of the sub-panels (based on the expansion of the first sub-panel). */
2183 Panel *first_child = panel->children.first;
2184 BLI_assert(first_child != NULL);
2185 panel_set_flag_recursive(panel, PNL_CLOSED, !UI_panel_is_closed(first_child));
2186 panel->flag |= PNL_CLOSED;
2187 }
2188 }
2189
2190 if (UI_panel_is_closed(panel)) {
2191 panel->flag &= ~PNL_CLOSED;
2192 /* Snap back up so full panel aligns with screen edge. */
2193 if (panel->snap & PNL_SNAP_BOTTOM) {
2194 panel->ofsy = 0;
2195 }
2196
2197 if (event_type == LEFTMOUSE) {
2198 ui_panel_drag_collapse_handler_add(C, false);
2199 }
2200 }
2201 else {
2202 /* Snap down to bottom screen edge. */
2203 panel->flag |= PNL_CLOSED;
2204 if (panel->snap & PNL_SNAP_BOTTOM) {
2205 panel->ofsy = -panel->sizey;
2206 }
2207
2208 if (event_type == LEFTMOUSE) {
2209 ui_panel_drag_collapse_handler_add(C, true);
2210 }
2211 }
2212
2213 set_panels_list_data_expand_flag(C, region);
2214 panel_activate_state(C, panel, PANEL_STATE_ANIMATION);
2215 return;
2216 }
2217
2218 /* Handle panel dragging. For now don't allow dragging in floating regions. */
2219 if (show_drag && !(region->alignment == RGN_ALIGN_FLOAT)) {
2220 const float drag_area_xmin = block->rect.xmax - (PNL_ICON * 1.5f);
2221 const float drag_area_xmax = block->rect.xmax;
2222 if (IN_RANGE(mx, drag_area_xmin, drag_area_xmax)) {
2223 panel_activate_state(C, panel, PANEL_STATE_DRAG);
2224 return;
2225 }
2226 }
2227
2228 /* Handle panel unpinning. */
2229 if (show_pin) {
2230 const float pin_area_xmin = expansion_area_xmax;
2231 const float pin_area_xmax = pin_area_xmin + PNL_ICON;
2232 if (IN_RANGE(mx, pin_area_xmin, pin_area_xmax)) {
2233 panel->flag ^= PNL_PIN;
2234 ED_region_tag_redraw(region);
2235 return;
2236 }
2237 }
2238 }
2239
UI_panel_category_is_visible(const ARegion * region)2240 bool UI_panel_category_is_visible(const ARegion *region)
2241 {
2242 /* Check for more than one category. */
2243 return region->panels_category.first &&
2244 region->panels_category.first != region->panels_category.last;
2245 }
2246
UI_panel_category_find(ARegion * region,const char * idname)2247 PanelCategoryDyn *UI_panel_category_find(ARegion *region, const char *idname)
2248 {
2249 return BLI_findstring(®ion->panels_category, idname, offsetof(PanelCategoryDyn, idname));
2250 }
2251
UI_panel_category_active_find(ARegion * region,const char * idname)2252 PanelCategoryStack *UI_panel_category_active_find(ARegion *region, const char *idname)
2253 {
2254 return BLI_findstring(
2255 ®ion->panels_category_active, idname, offsetof(PanelCategoryStack, idname));
2256 }
2257
ui_panel_category_active_set(ARegion * region,const char * idname,bool fallback)2258 static void ui_panel_category_active_set(ARegion *region, const char *idname, bool fallback)
2259 {
2260 ListBase *lb = ®ion->panels_category_active;
2261 PanelCategoryStack *pc_act = UI_panel_category_active_find(region, idname);
2262
2263 if (pc_act) {
2264 BLI_remlink(lb, pc_act);
2265 }
2266 else {
2267 pc_act = MEM_callocN(sizeof(PanelCategoryStack), __func__);
2268 BLI_strncpy(pc_act->idname, idname, sizeof(pc_act->idname));
2269 }
2270
2271 if (fallback) {
2272 /* For fall-backs, add at the end so explicitly chosen categories have priority. */
2273 BLI_addtail(lb, pc_act);
2274 }
2275 else {
2276 BLI_addhead(lb, pc_act);
2277 }
2278
2279 /* Validate all active panels. We could do this on load, they are harmless -
2280 * but we should remove them somewhere.
2281 * (Add-ons could define panels and gather cruft over time). */
2282 {
2283 PanelCategoryStack *pc_act_next;
2284 /* intentionally skip first */
2285 pc_act_next = pc_act->next;
2286 while ((pc_act = pc_act_next)) {
2287 pc_act_next = pc_act->next;
2288 if (!BLI_findstring(
2289 ®ion->type->paneltypes, pc_act->idname, offsetof(PanelType, category))) {
2290 BLI_remlink(lb, pc_act);
2291 MEM_freeN(pc_act);
2292 }
2293 }
2294 }
2295 }
2296
UI_panel_category_active_set(ARegion * region,const char * idname)2297 void UI_panel_category_active_set(ARegion *region, const char *idname)
2298 {
2299 ui_panel_category_active_set(region, idname, false);
2300 }
2301
UI_panel_category_active_set_default(ARegion * region,const char * idname)2302 void UI_panel_category_active_set_default(ARegion *region, const char *idname)
2303 {
2304 if (!UI_panel_category_active_find(region, idname)) {
2305 ui_panel_category_active_set(region, idname, true);
2306 }
2307 }
2308
UI_panel_category_active_get(ARegion * region,bool set_fallback)2309 const char *UI_panel_category_active_get(ARegion *region, bool set_fallback)
2310 {
2311 LISTBASE_FOREACH (PanelCategoryStack *, pc_act, ®ion->panels_category_active) {
2312 if (UI_panel_category_find(region, pc_act->idname)) {
2313 return pc_act->idname;
2314 }
2315 }
2316
2317 if (set_fallback) {
2318 PanelCategoryDyn *pc_dyn = region->panels_category.first;
2319 if (pc_dyn) {
2320 ui_panel_category_active_set(region, pc_dyn->idname, true);
2321 return pc_dyn->idname;
2322 }
2323 }
2324
2325 return NULL;
2326 }
2327
UI_panel_category_find_mouse_over_ex(ARegion * region,const int x,const int y)2328 PanelCategoryDyn *UI_panel_category_find_mouse_over_ex(ARegion *region, const int x, const int y)
2329 {
2330 LISTBASE_FOREACH (PanelCategoryDyn *, ptd, ®ion->panels_category) {
2331 if (BLI_rcti_isect_pt(&ptd->rect, x, y)) {
2332 return ptd;
2333 }
2334 }
2335
2336 return NULL;
2337 }
2338
UI_panel_category_find_mouse_over(ARegion * region,const wmEvent * event)2339 PanelCategoryDyn *UI_panel_category_find_mouse_over(ARegion *region, const wmEvent *event)
2340 {
2341 return UI_panel_category_find_mouse_over_ex(region, event->mval[0], event->mval[1]);
2342 }
2343
UI_panel_category_add(ARegion * region,const char * name)2344 void UI_panel_category_add(ARegion *region, const char *name)
2345 {
2346 PanelCategoryDyn *pc_dyn = MEM_callocN(sizeof(*pc_dyn), __func__);
2347 BLI_addtail(®ion->panels_category, pc_dyn);
2348
2349 BLI_strncpy(pc_dyn->idname, name, sizeof(pc_dyn->idname));
2350
2351 /* 'pc_dyn->rect' must be set on draw. */
2352 }
2353
UI_panel_category_clear_all(ARegion * region)2354 void UI_panel_category_clear_all(ARegion *region)
2355 {
2356 BLI_freelistN(®ion->panels_category);
2357 }
2358
ui_handle_panel_category_cycling(const wmEvent * event,ARegion * region,const uiBut * active_but)2359 static int ui_handle_panel_category_cycling(const wmEvent *event,
2360 ARegion *region,
2361 const uiBut *active_but)
2362 {
2363 const bool is_mousewheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE);
2364 const bool inside_tabregion =
2365 ((RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) ?
2366 (event->mval[0] < ((PanelCategoryDyn *)region->panels_category.first)->rect.xmax) :
2367 (event->mval[0] > ((PanelCategoryDyn *)region->panels_category.first)->rect.xmin));
2368
2369 /* If mouse is inside non-tab region, ctrl key is required. */
2370 if (is_mousewheel && !event->ctrl && !inside_tabregion) {
2371 return WM_UI_HANDLER_CONTINUE;
2372 }
2373
2374 if (active_but && ui_but_supports_cycling(active_but)) {
2375 /* Skip - exception to make cycling buttons using ctrl+mousewheel work in tabbed regions. */
2376 }
2377 else {
2378 const char *category = UI_panel_category_active_get(region, false);
2379 if (LIKELY(category)) {
2380 PanelCategoryDyn *pc_dyn = UI_panel_category_find(region, category);
2381 if (LIKELY(pc_dyn)) {
2382 if (is_mousewheel) {
2383 /* We can probably get rid of this and only allow ctrl-tabbing. */
2384 pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev;
2385 }
2386 else {
2387 const bool backwards = event->shift;
2388 pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next;
2389 if (!pc_dyn) {
2390 /* Proper cyclic behavior, back to first/last category (only used for ctrl+tab). */
2391 pc_dyn = backwards ? region->panels_category.last : region->panels_category.first;
2392 }
2393 }
2394
2395 if (pc_dyn) {
2396 /* Intentionally don't reset scroll in this case,
2397 * allowing for quick browsing between tabs. */
2398 UI_panel_category_active_set(region, pc_dyn->idname);
2399 ED_region_tag_redraw(region);
2400 }
2401 }
2402 }
2403 return WM_UI_HANDLER_BREAK;
2404 }
2405
2406 return WM_UI_HANDLER_CONTINUE;
2407 }
2408
2409 /**
2410 * Handle region panel events like opening and closing panels, changing categories, etc.
2411 *
2412 * \note Could become a modal key-map.
2413 */
ui_handler_panel_region(bContext * C,const wmEvent * event,ARegion * region,const uiBut * active_but)2414 int ui_handler_panel_region(bContext *C,
2415 const wmEvent *event,
2416 ARegion *region,
2417 const uiBut *active_but)
2418 {
2419 /* Mouse-move events are handled by separate handlers for dragging and drag collapsing. */
2420 if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
2421 return WM_UI_HANDLER_CONTINUE;
2422 }
2423
2424 /* We only use KM_PRESS events in this function, so it's simpler to return early. */
2425 if (event->val != KM_PRESS) {
2426 return WM_UI_HANDLER_CONTINUE;
2427 }
2428
2429 /* Scroll-bars can overlap panels now, they have handling priority. */
2430 if (UI_view2d_mouse_in_scrollers(region, ®ion->v2d, event->x, event->y)) {
2431 return WM_UI_HANDLER_CONTINUE;
2432 }
2433
2434 int retval = WM_UI_HANDLER_CONTINUE;
2435
2436 /* Handle category tabs. */
2437 if (UI_panel_category_is_visible(region)) {
2438 if (event->type == LEFTMOUSE) {
2439 PanelCategoryDyn *pc_dyn = UI_panel_category_find_mouse_over(region, event);
2440 if (pc_dyn) {
2441 UI_panel_category_active_set(region, pc_dyn->idname);
2442 ED_region_tag_redraw(region);
2443
2444 /* Reset scroll to the top (T38348). */
2445 UI_view2d_offset(®ion->v2d, -1.0f, 1.0f);
2446
2447 retval = WM_UI_HANDLER_BREAK;
2448 }
2449 }
2450 else if ((event->type == EVT_TABKEY && event->ctrl) ||
2451 ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) {
2452 /* Cycle tabs. */
2453 retval = ui_handle_panel_category_cycling(event, region, active_but);
2454 }
2455 }
2456
2457 if (retval == WM_UI_HANDLER_BREAK) {
2458 return retval;
2459 }
2460
2461 const bool region_has_active_button = (ui_region_find_active_but(region) != NULL);
2462
2463 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
2464 Panel *panel = block->panel;
2465 if (panel == NULL || panel->type == NULL) {
2466 continue;
2467 }
2468 /* We can't expand or collapse panels without headers, they would disappear. */
2469 if (panel->type->flag & PNL_NO_HEADER) {
2470 continue;
2471 }
2472
2473 int mx = event->x;
2474 int my = event->y;
2475 ui_window_to_block(region, block, &mx, &my);
2476
2477 const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my);
2478
2479 if (mouse_state != PANEL_MOUSE_OUTSIDE) {
2480 /* Mark panels that have been interacted with so their expansion
2481 * doesn't reset when property search finishes. */
2482 SET_FLAG_FROM_TEST(panel->flag, UI_panel_is_closed(panel), PNL_CLOSED);
2483 panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH;
2484
2485 /* The panel collapse / expand key "A" is special as it takes priority over
2486 * active button handling. */
2487 if (event->type == EVT_AKEY && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
2488 retval = WM_UI_HANDLER_BREAK;
2489 ui_handle_panel_header(C, block, mx, event->type, event->ctrl, event->shift);
2490 break;
2491 }
2492 }
2493
2494 /* Don't do any other panel handling with an active button. */
2495 if (region_has_active_button) {
2496 continue;
2497 }
2498
2499 /* All mouse clicks inside panels should return in break, but continue handling
2500 * in case there is a sub-panel header at the mouse location. */
2501 if (event->type == LEFTMOUSE &&
2502 ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) {
2503 retval = WM_UI_HANDLER_BREAK;
2504 }
2505
2506 if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) {
2507 if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) {
2508 retval = WM_UI_HANDLER_BREAK;
2509 ui_handle_panel_header(C, block, mx, event->type, event->ctrl, event->shift);
2510 }
2511 else if (event->type == RIGHTMOUSE) {
2512 retval = WM_UI_HANDLER_BREAK;
2513 ui_popup_context_menu_for_panel(C, region, block->panel);
2514 }
2515 break;
2516 }
2517 }
2518
2519 return retval;
2520 }
2521
ui_panel_custom_data_set_recursive(Panel * panel,PointerRNA * custom_data)2522 static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data)
2523 {
2524 panel->runtime.custom_data_ptr = custom_data;
2525
2526 LISTBASE_FOREACH (Panel *, child_panel, &panel->children) {
2527 ui_panel_custom_data_set_recursive(child_panel, custom_data);
2528 }
2529 }
2530
UI_panel_custom_data_set(Panel * panel,PointerRNA * custom_data)2531 void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data)
2532 {
2533 BLI_assert(panel->type != NULL);
2534
2535 /* Free the old custom data, which should be shared among all of the panel's sub-panels. */
2536 if (panel->runtime.custom_data_ptr != NULL) {
2537 MEM_freeN(panel->runtime.custom_data_ptr);
2538 }
2539
2540 ui_panel_custom_data_set_recursive(panel, custom_data);
2541 }
2542
UI_panel_custom_data_get(const Panel * panel)2543 PointerRNA *UI_panel_custom_data_get(const Panel *panel)
2544 {
2545 return panel->runtime.custom_data_ptr;
2546 }
2547
UI_region_panel_custom_data_under_cursor(const bContext * C,const wmEvent * event)2548 PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wmEvent *event)
2549 {
2550 ARegion *region = CTX_wm_region(C);
2551
2552 Panel *panel = NULL;
2553 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
2554 panel = block->panel;
2555 if (panel == NULL) {
2556 continue;
2557 }
2558
2559 int mx = event->x;
2560 int my = event->y;
2561 ui_window_to_block(region, block, &mx, &my);
2562 const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my);
2563 if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) {
2564 break;
2565 }
2566 }
2567
2568 if (panel == NULL) {
2569 return NULL;
2570 }
2571
2572 return UI_panel_custom_data_get(panel);
2573 }
2574
2575 /** \} */
2576
2577 /* -------------------------------------------------------------------- */
2578 /** \name Window Level Modal Panel Interaction
2579 * \{ */
2580
2581 /* Note, this is modal handler and should not swallow events for animation. */
ui_handler_panel(bContext * C,const wmEvent * event,void * userdata)2582 static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata)
2583 {
2584 Panel *panel = userdata;
2585 uiHandlePanelData *data = panel->activedata;
2586
2587 /* Verify if we can stop. */
2588 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
2589 panel_activate_state(C, panel, PANEL_STATE_ANIMATION);
2590 }
2591 else if (event->type == MOUSEMOVE) {
2592 if (data->state == PANEL_STATE_DRAG) {
2593 ui_do_drag(C, event, panel);
2594 }
2595 }
2596 else if (event->type == TIMER && event->customdata == data->animtimer) {
2597 if (data->state == PANEL_STATE_ANIMATION) {
2598 ui_do_animate(C, panel);
2599 }
2600 else if (data->state == PANEL_STATE_DRAG) {
2601 ui_do_drag(C, event, panel);
2602 }
2603 }
2604
2605 data = panel->activedata;
2606
2607 if (data && data->state == PANEL_STATE_ANIMATION) {
2608 return WM_UI_HANDLER_CONTINUE;
2609 }
2610 return WM_UI_HANDLER_BREAK;
2611 }
2612
ui_handler_remove_panel(bContext * C,void * userdata)2613 static void ui_handler_remove_panel(bContext *C, void *userdata)
2614 {
2615 Panel *panel = userdata;
2616
2617 panel_activate_state(C, panel, PANEL_STATE_EXIT);
2618 }
2619
panel_activate_state(const bContext * C,Panel * panel,uiHandlePanelState state)2620 static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelState state)
2621 {
2622 uiHandlePanelData *data = panel->activedata;
2623 wmWindow *win = CTX_wm_window(C);
2624 ARegion *region = CTX_wm_region(C);
2625
2626 if (data && data->state == state) {
2627 return;
2628 }
2629
2630 const bool was_drag_drop = (data && data->state == PANEL_STATE_DRAG);
2631
2632 /* Set selection state for the panel and its sub-panels, which need to know they are selected
2633 * too so they can be drawn above their parent when it's dragged. */
2634 if (state == PANEL_STATE_EXIT || state == PANEL_STATE_ANIMATION) {
2635 panel_set_flag_recursive(panel, PNL_SELECT, false);
2636 }
2637 else {
2638 panel_set_flag_recursive(panel, PNL_SELECT, true);
2639 }
2640
2641 if (data && data->animtimer) {
2642 WM_event_remove_timer(CTX_wm_manager(C), win, data->animtimer);
2643 data->animtimer = NULL;
2644 }
2645
2646 if (state == PANEL_STATE_EXIT) {
2647 MEM_freeN(data);
2648 panel->activedata = NULL;
2649
2650 WM_event_remove_ui_handler(
2651 &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, false);
2652 }
2653 else {
2654 if (!data) {
2655 data = MEM_callocN(sizeof(uiHandlePanelData), "uiHandlePanelData");
2656 panel->activedata = data;
2657
2658 WM_event_add_ui_handler(
2659 C, &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, 0);
2660 }
2661
2662 if (ELEM(state, PANEL_STATE_ANIMATION, PANEL_STATE_DRAG)) {
2663 data->animtimer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, ANIMATION_INTERVAL);
2664 }
2665
2666 /* Initiate edge panning during drags so we can move beyond the initial region view. */
2667 if (state == PANEL_STATE_DRAG) {
2668 wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true);
2669 ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT, true);
2670 }
2671
2672 data->state = state;
2673 data->startx = win->eventstate->x;
2674 data->starty = win->eventstate->y;
2675 data->startofsx = panel->ofsx;
2676 data->startofsy = panel->ofsy;
2677 data->startsizex = panel->sizex;
2678 data->startsizey = panel->sizey;
2679 data->start_cur_xmin = region->v2d.cur.xmin;
2680 data->start_cur_ymin = region->v2d.cur.ymin;
2681 data->starttime = PIL_check_seconds_timer();
2682
2683 /* Remember drag drop state even when animating to the aligned position after dragging. */
2684 data->is_drag_drop = was_drag_drop;
2685 if (state == PANEL_STATE_DRAG) {
2686 data->is_drag_drop = true;
2687 }
2688 }
2689
2690 ED_region_tag_redraw(region);
2691 }
2692
UI_paneltype_find(int space_id,int region_id,const char * idname)2693 PanelType *UI_paneltype_find(int space_id, int region_id, const char *idname)
2694 {
2695 SpaceType *st = BKE_spacetype_from_id(space_id);
2696 if (st) {
2697 ARegionType *art = BKE_regiontype_from_id(st, region_id);
2698 if (art) {
2699 return BLI_findstring(&art->paneltypes, idname, offsetof(PanelType, idname));
2700 }
2701 }
2702 return NULL;
2703 }
2704
2705 /** \} */
2706