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, ®ion->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