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(&region->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       &region_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, &region->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, &region->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, &region->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, &region->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, &region->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, &region->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, &region->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, &region->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, &region->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, &region->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, &region->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, &region->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 = &region->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, &region->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, &region->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, &region->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, &region->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, &region->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, &region->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, &region->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(&region->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, &region->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, &region->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(&region->v2d.cur) / (float)BLI_rcti_size_x(&region->winrct);
1966   dy *= BLI_rctf_size_y(&region->v2d.cur) / (float)BLI_rcti_size_y(&region->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, &region->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(&region->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(&region->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       &region->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 = &region->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               &region->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, &region->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, &region->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(&region->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(&region->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, &region->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(&region->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, &region->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, &region->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