2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * The Original Code is Copyright (C) 2008 Blender Foundation.
19 * All rights reserved.
21 * Contributor(s): Blender Foundation
23 * ***** END GPL LICENSE BLOCK *****
26 /** \file blender/editors/interface/interface_regions.c
27 * \ingroup edinterface
37 #include "MEM_guardedalloc.h"
39 #include "DNA_userdef_types.h"
42 #include "BLI_blenlib.h"
43 #include "BLI_utildefines.h"
44 #include "BLI_dynstr.h"
45 #include "BLI_ghash.h"
47 #include "BKE_context.h"
48 #include "BKE_screen.h"
49 #include "BKE_idcode.h"
50 #include "BKE_global.h"
55 #include "wm_subwindow.h"
56 #include "wm_window.h"
58 #include "RNA_access.h"
62 #include "UI_interface.h"
63 #include "UI_interface_icons.h"
64 #include "UI_view2d.h"
67 #include "BLF_translation.h"
69 #include "ED_screen.h"
71 #include "IMB_colormanagement.h"
73 #include "interface_intern.h"
76 #define MENU_SHADOW_SIDE 8
77 #define MENU_SHADOW_BOTTOM 10
80 /*********************** Menu Data Parsing ********************* */
82 typedef struct MenuEntry {
89 typedef struct MenuData {
95 int nitems, itemssize;
98 static MenuData *menudata_new(const char *instr)
100 MenuData *md = MEM_mallocN(sizeof(*md), "MenuData");
106 md->nitems = md->itemssize = 0;
111 static void menudata_set_title(MenuData *md, const char *title, int titleicon)
116 md->titleicon = titleicon;
119 static void menudata_add_item(MenuData *md, const char *str, int retval, int icon, int sepr)
121 if (md->nitems == md->itemssize) {
122 int nsize = md->itemssize ? (md->itemssize << 1) : 1;
123 MenuEntry *oitems = md->items;
125 md->items = MEM_mallocN(nsize * sizeof(*md->items), "md->items");
127 memcpy(md->items, oitems, md->nitems * sizeof(*md->items));
131 md->itemssize = nsize;
134 md->items[md->nitems].str = str;
135 md->items[md->nitems].retval = retval;
136 md->items[md->nitems].icon = icon;
137 md->items[md->nitems].sepr = sepr;
141 static void menudata_free(MenuData *md)
143 MEM_freeN((void *)md->instr);
145 MEM_freeN(md->items);
151 * Parse menu description strings, string is of the
152 * form "[sss%t|]{(sss[%xNN]|), (%l|), (sss%l|)}", ssss%t indicates the
153 * menu title, sss or sss%xNN indicates an option,
154 * if %xNN is given then NN is the return value if
155 * that option is selected otherwise the return value
156 * is the index of the option (starting with 1). %l
157 * indicates a separator, sss%l indicates a label and
160 * \param str String to be parsed.
161 * \retval new menudata structure, free with menudata_free()
163 static MenuData *decompose_menu_string(const char *str)
165 char *instr = BLI_strdup(str);
166 MenuData *md = menudata_new(instr);
167 const char *nitem = NULL;
169 int nicon = 0, nretval = 1, nitem_is_title = 0, nitem_is_sepr = 0;
176 nretval = atoi(s + 2);
181 else if (s[1] == 't') {
182 nitem_is_title = (s != instr); /* check for empty title */
187 else if (s[1] == 'l') {
189 if (!nitem) nitem = "";
194 else if (s[1] == 'i') {
201 else if (c == '|' || c == '\n' || c == '\0') {
205 if (nitem_is_title) {
206 menudata_set_title(md, nitem, nicon);
209 else if (nitem_is_sepr) {
210 /* prevent separator to get a value */
211 menudata_add_item(md, nitem, -1, nicon, 1);
212 nretval = md->nitems + 1;
216 menudata_add_item(md, nitem, nretval, nicon, 0);
217 nretval = md->nitems + 1;
238 void ui_set_name_menu(uiBut *but, int value)
243 md = decompose_menu_string(but->str);
244 for (i = 0; i < md->nitems; i++) {
245 if (md->items[i].retval == value) {
246 BLI_strncpy(but->drawstr, md->items[i].str, sizeof(but->drawstr));
254 int ui_step_name_menu(uiBut *but, int step)
257 int value = ui_get_but_val(but);
260 md = decompose_menu_string(but->str);
261 for (i = 0; i < md->nitems; i++)
262 if (md->items[i].retval == value)
266 /* skip separators */
267 for (; i < md->nitems - 1; i++) {
268 if (md->items[i + 1].retval != -1) {
269 value = md->items[i + 1].retval;
276 /* skip separators */
278 if (md->items[i - 1].retval != -1) {
279 value = md->items[i - 1].retval;
292 /******************** Creating Temporary regions ******************/
294 static ARegion *ui_add_temporary_region(bScreen *sc)
298 ar = MEM_callocN(sizeof(ARegion), "area region");
299 BLI_addtail(&sc->regionbase, ar);
301 ar->regiontype = RGN_TYPE_TEMPORARY;
302 ar->alignment = RGN_ALIGN_FLOAT;
307 static void ui_remove_temporary_region(bContext *C, bScreen *sc, ARegion *ar)
309 wmWindow *win = CTX_wm_window(C);
311 wm_draw_region_clear(win, ar);
313 ED_region_exit(C, ar);
314 BKE_area_region_free(NULL, ar); /* NULL: no spacetype */
315 BLI_freelinkN(&sc->regionbase, ar);
318 /************************* Creating Tooltips **********************/
326 } uiTooltipLineColor;
327 #define UI_TIP_LC_MAX 5
329 #define MAX_TOOLTIP_LINES 8
330 typedef struct uiTooltipData {
333 char lines[MAX_TOOLTIP_LINES][512];
334 uiTooltipLineColor color_id[MAX_TOOLTIP_LINES];
336 int toth, spaceh, lineh;
339 static void rgb_tint(float col[3],
340 float h, float h_strength,
341 float v, float v_strength)
343 float col_hsv_from[3];
346 rgb_to_hsv_v(col, col_hsv_from);
349 col_hsv_to[1] = h_strength;
350 col_hsv_to[2] = (col_hsv_from[2] * (1.0f - v_strength)) + (v * v_strength);
352 hsv_to_rgb_v(col_hsv_to, col);
355 static void ui_tooltip_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
357 uiTooltipData *data = ar->regiondata;
358 uiWidgetColors *theme = ui_tooltip_get_theme();
359 rcti bbox = data->bbox;
360 float tip_colors[UI_TIP_LC_MAX][3];
362 float *main_color = tip_colors[UI_TIP_LC_MAIN]; /* the color from the theme */
363 float *normal_color = tip_colors[UI_TIP_LC_NORMAL];
364 float *python_color = tip_colors[UI_TIP_LC_PYTHON];
365 float *alert_color = tip_colors[UI_TIP_LC_ALERT];
366 float *submenu_color = tip_colors[UI_TIP_LC_SUBMENU];
368 float background_color[3];
372 /* draw background */
373 ui_draw_tooltip_background(UI_GetStyle(), NULL, &bbox);
375 /* set background_color */
376 rgb_uchar_to_float(background_color, (const unsigned char *)theme->inner);
378 /* calculate normal_color */
379 rgb_uchar_to_float(main_color, (const unsigned char *)theme->text);
380 copy_v3_v3(normal_color, main_color);
381 copy_v3_v3(python_color, main_color);
382 copy_v3_v3(alert_color, main_color);
383 copy_v3_v3(submenu_color, main_color);
385 /* find the brightness difference between background and text colors */
387 tone_bg = rgb_to_grayscale(background_color);
388 /* tone_fg = rgb_to_grayscale(main_color); */
390 rgb_tint(normal_color, 0.0f, 0.0f, tone_bg, 0.3f); /* a shade darker (to bg) */
391 rgb_tint(python_color, 0.666f, 0.25f, tone_bg, 0.3f); /* blue */
392 rgb_tint(alert_color, 0.0f, 0.8f, tone_bg, 0.1f); /* bright red */
393 rgb_tint(submenu_color, 0.0f, 0.0f, tone_bg, 0.3f); /* a shade darker (to bg) */
396 uiStyleFontSet(&data->fstyle);
398 bbox.ymax = bbox.ymax - 0.5f * (BLI_rcti_size_y(&bbox) - data->toth);
399 bbox.ymin = bbox.ymax - data->lineh;
401 for (i = 0; i < data->totline; i++) {
402 glColor3fv(tip_colors[data->color_id[i]]);
403 uiStyleFontDraw(&data->fstyle, &bbox, data->lines[i]);
404 bbox.ymin -= data->lineh + data->spaceh;
405 bbox.ymax -= data->lineh + data->spaceh;
409 static void ui_tooltip_region_free_cb(ARegion *ar)
413 data = ar->regiondata;
415 ar->regiondata = NULL;
418 ARegion *ui_tooltip_create(bContext *C, ARegion *butregion, uiBut *but)
420 wmWindow *win = CTX_wm_window(C);
421 uiStyle *style = UI_GetStyle();
422 static ARegionType type;
425 /* IDProperty *prop;*/
427 float fonth, fontw, aspect = but->block->aspect;
428 int winx /*, winy */, ofsx, ofsy, w, h, a;
432 uiStringInfo but_tip = {BUT_GET_TIP, NULL};
433 uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL};
434 uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL};
435 uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, NULL};
436 uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL};
437 uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL};
439 if (but->flag & UI_BUT_NO_TOOLTIP)
442 /* create tooltip data */
443 data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
445 uiButGetStrInfo(C, but, &but_tip, &enum_label, &enum_tip, &op_keymap, &rna_struct, &rna_prop, NULL);
447 /* special case, enum rna buttons only have enum item description,
448 * use general enum description too before the specific one */
451 if (but_tip.strinfo) {
452 BLI_strncpy(data->lines[data->totline], but_tip.strinfo, sizeof(data->lines[0]));
453 data->color_id[data->totline] = UI_TIP_LC_MAIN;
456 /* Enum item label & tip */
457 if (enum_label.strinfo && enum_tip.strinfo) {
458 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
459 "%s: %s", enum_label.strinfo, enum_tip.strinfo);
460 data->color_id[data->totline] = UI_TIP_LC_SUBMENU;
465 if (op_keymap.strinfo) {
466 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Shortcut: %s"), op_keymap.strinfo);
467 data->color_id[data->totline] = UI_TIP_LC_NORMAL;
471 if (ELEM3(but->type, TEX, IDPOIN, SEARCH_MENU)) {
473 ui_get_but_string(but, buf, sizeof(buf));
475 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Value: %s"), buf);
476 data->color_id[data->totline] = UI_TIP_LC_NORMAL;
482 int unit_type = uiButGetUnitType(but);
484 if (unit_type == PROP_UNIT_ROTATION) {
485 if (RNA_property_type(but->rnaprop) == PROP_FLOAT) {
486 float value = RNA_property_array_check(but->rnaprop) ?
487 RNA_property_float_get_index(&but->rnapoin, but->rnaprop, but->rnaindex) :
488 RNA_property_float_get(&but->rnapoin, but->rnaprop);
489 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Radians: %f"), value);
490 data->color_id[data->totline] = UI_TIP_LC_NORMAL;
495 if (but->flag & UI_BUT_DRIVEN) {
496 if (ui_but_anim_expression_get(but, buf, sizeof(buf))) {
498 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Expression: %s"), buf);
499 data->color_id[data->totline] = UI_TIP_LC_NORMAL;
504 if (but->rnapoin.id.data) {
505 ID *id = but->rnapoin.id.data;
506 if (id->lib && id->lib->name) {
507 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Library: %s"), id->lib->name);
508 data->color_id[data->totline] = UI_TIP_LC_NORMAL;
513 else if (but->optype) {
516 opptr = uiButGetOperatorPtrRNA(but); /* allocated when needed, the button owns it */
518 /* so the context is passed to itemf functions (some py itemf functions use it) */
519 WM_operator_properties_sanitize(opptr, FALSE);
521 str = WM_operator_pystring(C, but->optype, opptr, 0);
524 if ((U.flag & USER_TOOLTIPS_PYTHON) == 0) {
525 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Python: %s"), str);
526 data->color_id[data->totline] = UI_TIP_LC_PYTHON;
532 /* second check if we are disabled - why */
533 if (but->flag & UI_BUT_DISABLED) {
534 const char *poll_msg;
535 CTX_wm_operator_poll_msg_set(C, NULL);
536 WM_operator_poll_context(C, but->optype, but->opcontext);
537 poll_msg = CTX_wm_operator_poll_msg_get(C);
539 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Disabled: %s"), poll_msg);
540 data->color_id[data->totline] = UI_TIP_LC_ALERT; /* alert */
545 if ((U.flag & USER_TOOLTIPS_PYTHON) == 0 && !but->optype && rna_struct.strinfo) {
546 if (rna_prop.strinfo) {
547 /* Struct and prop */
548 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
549 TIP_("Python: %s.%s"),
550 rna_struct.strinfo, rna_prop.strinfo);
553 /* Only struct (e.g. menus) */
554 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
555 TIP_("Python: %s"), rna_struct.strinfo);
557 data->color_id[data->totline] = UI_TIP_LC_PYTHON;
560 if (but->rnapoin.id.data) {
561 /* this could get its own 'BUT_GET_...' type */
562 PointerRNA *ptr = &but->rnapoin;
563 PropertyRNA *prop = but->rnaprop;
564 ID *id = ptr->id.data;
567 char *data_path = NULL;
570 id_path = RNA_path_from_ID_python(id);
572 if (ptr->id.data && ptr->data && prop) {
573 data_path = RNA_path_from_ID_to_property(ptr, prop);
577 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
578 "%s.%s", /* no need to translate */
580 MEM_freeN(data_path);
583 /* can't find the path. be explicit in our ignorance "..." */
584 BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
585 "%s ... %s", /* no need to translate */
586 id_path, rna_prop.strinfo ? rna_prop.strinfo : RNA_property_identifier(prop));
590 data->color_id[data->totline] = UI_TIP_LC_PYTHON;
595 /* Free strinfo's... */
597 MEM_freeN(but_tip.strinfo);
598 if (enum_label.strinfo)
599 MEM_freeN(enum_label.strinfo);
600 if (enum_tip.strinfo)
601 MEM_freeN(enum_tip.strinfo);
602 if (op_keymap.strinfo)
603 MEM_freeN(op_keymap.strinfo);
604 if (rna_struct.strinfo)
605 MEM_freeN(rna_struct.strinfo);
606 if (rna_prop.strinfo)
607 MEM_freeN(rna_prop.strinfo);
609 BLI_assert(data->totline < MAX_TOOLTIP_LINES);
611 if (data->totline == 0) {
616 /* create area region */
617 ar = ui_add_temporary_region(CTX_wm_screen(C));
619 memset(&type, 0, sizeof(ARegionType));
620 type.draw = ui_tooltip_region_draw_cb;
621 type.free = ui_tooltip_region_free_cb;
624 /* set font, get bb */
625 data->fstyle = style->widget; /* copy struct */
626 data->fstyle.align = UI_STYLE_TEXT_CENTER;
627 ui_fontscale(&data->fstyle.points, aspect);
629 uiStyleFontSet(&data->fstyle);
631 /* these defines tweaked depending on font */
632 #define TIP_MARGIN_Y (2.0f / aspect)
633 #define TIP_BORDER_X (16.0f / aspect)
634 #define TIP_BORDER_Y (6.0f / aspect)
636 h = BLF_height_max(data->fstyle.uifont_id);
638 for (a = 0, fontw = 0, fonth = 0; a < data->totline; a++) {
639 w = BLF_width(data->fstyle.uifont_id, data->lines[a]);
640 fontw = max_ff(fontw, (float)w);
641 fonth += (a == 0) ? h : h + TIP_MARGIN_Y;
646 ar->regiondata = data;
650 data->spaceh = TIP_MARGIN_Y;
652 /* compute position */
653 ofsx = 0; //(but->block->panel) ? but->block->panel->ofsx : 0;
654 ofsy = 0; //(but->block->panel) ? but->block->panel->ofsy : 0;
656 rect_fl.xmin = (but->rect.xmin + but->rect.xmax) * 0.5f + ofsx - (TIP_BORDER_X );
657 rect_fl.xmax = rect_fl.xmin + fontw + (TIP_BORDER_X );
658 rect_fl.ymax = but->rect.ymin + ofsy - (TIP_BORDER_Y );
659 rect_fl.ymin = rect_fl.ymax - fonth - (TIP_BORDER_Y );
665 /* since the text has beens caled already, the size of tooltips is defined now */
666 /* here we try to figure out the right location */
668 float ofsx = rect_fl.xmin, ofsy = rect_fl.ymax;
669 ui_block_to_window_fl(butregion, but->block, &ofsx, &ofsy);
670 BLI_rctf_translate(&rect_fl, ofsx - rect_fl.xmin, ofsy - rect_fl.ymax);
672 BLI_rcti_rctf_copy(&rect_i, &rect_fl);
674 /* clip with window boundaries */
675 winx = WM_window_pixels_x(win);
677 if (rect_i.xmax > winx) {
679 if (rect_i.xmax > winx + rect_i.xmin) {
684 rect_i.xmin -= rect_i.xmax - winx;
688 /* ensure at least 5 px above screen bounds
689 * 25 is just a guess to be above the menu item */
690 if (rect_i.ymin < 5) {
691 rect_i.ymax += (-rect_i.ymin) + 30;
695 /* widget rect, in region coords */
696 data->bbox.xmin = MENU_SHADOW_SIDE;
697 data->bbox.xmax = BLI_rcti_size_x(&rect_i) + MENU_SHADOW_SIDE;
698 data->bbox.ymin = MENU_SHADOW_BOTTOM;
699 data->bbox.ymax = BLI_rcti_size_y(&rect_i) + MENU_SHADOW_BOTTOM;
701 /* region bigger for shadow */
702 ar->winrct.xmin = rect_i.xmin - MENU_SHADOW_SIDE;
703 ar->winrct.xmax = rect_i.xmax + MENU_SHADOW_SIDE;
704 ar->winrct.ymin = rect_i.ymin - MENU_SHADOW_BOTTOM;
705 ar->winrct.ymax = rect_i.ymax + MENU_TOP;
708 ED_region_init(C, ar);
710 /* notify change and redraw */
711 ED_region_tag_redraw(ar);
716 void ui_tooltip_free(bContext *C, ARegion *ar)
718 ui_remove_temporary_region(C, CTX_wm_screen(C), ar);
722 /************************* Creating Search Box **********************/
724 struct uiSearchItems {
725 int maxitem, totitem, maxstrlen;
727 int offset, offset_i; /* offset for inserting in array */
728 int more; /* flag indicating there are more items */
734 AutoComplete *autocpl;
738 typedef struct uiSearchboxData {
742 int active; /* index in items array */
743 int noback; /* when menu opened with enough space for this */
744 int preview; /* draw thumbnail previews, rather than list */
745 int prv_rows, prv_cols;
748 #define SEARCH_ITEMS 10
750 /* exported for use by search callbacks */
751 /* returns zero if nothing to add */
752 int uiSearchItemAdd(uiSearchItems *items, const char *name, void *poin, int iconid)
754 /* hijack for autocomplete */
755 if (items->autocpl) {
756 autocomplete_do_name(items->autocpl, name);
760 /* hijack for finding active item */
762 if (poin == items->active)
763 items->offset_i = items->totitem;
768 if (items->totitem >= items->maxitem) {
773 /* skip first items in list */
774 if (items->offset_i > 0) {
780 BLI_strncpy(items->names[items->totitem], name, items->maxstrlen);
782 items->pointers[items->totitem] = poin;
784 items->icons[items->totitem] = iconid;
791 int uiSearchBoxHeight(void)
793 return SEARCH_ITEMS * UI_UNIT_Y + 2 * MENU_TOP;
796 int uiSearchBoxWidth(void)
798 /* was hardcoded at 150 */
799 return 9 * UI_UNIT_X;
802 /* ar is the search box itself */
803 static void ui_searchbox_select(bContext *C, ARegion *ar, uiBut *but, int step)
805 uiSearchboxData *data = ar->regiondata;
808 data->active += step;
810 if (data->items.totitem == 0)
812 else if (data->active > data->items.totitem) {
813 if (data->items.more) {
814 data->items.offset++;
815 data->active = data->items.totitem;
816 ui_searchbox_update(C, ar, but, 0);
819 data->active = data->items.totitem;
821 else if (data->active < 1) {
822 if (data->items.offset) {
823 data->items.offset--;
825 ui_searchbox_update(C, ar, but, 0);
827 else if (data->active < 0)
831 ED_region_tag_redraw(ar);
834 static void ui_searchbox_butrect(rcti *rect, uiSearchboxData *data, int itemnr)
836 /* thumbnail preview */
838 int butw = BLI_rcti_size_x(&data->bbox) / data->prv_cols;
839 int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_TOP) / data->prv_rows;
844 col = itemnr % data->prv_cols;
845 row = itemnr / data->prv_cols;
847 rect->xmin += col * butw;
848 rect->xmax = rect->xmin + butw;
850 rect->ymax = data->bbox.ymax - MENU_TOP - (row * buth);
851 rect->ymin = rect->ymax - buth;
855 int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_TOP) / SEARCH_ITEMS;
858 rect->xmin = data->bbox.xmin + 3.0f;
859 rect->xmax = data->bbox.xmax - 3.0f;
861 rect->ymax = data->bbox.ymax - MENU_TOP - itemnr * buth;
862 rect->ymin = rect->ymax - buth;
867 /* x and y in screencoords */
868 int ui_searchbox_inside(ARegion *ar, int x, int y)
870 uiSearchboxData *data = ar->regiondata;
872 return(BLI_rcti_isect_pt(&data->bbox, x - ar->winrct.xmin, y - ar->winrct.ymin));
875 /* string validated to be of correct length (but->hardmax) */
876 void ui_searchbox_apply(uiBut *but, ARegion *ar)
878 uiSearchboxData *data = ar->regiondata;
880 but->func_arg2 = NULL;
883 char *name = data->items.names[data->active - 1];
884 char *cpoin = strchr(name, '|');
886 if (cpoin) cpoin[0] = 0;
887 BLI_strncpy(but->editstr, name, data->items.maxstrlen);
888 if (cpoin) cpoin[0] = '|';
890 but->func_arg2 = data->items.pointers[data->active - 1];
894 void ui_searchbox_event(bContext *C, ARegion *ar, uiBut *but, wmEvent *event)
896 uiSearchboxData *data = ar->regiondata;
898 switch (event->type) {
901 ui_searchbox_select(C, ar, but, -1);
905 ui_searchbox_select(C, ar, but, 1);
908 if (BLI_rcti_isect_pt(&ar->winrct, event->x, event->y)) {
912 for (a = 0; a < data->items.totitem; a++) {
913 ui_searchbox_butrect(&rect, data, a);
914 if (BLI_rcti_isect_pt(&rect, event->x - ar->winrct.xmin, event->y - ar->winrct.ymin)) {
915 if (data->active != a + 1) {
916 data->active = a + 1;
917 ui_searchbox_select(C, ar, but, 0);
927 /* ar is the search box itself */
928 void ui_searchbox_update(bContext *C, ARegion *ar, uiBut *but, int reset)
930 uiSearchboxData *data = ar->regiondata;
933 data->items.totitem = 0;
934 data->items.more = 0;
936 data->items.offset_i = data->items.offset;
939 data->items.offset_i = data->items.offset = 0;
943 if (but->search_func && but->func_arg2) {
944 data->items.active = but->func_arg2;
945 but->search_func(C, but->search_arg, but->editstr, &data->items);
946 data->items.active = NULL;
948 /* found active item, calculate real offset by centering it */
949 if (data->items.totitem) {
950 /* first case, begin of list */
951 if (data->items.offset_i < data->items.maxitem) {
952 data->active = data->items.offset_i + 1;
953 data->items.offset_i = 0;
956 /* second case, end of list */
957 if (data->items.totitem - data->items.offset_i <= data->items.maxitem) {
958 data->active = 1 + data->items.offset_i - data->items.totitem + data->items.maxitem;
959 data->items.offset_i = data->items.totitem - data->items.maxitem;
962 /* center active item */
963 data->items.offset_i -= data->items.maxitem / 2;
964 data->active = 1 + data->items.maxitem / 2;
968 data->items.offset = data->items.offset_i;
969 data->items.totitem = 0;
974 if (but->search_func)
975 but->search_func(C, but->search_arg, but->editstr, &data->items);
977 /* handle case where editstr is equal to one of items */
978 if (reset && data->active == 0) {
981 for (a = 0; a < data->items.totitem; a++) {
982 char *cpoin = strchr(data->items.names[a], '|');
984 if (cpoin) cpoin[0] = 0;
985 if (0 == strcmp(but->editstr, data->items.names[a]))
986 data->active = a + 1;
987 if (cpoin) cpoin[0] = '|';
989 if (data->items.totitem == 1 && but->editstr[0])
993 /* validate selected item */
994 ui_searchbox_select(C, ar, but, 0);
996 ED_region_tag_redraw(ar);
999 void ui_searchbox_autocomplete(bContext *C, ARegion *ar, uiBut *but, char *str)
1001 uiSearchboxData *data = ar->regiondata;
1004 data->items.autocpl = autocomplete_begin(str, ui_get_but_string_max_length(but));
1006 but->search_func(C, but->search_arg, but->editstr, &data->items);
1008 autocomplete_end(data->items.autocpl, str);
1009 data->items.autocpl = NULL;
1013 static void ui_searchbox_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
1015 uiSearchboxData *data = ar->regiondata;
1018 wmOrtho2(-0.01f, ar->winx - 0.01f, -0.01f, ar->winy - 0.01f);
1021 ui_draw_search_back(NULL, NULL, &data->bbox); /* style not used yet */
1024 if (data->items.totitem) {
1028 if (data->preview) {
1030 for (a = 0; a < data->items.totitem; a++) {
1031 ui_searchbox_butrect(&rect, data, a);
1035 ui_draw_preview_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a], (a + 1) == data->active ? UI_ACTIVE : 0);
1037 ui_draw_menu_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a], (a + 1) == data->active ? UI_ACTIVE : 0);
1041 if (data->items.more) {
1042 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
1044 UI_icon_draw(rect.xmax - 18, rect.ymin - 7, ICON_TRIA_DOWN);
1045 glDisable(GL_BLEND);
1047 if (data->items.offset) {
1048 ui_searchbox_butrect(&rect, data, 0);
1050 UI_icon_draw(rect.xmin, rect.ymax - 9, ICON_TRIA_UP);
1051 glDisable(GL_BLEND);
1057 for (a = 0; a < data->items.totitem; a++) {
1058 ui_searchbox_butrect(&rect, data, a);
1061 ui_draw_menu_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a], (a + 1) == data->active ? UI_ACTIVE : 0);
1065 if (data->items.more) {
1066 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
1068 UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
1069 glDisable(GL_BLEND);
1071 if (data->items.offset) {
1072 ui_searchbox_butrect(&rect, data, 0);
1074 UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP);
1075 glDisable(GL_BLEND);
1081 static void ui_searchbox_region_free_cb(ARegion *ar)
1083 uiSearchboxData *data = ar->regiondata;
1086 /* free search data */
1087 for (a = 0; a < data->items.maxitem; a++) {
1088 MEM_freeN(data->items.names[a]);
1090 MEM_freeN(data->items.names);
1091 MEM_freeN(data->items.pointers);
1092 MEM_freeN(data->items.icons);
1095 ar->regiondata = NULL;
1098 ARegion *ui_searchbox_create(bContext *C, ARegion *butregion, uiBut *but)
1100 wmWindow *win = CTX_wm_window(C);
1101 uiStyle *style = UI_GetStyle();
1102 static ARegionType type;
1104 uiSearchboxData *data;
1105 float aspect = but->block->aspect;
1108 int winx /*, winy */, ofsx, ofsy;
1111 /* create area region */
1112 ar = ui_add_temporary_region(CTX_wm_screen(C));
1114 memset(&type, 0, sizeof(ARegionType));
1115 type.draw = ui_searchbox_region_draw_cb;
1116 type.free = ui_searchbox_region_free_cb;
1119 /* create searchbox data */
1120 data = MEM_callocN(sizeof(uiSearchboxData), "uiSearchboxData");
1122 /* set font, get bb */
1123 data->fstyle = style->widget; /* copy struct */
1124 data->fstyle.align = UI_STYLE_TEXT_CENTER;
1125 ui_fontscale(&data->fstyle.points, aspect);
1126 uiStyleFontSet(&data->fstyle);
1128 ar->regiondata = data;
1130 /* special case, hardcoded feature, not draw backdrop when called from menus,
1131 * assume for design that popup already added it */
1132 if (but->block->flag & UI_BLOCK_SEARCH_MENU)
1135 if (but->a1 > 0 && but->a2 > 0) {
1137 data->prv_rows = but->a1;
1138 data->prv_cols = but->a2;
1141 /* compute position */
1142 if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
1143 /* this case is search menu inside other menu */
1144 /* we copy region size */
1146 ar->winrct = butregion->winrct;
1148 /* widget rect, in region coords */
1149 data->bbox.xmin = MENU_SHADOW_SIDE;
1150 data->bbox.xmax = BLI_rcti_size_x(&ar->winrct) - MENU_SHADOW_SIDE;
1151 data->bbox.ymin = MENU_SHADOW_BOTTOM;
1152 data->bbox.ymax = BLI_rcti_size_y(&ar->winrct) - MENU_SHADOW_BOTTOM;
1154 /* check if button is lower half */
1155 if (but->rect.ymax < BLI_rctf_cent_y(&but->block->rect)) {
1156 data->bbox.ymin += BLI_rctf_size_y(&but->rect);
1159 data->bbox.ymax -= BLI_rctf_size_y(&but->rect);
1163 const int searchbox_width = uiSearchBoxWidth();
1164 rect_fl.xmin = but->rect.xmin - 5; /* align text with button */
1165 rect_fl.xmax = but->rect.xmax + 5; /* symmetrical */
1166 rect_fl.ymax = but->rect.ymin;
1167 rect_fl.ymin = rect_fl.ymax - uiSearchBoxHeight();
1169 ofsx = (but->block->panel) ? but->block->panel->ofsx : 0;
1170 ofsy = (but->block->panel) ? but->block->panel->ofsy : 0;
1172 BLI_rctf_translate(&rect_fl, ofsx, ofsy);
1175 if (BLI_rctf_size_x(&rect_fl) < searchbox_width) {
1176 rect_fl.xmax = rect_fl.xmin + searchbox_width;
1179 /* copy to int, gets projected if possible too */
1180 BLI_rcti_rctf_copy(&rect_i, &rect_fl);
1182 if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
1183 UI_view2d_to_region_no_clip(&butregion->v2d, rect_fl.xmin, rect_fl.ymin, &rect_i.xmin, &rect_i.ymin);
1184 UI_view2d_to_region_no_clip(&butregion->v2d, rect_fl.xmax, rect_fl.ymax, &rect_i.xmax, &rect_i.ymax);
1187 BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin);
1189 winx = WM_window_pixels_x(win);
1190 // winy = WM_window_pixels_y(win); /* UNUSED */
1191 //wm_window_get_size(win, &winx, &winy);
1193 if (rect_i.xmax > winx) {
1195 if (rect_i.xmax > winx + rect_i.xmin) {
1200 rect_i.xmin -= rect_i.xmax - winx;
1205 if (rect_i.ymin < 0) {
1207 UI_view2d_to_region_no_clip(&butregion->v2d, 0, but->rect.ymax + ofsy, NULL, &newy1);
1208 newy1 += butregion->winrct.ymin;
1210 rect_i.ymax = BLI_rcti_size_y(&rect_i) + newy1;
1211 rect_i.ymin = newy1;
1214 /* widget rect, in region coords */
1215 data->bbox.xmin = MENU_SHADOW_SIDE;
1216 data->bbox.xmax = BLI_rcti_size_x(&rect_i) + MENU_SHADOW_SIDE;
1217 data->bbox.ymin = MENU_SHADOW_BOTTOM;
1218 data->bbox.ymax = BLI_rcti_size_y(&rect_i) + MENU_SHADOW_BOTTOM;
1220 /* region bigger for shadow */
1221 ar->winrct.xmin = rect_i.xmin - MENU_SHADOW_SIDE;
1222 ar->winrct.xmax = rect_i.xmax + MENU_SHADOW_SIDE;
1223 ar->winrct.ymin = rect_i.ymin - MENU_SHADOW_BOTTOM;
1224 ar->winrct.ymax = rect_i.ymax;
1227 /* adds subwindow */
1228 ED_region_init(C, ar);
1230 /* notify change and redraw */
1231 ED_region_tag_redraw(ar);
1233 /* prepare search data */
1234 if (data->preview) {
1235 data->items.maxitem = data->prv_rows * data->prv_cols;
1238 data->items.maxitem = SEARCH_ITEMS;
1240 data->items.maxstrlen = but->hardmax;
1241 data->items.totitem = 0;
1242 data->items.names = MEM_callocN(data->items.maxitem * sizeof(void *), "search names");
1243 data->items.pointers = MEM_callocN(data->items.maxitem * sizeof(void *), "search pointers");
1244 data->items.icons = MEM_callocN(data->items.maxitem * sizeof(int), "search icons");
1245 for (i = 0; i < data->items.maxitem; i++)
1246 data->items.names[i] = MEM_callocN(but->hardmax + 1, "search pointers");
1251 void ui_searchbox_free(bContext *C, ARegion *ar)
1253 ui_remove_temporary_region(C, CTX_wm_screen(C), ar);
1256 /* sets red alert if button holds a string it can't find */
1257 /* XXX weak: search_func adds all partial matches... */
1258 void ui_but_search_test(uiBut *but)
1260 uiSearchItems *items;
1263 /* possibly very large lists (such as ID datablocks) only
1264 * only validate string RNA buts (not pointers) */
1265 if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) {
1269 items = MEM_callocN(sizeof(uiSearchItems), "search items");
1271 /* setup search struct */
1272 items->maxitem = 10;
1273 items->maxstrlen = 256;
1274 items->names = MEM_callocN(items->maxitem * sizeof(void *), "search names");
1275 for (x1 = 0; x1 < items->maxitem; x1++)
1276 items->names[x1] = MEM_callocN(but->hardmax + 1, "search names");
1278 but->search_func(but->block->evil_C, but->search_arg, but->drawstr, items);
1280 /* only redalert when we are sure of it, this can miss cases when >10 matches */
1281 if (items->totitem == 0)
1282 uiButSetFlag(but, UI_BUT_REDALERT);
1283 else if (items->more == 0) {
1284 for (x1 = 0; x1 < items->totitem; x1++)
1285 if (strcmp(but->drawstr, items->names[x1]) == 0)
1287 if (x1 == items->totitem)
1288 uiButSetFlag(but, UI_BUT_REDALERT);
1291 for (x1 = 0; x1 < items->maxitem; x1++) {
1292 MEM_freeN(items->names[x1]);
1294 MEM_freeN(items->names);
1299 /************************* Creating Menu Blocks **********************/
1301 /* position block relative to but, result is in window space */
1302 static void ui_block_position(wmWindow *window, ARegion *butregion, uiBut *but, uiBlock *block)
1305 uiSafetyRct *saferct;
1307 /*float aspect;*/ /*UNUSED*/
1308 int xsize, ysize, xof = 0, yof = 0, center;
1309 short dir1 = 0, dir2 = 0;
1311 /* transform to window coordinates, using the source button region/block */
1314 ui_block_to_window_fl(butregion, but->block, &butrct.xmin, &butrct.ymin);
1315 ui_block_to_window_fl(butregion, but->block, &butrct.xmax, &butrct.ymax);
1317 /* widget_roundbox_set has this correction too, keep in sync */
1318 if (but->type != PULLDOWN) {
1319 if (but->flag & UI_BUT_ALIGN_TOP)
1320 butrct.ymax += U.pixelsize;
1321 if (but->flag & UI_BUT_ALIGN_LEFT)
1322 butrct.xmin -= U.pixelsize;
1325 /* calc block rect */
1326 if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) {
1327 if (block->buttons.first) {
1328 BLI_rctf_init_minmax(&block->rect);
1330 for (bt = block->buttons.first; bt; bt = bt->next) {
1331 BLI_rctf_union(&block->rect, &bt->rect);
1335 /* we're nice and allow empty blocks too */
1336 block->rect.xmin = block->rect.ymin = 0;
1337 block->rect.xmax = block->rect.ymax = 20;
1341 /* aspect = (float)(BLI_rcti_size_x(&block->rect) + 4);*/ /*UNUSED*/
1342 ui_block_to_window_fl(butregion, but->block, &block->rect.xmin, &block->rect.ymin);
1343 ui_block_to_window_fl(butregion, but->block, &block->rect.xmax, &block->rect.ymax);
1345 //block->rect.xmin -= 2.0; block->rect.ymin -= 2.0;
1346 //block->rect.xmax += 2.0; block->rect.ymax += 2.0;
1348 xsize = BLI_rctf_size_x(&block->rect) + 0.2f * UI_UNIT_X; /* 4 for shadow */
1349 ysize = BLI_rctf_size_y(&block->rect) + 0.2f * UI_UNIT_Y;
1350 /* aspect /= (float)xsize;*/ /*UNUSED*/
1353 int left = 0, right = 0, top = 0, down = 0;
1357 winx = WM_window_pixels_x(window);
1358 winy = WM_window_pixels_y(window);
1359 // wm_window_get_size(window, &winx, &winy);
1361 if (block->direction & UI_CENTER) center = ysize / 2;
1364 /* check if there's space at all */
1365 if (butrct.xmin - xsize > 0.0f) left = 1;
1366 if (butrct.xmax + xsize < winx) right = 1;
1367 if (butrct.ymin - ysize + center > 0.0f) down = 1;
1368 if (butrct.ymax + ysize - center < winy) top = 1;
1370 if (top == 0 && down == 0) {
1371 if (butrct.ymin - ysize < winy - butrct.ymax - ysize)
1377 dir1 = block->direction & UI_DIRECTION;
1379 /* secundary directions */
1380 if (dir1 & (UI_TOP | UI_DOWN)) {
1381 if (dir1 & UI_LEFT) dir2 = UI_LEFT;
1382 else if (dir1 & UI_RIGHT) dir2 = UI_RIGHT;
1383 dir1 &= (UI_TOP | UI_DOWN);
1386 if ((dir2 == 0) && (dir1 == UI_LEFT || dir1 == UI_RIGHT)) dir2 = UI_DOWN;
1387 if ((dir2 == 0) && (dir1 == UI_TOP || dir1 == UI_DOWN)) dir2 = UI_LEFT;
1389 /* no space at all? don't change */
1390 if (left || right) {
1391 if (dir1 == UI_LEFT && left == 0) dir1 = UI_RIGHT;
1392 if (dir1 == UI_RIGHT && right == 0) dir1 = UI_LEFT;
1393 /* this is aligning, not append! */
1394 if (dir2 == UI_LEFT && right == 0) dir2 = UI_RIGHT;
1395 if (dir2 == UI_RIGHT && left == 0) dir2 = UI_LEFT;
1398 if (dir1 == UI_TOP && top == 0) dir1 = UI_DOWN;
1399 if (dir1 == UI_DOWN && down == 0) dir1 = UI_TOP;
1400 if (dir2 == UI_TOP && top == 0) dir2 = UI_DOWN;
1401 if (dir2 == UI_DOWN && down == 0) dir2 = UI_TOP;
1404 if (dir1 == UI_LEFT) {
1405 xof = butrct.xmin - block->rect.xmax;
1406 if (dir2 == UI_TOP) yof = butrct.ymin - block->rect.ymin - center;
1407 else yof = butrct.ymax - block->rect.ymax + center;
1409 else if (dir1 == UI_RIGHT) {
1410 xof = butrct.xmax - block->rect.xmin;
1411 if (dir2 == UI_TOP) yof = butrct.ymin - block->rect.ymin - center;
1412 else yof = butrct.ymax - block->rect.ymax + center;
1414 else if (dir1 == UI_TOP) {
1415 yof = butrct.ymax - block->rect.ymin;
1416 if (dir2 == UI_RIGHT) xof = butrct.xmax - block->rect.xmax;
1417 else xof = butrct.xmin - block->rect.xmin;
1418 /* changed direction? */
1419 if ((dir1 & block->direction) == 0) {
1420 if (block->direction & UI_SHIFT_FLIPPED)
1421 xof += dir2 == UI_LEFT ? 25 : -25;
1422 uiBlockFlipOrder(block);
1425 else if (dir1 == UI_DOWN) {
1426 yof = butrct.ymin - block->rect.ymax;
1427 if (dir2 == UI_RIGHT) xof = butrct.xmax - block->rect.xmax;
1428 else xof = butrct.xmin - block->rect.xmin;
1429 /* changed direction? */
1430 if ((dir1 & block->direction) == 0) {
1431 if (block->direction & UI_SHIFT_FLIPPED)
1432 xof += dir2 == UI_LEFT ? 25 : -25;
1433 uiBlockFlipOrder(block);
1437 /* and now we handle the exception; no space below or to top */
1438 if (top == 0 && down == 0) {
1439 if (dir1 == UI_LEFT || dir1 == UI_RIGHT) {
1440 /* align with bottom of screen */
1441 // yof = ysize; (not with menu scrolls)
1445 /* or no space left or right */
1446 if (left == 0 && right == 0) {
1447 if (dir1 == UI_TOP || dir1 == UI_DOWN) {
1448 /* align with left size of screen */
1449 xof = -block->rect.xmin + 5;
1454 /* clamp to window bounds, could be made into an option if its ever annoying */
1455 if ( (offscreen = (block->rect.ymin + yof)) < 0) yof -= offscreen; /* bottom */
1456 else if ((offscreen = (block->rect.ymax + yof) - winy) > 0) yof -= offscreen; /* top */
1457 if ( (offscreen = (block->rect.xmin + xof)) < 0) xof -= offscreen; /* left */
1458 else if ((offscreen = (block->rect.xmax + xof) - winx) > 0) xof -= offscreen; /* right */
1462 /* apply offset, buttons in window coords */
1464 for (bt = block->buttons.first; bt; bt = bt->next) {
1465 ui_block_to_window_fl(butregion, but->block, &bt->rect.xmin, &bt->rect.ymin);
1466 ui_block_to_window_fl(butregion, but->block, &bt->rect.xmax, &bt->rect.ymax);
1468 BLI_rctf_translate(&bt->rect, xof, yof);
1471 /* ui_check_but recalculates drawstring size in pixels */
1475 BLI_rctf_translate(&block->rect, xof, yof);
1477 /* safety calculus */
1479 const float midx = BLI_rctf_cent_x(&butrct);
1480 const float midy = BLI_rctf_cent_y(&butrct);
1482 /* when you are outside parent button, safety there should be smaller */
1484 /* parent button to left */
1485 if (midx < block->rect.xmin) block->safety.xmin = block->rect.xmin - 3;
1486 else block->safety.xmin = block->rect.xmin - 40;
1487 /* parent button to right */
1488 if (midx > block->rect.xmax) block->safety.xmax = block->rect.xmax + 3;
1489 else block->safety.xmax = block->rect.xmax + 40;
1491 /* parent button on bottom */
1492 if (midy < block->rect.ymin) block->safety.ymin = block->rect.ymin - 3;
1493 else block->safety.ymin = block->rect.ymin - 40;
1494 /* parent button on top */
1495 if (midy > block->rect.ymax) block->safety.ymax = block->rect.ymax + 3;
1496 else block->safety.ymax = block->rect.ymax + 40;
1498 /* exception for switched pulldowns... */
1499 if (dir1 && (dir1 & block->direction) == 0) {
1500 if (dir2 == UI_RIGHT) block->safety.xmax = block->rect.xmax + 3;
1501 if (dir2 == UI_LEFT) block->safety.xmin = block->rect.xmin - 3;
1503 block->direction = dir1;
1506 /* keep a list of these, needed for pulldown menus */
1507 saferct = MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct");
1508 saferct->parent = butrct;
1509 saferct->safety = block->safety;
1510 BLI_freelistN(&block->saferct);
1511 BLI_duplicatelist(&block->saferct, &but->block->saferct);
1512 BLI_addhead(&block->saferct, saferct);
1515 static void ui_block_region_draw(const bContext *C, ARegion *ar)
1519 for (block = ar->uiblocks.first; block; block = block->next)
1520 uiDrawBlock(C, block);
1523 static void ui_popup_block_clip(wmWindow *window, uiBlock *block)
1527 if (block->flag & UI_BLOCK_NO_WIN_CLIP) {
1531 winx = WM_window_pixels_x(window);
1532 winy = WM_window_pixels_y(window);
1533 // wm_window_get_size(window, &winx, &winy);
1535 if (block->rect.xmin < MENU_SHADOW_SIDE)
1536 block->rect.xmin = MENU_SHADOW_SIDE;
1537 if (block->rect.xmax > winx - MENU_SHADOW_SIDE)
1538 block->rect.xmax = winx - MENU_SHADOW_SIDE;
1540 if (block->rect.ymin < MENU_SHADOW_BOTTOM)
1541 block->rect.ymin = MENU_SHADOW_BOTTOM;
1542 if (block->rect.ymax > winy - MENU_TOP)
1543 block->rect.ymax = winy - MENU_TOP;
1546 void ui_popup_block_scrolltest(uiBlock *block)
1550 block->flag &= ~(UI_BLOCK_CLIPBOTTOM | UI_BLOCK_CLIPTOP);
1552 for (bt = block->buttons.first; bt; bt = bt->next)
1553 bt->flag &= ~UI_SCROLLED;
1555 if (block->buttons.first == block->buttons.last)
1558 /* mark buttons that are outside boundary */
1559 for (bt = block->buttons.first; bt; bt = bt->next) {
1560 if (bt->rect.ymin < block->rect.ymin) {
1561 bt->flag |= UI_SCROLLED;
1562 block->flag |= UI_BLOCK_CLIPBOTTOM;
1564 if (bt->rect.ymax > block->rect.ymax) {
1565 bt->flag |= UI_SCROLLED;
1566 block->flag |= UI_BLOCK_CLIPTOP;
1570 /* mark buttons overlapping arrows, if we have them */
1571 for (bt = block->buttons.first; bt; bt = bt->next) {
1572 if (block->flag & UI_BLOCK_CLIPBOTTOM) {
1573 if (bt->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW)
1574 bt->flag |= UI_SCROLLED;
1576 if (block->flag & UI_BLOCK_CLIPTOP) {
1577 if (bt->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW)
1578 bt->flag |= UI_SCROLLED;
1583 uiPopupBlockHandle *ui_popup_block_create(bContext *C, ARegion *butregion, uiBut *but,
1584 uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func,
1587 wmWindow *window = CTX_wm_window(C);
1588 static ARegionType type;
1591 uiPopupBlockHandle *handle;
1592 uiSafetyRct *saferct;
1595 handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
1597 /* store context for operator */
1598 handle->ctx_area = CTX_wm_area(C);
1599 handle->ctx_region = CTX_wm_region(C);
1601 /* create area region */
1602 ar = ui_add_temporary_region(CTX_wm_screen(C));
1603 handle->region = ar;
1605 memset(&type, 0, sizeof(ARegionType));
1606 type.draw = ui_block_region_draw;
1609 UI_add_region_handlers(&ar->handlers);
1611 /* create ui block */
1613 block = create_func(C, handle->region, arg);
1615 block = handle_create_func(C, handle, arg);
1617 if (block->handle) {
1618 memcpy(block->handle, handle, sizeof(uiPopupBlockHandle));
1620 handle = block->handle;
1623 block->handle = handle;
1625 ar->regiondata = handle;
1627 /* set UI_BLOCK_NUMSELECT before uiEndBlock() so we get alphanumeric keys assigned */
1629 if (but->type == PULLDOWN) {
1630 block->flag |= UI_BLOCK_NUMSELECT;
1634 block->flag |= UI_BLOCK_POPUP | UI_BLOCK_NUMSELECT;
1637 block->flag |= UI_BLOCK_LOOP;
1639 if (!block->endblock)
1640 uiEndBlock(C, block);
1642 /* if this is being created from a button */
1644 block->aspect = but->block->aspect;
1645 ui_block_position(window, butregion, but, block);
1646 handle->direction = block->direction;
1649 /* keep a list of these, needed for pulldown menus */
1650 saferct = MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct");
1651 saferct->safety = block->safety;
1652 BLI_addhead(&block->saferct, saferct);
1655 /* clip block with window boundary */
1656 ui_popup_block_clip(window, block);
1658 /* the block and buttons were positioned in window space as in 2.4x, now
1659 * these menu blocks are regions so we bring it back to region space.
1660 * additionally we add some padding for the menu shadow or rounded menus */
1661 ar->winrct.xmin = block->rect.xmin - MENU_SHADOW_SIDE;
1662 ar->winrct.xmax = block->rect.xmax + MENU_SHADOW_SIDE;
1663 ar->winrct.ymin = block->rect.ymin - MENU_SHADOW_BOTTOM;
1664 ar->winrct.ymax = block->rect.ymax + MENU_TOP;
1666 ui_block_translate(block, -ar->winrct.xmin, -ar->winrct.ymin);
1668 /* adds subwindow */
1669 ED_region_init(C, ar);
1671 /* checks which buttons are visible, sets flags to prevent draw (do after region init) */
1672 ui_popup_block_scrolltest(block);
1674 /* get winmat now that we actually have the subwindow */
1675 wmSubWindowSet(window, ar->swinid);
1677 wm_subwindow_getmatrix(window, ar->swinid, block->winmat);
1679 /* notify change and redraw */
1680 ED_region_tag_redraw(ar);
1685 void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle)
1687 ui_remove_temporary_region(C, CTX_wm_screen(C), handle->region);
1689 if (handle->scrolltimer)
1690 WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), handle->scrolltimer);
1695 /***************************** Menu Button ***************************/
1697 static void ui_block_func_MENUSTR(bContext *UNUSED(C), uiLayout *layout, void *arg_str)
1699 uiBlock *block = uiLayoutGetBlock(layout);
1700 uiPopupBlockHandle *handle = block->handle;
1701 uiLayout *split, *column = NULL;
1705 const char *instr = arg_str;
1706 int columns, rows, a, b;
1707 int column_start = 0, column_end = 0;
1709 uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
1711 /* compute menu data */
1712 md = decompose_menu_string(instr);
1714 /* columns and row estimation */
1715 columns = (md->nitems + 20) / 20;
1719 columns = (md->nitems + 25) / 25;
1721 rows = md->nitems / columns;
1724 while (rows * columns < md->nitems)
1729 if (md->titleicon) {
1730 uiItemL(layout, md->title, md->titleicon);
1733 uiItemL(layout, md->title, ICON_NONE);
1734 bt = block->buttons.last;
1735 bt->flag = UI_TEXT_LEFT;
1739 /* inconsistent, but menus with labels do not look good flipped */
1741 for (a = 0; a < md->nitems; a++, entry++) {
1742 if (entry->sepr && entry->str[0]) {
1743 block->flag |= UI_BLOCK_NO_FLIP;
1749 split = uiLayoutSplit(layout, 0.0f, FALSE);
1751 for (a = 0; a < md->nitems; a++) {
1752 if (a == column_end) {
1753 /* start new column, and find out where it ends in advance, so we
1754 * can flip the order of items properly per column */
1756 column_end = md->nitems;
1758 for (b = a + 1; b < md->nitems; b++) {
1759 entry = &md->items[b];
1761 /* new column on N rows or on separation label */
1762 if (((b - a) % rows == 0) || (entry->sepr && entry->str[0])) {
1768 column = uiLayoutColumn(split, FALSE);
1771 if (block->flag & UI_BLOCK_NO_FLIP)
1772 entry = &md->items[a];
1774 entry = &md->items[column_start + column_end - 1 - a];
1777 uiItemL(column, entry->str, entry->icon);
1778 bt = block->buttons.last;
1779 bt->flag = UI_TEXT_LEFT;
1781 else if (entry->icon) {
1782 uiDefIconTextButF(block, BUTM, B_NOP, entry->icon, entry->str, 0, 0,
1783 UI_UNIT_X * 5, UI_UNIT_Y, &handle->retvalue, (float) entry->retval, 0.0, 0, 0, "");
1786 uiDefButF(block, BUTM, B_NOP, entry->str, 0, 0,
1787 UI_UNIT_X * 5, UI_UNIT_X, &handle->retvalue, (float) entry->retval, 0.0, 0, 0, "");
1794 void ui_block_func_ICONROW(bContext *UNUSED(C), uiLayout *layout, void *arg_but)
1796 uiBlock *block = uiLayoutGetBlock(layout);
1797 uiPopupBlockHandle *handle = block->handle;
1798 uiBut *but = arg_but;
1801 uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
1803 for (a = (int)but->hardmin; a <= (int)but->hardmax; a++)
1804 uiDefIconButF(block, BUTM, B_NOP, but->icon + (a - but->hardmin), 0, 0, UI_UNIT_X * 5, UI_UNIT_Y,
1805 &handle->retvalue, (float)a, 0.0, 0, 0, "");
1808 void ui_block_func_ICONTEXTROW(bContext *UNUSED(C), uiLayout *layout, void *arg_but)
1810 uiBlock *block = uiLayoutGetBlock(layout);
1811 uiPopupBlockHandle *handle = block->handle;
1812 uiBut *but = arg_but, *bt;
1817 uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
1819 md = decompose_menu_string(but->str);
1823 bt = uiDefBut(block, LABEL, 0, md->title, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
1824 bt->flag = UI_TEXT_LEFT;
1827 /* loop through the menu options and draw them out with icons & text labels */
1828 for (a = 0; a < md->nitems; a++) {
1829 entry = &md->items[md->nitems - a - 1];
1834 uiDefIconTextButF(block, BUTM, B_NOP, (short)((but->icon) + (entry->retval - but->hardmin)), entry->str,
1835 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, &handle->retvalue, (float) entry->retval, 0.0, 0, 0, "");
1842 static void ui_warp_pointer(int x, int y)
1844 /* XXX 2.50 which function to use for this? */
1845 /* OSX has very poor mouse-warp support, it sends events;
1846 * this causes a menu being pressed immediately ... */
1853 /********************* Color Button ****************/
1855 /* for picker, while editing hsv */
1856 void ui_set_but_hsv(uiBut *but)
1859 float *hsv = ui_block_hsv_get(but->block);
1861 hsv_to_rgb_v(hsv, col);
1862 ui_set_but_vectorf(but, col);
1865 /* also used by small picker, be careful with name checks below... */
1866 static void ui_update_block_buts_rgb(uiBlock *block, const float rgb[3])
1869 float *hsv = ui_block_hsv_get(block);
1870 struct ColorManagedDisplay *display = NULL;
1872 /* this is to keep the H and S value when V is equal to zero
1873 * and we are working in HSV mode, of course!
1875 rgb_to_hsv_compat_v(rgb, hsv);
1877 if (block->color_profile)
1878 display = ui_block_display_get(block);
1880 /* this updates button strings, is hackish... but button pointers are on stack of caller function */
1881 for (bt = block->buttons.first; bt; bt = bt->next) {
1884 ui_set_but_vectorf(bt, rgb);
1887 else if (strcmp(bt->str, "Hex: ") == 0) {
1892 /* Hex code is assumed to be in sRGB space (coming from other applications, web, etc) */
1894 copy_v3_v3(rgb_gamma, rgb);
1897 /* make a display version, for Hex code */
1898 IMB_colormanagement_scene_linear_to_display_v3(rgb_gamma, display);
1901 if (rgb_gamma[0] > 1.0f) rgb_gamma[0] = modf(rgb_gamma[0], &intpart);
1902 if (rgb_gamma[1] > 1.0f) rgb_gamma[1] = modf(rgb_gamma[1], &intpart);
1903 if (rgb_gamma[2] > 1.0f) rgb_gamma[2] = modf(rgb_gamma[2], &intpart);
1905 BLI_snprintf(col, sizeof(col), "%02X%02X%02X",
1906 FTOCHAR(rgb_gamma[0]), FTOCHAR(rgb_gamma[1]), FTOCHAR(rgb_gamma[2]));
1908 strcpy(bt->poin, col);
1910 else if (bt->str[1] == ' ') {
1911 if (bt->str[0] == 'R') {
1912 ui_set_but_val(bt, rgb[0]);
1914 else if (bt->str[0] == 'G') {
1915 ui_set_but_val(bt, rgb[1]);
1917 else if (bt->str[0] == 'B') {
1918 ui_set_but_val(bt, rgb[2]);
1920 else if (bt->str[0] == 'H') {
1921 ui_set_but_val(bt, hsv[0]);
1923 else if (bt->str[0] == 'S') {
1924 ui_set_but_val(bt, hsv[1]);
1926 else if (bt->str[0] == 'V') {
1927 ui_set_but_val(bt, hsv[2]);
1935 static void do_picker_rna_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
1937 uiBut *but = (uiBut *)bt1;
1938 uiPopupBlockHandle *popup = but->block->handle;
1939 PropertyRNA *prop = but->rnaprop;
1940 PointerRNA ptr = but->rnapoin;
1944 RNA_property_float_get_array(&ptr, prop, rgb);
1945 ui_update_block_buts_rgb(but->block, rgb);
1949 popup->menuretval = UI_RETURN_UPDATE;
1952 static void do_hsv_rna_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
1954 uiBut *but = (uiBut *)bt1;
1955 uiPopupBlockHandle *popup = but->block->handle;
1957 float *hsv = ui_block_hsv_get(but->block);
1959 hsv_to_rgb_v(hsv, rgb);
1961 ui_update_block_buts_rgb(but->block, rgb);
1964 popup->menuretval = UI_RETURN_UPDATE;
1967 static void do_hex_rna_cb(bContext *UNUSED(C), void *bt1, void *hexcl)
1969 uiBut *but = (uiBut *)bt1;
1970 uiPopupBlockHandle *popup = but->block->handle;
1971 char *hexcol = (char *)hexcl;
1974 hex_to_rgb(hexcol, rgb, rgb + 1, rgb + 2);
1976 /* Hex code is assumed to be in sRGB space (coming from other applications, web, etc) */
1977 if (but->block->color_profile) {
1978 /* so we need to linearise it for Blender */
1979 ui_block_to_scene_linear_v3(but->block, rgb);
1982 ui_update_block_buts_rgb(but->block, rgb);
1985 popup->menuretval = UI_RETURN_UPDATE;
1988 static void close_popup_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
1990 uiBut *but = (uiBut *)bt1;
1991 uiPopupBlockHandle *popup = but->block->handle;
1994 popup->menuretval = UI_RETURN_OK;
1997 static void picker_new_hide_reveal(uiBlock *block, short colormode)
2002 for (bt = block->buttons.first; bt; bt = bt->next) {
2003 if (bt->func == do_picker_rna_cb && bt->type == NUMSLI && bt->rnaindex != 3) {
2004 /* RGB sliders (color circle and alpha are always shown) */
2005 if (colormode == 0) bt->flag &= ~UI_HIDDEN;
2006 else bt->flag |= UI_HIDDEN;
2008 else if (bt->func == do_hsv_rna_cb) {
2010 if (colormode == 1) bt->flag &= ~UI_HIDDEN;
2011 else bt->flag |= UI_HIDDEN;
2013 else if (bt->func == do_hex_rna_cb || bt->type == LABEL) {
2014 /* hex input or gamma correction status label */
2015 if (colormode == 2) bt->flag &= ~UI_HIDDEN;
2016 else bt->flag |= UI_HIDDEN;
2021 static void do_picker_new_mode_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
2024 short colormode = ui_get_but_val(bt);
2025 picker_new_hide_reveal(bt->block, colormode);
2028 #define PICKER_H (7.5f * U.widget_unit)
2029 #define PICKER_W (7.5f * U.widget_unit)
2030 #define PICKER_SPACE (0.3f * U.widget_unit)
2031 #define PICKER_BAR (0.7f * U.widget_unit)
2033 #define PICKER_TOTAL_W (PICKER_W + PICKER_SPACE + PICKER_BAR)
2035 static void circle_picker(uiBlock *block, PointerRNA *ptr, PropertyRNA *prop)
2040 bt = uiDefButR_prop(block, HSVCIRCLE, 0, "", 0, 0, PICKER_H, PICKER_W, ptr, prop, 0, 0.0, 0.0, 0, 0, "Color");
2041 uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2044 bt = uiDefButR_prop(block, HSVCUBE, 0, "", PICKER_W + PICKER_SPACE, 0, PICKER_BAR, PICKER_H, ptr, prop, 0, 0.0, 0.0, UI_GRAD_V_ALT, 0, "Value");
2045 uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2049 static void square_picker(uiBlock *block, PointerRNA *ptr, PropertyRNA *prop, int type)
2052 int bartype = type + 3;
2055 bt = uiDefButR_prop(block, HSVCUBE, 0, "", 0, PICKER_BAR + PICKER_SPACE, PICKER_TOTAL_W, PICKER_H, ptr, prop, 0, 0.0, 0.0, type, 0, "Color");
2056 uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2059 bt = uiDefButR_prop(block, HSVCUBE, 0, "", 0, 0, PICKER_TOTAL_W, PICKER_BAR, ptr, prop, 0, 0.0, 0.0, bartype, 0, "Value");
2060 uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2064 /* a HS circle, V slider, rgb/hsv/hex sliders */
2065 static void uiBlockPicker(uiBlock *block, float rgba[4], PointerRNA *ptr, PropertyRNA *prop, int show_picker)
2067 static short colormode = 0; /* temp? 0=rgb, 1=hsv, 2=hex */
2069 int width, butwidth;
2070 static char tip[50];
2071 static char hexcol[128];
2073 float min, max, step, precision;
2074 float *hsv = ui_block_hsv_get(block);
2077 ui_block_hsv_get(block);
2079 width = PICKER_TOTAL_W;
2080 butwidth = width - 1.5f * UI_UNIT_X;
2082 /* existence of profile means storage is in linear color space, with display correction */
2083 /* XXX That tip message is not use anywhere! */
2084 if (!block->color_profile) {
2085 BLI_strncpy(tip, N_("Value in Display Color Space"), sizeof(tip));
2086 copy_v3_v3(rgb_gamma, rgba);
2089 BLI_strncpy(tip, N_("Value in Linear RGB Color Space"), sizeof(tip));
2091 /* make a display version, for Hex code */
2092 copy_v3_v3(rgb_gamma, rgba);
2093 ui_block_to_display_space_v3(block, rgb_gamma);
2096 /* sneaky way to check for alpha */
2099 RNA_property_float_ui_range(ptr, prop, &min, &max, &step, &precision);
2100 RNA_property_float_get_array(ptr, prop, rgba);
2102 switch (U.color_picker_type) {
2103 case USER_CP_CIRCLE:
2104 circle_picker(block, ptr, prop);
2106 case USER_CP_SQUARE_SV:
2107 square_picker(block, ptr, prop, UI_GRAD_SV);
2109 case USER_CP_SQUARE_HS:
2110 square_picker(block, ptr, prop, UI_GRAD_HS);
2112 case USER_CP_SQUARE_HV:
2113 square_picker(block, ptr, prop, UI_GRAD_HV);
2118 yco = -1.5f * UI_UNIT_Y;
2119 uiBlockBeginAlign(block);
2120 bt = uiDefButS(block, ROW, 0, IFACE_("RGB"), 0, yco, width / 3, UI_UNIT_Y, &colormode, 0.0, 0.0, 0, 0, "");
2121 uiButSetFunc(bt, do_picker_new_mode_cb, bt, NULL);
2122 bt = uiDefButS(block, ROW, 0, IFACE_("HSV"), width / 3, yco, width / 3, UI_UNIT_Y, &colormode, 0.0, 1.0, 0, 0, "");
2123 uiButSetFunc(bt, do_picker_new_mode_cb, bt, NULL);
2124 bt = uiDefButS(block, ROW, 0, IFACE_("Hex"), 2 * width / 3, yco, width / 3, UI_UNIT_Y, &colormode, 0.0, 2.0, 0, 0, "");
2125 uiButSetFunc(bt, do_picker_new_mode_cb, bt, NULL);
2126 uiBlockEndAlign(block);
2128 yco = -3.0f * UI_UNIT_Y;
2130 bt = uiDefIconButO(block, BUT, "UI_OT_eyedropper", WM_OP_INVOKE_DEFAULT, ICON_EYEDROPPER, butwidth + 10, yco, UI_UNIT_X, UI_UNIT_Y, NULL);
2131 uiButSetFunc(bt, close_popup_cb, bt, NULL);
2135 uiBlockBeginAlign(block);
2136 bt = uiDefButR_prop(block, NUMSLI, 0, IFACE_("R "), 0, yco, butwidth, UI_UNIT_Y, ptr, prop, 0, 0.0, 0.0, 0, 3, TIP_("Red"));
2137 uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2138 bt = uiDefButR_prop(block, NUMSLI, 0, IFACE_("G "), 0, yco -= UI_UNIT_Y, butwidth, UI_UNIT_Y, ptr, prop, 1, 0.0, 0.0, 0, 3, TIP_("Green"));
2139 uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2140 bt = uiDefButR_prop(block, NUMSLI, 0, IFACE_("B "), 0, yco -= UI_UNIT_Y, butwidth, UI_UNIT_Y, ptr, prop, 2, 0.0, 0.0, 0, 3, TIP_("Blue"));
2141 uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2143 /* could use uiItemFullR(col, ptr, prop, -1, 0, UI_ITEM_R_EXPAND|UI_ITEM_R_SLIDER, "", ICON_NONE);
2144 * but need to use uiButSetFunc for updating other fake buttons */
2147 yco = -3.0f * UI_UNIT_Y;
2148 uiBlockBeginAlign(block);
2149 bt = uiDefButF(block, NUMSLI, 0, IFACE_("H "), 0, yco, butwidth, UI_UNIT_Y, hsv, 0.0, 1.0, 10, 3, TIP_("Hue"));
2150 uiButSetFunc(bt, do_hsv_rna_cb, bt, hsv);
2151 bt = uiDefButF(block, NUMSLI, 0, IFACE_("S "), 0, yco -= UI_UNIT_Y, butwidth, UI_UNIT_Y, hsv + 1, 0.0, 1.0, 10, 3, TIP_("Saturation"));
2152 uiButSetFunc(bt, do_hsv_rna_cb, bt, hsv);
2153 bt = uiDefButF(block, NUMSLI, 0, IFACE_("V "), 0, yco -= UI_UNIT_Y, butwidth, UI_UNIT_Y, hsv + 2, 0.0, max, 10, 3, TIP_("Value"));
2154 uiButSetFunc(bt, do_hsv_rna_cb, bt, hsv);
2155 uiBlockEndAlign(block);
2157 if (rgba[3] != FLT_MAX) {
2158 bt = uiDefButR_prop(block, NUMSLI, 0, IFACE_("A "), 0, yco -= UI_UNIT_Y, butwidth, UI_UNIT_Y, ptr, prop, 3, 0.0, 0.0, 0, 0, TIP_("Alpha"));
2159 uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
2165 BLI_snprintf(hexcol, sizeof(hexcol), "%02X%02X%02X", FTOCHAR(rgb_gamma[0]), FTOCHAR(rgb_gamma[1]), FTOCHAR(rgb_gamma[2]));
2167 yco = -3.0f * UI_UNIT_Y;
2168 bt = uiDefBut(block, TEX, 0, IFACE_("Hex: "), 0, yco, butwidth, UI_UNIT_Y, hexcol, 0, 8, 0, 0, TIP_("Hex triplet for color (#RRGGBB)"));
2169 uiButSetFunc(bt, do_hex_rna_cb, bt, hexcol);
2170 uiDefBut(block, LABEL, 0, IFACE_("(Gamma Corrected)"), 0, yco - UI_UNIT_Y, butwidth, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
2172 rgb_to_hsv_v(rgba, hsv);
2174 picker_new_hide_reveal(block, colormode);
2178 static int ui_picker_small_wheel_cb(const bContext *UNUSED(C), uiBlock *block, wmEvent *event)
2182 if (event->type == WHEELUPMOUSE)
2184 else if (event->type == WHEELDOWNMOUSE)
2190 for (but = block->buttons.first; but; but = but->next) {
2191 if (but->type == HSVCUBE && but->active == NULL) {
2192 uiPopupBlockHandle *popup = block->handle;
2194 float *hsv = ui_block_hsv_get(block);
2196 ui_get_but_vectorf(but, rgb);
2198 rgb_to_hsv_compat_v(rgb, hsv);
2199 hsv[2] = CLAMPIS(hsv[2] + add, 0.0f, 1.0f);
2200 hsv_to_rgb_v(hsv, rgb);
2202 ui_set_but_vectorf(but, rgb);
2204 ui_update_block_buts_rgb(block, rgb);
2206 popup->menuretval = UI_RETURN_UPDATE;
2215 uiBlock *ui_block_func_COLOR(bContext *C, uiPopupBlockHandle *handle, void *arg_but)
2217 uiBut *but = arg_but;
2219 int show_picker = TRUE;
2221 block = uiBeginBlock(C, handle->region, __func__, UI_EMBOSS);
2224 if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
2225 block->color_profile = FALSE;
2230 /* if color block is invoked from a popup we wouldn't be able to set color properly
2231 * this is because color picker will close popups first and then will try to figure
2232 * out active button RNA, and of course it'll fail
2234 show_picker = (but->block->flag & UI_BLOCK_POPUP) == 0;
2237 copy_v3_v3(handle->retvec, but->editvec);
2239 uiBlockPicker(block, handle->retvec, &but->rnapoin, but->rnaprop, show_picker);
2241 block->flag = UI_BLOCK_LOOP | UI_BLOCK_REDRAW | UI_BLOCK_KEEP_OPEN | UI_BLOCK_OUT_1 | UI_BLOCK_MOVEMOUSE_QUIT;
2242 uiBoundsBlock(block, 0.5 * UI_UNIT_X);
2244 block->block_event_func = ui_picker_small_wheel_cb;
2247 block->direction = UI_TOP;
2252 /************************ Popup Menu Memory ****************************/
2254 static int ui_popup_string_hash(const char *str)
2256 /* sometimes button contains hotkey, sometimes not, strip for proper compare */
2258 char *delimit = strchr(str, '|');
2260 if (delimit) *delimit = 0;
2261 hash = BLI_ghashutil_strhash(str);
2262 if (delimit) *delimit = '|';
2267 static int ui_popup_menu_hash(const char *str)
2269 return BLI_ghashutil_strhash(str);
2272 /* but == NULL read, otherwise set */
2273 uiBut *ui_popup_menu_memory(uiBlock *block, uiBut *but)
2275 static int mem[256], first = 1;
2276 int hash = block->puphash;
2280 memset(mem, -1, sizeof(mem));
2286 mem[hash & 255] = ui_popup_string_hash(but->str);
2291 for (but = block->buttons.first; but; but = but->next)
2292 if (ui_popup_string_hash(but->str) == mem[hash & 255])
2299 /******************** Popup Menu with callback or string **********************/
2301 struct uiPopupMenu {
2306 int mx, my, popup, slideout;
2307 int startx, starty, maxrow;
2309 uiMenuCreateFunc menu_func;
2313 static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
2317 uiPopupMenu *pup = arg_pup;
2318 int offset[2], direction, minwidth, width, height, flip;
2320 if (pup->menu_func) {
2321 pup->block->handle = handle;
2322 pup->menu_func(C, pup->layout, pup->menu_arg);
2323 pup->block->handle = NULL;
2327 /* minimum width to enforece */
2328 minwidth = BLI_rctf_size_x(&pup->but->rect);
2330 if (pup->but->type == PULLDOWN || pup->but->menu_create_func) {
2331 direction = UI_DOWN;
2341 direction = UI_DOWN;
2347 /* in some cases we create the block before the region,
2348 * so we set it delayed here if necessary */
2349 if (BLI_findindex(&handle->region->uiblocks, block) == -1)
2350 uiBlockSetRegion(block, handle->region);
2352 block->direction = direction;
2354 uiBlockLayoutResolve(block, &width, &height);
2356 uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
2359 uiBlockSetFlag(block, UI_BLOCK_LOOP | UI_BLOCK_REDRAW | UI_BLOCK_NUMSELECT);
2360 uiBlockSetDirection(block, direction);
2362 /* offset the mouse position, possibly based on earlier selection */
2363 if ((block->flag & UI_BLOCK_POPUP_MEMORY) &&
2364 (bt = ui_popup_menu_memory(block, NULL)))
2366 /* position mouse on last clicked item, at 0.8*width of the
2367 * button, so it doesn't overlap the text too much, also note
2368 * the offset is negative because we are inverse moving the
2369 * block to be under the mouse */
2370 offset[0] = -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect));
2371 offset[1] = -(bt->rect.ymin + 0.5f * UI_UNIT_Y);
2374 /* position mouse at 0.8*width of the button and below the tile
2375 * on the first item */
2377 for (bt = block->buttons.first; bt; bt = bt->next)
2378 offset[0] = min_ii(offset[0], -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect)));
2380 offset[1] = 1.5 * UI_UNIT_Y;
2383 block->minbounds = minwidth;
2384 uiMenuPopupBoundsBlock(block, 1, offset[0], offset[1]);
2387 /* for a header menu we set the direction automatic */
2388 if (!pup->slideout && flip) {
2389 ScrArea *sa = CTX_wm_area(C);
2390 if (sa && sa->headertype == HEADERDOWN) {
2391 ARegion *ar = CTX_wm_region(C);
2392 if (ar && ar->regiontype == RGN_TYPE_HEADER) {
2393 uiBlockSetDirection(block, UI_TOP);
2394 uiBlockFlipOrder(block);
2399 block->minbounds = minwidth;
2400 uiTextBoundsBlock(block, 2.5 * UI_UNIT_X);
2403 /* if menu slides out of other menu, override direction */
2405 uiBlockSetDirection(block, UI_RIGHT);
2407 uiEndBlock(C, block);
2412 uiPopupBlockHandle *ui_popup_menu_create(bContext *C, ARegion *butregion, uiBut *but,
2413 uiMenuCreateFunc menu_func, void *arg, char *str)
2415 wmWindow *window = CTX_wm_window(C);
2416 uiStyle *style = UI_GetStyleDraw();
2417 uiPopupBlockHandle *handle;
2419 pup = MEM_callocN(sizeof(uiPopupMenu), __func__);
2420 pup->block = uiBeginBlock(C, NULL, __func__, UI_EMBOSSP);
2421 pup->block->flag |= UI_BLOCK_NUMSELECT; /* default menus to numselect */
2422 pup->layout = uiBlockLayout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, style);
2423 pup->slideout = (but && (but->block->flag & UI_BLOCK_LOOP));
2425 uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN);
2428 /* no button to start from, means we are a popup */
2429 pup->mx = window->eventstate->x;
2430 pup->my = window->eventstate->y;
2432 pup->block->flag |= UI_BLOCK_NO_FLIP;
2434 /* some enums reversing is strange, currently we have no good way to
2435 * reverse some enum's but not others, so reverse all so the first menu
2436 * items are always close to the mouse cursor */
2439 /* if this is an rna button then we can assume its an enum
2440 * flipping enums is generally not good since the order can be
2441 * important [#28786] */
2442 if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) {
2443 pup->block->flag |= UI_BLOCK_NO_FLIP;
2447 uiLayoutContextCopy(pup->layout, but->context);
2451 /* menu is created from a string */
2452 pup->menu_func = ui_block_func_MENUSTR;
2453 pup->menu_arg = str;
2456 /* menu is created from a callback */
2457 pup->menu_func = menu_func;
2458 pup->menu_arg = arg;
2461 handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);
2466 UI_add_popup_handlers(C, &window->modalhandlers, handle);
2467 WM_event_add_mousemove(C);
2475 /******************** Popup Menu API with begin and end ***********************/
2477 /* only return handler, and set optional title */
2478 uiPopupMenu *uiPupMenuBegin(bContext *C, const char *title, int icon)
2480 uiStyle *style = UI_GetStyleDraw();
2481 uiPopupMenu *pup = MEM_callocN(sizeof(uiPopupMenu), "popup menu");
2484 pup->block = uiBeginBlock(C, NULL, __func__, UI_EMBOSSP);
2485 pup->block->flag |= UI_BLOCK_POPUP_MEMORY;
2486 pup->block->puphash = ui_popup_menu_hash(title);
2487 pup->layout = uiBlockLayout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, style);
2489 /* note, this intentionally differs from the menu & submenu default because many operators
2490 * use popups like this to select one of their options - where having invoke doesn't make sense */
2491 uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN);
2493 /* create in advance so we can let buttons point to retval already */
2494 pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
2496 /* create title button */
2497 if (title && title[0]) {
2501 BLI_snprintf(titlestr, sizeof(titlestr), " %s", title);
2502 uiDefIconTextBut(pup->block, LABEL, 0, icon, titlestr, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
2505 but = uiDefBut(pup->block, LABEL, 0, title, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
2506 but->flag = UI_TEXT_LEFT;
2513 /* set the whole structure to work */
2514 void uiPupMenuEnd(bContext *C, uiPopupMenu *pup)
2516 wmWindow *window = CTX_wm_window(C);
2517 uiPopupBlockHandle *menu;
2520 pup->mx = window->eventstate->x;
2521 pup->my = window->eventstate->y;
2523 menu = ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_POPUP, pup);
2526 UI_add_popup_handlers(C, &window->modalhandlers, menu);
2527 WM_event_add_mousemove(C);
2532 uiLayout *uiPupMenuLayout(uiPopupMenu *pup)
2537 /*************************** Standard Popup Menus ****************************/
2539 static void operator_name_cb(bContext *C, void *arg, int retval)
2541 const char *opname = arg;
2543 if (opname && retval > 0)
2544 WM_operator_name_call(C, opname, WM_OP_EXEC_DEFAULT, NULL);
2547 static void operator_cb(bContext *C, void *arg, int retval)
2549 wmOperator *op = arg;
2551 if (op && retval > 0)
2552 WM_operator_call(C, op);
2554 WM_operator_free(op);
2557 static void confirm_cancel_operator(void *opv)
2559 WM_operator_free(opv);
2562 static void vconfirm_opname(bContext *C, const char *opname, const char *title, const char *itemfmt, va_list ap)
2564 __attribute__ ((format(printf, 4, 0)))
2567 static void vconfirm_opname(bContext *C, const char *opname, const char *title, const char *itemfmt, va_list ap)
2569 uiPopupBlockHandle *handle;
2573 if (title) s += sprintf(s, "%s%%t|", title);
2574 vsnprintf(s, sizeof(buf) - (s - buf), itemfmt, ap);
2575 buf[sizeof(buf) - 1] = '\0';
2577 handle = ui_popup_menu_create(C, NULL, NULL, NULL, NULL, buf);
2579 handle->popup_func = operator_name_cb;
2580 handle->popup_arg = (void *)opname;
2583 static void confirm_operator(bContext *C, wmOperator *op, const char *title, const char *item)
2585 uiPopupBlockHandle *handle;
2589 if (title) s += BLI_snprintf(s, sizeof(buf), "%s%%t|%s", title, item);
2592 handle = ui_popup_menu_create(C, NULL, NULL, NULL, NULL, buf);
2594 handle->popup_func = operator_cb;
2595 handle->popup_arg = op;
2596 handle->cancel_func = confirm_cancel_operator;
2599 void uiPupMenuOkee(bContext *C, const char *opname, const char *str, ...)
2604 BLI_snprintf(titlestr, sizeof(titlestr), "OK? %%i%d", ICON_QUESTION);
2607 vconfirm_opname(C, opname, titlestr, str, ap);
2611 /* note, only call this is the file exists,
2612 * the case where the file does not exist so can be saved without a
2613 * popup must be checked for already, since saving from here
2614 * will free the operator which will break invoke().
2615 * The operator state for this is implicitly OPERATOR_RUNNING_MODAL */
2616 void uiPupMenuSaveOver(bContext *C, wmOperator *op, const char *filename)
2618 confirm_operator(C, op, "Save Over?", filename);
2621 void uiPupMenuNotice(bContext *C, const char *str, ...)
2626 vconfirm_opname(C, NULL, NULL, str, ap);
2630 void uiPupMenuError(bContext *C, const char *str, ...)
2636 BLI_snprintf(titlestr, sizeof(titlestr), "Error %%i%d", ICON_ERROR);
2638 BLI_strncpy(nfmt, str, sizeof(nfmt));
2641 vconfirm_opname(C, NULL, titlestr, nfmt, ap);
2645 void uiPupMenuReports(bContext *C, ReportList *reports)
2651 if (!reports || !reports->list.first)
2653 if (!CTX_wm_window(C))
2656 ds = BLI_dynstr_new();
2658 for (report = reports->list.first; report; report = report->next) {
2659 if (report->type < reports->printlevel) {
2662 else if (report->type >= RPT_ERROR) {
2663 BLI_dynstr_appendf(ds, "Error %%i%d%%t|%s", ICON_ERROR, report->message);
2665 else if (report->type >= RPT_WARNING) {
2666 BLI_dynstr_appendf(ds, "Warning %%i%d%%t|%s", ICON_ERROR, report->message);
2668 else if (report->type >= RPT_INFO) {
2669 BLI_dynstr_appendf(ds, "Info %%i%d%%t|%s", ICON_INFO, report->message);
2673 str = BLI_dynstr_get_cstring(ds);
2675 ui_popup_menu_create(C, NULL, NULL, NULL, NULL, str);
2678 BLI_dynstr_free(ds);
2681 void uiPupMenuInvoke(bContext *C, const char *idname)
2686 MenuType *mt = WM_menutype_find(idname, TRUE);
2689 printf("%s: named menu \"%s\" not found\n", __func__, idname);
2693 if (mt->poll && mt->poll(C, mt) == 0)
2696 pup = uiPupMenuBegin(C, IFACE_(mt->label), ICON_NONE);
2697 layout = uiPupMenuLayout(pup);
2699 menu.layout = layout;
2702 if (G.debug & G_DEBUG_WM) {
2703 printf("%s: opening menu \"%s\"\n", __func__, idname);
2708 uiPupMenuEnd(C, pup);
2712 /*************************** Popup Block API **************************/
2714 void uiPupBlockO(bContext *C, uiBlockCreateFunc func, void *arg, const char *opname, int opcontext)
2716 wmWindow *window = CTX_wm_window(C);
2717 uiPopupBlockHandle *handle;
2719 handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
2721 handle->optype = (opname) ? WM_operatortype_find(opname, 0) : NULL;
2722 handle->opcontext = opcontext;
2724 UI_add_popup_handlers(C, &window->modalhandlers, handle);
2725 WM_event_add_mousemove(C);
2728 void uiPupBlock(bContext *C, uiBlockCreateFunc func, void *arg)
2730 uiPupBlockO(C, func, arg, NULL, 0);
2733 void uiPupBlockEx(bContext *C, uiBlockCreateFunc func, uiBlockHandleFunc popup_func, uiBlockCancelFunc cancel_func, void *arg)
2735 wmWindow *window = CTX_wm_window(C);
2736 uiPopupBlockHandle *handle;
2738 handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
2740 handle->retvalue = 1;
2742 handle->popup_arg = arg;
2743 handle->popup_func = popup_func;
2744 handle->cancel_func = cancel_func;
2745 // handle->opcontext = opcontext;
2747 UI_add_popup_handlers(C, &window->modalhandlers, handle);
2748 WM_event_add_mousemove(C);
2752 void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext)
2754 wmWindow *window = CTX_wm_window(C);
2755 uiPopupBlockHandle *handle;
2757 handle = ui_popup_block_create(C, NULL, NULL, func, NULL, op);
2759 handle->retvalue = 1;
2761 handle->popup_arg = op;
2762 handle->popup_func = operator_cb;
2763 handle->cancel_func = confirm_cancel_operator;
2764 handle->opcontext = opcontext;
2766 UI_add_popup_handlers(C, &window->modalhandlers, handle);
2767 WM_event_add_mousemove(C);
2771 void uiPupBlockClose(bContext *C, uiBlock *block)
2773 if (block->handle) {
2774 wmWindow *win = CTX_wm_window(C);
2776 /* if loading new .blend while popup is open, window will be NULL */
2778 UI_remove_popup_handlers(&win->modalhandlers, block->handle);
2779 ui_popup_block_free(C, block->handle);
2784 float *ui_block_hsv_get(uiBlock *block)