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 
17 /** \file
18  * \ingroup edinterface
19  */
20 
21 #include <limits.h>
22 #include <math.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "MEM_guardedalloc.h"
27 
28 #include "DNA_armature_types.h"
29 #include "DNA_screen_types.h"
30 #include "DNA_userdef_types.h"
31 
32 #include "BLI_alloca.h"
33 #include "BLI_dynstr.h"
34 #include "BLI_listbase.h"
35 #include "BLI_math.h"
36 #include "BLI_rect.h"
37 #include "BLI_string.h"
38 #include "BLI_utildefines.h"
39 
40 #include "BLT_translation.h"
41 
42 #include "BKE_anim_data.h"
43 #include "BKE_armature.h"
44 #include "BKE_context.h"
45 #include "BKE_global.h"
46 #include "BKE_idprop.h"
47 #include "BKE_screen.h"
48 
49 #include "RNA_access.h"
50 
51 #include "UI_interface.h"
52 
53 #include "WM_api.h"
54 #include "WM_types.h"
55 
56 #include "interface_intern.h"
57 
58 /* Show an icon button after each RNA button to use to quickly set keyframes,
59  * this is a way to display animation/driven/override status, see T54951. */
60 #define UI_PROP_DECORATE
61 /* Alternate draw mode where some buttons can use single icon width,
62  * giving more room for the text at the expense of nicely aligned text. */
63 #define UI_PROP_SEP_ICON_WIDTH_EXCEPTION
64 
65 /* -------------------------------------------------------------------- */
66 /** \name Structs and Defines
67  * \{ */
68 
69 #define UI_OPERATOR_ERROR_RET(_ot, _opname, return_statement) \
70   if (ot == NULL) { \
71     ui_item_disabled(layout, _opname); \
72     RNA_warning("'%s' unknown operator", _opname); \
73     return_statement; \
74   } \
75   (void)0
76 
77 #define UI_ITEM_PROP_SEP_DIVIDE 0.4f
78 
79 /* uiLayoutRoot */
80 
81 typedef struct uiLayoutRoot {
82   struct uiLayoutRoot *next, *prev;
83 
84   int type;
85   int opcontext;
86 
87   int emw, emh;
88   int padding;
89 
90   uiMenuHandleFunc handlefunc;
91   void *argv;
92 
93   const uiStyle *style;
94   uiBlock *block;
95   uiLayout *layout;
96 } uiLayoutRoot;
97 
98 /* Item */
99 
100 typedef enum uiItemType {
101   ITEM_BUTTON,
102 
103   ITEM_LAYOUT_ROW,
104   ITEM_LAYOUT_COLUMN,
105   ITEM_LAYOUT_COLUMN_FLOW,
106   ITEM_LAYOUT_ROW_FLOW,
107   ITEM_LAYOUT_GRID_FLOW,
108   ITEM_LAYOUT_BOX,
109   ITEM_LAYOUT_ABSOLUTE,
110   ITEM_LAYOUT_SPLIT,
111   ITEM_LAYOUT_OVERLAP,
112   ITEM_LAYOUT_RADIAL,
113 
114   ITEM_LAYOUT_ROOT
115 #if 0
116       TEMPLATE_COLUMN_FLOW,
117   TEMPLATE_SPLIT,
118   TEMPLATE_BOX,
119 
120   TEMPLATE_HEADER,
121   TEMPLATE_HEADER_ID,
122 #endif
123 } uiItemType;
124 
125 typedef struct uiItem {
126   void *next, *prev;
127   uiItemType type;
128   int flag;
129 } uiItem;
130 
131 enum {
132   UI_ITEM_AUTO_FIXED_SIZE = 1 << 0,
133   UI_ITEM_FIXED_SIZE = 1 << 1,
134 
135   UI_ITEM_BOX_ITEM = 1 << 2, /* The item is "inside" a box item */
136   UI_ITEM_PROP_SEP = 1 << 3,
137   UI_ITEM_INSIDE_PROP_SEP = 1 << 4,
138   /* Show an icon button next to each property (to set keyframes, show status).
139    * Enabled by default, depends on 'UI_ITEM_PROP_SEP'. */
140   UI_ITEM_PROP_DECORATE = 1 << 5,
141   UI_ITEM_PROP_DECORATE_NO_PAD = 1 << 6,
142 };
143 
144 typedef struct uiButtonItem {
145   uiItem item;
146   uiBut *but;
147 } uiButtonItem;
148 
149 struct uiLayout {
150   uiItem item;
151 
152   uiLayoutRoot *root;
153   bContextStore *context;
154   uiLayout *parent;
155   ListBase items;
156 
157   char heading[UI_MAX_NAME_STR];
158 
159   /** Sub layout to add child items, if not the layout itself. */
160   uiLayout *child_items_layout;
161 
162   int x, y, w, h;
163   float scale[2];
164   short space;
165   bool align;
166   bool active;
167   bool active_default;
168   bool activate_init;
169   bool enabled;
170   bool redalert;
171   bool keepaspect;
172   /** For layouts inside gridflow, they and their items shall never have a fixed maximal size. */
173   bool variable_size;
174   char alignment;
175   char emboss;
176   /** for fixed width or height to avoid UI size changes */
177   float units[2];
178 };
179 
180 typedef struct uiLayoutItemFlow {
181   uiLayout litem;
182   int number;
183   int totcol;
184 } uiLayoutItemFlow;
185 
186 typedef struct uiLayoutItemGridFlow {
187   uiLayout litem;
188 
189   /* Extra parameters */
190   bool row_major;    /* Fill first row first, instead of filling first column first. */
191   bool even_columns; /* Same width for all columns. */
192   bool even_rows;    /* Same height for all rows. */
193   /**
194    * - If positive, absolute fixed number of columns.
195    * - If 0, fully automatic (based on available width).
196    * - If negative, automatic but only generates number of columns/rows
197    *   multiple of given (absolute) value.
198    */
199   int columns_len;
200 
201   /* Pure internal runtime storage. */
202   int tot_items, tot_columns, tot_rows;
203 } uiLayoutItemGridFlow;
204 
205 typedef struct uiLayoutItemBx {
206   uiLayout litem;
207   uiBut *roundbox;
208 } uiLayoutItemBx;
209 
210 typedef struct uiLayoutItemSplit {
211   uiLayout litem;
212   float percentage;
213 } uiLayoutItemSplit;
214 
215 typedef struct uiLayoutItemRoot {
216   uiLayout litem;
217 } uiLayoutItemRoot;
218 
219 /** \} */
220 
221 /* -------------------------------------------------------------------- */
222 /** \name Item
223  * \{ */
224 
ui_item_name_add_colon(const char * name,char namestr[UI_MAX_NAME_STR])225 static const char *ui_item_name_add_colon(const char *name, char namestr[UI_MAX_NAME_STR])
226 {
227   const int len = strlen(name);
228 
229   if (len != 0 && len + 1 < UI_MAX_NAME_STR) {
230     memcpy(namestr, name, len);
231     namestr[len] = ':';
232     namestr[len + 1] = '\0';
233     return namestr;
234   }
235 
236   return name;
237 }
238 
ui_item_fit(int item,int pos,int all,int available,bool is_last,int alignment,float * extra_pixel)239 static int ui_item_fit(
240     int item, int pos, int all, int available, bool is_last, int alignment, float *extra_pixel)
241 {
242   /* available == 0 is unlimited */
243   if (ELEM(0, available, all)) {
244     return item;
245   }
246 
247   if (all > available) {
248     /* contents is bigger than available space */
249     if (is_last) {
250       return available - pos;
251     }
252 
253     const float width = *extra_pixel + (item * available) / (float)all;
254     *extra_pixel = width - (int)width;
255     return (int)width;
256   }
257 
258   /* contents is smaller or equal to available space */
259   if (alignment == UI_LAYOUT_ALIGN_EXPAND) {
260     if (is_last) {
261       return available - pos;
262     }
263 
264     const float width = *extra_pixel + (item * available) / (float)all;
265     *extra_pixel = width - (int)width;
266     return (int)width;
267   }
268   return item;
269 }
270 
271 /* variable button size in which direction? */
272 #define UI_ITEM_VARY_X 1
273 #define UI_ITEM_VARY_Y 2
274 
ui_layout_vary_direction(uiLayout * layout)275 static int ui_layout_vary_direction(uiLayout *layout)
276 {
277   return ((ELEM(layout->root->type, UI_LAYOUT_HEADER, UI_LAYOUT_PIEMENU) ||
278            (layout->alignment != UI_LAYOUT_ALIGN_EXPAND)) ?
279               UI_ITEM_VARY_X :
280               UI_ITEM_VARY_Y);
281 }
282 
ui_layout_variable_size(uiLayout * layout)283 static bool ui_layout_variable_size(uiLayout *layout)
284 {
285   /* Note that this code is probably a bit flakey, we'd probably want to know whether it's
286    * variable in X and/or Y, etc. But for now it mimics previous one,
287    * with addition of variable flag set for children of grid-flow layouts. */
288   return ui_layout_vary_direction(layout) == UI_ITEM_VARY_X || layout->variable_size;
289 }
290 
291 /* estimated size of text + icon */
ui_text_icon_width(uiLayout * layout,const char * name,int icon,bool compact)292 static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact)
293 {
294   const int unit_x = UI_UNIT_X * (layout->scale[0] ? layout->scale[0] : 1.0f);
295 
296   if (icon && !name[0]) {
297     return unit_x; /* icon only */
298   }
299 
300   if (ui_layout_variable_size(layout)) {
301     if (!icon && !name[0]) {
302       return unit_x; /* No icon or name. */
303     }
304     if (layout->alignment != UI_LAYOUT_ALIGN_EXPAND) {
305       layout->item.flag |= UI_ITEM_FIXED_SIZE;
306     }
307     const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
308     float margin = compact ? 1.25 : 1.50;
309     if (icon) {
310       /* It may seem odd that the icon only adds (unit_x / 4)
311        * but taking margins into account its fine, except
312        * in compact mode a bit more margin is required. */
313       margin += compact ? 0.35 : 0.25;
314     }
315     return UI_fontstyle_string_width(fstyle, name) + (unit_x * margin);
316   }
317   return unit_x * 10;
318 }
319 
ui_item_size(uiItem * item,int * r_w,int * r_h)320 static void ui_item_size(uiItem *item, int *r_w, int *r_h)
321 {
322   if (item->type == ITEM_BUTTON) {
323     uiButtonItem *bitem = (uiButtonItem *)item;
324 
325     if (r_w) {
326       *r_w = BLI_rctf_size_x(&bitem->but->rect);
327     }
328     if (r_h) {
329       *r_h = BLI_rctf_size_y(&bitem->but->rect);
330     }
331   }
332   else {
333     uiLayout *litem = (uiLayout *)item;
334 
335     if (r_w) {
336       *r_w = litem->w;
337     }
338     if (r_h) {
339       *r_h = litem->h;
340     }
341   }
342 }
343 
ui_item_offset(uiItem * item,int * r_x,int * r_y)344 static void ui_item_offset(uiItem *item, int *r_x, int *r_y)
345 {
346   if (item->type == ITEM_BUTTON) {
347     uiButtonItem *bitem = (uiButtonItem *)item;
348 
349     if (r_x) {
350       *r_x = bitem->but->rect.xmin;
351     }
352     if (r_y) {
353       *r_y = bitem->but->rect.ymin;
354     }
355   }
356   else {
357     if (r_x) {
358       *r_x = 0;
359     }
360     if (r_y) {
361       *r_y = 0;
362     }
363   }
364 }
365 
ui_item_position(uiItem * item,int x,int y,int w,int h)366 static void ui_item_position(uiItem *item, int x, int y, int w, int h)
367 {
368   if (item->type == ITEM_BUTTON) {
369     uiButtonItem *bitem = (uiButtonItem *)item;
370 
371     bitem->but->rect.xmin = x;
372     bitem->but->rect.ymin = y;
373     bitem->but->rect.xmax = x + w;
374     bitem->but->rect.ymax = y + h;
375 
376     ui_but_update(bitem->but); /* for strlen */
377   }
378   else {
379     uiLayout *litem = (uiLayout *)item;
380 
381     litem->x = x;
382     litem->y = y + h;
383     litem->w = w;
384     litem->h = h;
385   }
386 }
387 
ui_item_move(uiItem * item,int delta_xmin,int delta_xmax)388 static void ui_item_move(uiItem *item, int delta_xmin, int delta_xmax)
389 {
390   if (item->type == ITEM_BUTTON) {
391     uiButtonItem *bitem = (uiButtonItem *)item;
392 
393     bitem->but->rect.xmin += delta_xmin;
394     bitem->but->rect.xmax += delta_xmax;
395 
396     ui_but_update(bitem->but); /* for strlen */
397   }
398   else {
399     uiLayout *litem = (uiLayout *)item;
400 
401     if (delta_xmin > 0) {
402       litem->x += delta_xmin;
403     }
404     else {
405       litem->w += delta_xmax;
406     }
407   }
408 }
409 
410 /** \} */
411 
412 /* -------------------------------------------------------------------- */
413 /** \name Special RNA Items
414  * \{ */
415 
uiLayoutGetLocalDir(const uiLayout * layout)416 int uiLayoutGetLocalDir(const uiLayout *layout)
417 {
418   switch (layout->item.type) {
419     case ITEM_LAYOUT_ROW:
420     case ITEM_LAYOUT_ROOT:
421     case ITEM_LAYOUT_OVERLAP:
422       return UI_LAYOUT_HORIZONTAL;
423     case ITEM_LAYOUT_COLUMN:
424     case ITEM_LAYOUT_COLUMN_FLOW:
425     case ITEM_LAYOUT_GRID_FLOW:
426     case ITEM_LAYOUT_SPLIT:
427     case ITEM_LAYOUT_ABSOLUTE:
428     case ITEM_LAYOUT_BOX:
429     default:
430       return UI_LAYOUT_VERTICAL;
431   }
432 }
433 
ui_item_local_sublayout(uiLayout * test,uiLayout * layout,bool align)434 static uiLayout *ui_item_local_sublayout(uiLayout *test, uiLayout *layout, bool align)
435 {
436   uiLayout *sub;
437   if (uiLayoutGetLocalDir(test) == UI_LAYOUT_HORIZONTAL) {
438     sub = uiLayoutRow(layout, align);
439   }
440   else {
441     sub = uiLayoutColumn(layout, align);
442   }
443 
444   sub->space = 0;
445   return sub;
446 }
447 
ui_layer_but_cb(bContext * C,void * arg_but,void * arg_index)448 static void ui_layer_but_cb(bContext *C, void *arg_but, void *arg_index)
449 {
450   wmWindow *win = CTX_wm_window(C);
451   uiBut *but = arg_but;
452   PointerRNA *ptr = &but->rnapoin;
453   PropertyRNA *prop = but->rnaprop;
454   int index = POINTER_AS_INT(arg_index);
455   const int shift = win->eventstate->shift;
456   const int len = RNA_property_array_length(ptr, prop);
457 
458   if (!shift) {
459     RNA_property_boolean_set_index(ptr, prop, index, true);
460 
461     for (int i = 0; i < len; i++) {
462       if (i != index) {
463         RNA_property_boolean_set_index(ptr, prop, i, 0);
464       }
465     }
466 
467     RNA_property_update(C, ptr, prop);
468 
469     LISTBASE_FOREACH (uiBut *, cbut, &but->block->buttons) {
470       ui_but_update(cbut);
471     }
472   }
473 }
474 
475 /* create buttons for an item with an RNA array */
ui_item_array(uiLayout * layout,uiBlock * block,const char * name,int icon,PointerRNA * ptr,PropertyRNA * prop,int len,int x,int y,int w,int UNUSED (h),bool expand,bool slider,int toggle,bool icon_only,bool compact,bool show_text)476 static void ui_item_array(uiLayout *layout,
477                           uiBlock *block,
478                           const char *name,
479                           int icon,
480                           PointerRNA *ptr,
481                           PropertyRNA *prop,
482                           int len,
483                           int x,
484                           int y,
485                           int w,
486                           int UNUSED(h),
487                           bool expand,
488                           bool slider,
489                           int toggle,
490                           bool icon_only,
491                           bool compact,
492                           bool show_text)
493 {
494   const uiStyle *style = layout->root->style;
495 
496   /* retrieve type and subtype */
497   const PropertyType type = RNA_property_type(prop);
498   const PropertySubType subtype = RNA_property_subtype(prop);
499 
500   uiLayout *sub = ui_item_local_sublayout(layout, layout, 1);
501   UI_block_layout_set_current(block, sub);
502 
503   /* create label */
504   if (name[0] && show_text) {
505     uiDefBut(block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
506   }
507 
508   /* create buttons */
509   if (type == PROP_BOOLEAN && ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER)) {
510     /* special check for layer layout */
511     const int cols = (len >= 20) ? 2 : 1;
512     const int colbuts = len / (2 * cols);
513     uint layer_used = 0;
514     uint layer_active = 0;
515 
516     UI_block_layout_set_current(block, uiLayoutAbsolute(layout, false));
517 
518     const int butw = UI_UNIT_X * 0.75;
519     const int buth = UI_UNIT_X * 0.75;
520 
521     if (ptr->type == &RNA_Armature) {
522       bArmature *arm = ptr->data;
523 
524       layer_used = arm->layer_used;
525 
526       if (arm->edbo) {
527         if (arm->act_edbone) {
528           layer_active |= arm->act_edbone->layer;
529         }
530       }
531       else {
532         if (arm->act_bone) {
533           layer_active |= arm->act_bone->layer;
534         }
535       }
536     }
537 
538     for (int b = 0; b < cols; b++) {
539       UI_block_align_begin(block);
540 
541       for (int a = 0; a < colbuts; a++) {
542         const int layer_num = a + b * colbuts;
543         const uint layer_flag = (1u << layer_num);
544 
545         if (layer_used & layer_flag) {
546           if (layer_active & layer_flag) {
547             icon = ICON_LAYER_ACTIVE;
548           }
549           else {
550             icon = ICON_LAYER_USED;
551           }
552         }
553         else {
554           icon = ICON_BLANK1;
555         }
556 
557         uiBut *but = uiDefAutoButR(
558             block, ptr, prop, layer_num, "", icon, x + butw * a, y + buth, butw, buth);
559         if (subtype == PROP_LAYER_MEMBER) {
560           UI_but_func_set(but, ui_layer_but_cb, but, POINTER_FROM_INT(layer_num));
561         }
562       }
563       for (int a = 0; a < colbuts; a++) {
564         const int layer_num = a + len / 2 + b * colbuts;
565         const uint layer_flag = (1u << layer_num);
566 
567         if (layer_used & layer_flag) {
568           if (layer_active & layer_flag) {
569             icon = ICON_LAYER_ACTIVE;
570           }
571           else {
572             icon = ICON_LAYER_USED;
573           }
574         }
575         else {
576           icon = ICON_BLANK1;
577         }
578 
579         uiBut *but = uiDefAutoButR(
580             block, ptr, prop, layer_num, "", icon, x + butw * a, y, butw, buth);
581         if (subtype == PROP_LAYER_MEMBER) {
582           UI_but_func_set(but, ui_layer_but_cb, but, POINTER_FROM_INT(layer_num));
583         }
584       }
585       UI_block_align_end(block);
586 
587       x += colbuts * butw + style->buttonspacex;
588     }
589   }
590   else if (subtype == PROP_MATRIX) {
591     int totdim, dim_size[3]; /* 3 == RNA_MAX_ARRAY_DIMENSION */
592     int row, col;
593 
594     UI_block_layout_set_current(block, uiLayoutAbsolute(layout, true));
595 
596     totdim = RNA_property_array_dimension(ptr, prop, dim_size);
597     if (totdim != 2) {
598       /* Only 2D matrices supported in UI so far. */
599       return;
600     }
601 
602     w /= dim_size[0];
603     /* h /= dim_size[1]; */ /* UNUSED */
604 
605     for (int a = 0; a < len; a++) {
606       col = a % dim_size[0];
607       row = a / dim_size[0];
608 
609       uiBut *but = uiDefAutoButR(block,
610                                  ptr,
611                                  prop,
612                                  a,
613                                  "",
614                                  ICON_NONE,
615                                  x + w * col,
616                                  y + (dim_size[1] * UI_UNIT_Y) - (row * UI_UNIT_Y),
617                                  w,
618                                  UI_UNIT_Y);
619       if (slider && but->type == UI_BTYPE_NUM) {
620         uiButNumber *number_but = (uiButNumber *)but;
621 
622         but->a1 = number_but->step_size;
623         but = ui_but_change_type(but, UI_BTYPE_NUM_SLIDER);
624       }
625     }
626   }
627   else if (subtype == PROP_DIRECTION && !expand) {
628     uiDefButR_prop(block,
629                    UI_BTYPE_UNITVEC,
630                    0,
631                    name,
632                    x,
633                    y,
634                    UI_UNIT_X * 3,
635                    UI_UNIT_Y * 3,
636                    ptr,
637                    prop,
638                    -1,
639                    0,
640                    0,
641                    -1,
642                    -1,
643                    NULL);
644   }
645   else {
646     /* note, this block of code is a bit arbitrary and has just been made
647      * to work with common cases, but may need to be re-worked */
648 
649     /* special case, boolean array in a menu, this could be used in a more generic way too */
650     if (ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA) && !expand && ELEM(len, 3, 4)) {
651       uiDefAutoButR(block, ptr, prop, -1, "", ICON_NONE, 0, 0, w, UI_UNIT_Y);
652     }
653     else {
654       /* even if 'expand' is fale, expanding anyway */
655 
656       /* layout for known array subtypes */
657       char str[3] = {'\0'};
658 
659       if (!icon_only && show_text) {
660         if (type != PROP_BOOLEAN) {
661           str[1] = ':';
662         }
663       }
664 
665       /* show checkboxes for rna on a non-emboss block (menu for eg) */
666       bool *boolarr = NULL;
667       if (type == PROP_BOOLEAN &&
668           ELEM(layout->root->block->emboss, UI_EMBOSS_NONE, UI_EMBOSS_PULLDOWN)) {
669         boolarr = MEM_callocN(sizeof(bool) * len, __func__);
670         RNA_property_boolean_get_array(ptr, prop, boolarr);
671       }
672 
673       const char *str_buf = show_text ? str : "";
674       for (int a = 0; a < len; a++) {
675         if (!icon_only && show_text) {
676           str[0] = RNA_property_array_item_char(prop, a);
677         }
678         if (boolarr) {
679           icon = boolarr[a] ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
680         }
681 
682         const int width_item = ((compact && type == PROP_BOOLEAN) ?
683                                     min_ii(w, ui_text_icon_width(layout, str_buf, icon, false)) :
684                                     w);
685 
686         uiBut *but = uiDefAutoButR(
687             block, ptr, prop, a, str_buf, icon, 0, 0, width_item, UI_UNIT_Y);
688         if (slider && but->type == UI_BTYPE_NUM) {
689           uiButNumber *number_but = (uiButNumber *)but;
690 
691           but->a1 = number_but->step_size;
692           but = ui_but_change_type(but, UI_BTYPE_NUM_SLIDER);
693         }
694         if ((toggle == 1) && but->type == UI_BTYPE_CHECKBOX) {
695           but->type = UI_BTYPE_TOGGLE;
696         }
697         if ((a == 0) && (subtype == PROP_AXISANGLE)) {
698           UI_but_unit_type_set(but, PROP_UNIT_ROTATION);
699         }
700       }
701 
702       if (boolarr) {
703         MEM_freeN(boolarr);
704       }
705     }
706   }
707 
708   UI_block_layout_set_current(block, layout);
709 }
710 
ui_item_enum_expand_handle(bContext * C,void * arg1,void * arg2)711 static void ui_item_enum_expand_handle(bContext *C, void *arg1, void *arg2)
712 {
713   wmWindow *win = CTX_wm_window(C);
714 
715   if (!win->eventstate->shift) {
716     uiBut *but = (uiBut *)arg1;
717     const int enum_value = POINTER_AS_INT(arg2);
718 
719     int current_value = RNA_property_enum_get(&but->rnapoin, but->rnaprop);
720     if (!(current_value & enum_value)) {
721       current_value = enum_value;
722     }
723     else {
724       current_value &= enum_value;
725     }
726     RNA_property_enum_set(&but->rnapoin, but->rnaprop, current_value);
727   }
728 }
729 
730 /**
731  * Draw a single enum button, a utility for #ui_item_enum_expand_exec
732  */
ui_item_enum_expand_elem_exec(uiLayout * layout,uiBlock * block,PointerRNA * ptr,PropertyRNA * prop,const char * uiname,const int h,const eButType but_type,const bool icon_only,const EnumPropertyItem * item,const bool is_first)733 static void ui_item_enum_expand_elem_exec(uiLayout *layout,
734                                           uiBlock *block,
735                                           PointerRNA *ptr,
736                                           PropertyRNA *prop,
737                                           const char *uiname,
738                                           const int h,
739                                           const eButType but_type,
740                                           const bool icon_only,
741                                           const EnumPropertyItem *item,
742                                           const bool is_first)
743 {
744   const char *name = (!uiname || uiname[0]) ? item->name : "";
745   const int icon = item->icon;
746   const int value = item->value;
747   const int itemw = ui_text_icon_width(block->curlayout, icon_only ? "" : name, icon, 0);
748 
749   uiBut *but;
750   if (icon && name[0] && !icon_only) {
751     but = uiDefIconTextButR_prop(
752         block, but_type, 0, icon, name, 0, 0, itemw, h, ptr, prop, -1, 0, value, -1, -1, NULL);
753   }
754   else if (icon) {
755     const int w = (is_first) ? itemw : ceilf(itemw - U.pixelsize);
756     but = uiDefIconButR_prop(
757         block, but_type, 0, icon, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL);
758   }
759   else {
760     but = uiDefButR_prop(
761         block, but_type, 0, name, 0, 0, itemw, h, ptr, prop, -1, 0, value, -1, -1, NULL);
762   }
763 
764   if (RNA_property_flag(prop) & PROP_ENUM_FLAG) {
765     /* If this is set, assert since we're clobbering someone elses callback. */
766     /* Buttons get their block's func by default, so we cannot assert in that case either. */
767     BLI_assert(ELEM(but->func, NULL, block->func));
768     UI_but_func_set(but, ui_item_enum_expand_handle, but, POINTER_FROM_INT(value));
769   }
770 
771   if (uiLayoutGetLocalDir(layout) != UI_LAYOUT_HORIZONTAL) {
772     but->drawflag |= UI_BUT_TEXT_LEFT;
773   }
774 
775   /* Allow quick, inaccurate swipe motions to switch tabs
776    * (no need to keep cursor over them). */
777   if (but_type == UI_BTYPE_TAB) {
778     but->flag |= UI_BUT_DRAG_LOCK;
779   }
780 }
781 
ui_item_enum_expand_exec(uiLayout * layout,uiBlock * block,PointerRNA * ptr,PropertyRNA * prop,const char * uiname,const int h,const eButType but_type,const bool icon_only)782 static void ui_item_enum_expand_exec(uiLayout *layout,
783                                      uiBlock *block,
784                                      PointerRNA *ptr,
785                                      PropertyRNA *prop,
786                                      const char *uiname,
787                                      const int h,
788                                      const eButType but_type,
789                                      const bool icon_only)
790 {
791   /* XXX: The way this function currently handles uiname parameter
792    * is insane and inconsistent with general UI API:
793    *
794    * - uiname is the *enum property* label.
795    * - when it is NULL or empty, we do not draw *enum items* labels,
796    *   this doubles the icon_only parameter.
797    * - we *never* draw (i.e. really use) the enum label uiname, it is just used as a mere flag!
798    *
799    * Unfortunately, fixing this implies an API "soft break", so better to defer it for later... :/
800    * - mont29
801    */
802 
803   BLI_assert(RNA_property_type(prop) == PROP_ENUM);
804 
805   const bool radial = (layout->root->type == UI_LAYOUT_PIEMENU);
806 
807   bool free;
808   const EnumPropertyItem *item_array;
809   if (radial) {
810     RNA_property_enum_items_gettexted_all(block->evil_C, ptr, prop, &item_array, NULL, &free);
811   }
812   else {
813     RNA_property_enum_items_gettexted(block->evil_C, ptr, prop, &item_array, NULL, &free);
814   }
815 
816   /* we dont want nested rows, cols in menus */
817   uiLayout *layout_radial = NULL;
818   if (radial) {
819     if (layout->root->layout == layout) {
820       layout_radial = uiLayoutRadial(layout);
821       UI_block_layout_set_current(block, layout_radial);
822     }
823     else {
824       if (layout->item.type == ITEM_LAYOUT_RADIAL) {
825         layout_radial = layout;
826       }
827       UI_block_layout_set_current(block, layout);
828     }
829   }
830   else if (ELEM(layout->item.type, ITEM_LAYOUT_GRID_FLOW, ITEM_LAYOUT_COLUMN_FLOW) ||
831            layout->root->type == UI_LAYOUT_MENU) {
832     UI_block_layout_set_current(block, layout);
833   }
834   else {
835     UI_block_layout_set_current(block, ui_item_local_sublayout(layout, layout, 1));
836   }
837 
838   for (const EnumPropertyItem *item = item_array; item->identifier; item++) {
839     const bool is_first = item == item_array;
840 
841     if (!item->identifier[0]) {
842       const EnumPropertyItem *next_item = item + 1;
843 
844       /* Separate items, potentially with a label. */
845       if (next_item->identifier) {
846         /* Item without identifier but with name:
847          * Add group label for the following items. */
848         if (item->name) {
849           if (!is_first) {
850             uiItemS(block->curlayout);
851           }
852           uiItemL(block->curlayout, item->name, item->icon);
853         }
854         else if (radial && layout_radial) {
855           uiItemS(layout_radial);
856         }
857         else {
858           uiItemS(block->curlayout);
859         }
860       }
861       continue;
862     }
863 
864     ui_item_enum_expand_elem_exec(
865         layout, block, ptr, prop, uiname, h, but_type, icon_only, item, is_first);
866   }
867 
868   UI_block_layout_set_current(block, layout);
869 
870   if (free) {
871     MEM_freeN((void *)item_array);
872   }
873 }
ui_item_enum_expand(uiLayout * layout,uiBlock * block,PointerRNA * ptr,PropertyRNA * prop,const char * uiname,const int h,const bool icon_only)874 static void ui_item_enum_expand(uiLayout *layout,
875                                 uiBlock *block,
876                                 PointerRNA *ptr,
877                                 PropertyRNA *prop,
878                                 const char *uiname,
879                                 const int h,
880                                 const bool icon_only)
881 {
882   ui_item_enum_expand_exec(layout, block, ptr, prop, uiname, h, UI_BTYPE_ROW, icon_only);
883 }
ui_item_enum_expand_tabs(uiLayout * layout,bContext * C,uiBlock * block,PointerRNA * ptr,PropertyRNA * prop,PointerRNA * ptr_highlight,PropertyRNA * prop_highlight,const char * uiname,const int h,const bool icon_only)884 static void ui_item_enum_expand_tabs(uiLayout *layout,
885                                      bContext *C,
886                                      uiBlock *block,
887                                      PointerRNA *ptr,
888                                      PropertyRNA *prop,
889                                      PointerRNA *ptr_highlight,
890                                      PropertyRNA *prop_highlight,
891                                      const char *uiname,
892                                      const int h,
893                                      const bool icon_only)
894 {
895   uiBut *last = block->buttons.last;
896 
897   ui_item_enum_expand_exec(layout, block, ptr, prop, uiname, h, UI_BTYPE_TAB, icon_only);
898   BLI_assert(last != block->buttons.last);
899 
900   for (uiBut *tab = last ? last->next : block->buttons.first; tab; tab = tab->next) {
901     UI_but_drawflag_enable(tab, ui_but_align_opposite_to_area_align_get(CTX_wm_region(C)));
902   }
903 
904   const bool use_custom_highlight = (prop_highlight != NULL);
905 
906   if (use_custom_highlight) {
907     const int highlight_array_len = RNA_property_array_length(ptr_highlight, prop_highlight);
908     bool *highlight_array = alloca(sizeof(bool) * highlight_array_len);
909     RNA_property_boolean_get_array(ptr_highlight, prop_highlight, highlight_array);
910     int i = 0;
911     for (uiBut *tab_but = last ? last->next : block->buttons.first;
912          (tab_but != NULL) && (i < highlight_array_len);
913          tab_but = tab_but->next, i++) {
914       SET_FLAG_FROM_TEST(tab_but->flag, !highlight_array[i], UI_BUT_INACTIVE);
915     }
916   }
917 }
918 
919 /* callback for keymap item change button */
ui_keymap_but_cb(bContext * UNUSED (C),void * but_v,void * UNUSED (key_v))920 static void ui_keymap_but_cb(bContext *UNUSED(C), void *but_v, void *UNUSED(key_v))
921 {
922   uiBut *but = but_v;
923 
924   RNA_boolean_set(&but->rnapoin, "shift", (but->modifier_key & KM_SHIFT) != 0);
925   RNA_boolean_set(&but->rnapoin, "ctrl", (but->modifier_key & KM_CTRL) != 0);
926   RNA_boolean_set(&but->rnapoin, "alt", (but->modifier_key & KM_ALT) != 0);
927   RNA_boolean_set(&but->rnapoin, "oskey", (but->modifier_key & KM_OSKEY) != 0);
928 }
929 
930 /**
931  * Create label + button for RNA property
932  *
933  * \param w_hint: For varying width layout, this becomes the label width.
934  *                Otherwise it's used to fit both items into it.
935  */
ui_item_with_label(uiLayout * layout,uiBlock * block,const char * name,int icon,PointerRNA * ptr,PropertyRNA * prop,int index,int x,int y,int w_hint,int h,int flag)936 static uiBut *ui_item_with_label(uiLayout *layout,
937                                  uiBlock *block,
938                                  const char *name,
939                                  int icon,
940                                  PointerRNA *ptr,
941                                  PropertyRNA *prop,
942                                  int index,
943                                  int x,
944                                  int y,
945                                  int w_hint,
946                                  int h,
947                                  int flag)
948 {
949   uiLayout *sub = layout;
950   int prop_but_width = w_hint;
951 #ifdef UI_PROP_DECORATE
952   uiLayout *layout_prop_decorate = NULL;
953   const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0);
954   const bool use_prop_decorate = use_prop_sep && (layout->item.flag & UI_ITEM_PROP_DECORATE) &&
955                                  (layout->item.flag & UI_ITEM_PROP_DECORATE_NO_PAD) == 0;
956 #endif
957 
958   const bool is_keymapitem_ptr = RNA_struct_is_a(ptr->type, &RNA_KeyMapItem);
959   if ((flag & flag & UI_ITEM_R_FULL_EVENT) && !is_keymapitem_ptr) {
960     RNA_warning("Data is not a keymap item struct: %s. Ignoring 'full_event' option.",
961                 RNA_struct_identifier(ptr->type));
962   }
963 
964   UI_block_layout_set_current(block, layout);
965 
966   /* Only add new row if more than 1 item will be added. */
967   if (name[0] || use_prop_decorate) {
968     /* Also avoid setting 'align' if possible. Set the space to zero instead as aligning a large
969      * number of labels can end up aligning thousands of buttons when displaying key-map search (a
970      * heavy operation), see: T78636. */
971     sub = uiLayoutRow(layout, layout->align);
972     sub->space = 0;
973   }
974 
975 #ifdef UI_PROP_DECORATE
976   if (name[0]) {
977     if (use_prop_sep) {
978       layout_prop_decorate = uiItemL_respect_property_split(layout, name, 0);
979     }
980     else
981 #endif
982     {
983       int w_label;
984       if (ui_layout_variable_size(layout)) {
985         /* w_hint is width for label in this case.
986          * Use a default width for property button(s) */
987         prop_but_width = UI_UNIT_X * 5;
988         w_label = w_hint;
989       }
990       else {
991         w_label = w_hint / 3;
992       }
993       uiDefBut(block, UI_BTYPE_LABEL, 0, name, x, y, w_label, h, NULL, 0.0, 0.0, 0, 0, "");
994     }
995   }
996 
997   const PropertyType type = RNA_property_type(prop);
998   const PropertySubType subtype = RNA_property_subtype(prop);
999 
1000   uiBut *but;
1001   if (subtype == PROP_FILEPATH || subtype == PROP_DIRPATH) {
1002     UI_block_layout_set_current(block, uiLayoutRow(sub, true));
1003     but = uiDefAutoButR(block, ptr, prop, index, "", icon, x, y, prop_but_width - UI_UNIT_X, h);
1004 
1005     /* BUTTONS_OT_file_browse calls UI_context_active_but_prop_get_filebrowser */
1006     uiDefIconButO(block,
1007                   UI_BTYPE_BUT,
1008                   subtype == PROP_DIRPATH ? "BUTTONS_OT_directory_browse" :
1009                                             "BUTTONS_OT_file_browse",
1010                   WM_OP_INVOKE_DEFAULT,
1011                   ICON_FILEBROWSER,
1012                   x,
1013                   y,
1014                   UI_UNIT_X,
1015                   h,
1016                   NULL);
1017   }
1018   else if (flag & UI_ITEM_R_EVENT) {
1019     but = uiDefButR_prop(block,
1020                          UI_BTYPE_KEY_EVENT,
1021                          0,
1022                          name,
1023                          x,
1024                          y,
1025                          prop_but_width,
1026                          h,
1027                          ptr,
1028                          prop,
1029                          index,
1030                          0,
1031                          0,
1032                          -1,
1033                          -1,
1034                          NULL);
1035   }
1036   else if ((flag & UI_ITEM_R_FULL_EVENT) && is_keymapitem_ptr) {
1037     char buf[128];
1038 
1039     WM_keymap_item_to_string(ptr->data, false, buf, sizeof(buf));
1040 
1041     but = uiDefButR_prop(block,
1042                          UI_BTYPE_HOTKEY_EVENT,
1043                          0,
1044                          buf,
1045                          x,
1046                          y,
1047                          prop_but_width,
1048                          h,
1049                          ptr,
1050                          prop,
1051                          0,
1052                          0,
1053                          0,
1054                          -1,
1055                          -1,
1056                          NULL);
1057     UI_but_func_set(but, ui_keymap_but_cb, but, NULL);
1058     if (flag & UI_ITEM_R_IMMEDIATE) {
1059       UI_but_flag_enable(but, UI_BUT_IMMEDIATE);
1060     }
1061   }
1062   else {
1063     const char *str = (type == PROP_ENUM && !(flag & UI_ITEM_R_ICON_ONLY)) ? NULL : "";
1064     but = uiDefAutoButR(block, ptr, prop, index, str, icon, x, y, prop_but_width, h);
1065   }
1066 
1067 #ifdef UI_PROP_DECORATE
1068   /* Only for alignment. */
1069   if (use_prop_decorate) { /* Note that sep flag may have been unset meanwhile. */
1070     uiItemL(layout_prop_decorate ? layout_prop_decorate : sub, NULL, ICON_BLANK1);
1071   }
1072 #endif /* UI_PROP_DECORATE */
1073 
1074   UI_block_layout_set_current(block, layout);
1075   return but;
1076 }
1077 
UI_context_active_but_prop_get_filebrowser(const bContext * C,PointerRNA * r_ptr,PropertyRNA ** r_prop,bool * r_is_undo,bool * r_is_userdef)1078 void UI_context_active_but_prop_get_filebrowser(const bContext *C,
1079                                                 PointerRNA *r_ptr,
1080                                                 PropertyRNA **r_prop,
1081                                                 bool *r_is_undo,
1082                                                 bool *r_is_userdef)
1083 {
1084   ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C);
1085   uiBut *prevbut = NULL;
1086 
1087   memset(r_ptr, 0, sizeof(*r_ptr));
1088   *r_prop = NULL;
1089   *r_is_undo = false;
1090   *r_is_userdef = false;
1091 
1092   if (!region) {
1093     return;
1094   }
1095 
1096   LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
1097     LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
1098       if (but && but->rnapoin.data) {
1099         if (RNA_property_type(but->rnaprop) == PROP_STRING) {
1100           prevbut = but;
1101         }
1102       }
1103 
1104       /* find the button before the active one */
1105       if ((but->flag & UI_BUT_LAST_ACTIVE) && prevbut) {
1106         *r_ptr = prevbut->rnapoin;
1107         *r_prop = prevbut->rnaprop;
1108         *r_is_undo = (prevbut->flag & UI_BUT_UNDO) != 0;
1109         *r_is_userdef = UI_but_is_userdef(prevbut);
1110         return;
1111       }
1112     }
1113   }
1114 }
1115 
1116 /** \} */
1117 
1118 /* -------------------------------------------------------------------- */
1119 /** \name Button Items
1120  * \{ */
1121 
1122 /**
1123  * Update a buttons tip with an enum's description if possible.
1124  */
ui_but_tip_from_enum_item(uiBut * but,const EnumPropertyItem * item)1125 static void ui_but_tip_from_enum_item(uiBut *but, const EnumPropertyItem *item)
1126 {
1127   if (but->tip == NULL || but->tip[0] == '\0') {
1128     if (item->description && item->description[0] &&
1129         !(but->optype && but->optype->get_description)) {
1130       but->tip = item->description;
1131     }
1132   }
1133 }
1134 
1135 /* disabled item */
ui_item_disabled(uiLayout * layout,const char * name)1136 static void ui_item_disabled(uiLayout *layout, const char *name)
1137 {
1138   uiBlock *block = layout->root->block;
1139 
1140   UI_block_layout_set_current(block, layout);
1141 
1142   if (!name) {
1143     name = "";
1144   }
1145 
1146   const int w = ui_text_icon_width(layout, name, 0, 0);
1147 
1148   uiBut *but = uiDefBut(
1149       block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
1150   UI_but_disable(but, "");
1151 }
1152 
1153 /**
1154  * Operator Item
1155  * \param r_opptr: Optional, initialize with operator properties when not NULL.
1156  * Will always be written to even in the case of errors.
1157  */
uiItemFullO_ptr_ex(uiLayout * layout,wmOperatorType * ot,const char * name,int icon,IDProperty * properties,int context,int flag,PointerRNA * r_opptr)1158 static uiBut *uiItemFullO_ptr_ex(uiLayout *layout,
1159                                  wmOperatorType *ot,
1160                                  const char *name,
1161                                  int icon,
1162                                  IDProperty *properties,
1163                                  int context,
1164                                  int flag,
1165                                  PointerRNA *r_opptr)
1166 {
1167   /* Take care to fill 'r_opptr' whatever happens. */
1168   uiBlock *block = layout->root->block;
1169 
1170   if (!name) {
1171     if (ot && ot->srna && (flag & UI_ITEM_R_ICON_ONLY) == 0) {
1172       name = WM_operatortype_name(ot, NULL);
1173     }
1174     else {
1175       name = "";
1176     }
1177   }
1178 
1179   if (layout->root->type == UI_LAYOUT_MENU && !icon) {
1180     icon = ICON_BLANK1;
1181   }
1182 
1183   UI_block_layout_set_current(block, layout);
1184   ui_block_new_button_group(block, 0);
1185 
1186   const int w = ui_text_icon_width(layout, name, icon, 0);
1187 
1188   const int prev_emboss = layout->emboss;
1189   if (flag & UI_ITEM_R_NO_BG) {
1190     layout->emboss = UI_EMBOSS_NONE;
1191   }
1192 
1193   /* create the button */
1194   uiBut *but;
1195   if (icon) {
1196     if (name[0]) {
1197       but = uiDefIconTextButO_ptr(
1198           block, UI_BTYPE_BUT, ot, context, icon, name, 0, 0, w, UI_UNIT_Y, NULL);
1199     }
1200     else {
1201       but = uiDefIconButO_ptr(block, UI_BTYPE_BUT, ot, context, icon, 0, 0, w, UI_UNIT_Y, NULL);
1202     }
1203   }
1204   else {
1205     but = uiDefButO_ptr(block, UI_BTYPE_BUT, ot, context, name, 0, 0, w, UI_UNIT_Y, NULL);
1206   }
1207 
1208   BLI_assert(but->optype != NULL);
1209 
1210   if (flag & UI_ITEM_R_NO_BG) {
1211     layout->emboss = prev_emboss;
1212   }
1213 
1214   if (flag & UI_ITEM_O_DEPRESS) {
1215     but->flag |= UI_SELECT_DRAW;
1216   }
1217 
1218   if (flag & UI_ITEM_R_ICON_ONLY) {
1219     UI_but_drawflag_disable(but, UI_BUT_ICON_LEFT);
1220   }
1221 
1222   if (layout->redalert) {
1223     UI_but_flag_enable(but, UI_BUT_REDALERT);
1224   }
1225 
1226   if (layout->active_default) {
1227     UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
1228   }
1229 
1230   /* assign properties */
1231   if (properties || r_opptr) {
1232     PointerRNA *opptr = UI_but_operator_ptr_get(but);
1233     if (properties) {
1234       opptr->data = properties;
1235     }
1236     else {
1237       const IDPropertyTemplate val = {0};
1238       opptr->data = IDP_New(IDP_GROUP, &val, "wmOperatorProperties");
1239     }
1240     if (r_opptr) {
1241       *r_opptr = *opptr;
1242     }
1243   }
1244 
1245   return but;
1246 }
1247 
ui_item_menu_hold(struct bContext * C,ARegion * butregion,uiBut * but)1248 static void ui_item_menu_hold(struct bContext *C, ARegion *butregion, uiBut *but)
1249 {
1250   uiPopupMenu *pup = UI_popup_menu_begin(C, "", ICON_NONE);
1251   uiLayout *layout = UI_popup_menu_layout(pup);
1252   uiBlock *block = layout->root->block;
1253   UI_popup_menu_but_set(pup, butregion, but);
1254 
1255   block->flag |= UI_BLOCK_POPUP_HOLD;
1256   block->flag |= UI_BLOCK_IS_FLIP;
1257 
1258   char direction = UI_DIR_DOWN;
1259   if (!but->drawstr[0]) {
1260     switch (RGN_ALIGN_ENUM_FROM_MASK(butregion->alignment)) {
1261       case RGN_ALIGN_LEFT:
1262         direction = UI_DIR_RIGHT;
1263         break;
1264       case RGN_ALIGN_RIGHT:
1265         direction = UI_DIR_LEFT;
1266         break;
1267       case RGN_ALIGN_BOTTOM:
1268         direction = UI_DIR_UP;
1269         break;
1270       default:
1271         direction = UI_DIR_DOWN;
1272         break;
1273     }
1274   }
1275   UI_block_direction_set(block, direction);
1276 
1277   const char *menu_id = but->hold_argN;
1278   MenuType *mt = WM_menutype_find(menu_id, true);
1279   if (mt) {
1280     uiLayoutSetContextFromBut(layout, but);
1281     UI_menutype_draw(C, mt, layout);
1282   }
1283   else {
1284     uiItemL(layout, "Menu Missing:", ICON_NONE);
1285     uiItemL(layout, menu_id, ICON_NONE);
1286   }
1287   UI_popup_menu_end(C, pup);
1288 }
1289 
uiItemFullO_ptr(uiLayout * layout,wmOperatorType * ot,const char * name,int icon,IDProperty * properties,int context,int flag,PointerRNA * r_opptr)1290 void uiItemFullO_ptr(uiLayout *layout,
1291                      wmOperatorType *ot,
1292                      const char *name,
1293                      int icon,
1294                      IDProperty *properties,
1295                      int context,
1296                      int flag,
1297                      PointerRNA *r_opptr)
1298 {
1299   uiItemFullO_ptr_ex(layout, ot, name, icon, properties, context, flag, r_opptr);
1300 }
1301 
uiItemFullOMenuHold_ptr(uiLayout * layout,wmOperatorType * ot,const char * name,int icon,IDProperty * properties,int context,int flag,const char * menu_id,PointerRNA * r_opptr)1302 void uiItemFullOMenuHold_ptr(uiLayout *layout,
1303                              wmOperatorType *ot,
1304                              const char *name,
1305                              int icon,
1306                              IDProperty *properties,
1307                              int context,
1308                              int flag,
1309                              const char *menu_id,
1310                              PointerRNA *r_opptr)
1311 {
1312   uiBut *but = uiItemFullO_ptr_ex(layout, ot, name, icon, properties, context, flag, r_opptr);
1313   UI_but_func_hold_set(but, ui_item_menu_hold, BLI_strdup(menu_id));
1314 }
1315 
uiItemFullO(uiLayout * layout,const char * opname,const char * name,int icon,IDProperty * properties,int context,int flag,PointerRNA * r_opptr)1316 void uiItemFullO(uiLayout *layout,
1317                  const char *opname,
1318                  const char *name,
1319                  int icon,
1320                  IDProperty *properties,
1321                  int context,
1322                  int flag,
1323                  PointerRNA *r_opptr)
1324 {
1325   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
1326 
1327   UI_OPERATOR_ERROR_RET(ot, opname, {
1328     if (r_opptr) {
1329       *r_opptr = PointerRNA_NULL;
1330     }
1331     return;
1332   });
1333 
1334   uiItemFullO_ptr(layout, ot, name, icon, properties, context, flag, r_opptr);
1335 }
1336 
ui_menu_enumpropname(uiLayout * layout,PointerRNA * ptr,PropertyRNA * prop,int retval)1337 static const char *ui_menu_enumpropname(uiLayout *layout,
1338                                         PointerRNA *ptr,
1339                                         PropertyRNA *prop,
1340                                         int retval)
1341 {
1342   bool free;
1343   const EnumPropertyItem *item;
1344   RNA_property_enum_items(layout->root->block->evil_C, ptr, prop, &item, NULL, &free);
1345 
1346   const char *name;
1347   if (RNA_enum_name(item, retval, &name)) {
1348     name = CTX_IFACE_(RNA_property_translation_context(prop), name);
1349   }
1350   else {
1351     name = "";
1352   }
1353 
1354   if (free) {
1355     MEM_freeN((void *)item);
1356   }
1357 
1358   return name;
1359 }
1360 
uiItemEnumO_ptr(uiLayout * layout,wmOperatorType * ot,const char * name,int icon,const char * propname,int value)1361 void uiItemEnumO_ptr(uiLayout *layout,
1362                      wmOperatorType *ot,
1363                      const char *name,
1364                      int icon,
1365                      const char *propname,
1366                      int value)
1367 {
1368   PointerRNA ptr;
1369   WM_operator_properties_create_ptr(&ptr, ot);
1370 
1371   PropertyRNA *prop = RNA_struct_find_property(&ptr, propname);
1372   if (prop == NULL) {
1373     RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname);
1374     return;
1375   }
1376 
1377   RNA_property_enum_set(&ptr, prop, value);
1378 
1379   if (!name) {
1380     name = ui_menu_enumpropname(layout, &ptr, prop, value);
1381   }
1382 
1383   uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL);
1384 }
uiItemEnumO(uiLayout * layout,const char * opname,const char * name,int icon,const char * propname,int value)1385 void uiItemEnumO(uiLayout *layout,
1386                  const char *opname,
1387                  const char *name,
1388                  int icon,
1389                  const char *propname,
1390                  int value)
1391 {
1392   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
1393 
1394   if (ot) {
1395     uiItemEnumO_ptr(layout, ot, name, icon, propname, value);
1396   }
1397   else {
1398     ui_item_disabled(layout, opname);
1399     RNA_warning("unknown operator '%s'", opname);
1400   }
1401 }
1402 
ui_layout_is_radial(const uiLayout * layout)1403 BLI_INLINE bool ui_layout_is_radial(const uiLayout *layout)
1404 {
1405   return (layout->item.type == ITEM_LAYOUT_RADIAL) ||
1406          ((layout->item.type == ITEM_LAYOUT_ROOT) && (layout->root->type == UI_LAYOUT_PIEMENU));
1407 }
1408 
1409 /**
1410  * Create ui items for enum items in \a item_array.
1411  *
1412  * A version of #uiItemsFullEnumO that takes pre-calculated item array.
1413  */
uiItemsFullEnumO_items(uiLayout * layout,wmOperatorType * ot,PointerRNA ptr,PropertyRNA * prop,IDProperty * properties,int context,int flag,const EnumPropertyItem * item_array,int totitem)1414 void uiItemsFullEnumO_items(uiLayout *layout,
1415                             wmOperatorType *ot,
1416                             PointerRNA ptr,
1417                             PropertyRNA *prop,
1418                             IDProperty *properties,
1419                             int context,
1420                             int flag,
1421                             const EnumPropertyItem *item_array,
1422                             int totitem)
1423 {
1424   const char *propname = RNA_property_identifier(prop);
1425   if (RNA_property_type(prop) != PROP_ENUM) {
1426     RNA_warning("%s.%s, not an enum type", RNA_struct_identifier(ptr.type), propname);
1427     return;
1428   }
1429 
1430   uiLayout *target, *split = NULL;
1431   uiBlock *block = layout->root->block;
1432   const bool radial = ui_layout_is_radial(layout);
1433 
1434   if (radial) {
1435     target = uiLayoutRadial(layout);
1436   }
1437   else if ((uiLayoutGetLocalDir(layout) == UI_LAYOUT_HORIZONTAL) && (flag & UI_ITEM_R_ICON_ONLY)) {
1438     target = layout;
1439     UI_block_layout_set_current(block, target);
1440 
1441     /* Add a blank button to the beginning of the row. */
1442     uiDefIconBut(block,
1443                  UI_BTYPE_LABEL,
1444                  0,
1445                  ICON_BLANK1,
1446                  0,
1447                  0,
1448                  1.25f * UI_UNIT_X,
1449                  UI_UNIT_Y,
1450                  NULL,
1451                  0,
1452                  0,
1453                  0,
1454                  0,
1455                  NULL);
1456   }
1457   else {
1458     split = uiLayoutSplit(layout, 0.0f, false);
1459     target = uiLayoutColumn(split, layout->align);
1460   }
1461 
1462   bool last_iter = false;
1463   const EnumPropertyItem *item = item_array;
1464   for (int i = 1; item->identifier && !last_iter; i++, item++) {
1465     /* handle oversized pies */
1466     if (radial && (totitem > PIE_MAX_ITEMS) && (i >= PIE_MAX_ITEMS)) {
1467       if (item->name) { /* only visible items */
1468         const EnumPropertyItem *tmp;
1469 
1470         /* Check if there are more visible items for the next level. If not, we don't
1471          * add a new level and add the remaining item instead of the 'more' button. */
1472         for (tmp = item + 1; tmp->identifier; tmp++) {
1473           if (tmp->name) {
1474             break;
1475           }
1476         }
1477 
1478         if (tmp->identifier) { /* only true if loop above found item and did early-exit */
1479           ui_pie_menu_level_create(
1480               block, ot, propname, properties, item_array, totitem, context, flag);
1481           /* break since rest of items is handled in new pie level */
1482           break;
1483         }
1484         last_iter = true;
1485       }
1486       else {
1487         continue;
1488       }
1489     }
1490 
1491     if (item->identifier[0]) {
1492       PointerRNA tptr;
1493       WM_operator_properties_create_ptr(&tptr, ot);
1494       if (properties) {
1495         if (tptr.data) {
1496           IDP_FreeProperty(tptr.data);
1497         }
1498         tptr.data = IDP_CopyProperty(properties);
1499       }
1500       RNA_property_enum_set(&tptr, prop, item->value);
1501 
1502       uiItemFullO_ptr(target,
1503                       ot,
1504                       (flag & UI_ITEM_R_ICON_ONLY) ? NULL : item->name,
1505                       item->icon,
1506                       tptr.data,
1507                       context,
1508                       flag,
1509                       NULL);
1510 
1511       ui_but_tip_from_enum_item(block->buttons.last, item);
1512     }
1513     else {
1514       if (item->name) {
1515         if (item != item_array && !radial && split != NULL) {
1516           target = uiLayoutColumn(split, layout->align);
1517 
1518           /* inconsistent, but menus with labels do not look good flipped */
1519           block->flag |= UI_BLOCK_NO_FLIP;
1520         }
1521 
1522         uiBut *but;
1523         if (item->icon || radial) {
1524           uiItemL(target, item->name, item->icon);
1525 
1526           but = block->buttons.last;
1527         }
1528         else {
1529           /* Do not use uiItemL here, as our root layout is a menu one,
1530            * it will add a fake blank icon! */
1531           but = uiDefBut(block,
1532                          UI_BTYPE_LABEL,
1533                          0,
1534                          item->name,
1535                          0,
1536                          0,
1537                          UI_UNIT_X * 5,
1538                          UI_UNIT_Y,
1539                          NULL,
1540                          0.0,
1541                          0.0,
1542                          0,
1543                          0,
1544                          "");
1545           uiItemS(target);
1546         }
1547         ui_but_tip_from_enum_item(but, item);
1548       }
1549       else {
1550         if (radial) {
1551           /* invisible dummy button to ensure all items are
1552            * always at the same position */
1553           uiItemS(target);
1554         }
1555         else {
1556           /* XXX bug here, columns draw bottom item badly */
1557           uiItemS(target);
1558         }
1559       }
1560     }
1561   }
1562 }
1563 
uiItemsFullEnumO(uiLayout * layout,const char * opname,const char * propname,IDProperty * properties,int context,int flag)1564 void uiItemsFullEnumO(uiLayout *layout,
1565                       const char *opname,
1566                       const char *propname,
1567                       IDProperty *properties,
1568                       int context,
1569                       int flag)
1570 {
1571   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
1572 
1573   if (!ot || !ot->srna) {
1574     ui_item_disabled(layout, opname);
1575     RNA_warning("%s '%s'", ot ? "unknown operator" : "operator missing srna", opname);
1576     return;
1577   }
1578 
1579   PointerRNA ptr;
1580   WM_operator_properties_create_ptr(&ptr, ot);
1581   /* so the context is passed to itemf functions (some need it) */
1582   WM_operator_properties_sanitize(&ptr, false);
1583   PropertyRNA *prop = RNA_struct_find_property(&ptr, propname);
1584 
1585   /* don't let bad properties slip through */
1586   BLI_assert((prop == NULL) || (RNA_property_type(prop) == PROP_ENUM));
1587 
1588   uiBlock *block = layout->root->block;
1589   if (prop && RNA_property_type(prop) == PROP_ENUM) {
1590     const EnumPropertyItem *item_array = NULL;
1591     int totitem;
1592     bool free;
1593 
1594     if (ui_layout_is_radial(layout)) {
1595       /* XXX: While "_all()" guarantees spatial stability,
1596        * it's bad when an enum has > 8 items total,
1597        * but only a small subset will ever be shown at once
1598        * (e.g. Mode Switch menu, after the introduction of GP editing modes).
1599        */
1600 #if 0
1601       RNA_property_enum_items_gettexted_all(
1602           block->evil_C, &ptr, prop, &item_array, &totitem, &free);
1603 #else
1604       RNA_property_enum_items_gettexted(block->evil_C, &ptr, prop, &item_array, &totitem, &free);
1605 #endif
1606     }
1607     else {
1608       RNA_property_enum_items_gettexted(block->evil_C, &ptr, prop, &item_array, &totitem, &free);
1609     }
1610 
1611     /* add items */
1612     uiItemsFullEnumO_items(layout, ot, ptr, prop, properties, context, flag, item_array, totitem);
1613 
1614     if (free) {
1615       MEM_freeN((void *)item_array);
1616     }
1617 
1618     /* intentionally don't touch UI_BLOCK_IS_FLIP here,
1619      * we don't know the context this is called in */
1620   }
1621   else if (prop && RNA_property_type(prop) != PROP_ENUM) {
1622     RNA_warning("%s.%s, not an enum type", RNA_struct_identifier(ptr.type), propname);
1623     return;
1624   }
1625   else {
1626     RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname);
1627     return;
1628   }
1629 }
1630 
uiItemsEnumO(uiLayout * layout,const char * opname,const char * propname)1631 void uiItemsEnumO(uiLayout *layout, const char *opname, const char *propname)
1632 {
1633   uiItemsFullEnumO(layout, opname, propname, NULL, layout->root->opcontext, 0);
1634 }
1635 
1636 /* for use in cases where we have */
uiItemEnumO_value(uiLayout * layout,const char * name,int icon,const char * opname,const char * propname,int value)1637 void uiItemEnumO_value(uiLayout *layout,
1638                        const char *name,
1639                        int icon,
1640                        const char *opname,
1641                        const char *propname,
1642                        int value)
1643 {
1644   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
1645   UI_OPERATOR_ERROR_RET(ot, opname, return );
1646 
1647   PointerRNA ptr;
1648   WM_operator_properties_create_ptr(&ptr, ot);
1649 
1650   /* enum lookup */
1651   PropertyRNA *prop = RNA_struct_find_property(&ptr, propname);
1652   if (prop == NULL) {
1653     RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname);
1654     return;
1655   }
1656 
1657   RNA_property_enum_set(&ptr, prop, value);
1658 
1659   /* same as uiItemEnumO */
1660   if (!name) {
1661     name = ui_menu_enumpropname(layout, &ptr, prop, value);
1662   }
1663 
1664   uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL);
1665 }
1666 
uiItemEnumO_string(uiLayout * layout,const char * name,int icon,const char * opname,const char * propname,const char * value_str)1667 void uiItemEnumO_string(uiLayout *layout,
1668                         const char *name,
1669                         int icon,
1670                         const char *opname,
1671                         const char *propname,
1672                         const char *value_str)
1673 {
1674   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
1675   UI_OPERATOR_ERROR_RET(ot, opname, return );
1676 
1677   PointerRNA ptr;
1678   WM_operator_properties_create_ptr(&ptr, ot);
1679 
1680   PropertyRNA *prop = RNA_struct_find_property(&ptr, propname);
1681   if (prop == NULL) {
1682     RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname);
1683     return;
1684   }
1685 
1686   /* enum lookup */
1687   /* no need for translations here */
1688   const EnumPropertyItem *item;
1689   bool free;
1690   RNA_property_enum_items(layout->root->block->evil_C, &ptr, prop, &item, NULL, &free);
1691 
1692   int value;
1693   if (item == NULL || RNA_enum_value_from_id(item, value_str, &value) == 0) {
1694     if (free) {
1695       MEM_freeN((void *)item);
1696     }
1697     RNA_warning("%s.%s, enum %s not found", RNA_struct_identifier(ptr.type), propname, value_str);
1698     return;
1699   }
1700 
1701   if (free) {
1702     MEM_freeN((void *)item);
1703   }
1704 
1705   RNA_property_enum_set(&ptr, prop, value);
1706 
1707   /* same as uiItemEnumO */
1708   if (!name) {
1709     name = ui_menu_enumpropname(layout, &ptr, prop, value);
1710   }
1711 
1712   uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL);
1713 }
1714 
uiItemBooleanO(uiLayout * layout,const char * name,int icon,const char * opname,const char * propname,int value)1715 void uiItemBooleanO(uiLayout *layout,
1716                     const char *name,
1717                     int icon,
1718                     const char *opname,
1719                     const char *propname,
1720                     int value)
1721 {
1722   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
1723   UI_OPERATOR_ERROR_RET(ot, opname, return );
1724 
1725   PointerRNA ptr;
1726   WM_operator_properties_create_ptr(&ptr, ot);
1727   RNA_boolean_set(&ptr, propname, value);
1728 
1729   uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL);
1730 }
1731 
uiItemIntO(uiLayout * layout,const char * name,int icon,const char * opname,const char * propname,int value)1732 void uiItemIntO(uiLayout *layout,
1733                 const char *name,
1734                 int icon,
1735                 const char *opname,
1736                 const char *propname,
1737                 int value)
1738 {
1739   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
1740   UI_OPERATOR_ERROR_RET(ot, opname, return );
1741 
1742   PointerRNA ptr;
1743   WM_operator_properties_create_ptr(&ptr, ot);
1744   RNA_int_set(&ptr, propname, value);
1745 
1746   uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL);
1747 }
1748 
uiItemFloatO(uiLayout * layout,const char * name,int icon,const char * opname,const char * propname,float value)1749 void uiItemFloatO(uiLayout *layout,
1750                   const char *name,
1751                   int icon,
1752                   const char *opname,
1753                   const char *propname,
1754                   float value)
1755 {
1756   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
1757 
1758   UI_OPERATOR_ERROR_RET(ot, opname, return );
1759 
1760   PointerRNA ptr;
1761   WM_operator_properties_create_ptr(&ptr, ot);
1762   RNA_float_set(&ptr, propname, value);
1763 
1764   uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL);
1765 }
1766 
uiItemStringO(uiLayout * layout,const char * name,int icon,const char * opname,const char * propname,const char * value)1767 void uiItemStringO(uiLayout *layout,
1768                    const char *name,
1769                    int icon,
1770                    const char *opname,
1771                    const char *propname,
1772                    const char *value)
1773 {
1774   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
1775 
1776   UI_OPERATOR_ERROR_RET(ot, opname, return );
1777 
1778   PointerRNA ptr;
1779   WM_operator_properties_create_ptr(&ptr, ot);
1780   RNA_string_set(&ptr, propname, value);
1781 
1782   uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL);
1783 }
1784 
uiItemO(uiLayout * layout,const char * name,int icon,const char * opname)1785 void uiItemO(uiLayout *layout, const char *name, int icon, const char *opname)
1786 {
1787   uiItemFullO(layout, opname, name, icon, NULL, layout->root->opcontext, 0, NULL);
1788 }
1789 
1790 /* RNA property items */
1791 
ui_item_rna_size(uiLayout * layout,const char * name,int icon,PointerRNA * ptr,PropertyRNA * prop,int index,bool icon_only,bool compact,int * r_w,int * r_h)1792 static void ui_item_rna_size(uiLayout *layout,
1793                              const char *name,
1794                              int icon,
1795                              PointerRNA *ptr,
1796                              PropertyRNA *prop,
1797                              int index,
1798                              bool icon_only,
1799                              bool compact,
1800                              int *r_w,
1801                              int *r_h)
1802 {
1803   int w = 0, h;
1804 
1805   /* arbitrary extended width by type */
1806   PropertyType type = RNA_property_type(prop);
1807   PropertySubType subtype = RNA_property_subtype(prop);
1808   const int len = RNA_property_array_length(ptr, prop);
1809 
1810   bool is_checkbox_only = false;
1811   if (!name[0] && !icon_only) {
1812     if (ELEM(type, PROP_STRING, PROP_POINTER)) {
1813       name = "non-empty text";
1814     }
1815     else if (type == PROP_BOOLEAN) {
1816       if (icon == ICON_NONE) {
1817         /* Exception for checkboxes, they need a little less space to align nicely. */
1818         is_checkbox_only = true;
1819       }
1820       icon = ICON_DOT;
1821     }
1822     else if (type == PROP_ENUM) {
1823       /* Find the longest enum item name, instead of using a dummy text! */
1824       const EnumPropertyItem *item_array;
1825       bool free;
1826       RNA_property_enum_items_gettexted(
1827           layout->root->block->evil_C, ptr, prop, &item_array, NULL, &free);
1828 
1829       for (const EnumPropertyItem *item = item_array; item->identifier; item++) {
1830         if (item->identifier[0]) {
1831           w = max_ii(w, ui_text_icon_width(layout, item->name, item->icon, compact));
1832         }
1833       }
1834       if (free) {
1835         MEM_freeN((void *)item_array);
1836       }
1837     }
1838   }
1839 
1840   if (!w) {
1841     if (type == PROP_ENUM && icon_only) {
1842       w = ui_text_icon_width(layout, "", ICON_BLANK1, compact);
1843       if (index != RNA_ENUM_VALUE) {
1844         w += 0.6f * UI_UNIT_X;
1845       }
1846     }
1847     else {
1848       /* not compact for float/int buttons, looks too squashed */
1849       w = ui_text_icon_width(
1850           layout, name, icon, ELEM(type, PROP_FLOAT, PROP_INT) ? false : compact);
1851     }
1852   }
1853   h = UI_UNIT_Y;
1854 
1855   /* increase height for arrays */
1856   if (index == RNA_NO_INDEX && len > 0) {
1857     if (!name[0] && icon == ICON_NONE) {
1858       h = 0;
1859     }
1860     if (layout->item.flag & UI_ITEM_PROP_SEP) {
1861       h = 0;
1862     }
1863     if (ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER)) {
1864       h += 2 * UI_UNIT_Y;
1865     }
1866     else if (subtype == PROP_MATRIX) {
1867       h += ceilf(sqrtf(len)) * UI_UNIT_Y;
1868     }
1869     else {
1870       h += len * UI_UNIT_Y;
1871     }
1872   }
1873 
1874   /* Increase width requirement if in a variable size layout. */
1875   if (ui_layout_variable_size(layout)) {
1876     if (type == PROP_BOOLEAN && name[0]) {
1877       w += UI_UNIT_X / 5;
1878     }
1879     else if (is_checkbox_only) {
1880       w -= UI_UNIT_X / 4;
1881     }
1882     else if (type == PROP_ENUM && !icon_only) {
1883       w += UI_UNIT_X / 4;
1884     }
1885     else if (type == PROP_FLOAT || type == PROP_INT) {
1886       w += UI_UNIT_X * 3;
1887     }
1888   }
1889 
1890   *r_w = w;
1891   *r_h = h;
1892 }
1893 
ui_item_rna_is_expand(PropertyRNA * prop,int index,int item_flag)1894 static bool ui_item_rna_is_expand(PropertyRNA *prop, int index, int item_flag)
1895 {
1896   const bool is_array = RNA_property_array_check(prop);
1897   const int subtype = RNA_property_subtype(prop);
1898   return is_array && (index == RNA_NO_INDEX) &&
1899          ((item_flag & UI_ITEM_R_EXPAND) ||
1900           !ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA, PROP_DIRECTION));
1901 }
1902 
1903 /**
1904  * Find first layout ancestor (or self) with a heading set.
1905  *
1906  * \returns the layout to add the heading to as fallback (i.e. if it can't be placed in a split
1907  *          layout). Its #uiLayout.heading member can be cleared to mark the heading as added (so
1908  *          it's not added multiple times). Returns a pointer to the heading
1909  */
ui_layout_heading_find(uiLayout * cur_layout)1910 static uiLayout *ui_layout_heading_find(uiLayout *cur_layout)
1911 {
1912   for (uiLayout *parent = cur_layout; parent; parent = parent->parent) {
1913     if (parent->heading[0]) {
1914       return parent;
1915     }
1916   }
1917 
1918   return NULL;
1919 }
1920 
ui_layout_heading_label_add(uiLayout * layout,uiLayout * heading_layout,bool right_align,bool respect_prop_split)1921 static void ui_layout_heading_label_add(uiLayout *layout,
1922                                         uiLayout *heading_layout,
1923                                         bool right_align,
1924                                         bool respect_prop_split)
1925 {
1926   const int prev_alignment = layout->alignment;
1927 
1928   if (right_align) {
1929     uiLayoutSetAlignment(layout, UI_LAYOUT_ALIGN_RIGHT);
1930   }
1931 
1932   if (respect_prop_split) {
1933     uiItemL_respect_property_split(layout, heading_layout->heading, ICON_NONE);
1934   }
1935   else {
1936     uiItemL(layout, heading_layout->heading, ICON_NONE);
1937   }
1938   /* After adding the heading label, we have to mark it somehow as added, so it's not added again
1939    * for other items in this layout. For now just clear it. */
1940   heading_layout->heading[0] = '\0';
1941 
1942   layout->alignment = prev_alignment;
1943 }
1944 
1945 /**
1946  * Hack to add further items in a row into the second part of the split layout, so the label part
1947  * keeps a fixed size.
1948  * \return The layout to place further items in for the split layout.
1949  */
ui_item_prop_split_layout_hack(uiLayout * layout_parent,uiLayout * layout_split)1950 static uiLayout *ui_item_prop_split_layout_hack(uiLayout *layout_parent, uiLayout *layout_split)
1951 {
1952   /* Tag item as using property split layout, this is inherited to children so they can get special
1953    * treatment if needed. */
1954   layout_parent->item.flag |= UI_ITEM_INSIDE_PROP_SEP;
1955 
1956   if (layout_parent->item.type == ITEM_LAYOUT_ROW) {
1957     /* Prevent further splits within the row. */
1958     uiLayoutSetPropSep(layout_parent, false);
1959 
1960     layout_parent->child_items_layout = uiLayoutRow(layout_split, true);
1961     return layout_parent->child_items_layout;
1962   }
1963   return layout_split;
1964 }
1965 
uiItemFullR(uiLayout * layout,PointerRNA * ptr,PropertyRNA * prop,int index,int value,int flag,const char * name,int icon)1966 void uiItemFullR(uiLayout *layout,
1967                  PointerRNA *ptr,
1968                  PropertyRNA *prop,
1969                  int index,
1970                  int value,
1971                  int flag,
1972                  const char *name,
1973                  int icon)
1974 {
1975   uiBlock *block = layout->root->block;
1976   char namestr[UI_MAX_NAME_STR];
1977   const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0);
1978   const bool inside_prop_sep = ((layout->item.flag & UI_ITEM_INSIDE_PROP_SEP) != 0);
1979   /* Columns can define a heading to insert. If the first item added to a split layout doesn't have
1980    * a label to display in the first column, the heading is inserted there. Otherwise it's inserted
1981    * as a new row before the first item. */
1982   uiLayout *heading_layout = ui_layout_heading_find(layout);
1983   /* Although checkboxes use the split layout, they are an exception and should only place their
1984    * label in the second column, to not make that almost empty.
1985    *
1986    * Keep using 'use_prop_sep' instead of disabling it entirely because
1987    * we need the ability to have decorators still. */
1988   bool use_prop_sep_split_label = use_prop_sep;
1989   bool use_split_empty_name = (flag & UI_ITEM_R_SPLIT_EMPTY_NAME);
1990 
1991 #ifdef UI_PROP_DECORATE
1992   struct {
1993     bool use_prop_decorate;
1994     int len;
1995     uiLayout *layout;
1996     uiBut *but;
1997   } ui_decorate = {
1998       .use_prop_decorate = (((layout->item.flag & UI_ITEM_PROP_DECORATE) != 0) && use_prop_sep),
1999   };
2000 #endif /* UI_PROP_DECORATE */
2001 
2002   UI_block_layout_set_current(block, layout);
2003   ui_block_new_button_group(block, 0);
2004 
2005   /* retrieve info */
2006   const PropertyType type = RNA_property_type(prop);
2007   const bool is_array = RNA_property_array_check(prop);
2008   const int len = (is_array) ? RNA_property_array_length(ptr, prop) : 0;
2009 
2010   const bool icon_only = (flag & UI_ITEM_R_ICON_ONLY) != 0;
2011 
2012   /* Boolean with -1 to signify that the value depends on the presence of an icon. */
2013   const int toggle = ((flag & UI_ITEM_R_TOGGLE) ? 1 : ((flag & UI_ITEM_R_ICON_NEVER) ? 0 : -1));
2014   const bool no_icon = (toggle == 0);
2015 
2016   /* set name and icon */
2017   if (!name) {
2018     if (!icon_only) {
2019       name = RNA_property_ui_name(prop);
2020     }
2021     else {
2022       name = "";
2023     }
2024   }
2025 
2026   if (type != PROP_BOOLEAN) {
2027     flag &= ~UI_ITEM_R_CHECKBOX_INVERT;
2028   }
2029 
2030   if (flag & UI_ITEM_R_ICON_ONLY) {
2031     /* pass */
2032   }
2033   else if (ELEM(type, PROP_INT, PROP_FLOAT, PROP_STRING, PROP_POINTER)) {
2034     if (use_prop_sep == false) {
2035       name = ui_item_name_add_colon(name, namestr);
2036     }
2037   }
2038   else if (type == PROP_BOOLEAN && is_array && index == RNA_NO_INDEX) {
2039     if (use_prop_sep == false) {
2040       name = ui_item_name_add_colon(name, namestr);
2041     }
2042   }
2043   else if (type == PROP_ENUM && index != RNA_ENUM_VALUE) {
2044     if (flag & UI_ITEM_R_COMPACT) {
2045       name = "";
2046     }
2047     else {
2048       if (use_prop_sep == false) {
2049         name = ui_item_name_add_colon(name, namestr);
2050       }
2051     }
2052   }
2053 
2054   if (no_icon == false) {
2055     if (icon == ICON_NONE) {
2056       icon = RNA_property_ui_icon(prop);
2057     }
2058 
2059     /* Menus and pie-menus don't show checkbox without this. */
2060     if ((layout->root->type == UI_LAYOUT_MENU) ||
2061         /* Use checkboxes only as a fallback in pie-menu's, when no icon is defined. */
2062         ((layout->root->type == UI_LAYOUT_PIEMENU) && (icon == ICON_NONE))) {
2063       const int prop_flag = RNA_property_flag(prop);
2064       if (type == PROP_BOOLEAN) {
2065         if ((is_array == false) || (index != RNA_NO_INDEX)) {
2066           if (prop_flag & PROP_ICONS_CONSECUTIVE) {
2067             icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */
2068           }
2069           else if (is_array) {
2070             icon = (RNA_property_boolean_get_index(ptr, prop, index)) ? ICON_CHECKBOX_HLT :
2071                                                                         ICON_CHECKBOX_DEHLT;
2072           }
2073           else {
2074             icon = (RNA_property_boolean_get(ptr, prop)) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
2075           }
2076         }
2077       }
2078       else if (type == PROP_ENUM) {
2079         if (index == RNA_ENUM_VALUE) {
2080           const int enum_value = RNA_property_enum_get(ptr, prop);
2081           if (prop_flag & PROP_ICONS_CONSECUTIVE) {
2082             icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */
2083           }
2084           else if (prop_flag & PROP_ENUM_FLAG) {
2085             icon = (enum_value & value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
2086           }
2087           else {
2088             icon = (enum_value == value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
2089           }
2090         }
2091       }
2092     }
2093   }
2094 
2095 #ifdef UI_PROP_SEP_ICON_WIDTH_EXCEPTION
2096   if (use_prop_sep) {
2097     if (type == PROP_BOOLEAN && (icon == ICON_NONE) && !icon_only) {
2098       use_prop_sep_split_label = false;
2099       /* For check-boxes we make an exception: We allow showing them in a split row even without
2100        * label. It typically relates to its neighbor items, so no need for an extra label. */
2101       use_split_empty_name = true;
2102     }
2103   }
2104 #endif
2105 
2106   if ((type == PROP_ENUM) && (RNA_property_flag(prop) & PROP_ENUM_FLAG)) {
2107     flag |= UI_ITEM_R_EXPAND;
2108   }
2109 
2110   const bool slider = (flag & UI_ITEM_R_SLIDER) != 0;
2111   const bool expand = (flag & UI_ITEM_R_EXPAND) != 0;
2112   const bool no_bg = (flag & UI_ITEM_R_NO_BG) != 0;
2113   const bool compact = (flag & UI_ITEM_R_COMPACT) != 0;
2114 
2115   /* get size */
2116   int w, h;
2117   ui_item_rna_size(layout, name, icon, ptr, prop, index, icon_only, compact, &w, &h);
2118 
2119   const int prev_emboss = layout->emboss;
2120   if (no_bg) {
2121     layout->emboss = UI_EMBOSS_NONE;
2122   }
2123 
2124   uiBut *but = NULL;
2125 
2126   /* Split the label / property. */
2127   uiLayout *layout_parent = layout;
2128 
2129   if (use_prop_sep) {
2130     uiLayout *layout_row = NULL;
2131 #ifdef UI_PROP_DECORATE
2132     if (ui_decorate.use_prop_decorate) {
2133       layout_row = uiLayoutRow(layout, true);
2134       layout_row->space = 0;
2135       ui_decorate.len = max_ii(1, len);
2136     }
2137 #endif /* UI_PROP_DECORATE */
2138 
2139     if ((name[0] == '\0') && !use_split_empty_name) {
2140       /* Ensure we get a column when text is not set. */
2141       layout = uiLayoutColumn(layout_row ? layout_row : layout, true);
2142       layout->space = 0;
2143       if (heading_layout) {
2144         ui_layout_heading_label_add(layout, heading_layout, false, false);
2145       }
2146     }
2147     else {
2148       uiLayout *layout_split = uiLayoutSplit(
2149           layout_row ? layout_row : layout, UI_ITEM_PROP_SEP_DIVIDE, true);
2150       bool label_added = false;
2151       layout_split->space = 0;
2152       uiLayout *layout_sub = uiLayoutColumn(layout_split, true);
2153       layout_sub->space = 0;
2154 
2155       if (!use_prop_sep_split_label) {
2156         /* Pass */
2157       }
2158       else if (ui_item_rna_is_expand(prop, index, flag)) {
2159         char name_with_suffix[UI_MAX_DRAW_STR + 2];
2160         char str[2] = {'\0'};
2161         for (int a = 0; a < len; a++) {
2162           str[0] = RNA_property_array_item_char(prop, a);
2163           const bool use_prefix = (a == 0 && name && name[0]);
2164           if (use_prefix) {
2165             char *s = name_with_suffix;
2166             s += STRNCPY_RLEN(name_with_suffix, name);
2167             *s++ = ' ';
2168             *s++ = str[0];
2169             *s++ = '\0';
2170           }
2171           but = uiDefBut(block,
2172                          UI_BTYPE_LABEL,
2173                          0,
2174                          use_prefix ? name_with_suffix : str,
2175                          0,
2176                          0,
2177                          w,
2178                          UI_UNIT_Y,
2179                          NULL,
2180                          0.0,
2181                          0.0,
2182                          0,
2183                          0,
2184                          "");
2185           but->drawflag |= UI_BUT_TEXT_RIGHT;
2186           but->drawflag &= ~UI_BUT_TEXT_LEFT;
2187 
2188           label_added = true;
2189         }
2190       }
2191       else {
2192         if (name) {
2193           but = uiDefBut(
2194               block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
2195           but->drawflag |= UI_BUT_TEXT_RIGHT;
2196           but->drawflag &= ~UI_BUT_TEXT_LEFT;
2197 
2198           label_added = true;
2199         }
2200       }
2201 
2202       if (!label_added && heading_layout) {
2203         ui_layout_heading_label_add(layout_sub, heading_layout, true, false);
2204       }
2205 
2206       layout_split = ui_item_prop_split_layout_hack(layout_parent, layout_split);
2207 
2208       /* Watch out! We can only write into the new layout now. */
2209       if ((type == PROP_ENUM) && (flag & UI_ITEM_R_EXPAND)) {
2210         /* Expanded enums each have their own name. */
2211 
2212         /* Often expanded enum's are better arranged into a row,
2213          * so check the existing layout. */
2214         if (uiLayoutGetLocalDir(layout) == UI_LAYOUT_HORIZONTAL) {
2215           layout = uiLayoutRow(layout_split, true);
2216         }
2217         else {
2218           layout = uiLayoutColumn(layout_split, true);
2219         }
2220       }
2221       else {
2222         if (use_prop_sep_split_label) {
2223           name = "";
2224         }
2225         layout = uiLayoutColumn(layout_split, true);
2226       }
2227       layout->space = 0;
2228     }
2229 
2230 #ifdef UI_PROP_DECORATE
2231     if (ui_decorate.use_prop_decorate) {
2232       ui_decorate.layout = uiLayoutColumn(layout_row, true);
2233       ui_decorate.layout->space = 0;
2234       UI_block_layout_set_current(block, layout);
2235       ui_decorate.but = block->buttons.last;
2236 
2237       /* Clear after. */
2238       layout->item.flag |= UI_ITEM_PROP_DECORATE_NO_PAD;
2239     }
2240 #endif /* UI_PROP_DECORATE */
2241   }
2242   /* End split. */
2243   else if (heading_layout) {
2244     /* Could not add heading to split layout, fallback to inserting it to the layout with the
2245      * heading itself. */
2246     ui_layout_heading_label_add(heading_layout, heading_layout, false, false);
2247   }
2248 
2249   /* array property */
2250   if (index == RNA_NO_INDEX && is_array) {
2251     if (inside_prop_sep) {
2252       /* Within a split row, add array items to a column so they match the column layout of
2253        * previous items (e.g. transform vector with lock icon for each item). */
2254       layout = uiLayoutColumn(layout, true);
2255     }
2256 
2257     ui_item_array(layout,
2258                   block,
2259                   name,
2260                   icon,
2261                   ptr,
2262                   prop,
2263                   len,
2264                   0,
2265                   0,
2266                   w,
2267                   h,
2268                   expand,
2269                   slider,
2270                   toggle,
2271                   icon_only,
2272                   compact,
2273                   !use_prop_sep_split_label);
2274   }
2275   /* enum item */
2276   else if (type == PROP_ENUM && index == RNA_ENUM_VALUE) {
2277     if (icon && name[0] && !icon_only) {
2278       uiDefIconTextButR_prop(
2279           block, UI_BTYPE_ROW, 0, icon, name, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL);
2280     }
2281     else if (icon) {
2282       uiDefIconButR_prop(
2283           block, UI_BTYPE_ROW, 0, icon, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL);
2284     }
2285     else {
2286       uiDefButR_prop(
2287           block, UI_BTYPE_ROW, 0, name, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL);
2288     }
2289   }
2290   /* expanded enum */
2291   else if (type == PROP_ENUM && expand) {
2292     ui_item_enum_expand(layout, block, ptr, prop, name, h, icon_only);
2293   }
2294   /* property with separate label */
2295   else if (type == PROP_ENUM || type == PROP_STRING || type == PROP_POINTER) {
2296     but = ui_item_with_label(layout, block, name, icon, ptr, prop, index, 0, 0, w, h, flag);
2297     but = ui_but_add_search(but, ptr, prop, NULL, NULL);
2298 
2299     if (layout->redalert) {
2300       UI_but_flag_enable(but, UI_BUT_REDALERT);
2301     }
2302 
2303     if (layout->activate_init) {
2304       UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
2305     }
2306   }
2307   /* single button */
2308   else {
2309     but = uiDefAutoButR(block, ptr, prop, index, name, icon, 0, 0, w, h);
2310 
2311     if (slider && but->type == UI_BTYPE_NUM) {
2312       uiButNumber *num_but = (uiButNumber *)but;
2313 
2314       but->a1 = num_but->step_size;
2315       but = ui_but_change_type(but, UI_BTYPE_NUM_SLIDER);
2316     }
2317 
2318     if (flag & UI_ITEM_R_CHECKBOX_INVERT) {
2319       if (ELEM(but->type,
2320                UI_BTYPE_CHECKBOX,
2321                UI_BTYPE_CHECKBOX_N,
2322                UI_BTYPE_ICON_TOGGLE,
2323                UI_BTYPE_ICON_TOGGLE_N)) {
2324         but->drawflag |= UI_BUT_CHECKBOX_INVERT;
2325       }
2326     }
2327 
2328     if ((toggle == 1) && but->type == UI_BTYPE_CHECKBOX) {
2329       but->type = UI_BTYPE_TOGGLE;
2330     }
2331 
2332     if (layout->redalert) {
2333       UI_but_flag_enable(but, UI_BUT_REDALERT);
2334     }
2335 
2336     if (layout->activate_init) {
2337       UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
2338     }
2339   }
2340 
2341   /* The resulting button may have the icon set since boolean button drawing
2342    * is being 'helpful' and adding an icon for us.
2343    * In this case we want the ability not to have an icon.
2344    *
2345    * We could pass an argument not to set the icon to begin with however this is the one case
2346    * the functionality is needed.  */
2347   if (but && no_icon) {
2348     if ((icon == ICON_NONE) && (but->icon != ICON_NONE)) {
2349       ui_def_but_icon_clear(but);
2350     }
2351   }
2352 
2353   /* Mark non-embossed textfields inside a listbox. */
2354   if (but && (block->flag & UI_BLOCK_LIST_ITEM) && (but->type == UI_BTYPE_TEXT) &&
2355       (but->emboss & UI_EMBOSS_NONE)) {
2356     UI_but_flag_enable(but, UI_BUT_LIST_ITEM);
2357   }
2358 
2359 #ifdef UI_PROP_DECORATE
2360   if (ui_decorate.use_prop_decorate) {
2361     uiBut *but_decorate = ui_decorate.but ? ui_decorate.but->next : block->buttons.first;
2362     const bool use_blank_decorator = (flag & UI_ITEM_R_FORCE_BLANK_DECORATE);
2363     uiLayout *layout_col = uiLayoutColumn(ui_decorate.layout, false);
2364     layout_col->space = 0;
2365     layout_col->emboss = UI_EMBOSS_NONE;
2366 
2367     int i;
2368     for (i = 0; i < ui_decorate.len && but_decorate; i++) {
2369       PointerRNA *ptr_dec = use_blank_decorator ? NULL : &but_decorate->rnapoin;
2370       PropertyRNA *prop_dec = use_blank_decorator ? NULL : but_decorate->rnaprop;
2371 
2372       /* The icons are set in 'ui_but_anim_flag' */
2373       uiItemDecoratorR_prop(layout_col, ptr_dec, prop_dec, but_decorate->rnaindex);
2374       but = block->buttons.last;
2375 
2376       /* Order the decorator after the button we decorate, this is used so we can always
2377        * do a quick lookup. */
2378       BLI_remlink(&block->buttons, but);
2379       BLI_insertlinkafter(&block->buttons, but_decorate, but);
2380       but_decorate = but->next;
2381     }
2382     BLI_assert(ELEM(i, 1, ui_decorate.len));
2383 
2384     layout->item.flag &= ~UI_ITEM_PROP_DECORATE_NO_PAD;
2385   }
2386 #endif /* UI_PROP_DECORATE */
2387 
2388   if (no_bg) {
2389     layout->emboss = prev_emboss;
2390   }
2391 
2392   /* ensure text isn't added to icon_only buttons */
2393   if (but && icon_only) {
2394     BLI_assert(but->str[0] == '\0');
2395   }
2396 }
2397 
uiItemR(uiLayout * layout,PointerRNA * ptr,const char * propname,int flag,const char * name,int icon)2398 void uiItemR(
2399     uiLayout *layout, PointerRNA *ptr, const char *propname, int flag, const char *name, int icon)
2400 {
2401   PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
2402 
2403   if (!prop) {
2404     ui_item_disabled(layout, propname);
2405     RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname);
2406     return;
2407   }
2408 
2409   uiItemFullR(layout, ptr, prop, RNA_NO_INDEX, 0, flag, name, icon);
2410 }
2411 
2412 /**
2413  * Use a wrapper function since re-implementing all the logic in this function would be messy.
2414  */
uiItemFullR_with_popover(uiLayout * layout,PointerRNA * ptr,PropertyRNA * prop,int index,int value,int flag,const char * name,int icon,const char * panel_type)2415 void uiItemFullR_with_popover(uiLayout *layout,
2416                               PointerRNA *ptr,
2417                               PropertyRNA *prop,
2418                               int index,
2419                               int value,
2420                               int flag,
2421                               const char *name,
2422                               int icon,
2423                               const char *panel_type)
2424 {
2425   uiBlock *block = layout->root->block;
2426   uiBut *but = block->buttons.last;
2427   uiItemFullR(layout, ptr, prop, index, value, flag, name, icon);
2428   but = but->next;
2429   while (but) {
2430     if (but->rnaprop == prop && ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_COLOR)) {
2431       ui_but_rna_menu_convert_to_panel_type(but, panel_type);
2432       break;
2433     }
2434     but = but->next;
2435   }
2436   if (but == NULL) {
2437     const char *propname = RNA_property_identifier(prop);
2438     ui_item_disabled(layout, panel_type);
2439     RNA_warning("property could not use a popover: %s.%s (%s)",
2440                 RNA_struct_identifier(ptr->type),
2441                 propname,
2442                 panel_type);
2443   }
2444 }
2445 
uiItemFullR_with_menu(uiLayout * layout,PointerRNA * ptr,PropertyRNA * prop,int index,int value,int flag,const char * name,int icon,const char * menu_type)2446 void uiItemFullR_with_menu(uiLayout *layout,
2447                            PointerRNA *ptr,
2448                            PropertyRNA *prop,
2449                            int index,
2450                            int value,
2451                            int flag,
2452                            const char *name,
2453                            int icon,
2454                            const char *menu_type)
2455 {
2456   uiBlock *block = layout->root->block;
2457   uiBut *but = block->buttons.last;
2458   uiItemFullR(layout, ptr, prop, index, value, flag, name, icon);
2459   but = but->next;
2460   while (but) {
2461     if (but->rnaprop == prop && but->type == UI_BTYPE_MENU) {
2462       ui_but_rna_menu_convert_to_menu_type(but, menu_type);
2463       break;
2464     }
2465     but = but->next;
2466   }
2467   if (but == NULL) {
2468     const char *propname = RNA_property_identifier(prop);
2469     ui_item_disabled(layout, menu_type);
2470     RNA_warning("property could not use a menu: %s.%s (%s)",
2471                 RNA_struct_identifier(ptr->type),
2472                 propname,
2473                 menu_type);
2474   }
2475 }
2476 
uiItemEnumR_prop(uiLayout * layout,const char * name,int icon,struct PointerRNA * ptr,PropertyRNA * prop,int value)2477 void uiItemEnumR_prop(uiLayout *layout,
2478                       const char *name,
2479                       int icon,
2480                       struct PointerRNA *ptr,
2481                       PropertyRNA *prop,
2482                       int value)
2483 {
2484   if (RNA_property_type(prop) != PROP_ENUM) {
2485     const char *propname = RNA_property_identifier(prop);
2486     ui_item_disabled(layout, propname);
2487     RNA_warning("property not an enum: %s.%s", RNA_struct_identifier(ptr->type), propname);
2488     return;
2489   }
2490 
2491   uiItemFullR(layout, ptr, prop, RNA_ENUM_VALUE, value, 0, name, icon);
2492 }
2493 
uiItemEnumR(uiLayout * layout,const char * name,int icon,struct PointerRNA * ptr,const char * propname,int value)2494 void uiItemEnumR(uiLayout *layout,
2495                  const char *name,
2496                  int icon,
2497                  struct PointerRNA *ptr,
2498                  const char *propname,
2499                  int value)
2500 {
2501   PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
2502 
2503   if (prop == NULL) {
2504     ui_item_disabled(layout, propname);
2505     RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname);
2506     return;
2507   }
2508 
2509   uiItemFullR(layout, ptr, prop, RNA_ENUM_VALUE, value, 0, name, icon);
2510 }
2511 
uiItemEnumR_string_prop(uiLayout * layout,struct PointerRNA * ptr,PropertyRNA * prop,const char * value,const char * name,int icon)2512 void uiItemEnumR_string_prop(uiLayout *layout,
2513                              struct PointerRNA *ptr,
2514                              PropertyRNA *prop,
2515                              const char *value,
2516                              const char *name,
2517                              int icon)
2518 {
2519   if (UNLIKELY(RNA_property_type(prop) != PROP_ENUM)) {
2520     const char *propname = RNA_property_identifier(prop);
2521     ui_item_disabled(layout, propname);
2522     RNA_warning("not an enum property: %s.%s", RNA_struct_identifier(ptr->type), propname);
2523     return;
2524   }
2525 
2526   const EnumPropertyItem *item;
2527   bool free;
2528   RNA_property_enum_items(layout->root->block->evil_C, ptr, prop, &item, NULL, &free);
2529 
2530   int ivalue;
2531   if (!RNA_enum_value_from_id(item, value, &ivalue)) {
2532     const char *propname = RNA_property_identifier(prop);
2533     if (free) {
2534       MEM_freeN((void *)item);
2535     }
2536     ui_item_disabled(layout, propname);
2537     RNA_warning("enum property value not found: %s", value);
2538     return;
2539   }
2540 
2541   for (int a = 0; item[a].identifier; a++) {
2542     if (item[a].identifier[0] == '\0') {
2543       /* Skip enum item separators. */
2544       continue;
2545     }
2546     if (item[a].value == ivalue) {
2547       const char *item_name = name ?
2548                                   name :
2549                                   CTX_IFACE_(RNA_property_translation_context(prop), item[a].name);
2550       const int flag = item_name[0] ? 0 : UI_ITEM_R_ICON_ONLY;
2551 
2552       uiItemFullR(
2553           layout, ptr, prop, RNA_ENUM_VALUE, ivalue, flag, item_name, icon ? icon : item[a].icon);
2554       break;
2555     }
2556   }
2557 
2558   if (free) {
2559     MEM_freeN((void *)item);
2560   }
2561 }
2562 
uiItemEnumR_string(uiLayout * layout,struct PointerRNA * ptr,const char * propname,const char * value,const char * name,int icon)2563 void uiItemEnumR_string(uiLayout *layout,
2564                         struct PointerRNA *ptr,
2565                         const char *propname,
2566                         const char *value,
2567                         const char *name,
2568                         int icon)
2569 {
2570   PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
2571   if (UNLIKELY(prop == NULL)) {
2572     ui_item_disabled(layout, propname);
2573     RNA_warning("enum property not found: %s.%s", RNA_struct_identifier(ptr->type), propname);
2574     return;
2575   }
2576   uiItemEnumR_string_prop(layout, ptr, prop, value, name, icon);
2577 }
2578 
uiItemsEnumR(uiLayout * layout,struct PointerRNA * ptr,const char * propname)2579 void uiItemsEnumR(uiLayout *layout, struct PointerRNA *ptr, const char *propname)
2580 {
2581   uiBlock *block = layout->root->block;
2582 
2583   PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
2584 
2585   if (!prop) {
2586     ui_item_disabled(layout, propname);
2587     RNA_warning("enum property not found: %s.%s", RNA_struct_identifier(ptr->type), propname);
2588     return;
2589   }
2590 
2591   if (RNA_property_type(prop) != PROP_ENUM) {
2592     RNA_warning("not an enum property: %s.%s", RNA_struct_identifier(ptr->type), propname);
2593     return;
2594   }
2595 
2596   uiLayout *split = uiLayoutSplit(layout, 0.0f, false);
2597   uiLayout *column = uiLayoutColumn(split, false);
2598 
2599   int totitem;
2600   const EnumPropertyItem *item;
2601   bool free;
2602   RNA_property_enum_items_gettexted(block->evil_C, ptr, prop, &item, &totitem, &free);
2603 
2604   for (int i = 0; i < totitem; i++) {
2605     if (item[i].identifier[0]) {
2606       uiItemEnumR_prop(column, item[i].name, item[i].icon, ptr, prop, item[i].value);
2607       ui_but_tip_from_enum_item(block->buttons.last, &item[i]);
2608     }
2609     else {
2610       if (item[i].name) {
2611         if (i != 0) {
2612           column = uiLayoutColumn(split, false);
2613           /* inconsistent, but menus with labels do not look good flipped */
2614           block->flag |= UI_BLOCK_NO_FLIP;
2615         }
2616 
2617         uiItemL(column, item[i].name, ICON_NONE);
2618         uiBut *bt = block->buttons.last;
2619         bt->drawflag = UI_BUT_TEXT_LEFT;
2620 
2621         ui_but_tip_from_enum_item(bt, &item[i]);
2622       }
2623       else {
2624         uiItemS(column);
2625       }
2626     }
2627   }
2628 
2629   if (free) {
2630     MEM_freeN((void *)item);
2631   }
2632 
2633   /* intentionally don't touch UI_BLOCK_IS_FLIP here,
2634    * we don't know the context this is called in */
2635 }
2636 
2637 /* Pointer RNA button with search */
2638 
search_id_collection(StructRNA * ptype,PointerRNA * r_ptr,PropertyRNA ** r_prop)2639 static void search_id_collection(StructRNA *ptype, PointerRNA *r_ptr, PropertyRNA **r_prop)
2640 {
2641   /* look for collection property in Main */
2642   /* Note: using global Main is OK-ish here, UI shall not access other Mains anyay... */
2643   RNA_main_pointer_create(G_MAIN, r_ptr);
2644 
2645   *r_prop = NULL;
2646 
2647   RNA_STRUCT_BEGIN (r_ptr, iprop) {
2648     /* if it's a collection and has same pointer type, we've got it */
2649     if (RNA_property_type(iprop) == PROP_COLLECTION) {
2650       StructRNA *srna = RNA_property_pointer_type(r_ptr, iprop);
2651 
2652       if (ptype == srna) {
2653         *r_prop = iprop;
2654         break;
2655       }
2656     }
2657   }
2658   RNA_STRUCT_END;
2659 }
2660 
ui_rna_collection_search_arg_free_fn(void * ptr)2661 static void ui_rna_collection_search_arg_free_fn(void *ptr)
2662 {
2663   uiRNACollectionSearch *coll_search = ptr;
2664   UI_butstore_free(coll_search->butstore_block, coll_search->butstore);
2665   MEM_freeN(ptr);
2666 }
2667 
2668 /**
2669  * \note May reallocate \a but, so the possibly new address is returned.
2670  */
ui_but_add_search(uiBut * but,PointerRNA * ptr,PropertyRNA * prop,PointerRNA * searchptr,PropertyRNA * searchprop)2671 uiBut *ui_but_add_search(
2672     uiBut *but, PointerRNA *ptr, PropertyRNA *prop, PointerRNA *searchptr, PropertyRNA *searchprop)
2673 {
2674   /* for ID's we do automatic lookup */
2675   PointerRNA sptr;
2676   if (!searchprop) {
2677     if (RNA_property_type(prop) == PROP_POINTER) {
2678       StructRNA *ptype = RNA_property_pointer_type(ptr, prop);
2679       search_id_collection(ptype, &sptr, &searchprop);
2680       searchptr = &sptr;
2681     }
2682   }
2683 
2684   /* turn button into search button */
2685   if (searchprop) {
2686     uiRNACollectionSearch *coll_search = MEM_mallocN(sizeof(*coll_search), __func__);
2687     uiButSearch *search_but;
2688 
2689     but = ui_but_change_type(but, UI_BTYPE_SEARCH_MENU);
2690     search_but = (uiButSearch *)but;
2691     search_but->rnasearchpoin = *searchptr;
2692     search_but->rnasearchprop = searchprop;
2693     but->hardmax = MAX2(but->hardmax, 256.0f);
2694     but->drawflag |= UI_BUT_ICON_LEFT | UI_BUT_TEXT_LEFT;
2695     if (RNA_property_is_unlink(prop)) {
2696       but->flag |= UI_BUT_VALUE_CLEAR;
2697     }
2698 
2699     coll_search->target_ptr = *ptr;
2700     coll_search->target_prop = prop;
2701     coll_search->search_ptr = *searchptr;
2702     coll_search->search_prop = searchprop;
2703     coll_search->search_but = but;
2704     coll_search->butstore_block = but->block;
2705     coll_search->butstore = UI_butstore_create(coll_search->butstore_block);
2706     UI_butstore_register(coll_search->butstore, &coll_search->search_but);
2707 
2708     if (RNA_property_type(prop) == PROP_ENUM) {
2709       /* XXX, this will have a menu string,
2710        * but in this case we just want the text */
2711       but->str[0] = 0;
2712     }
2713 
2714     UI_but_func_search_set(but,
2715                            ui_searchbox_create_generic,
2716                            ui_rna_collection_search_update_fn,
2717                            coll_search,
2718                            ui_rna_collection_search_arg_free_fn,
2719                            NULL,
2720                            NULL);
2721   }
2722   else if (but->type == UI_BTYPE_SEARCH_MENU) {
2723     /* In case we fail to find proper searchprop,
2724      * so other code might have already set but->type to search menu... */
2725     but->flag |= UI_BUT_DISABLED;
2726   }
2727 
2728   return but;
2729 }
2730 
uiItemPointerR_prop(uiLayout * layout,PointerRNA * ptr,PropertyRNA * prop,PointerRNA * searchptr,PropertyRNA * searchprop,const char * name,int icon)2731 void uiItemPointerR_prop(uiLayout *layout,
2732                          PointerRNA *ptr,
2733                          PropertyRNA *prop,
2734                          PointerRNA *searchptr,
2735                          PropertyRNA *searchprop,
2736                          const char *name,
2737                          int icon)
2738 {
2739   const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0);
2740 
2741   ui_block_new_button_group(uiLayoutGetBlock(layout), 0);
2742 
2743   const PropertyType type = RNA_property_type(prop);
2744   if (!ELEM(type, PROP_POINTER, PROP_STRING, PROP_ENUM)) {
2745     RNA_warning("Property %s.%s must be a pointer, string or enum",
2746                 RNA_struct_identifier(ptr->type),
2747                 RNA_property_identifier(prop));
2748     return;
2749   }
2750   if (RNA_property_type(searchprop) != PROP_COLLECTION) {
2751     RNA_warning("search collection property is not a collection type: %s.%s",
2752                 RNA_struct_identifier(searchptr->type),
2753                 RNA_property_identifier(searchprop));
2754     return;
2755   }
2756 
2757   /* get icon & name */
2758   if (icon == ICON_NONE) {
2759     StructRNA *icontype;
2760     if (type == PROP_POINTER) {
2761       icontype = RNA_property_pointer_type(ptr, prop);
2762     }
2763     else {
2764       icontype = RNA_property_pointer_type(searchptr, searchprop);
2765     }
2766 
2767     icon = RNA_struct_ui_icon(icontype);
2768   }
2769   if (!name) {
2770     name = RNA_property_ui_name(prop);
2771   }
2772 
2773   char namestr[UI_MAX_NAME_STR];
2774   if (use_prop_sep == false) {
2775     name = ui_item_name_add_colon(name, namestr);
2776   }
2777 
2778   /* create button */
2779   uiBlock *block = uiLayoutGetBlock(layout);
2780 
2781   int w, h;
2782   ui_item_rna_size(layout, name, icon, ptr, prop, 0, 0, false, &w, &h);
2783   w += UI_UNIT_X; /* X icon needs more space */
2784   uiBut *but = ui_item_with_label(layout, block, name, icon, ptr, prop, 0, 0, 0, w, h, 0);
2785 
2786   ui_but_add_search(but, ptr, prop, searchptr, searchprop);
2787 }
2788 
uiItemPointerR(uiLayout * layout,PointerRNA * ptr,const char * propname,PointerRNA * searchptr,const char * searchpropname,const char * name,int icon)2789 void uiItemPointerR(uiLayout *layout,
2790                     PointerRNA *ptr,
2791                     const char *propname,
2792                     PointerRNA *searchptr,
2793                     const char *searchpropname,
2794                     const char *name,
2795                     int icon)
2796 {
2797   /* validate arguments */
2798   PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
2799   if (!prop) {
2800     RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname);
2801     return;
2802   }
2803   PropertyRNA *searchprop = RNA_struct_find_property(searchptr, searchpropname);
2804   if (!searchprop) {
2805     RNA_warning("search collection property not found: %s.%s",
2806                 RNA_struct_identifier(searchptr->type),
2807                 searchpropname);
2808     return;
2809   }
2810 
2811   uiItemPointerR_prop(layout, ptr, prop, searchptr, searchprop, name, icon);
2812 }
2813 
2814 /* menu item */
ui_item_menutype_func(bContext * C,uiLayout * layout,void * arg_mt)2815 void ui_item_menutype_func(bContext *C, uiLayout *layout, void *arg_mt)
2816 {
2817   MenuType *mt = (MenuType *)arg_mt;
2818 
2819   UI_menutype_draw(C, mt, layout);
2820 
2821   /* menus are created flipped (from event handling pov) */
2822   layout->root->block->flag ^= UI_BLOCK_IS_FLIP;
2823 }
2824 
ui_item_paneltype_func(bContext * C,uiLayout * layout,void * arg_pt)2825 void ui_item_paneltype_func(bContext *C, uiLayout *layout, void *arg_pt)
2826 {
2827   PanelType *pt = (PanelType *)arg_pt;
2828   UI_paneltype_draw(C, pt, layout);
2829 
2830   /* panels are created flipped (from event handling pov) */
2831   layout->root->block->flag ^= UI_BLOCK_IS_FLIP;
2832 }
2833 
ui_item_menu(uiLayout * layout,const char * name,int icon,uiMenuCreateFunc func,void * arg,void * argN,const char * tip,bool force_menu)2834 static uiBut *ui_item_menu(uiLayout *layout,
2835                            const char *name,
2836                            int icon,
2837                            uiMenuCreateFunc func,
2838                            void *arg,
2839                            void *argN,
2840                            const char *tip,
2841                            bool force_menu)
2842 {
2843   uiBlock *block = layout->root->block;
2844   uiLayout *heading_layout = ui_layout_heading_find(layout);
2845 
2846   UI_block_layout_set_current(block, layout);
2847   ui_block_new_button_group(block, 0);
2848 
2849   if (!name) {
2850     name = "";
2851   }
2852   if (layout->root->type == UI_LAYOUT_MENU && !icon) {
2853     icon = ICON_BLANK1;
2854   }
2855 
2856   int w = ui_text_icon_width(layout, name, icon, 1);
2857   int h = UI_UNIT_Y;
2858 
2859   if (layout->root->type == UI_LAYOUT_HEADER) { /* ugly .. */
2860     if (icon == ICON_NONE && force_menu) {
2861       /* pass */
2862     }
2863     else if (force_menu) {
2864       w += 0.6f * UI_UNIT_X;
2865     }
2866     else {
2867       if (name[0]) {
2868         w -= UI_UNIT_X / 2;
2869       }
2870     }
2871   }
2872 
2873   if (heading_layout) {
2874     ui_layout_heading_label_add(layout, heading_layout, true, true);
2875   }
2876 
2877   uiBut *but;
2878   if (name[0] && icon) {
2879     but = uiDefIconTextMenuBut(block, func, arg, icon, name, 0, 0, w, h, tip);
2880   }
2881   else if (icon) {
2882     but = uiDefIconMenuBut(block, func, arg, icon, 0, 0, w, h, tip);
2883     if (force_menu && name[0]) {
2884       UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT);
2885     }
2886   }
2887   else {
2888     but = uiDefMenuBut(block, func, arg, name, 0, 0, w, h, tip);
2889   }
2890 
2891   if (argN) {
2892     /* ugly .. */
2893     if (arg != argN) {
2894       but->poin = (char *)but;
2895     }
2896     but->func_argN = argN;
2897   }
2898 
2899   if (ELEM(layout->root->type, UI_LAYOUT_PANEL, UI_LAYOUT_TOOLBAR) ||
2900       /* We never want a drop-down in menu! */
2901       (force_menu && layout->root->type != UI_LAYOUT_MENU)) {
2902     UI_but_type_set_menu_from_pulldown(but);
2903   }
2904 
2905   return but;
2906 }
2907 
uiItemM_ptr(uiLayout * layout,MenuType * mt,const char * name,int icon)2908 void uiItemM_ptr(uiLayout *layout, MenuType *mt, const char *name, int icon)
2909 {
2910   if (!name) {
2911     name = CTX_IFACE_(mt->translation_context, mt->label);
2912   }
2913 
2914   if (layout->root->type == UI_LAYOUT_MENU && !icon) {
2915     icon = ICON_BLANK1;
2916   }
2917 
2918   ui_item_menu(layout,
2919                name,
2920                icon,
2921                ui_item_menutype_func,
2922                mt,
2923                NULL,
2924                mt->description ? TIP_(mt->description) : "",
2925                false);
2926 }
2927 
uiItemM(uiLayout * layout,const char * menuname,const char * name,int icon)2928 void uiItemM(uiLayout *layout, const char *menuname, const char *name, int icon)
2929 {
2930   MenuType *mt = WM_menutype_find(menuname, false);
2931   if (mt == NULL) {
2932     RNA_warning("not found %s", menuname);
2933     return;
2934   }
2935   uiItemM_ptr(layout, mt, name, icon);
2936 }
2937 
uiItemMContents(uiLayout * layout,const char * menuname)2938 void uiItemMContents(uiLayout *layout, const char *menuname)
2939 {
2940   MenuType *mt = WM_menutype_find(menuname, false);
2941   if (mt == NULL) {
2942     RNA_warning("not found %s", menuname);
2943     return;
2944   }
2945 
2946   uiBlock *block = layout->root->block;
2947   bContext *C = block->evil_C;
2948   UI_menutype_draw(C, mt, layout);
2949 }
2950 
2951 /**
2952  * Insert a decorator item for a button with the same property as \a prop.
2953  * To force inserting a blank dummy element, NULL can be passed for \a ptr and \a prop.
2954  */
uiItemDecoratorR_prop(uiLayout * layout,PointerRNA * ptr,PropertyRNA * prop,int index)2955 void uiItemDecoratorR_prop(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop, int index)
2956 {
2957   uiBlock *block = layout->root->block;
2958 
2959   UI_block_layout_set_current(block, layout);
2960   uiLayout *col = uiLayoutColumn(layout, false);
2961   col->space = 0;
2962   col->emboss = UI_EMBOSS_NONE;
2963 
2964   if (ELEM(NULL, ptr, prop) || !RNA_property_animateable(ptr, prop)) {
2965     uiBut *but = uiDefIconBut(block,
2966                               UI_BTYPE_DECORATOR,
2967                               0,
2968                               ICON_BLANK1,
2969                               0,
2970                               0,
2971                               UI_UNIT_X,
2972                               UI_UNIT_Y,
2973                               NULL,
2974                               0.0,
2975                               0.0,
2976                               0.0,
2977                               0.0,
2978                               "");
2979     but->flag |= UI_BUT_DISABLED;
2980     return;
2981   }
2982 
2983   const bool is_expand = ui_item_rna_is_expand(prop, index, 0);
2984   const bool is_array = RNA_property_array_check(prop);
2985 
2986   /* Loop for the array-case, but only do in case of an expanded array. */
2987   for (int i = 0; i < (is_expand ? RNA_property_array_length(ptr, prop) : 1); i++) {
2988     uiButDecorator *decorator_but = (uiButDecorator *)uiDefIconBut(block,
2989                                                                    UI_BTYPE_DECORATOR,
2990                                                                    0,
2991                                                                    ICON_DOT,
2992                                                                    0,
2993                                                                    0,
2994                                                                    UI_UNIT_X,
2995                                                                    UI_UNIT_Y,
2996                                                                    NULL,
2997                                                                    0.0,
2998                                                                    0.0,
2999                                                                    0.0,
3000                                                                    0.0,
3001                                                                    TIP_("Animate property"));
3002 
3003     UI_but_func_set(&decorator_but->but, ui_but_anim_decorate_cb, decorator_but, NULL);
3004     decorator_but->but.flag |= UI_BUT_UNDO | UI_BUT_DRAG_LOCK;
3005     /* Reusing RNA search members, setting actual RNA data has many side-effects. */
3006     decorator_but->rnapoin = *ptr;
3007     decorator_but->rnaprop = prop;
3008     /* ui_def_but_rna() sets non-array buttons to have a RNA index of 0. */
3009     decorator_but->rnaindex = (!is_array || is_expand) ? i : index;
3010   }
3011 }
3012 
3013 /**
3014  * Insert a decorator item for a button with the same property as \a prop.
3015  * To force inserting a blank dummy element, NULL can be passed for \a ptr and \a propname.
3016  */
uiItemDecoratorR(uiLayout * layout,PointerRNA * ptr,const char * propname,int index)3017 void uiItemDecoratorR(uiLayout *layout, PointerRNA *ptr, const char *propname, int index)
3018 {
3019   PropertyRNA *prop = NULL;
3020 
3021   if (ptr && propname) {
3022     /* validate arguments */
3023     prop = RNA_struct_find_property(ptr, propname);
3024     if (!prop) {
3025       ui_item_disabled(layout, propname);
3026       RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname);
3027       return;
3028     }
3029   }
3030 
3031   /* ptr and prop are allowed to be NULL here. */
3032   uiItemDecoratorR_prop(layout, ptr, prop, index);
3033 }
3034 
3035 /* popover */
uiItemPopoverPanel_ptr(uiLayout * layout,bContext * C,PanelType * pt,const char * name,int icon)3036 void uiItemPopoverPanel_ptr(
3037     uiLayout *layout, bContext *C, PanelType *pt, const char *name, int icon)
3038 {
3039   if (!name) {
3040     name = CTX_IFACE_(pt->translation_context, pt->label);
3041   }
3042 
3043   if (layout->root->type == UI_LAYOUT_MENU && !icon) {
3044     icon = ICON_BLANK1;
3045   }
3046 
3047   const bool ok = (pt->poll == NULL) || pt->poll(C, pt);
3048   if (ok && (pt->draw_header != NULL)) {
3049     layout = uiLayoutRow(layout, true);
3050     Panel panel = {
3051         .type = pt,
3052         .layout = layout,
3053         .flag = PNL_POPOVER,
3054     };
3055     pt->draw_header(C, &panel);
3056   }
3057   uiBut *but = ui_item_menu(layout, name, icon, ui_item_paneltype_func, pt, NULL, NULL, true);
3058   but->type = UI_BTYPE_POPOVER;
3059   if (!ok) {
3060     but->flag |= UI_BUT_DISABLED;
3061   }
3062 }
3063 
uiItemPopoverPanel(uiLayout * layout,bContext * C,const char * panel_type,const char * name,int icon)3064 void uiItemPopoverPanel(
3065     uiLayout *layout, bContext *C, const char *panel_type, const char *name, int icon)
3066 {
3067   PanelType *pt = WM_paneltype_find(panel_type, true);
3068   if (pt == NULL) {
3069     RNA_warning("Panel type not found '%s'", panel_type);
3070     return;
3071   }
3072   uiItemPopoverPanel_ptr(layout, C, pt, name, icon);
3073 }
3074 
uiItemPopoverPanelFromGroup(uiLayout * layout,bContext * C,int space_id,int region_id,const char * context,const char * category)3075 void uiItemPopoverPanelFromGroup(uiLayout *layout,
3076                                  bContext *C,
3077                                  int space_id,
3078                                  int region_id,
3079                                  const char *context,
3080                                  const char *category)
3081 {
3082   SpaceType *st = BKE_spacetype_from_id(space_id);
3083   if (st == NULL) {
3084     RNA_warning("space type not found %d", space_id);
3085     return;
3086   }
3087   ARegionType *art = BKE_regiontype_from_id(st, region_id);
3088   if (art == NULL) {
3089     RNA_warning("region type not found %d", region_id);
3090     return;
3091   }
3092 
3093   LISTBASE_FOREACH (PanelType *, pt, &art->paneltypes) {
3094     /* Causes too many panels, check context. */
3095     if (pt->parent_id[0] == '\0') {
3096       if (/* (*context == '\0') || */ STREQ(pt->context, context)) {
3097         if ((*category == '\0') || STREQ(pt->category, category)) {
3098           if (pt->poll == NULL || pt->poll(C, pt)) {
3099             uiItemPopoverPanel_ptr(layout, C, pt, NULL, ICON_NONE);
3100           }
3101         }
3102       }
3103     }
3104   }
3105 }
3106 
3107 /* label item */
uiItemL_(uiLayout * layout,const char * name,int icon)3108 static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon)
3109 {
3110   uiBlock *block = layout->root->block;
3111 
3112   UI_block_layout_set_current(block, layout);
3113   ui_block_new_button_group(block, 0);
3114 
3115   if (!name) {
3116     name = "";
3117   }
3118   if (layout->root->type == UI_LAYOUT_MENU && !icon) {
3119     icon = ICON_BLANK1;
3120   }
3121 
3122   const int w = ui_text_icon_width(layout, name, icon, 0);
3123 
3124   uiBut *but;
3125   if (icon && name[0]) {
3126     but = uiDefIconTextBut(
3127         block, UI_BTYPE_LABEL, 0, icon, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, NULL);
3128   }
3129   else if (icon) {
3130     but = uiDefIconBut(
3131         block, UI_BTYPE_LABEL, 0, icon, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, NULL);
3132   }
3133   else {
3134     but = uiDefBut(block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, NULL);
3135   }
3136 
3137   /* to compensate for string size padding in ui_text_icon_width,
3138    * make text aligned right if the layout is aligned right.
3139    */
3140   if (uiLayoutGetAlignment(layout) == UI_LAYOUT_ALIGN_RIGHT) {
3141     but->drawflag &= ~UI_BUT_TEXT_LEFT; /* default, needs to be unset */
3142     but->drawflag |= UI_BUT_TEXT_RIGHT;
3143   }
3144 
3145   /* Mark as a label inside a listbox. */
3146   if (block->flag & UI_BLOCK_LIST_ITEM) {
3147     but->flag |= UI_BUT_LIST_ITEM;
3148   }
3149 
3150   if (layout->redalert) {
3151     UI_but_flag_enable(but, UI_BUT_REDALERT);
3152   }
3153 
3154   return but;
3155 }
3156 
uiItemL_ex(uiLayout * layout,const char * name,int icon,const bool highlight,const bool redalert)3157 void uiItemL_ex(
3158     uiLayout *layout, const char *name, int icon, const bool highlight, const bool redalert)
3159 {
3160   uiBut *but = uiItemL_(layout, name, icon);
3161 
3162   if (highlight) {
3163     /* TODO: add another flag for this. */
3164     UI_but_flag_enable(but, UI_SELECT_DRAW);
3165   }
3166 
3167   if (redalert) {
3168     UI_but_flag_enable(but, UI_BUT_REDALERT);
3169   }
3170 }
3171 
uiItemL(uiLayout * layout,const char * name,int icon)3172 void uiItemL(uiLayout *layout, const char *name, int icon)
3173 {
3174   uiItemL_(layout, name, icon);
3175 }
3176 
3177 /**
3178  * Normally, we handle the split layout in #uiItemFullR(), but there are other cases where the
3179  * logic is needed. Ideally, #uiItemFullR() could just call this, but it currently has too many
3180  * special needs.
3181  */
uiItemPropertySplitWrapperCreate(uiLayout * parent_layout)3182 uiPropertySplitWrapper uiItemPropertySplitWrapperCreate(uiLayout *parent_layout)
3183 {
3184   uiPropertySplitWrapper split_wrapper = {NULL};
3185 
3186   uiLayout *layout_row = uiLayoutRow(parent_layout, true);
3187   uiLayout *layout_split = uiLayoutSplit(layout_row, UI_ITEM_PROP_SEP_DIVIDE, true);
3188 
3189   layout_split->space = 0;
3190   split_wrapper.label_column = uiLayoutColumn(layout_split, true);
3191   split_wrapper.label_column->alignment = UI_LAYOUT_ALIGN_RIGHT;
3192   split_wrapper.property_row = ui_item_prop_split_layout_hack(parent_layout, layout_split);
3193   split_wrapper.decorate_column = uiLayoutColumn(layout_row, true);
3194 
3195   return split_wrapper;
3196 }
3197 
3198 /*
3199  * Helper to add a label and creates a property split layout if needed.
3200  */
uiItemL_respect_property_split(uiLayout * layout,const char * text,int icon)3201 uiLayout *uiItemL_respect_property_split(uiLayout *layout, const char *text, int icon)
3202 {
3203   if (layout->item.flag & UI_ITEM_PROP_SEP) {
3204     uiBlock *block = uiLayoutGetBlock(layout);
3205     const uiPropertySplitWrapper split_wrapper = uiItemPropertySplitWrapperCreate(layout);
3206     /* Further items added to 'layout' will automatically be added to split_wrapper.property_row */
3207 
3208     uiItemL_(split_wrapper.label_column, text, icon);
3209     UI_block_layout_set_current(block, split_wrapper.property_row);
3210 
3211     return split_wrapper.decorate_column;
3212   }
3213 
3214   char namestr[UI_MAX_NAME_STR];
3215   if (text) {
3216     text = ui_item_name_add_colon(text, namestr);
3217   }
3218   uiItemL_(layout, text, icon);
3219 
3220   return layout;
3221 }
3222 
uiItemLDrag(uiLayout * layout,PointerRNA * ptr,const char * name,int icon)3223 void uiItemLDrag(uiLayout *layout, PointerRNA *ptr, const char *name, int icon)
3224 {
3225   uiBut *but = uiItemL_(layout, name, icon);
3226 
3227   if (ptr && ptr->type) {
3228     if (RNA_struct_is_ID(ptr->type)) {
3229       UI_but_drag_set_id(but, ptr->owner_id);
3230     }
3231   }
3232 }
3233 
3234 /* value item */
uiItemV(uiLayout * layout,const char * name,int icon,int argval)3235 void uiItemV(uiLayout *layout, const char *name, int icon, int argval)
3236 {
3237   /* label */
3238   uiBlock *block = layout->root->block;
3239   int *retvalue = (block->handle) ? &block->handle->retvalue : NULL;
3240 
3241   UI_block_layout_set_current(block, layout);
3242 
3243   if (!name) {
3244     name = "";
3245   }
3246   if (layout->root->type == UI_LAYOUT_MENU && !icon) {
3247     icon = ICON_BLANK1;
3248   }
3249 
3250   const int w = ui_text_icon_width(layout, name, icon, 0);
3251 
3252   if (icon && name[0]) {
3253     uiDefIconTextButI(block,
3254                       UI_BTYPE_BUT,
3255                       argval,
3256                       icon,
3257                       name,
3258                       0,
3259                       0,
3260                       w,
3261                       UI_UNIT_Y,
3262                       retvalue,
3263                       0.0,
3264                       0.0,
3265                       0,
3266                       -1,
3267                       "");
3268   }
3269   else if (icon) {
3270     uiDefIconButI(
3271         block, UI_BTYPE_BUT, argval, icon, 0, 0, w, UI_UNIT_Y, retvalue, 0.0, 0.0, 0, -1, "");
3272   }
3273   else {
3274     uiDefButI(
3275         block, UI_BTYPE_BUT, argval, name, 0, 0, w, UI_UNIT_Y, retvalue, 0.0, 0.0, 0, -1, "");
3276   }
3277 }
3278 
3279 /* separator item */
uiItemS_ex(uiLayout * layout,float factor)3280 void uiItemS_ex(uiLayout *layout, float factor)
3281 {
3282   uiBlock *block = layout->root->block;
3283   const bool is_menu = ui_block_is_menu(block);
3284   if (is_menu && !UI_block_can_add_separator(block)) {
3285     return;
3286   }
3287   int space = (is_menu) ? 0.45f * UI_UNIT_X : 0.3f * UI_UNIT_X;
3288   space *= factor;
3289 
3290   UI_block_layout_set_current(block, layout);
3291   uiDefBut(block,
3292            (is_menu) ? UI_BTYPE_SEPR_LINE : UI_BTYPE_SEPR,
3293            0,
3294            "",
3295            0,
3296            0,
3297            space,
3298            space,
3299            NULL,
3300            0.0,
3301            0.0,
3302            0,
3303            0,
3304            "");
3305 }
3306 
3307 /* separator item */
uiItemS(uiLayout * layout)3308 void uiItemS(uiLayout *layout)
3309 {
3310   uiItemS_ex(layout, 1.0f);
3311 }
3312 
3313 /* Flexible spacing. */
uiItemSpacer(uiLayout * layout)3314 void uiItemSpacer(uiLayout *layout)
3315 {
3316   uiBlock *block = layout->root->block;
3317   const bool is_popup = ui_block_is_popup_any(block);
3318 
3319   if (is_popup) {
3320     printf("Error: separator_spacer() not supported in popups.\n");
3321     return;
3322   }
3323 
3324   if (block->direction & UI_DIR_RIGHT) {
3325     printf("Error: separator_spacer() only supported in horizontal blocks.\n");
3326     return;
3327   }
3328 
3329   UI_block_layout_set_current(block, layout);
3330   uiDefBut(block,
3331            UI_BTYPE_SEPR_SPACER,
3332            0,
3333            "",
3334            0,
3335            0,
3336            0.3f * UI_UNIT_X,
3337            UI_UNIT_Y,
3338            NULL,
3339            0.0,
3340            0.0,
3341            0,
3342            0,
3343            "");
3344 }
3345 
3346 /* level items */
uiItemMenuF(uiLayout * layout,const char * name,int icon,uiMenuCreateFunc func,void * arg)3347 void uiItemMenuF(uiLayout *layout, const char *name, int icon, uiMenuCreateFunc func, void *arg)
3348 {
3349   if (!func) {
3350     return;
3351   }
3352 
3353   ui_item_menu(layout, name, icon, func, arg, NULL, "", false);
3354 }
3355 
3356 /**
3357  * Version of #uiItemMenuF that free's `argN`.
3358  */
uiItemMenuFN(uiLayout * layout,const char * name,int icon,uiMenuCreateFunc func,void * argN)3359 void uiItemMenuFN(uiLayout *layout, const char *name, int icon, uiMenuCreateFunc func, void *argN)
3360 {
3361   if (!func) {
3362     return;
3363   }
3364 
3365   /* Second 'argN' only ensures it gets freed. */
3366   ui_item_menu(layout, name, icon, func, argN, argN, "", false);
3367 }
3368 
3369 typedef struct MenuItemLevel {
3370   int opcontext;
3371   /* don't use pointers to the strings because python can dynamically
3372    * allocate strings and free before the menu draws, see T27304. */
3373   char opname[OP_MAX_TYPENAME];
3374   char propname[MAX_IDPROP_NAME];
3375   PointerRNA rnapoin;
3376 } MenuItemLevel;
3377 
menu_item_enum_opname_menu(bContext * UNUSED (C),uiLayout * layout,void * arg)3378 static void menu_item_enum_opname_menu(bContext *UNUSED(C), uiLayout *layout, void *arg)
3379 {
3380   MenuItemLevel *lvl = (MenuItemLevel *)(((uiBut *)arg)->func_argN);
3381 
3382   uiLayoutSetOperatorContext(layout, lvl->opcontext);
3383   uiItemsEnumO(layout, lvl->opname, lvl->propname);
3384 
3385   layout->root->block->flag |= UI_BLOCK_IS_FLIP;
3386 
3387   /* override default, needed since this was assumed pre 2.70 */
3388   UI_block_direction_set(layout->root->block, UI_DIR_DOWN);
3389 }
3390 
uiItemMenuEnumO_ptr(uiLayout * layout,bContext * C,wmOperatorType * ot,const char * propname,const char * name,int icon)3391 void uiItemMenuEnumO_ptr(uiLayout *layout,
3392                          bContext *C,
3393                          wmOperatorType *ot,
3394                          const char *propname,
3395                          const char *name,
3396                          int icon)
3397 {
3398   /* Caller must check */
3399   BLI_assert(ot->srna != NULL);
3400 
3401   if (name == NULL) {
3402     name = WM_operatortype_name(ot, NULL);
3403   }
3404 
3405   if (layout->root->type == UI_LAYOUT_MENU && !icon) {
3406     icon = ICON_BLANK1;
3407   }
3408 
3409   MenuItemLevel *lvl = MEM_callocN(sizeof(MenuItemLevel), "MenuItemLevel");
3410   BLI_strncpy(lvl->opname, ot->idname, sizeof(lvl->opname));
3411   BLI_strncpy(lvl->propname, propname, sizeof(lvl->propname));
3412   lvl->opcontext = layout->root->opcontext;
3413 
3414   uiBut *but = ui_item_menu(layout, name, icon, menu_item_enum_opname_menu, NULL, lvl, NULL, true);
3415 
3416   /* add hotkey here, lower UI code can't detect it */
3417   if ((layout->root->block->flag & UI_BLOCK_LOOP) && (ot->prop && ot->invoke)) {
3418     char keybuf[128];
3419     if (WM_key_event_operator_string(
3420             C, ot->idname, layout->root->opcontext, NULL, false, keybuf, sizeof(keybuf))) {
3421       ui_but_add_shortcut(but, keybuf, false);
3422     }
3423   }
3424 }
3425 
uiItemMenuEnumO(uiLayout * layout,bContext * C,const char * opname,const char * propname,const char * name,int icon)3426 void uiItemMenuEnumO(uiLayout *layout,
3427                      bContext *C,
3428                      const char *opname,
3429                      const char *propname,
3430                      const char *name,
3431                      int icon)
3432 {
3433   wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
3434 
3435   UI_OPERATOR_ERROR_RET(ot, opname, return );
3436 
3437   if (!ot->srna) {
3438     ui_item_disabled(layout, opname);
3439     RNA_warning("operator missing srna '%s'", opname);
3440     return;
3441   }
3442 
3443   uiItemMenuEnumO_ptr(layout, C, ot, propname, name, icon);
3444 }
3445 
menu_item_enum_rna_menu(bContext * UNUSED (C),uiLayout * layout,void * arg)3446 static void menu_item_enum_rna_menu(bContext *UNUSED(C), uiLayout *layout, void *arg)
3447 {
3448   MenuItemLevel *lvl = (MenuItemLevel *)(((uiBut *)arg)->func_argN);
3449 
3450   uiLayoutSetOperatorContext(layout, lvl->opcontext);
3451   uiItemsEnumR(layout, &lvl->rnapoin, lvl->propname);
3452   layout->root->block->flag |= UI_BLOCK_IS_FLIP;
3453 }
3454 
uiItemMenuEnumR_prop(uiLayout * layout,struct PointerRNA * ptr,PropertyRNA * prop,const char * name,int icon)3455 void uiItemMenuEnumR_prop(
3456     uiLayout *layout, struct PointerRNA *ptr, PropertyRNA *prop, const char *name, int icon)
3457 {
3458   MenuItemLevel *lvl;
3459 
3460   if (!name) {
3461     name = RNA_property_ui_name(prop);
3462   }
3463   if (layout->root->type == UI_LAYOUT_MENU && !icon) {
3464     icon = ICON_BLANK1;
3465   }
3466 
3467   lvl = MEM_callocN(sizeof(MenuItemLevel), "MenuItemLevel");
3468   lvl->rnapoin = *ptr;
3469   BLI_strncpy(lvl->propname, RNA_property_identifier(prop), sizeof(lvl->propname));
3470   lvl->opcontext = layout->root->opcontext;
3471 
3472   ui_item_menu(layout,
3473                name,
3474                icon,
3475                menu_item_enum_rna_menu,
3476                NULL,
3477                lvl,
3478                RNA_property_description(prop),
3479                false);
3480 }
3481 
uiItemMenuEnumR(uiLayout * layout,struct PointerRNA * ptr,const char * propname,const char * name,int icon)3482 void uiItemMenuEnumR(
3483     uiLayout *layout, struct PointerRNA *ptr, const char *propname, const char *name, int icon)
3484 {
3485   PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
3486   if (!prop) {
3487     ui_item_disabled(layout, propname);
3488     RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname);
3489     return;
3490   }
3491 
3492   uiItemMenuEnumR_prop(layout, ptr, prop, name, icon);
3493 }
3494 
uiItemTabsEnumR_prop(uiLayout * layout,bContext * C,PointerRNA * ptr,PropertyRNA * prop,PointerRNA * ptr_highlight,PropertyRNA * prop_highlight,bool icon_only)3495 void uiItemTabsEnumR_prop(uiLayout *layout,
3496                           bContext *C,
3497                           PointerRNA *ptr,
3498                           PropertyRNA *prop,
3499                           PointerRNA *ptr_highlight,
3500                           PropertyRNA *prop_highlight,
3501                           bool icon_only)
3502 {
3503   uiBlock *block = layout->root->block;
3504 
3505   UI_block_layout_set_current(block, layout);
3506   ui_item_enum_expand_tabs(
3507       layout, C, block, ptr, prop, ptr_highlight, prop_highlight, NULL, UI_UNIT_Y, icon_only);
3508 }
3509 
3510 /** \} */
3511 
3512 /* -------------------------------------------------------------------- */
3513 /** \name Layout Items
3514  * \{ */
3515 
3516 /* single-row layout */
ui_litem_estimate_row(uiLayout * litem)3517 static void ui_litem_estimate_row(uiLayout *litem)
3518 {
3519   int itemw, itemh;
3520   bool min_size_flag = true;
3521 
3522   litem->w = 0;
3523   litem->h = 0;
3524 
3525   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
3526     ui_item_size(item, &itemw, &itemh);
3527 
3528     min_size_flag = min_size_flag && (item->flag & UI_ITEM_FIXED_SIZE);
3529 
3530     litem->w += itemw;
3531     litem->h = MAX2(itemh, litem->h);
3532 
3533     if (item->next) {
3534       litem->w += litem->space;
3535     }
3536   }
3537 
3538   if (min_size_flag) {
3539     litem->item.flag |= UI_ITEM_FIXED_SIZE;
3540   }
3541 }
3542 
ui_litem_min_width(int itemw)3543 static int ui_litem_min_width(int itemw)
3544 {
3545   return MIN2(2 * UI_UNIT_X, itemw);
3546 }
3547 
ui_litem_layout_row(uiLayout * litem)3548 static void ui_litem_layout_row(uiLayout *litem)
3549 {
3550   uiItem *last_free_item = NULL;
3551   int x, y, w, tot, totw, neww, newtotw, itemw, minw, itemh, offset;
3552   int fixedw, freew, fixedx, freex, flag = 0, lastw = 0;
3553   float extra_pixel;
3554 
3555   /* x = litem->x; */ /* UNUSED */
3556   y = litem->y;
3557   w = litem->w;
3558   totw = 0;
3559   tot = 0;
3560 
3561   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
3562     ui_item_size(item, &itemw, &itemh);
3563     totw += itemw;
3564     tot++;
3565   }
3566 
3567   if (totw == 0) {
3568     return;
3569   }
3570 
3571   if (w != 0) {
3572     w -= (tot - 1) * litem->space;
3573   }
3574   fixedw = 0;
3575 
3576   /* keep clamping items to fixed minimum size until all are done */
3577   do {
3578     freew = 0;
3579     x = 0;
3580     flag = 0;
3581     newtotw = totw;
3582     extra_pixel = 0.0f;
3583 
3584     LISTBASE_FOREACH (uiItem *, item, &litem->items) {
3585       if (item->flag & UI_ITEM_AUTO_FIXED_SIZE) {
3586         continue;
3587       }
3588 
3589       ui_item_size(item, &itemw, &itemh);
3590       minw = ui_litem_min_width(itemw);
3591 
3592       if (w - lastw > 0) {
3593         neww = ui_item_fit(itemw, x, totw, w - lastw, !item->next, litem->alignment, &extra_pixel);
3594       }
3595       else {
3596         neww = 0; /* no space left, all will need clamping to minimum size */
3597       }
3598 
3599       x += neww;
3600 
3601       bool min_flag = item->flag & UI_ITEM_FIXED_SIZE;
3602       /* ignore min flag for rows with right or center alignment */
3603       if (item->type != ITEM_BUTTON &&
3604           ELEM(((uiLayout *)item)->alignment, UI_LAYOUT_ALIGN_RIGHT, UI_LAYOUT_ALIGN_CENTER) &&
3605           litem->alignment == UI_LAYOUT_ALIGN_EXPAND &&
3606           ((uiItem *)litem)->flag & UI_ITEM_FIXED_SIZE) {
3607         min_flag = false;
3608       }
3609 
3610       if ((neww < minw || min_flag) && w != 0) {
3611         /* fixed size */
3612         item->flag |= UI_ITEM_AUTO_FIXED_SIZE;
3613         if (item->type != ITEM_BUTTON && item->flag & UI_ITEM_FIXED_SIZE) {
3614           minw = itemw;
3615         }
3616         fixedw += minw;
3617         flag = 1;
3618         newtotw -= itemw;
3619       }
3620       else {
3621         /* keep free size */
3622         item->flag &= ~UI_ITEM_AUTO_FIXED_SIZE;
3623         freew += itemw;
3624       }
3625     }
3626 
3627     totw = newtotw;
3628     lastw = fixedw;
3629   } while (flag);
3630 
3631   freex = 0;
3632   fixedx = 0;
3633   extra_pixel = 0.0f;
3634   x = litem->x;
3635 
3636   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
3637     ui_item_size(item, &itemw, &itemh);
3638     minw = ui_litem_min_width(itemw);
3639 
3640     if (item->flag & UI_ITEM_AUTO_FIXED_SIZE) {
3641       /* fixed minimum size items */
3642       if (item->type != ITEM_BUTTON && item->flag & UI_ITEM_FIXED_SIZE) {
3643         minw = itemw;
3644       }
3645       itemw = ui_item_fit(
3646           minw, fixedx, fixedw, min_ii(w, fixedw), !item->next, litem->alignment, &extra_pixel);
3647       fixedx += itemw;
3648     }
3649     else {
3650       /* free size item */
3651       itemw = ui_item_fit(
3652           itemw, freex, freew, w - fixedw, !item->next, litem->alignment, &extra_pixel);
3653       freex += itemw;
3654       last_free_item = item;
3655     }
3656 
3657     /* align right/center */
3658     offset = 0;
3659     if (litem->alignment == UI_LAYOUT_ALIGN_RIGHT) {
3660       if (freew + fixedw > 0 && freew + fixedw < w) {
3661         offset = w - (fixedw + freew);
3662       }
3663     }
3664     else if (litem->alignment == UI_LAYOUT_ALIGN_CENTER) {
3665       if (freew + fixedw > 0 && freew + fixedw < w) {
3666         offset = (w - (fixedw + freew)) / 2;
3667       }
3668     }
3669 
3670     /* position item */
3671     ui_item_position(item, x + offset, y - itemh, itemw, itemh);
3672 
3673     x += itemw;
3674     if (item->next) {
3675       x += litem->space;
3676     }
3677   }
3678 
3679   /* add extra pixel */
3680   uiItem *last_item = litem->items.last;
3681   extra_pixel = litem->w - (x - litem->x);
3682   if (extra_pixel > 0 && litem->alignment == UI_LAYOUT_ALIGN_EXPAND && last_free_item &&
3683       last_item && last_item->flag & UI_ITEM_AUTO_FIXED_SIZE) {
3684     ui_item_move(last_free_item, 0, extra_pixel);
3685     for (uiItem *item = last_free_item->next; item; item = item->next) {
3686       ui_item_move(item, extra_pixel, extra_pixel);
3687     }
3688   }
3689 
3690   litem->w = x - litem->x;
3691   litem->h = litem->y - y;
3692   litem->x = x;
3693   litem->y = y;
3694 }
3695 
3696 /* single-column layout */
ui_litem_estimate_column(uiLayout * litem,bool is_box)3697 static void ui_litem_estimate_column(uiLayout *litem, bool is_box)
3698 {
3699   int itemw, itemh;
3700   bool min_size_flag = true;
3701 
3702   litem->w = 0;
3703   litem->h = 0;
3704 
3705   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
3706     ui_item_size(item, &itemw, &itemh);
3707 
3708     min_size_flag = min_size_flag && (item->flag & UI_ITEM_FIXED_SIZE);
3709 
3710     litem->w = MAX2(litem->w, itemw);
3711     litem->h += itemh;
3712 
3713     if (item->next && (!is_box || item != litem->items.first)) {
3714       litem->h += litem->space;
3715     }
3716   }
3717 
3718   if (min_size_flag) {
3719     litem->item.flag |= UI_ITEM_FIXED_SIZE;
3720   }
3721 }
3722 
ui_litem_layout_column(uiLayout * litem,bool is_box,bool is_menu)3723 static void ui_litem_layout_column(uiLayout *litem, bool is_box, bool is_menu)
3724 {
3725   int itemw, itemh, x, y;
3726 
3727   x = litem->x;
3728   y = litem->y;
3729 
3730   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
3731     ui_item_size(item, &itemw, &itemh);
3732 
3733     y -= itemh;
3734     ui_item_position(item, x, y, is_menu ? itemw : litem->w, itemh);
3735 
3736     if (item->next && (!is_box || item != litem->items.first)) {
3737       y -= litem->space;
3738     }
3739 
3740     if (is_box) {
3741       item->flag |= UI_ITEM_BOX_ITEM;
3742     }
3743   }
3744 
3745   litem->h = litem->y - y;
3746   litem->x = x;
3747   litem->y = y;
3748 }
3749 
3750 /* calculates the angle of a specified button in a radial menu,
3751  * stores a float vector in unit circle */
ui_get_radialbut_vec(float vec[2],short itemnum)3752 static RadialDirection ui_get_radialbut_vec(float vec[2], short itemnum)
3753 {
3754   RadialDirection dir;
3755 
3756   if (itemnum >= PIE_MAX_ITEMS) {
3757     itemnum %= PIE_MAX_ITEMS;
3758     printf("Warning: Pie menus with more than %i items are currently unsupported\n",
3759            PIE_MAX_ITEMS);
3760   }
3761 
3762   dir = ui_radial_dir_order[itemnum];
3763   ui_but_pie_dir(dir, vec);
3764 
3765   return dir;
3766 }
3767 
ui_item_is_radial_displayable(uiItem * item)3768 static bool ui_item_is_radial_displayable(uiItem *item)
3769 {
3770 
3771   if ((item->type == ITEM_BUTTON) && (((uiButtonItem *)item)->but->type == UI_BTYPE_LABEL)) {
3772     return false;
3773   }
3774 
3775   return true;
3776 }
3777 
ui_item_is_radial_drawable(uiButtonItem * bitem)3778 static bool ui_item_is_radial_drawable(uiButtonItem *bitem)
3779 {
3780 
3781   if (ELEM(bitem->but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR_SPACER)) {
3782     return false;
3783   }
3784 
3785   return true;
3786 }
3787 
ui_litem_layout_radial(uiLayout * litem)3788 static void ui_litem_layout_radial(uiLayout *litem)
3789 {
3790   int itemh, itemw, x, y;
3791   int itemnum = 0;
3792   int totitems = 0;
3793 
3794   /* For the radial layout we will use Matt Ebb's design
3795    * for radiation, see http://mattebb.com/weblog/radiation/
3796    * also the old code at http://developer.blender.org/T5103
3797    */
3798 
3799   const int pie_radius = U.pie_menu_radius * UI_DPI_FAC;
3800 
3801   x = litem->x;
3802   y = litem->y;
3803 
3804   int minx = x, miny = y, maxx = x, maxy = y;
3805 
3806   /* first count total items */
3807   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
3808     totitems++;
3809   }
3810 
3811   if (totitems < 5) {
3812     litem->root->block->pie_data.flags |= UI_PIE_DEGREES_RANGE_LARGE;
3813   }
3814 
3815   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
3816     /* not all button types are drawn in a radial menu, do filtering here */
3817     if (ui_item_is_radial_displayable(item)) {
3818       RadialDirection dir;
3819       float vec[2];
3820       float factor[2];
3821 
3822       dir = ui_get_radialbut_vec(vec, itemnum);
3823       factor[0] = (vec[0] > 0.01f) ? 0.0f : ((vec[0] < -0.01f) ? -1.0f : -0.5f);
3824       factor[1] = (vec[1] > 0.99f) ? 0.0f : ((vec[1] < -0.99f) ? -1.0f : -0.5f);
3825 
3826       itemnum++;
3827 
3828       if (item->type == ITEM_BUTTON) {
3829         uiButtonItem *bitem = (uiButtonItem *)item;
3830 
3831         bitem->but->pie_dir = dir;
3832         /* scale the buttons */
3833         bitem->but->rect.ymax *= 1.5f;
3834         /* add a little bit more here to include number */
3835         bitem->but->rect.xmax += 1.5f * UI_UNIT_X;
3836         /* enable drawing as pie item if supported by widget */
3837         if (ui_item_is_radial_drawable(bitem)) {
3838           bitem->but->emboss = UI_EMBOSS_RADIAL;
3839           bitem->but->drawflag |= UI_BUT_ICON_LEFT;
3840         }
3841       }
3842 
3843       ui_item_size(item, &itemw, &itemh);
3844 
3845       ui_item_position(item,
3846                        x + vec[0] * pie_radius + factor[0] * itemw,
3847                        y + vec[1] * pie_radius + factor[1] * itemh,
3848                        itemw,
3849                        itemh);
3850 
3851       minx = min_ii(minx, x + vec[0] * pie_radius - itemw / 2);
3852       maxx = max_ii(maxx, x + vec[0] * pie_radius + itemw / 2);
3853       miny = min_ii(miny, y + vec[1] * pie_radius - itemh / 2);
3854       maxy = max_ii(maxy, y + vec[1] * pie_radius + itemh / 2);
3855     }
3856   }
3857 
3858   litem->x = minx;
3859   litem->y = miny;
3860   litem->w = maxx - minx;
3861   litem->h = maxy - miny;
3862 }
3863 
3864 /* root layout */
ui_litem_estimate_root(uiLayout * UNUSED (litem))3865 static void ui_litem_estimate_root(uiLayout *UNUSED(litem))
3866 {
3867   /* nothing to do */
3868 }
3869 
ui_litem_layout_root_radial(uiLayout * litem)3870 static void ui_litem_layout_root_radial(uiLayout *litem)
3871 {
3872   /* first item is pie menu title, align on center of menu */
3873   uiItem *item = litem->items.first;
3874 
3875   if (item->type == ITEM_BUTTON) {
3876     int itemh, itemw, x, y;
3877     x = litem->x;
3878     y = litem->y;
3879 
3880     ui_item_size(item, &itemw, &itemh);
3881 
3882     ui_item_position(
3883         item, x - itemw / 2, y + U.dpi_fac * (U.pie_menu_threshold + 9.0f), itemw, itemh);
3884   }
3885 }
3886 
ui_litem_layout_root(uiLayout * litem)3887 static void ui_litem_layout_root(uiLayout *litem)
3888 {
3889   if (litem->root->type == UI_LAYOUT_HEADER) {
3890     ui_litem_layout_row(litem);
3891   }
3892   else if (litem->root->type == UI_LAYOUT_PIEMENU) {
3893     ui_litem_layout_root_radial(litem);
3894   }
3895   else if (litem->root->type == UI_LAYOUT_MENU) {
3896     ui_litem_layout_column(litem, false, true);
3897   }
3898   else {
3899     ui_litem_layout_column(litem, false, false);
3900   }
3901 }
3902 
3903 /* box layout */
ui_litem_estimate_box(uiLayout * litem)3904 static void ui_litem_estimate_box(uiLayout *litem)
3905 {
3906   const uiStyle *style = litem->root->style;
3907 
3908   ui_litem_estimate_column(litem, true);
3909 
3910   int boxspace = style->boxspace;
3911   if (litem->root->type == UI_LAYOUT_HEADER) {
3912     boxspace = 0;
3913   }
3914   litem->w += 2 * boxspace;
3915   litem->h += 2 * boxspace;
3916 }
3917 
ui_litem_layout_box(uiLayout * litem)3918 static void ui_litem_layout_box(uiLayout *litem)
3919 {
3920   uiLayoutItemBx *box = (uiLayoutItemBx *)litem;
3921   const uiStyle *style = litem->root->style;
3922   uiBut *but;
3923   int w, h;
3924 
3925   int boxspace = style->boxspace;
3926   if (litem->root->type == UI_LAYOUT_HEADER) {
3927     boxspace = 0;
3928   }
3929 
3930   w = litem->w;
3931   h = litem->h;
3932 
3933   litem->x += boxspace;
3934   litem->y -= boxspace;
3935 
3936   if (w != 0) {
3937     litem->w -= 2 * boxspace;
3938   }
3939   if (h != 0) {
3940     litem->h -= 2 * boxspace;
3941   }
3942 
3943   ui_litem_layout_column(litem, true, false);
3944 
3945   litem->x -= boxspace;
3946   litem->y -= boxspace;
3947 
3948   if (w != 0) {
3949     litem->w += 2 * boxspace;
3950   }
3951   if (h != 0) {
3952     litem->h += 2 * boxspace;
3953   }
3954 
3955   /* roundbox around the sublayout */
3956   but = box->roundbox;
3957   but->rect.xmin = litem->x;
3958   but->rect.ymin = litem->y;
3959   but->rect.xmax = litem->x + litem->w;
3960   but->rect.ymax = litem->y + litem->h;
3961 }
3962 
3963 /* multi-column layout, automatically flowing to the next */
ui_litem_estimate_column_flow(uiLayout * litem)3964 static void ui_litem_estimate_column_flow(uiLayout *litem)
3965 {
3966   const uiStyle *style = litem->root->style;
3967   uiLayoutItemFlow *flow = (uiLayoutItemFlow *)litem;
3968   int col, x, y, emh, emy, miny, itemw, itemh, maxw = 0;
3969   int toth, totitem;
3970 
3971   /* compute max needed width and total height */
3972   toth = 0;
3973   totitem = 0;
3974   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
3975     ui_item_size(item, &itemw, &itemh);
3976     maxw = MAX2(maxw, itemw);
3977     toth += itemh;
3978     totitem++;
3979   }
3980 
3981   if (flow->number <= 0) {
3982     /* auto compute number of columns, not very good */
3983     if (maxw == 0) {
3984       flow->totcol = 1;
3985       return;
3986     }
3987 
3988     flow->totcol = max_ii(litem->root->emw / maxw, 1);
3989     flow->totcol = min_ii(flow->totcol, totitem);
3990   }
3991   else {
3992     flow->totcol = flow->number;
3993   }
3994 
3995   /* compute sizes */
3996   x = 0;
3997   y = 0;
3998   emy = 0;
3999   miny = 0;
4000 
4001   maxw = 0;
4002   emh = toth / flow->totcol;
4003 
4004   /* create column per column */
4005   col = 0;
4006   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
4007     ui_item_size(item, &itemw, &itemh);
4008 
4009     y -= itemh + style->buttonspacey;
4010     miny = min_ii(miny, y);
4011     emy -= itemh;
4012     maxw = max_ii(itemw, maxw);
4013 
4014     /* decide to go to next one */
4015     if (col < flow->totcol - 1 && emy <= -emh) {
4016       x += maxw + litem->space;
4017       maxw = 0;
4018       y = 0;
4019       emy = 0; /* need to reset height again for next column */
4020       col++;
4021     }
4022   }
4023 
4024   litem->w = x;
4025   litem->h = litem->y - miny;
4026 }
4027 
ui_litem_layout_column_flow(uiLayout * litem)4028 static void ui_litem_layout_column_flow(uiLayout *litem)
4029 {
4030   const uiStyle *style = litem->root->style;
4031   uiLayoutItemFlow *flow = (uiLayoutItemFlow *)litem;
4032   int col, x, y, w, emh, emy, miny, itemw, itemh;
4033   int toth, totitem;
4034 
4035   /* compute max needed width and total height */
4036   toth = 0;
4037   totitem = 0;
4038   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
4039     ui_item_size(item, &itemw, &itemh);
4040     toth += itemh;
4041     totitem++;
4042   }
4043 
4044   /* compute sizes */
4045   x = litem->x;
4046   y = litem->y;
4047   emy = 0;
4048   miny = 0;
4049 
4050   w = litem->w - (flow->totcol - 1) * style->columnspace;
4051   emh = toth / flow->totcol;
4052 
4053   /* create column per column */
4054   col = 0;
4055   w = (litem->w - (flow->totcol - 1) * style->columnspace) / flow->totcol;
4056   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
4057     ui_item_size(item, &itemw, &itemh);
4058 
4059     itemw = (litem->alignment == UI_LAYOUT_ALIGN_EXPAND) ? w : min_ii(w, itemw);
4060 
4061     y -= itemh;
4062     emy -= itemh;
4063     ui_item_position(item, x, y, itemw, itemh);
4064     y -= style->buttonspacey;
4065     miny = min_ii(miny, y);
4066 
4067     /* decide to go to next one */
4068     if (col < flow->totcol - 1 && emy <= -emh) {
4069       x += w + style->columnspace;
4070       y = litem->y;
4071       emy = 0; /* need to reset height again for next column */
4072       col++;
4073 
4074       const int remaining_width = litem->w - (x - litem->x);
4075       const int remaining_width_between_columns = (flow->totcol - col - 1) * style->columnspace;
4076       const int remaining_columns = flow->totcol - col;
4077       w = (remaining_width - remaining_width_between_columns) / remaining_columns;
4078     }
4079   }
4080 
4081   litem->h = litem->y - miny;
4082   litem->x = x;
4083   litem->y = miny;
4084 }
4085 
4086 /* multi-column and multi-row layout. */
4087 typedef struct UILayoutGridFlowInput {
4088   /* General layout control settings. */
4089   const bool row_major : 1;    /* Fill rows before columns */
4090   const bool even_columns : 1; /* All columns will have same width. */
4091   const bool even_rows : 1;    /* All rows will have same height. */
4092   const int space_x;           /* Space between columns. */
4093   const int space_y;           /* Space between rows. */
4094   /* Real data about current position and size of this layout item
4095    * (either estimated, or final values). */
4096   const int litem_w; /* Layout item width. */
4097   const int litem_x; /* Layout item X position. */
4098   const int litem_y; /* Layout item Y position. */
4099   /* Actual number of columns and rows to generate (computed from first pass usually). */
4100   const int tot_columns; /* Number of columns. */
4101   const int tot_rows;    /* Number of rows. */
4102 } UILayoutGridFlowInput;
4103 
4104 typedef struct UILayoutGridFlowOutput {
4105   int *tot_items; /* Total number of items in this grid layout. */
4106   /* Width / X pos data. */
4107   float *global_avg_w; /* Computed average width of the columns. */
4108   int *cos_x_array;    /* Computed X coordinate of each column. */
4109   int *widths_array;   /* Computed width of each column. */
4110   int *tot_w;          /* Computed total width. */
4111   /* Height / Y pos data. */
4112   int *global_max_h;  /* Computed height of the tallest item in the grid. */
4113   int *cos_y_array;   /* Computed Y coordinate of each column. */
4114   int *heights_array; /* Computed height of each column. */
4115   int *tot_h;         /* Computed total height. */
4116 } UILayoutGridFlowOutput;
4117 
ui_litem_grid_flow_compute(ListBase * items,UILayoutGridFlowInput * parameters,UILayoutGridFlowOutput * results)4118 static void ui_litem_grid_flow_compute(ListBase *items,
4119                                        UILayoutGridFlowInput *parameters,
4120                                        UILayoutGridFlowOutput *results)
4121 {
4122   float tot_w = 0.0f, tot_h = 0.0f;
4123   float global_avg_w = 0.0f, global_totweight_w = 0.0f;
4124   int global_max_h = 0;
4125 
4126   float *avg_w = NULL, *totweight_w = NULL;
4127   int *max_h = NULL;
4128 
4129   BLI_assert(
4130       parameters->tot_columns != 0 ||
4131       (results->cos_x_array == NULL && results->widths_array == NULL && results->tot_w == NULL));
4132   BLI_assert(
4133       parameters->tot_rows != 0 ||
4134       (results->cos_y_array == NULL && results->heights_array == NULL && results->tot_h == NULL));
4135 
4136   if (results->tot_items) {
4137     *results->tot_items = 0;
4138   }
4139 
4140   if (items->first == NULL) {
4141     if (results->global_avg_w) {
4142       *results->global_avg_w = 0.0f;
4143     }
4144     if (results->global_max_h) {
4145       *results->global_max_h = 0;
4146     }
4147     return;
4148   }
4149 
4150   if (parameters->tot_columns != 0) {
4151     avg_w = BLI_array_alloca(avg_w, parameters->tot_columns);
4152     totweight_w = BLI_array_alloca(totweight_w, parameters->tot_columns);
4153     memset(avg_w, 0, sizeof(*avg_w) * parameters->tot_columns);
4154     memset(totweight_w, 0, sizeof(*totweight_w) * parameters->tot_columns);
4155   }
4156   if (parameters->tot_rows != 0) {
4157     max_h = BLI_array_alloca(max_h, parameters->tot_rows);
4158     memset(max_h, 0, sizeof(*max_h) * parameters->tot_rows);
4159   }
4160 
4161   int i = 0;
4162   LISTBASE_FOREACH (uiItem *, item, items) {
4163     int item_w, item_h;
4164     ui_item_size(item, &item_w, &item_h);
4165 
4166     global_avg_w += (float)(item_w * item_w);
4167     global_totweight_w += (float)item_w;
4168     global_max_h = max_ii(global_max_h, item_h);
4169 
4170     if (parameters->tot_rows != 0 && parameters->tot_columns != 0) {
4171       const int index_col = parameters->row_major ? i % parameters->tot_columns :
4172                                                     i / parameters->tot_rows;
4173       const int index_row = parameters->row_major ? i / parameters->tot_columns :
4174                                                     i % parameters->tot_rows;
4175 
4176       avg_w[index_col] += (float)(item_w * item_w);
4177       totweight_w[index_col] += (float)item_w;
4178 
4179       max_h[index_row] = max_ii(max_h[index_row], item_h);
4180     }
4181 
4182     if (results->tot_items) {
4183       (*results->tot_items)++;
4184     }
4185     i++;
4186   }
4187 
4188   /* Finalize computing of column average sizes */
4189   global_avg_w /= global_totweight_w;
4190   if (parameters->tot_columns != 0) {
4191     for (i = 0; i < parameters->tot_columns; i++) {
4192       avg_w[i] /= totweight_w[i];
4193       tot_w += avg_w[i];
4194     }
4195     if (parameters->even_columns) {
4196       tot_w = ceilf(global_avg_w) * parameters->tot_columns;
4197     }
4198   }
4199   /* Finalize computing of rows max sizes */
4200   if (parameters->tot_rows != 0) {
4201     for (i = 0; i < parameters->tot_rows; i++) {
4202       tot_h += max_h[i];
4203     }
4204     if (parameters->even_rows) {
4205       tot_h = global_max_h * parameters->tot_columns;
4206     }
4207   }
4208 
4209   /* Compute positions and sizes of all cells. */
4210   if (results->cos_x_array != NULL && results->widths_array != NULL) {
4211     /* We enlarge/narrow columns evenly to match available width. */
4212     const float wfac = (float)(parameters->litem_w -
4213                                (parameters->tot_columns - 1) * parameters->space_x) /
4214                        tot_w;
4215 
4216     for (int col = 0; col < parameters->tot_columns; col++) {
4217       results->cos_x_array[col] = (col ? results->cos_x_array[col - 1] +
4218                                              results->widths_array[col - 1] + parameters->space_x :
4219                                          parameters->litem_x);
4220       if (parameters->even_columns) {
4221         /* (< remaining width > - < space between remaining columns >) / < remaining columns > */
4222         results->widths_array[col] = (((parameters->litem_w -
4223                                         (results->cos_x_array[col] - parameters->litem_x)) -
4224                                        (parameters->tot_columns - col - 1) * parameters->space_x) /
4225                                       (parameters->tot_columns - col));
4226       }
4227       else if (col == parameters->tot_columns - 1) {
4228         /* Last column copes width rounding errors... */
4229         results->widths_array[col] = parameters->litem_w -
4230                                      (results->cos_x_array[col] - parameters->litem_x);
4231       }
4232       else {
4233         results->widths_array[col] = (int)(avg_w[col] * wfac);
4234       }
4235     }
4236   }
4237   if (results->cos_y_array != NULL && results->heights_array != NULL) {
4238     for (int row = 0; row < parameters->tot_rows; row++) {
4239       if (parameters->even_rows) {
4240         results->heights_array[row] = global_max_h;
4241       }
4242       else {
4243         results->heights_array[row] = max_h[row];
4244       }
4245       results->cos_y_array[row] = (row ? results->cos_y_array[row - 1] - parameters->space_y -
4246                                              results->heights_array[row] :
4247                                          parameters->litem_y - results->heights_array[row]);
4248     }
4249   }
4250 
4251   if (results->global_avg_w) {
4252     *results->global_avg_w = global_avg_w;
4253   }
4254   if (results->global_max_h) {
4255     *results->global_max_h = global_max_h;
4256   }
4257   if (results->tot_w) {
4258     *results->tot_w = (int)tot_w + parameters->space_x * (parameters->tot_columns - 1);
4259   }
4260   if (results->tot_h) {
4261     *results->tot_h = tot_h + parameters->space_y * (parameters->tot_rows - 1);
4262   }
4263 }
4264 
ui_litem_estimate_grid_flow(uiLayout * litem)4265 static void ui_litem_estimate_grid_flow(uiLayout *litem)
4266 {
4267   const uiStyle *style = litem->root->style;
4268   uiLayoutItemGridFlow *gflow = (uiLayoutItemGridFlow *)litem;
4269 
4270   const int space_x = style->columnspace;
4271   const int space_y = style->buttonspacey;
4272 
4273   /* Estimate average needed width and height per item. */
4274   {
4275     float avg_w;
4276     int max_h;
4277 
4278     ui_litem_grid_flow_compute(&litem->items,
4279                                &((UILayoutGridFlowInput){
4280                                    .row_major = gflow->row_major,
4281                                    .even_columns = gflow->even_columns,
4282                                    .even_rows = gflow->even_rows,
4283                                    .litem_w = litem->w,
4284                                    .litem_x = litem->x,
4285                                    .litem_y = litem->y,
4286                                    .space_x = space_x,
4287                                    .space_y = space_y,
4288                                }),
4289                                &((UILayoutGridFlowOutput){
4290                                    .tot_items = &gflow->tot_items,
4291                                    .global_avg_w = &avg_w,
4292                                    .global_max_h = &max_h,
4293                                }));
4294 
4295     if (gflow->tot_items == 0) {
4296       litem->w = litem->h = 0;
4297       gflow->tot_columns = gflow->tot_rows = 0;
4298       return;
4299     }
4300 
4301     /* Even in varying column width case,
4302      * we fix our columns number from weighted average width of items,
4303      * a proper solving of required width would be too costly,
4304      * and this should give reasonably good results in all reasonable cases. */
4305     if (gflow->columns_len > 0) {
4306       gflow->tot_columns = gflow->columns_len;
4307     }
4308     else {
4309       if (avg_w == 0.0f) {
4310         gflow->tot_columns = 1;
4311       }
4312       else {
4313         gflow->tot_columns = min_ii(max_ii((int)(litem->w / avg_w), 1), gflow->tot_items);
4314       }
4315     }
4316     gflow->tot_rows = (int)ceilf((float)gflow->tot_items / gflow->tot_columns);
4317 
4318     /* Try to tweak number of columns and rows to get better filling of last column or row,
4319      * and apply 'modulo' value to number of columns or rows.
4320      * Note that modulo does not prevent ending with fewer columns/rows than modulo, if mandatory
4321      * to avoid empty column/row. */
4322     {
4323       const int modulo = (gflow->columns_len < -1) ? -gflow->columns_len : 0;
4324       const int step = modulo ? modulo : 1;
4325 
4326       if (gflow->row_major) {
4327         /* Adjust number of columns to be multiple of given modulo. */
4328         if (modulo && gflow->tot_columns % modulo != 0 && gflow->tot_columns > modulo) {
4329           gflow->tot_columns = gflow->tot_columns - (gflow->tot_columns % modulo);
4330         }
4331         /* Find smallest number of columns conserving computed optimal number of rows. */
4332         for (gflow->tot_rows = (int)ceilf((float)gflow->tot_items / gflow->tot_columns);
4333              (gflow->tot_columns - step) > 0 &&
4334              (int)ceilf((float)gflow->tot_items / (gflow->tot_columns - step)) <= gflow->tot_rows;
4335              gflow->tot_columns -= step) {
4336           /* pass */
4337         }
4338       }
4339       else {
4340         /* Adjust number of rows to be multiple of given modulo. */
4341         if (modulo && gflow->tot_rows % modulo != 0) {
4342           gflow->tot_rows = min_ii(gflow->tot_rows + modulo - (gflow->tot_rows % modulo),
4343                                    gflow->tot_items);
4344         }
4345         /* Find smallest number of rows conserving computed optimal number of columns. */
4346         for (gflow->tot_columns = (int)ceilf((float)gflow->tot_items / gflow->tot_rows);
4347              (gflow->tot_rows - step) > 0 &&
4348              (int)ceilf((float)gflow->tot_items / (gflow->tot_rows - step)) <= gflow->tot_columns;
4349              gflow->tot_rows -= step) {
4350           /* pass */
4351         }
4352       }
4353     }
4354 
4355     /* Set evenly-spaced axes size
4356      * (quick optimization in case we have even columns and rows). */
4357     if (gflow->even_columns && gflow->even_rows) {
4358       litem->w = (int)(gflow->tot_columns * avg_w) + space_x * (gflow->tot_columns - 1);
4359       litem->h = (int)(gflow->tot_rows * max_h) + space_y * (gflow->tot_rows - 1);
4360       return;
4361     }
4362   }
4363 
4364   /* Now that we have our final number of columns and rows,
4365    * we can compute actual needed space for non-evenly sized axes. */
4366   {
4367     int tot_w, tot_h;
4368 
4369     ui_litem_grid_flow_compute(&litem->items,
4370                                &((UILayoutGridFlowInput){
4371                                    .row_major = gflow->row_major,
4372                                    .even_columns = gflow->even_columns,
4373                                    .even_rows = gflow->even_rows,
4374                                    .litem_w = litem->w,
4375                                    .litem_x = litem->x,
4376                                    .litem_y = litem->y,
4377                                    .space_x = space_x,
4378                                    .space_y = space_y,
4379                                    .tot_columns = gflow->tot_columns,
4380                                    .tot_rows = gflow->tot_rows,
4381                                }),
4382                                &((UILayoutGridFlowOutput){
4383                                    .tot_w = &tot_w,
4384                                    .tot_h = &tot_h,
4385                                }));
4386 
4387     litem->w = tot_w;
4388     litem->h = tot_h;
4389   }
4390 }
4391 
ui_litem_layout_grid_flow(uiLayout * litem)4392 static void ui_litem_layout_grid_flow(uiLayout *litem)
4393 {
4394   const uiStyle *style = litem->root->style;
4395   uiLayoutItemGridFlow *gflow = (uiLayoutItemGridFlow *)litem;
4396 
4397   if (gflow->tot_items == 0) {
4398     litem->w = litem->h = 0;
4399     return;
4400   }
4401 
4402   BLI_assert(gflow->tot_columns > 0);
4403   BLI_assert(gflow->tot_rows > 0);
4404 
4405   const int space_x = style->columnspace;
4406   const int space_y = style->buttonspacey;
4407 
4408   int *widths = BLI_array_alloca(widths, gflow->tot_columns);
4409   int *heights = BLI_array_alloca(heights, gflow->tot_rows);
4410   int *cos_x = BLI_array_alloca(cos_x, gflow->tot_columns);
4411   int *cos_y = BLI_array_alloca(cos_y, gflow->tot_rows);
4412 
4413   /* This time we directly compute coordinates and sizes of all cells. */
4414   ui_litem_grid_flow_compute(&litem->items,
4415                              &((UILayoutGridFlowInput){
4416                                  .row_major = gflow->row_major,
4417                                  .even_columns = gflow->even_columns,
4418                                  .even_rows = gflow->even_rows,
4419                                  .litem_w = litem->w,
4420                                  .litem_x = litem->x,
4421                                  .litem_y = litem->y,
4422                                  .space_x = space_x,
4423                                  .space_y = space_y,
4424                                  .tot_columns = gflow->tot_columns,
4425                                  .tot_rows = gflow->tot_rows,
4426                              }),
4427                              &((UILayoutGridFlowOutput){
4428                                  .cos_x_array = cos_x,
4429                                  .cos_y_array = cos_y,
4430                                  .widths_array = widths,
4431                                  .heights_array = heights,
4432                              }));
4433 
4434   int i;
4435   LISTBASE_FOREACH_INDEX (uiItem *, item, &litem->items, i) {
4436     const int col = gflow->row_major ? i % gflow->tot_columns : i / gflow->tot_rows;
4437     const int row = gflow->row_major ? i / gflow->tot_columns : i % gflow->tot_rows;
4438     int item_w, item_h;
4439     ui_item_size(item, &item_w, &item_h);
4440 
4441     const int w = widths[col];
4442     const int h = heights[row];
4443 
4444     item_w = (litem->alignment == UI_LAYOUT_ALIGN_EXPAND) ? w : min_ii(w, item_w);
4445     item_h = (litem->alignment == UI_LAYOUT_ALIGN_EXPAND) ? h : min_ii(h, item_h);
4446 
4447     ui_item_position(item, cos_x[col], cos_y[row], item_w, item_h);
4448   }
4449 
4450   litem->h = litem->y - cos_y[gflow->tot_rows - 1];
4451   litem->x = (cos_x[gflow->tot_columns - 1] - litem->x) + widths[gflow->tot_columns - 1];
4452   litem->y = litem->y - litem->h;
4453 }
4454 
4455 /* free layout */
ui_litem_estimate_absolute(uiLayout * litem)4456 static void ui_litem_estimate_absolute(uiLayout *litem)
4457 {
4458   int itemx, itemy, itemw, itemh, minx, miny;
4459 
4460   minx = 1e6;
4461   miny = 1e6;
4462   litem->w = 0;
4463   litem->h = 0;
4464 
4465   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
4466     ui_item_offset(item, &itemx, &itemy);
4467     ui_item_size(item, &itemw, &itemh);
4468 
4469     minx = min_ii(minx, itemx);
4470     miny = min_ii(miny, itemy);
4471 
4472     litem->w = MAX2(litem->w, itemx + itemw);
4473     litem->h = MAX2(litem->h, itemy + itemh);
4474   }
4475 
4476   litem->w -= minx;
4477   litem->h -= miny;
4478 }
4479 
ui_litem_layout_absolute(uiLayout * litem)4480 static void ui_litem_layout_absolute(uiLayout *litem)
4481 {
4482   float scalex = 1.0f, scaley = 1.0f;
4483   int x, y, newx, newy, itemx, itemy, itemh, itemw, minx, miny, totw, toth;
4484 
4485   minx = 1e6;
4486   miny = 1e6;
4487   totw = 0;
4488   toth = 0;
4489 
4490   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
4491     ui_item_offset(item, &itemx, &itemy);
4492     ui_item_size(item, &itemw, &itemh);
4493 
4494     minx = min_ii(minx, itemx);
4495     miny = min_ii(miny, itemy);
4496 
4497     totw = max_ii(totw, itemx + itemw);
4498     toth = max_ii(toth, itemy + itemh);
4499   }
4500 
4501   totw -= minx;
4502   toth -= miny;
4503 
4504   if (litem->w && totw > 0) {
4505     scalex = (float)litem->w / (float)totw;
4506   }
4507   if (litem->h && toth > 0) {
4508     scaley = (float)litem->h / (float)toth;
4509   }
4510 
4511   x = litem->x;
4512   y = litem->y - scaley * toth;
4513 
4514   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
4515     ui_item_offset(item, &itemx, &itemy);
4516     ui_item_size(item, &itemw, &itemh);
4517 
4518     if (scalex != 1.0f) {
4519       newx = (itemx - minx) * scalex;
4520       itemw = (itemx - minx + itemw) * scalex - newx;
4521       itemx = minx + newx;
4522     }
4523 
4524     if (scaley != 1.0f) {
4525       newy = (itemy - miny) * scaley;
4526       itemh = (itemy - miny + itemh) * scaley - newy;
4527       itemy = miny + newy;
4528     }
4529 
4530     ui_item_position(item, x + itemx - minx, y + itemy - miny, itemw, itemh);
4531   }
4532 
4533   litem->w = scalex * totw;
4534   litem->h = litem->y - y;
4535   litem->x = x + litem->w;
4536   litem->y = y;
4537 }
4538 
4539 /* split layout */
ui_litem_estimate_split(uiLayout * litem)4540 static void ui_litem_estimate_split(uiLayout *litem)
4541 {
4542   ui_litem_estimate_row(litem);
4543   litem->item.flag &= ~UI_ITEM_FIXED_SIZE;
4544 }
4545 
ui_litem_layout_split(uiLayout * litem)4546 static void ui_litem_layout_split(uiLayout *litem)
4547 {
4548   uiLayoutItemSplit *split = (uiLayoutItemSplit *)litem;
4549   float percentage, extra_pixel = 0.0f;
4550   const int tot = BLI_listbase_count(&litem->items);
4551   int itemh, x, y, w, colw = 0;
4552 
4553   if (tot == 0) {
4554     return;
4555   }
4556 
4557   x = litem->x;
4558   y = litem->y;
4559 
4560   percentage = (split->percentage == 0.0f) ? 1.0f / (float)tot : split->percentage;
4561 
4562   w = (litem->w - (tot - 1) * litem->space);
4563   colw = w * percentage;
4564   colw = MAX2(colw, 0);
4565 
4566   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
4567     ui_item_size(item, NULL, &itemh);
4568 
4569     ui_item_position(item, x, y - itemh, colw, itemh);
4570     x += colw;
4571 
4572     if (item->next) {
4573       const float width = extra_pixel + (w - (int)(w * percentage)) / ((float)tot - 1);
4574       extra_pixel = width - (int)width;
4575       colw = (int)width;
4576       colw = MAX2(colw, 0);
4577 
4578       x += litem->space;
4579     }
4580   }
4581 
4582   litem->w = x - litem->x;
4583   litem->h = litem->y - y;
4584   litem->x = x;
4585   litem->y = y;
4586 }
4587 
4588 /* overlap layout */
ui_litem_estimate_overlap(uiLayout * litem)4589 static void ui_litem_estimate_overlap(uiLayout *litem)
4590 {
4591   int itemw, itemh;
4592 
4593   litem->w = 0;
4594   litem->h = 0;
4595 
4596   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
4597     ui_item_size(item, &itemw, &itemh);
4598 
4599     litem->w = MAX2(itemw, litem->w);
4600     litem->h = MAX2(itemh, litem->h);
4601   }
4602 }
4603 
ui_litem_layout_overlap(uiLayout * litem)4604 static void ui_litem_layout_overlap(uiLayout *litem)
4605 {
4606   int itemw, itemh, x, y;
4607 
4608   x = litem->x;
4609   y = litem->y;
4610 
4611   LISTBASE_FOREACH (uiItem *, item, &litem->items) {
4612     ui_item_size(item, &itemw, &itemh);
4613     ui_item_position(item, x, y - itemh, litem->w, itemh);
4614 
4615     litem->h = MAX2(litem->h, itemh);
4616   }
4617 
4618   litem->x = x;
4619   litem->y = y - litem->h;
4620 }
4621 
ui_litem_init_from_parent(uiLayout * litem,uiLayout * layout,int align)4622 static void ui_litem_init_from_parent(uiLayout *litem, uiLayout *layout, int align)
4623 {
4624   litem->root = layout->root;
4625   litem->align = align;
4626   /* Children of gridflow layout shall never have "ideal big size" returned as estimated size. */
4627   litem->variable_size = layout->variable_size || layout->item.type == ITEM_LAYOUT_GRID_FLOW;
4628   litem->active = true;
4629   litem->enabled = true;
4630   litem->context = layout->context;
4631   litem->redalert = layout->redalert;
4632   litem->w = layout->w;
4633   litem->emboss = layout->emboss;
4634   litem->item.flag = (layout->item.flag &
4635                       (UI_ITEM_PROP_SEP | UI_ITEM_PROP_DECORATE | UI_ITEM_INSIDE_PROP_SEP));
4636 
4637   if (layout->child_items_layout) {
4638     BLI_addtail(&layout->child_items_layout->items, litem);
4639     litem->parent = layout->child_items_layout;
4640   }
4641   else {
4642     BLI_addtail(&layout->items, litem);
4643     litem->parent = layout;
4644   }
4645 }
4646 
ui_layout_heading_set(uiLayout * layout,const char * heading)4647 static void ui_layout_heading_set(uiLayout *layout, const char *heading)
4648 {
4649   BLI_assert(layout->heading[0] == '\0');
4650   if (heading) {
4651     STRNCPY(layout->heading, heading);
4652   }
4653 }
4654 
4655 /* layout create functions */
uiLayoutRow(uiLayout * layout,bool align)4656 uiLayout *uiLayoutRow(uiLayout *layout, bool align)
4657 {
4658   uiLayout *litem;
4659 
4660   litem = MEM_callocN(sizeof(uiLayout), "uiLayoutRow");
4661   ui_litem_init_from_parent(litem, layout, align);
4662 
4663   litem->item.type = ITEM_LAYOUT_ROW;
4664   litem->space = (align) ? 0 : layout->root->style->buttonspacex;
4665 
4666   UI_block_layout_set_current(layout->root->block, litem);
4667 
4668   return litem;
4669 }
4670 
4671 /**
4672  * See #uiLayoutColumnWithHeading().
4673  */
uiLayoutRowWithHeading(uiLayout * layout,bool align,const char * heading)4674 uiLayout *uiLayoutRowWithHeading(uiLayout *layout, bool align, const char *heading)
4675 {
4676   uiLayout *litem = uiLayoutRow(layout, align);
4677   ui_layout_heading_set(litem, heading);
4678   return litem;
4679 }
4680 
uiLayoutColumn(uiLayout * layout,bool align)4681 uiLayout *uiLayoutColumn(uiLayout *layout, bool align)
4682 {
4683   uiLayout *litem;
4684 
4685   litem = MEM_callocN(sizeof(uiLayout), "uiLayoutColumn");
4686   ui_litem_init_from_parent(litem, layout, align);
4687 
4688   litem->item.type = ITEM_LAYOUT_COLUMN;
4689   litem->space = (align) ? 0 : layout->root->style->buttonspacey;
4690 
4691   UI_block_layout_set_current(layout->root->block, litem);
4692 
4693   return litem;
4694 }
4695 
4696 /**
4697  * Variant of #uiLayoutColumn() that sets a heading label for the layout if the first item is
4698  * added through #uiItemFullR(). If split layout is used and the item has no string to add to the
4699  * first split-column, the heading is added there instead. Otherwise the heading inserted with a
4700  * new row.
4701  */
uiLayoutColumnWithHeading(uiLayout * layout,bool align,const char * heading)4702 uiLayout *uiLayoutColumnWithHeading(uiLayout *layout, bool align, const char *heading)
4703 {
4704   uiLayout *litem = uiLayoutColumn(layout, align);
4705   ui_layout_heading_set(litem, heading);
4706   return litem;
4707 }
4708 
uiLayoutColumnFlow(uiLayout * layout,int number,bool align)4709 uiLayout *uiLayoutColumnFlow(uiLayout *layout, int number, bool align)
4710 {
4711   uiLayoutItemFlow *flow;
4712 
4713   flow = MEM_callocN(sizeof(uiLayoutItemFlow), "uiLayoutItemFlow");
4714   ui_litem_init_from_parent(&flow->litem, layout, align);
4715 
4716   flow->litem.item.type = ITEM_LAYOUT_COLUMN_FLOW;
4717   flow->litem.space = (flow->litem.align) ? 0 : layout->root->style->columnspace;
4718   flow->number = number;
4719 
4720   UI_block_layout_set_current(layout->root->block, &flow->litem);
4721 
4722   return &flow->litem;
4723 }
4724 
uiLayoutGridFlow(uiLayout * layout,bool row_major,int columns_len,bool even_columns,bool even_rows,bool align)4725 uiLayout *uiLayoutGridFlow(uiLayout *layout,
4726                            bool row_major,
4727                            int columns_len,
4728                            bool even_columns,
4729                            bool even_rows,
4730                            bool align)
4731 {
4732   uiLayoutItemGridFlow *flow;
4733 
4734   flow = MEM_callocN(sizeof(uiLayoutItemGridFlow), __func__);
4735   flow->litem.item.type = ITEM_LAYOUT_GRID_FLOW;
4736   ui_litem_init_from_parent(&flow->litem, layout, align);
4737 
4738   flow->litem.space = (flow->litem.align) ? 0 : layout->root->style->columnspace;
4739   flow->row_major = row_major;
4740   flow->columns_len = columns_len;
4741   flow->even_columns = even_columns;
4742   flow->even_rows = even_rows;
4743 
4744   UI_block_layout_set_current(layout->root->block, &flow->litem);
4745 
4746   return &flow->litem;
4747 }
4748 
ui_layout_box(uiLayout * layout,int type)4749 static uiLayoutItemBx *ui_layout_box(uiLayout *layout, int type)
4750 {
4751   uiLayoutItemBx *box;
4752 
4753   box = MEM_callocN(sizeof(uiLayoutItemBx), "uiLayoutItemBx");
4754   ui_litem_init_from_parent(&box->litem, layout, false);
4755 
4756   box->litem.item.type = ITEM_LAYOUT_BOX;
4757   box->litem.space = layout->root->style->columnspace;
4758 
4759   UI_block_layout_set_current(layout->root->block, &box->litem);
4760 
4761   box->roundbox = uiDefBut(layout->root->block, type, 0, "", 0, 0, 0, 0, NULL, 0.0, 0.0, 0, 0, "");
4762 
4763   return box;
4764 }
4765 
uiLayoutRadial(uiLayout * layout)4766 uiLayout *uiLayoutRadial(uiLayout *layout)
4767 {
4768   uiLayout *litem;
4769 
4770   /* radial layouts are only valid for radial menus */
4771   if (layout->root->type != UI_LAYOUT_PIEMENU) {
4772     return ui_item_local_sublayout(layout, layout, 0);
4773   }
4774 
4775   /* only one radial wheel per root layout is allowed, so check and return that, if it exists */
4776   LISTBASE_FOREACH (uiItem *, item, &layout->root->layout->items) {
4777     litem = (uiLayout *)item;
4778     if (litem->item.type == ITEM_LAYOUT_RADIAL) {
4779       UI_block_layout_set_current(layout->root->block, litem);
4780       return litem;
4781     }
4782   }
4783 
4784   litem = MEM_callocN(sizeof(uiLayout), "uiLayoutRadial");
4785   ui_litem_init_from_parent(litem, layout, false);
4786 
4787   litem->item.type = ITEM_LAYOUT_RADIAL;
4788 
4789   UI_block_layout_set_current(layout->root->block, litem);
4790 
4791   return litem;
4792 }
4793 
uiLayoutBox(uiLayout * layout)4794 uiLayout *uiLayoutBox(uiLayout *layout)
4795 {
4796   return (uiLayout *)ui_layout_box(layout, UI_BTYPE_ROUNDBOX);
4797 }
4798 
4799 /**
4800  * Check all buttons defined in this layout,
4801  * and set any button flagged as UI_BUT_LIST_ITEM as active/selected.
4802  * Needed to handle correctly text colors of active (selected) list item.
4803  */
ui_layout_list_set_labels_active(uiLayout * layout)4804 void ui_layout_list_set_labels_active(uiLayout *layout)
4805 {
4806   LISTBASE_FOREACH (uiButtonItem *, bitem, &layout->items) {
4807     if (bitem->item.type != ITEM_BUTTON) {
4808       ui_layout_list_set_labels_active((uiLayout *)(&bitem->item));
4809     }
4810     else if (bitem->but->flag & UI_BUT_LIST_ITEM) {
4811       UI_but_flag_enable(bitem->but, UI_SELECT);
4812     }
4813   }
4814 }
4815 
uiLayoutListBox(uiLayout * layout,uiList * ui_list,PointerRNA * actptr,PropertyRNA * actprop)4816 uiLayout *uiLayoutListBox(uiLayout *layout,
4817                           uiList *ui_list,
4818                           PointerRNA *actptr,
4819                           PropertyRNA *actprop)
4820 {
4821   uiLayoutItemBx *box = ui_layout_box(layout, UI_BTYPE_LISTBOX);
4822   uiBut *but = box->roundbox;
4823 
4824   but->custom_data = ui_list;
4825 
4826   but->rnapoin = *actptr;
4827   but->rnaprop = actprop;
4828 
4829   /* only for the undo string */
4830   if (but->flag & UI_BUT_UNDO) {
4831     but->tip = RNA_property_description(actprop);
4832   }
4833 
4834   return (uiLayout *)box;
4835 }
4836 
uiLayoutAbsolute(uiLayout * layout,bool align)4837 uiLayout *uiLayoutAbsolute(uiLayout *layout, bool align)
4838 {
4839   uiLayout *litem;
4840 
4841   litem = MEM_callocN(sizeof(uiLayout), "uiLayoutAbsolute");
4842   ui_litem_init_from_parent(litem, layout, align);
4843 
4844   litem->item.type = ITEM_LAYOUT_ABSOLUTE;
4845 
4846   UI_block_layout_set_current(layout->root->block, litem);
4847 
4848   return litem;
4849 }
4850 
uiLayoutAbsoluteBlock(uiLayout * layout)4851 uiBlock *uiLayoutAbsoluteBlock(uiLayout *layout)
4852 {
4853   uiBlock *block;
4854 
4855   block = uiLayoutGetBlock(layout);
4856   uiLayoutAbsolute(layout, false);
4857 
4858   return block;
4859 }
4860 
uiLayoutOverlap(uiLayout * layout)4861 uiLayout *uiLayoutOverlap(uiLayout *layout)
4862 {
4863   uiLayout *litem;
4864 
4865   litem = MEM_callocN(sizeof(uiLayout), "uiLayoutOverlap");
4866   ui_litem_init_from_parent(litem, layout, false);
4867 
4868   litem->item.type = ITEM_LAYOUT_OVERLAP;
4869 
4870   UI_block_layout_set_current(layout->root->block, litem);
4871 
4872   return litem;
4873 }
4874 
uiLayoutSplit(uiLayout * layout,float percentage,bool align)4875 uiLayout *uiLayoutSplit(uiLayout *layout, float percentage, bool align)
4876 {
4877   uiLayoutItemSplit *split;
4878 
4879   split = MEM_callocN(sizeof(uiLayoutItemSplit), "uiLayoutItemSplit");
4880   ui_litem_init_from_parent(&split->litem, layout, align);
4881 
4882   split->litem.item.type = ITEM_LAYOUT_SPLIT;
4883   split->litem.space = layout->root->style->columnspace;
4884   split->percentage = percentage;
4885 
4886   UI_block_layout_set_current(layout->root->block, &split->litem);
4887 
4888   return &split->litem;
4889 }
4890 
uiLayoutSetActive(uiLayout * layout,bool active)4891 void uiLayoutSetActive(uiLayout *layout, bool active)
4892 {
4893   layout->active = active;
4894 }
4895 
uiLayoutSetActiveDefault(uiLayout * layout,bool active_default)4896 void uiLayoutSetActiveDefault(uiLayout *layout, bool active_default)
4897 {
4898   layout->active_default = active_default;
4899 }
4900 
uiLayoutSetActivateInit(uiLayout * layout,bool activate_init)4901 void uiLayoutSetActivateInit(uiLayout *layout, bool activate_init)
4902 {
4903   layout->activate_init = activate_init;
4904 }
4905 
uiLayoutSetEnabled(uiLayout * layout,bool enabled)4906 void uiLayoutSetEnabled(uiLayout *layout, bool enabled)
4907 {
4908   layout->enabled = enabled;
4909 }
4910 
uiLayoutSetRedAlert(uiLayout * layout,bool redalert)4911 void uiLayoutSetRedAlert(uiLayout *layout, bool redalert)
4912 {
4913   layout->redalert = redalert;
4914 }
4915 
uiLayoutSetKeepAspect(uiLayout * layout,bool keepaspect)4916 void uiLayoutSetKeepAspect(uiLayout *layout, bool keepaspect)
4917 {
4918   layout->keepaspect = keepaspect;
4919 }
4920 
uiLayoutSetAlignment(uiLayout * layout,char alignment)4921 void uiLayoutSetAlignment(uiLayout *layout, char alignment)
4922 {
4923   layout->alignment = alignment;
4924 }
4925 
uiLayoutSetScaleX(uiLayout * layout,float scale)4926 void uiLayoutSetScaleX(uiLayout *layout, float scale)
4927 {
4928   layout->scale[0] = scale;
4929 }
4930 
uiLayoutSetScaleY(uiLayout * layout,float scale)4931 void uiLayoutSetScaleY(uiLayout *layout, float scale)
4932 {
4933   layout->scale[1] = scale;
4934 }
4935 
uiLayoutSetUnitsX(uiLayout * layout,float unit)4936 void uiLayoutSetUnitsX(uiLayout *layout, float unit)
4937 {
4938   layout->units[0] = unit;
4939 }
4940 
uiLayoutSetUnitsY(uiLayout * layout,float unit)4941 void uiLayoutSetUnitsY(uiLayout *layout, float unit)
4942 {
4943   layout->units[1] = unit;
4944 }
4945 
uiLayoutSetEmboss(uiLayout * layout,char emboss)4946 void uiLayoutSetEmboss(uiLayout *layout, char emboss)
4947 {
4948   layout->emboss = emboss;
4949 }
4950 
uiLayoutGetPropSep(uiLayout * layout)4951 bool uiLayoutGetPropSep(uiLayout *layout)
4952 {
4953   return (layout->item.flag & UI_ITEM_PROP_SEP) != 0;
4954 }
4955 
uiLayoutSetPropSep(uiLayout * layout,bool is_sep)4956 void uiLayoutSetPropSep(uiLayout *layout, bool is_sep)
4957 {
4958   SET_FLAG_FROM_TEST(layout->item.flag, is_sep, UI_ITEM_PROP_SEP);
4959 }
4960 
uiLayoutGetPropDecorate(uiLayout * layout)4961 bool uiLayoutGetPropDecorate(uiLayout *layout)
4962 {
4963   return (layout->item.flag & UI_ITEM_PROP_DECORATE) != 0;
4964 }
4965 
uiLayoutSetPropDecorate(uiLayout * layout,bool is_sep)4966 void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep)
4967 {
4968   SET_FLAG_FROM_TEST(layout->item.flag, is_sep, UI_ITEM_PROP_DECORATE);
4969 }
4970 
uiLayoutGetActive(uiLayout * layout)4971 bool uiLayoutGetActive(uiLayout *layout)
4972 {
4973   return layout->active;
4974 }
4975 
uiLayoutGetActiveDefault(uiLayout * layout)4976 bool uiLayoutGetActiveDefault(uiLayout *layout)
4977 {
4978   return layout->active_default;
4979 }
4980 
uiLayoutGetActivateInit(uiLayout * layout)4981 bool uiLayoutGetActivateInit(uiLayout *layout)
4982 {
4983   return layout->activate_init;
4984 }
4985 
uiLayoutGetEnabled(uiLayout * layout)4986 bool uiLayoutGetEnabled(uiLayout *layout)
4987 {
4988   return layout->enabled;
4989 }
4990 
uiLayoutGetRedAlert(uiLayout * layout)4991 bool uiLayoutGetRedAlert(uiLayout *layout)
4992 {
4993   return layout->redalert;
4994 }
4995 
uiLayoutGetKeepAspect(uiLayout * layout)4996 bool uiLayoutGetKeepAspect(uiLayout *layout)
4997 {
4998   return layout->keepaspect;
4999 }
5000 
uiLayoutGetAlignment(uiLayout * layout)5001 int uiLayoutGetAlignment(uiLayout *layout)
5002 {
5003   return layout->alignment;
5004 }
5005 
uiLayoutGetWidth(uiLayout * layout)5006 int uiLayoutGetWidth(uiLayout *layout)
5007 {
5008   return layout->w;
5009 }
5010 
uiLayoutGetScaleX(uiLayout * layout)5011 float uiLayoutGetScaleX(uiLayout *layout)
5012 {
5013   return layout->scale[0];
5014 }
5015 
uiLayoutGetScaleY(uiLayout * layout)5016 float uiLayoutGetScaleY(uiLayout *layout)
5017 {
5018   return layout->scale[1];
5019 }
5020 
uiLayoutGetUnitsX(uiLayout * layout)5021 float uiLayoutGetUnitsX(uiLayout *layout)
5022 {
5023   return layout->units[0];
5024 }
5025 
uiLayoutGetUnitsY(uiLayout * layout)5026 float uiLayoutGetUnitsY(uiLayout *layout)
5027 {
5028   return layout->units[1];
5029 }
5030 
uiLayoutGetEmboss(uiLayout * layout)5031 int uiLayoutGetEmboss(uiLayout *layout)
5032 {
5033   if (layout->emboss == UI_EMBOSS_UNDEFINED) {
5034     return layout->root->block->emboss;
5035   }
5036   return layout->emboss;
5037 }
5038 
5039 /** \} */
5040 
5041 /* -------------------------------------------------------------------- */
5042 /** \name Block Layout Search Filtering
5043  * \{ */
5044 
5045 /* Disabled for performance reasons, but this could be turned on in the future. */
5046 // #define PROPERTY_SEARCH_USE_TOOLTIPS
5047 
block_search_panel_label_matches(const uiBlock * block,const char * search_string)5048 static bool block_search_panel_label_matches(const uiBlock *block, const char *search_string)
5049 {
5050   if ((block->panel != NULL) && (block->panel->type != NULL)) {
5051     if (BLI_strcasestr(block->panel->type->label, search_string)) {
5052       return true;
5053     }
5054   }
5055   return false;
5056 }
5057 
5058 /**
5059  * Returns true if a button or the data / operator it represents matches the search filter.
5060  */
button_matches_search_filter(uiBut * but,const char * search_filter)5061 static bool button_matches_search_filter(uiBut *but, const char *search_filter)
5062 {
5063   /* Do the shorter checks first for better performance in case there is a match. */
5064   if (BLI_strcasestr(but->str, search_filter)) {
5065     return true;
5066   }
5067 
5068   if (but->optype != NULL) {
5069     if (BLI_strcasestr(but->optype->name, search_filter)) {
5070       return true;
5071     }
5072   }
5073 
5074   if (but->rnaprop != NULL) {
5075     if (BLI_strcasestr(RNA_property_ui_name(but->rnaprop), search_filter)) {
5076       return true;
5077     }
5078 #ifdef PROPERTY_SEARCH_USE_TOOLTIPS
5079     if (BLI_strcasestr(RNA_property_description(but->rnaprop), search_filter)) {
5080       return true;
5081     }
5082 #endif
5083 
5084     /* Search through labels of enum property items if they are in a drop-down menu.
5085      * Unfortunately we have no #bContext here so we cannot search through RNA enums
5086      * with dynamic entries (or "itemf" functions) which require context. */
5087     if (but->type == UI_BTYPE_MENU) {
5088       PointerRNA *ptr = &but->rnapoin;
5089       PropertyRNA *enum_prop = but->rnaprop;
5090 
5091       int items_len;
5092       const EnumPropertyItem *items_array = NULL;
5093       bool free;
5094       RNA_property_enum_items_gettexted(NULL, ptr, enum_prop, &items_array, &items_len, &free);
5095 
5096       if (items_array == NULL) {
5097         return false;
5098       }
5099 
5100       for (int i = 0; i < items_len; i++) {
5101         /* Check for NULL name field which enums use for separators. */
5102         if (items_array[i].name == NULL) {
5103           continue;
5104         }
5105         if (BLI_strcasestr(items_array[i].name, search_filter)) {
5106           return true;
5107         }
5108       }
5109       if (free) {
5110         MEM_freeN((EnumPropertyItem *)items_array);
5111       }
5112     }
5113   }
5114 
5115   return false;
5116 }
5117 
5118 /**
5119  * Test for a search result within a specific button group.
5120  */
button_group_has_search_match(uiButtonGroup * button_group,const char * search_filter)5121 static bool button_group_has_search_match(uiButtonGroup *button_group, const char *search_filter)
5122 {
5123   LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) {
5124     uiBut *but = link->data;
5125     if (button_matches_search_filter(but, search_filter)) {
5126       return true;
5127     }
5128   }
5129 
5130   return false;
5131 }
5132 
5133 /**
5134  * Apply the search filter, tagging all buttons with whether they match or not.
5135  * Tag every button in the group as a result if any button in the group matches.
5136  *
5137  * \note It would be great to return early here if we found a match, but because
5138  * the results may be visible we have to continue searching the entire block.
5139  *
5140  * \return True if the block has any search results.
5141  */
block_search_filter_tag_buttons(uiBlock * block,const char * search_filter)5142 static bool block_search_filter_tag_buttons(uiBlock *block, const char *search_filter)
5143 {
5144   bool has_result = false;
5145   LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) {
5146     if (button_group_has_search_match(button_group, search_filter)) {
5147       has_result = true;
5148     }
5149     else {
5150       LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) {
5151         uiBut *but = link->data;
5152         but->flag |= UI_SEARCH_FILTER_NO_MATCH;
5153       }
5154     }
5155   }
5156   return has_result;
5157 }
5158 
5159 /**
5160  * Apply property search behavior, setting panel flags and deactivating buttons that don't match.
5161  *
5162  * \note Must not be run after #UI_block_layout_resolve.
5163  */
UI_block_apply_search_filter(uiBlock * block,const char * search_filter)5164 bool UI_block_apply_search_filter(uiBlock *block, const char *search_filter)
5165 {
5166   if (search_filter == NULL || search_filter[0] == '\0') {
5167     return false;
5168   }
5169 
5170   if (block->panel && block->panel->type && block->panel->type->flag & PNL_NO_SEARCH) {
5171     return false;
5172   }
5173 
5174   const bool panel_label_matches = block_search_panel_label_matches(block, search_filter);
5175 
5176   const bool has_result = (panel_label_matches) ?
5177                               true :
5178                               block_search_filter_tag_buttons(block, search_filter);
5179 
5180   if (block->panel != NULL) {
5181     if (has_result) {
5182       ui_panel_tag_search_filter_match(block->panel);
5183     }
5184   }
5185 
5186   return has_result;
5187 }
5188 
5189 /** \} */
5190 
5191 /* -------------------------------------------------------------------- */
5192 /** \name Layout
5193  * \{ */
5194 
ui_item_scale(uiLayout * litem,const float scale[2])5195 static void ui_item_scale(uiLayout *litem, const float scale[2])
5196 {
5197   int x, y, w, h;
5198 
5199   LISTBASE_FOREACH_BACKWARD (uiItem *, item, &litem->items) {
5200     if (item->type != ITEM_BUTTON) {
5201       uiLayout *subitem = (uiLayout *)item;
5202       ui_item_scale(subitem, scale);
5203     }
5204 
5205     ui_item_size(item, &w, &h);
5206     ui_item_offset(item, &x, &y);
5207 
5208     if (scale[0] != 0.0f) {
5209       x *= scale[0];
5210       w *= scale[0];
5211     }
5212 
5213     if (scale[1] != 0.0f) {
5214       y *= scale[1];
5215       h *= scale[1];
5216     }
5217 
5218     ui_item_position(item, x, y, w, h);
5219   }
5220 }
5221 
ui_item_estimate(uiItem * item)5222 static void ui_item_estimate(uiItem *item)
5223 {
5224   if (item->type != ITEM_BUTTON) {
5225     uiLayout *litem = (uiLayout *)item;
5226 
5227     LISTBASE_FOREACH (uiItem *, subitem, &litem->items) {
5228       ui_item_estimate(subitem);
5229     }
5230 
5231     if (BLI_listbase_is_empty(&litem->items)) {
5232       litem->w = 0;
5233       litem->h = 0;
5234       return;
5235     }
5236 
5237     if (litem->scale[0] != 0.0f || litem->scale[1] != 0.0f) {
5238       ui_item_scale(litem, litem->scale);
5239     }
5240 
5241     switch (litem->item.type) {
5242       case ITEM_LAYOUT_COLUMN:
5243         ui_litem_estimate_column(litem, false);
5244         break;
5245       case ITEM_LAYOUT_COLUMN_FLOW:
5246         ui_litem_estimate_column_flow(litem);
5247         break;
5248       case ITEM_LAYOUT_GRID_FLOW:
5249         ui_litem_estimate_grid_flow(litem);
5250         break;
5251       case ITEM_LAYOUT_ROW:
5252         ui_litem_estimate_row(litem);
5253         break;
5254       case ITEM_LAYOUT_BOX:
5255         ui_litem_estimate_box(litem);
5256         break;
5257       case ITEM_LAYOUT_ROOT:
5258         ui_litem_estimate_root(litem);
5259         break;
5260       case ITEM_LAYOUT_ABSOLUTE:
5261         ui_litem_estimate_absolute(litem);
5262         break;
5263       case ITEM_LAYOUT_SPLIT:
5264         ui_litem_estimate_split(litem);
5265         break;
5266       case ITEM_LAYOUT_OVERLAP:
5267         ui_litem_estimate_overlap(litem);
5268         break;
5269       default:
5270         break;
5271     }
5272 
5273     /* Force fixed size. */
5274     if (litem->units[0] > 0) {
5275       litem->w = UI_UNIT_X * litem->units[0];
5276     }
5277     if (litem->units[1] > 0) {
5278       litem->h = UI_UNIT_Y * litem->units[1];
5279     }
5280   }
5281 }
5282 
ui_item_align(uiLayout * litem,short nr)5283 static void ui_item_align(uiLayout *litem, short nr)
5284 {
5285   uiButtonItem *bitem;
5286   uiLayoutItemBx *box;
5287 
5288   LISTBASE_FOREACH_BACKWARD (uiItem *, item, &litem->items) {
5289     if (item->type == ITEM_BUTTON) {
5290       bitem = (uiButtonItem *)item;
5291 #ifndef USE_UIBUT_SPATIAL_ALIGN
5292       if (ui_but_can_align(bitem->but))
5293 #endif
5294       {
5295         if (!bitem->but->alignnr) {
5296           bitem->but->alignnr = nr;
5297         }
5298       }
5299     }
5300     else if (item->type == ITEM_LAYOUT_ABSOLUTE) {
5301       /* pass */
5302     }
5303     else if (item->type == ITEM_LAYOUT_OVERLAP) {
5304       /* pass */
5305     }
5306     else if (item->type == ITEM_LAYOUT_BOX) {
5307       box = (uiLayoutItemBx *)item;
5308       if (!box->roundbox->alignnr) {
5309         box->roundbox->alignnr = nr;
5310       }
5311     }
5312     else if (((uiLayout *)item)->align) {
5313       ui_item_align((uiLayout *)item, nr);
5314     }
5315   }
5316 }
5317 
ui_item_flag(uiLayout * litem,int flag)5318 static void ui_item_flag(uiLayout *litem, int flag)
5319 {
5320   uiButtonItem *bitem;
5321 
5322   LISTBASE_FOREACH_BACKWARD (uiItem *, item, &litem->items) {
5323     if (item->type == ITEM_BUTTON) {
5324       bitem = (uiButtonItem *)item;
5325       bitem->but->flag |= flag;
5326     }
5327     else {
5328       ui_item_flag((uiLayout *)item, flag);
5329     }
5330   }
5331 }
5332 
ui_item_layout(uiItem * item)5333 static void ui_item_layout(uiItem *item)
5334 {
5335   if (item->type != ITEM_BUTTON) {
5336     uiLayout *litem = (uiLayout *)item;
5337 
5338     if (BLI_listbase_is_empty(&litem->items)) {
5339       return;
5340     }
5341 
5342     if (litem->align) {
5343       ui_item_align(litem, ++litem->root->block->alignnr);
5344     }
5345     if (!litem->active) {
5346       ui_item_flag(litem, UI_BUT_INACTIVE);
5347     }
5348     if (!litem->enabled) {
5349       ui_item_flag(litem, UI_BUT_DISABLED);
5350     }
5351 
5352     switch (litem->item.type) {
5353       case ITEM_LAYOUT_COLUMN:
5354         ui_litem_layout_column(litem, false, false);
5355         break;
5356       case ITEM_LAYOUT_COLUMN_FLOW:
5357         ui_litem_layout_column_flow(litem);
5358         break;
5359       case ITEM_LAYOUT_GRID_FLOW:
5360         ui_litem_layout_grid_flow(litem);
5361         break;
5362       case ITEM_LAYOUT_ROW:
5363         ui_litem_layout_row(litem);
5364         break;
5365       case ITEM_LAYOUT_BOX:
5366         ui_litem_layout_box(litem);
5367         break;
5368       case ITEM_LAYOUT_ROOT:
5369         ui_litem_layout_root(litem);
5370         break;
5371       case ITEM_LAYOUT_ABSOLUTE:
5372         ui_litem_layout_absolute(litem);
5373         break;
5374       case ITEM_LAYOUT_SPLIT:
5375         ui_litem_layout_split(litem);
5376         break;
5377       case ITEM_LAYOUT_OVERLAP:
5378         ui_litem_layout_overlap(litem);
5379         break;
5380       case ITEM_LAYOUT_RADIAL:
5381         ui_litem_layout_radial(litem);
5382         break;
5383       default:
5384         break;
5385     }
5386 
5387     LISTBASE_FOREACH (uiItem *, subitem, &litem->items) {
5388       if (item->flag & UI_ITEM_BOX_ITEM) {
5389         subitem->flag |= UI_ITEM_BOX_ITEM;
5390       }
5391       ui_item_layout(subitem);
5392     }
5393   }
5394   else {
5395     if (item->flag & UI_ITEM_BOX_ITEM) {
5396       uiButtonItem *bitem = (uiButtonItem *)item;
5397       bitem->but->drawflag |= UI_BUT_BOX_ITEM;
5398     }
5399   }
5400 }
5401 
ui_layout_end(uiBlock * block,uiLayout * layout,int * r_x,int * r_y)5402 static void ui_layout_end(uiBlock *block, uiLayout *layout, int *r_x, int *r_y)
5403 {
5404   if (layout->root->handlefunc) {
5405     UI_block_func_handle_set(block, layout->root->handlefunc, layout->root->argv);
5406   }
5407 
5408   ui_item_estimate(&layout->item);
5409   ui_item_layout(&layout->item);
5410 
5411   if (r_x) {
5412     *r_x = layout->x;
5413   }
5414   if (r_y) {
5415     *r_y = layout->y;
5416   }
5417 }
5418 
ui_layout_free(uiLayout * layout)5419 static void ui_layout_free(uiLayout *layout)
5420 {
5421   LISTBASE_FOREACH_MUTABLE (uiItem *, item, &layout->items) {
5422     if (item->type == ITEM_BUTTON) {
5423       uiButtonItem *bitem = (uiButtonItem *)item;
5424 
5425       bitem->but->layout = NULL;
5426       MEM_freeN(item);
5427     }
5428     else {
5429       ui_layout_free((uiLayout *)item);
5430     }
5431   }
5432 
5433   MEM_freeN(layout);
5434 }
5435 
ui_layout_add_padding_button(uiLayoutRoot * root)5436 static void ui_layout_add_padding_button(uiLayoutRoot *root)
5437 {
5438   if (root->padding) {
5439     /* add an invisible button for padding */
5440     uiBlock *block = root->block;
5441     uiLayout *prev_layout = block->curlayout;
5442 
5443     block->curlayout = root->layout;
5444     uiDefBut(
5445         block, UI_BTYPE_SEPR, 0, "", 0, 0, root->padding, root->padding, NULL, 0.0, 0.0, 0, 0, "");
5446     block->curlayout = prev_layout;
5447   }
5448 }
5449 
UI_block_layout(uiBlock * block,int dir,int type,int x,int y,int size,int em,int padding,const uiStyle * style)5450 uiLayout *UI_block_layout(uiBlock *block,
5451                           int dir,
5452                           int type,
5453                           int x,
5454                           int y,
5455                           int size,
5456                           int em,
5457                           int padding,
5458                           const uiStyle *style)
5459 {
5460   uiLayout *layout;
5461   uiLayoutRoot *root;
5462 
5463   root = MEM_callocN(sizeof(uiLayoutRoot), "uiLayoutRoot");
5464   root->type = type;
5465   root->style = style;
5466   root->block = block;
5467   root->padding = padding;
5468   root->opcontext = WM_OP_INVOKE_REGION_WIN;
5469 
5470   layout = MEM_callocN(sizeof(uiLayout), "uiLayout");
5471   layout->item.type = (type == UI_LAYOUT_VERT_BAR) ? ITEM_LAYOUT_COLUMN : ITEM_LAYOUT_ROOT;
5472 
5473   /* Only used when 'UI_ITEM_PROP_SEP' is set. */
5474   layout->item.flag = UI_ITEM_PROP_DECORATE;
5475 
5476   layout->x = x;
5477   layout->y = y;
5478   layout->root = root;
5479   layout->space = style->templatespace;
5480   layout->active = 1;
5481   layout->enabled = 1;
5482   layout->context = NULL;
5483   layout->emboss = UI_EMBOSS_UNDEFINED;
5484 
5485   if (type == UI_LAYOUT_MENU || type == UI_LAYOUT_PIEMENU) {
5486     layout->space = 0;
5487   }
5488 
5489   if (dir == UI_LAYOUT_HORIZONTAL) {
5490     layout->h = size;
5491     layout->root->emh = em * UI_UNIT_Y;
5492   }
5493   else {
5494     layout->w = size;
5495     layout->root->emw = em * UI_UNIT_X;
5496   }
5497 
5498   block->curlayout = layout;
5499   root->layout = layout;
5500   BLI_addtail(&block->layouts, root);
5501 
5502   ui_layout_add_padding_button(root);
5503 
5504   return layout;
5505 }
5506 
uiLayoutGetBlock(uiLayout * layout)5507 uiBlock *uiLayoutGetBlock(uiLayout *layout)
5508 {
5509   return layout->root->block;
5510 }
5511 
uiLayoutGetOperatorContext(uiLayout * layout)5512 int uiLayoutGetOperatorContext(uiLayout *layout)
5513 {
5514   return layout->root->opcontext;
5515 }
5516 
UI_block_layout_set_current(uiBlock * block,uiLayout * layout)5517 void UI_block_layout_set_current(uiBlock *block, uiLayout *layout)
5518 {
5519   block->curlayout = layout;
5520 }
5521 
ui_layout_add_but(uiLayout * layout,uiBut * but)5522 void ui_layout_add_but(uiLayout *layout, uiBut *but)
5523 {
5524   uiButtonItem *bitem;
5525 
5526   bitem = MEM_callocN(sizeof(uiButtonItem), "uiButtonItem");
5527   bitem->item.type = ITEM_BUTTON;
5528   bitem->but = but;
5529 
5530   int w, h;
5531   ui_item_size((uiItem *)bitem, &w, &h);
5532   /* XXX uiBut hasn't scaled yet
5533    * we can flag the button as not expandable, depending on its size */
5534   if (w <= 2 * UI_UNIT_X && (!but->str || but->str[0] == '\0')) {
5535     bitem->item.flag |= UI_ITEM_FIXED_SIZE;
5536   }
5537 
5538   if (layout->child_items_layout) {
5539     BLI_addtail(&layout->child_items_layout->items, bitem);
5540   }
5541   else {
5542     BLI_addtail(&layout->items, bitem);
5543   }
5544   but->layout = layout;
5545 
5546   if (layout->context) {
5547     but->context = layout->context;
5548     but->context->used = true;
5549   }
5550 
5551   if (layout->emboss != UI_EMBOSS_UNDEFINED) {
5552     but->emboss = layout->emboss;
5553   }
5554 
5555   ui_button_group_add_but(uiLayoutGetBlock(layout), but);
5556 }
5557 
ui_layout_replace_but_ptr(uiLayout * layout,const void * old_but_ptr,uiBut * new_but)5558 bool ui_layout_replace_but_ptr(uiLayout *layout, const void *old_but_ptr, uiBut *new_but)
5559 {
5560   ListBase *child_list = layout->child_items_layout ? &layout->child_items_layout->items :
5561                                                       &layout->items;
5562 
5563   LISTBASE_FOREACH (uiItem *, item, child_list) {
5564     if (item->type == ITEM_BUTTON) {
5565       uiButtonItem *bitem = (uiButtonItem *)item;
5566 
5567       if (bitem->but == old_but_ptr) {
5568         bitem->but = new_but;
5569         return true;
5570       }
5571     }
5572     else {
5573       if (ui_layout_replace_but_ptr((uiLayout *)item, old_but_ptr, new_but)) {
5574         return true;
5575       }
5576     }
5577   }
5578 
5579   return false;
5580 }
5581 
uiLayoutSetFixedSize(uiLayout * layout,bool fixed_size)5582 void uiLayoutSetFixedSize(uiLayout *layout, bool fixed_size)
5583 {
5584   if (fixed_size) {
5585     layout->item.flag |= UI_ITEM_FIXED_SIZE;
5586   }
5587   else {
5588     layout->item.flag &= ~UI_ITEM_FIXED_SIZE;
5589   }
5590 }
5591 
uiLayoutGetFixedSize(uiLayout * layout)5592 bool uiLayoutGetFixedSize(uiLayout *layout)
5593 {
5594   return (layout->item.flag & UI_ITEM_FIXED_SIZE) != 0;
5595 }
5596 
uiLayoutSetOperatorContext(uiLayout * layout,int opcontext)5597 void uiLayoutSetOperatorContext(uiLayout *layout, int opcontext)
5598 {
5599   layout->root->opcontext = opcontext;
5600 }
5601 
uiLayoutSetFunc(uiLayout * layout,uiMenuHandleFunc handlefunc,void * argv)5602 void uiLayoutSetFunc(uiLayout *layout, uiMenuHandleFunc handlefunc, void *argv)
5603 {
5604   layout->root->handlefunc = handlefunc;
5605   layout->root->argv = argv;
5606 }
5607 
5608 /**
5609  * Used for property search when the layout process needs to be cancelled in order to avoid
5610  * computing the locations for buttons, but the layout items created while adding the buttons
5611  * must still be freed.
5612  */
UI_block_layout_free(uiBlock * block)5613 void UI_block_layout_free(uiBlock *block)
5614 {
5615   LISTBASE_FOREACH_MUTABLE (uiLayoutRoot *, root, &block->layouts) {
5616     ui_layout_free(root->layout);
5617     MEM_freeN(root);
5618   }
5619 }
5620 
UI_block_layout_resolve(uiBlock * block,int * r_x,int * r_y)5621 void UI_block_layout_resolve(uiBlock *block, int *r_x, int *r_y)
5622 {
5623   BLI_assert(block->active);
5624 
5625   if (r_x) {
5626     *r_x = 0;
5627   }
5628   if (r_y) {
5629     *r_y = 0;
5630   }
5631 
5632   block->curlayout = NULL;
5633 
5634   LISTBASE_FOREACH_MUTABLE (uiLayoutRoot *, root, &block->layouts) {
5635     ui_layout_add_padding_button(root);
5636 
5637     /* NULL in advance so we don't interfere when adding button */
5638     ui_layout_end(block, root->layout, r_x, r_y);
5639     ui_layout_free(root->layout);
5640     MEM_freeN(root);
5641   }
5642 
5643   BLI_listbase_clear(&block->layouts);
5644 
5645   /* XXX silly trick, interface_templates.c doesn't get linked
5646    * because it's not used by other files in this module? */
5647   {
5648     UI_template_fix_linking();
5649   }
5650 }
5651 
uiLayoutSetContextPointer(uiLayout * layout,const char * name,PointerRNA * ptr)5652 void uiLayoutSetContextPointer(uiLayout *layout, const char *name, PointerRNA *ptr)
5653 {
5654   uiBlock *block = layout->root->block;
5655   layout->context = CTX_store_add(&block->contexts, name, ptr);
5656 }
5657 
uiLayoutContextCopy(uiLayout * layout,bContextStore * context)5658 void uiLayoutContextCopy(uiLayout *layout, bContextStore *context)
5659 {
5660   uiBlock *block = layout->root->block;
5661   layout->context = CTX_store_add_all(&block->contexts, context);
5662 }
5663 
uiLayoutSetContextFromBut(uiLayout * layout,uiBut * but)5664 void uiLayoutSetContextFromBut(uiLayout *layout, uiBut *but)
5665 {
5666   if (but->opptr) {
5667     uiLayoutSetContextPointer(layout, "button_operator", but->opptr);
5668   }
5669 
5670   if (but->rnapoin.data && but->rnaprop) {
5671     /* TODO: index could be supported as well */
5672     PointerRNA ptr_prop;
5673     RNA_pointer_create(NULL, &RNA_Property, but->rnaprop, &ptr_prop);
5674     uiLayoutSetContextPointer(layout, "button_prop", &ptr_prop);
5675     uiLayoutSetContextPointer(layout, "button_pointer", &but->rnapoin);
5676   }
5677 }
5678 
5679 /* this is a bit of a hack but best keep it in one place at least */
UI_but_operatortype_get_from_enum_menu(uiBut * but,PropertyRNA ** r_prop)5680 wmOperatorType *UI_but_operatortype_get_from_enum_menu(uiBut *but, PropertyRNA **r_prop)
5681 {
5682   if (r_prop != NULL) {
5683     *r_prop = NULL;
5684   }
5685 
5686   if (but->menu_create_func == menu_item_enum_opname_menu) {
5687     MenuItemLevel *lvl = but->func_argN;
5688     wmOperatorType *ot = WM_operatortype_find(lvl->opname, false);
5689     if ((ot != NULL) && (r_prop != NULL)) {
5690       *r_prop = RNA_struct_type_find_property(ot->srna, lvl->propname);
5691     }
5692     return ot;
5693   }
5694   return NULL;
5695 }
5696 
5697 /* this is a bit of a hack but best keep it in one place at least */
UI_but_menutype_get(uiBut * but)5698 MenuType *UI_but_menutype_get(uiBut *but)
5699 {
5700   if (but->menu_create_func == ui_item_menutype_func) {
5701     return (MenuType *)but->poin;
5702   }
5703   return NULL;
5704 }
5705 
5706 /* this is a bit of a hack but best keep it in one place at least */
UI_but_paneltype_get(uiBut * but)5707 PanelType *UI_but_paneltype_get(uiBut *but)
5708 {
5709   if (but->menu_create_func == ui_item_paneltype_func) {
5710     return (PanelType *)but->poin;
5711   }
5712   return NULL;
5713 }
5714 
UI_menutype_draw(bContext * C,MenuType * mt,struct uiLayout * layout)5715 void UI_menutype_draw(bContext *C, MenuType *mt, struct uiLayout *layout)
5716 {
5717   Menu menu = {
5718       .layout = layout,
5719       .type = mt,
5720   };
5721 
5722   if (G.debug & G_DEBUG_WM) {
5723     printf("%s: opening menu \"%s\"\n", __func__, mt->idname);
5724   }
5725 
5726   if (layout->context) {
5727     CTX_store_set(C, layout->context);
5728   }
5729 
5730   mt->draw(C, &menu);
5731 
5732   if (layout->context) {
5733     CTX_store_set(C, NULL);
5734   }
5735 }
5736 
ui_layout_has_panel_label(const uiLayout * layout,const PanelType * pt)5737 static bool ui_layout_has_panel_label(const uiLayout *layout, const PanelType *pt)
5738 {
5739   LISTBASE_FOREACH (uiItem *, subitem, &layout->items) {
5740     if (subitem->type == ITEM_BUTTON) {
5741       uiButtonItem *bitem = (uiButtonItem *)subitem;
5742       if (!(bitem->but->flag & UI_HIDDEN) && STREQ(bitem->but->str, pt->label)) {
5743         return true;
5744       }
5745     }
5746     else {
5747       uiLayout *litem = (uiLayout *)subitem;
5748       if (ui_layout_has_panel_label(litem, pt)) {
5749         return true;
5750       }
5751     }
5752   }
5753 
5754   return false;
5755 }
5756 
ui_paneltype_draw_impl(bContext * C,PanelType * pt,uiLayout * layout,bool show_header)5757 static void ui_paneltype_draw_impl(bContext *C, PanelType *pt, uiLayout *layout, bool show_header)
5758 {
5759   Panel *panel = MEM_callocN(sizeof(Panel), "popover panel");
5760   panel->type = pt;
5761   panel->flag = PNL_POPOVER;
5762 
5763   uiLayout *last_item = layout->items.last;
5764 
5765   /* Draw main panel. */
5766   if (show_header) {
5767     uiLayout *row = uiLayoutRow(layout, false);
5768     if (pt->draw_header) {
5769       panel->layout = row;
5770       pt->draw_header(C, panel);
5771       panel->layout = NULL;
5772     }
5773 
5774     /* draw_header() is often used to add a checkbox to the header. If we add the label like below
5775      * the label is disconnected from the checkbox, adding a weird looking gap. As workaround, let
5776      * the checkbox add the label instead. */
5777     if (!ui_layout_has_panel_label(row, pt)) {
5778       uiItemL(row, CTX_IFACE_(pt->translation_context, pt->label), ICON_NONE);
5779     }
5780   }
5781 
5782   panel->layout = layout;
5783   pt->draw(C, panel);
5784   panel->layout = NULL;
5785   BLI_assert(panel->runtime.custom_data_ptr == NULL);
5786 
5787   MEM_freeN(panel);
5788 
5789   /* Draw child panels. */
5790   LISTBASE_FOREACH (LinkData *, link, &pt->children) {
5791     PanelType *child_pt = link->data;
5792 
5793     if (child_pt->poll == NULL || child_pt->poll(C, child_pt)) {
5794       /* Add space if something was added to the layout. */
5795       if (last_item != layout->items.last) {
5796         uiItemS(layout);
5797         last_item = layout->items.last;
5798       }
5799 
5800       uiLayout *col = uiLayoutColumn(layout, false);
5801       ui_paneltype_draw_impl(C, child_pt, col, true);
5802     }
5803   }
5804 }
5805 
5806 /**
5807  * Used for popup panels only.
5808  */
UI_paneltype_draw(bContext * C,PanelType * pt,uiLayout * layout)5809 void UI_paneltype_draw(bContext *C, PanelType *pt, uiLayout *layout)
5810 {
5811   if (layout->context) {
5812     CTX_store_set(C, layout->context);
5813   }
5814 
5815   ui_paneltype_draw_impl(C, pt, layout, false);
5816 
5817   if (layout->context) {
5818     CTX_store_set(C, NULL);
5819   }
5820 }
5821 
5822 /** \} */
5823 
5824 /* -------------------------------------------------------------------- */
5825 /** \name Layout (Debugging/Introspection)
5826  *
5827  * Serialize the layout as a Python compatible dictionary,
5828  *
5829  * \note Proper string escaping isn't used,
5830  * triple quotes are used to prevent single quotes from interfering with Python syntax.
5831  * If we want this to be fool-proof, we would need full Python compatible string escape support.
5832  * As we don't use triple quotes in the UI it's good-enough in practice.
5833  * \{ */
5834 
ui_layout_introspect_button(DynStr * ds,uiButtonItem * bitem)5835 static void ui_layout_introspect_button(DynStr *ds, uiButtonItem *bitem)
5836 {
5837   uiBut *but = bitem->but;
5838   BLI_dynstr_appendf(ds, "'type':%d, ", (int)but->type);
5839   BLI_dynstr_appendf(ds, "'draw_string':'''%s''', ", but->drawstr);
5840   /* Not exactly needed, rna has this. */
5841   BLI_dynstr_appendf(ds, "'tip':'''%s''', ", but->tip ? but->tip : "");
5842 
5843   if (but->optype) {
5844     char *opstr = WM_operator_pystring_ex(
5845         but->block->evil_C, NULL, false, true, but->optype, but->opptr);
5846     BLI_dynstr_appendf(ds, "'operator':'''%s''', ", opstr ? opstr : "");
5847     MEM_freeN(opstr);
5848   }
5849 
5850   {
5851     PropertyRNA *prop = NULL;
5852     wmOperatorType *ot = UI_but_operatortype_get_from_enum_menu(but, &prop);
5853     if (ot) {
5854       char *opstr = WM_operator_pystring_ex(but->block->evil_C, NULL, false, true, ot, NULL);
5855       BLI_dynstr_appendf(ds, "'operator':'''%s''', ", opstr ? opstr : "");
5856       BLI_dynstr_appendf(ds, "'property':'''%s''', ", prop ? RNA_property_identifier(prop) : "");
5857       MEM_freeN(opstr);
5858     }
5859   }
5860 
5861   if (but->rnaprop) {
5862     BLI_dynstr_appendf(ds,
5863                        "'rna':'%s.%s[%d]', ",
5864                        RNA_struct_identifier(but->rnapoin.type),
5865                        RNA_property_identifier(but->rnaprop),
5866                        but->rnaindex);
5867   }
5868 }
5869 
ui_layout_introspect_items(DynStr * ds,ListBase * lb)5870 static void ui_layout_introspect_items(DynStr *ds, ListBase *lb)
5871 {
5872   uiItem *item;
5873 
5874   BLI_dynstr_append(ds, "[");
5875 
5876   for (item = lb->first; item; item = item->next) {
5877 
5878     BLI_dynstr_append(ds, "{");
5879 
5880 #define CASE_ITEM(id) \
5881   case id: { \
5882     const char *id_str = STRINGIFY(id); \
5883     BLI_dynstr_append(ds, "'type': '"); \
5884     /* Skip 'ITEM_'. */ \
5885     BLI_dynstr_append(ds, id_str + 5); \
5886     BLI_dynstr_append(ds, "', "); \
5887     break; \
5888   } \
5889     ((void)0)
5890 
5891     switch (item->type) {
5892       CASE_ITEM(ITEM_BUTTON);
5893       CASE_ITEM(ITEM_LAYOUT_ROW);
5894       CASE_ITEM(ITEM_LAYOUT_COLUMN);
5895       CASE_ITEM(ITEM_LAYOUT_COLUMN_FLOW);
5896       CASE_ITEM(ITEM_LAYOUT_ROW_FLOW);
5897       CASE_ITEM(ITEM_LAYOUT_BOX);
5898       CASE_ITEM(ITEM_LAYOUT_ABSOLUTE);
5899       CASE_ITEM(ITEM_LAYOUT_SPLIT);
5900       CASE_ITEM(ITEM_LAYOUT_OVERLAP);
5901       CASE_ITEM(ITEM_LAYOUT_ROOT);
5902       CASE_ITEM(ITEM_LAYOUT_GRID_FLOW);
5903       CASE_ITEM(ITEM_LAYOUT_RADIAL);
5904     }
5905 
5906 #undef CASE_ITEM
5907 
5908     switch (item->type) {
5909       case ITEM_BUTTON:
5910         ui_layout_introspect_button(ds, (uiButtonItem *)item);
5911         break;
5912       default:
5913         BLI_dynstr_append(ds, "'items':");
5914         ui_layout_introspect_items(ds, &((uiLayout *)item)->items);
5915         break;
5916     }
5917 
5918     BLI_dynstr_append(ds, "}");
5919 
5920     if (item != lb->last) {
5921       BLI_dynstr_append(ds, ", ");
5922     }
5923   }
5924   /* Don't use a comma here as it's not needed and
5925    * causes the result to evaluate to a tuple of 1. */
5926   BLI_dynstr_append(ds, "]");
5927 }
5928 
5929 /**
5930  * Evaluate layout items as a Python dictionary.
5931  */
UI_layout_introspect(uiLayout * layout)5932 const char *UI_layout_introspect(uiLayout *layout)
5933 {
5934   DynStr *ds = BLI_dynstr_new();
5935   uiLayout layout_copy = *layout;
5936   layout_copy.item.next = NULL;
5937   layout_copy.item.prev = NULL;
5938   ListBase layout_dummy_list = {&layout_copy, &layout_copy};
5939   ui_layout_introspect_items(ds, &layout_dummy_list);
5940   const char *result = BLI_dynstr_get_cstring(ds);
5941   BLI_dynstr_free(ds);
5942   return result;
5943 }
5944 
5945 /** \} */
5946