Adds some padding to the left of the icon, adds more space around the separator line and make it more visible, and add some spacing at the top and bottom of the menu. Ref T37794 Reviewed By: dingto, billrey Differential Revision: https://developer.blender.org/D223
2857 lines
78 KiB
C
2857 lines
78 KiB
C
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2008 Blender Foundation.
|
|
* All rights reserved.
|
|
*
|
|
* Contributor(s): Blender Foundation
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/** \file blender/editors/interface/interface_regions.c
|
|
* \ingroup edinterface
|
|
*/
|
|
|
|
|
|
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_userdef_types.h"
|
|
|
|
#include "BLI_math.h"
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_dynstr.h"
|
|
#include "BLI_ghash.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_screen.h"
|
|
#include "BKE_idcode.h"
|
|
#include "BKE_global.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
#include "wm_draw.h"
|
|
#include "wm_subwindow.h"
|
|
#include "wm_window.h"
|
|
|
|
#include "RNA_access.h"
|
|
|
|
#include "BIF_gl.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_interface_icons.h"
|
|
#include "UI_view2d.h"
|
|
|
|
#include "BLF_api.h"
|
|
#include "BLF_translation.h"
|
|
|
|
#include "ED_screen.h"
|
|
|
|
#include "IMB_colormanagement.h"
|
|
|
|
#include "interface_intern.h"
|
|
|
|
#define B_NOP -1
|
|
#define MENU_TOP 8
|
|
#define MENU_PADDING (int)(0.2f * UI_UNIT_Y)
|
|
|
|
/*********************** Menu Data Parsing ********************* */
|
|
|
|
typedef struct MenuEntry {
|
|
const char *str;
|
|
int retval;
|
|
int icon;
|
|
int sepr;
|
|
} MenuEntry;
|
|
|
|
typedef struct MenuData {
|
|
const char *instr;
|
|
const char *title;
|
|
int titleicon;
|
|
|
|
MenuEntry *items;
|
|
int nitems, itemssize;
|
|
} MenuData;
|
|
|
|
static MenuData *menudata_new(const char *instr)
|
|
{
|
|
MenuData *md = MEM_mallocN(sizeof(*md), "MenuData");
|
|
|
|
md->instr = instr;
|
|
md->title = NULL;
|
|
md->titleicon = 0;
|
|
md->items = NULL;
|
|
md->nitems = md->itemssize = 0;
|
|
|
|
return md;
|
|
}
|
|
|
|
static void menudata_set_title(MenuData *md, const char *title, int titleicon)
|
|
{
|
|
if (!md->title)
|
|
md->title = title;
|
|
if (!md->titleicon)
|
|
md->titleicon = titleicon;
|
|
}
|
|
|
|
static void menudata_add_item(MenuData *md, const char *str, int retval, int icon, int sepr)
|
|
{
|
|
if (md->nitems == md->itemssize) {
|
|
int nsize = md->itemssize ? (md->itemssize << 1) : 1;
|
|
MenuEntry *oitems = md->items;
|
|
|
|
md->items = MEM_mallocN(nsize * sizeof(*md->items), "md->items");
|
|
if (oitems) {
|
|
memcpy(md->items, oitems, md->nitems * sizeof(*md->items));
|
|
MEM_freeN(oitems);
|
|
}
|
|
|
|
md->itemssize = nsize;
|
|
}
|
|
|
|
md->items[md->nitems].str = str;
|
|
md->items[md->nitems].retval = retval;
|
|
md->items[md->nitems].icon = icon;
|
|
md->items[md->nitems].sepr = sepr;
|
|
md->nitems++;
|
|
}
|
|
|
|
static void menudata_free(MenuData *md)
|
|
{
|
|
MEM_freeN((void *)md->instr);
|
|
if (md->items) {
|
|
MEM_freeN(md->items);
|
|
}
|
|
MEM_freeN(md);
|
|
}
|
|
|
|
/**
|
|
* Parse menu description strings, string is of the
|
|
* form "[sss%t|]{(sss[%xNN]|), (%l|), (sss%l|)}", ssss%t indicates the
|
|
* menu title, sss or sss%xNN indicates an option,
|
|
* if %xNN is given then NN is the return value if
|
|
* that option is selected otherwise the return value
|
|
* is the index of the option (starting with 1). %l
|
|
* indicates a separator, sss%l indicates a label and
|
|
* new column.
|
|
*
|
|
* \param str String to be parsed.
|
|
* \retval new menudata structure, free with menudata_free()
|
|
*/
|
|
static MenuData *decompose_menu_string(const char *str)
|
|
{
|
|
char *instr = BLI_strdup(str);
|
|
MenuData *md = menudata_new(instr);
|
|
const char *nitem = NULL;
|
|
char *s = instr;
|
|
int nicon = 0, nretval = 1, nitem_is_title = 0, nitem_is_sepr = 0;
|
|
|
|
while (1) {
|
|
char c = *s;
|
|
|
|
if (c == '%') {
|
|
if (s[1] == 'x') {
|
|
nretval = atoi(s + 2);
|
|
|
|
*s = '\0';
|
|
s++;
|
|
}
|
|
else if (s[1] == 't') {
|
|
nitem_is_title = (s != instr); /* check for empty title */
|
|
|
|
*s = '\0';
|
|
s++;
|
|
}
|
|
else if (s[1] == 'l') {
|
|
nitem_is_sepr = 1;
|
|
if (!nitem) nitem = "";
|
|
|
|
*s = '\0';
|
|
s++;
|
|
}
|
|
else if (s[1] == 'i') {
|
|
nicon = atoi(s + 2);
|
|
|
|
*s = '\0';
|
|
s++;
|
|
}
|
|
}
|
|
else if (c == UI_SEP_CHAR || c == '\n' || c == '\0') {
|
|
if (nitem) {
|
|
*s = '\0';
|
|
|
|
if (nitem_is_title) {
|
|
menudata_set_title(md, nitem, nicon);
|
|
nitem_is_title = 0;
|
|
}
|
|
else if (nitem_is_sepr) {
|
|
/* prevent separator to get a value */
|
|
menudata_add_item(md, nitem, -1, nicon, 1);
|
|
nretval = md->nitems + 1;
|
|
nitem_is_sepr = 0;
|
|
}
|
|
else {
|
|
menudata_add_item(md, nitem, nretval, nicon, 0);
|
|
nretval = md->nitems + 1;
|
|
}
|
|
|
|
nitem = NULL;
|
|
nicon = 0;
|
|
}
|
|
|
|
if (c == '\0') {
|
|
break;
|
|
}
|
|
}
|
|
else if (!nitem) {
|
|
nitem = s;
|
|
}
|
|
|
|
s++;
|
|
}
|
|
|
|
return md;
|
|
}
|
|
|
|
void ui_set_name_menu(uiBut *but, int value)
|
|
{
|
|
MenuData *md;
|
|
int i;
|
|
|
|
md = decompose_menu_string(but->str);
|
|
for (i = 0; i < md->nitems; i++) {
|
|
if (md->items[i].retval == value) {
|
|
BLI_strncpy(but->drawstr, md->items[i].str, sizeof(but->drawstr));
|
|
break;
|
|
}
|
|
}
|
|
|
|
menudata_free(md);
|
|
}
|
|
|
|
int ui_step_name_menu(uiBut *but, int step)
|
|
{
|
|
MenuData *md;
|
|
int value = ui_get_but_val(but);
|
|
int i;
|
|
|
|
md = decompose_menu_string(but->str);
|
|
for (i = 0; i < md->nitems; i++)
|
|
if (md->items[i].retval == value)
|
|
break;
|
|
|
|
if (step == 1) {
|
|
/* skip separators */
|
|
for (; i < md->nitems - 1; i++) {
|
|
if (md->items[i + 1].retval != -1) {
|
|
value = md->items[i + 1].retval;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (i > 0) {
|
|
/* skip separators */
|
|
for (; i > 0; i--) {
|
|
if (md->items[i - 1].retval != -1) {
|
|
value = md->items[i - 1].retval;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
menudata_free(md);
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
/******************** Creating Temporary regions ******************/
|
|
|
|
static ARegion *ui_add_temporary_region(bScreen *sc)
|
|
{
|
|
ARegion *ar;
|
|
|
|
ar = MEM_callocN(sizeof(ARegion), "area region");
|
|
BLI_addtail(&sc->regionbase, ar);
|
|
|
|
ar->regiontype = RGN_TYPE_TEMPORARY;
|
|
ar->alignment = RGN_ALIGN_FLOAT;
|
|
|
|
return ar;
|
|
}
|
|
|
|
static void ui_remove_temporary_region(bContext *C, bScreen *sc, ARegion *ar)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
if (win)
|
|
wm_draw_region_clear(win, ar);
|
|
|
|
ED_region_exit(C, ar);
|
|
BKE_area_region_free(NULL, ar); /* NULL: no spacetype */
|
|
BLI_freelinkN(&sc->regionbase, ar);
|
|
}
|
|
|
|
/************************* Creating Tooltips **********************/
|
|
|
|
typedef enum {
|
|
UI_TIP_LC_MAIN,
|
|
UI_TIP_LC_NORMAL,
|
|
UI_TIP_LC_PYTHON,
|
|
UI_TIP_LC_ALERT,
|
|
UI_TIP_LC_SUBMENU
|
|
} uiTooltipLineColor;
|
|
#define UI_TIP_LC_MAX 5
|
|
|
|
#define MAX_TOOLTIP_LINES 8
|
|
typedef struct uiTooltipData {
|
|
rcti bbox;
|
|
uiFontStyle fstyle;
|
|
char lines[MAX_TOOLTIP_LINES][512];
|
|
uiTooltipLineColor color_id[MAX_TOOLTIP_LINES];
|
|
int totline;
|
|
int toth, spaceh, lineh;
|
|
} uiTooltipData;
|
|
|
|
static void rgb_tint(float col[3],
|
|
float h, float h_strength,
|
|
float v, float v_strength)
|
|
{
|
|
float col_hsv_from[3];
|
|
float col_hsv_to[3];
|
|
|
|
rgb_to_hsv_v(col, col_hsv_from);
|
|
|
|
col_hsv_to[0] = h;
|
|
col_hsv_to[1] = h_strength;
|
|
col_hsv_to[2] = (col_hsv_from[2] * (1.0f - v_strength)) + (v * v_strength);
|
|
|
|
hsv_to_rgb_v(col_hsv_to, col);
|
|
}
|
|
|
|
static void ui_tooltip_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
|
|
{
|
|
uiTooltipData *data = ar->regiondata;
|
|
uiWidgetColors *theme = ui_tooltip_get_theme();
|
|
rcti bbox = data->bbox;
|
|
float tip_colors[UI_TIP_LC_MAX][3];
|
|
|
|
float *main_color = tip_colors[UI_TIP_LC_MAIN]; /* the color from the theme */
|
|
float *normal_color = tip_colors[UI_TIP_LC_NORMAL];
|
|
float *python_color = tip_colors[UI_TIP_LC_PYTHON];
|
|
float *alert_color = tip_colors[UI_TIP_LC_ALERT];
|
|
float *submenu_color = tip_colors[UI_TIP_LC_SUBMENU];
|
|
|
|
float background_color[3];
|
|
float tone_bg;
|
|
int i, multisample_enabled;
|
|
|
|
/* disable AA, makes widgets too blurry */
|
|
multisample_enabled = glIsEnabled(GL_MULTISAMPLE_ARB);
|
|
if (multisample_enabled)
|
|
glDisable(GL_MULTISAMPLE_ARB);
|
|
|
|
/* draw background */
|
|
ui_draw_tooltip_background(UI_GetStyle(), NULL, &bbox);
|
|
|
|
/* set background_color */
|
|
rgb_uchar_to_float(background_color, (const unsigned char *)theme->inner);
|
|
|
|
/* calculate normal_color */
|
|
rgb_uchar_to_float(main_color, (const unsigned char *)theme->text);
|
|
copy_v3_v3(normal_color, main_color);
|
|
copy_v3_v3(python_color, main_color);
|
|
copy_v3_v3(alert_color, main_color);
|
|
copy_v3_v3(submenu_color, main_color);
|
|
|
|
/* find the brightness difference between background and text colors */
|
|
|
|
tone_bg = rgb_to_grayscale(background_color);
|
|
/* tone_fg = rgb_to_grayscale(main_color); */
|
|
|
|
rgb_tint(normal_color, 0.0f, 0.0f, tone_bg, 0.3f); /* a shade darker (to bg) */
|
|
rgb_tint(python_color, 0.666f, 0.25f, tone_bg, 0.3f); /* blue */
|
|
rgb_tint(alert_color, 0.0f, 0.8f, tone_bg, 0.1f); /* bright red */
|
|
rgb_tint(submenu_color, 0.0f, 0.0f, tone_bg, 0.3f); /* a shade darker (to bg) */
|
|
|
|
/* draw text */
|
|
uiStyleFontSet(&data->fstyle);
|
|
|
|
bbox.ymax = bbox.ymax - 0.5f * (BLI_rcti_size_y(&bbox) - data->toth);
|
|
bbox.ymin = bbox.ymax - data->lineh;
|
|
|
|
for (i = 0; i < data->totline; i++) {
|
|
glColor3fv(tip_colors[data->color_id[i]]);
|
|
uiStyleFontDraw(&data->fstyle, &bbox, data->lines[i]);
|
|
bbox.ymin -= data->lineh + data->spaceh;
|
|
bbox.ymax -= data->lineh + data->spaceh;
|
|
}
|
|
|
|
if (multisample_enabled)
|
|
glEnable(GL_MULTISAMPLE_ARB);
|
|
}
|
|
|
|
static void ui_tooltip_region_free_cb(ARegion *ar)
|
|
{
|
|
uiTooltipData *data;
|
|
|
|
data = ar->regiondata;
|
|
MEM_freeN(data);
|
|
ar->regiondata = NULL;
|
|
}
|
|
|
|
ARegion *ui_tooltip_create(bContext *C, ARegion *butregion, uiBut *but)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
uiStyle *style = UI_GetStyle();
|
|
static ARegionType type;
|
|
ARegion *ar;
|
|
uiTooltipData *data;
|
|
/* IDProperty *prop;*/
|
|
char buf[512];
|
|
/* aspect values that shrink text are likely unreadable */
|
|
const float aspect = min_ff(1.0f, but->block->aspect);
|
|
float fonth, fontw;
|
|
int winx /*, winy */, ofsx, ofsy, w, h, a;
|
|
rctf rect_fl;
|
|
rcti rect_i;
|
|
|
|
uiStringInfo but_tip = {BUT_GET_TIP, NULL};
|
|
uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL};
|
|
uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL};
|
|
uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, NULL};
|
|
uiStringInfo prop_keymap = {BUT_GET_PROP_KEYMAP, NULL};
|
|
uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL};
|
|
uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL};
|
|
|
|
if (but->drawflag & UI_BUT_NO_TOOLTIP)
|
|
return NULL;
|
|
|
|
/* create tooltip data */
|
|
data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
|
|
|
|
uiButGetStrInfo(C, but, &but_tip, &enum_label, &enum_tip, &op_keymap, &prop_keymap, &rna_struct, &rna_prop, NULL);
|
|
|
|
/* special case, enum rna buttons only have enum item description,
|
|
* use general enum description too before the specific one */
|
|
|
|
/* Tip */
|
|
if (but_tip.strinfo) {
|
|
/* Expanded Bit-flag enums have a specific way to select multiple... */
|
|
if ((but->type & ROW) && but->rnaprop && RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG) {
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
|
|
"%s %s", but_tip.strinfo, IFACE_("(Shift-click to select multiple)"));
|
|
}
|
|
else {
|
|
BLI_strncpy(data->lines[data->totline], but_tip.strinfo, sizeof(data->lines[0]));
|
|
}
|
|
data->color_id[data->totline] = UI_TIP_LC_MAIN;
|
|
data->totline++;
|
|
}
|
|
/* Enum item label & tip */
|
|
if (enum_label.strinfo && enum_tip.strinfo) {
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
|
|
"%s: %s", enum_label.strinfo, enum_tip.strinfo);
|
|
data->color_id[data->totline] = UI_TIP_LC_SUBMENU;
|
|
data->totline++;
|
|
}
|
|
|
|
/* Op shortcut */
|
|
if (op_keymap.strinfo) {
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Shortcut: %s"), op_keymap.strinfo);
|
|
data->color_id[data->totline] = UI_TIP_LC_NORMAL;
|
|
data->totline++;
|
|
}
|
|
|
|
/* Property context-toggle shortcut */
|
|
if (prop_keymap.strinfo) {
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Shortcut: %s"), prop_keymap.strinfo);
|
|
data->color_id[data->totline] = UI_TIP_LC_NORMAL;
|
|
data->totline++;
|
|
}
|
|
|
|
if (ELEM3(but->type, TEX, SEARCH_MENU, SEARCH_MENU_UNLINK)) {
|
|
/* full string */
|
|
ui_get_but_string(but, buf, sizeof(buf));
|
|
if (buf[0]) {
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Value: %s"), buf);
|
|
data->color_id[data->totline] = UI_TIP_LC_NORMAL;
|
|
data->totline++;
|
|
}
|
|
}
|
|
|
|
if (but->rnaprop) {
|
|
int unit_type = uiButGetUnitType(but);
|
|
|
|
if (unit_type == PROP_UNIT_ROTATION) {
|
|
if (RNA_property_type(but->rnaprop) == PROP_FLOAT) {
|
|
float value = RNA_property_array_check(but->rnaprop) ?
|
|
RNA_property_float_get_index(&but->rnapoin, but->rnaprop, but->rnaindex) :
|
|
RNA_property_float_get(&but->rnapoin, but->rnaprop);
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Radians: %f"), value);
|
|
data->color_id[data->totline] = UI_TIP_LC_NORMAL;
|
|
data->totline++;
|
|
}
|
|
}
|
|
|
|
if (but->flag & UI_BUT_DRIVEN) {
|
|
if (ui_but_anim_expression_get(but, buf, sizeof(buf))) {
|
|
/* expression */
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Expression: %s"), buf);
|
|
data->color_id[data->totline] = UI_TIP_LC_NORMAL;
|
|
data->totline++;
|
|
}
|
|
}
|
|
|
|
if (but->rnapoin.id.data) {
|
|
ID *id = but->rnapoin.id.data;
|
|
if (id->lib) {
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Library: %s"), id->lib->name);
|
|
data->color_id[data->totline] = UI_TIP_LC_NORMAL;
|
|
data->totline++;
|
|
}
|
|
}
|
|
}
|
|
else if (but->optype) {
|
|
PointerRNA *opptr;
|
|
char *str;
|
|
opptr = uiButGetOperatorPtrRNA(but); /* allocated when needed, the button owns it */
|
|
|
|
/* so the context is passed to itemf functions (some py itemf functions use it) */
|
|
WM_operator_properties_sanitize(opptr, false);
|
|
|
|
str = WM_operator_pystring_ex(C, NULL, false, false, but->optype, opptr);
|
|
|
|
/* avoid overly verbose tips (eg, arrays of 20 layers), exact limit is arbitrary */
|
|
WM_operator_pystring_abbreviate(str, 32);
|
|
|
|
/* operator info */
|
|
if ((U.flag & USER_TOOLTIPS_PYTHON) == 0) {
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Python: %s"), str);
|
|
data->color_id[data->totline] = UI_TIP_LC_PYTHON;
|
|
data->totline++;
|
|
}
|
|
|
|
MEM_freeN(str);
|
|
|
|
/* second check if we are disabled - why */
|
|
if (but->flag & UI_BUT_DISABLED) {
|
|
const char *poll_msg;
|
|
CTX_wm_operator_poll_msg_set(C, NULL);
|
|
WM_operator_poll_context(C, but->optype, but->opcontext);
|
|
poll_msg = CTX_wm_operator_poll_msg_get(C);
|
|
if (poll_msg) {
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), TIP_("Disabled: %s"), poll_msg);
|
|
data->color_id[data->totline] = UI_TIP_LC_ALERT; /* alert */
|
|
data->totline++;
|
|
}
|
|
}
|
|
}
|
|
if ((U.flag & USER_TOOLTIPS_PYTHON) == 0 && !but->optype && rna_struct.strinfo) {
|
|
if (rna_prop.strinfo) {
|
|
/* Struct and prop */
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
|
|
TIP_("Python: %s.%s"),
|
|
rna_struct.strinfo, rna_prop.strinfo);
|
|
}
|
|
else {
|
|
/* Only struct (e.g. menus) */
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
|
|
TIP_("Python: %s"), rna_struct.strinfo);
|
|
}
|
|
data->color_id[data->totline] = UI_TIP_LC_PYTHON;
|
|
data->totline++;
|
|
|
|
if (but->rnapoin.id.data) {
|
|
/* this could get its own 'BUT_GET_...' type */
|
|
PointerRNA *ptr = &but->rnapoin;
|
|
PropertyRNA *prop = but->rnaprop;
|
|
ID *id = ptr->id.data;
|
|
|
|
char *id_path;
|
|
char *data_path = NULL;
|
|
|
|
/* never fails */
|
|
id_path = RNA_path_full_ID_py(id);
|
|
|
|
if (ptr->data && prop) {
|
|
data_path = RNA_path_from_ID_to_property(ptr, prop);
|
|
}
|
|
|
|
if (data_path) {
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
|
|
"%s.%s", /* no need to translate */
|
|
id_path, data_path);
|
|
MEM_freeN(data_path);
|
|
}
|
|
else if (prop) {
|
|
/* can't find the path. be explicit in our ignorance "..." */
|
|
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]),
|
|
"%s ... %s", /* no need to translate */
|
|
id_path, rna_prop.strinfo ? rna_prop.strinfo : RNA_property_identifier(prop));
|
|
}
|
|
MEM_freeN(id_path);
|
|
|
|
data->color_id[data->totline] = UI_TIP_LC_PYTHON;
|
|
data->totline++;
|
|
}
|
|
}
|
|
|
|
/* Free strinfo's... */
|
|
if (but_tip.strinfo)
|
|
MEM_freeN(but_tip.strinfo);
|
|
if (enum_label.strinfo)
|
|
MEM_freeN(enum_label.strinfo);
|
|
if (enum_tip.strinfo)
|
|
MEM_freeN(enum_tip.strinfo);
|
|
if (op_keymap.strinfo)
|
|
MEM_freeN(op_keymap.strinfo);
|
|
if (prop_keymap.strinfo)
|
|
MEM_freeN(prop_keymap.strinfo);
|
|
if (rna_struct.strinfo)
|
|
MEM_freeN(rna_struct.strinfo);
|
|
if (rna_prop.strinfo)
|
|
MEM_freeN(rna_prop.strinfo);
|
|
|
|
BLI_assert(data->totline < MAX_TOOLTIP_LINES);
|
|
|
|
if (data->totline == 0) {
|
|
MEM_freeN(data);
|
|
return NULL;
|
|
}
|
|
|
|
/* create area region */
|
|
ar = ui_add_temporary_region(CTX_wm_screen(C));
|
|
|
|
memset(&type, 0, sizeof(ARegionType));
|
|
type.draw = ui_tooltip_region_draw_cb;
|
|
type.free = ui_tooltip_region_free_cb;
|
|
type.regionid = RGN_TYPE_TEMPORARY;
|
|
ar->type = &type;
|
|
|
|
/* set font, get bb */
|
|
data->fstyle = style->widget; /* copy struct */
|
|
data->fstyle.align = UI_STYLE_TEXT_CENTER;
|
|
ui_fontscale(&data->fstyle.points, aspect);
|
|
|
|
uiStyleFontSet(&data->fstyle);
|
|
|
|
/* these defines tweaked depending on font */
|
|
#define TIP_MARGIN_Y (2.0f / aspect)
|
|
#define TIP_BORDER_X (16.0f / aspect)
|
|
#define TIP_BORDER_Y (6.0f / aspect)
|
|
|
|
h = BLF_height_max(data->fstyle.uifont_id);
|
|
|
|
for (a = 0, fontw = 0, fonth = 0; a < data->totline; a++) {
|
|
w = BLF_width(data->fstyle.uifont_id, data->lines[a], sizeof(data->lines[a]));
|
|
fontw = max_ff(fontw, (float)w);
|
|
fonth += (a == 0) ? h : h + TIP_MARGIN_Y;
|
|
}
|
|
|
|
//fontw *= aspect;
|
|
|
|
ar->regiondata = data;
|
|
|
|
data->toth = fonth;
|
|
data->lineh = h;
|
|
data->spaceh = TIP_MARGIN_Y;
|
|
|
|
/* compute position */
|
|
ofsx = 0; //(but->block->panel) ? but->block->panel->ofsx : 0;
|
|
ofsy = 0; //(but->block->panel) ? but->block->panel->ofsy : 0;
|
|
|
|
rect_fl.xmin = BLI_rctf_cent_x(&but->rect) + ofsx - TIP_BORDER_X;
|
|
rect_fl.xmax = rect_fl.xmin + fontw + (TIP_BORDER_X * 2);
|
|
rect_fl.ymax = but->rect.ymin + ofsy - TIP_BORDER_Y;
|
|
rect_fl.ymin = rect_fl.ymax - fonth - TIP_BORDER_Y;
|
|
|
|
#undef TIP_MARGIN_Y
|
|
#undef TIP_BORDER_X
|
|
#undef TIP_BORDER_Y
|
|
|
|
/* since the text has beens caled already, the size of tooltips is defined now */
|
|
/* here we try to figure out the right location */
|
|
if (butregion) {
|
|
float ofsx_fl = rect_fl.xmin, ofsy_fl = rect_fl.ymax;
|
|
ui_block_to_window_fl(butregion, but->block, &ofsx_fl, &ofsy_fl);
|
|
BLI_rctf_translate(&rect_fl, ofsx_fl - rect_fl.xmin, ofsy_fl - rect_fl.ymax);
|
|
}
|
|
BLI_rcti_rctf_copy(&rect_i, &rect_fl);
|
|
|
|
/* clip with window boundaries */
|
|
winx = WM_window_pixels_x(win);
|
|
|
|
if (rect_i.xmax > winx) {
|
|
/* super size */
|
|
if (rect_i.xmax > winx + rect_i.xmin) {
|
|
rect_i.xmax = winx;
|
|
rect_i.xmin = 0;
|
|
}
|
|
else {
|
|
rect_i.xmin -= rect_i.xmax - winx;
|
|
rect_i.xmax = winx;
|
|
}
|
|
}
|
|
/* ensure at least 5 px above screen bounds
|
|
* 25 is just a guess to be above the menu item */
|
|
if (rect_i.ymin < 5) {
|
|
rect_i.ymax += (-rect_i.ymin) + 30;
|
|
rect_i.ymin = 30;
|
|
}
|
|
|
|
/* widget rect, in region coords */
|
|
{
|
|
int width = UI_ThemeMenuShadowWidth();
|
|
|
|
data->bbox.xmin = width;
|
|
data->bbox.xmax = BLI_rcti_size_x(&rect_i) + width;
|
|
data->bbox.ymin = width;
|
|
data->bbox.ymax = BLI_rcti_size_y(&rect_i) + width;
|
|
|
|
/* region bigger for shadow */
|
|
ar->winrct.xmin = rect_i.xmin - width;
|
|
ar->winrct.xmax = rect_i.xmax + width;
|
|
ar->winrct.ymin = rect_i.ymin - width;
|
|
ar->winrct.ymax = rect_i.ymax + MENU_TOP;
|
|
}
|
|
|
|
/* adds subwindow */
|
|
ED_region_init(C, ar);
|
|
|
|
/* notify change and redraw */
|
|
ED_region_tag_redraw(ar);
|
|
|
|
return ar;
|
|
}
|
|
|
|
void ui_tooltip_free(bContext *C, ARegion *ar)
|
|
{
|
|
ui_remove_temporary_region(C, CTX_wm_screen(C), ar);
|
|
}
|
|
|
|
|
|
/************************* Creating Search Box **********************/
|
|
|
|
struct uiSearchItems {
|
|
int maxitem, totitem, maxstrlen;
|
|
|
|
int offset, offset_i; /* offset for inserting in array */
|
|
int more; /* flag indicating there are more items */
|
|
|
|
char **names;
|
|
void **pointers;
|
|
int *icons;
|
|
|
|
AutoComplete *autocpl;
|
|
void *active;
|
|
};
|
|
|
|
typedef struct uiSearchboxData {
|
|
rcti bbox;
|
|
uiFontStyle fstyle;
|
|
uiSearchItems items;
|
|
int active; /* index in items array */
|
|
bool noback; /* when menu opened with enough space for this */
|
|
bool preview; /* draw thumbnail previews, rather than list */
|
|
bool use_sep; /* use the UI_SEP_CHAR char for splitting shortcuts (good for operators, bad for data) */
|
|
int prv_rows, prv_cols;
|
|
} uiSearchboxData;
|
|
|
|
#define SEARCH_ITEMS 10
|
|
|
|
/* exported for use by search callbacks */
|
|
/* returns zero if nothing to add */
|
|
bool uiSearchItemAdd(uiSearchItems *items, const char *name, void *poin, int iconid)
|
|
{
|
|
/* hijack for autocomplete */
|
|
if (items->autocpl) {
|
|
autocomplete_do_name(items->autocpl, name);
|
|
return true;
|
|
}
|
|
|
|
/* hijack for finding active item */
|
|
if (items->active) {
|
|
if (poin == items->active)
|
|
items->offset_i = items->totitem;
|
|
items->totitem++;
|
|
return true;
|
|
}
|
|
|
|
if (items->totitem >= items->maxitem) {
|
|
items->more = 1;
|
|
return false;
|
|
}
|
|
|
|
/* skip first items in list */
|
|
if (items->offset_i > 0) {
|
|
items->offset_i--;
|
|
return true;
|
|
}
|
|
|
|
if (items->names)
|
|
BLI_strncpy(items->names[items->totitem], name, items->maxstrlen);
|
|
if (items->pointers)
|
|
items->pointers[items->totitem] = poin;
|
|
if (items->icons)
|
|
items->icons[items->totitem] = iconid;
|
|
|
|
items->totitem++;
|
|
|
|
return true;
|
|
}
|
|
|
|
int uiSearchBoxHeight(void)
|
|
{
|
|
return SEARCH_ITEMS * UI_UNIT_Y + 2 * MENU_TOP;
|
|
}
|
|
|
|
int uiSearchBoxWidth(void)
|
|
{
|
|
/* was hardcoded at 150 */
|
|
return 9 * UI_UNIT_X;
|
|
}
|
|
|
|
int uiSearchItemFindIndex(uiSearchItems *items, const char *name)
|
|
{
|
|
int i;
|
|
for (i = 0; i < items->totitem; i++) {
|
|
if (STREQ(name, items->names[i])) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* ar is the search box itself */
|
|
static void ui_searchbox_select(bContext *C, ARegion *ar, uiBut *but, int step)
|
|
{
|
|
uiSearchboxData *data = ar->regiondata;
|
|
|
|
/* apply step */
|
|
data->active += step;
|
|
|
|
if (data->items.totitem == 0) {
|
|
data->active = -1;
|
|
}
|
|
else if (data->active >= data->items.totitem) {
|
|
if (data->items.more) {
|
|
data->items.offset++;
|
|
data->active = data->items.totitem - 1;
|
|
ui_searchbox_update(C, ar, but, false);
|
|
}
|
|
else {
|
|
data->active = data->items.totitem - 1;
|
|
}
|
|
}
|
|
else if (data->active < 0) {
|
|
if (data->items.offset) {
|
|
data->items.offset--;
|
|
data->active = 0;
|
|
ui_searchbox_update(C, ar, but, false);
|
|
}
|
|
else if (data->active < -1)
|
|
data->active = -1;
|
|
}
|
|
|
|
ED_region_tag_redraw(ar);
|
|
}
|
|
|
|
static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr)
|
|
{
|
|
/* thumbnail preview */
|
|
if (data->preview) {
|
|
int butw = BLI_rcti_size_x(&data->bbox) / data->prv_cols;
|
|
int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_TOP) / data->prv_rows;
|
|
int row, col;
|
|
|
|
*r_rect = data->bbox;
|
|
|
|
col = itemnr % data->prv_cols;
|
|
row = itemnr / data->prv_cols;
|
|
|
|
r_rect->xmin += col * butw;
|
|
r_rect->xmax = r_rect->xmin + butw;
|
|
|
|
r_rect->ymax = data->bbox.ymax - MENU_TOP - (row * buth);
|
|
r_rect->ymin = r_rect->ymax - buth;
|
|
}
|
|
/* list view */
|
|
else {
|
|
int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_TOP) / SEARCH_ITEMS;
|
|
|
|
*r_rect = data->bbox;
|
|
r_rect->xmin = data->bbox.xmin + 3.0f;
|
|
r_rect->xmax = data->bbox.xmax - 3.0f;
|
|
|
|
r_rect->ymax = data->bbox.ymax - MENU_TOP - itemnr * buth;
|
|
r_rect->ymin = r_rect->ymax - buth;
|
|
}
|
|
|
|
}
|
|
|
|
int ui_searchbox_find_index(ARegion *ar, const char *name)
|
|
{
|
|
uiSearchboxData *data = ar->regiondata;
|
|
return uiSearchItemFindIndex(&data->items, name);
|
|
}
|
|
|
|
/* x and y in screencoords */
|
|
bool ui_searchbox_inside(ARegion *ar, int x, int y)
|
|
{
|
|
uiSearchboxData *data = ar->regiondata;
|
|
|
|
return BLI_rcti_isect_pt(&data->bbox, x - ar->winrct.xmin, y - ar->winrct.ymin);
|
|
}
|
|
|
|
/* string validated to be of correct length (but->hardmax) */
|
|
bool ui_searchbox_apply(uiBut *but, ARegion *ar)
|
|
{
|
|
uiSearchboxData *data = ar->regiondata;
|
|
|
|
but->func_arg2 = NULL;
|
|
|
|
if (data->active != -1) {
|
|
const char *name = data->items.names[data->active];
|
|
const char *name_sep = data->use_sep ? strchr(name, UI_SEP_CHAR) : NULL;
|
|
|
|
BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen);
|
|
|
|
but->func_arg2 = data->items.pointers[data->active];
|
|
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ui_searchbox_event(bContext *C, ARegion *ar, uiBut *but, const wmEvent *event)
|
|
{
|
|
uiSearchboxData *data = ar->regiondata;
|
|
int type = event->type, val = event->val;
|
|
|
|
if (type == MOUSEPAN)
|
|
ui_pan_to_scroll(event, &type, &val);
|
|
|
|
switch (type) {
|
|
case WHEELUPMOUSE:
|
|
case UPARROWKEY:
|
|
ui_searchbox_select(C, ar, but, -1);
|
|
break;
|
|
case WHEELDOWNMOUSE:
|
|
case DOWNARROWKEY:
|
|
ui_searchbox_select(C, ar, but, 1);
|
|
break;
|
|
case MOUSEMOVE:
|
|
if (BLI_rcti_isect_pt(&ar->winrct, event->x, event->y)) {
|
|
rcti rect;
|
|
int a;
|
|
|
|
for (a = 0; a < data->items.totitem; a++) {
|
|
ui_searchbox_butrect(&rect, data, a);
|
|
if (BLI_rcti_isect_pt(&rect, event->x - ar->winrct.xmin, event->y - ar->winrct.ymin)) {
|
|
if (data->active != a) {
|
|
data->active = a;
|
|
ui_searchbox_select(C, ar, but, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ar is the search box itself */
|
|
void ui_searchbox_update(bContext *C, ARegion *ar, uiBut *but, const bool reset)
|
|
{
|
|
uiSearchboxData *data = ar->regiondata;
|
|
|
|
/* reset vars */
|
|
data->items.totitem = 0;
|
|
data->items.more = 0;
|
|
if (reset == false) {
|
|
data->items.offset_i = data->items.offset;
|
|
}
|
|
else {
|
|
data->items.offset_i = data->items.offset = 0;
|
|
data->active = -1;
|
|
|
|
/* handle active */
|
|
if (but->search_func && but->func_arg2) {
|
|
data->items.active = but->func_arg2;
|
|
but->search_func(C, but->search_arg, but->editstr, &data->items);
|
|
data->items.active = NULL;
|
|
|
|
/* found active item, calculate real offset by centering it */
|
|
if (data->items.totitem) {
|
|
/* first case, begin of list */
|
|
if (data->items.offset_i < data->items.maxitem) {
|
|
data->active = data->items.offset_i;
|
|
data->items.offset_i = 0;
|
|
}
|
|
else {
|
|
/* second case, end of list */
|
|
if (data->items.totitem - data->items.offset_i <= data->items.maxitem) {
|
|
data->active = data->items.offset_i - data->items.totitem + data->items.maxitem;
|
|
data->items.offset_i = data->items.totitem - data->items.maxitem;
|
|
}
|
|
else {
|
|
/* center active item */
|
|
data->items.offset_i -= data->items.maxitem / 2;
|
|
data->active = data->items.maxitem / 2;
|
|
}
|
|
}
|
|
}
|
|
data->items.offset = data->items.offset_i;
|
|
data->items.totitem = 0;
|
|
}
|
|
}
|
|
|
|
/* callback */
|
|
if (but->search_func)
|
|
but->search_func(C, but->search_arg, but->editstr, &data->items);
|
|
|
|
/* handle case where editstr is equal to one of items */
|
|
if (reset && data->active == -1) {
|
|
int a;
|
|
|
|
for (a = 0; a < data->items.totitem; a++) {
|
|
const char *name = data->items.names[a];
|
|
const char *name_sep = data->use_sep ? strchr(name, UI_SEP_CHAR) : NULL;
|
|
if (STREQLEN(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen)) {
|
|
data->active = a;
|
|
break;
|
|
}
|
|
}
|
|
if (data->items.totitem == 1 && but->editstr[0])
|
|
data->active = 0;
|
|
}
|
|
|
|
/* validate selected item */
|
|
ui_searchbox_select(C, ar, but, 0);
|
|
|
|
ED_region_tag_redraw(ar);
|
|
}
|
|
|
|
int ui_searchbox_autocomplete(bContext *C, ARegion *ar, uiBut *but, char *str)
|
|
{
|
|
uiSearchboxData *data = ar->regiondata;
|
|
int match = AUTOCOMPLETE_NO_MATCH;
|
|
|
|
if (str[0]) {
|
|
data->items.autocpl = autocomplete_begin(str, ui_get_but_string_max_length(but));
|
|
|
|
but->search_func(C, but->search_arg, but->editstr, &data->items);
|
|
|
|
match = autocomplete_end(data->items.autocpl, str);
|
|
data->items.autocpl = NULL;
|
|
}
|
|
|
|
return match;
|
|
}
|
|
|
|
static void ui_searchbox_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
|
|
{
|
|
uiSearchboxData *data = ar->regiondata;
|
|
|
|
/* pixel space */
|
|
wmOrtho2(-0.01f, ar->winx - 0.01f, -0.01f, ar->winy - 0.01f);
|
|
|
|
if (data->noback == false)
|
|
ui_draw_search_back(NULL, NULL, &data->bbox); /* style not used yet */
|
|
|
|
/* draw text */
|
|
if (data->items.totitem) {
|
|
rcti rect;
|
|
int a;
|
|
|
|
if (data->preview) {
|
|
/* draw items */
|
|
for (a = 0; a < data->items.totitem; a++) {
|
|
ui_searchbox_butrect(&rect, data, a);
|
|
|
|
/* widget itself */
|
|
if (data->preview) {
|
|
ui_draw_preview_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a],
|
|
(a == data->active) ? UI_ACTIVE : 0);
|
|
}
|
|
else {
|
|
ui_draw_menu_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a],
|
|
(a == data->active) ? UI_ACTIVE : 0, data->use_sep);
|
|
}
|
|
}
|
|
|
|
/* indicate more */
|
|
if (data->items.more) {
|
|
ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
|
|
glEnable(GL_BLEND);
|
|
UI_icon_draw(rect.xmax - 18, rect.ymin - 7, ICON_TRIA_DOWN);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
if (data->items.offset) {
|
|
ui_searchbox_butrect(&rect, data, 0);
|
|
glEnable(GL_BLEND);
|
|
UI_icon_draw(rect.xmin, rect.ymax - 9, ICON_TRIA_UP);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
}
|
|
else {
|
|
/* draw items */
|
|
for (a = 0; a < data->items.totitem; a++) {
|
|
ui_searchbox_butrect(&rect, data, a);
|
|
|
|
/* widget itself */
|
|
ui_draw_menu_item(&data->fstyle, &rect, data->items.names[a], data->items.icons[a],
|
|
(a == data->active) ? UI_ACTIVE : 0, data->use_sep);
|
|
|
|
}
|
|
/* indicate more */
|
|
if (data->items.more) {
|
|
ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
|
|
glEnable(GL_BLEND);
|
|
UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
if (data->items.offset) {
|
|
ui_searchbox_butrect(&rect, data, 0);
|
|
glEnable(GL_BLEND);
|
|
UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ui_searchbox_region_free_cb(ARegion *ar)
|
|
{
|
|
uiSearchboxData *data = ar->regiondata;
|
|
int a;
|
|
|
|
/* free search data */
|
|
for (a = 0; a < data->items.maxitem; a++) {
|
|
MEM_freeN(data->items.names[a]);
|
|
}
|
|
MEM_freeN(data->items.names);
|
|
MEM_freeN(data->items.pointers);
|
|
MEM_freeN(data->items.icons);
|
|
|
|
MEM_freeN(data);
|
|
ar->regiondata = NULL;
|
|
}
|
|
|
|
ARegion *ui_searchbox_create(bContext *C, ARegion *butregion, uiBut *but)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
uiStyle *style = UI_GetStyle();
|
|
static ARegionType type;
|
|
ARegion *ar;
|
|
uiSearchboxData *data;
|
|
float aspect = but->block->aspect;
|
|
rctf rect_fl;
|
|
rcti rect_i;
|
|
int winx /*, winy */, ofsx, ofsy;
|
|
int i;
|
|
|
|
/* create area region */
|
|
ar = ui_add_temporary_region(CTX_wm_screen(C));
|
|
|
|
memset(&type, 0, sizeof(ARegionType));
|
|
type.draw = ui_searchbox_region_draw_cb;
|
|
type.free = ui_searchbox_region_free_cb;
|
|
type.regionid = RGN_TYPE_TEMPORARY;
|
|
ar->type = &type;
|
|
|
|
/* create searchbox data */
|
|
data = MEM_callocN(sizeof(uiSearchboxData), "uiSearchboxData");
|
|
|
|
/* set font, get bb */
|
|
data->fstyle = style->widget; /* copy struct */
|
|
data->fstyle.align = UI_STYLE_TEXT_CENTER;
|
|
ui_fontscale(&data->fstyle.points, aspect);
|
|
uiStyleFontSet(&data->fstyle);
|
|
|
|
ar->regiondata = data;
|
|
|
|
/* special case, hardcoded feature, not draw backdrop when called from menus,
|
|
* assume for design that popup already added it */
|
|
if (but->block->flag & UI_BLOCK_SEARCH_MENU)
|
|
data->noback = true;
|
|
|
|
if (but->a1 > 0 && but->a2 > 0) {
|
|
data->preview = true;
|
|
data->prv_rows = but->a1;
|
|
data->prv_cols = but->a2;
|
|
}
|
|
|
|
/* only show key shortcuts when needed (not rna buttons) [#36699] */
|
|
if (but->rnaprop == NULL) {
|
|
data->use_sep = true;
|
|
}
|
|
|
|
/* compute position */
|
|
if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
|
|
int width = UI_ThemeMenuShadowWidth();
|
|
/* this case is search menu inside other menu */
|
|
/* we copy region size */
|
|
|
|
ar->winrct = butregion->winrct;
|
|
|
|
/* widget rect, in region coords */
|
|
data->bbox.xmin = width;
|
|
data->bbox.xmax = BLI_rcti_size_x(&ar->winrct) - width;
|
|
data->bbox.ymin = width;
|
|
data->bbox.ymax = BLI_rcti_size_y(&ar->winrct) - width;
|
|
|
|
/* check if button is lower half */
|
|
if (but->rect.ymax < BLI_rctf_cent_y(&but->block->rect)) {
|
|
data->bbox.ymin += BLI_rctf_size_y(&but->rect);
|
|
}
|
|
else {
|
|
data->bbox.ymax -= BLI_rctf_size_y(&but->rect);
|
|
}
|
|
}
|
|
else {
|
|
const int searchbox_width = uiSearchBoxWidth();
|
|
const int shadow_width = UI_ThemeMenuShadowWidth();
|
|
|
|
rect_fl.xmin = but->rect.xmin - 5; /* align text with button */
|
|
rect_fl.xmax = but->rect.xmax + 5; /* symmetrical */
|
|
rect_fl.ymax = but->rect.ymin;
|
|
rect_fl.ymin = rect_fl.ymax - uiSearchBoxHeight();
|
|
|
|
ofsx = (but->block->panel) ? but->block->panel->ofsx : 0;
|
|
ofsy = (but->block->panel) ? but->block->panel->ofsy : 0;
|
|
|
|
BLI_rctf_translate(&rect_fl, ofsx, ofsy);
|
|
|
|
/* minimal width */
|
|
if (BLI_rctf_size_x(&rect_fl) < searchbox_width) {
|
|
rect_fl.xmax = rect_fl.xmin + searchbox_width;
|
|
}
|
|
|
|
/* copy to int, gets projected if possible too */
|
|
BLI_rcti_rctf_copy(&rect_i, &rect_fl);
|
|
|
|
if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
|
|
UI_view2d_to_region_no_clip(&butregion->v2d, rect_fl.xmin, rect_fl.ymin, &rect_i.xmin, &rect_i.ymin);
|
|
UI_view2d_to_region_no_clip(&butregion->v2d, rect_fl.xmax, rect_fl.ymax, &rect_i.xmax, &rect_i.ymax);
|
|
}
|
|
|
|
BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin);
|
|
|
|
winx = WM_window_pixels_x(win);
|
|
// winy = WM_window_pixels_y(win); /* UNUSED */
|
|
//wm_window_get_size(win, &winx, &winy);
|
|
|
|
if (rect_i.xmax > winx) {
|
|
/* super size */
|
|
if (rect_i.xmax > winx + rect_i.xmin) {
|
|
rect_i.xmax = winx;
|
|
rect_i.xmin = 0;
|
|
}
|
|
else {
|
|
rect_i.xmin -= rect_i.xmax - winx;
|
|
rect_i.xmax = winx;
|
|
}
|
|
}
|
|
|
|
if (rect_i.ymin < 0) {
|
|
int newy1 = but->rect.ymax + ofsy;
|
|
|
|
if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax)
|
|
UI_view2d_to_region_no_clip(&butregion->v2d, 0, newy1, NULL, &newy1);
|
|
|
|
newy1 += butregion->winrct.ymin;
|
|
|
|
rect_i.ymax = BLI_rcti_size_y(&rect_i) + newy1;
|
|
rect_i.ymin = newy1;
|
|
}
|
|
|
|
/* widget rect, in region coords */
|
|
data->bbox.xmin = shadow_width;
|
|
data->bbox.xmax = BLI_rcti_size_x(&rect_i) + shadow_width;
|
|
data->bbox.ymin = shadow_width;
|
|
data->bbox.ymax = BLI_rcti_size_y(&rect_i) + shadow_width;
|
|
|
|
/* region bigger for shadow */
|
|
ar->winrct.xmin = rect_i.xmin - shadow_width;
|
|
ar->winrct.xmax = rect_i.xmax + shadow_width;
|
|
ar->winrct.ymin = rect_i.ymin - shadow_width;
|
|
ar->winrct.ymax = rect_i.ymax;
|
|
}
|
|
|
|
/* adds subwindow */
|
|
ED_region_init(C, ar);
|
|
|
|
/* notify change and redraw */
|
|
ED_region_tag_redraw(ar);
|
|
|
|
/* prepare search data */
|
|
if (data->preview) {
|
|
data->items.maxitem = data->prv_rows * data->prv_cols;
|
|
}
|
|
else {
|
|
data->items.maxitem = SEARCH_ITEMS;
|
|
}
|
|
data->items.maxstrlen = but->hardmax;
|
|
data->items.totitem = 0;
|
|
data->items.names = MEM_callocN(data->items.maxitem * sizeof(void *), "search names");
|
|
data->items.pointers = MEM_callocN(data->items.maxitem * sizeof(void *), "search pointers");
|
|
data->items.icons = MEM_callocN(data->items.maxitem * sizeof(int), "search icons");
|
|
for (i = 0; i < data->items.maxitem; i++)
|
|
data->items.names[i] = MEM_callocN(but->hardmax + 1, "search pointers");
|
|
|
|
return ar;
|
|
}
|
|
|
|
void ui_searchbox_free(bContext *C, ARegion *ar)
|
|
{
|
|
ui_remove_temporary_region(C, CTX_wm_screen(C), ar);
|
|
}
|
|
|
|
/* sets red alert if button holds a string it can't find */
|
|
/* XXX weak: search_func adds all partial matches... */
|
|
void ui_but_search_test(uiBut *but)
|
|
{
|
|
uiSearchItems *items;
|
|
int x1;
|
|
|
|
/* possibly very large lists (such as ID datablocks) only
|
|
* only validate string RNA buts (not pointers) */
|
|
if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) {
|
|
return;
|
|
}
|
|
|
|
items = MEM_callocN(sizeof(uiSearchItems), "search items");
|
|
|
|
/* setup search struct */
|
|
items->maxitem = 10;
|
|
items->maxstrlen = 256;
|
|
items->names = MEM_callocN(items->maxitem * sizeof(void *), "search names");
|
|
for (x1 = 0; x1 < items->maxitem; x1++)
|
|
items->names[x1] = MEM_callocN(but->hardmax + 1, "search names");
|
|
|
|
but->search_func(but->block->evil_C, but->search_arg, but->drawstr, items);
|
|
|
|
/* only redalert when we are sure of it, this can miss cases when >10 matches */
|
|
if (items->totitem == 0) {
|
|
uiButSetFlag(but, UI_BUT_REDALERT);
|
|
}
|
|
else if (items->more == 0) {
|
|
if (uiSearchItemFindIndex(items, but->drawstr) == -1) {
|
|
uiButSetFlag(but, UI_BUT_REDALERT);
|
|
}
|
|
}
|
|
|
|
for (x1 = 0; x1 < items->maxitem; x1++) {
|
|
MEM_freeN(items->names[x1]);
|
|
}
|
|
MEM_freeN(items->names);
|
|
MEM_freeN(items);
|
|
}
|
|
|
|
|
|
/************************* Creating Menu Blocks **********************/
|
|
|
|
/* position block relative to but, result is in window space */
|
|
static void ui_block_position(wmWindow *window, ARegion *butregion, uiBut *but, uiBlock *block)
|
|
{
|
|
uiBut *bt;
|
|
uiSafetyRct *saferct;
|
|
rctf butrct;
|
|
/*float aspect;*/ /*UNUSED*/
|
|
int xsize, ysize, xof = 0, yof = 0, center;
|
|
short dir1 = 0, dir2 = 0;
|
|
|
|
/* transform to window coordinates, using the source button region/block */
|
|
butrct = but->rect;
|
|
|
|
ui_block_to_window_fl(butregion, but->block, &butrct.xmin, &butrct.ymin);
|
|
ui_block_to_window_fl(butregion, but->block, &butrct.xmax, &butrct.ymax);
|
|
|
|
/* widget_roundbox_set has this correction too, keep in sync */
|
|
if (but->type != PULLDOWN) {
|
|
if (but->drawflag & UI_BUT_ALIGN_TOP)
|
|
butrct.ymax += U.pixelsize;
|
|
if (but->drawflag & UI_BUT_ALIGN_LEFT)
|
|
butrct.xmin -= U.pixelsize;
|
|
}
|
|
|
|
/* calc block rect */
|
|
if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) {
|
|
if (block->buttons.first) {
|
|
BLI_rctf_init_minmax(&block->rect);
|
|
|
|
for (bt = block->buttons.first; bt; bt = bt->next) {
|
|
BLI_rctf_union(&block->rect, &bt->rect);
|
|
}
|
|
}
|
|
else {
|
|
/* we're nice and allow empty blocks too */
|
|
block->rect.xmin = block->rect.ymin = 0;
|
|
block->rect.xmax = block->rect.ymax = 20;
|
|
}
|
|
}
|
|
|
|
/* aspect = (float)(BLI_rcti_size_x(&block->rect) + 4);*/ /*UNUSED*/
|
|
ui_block_to_window_fl(butregion, but->block, &block->rect.xmin, &block->rect.ymin);
|
|
ui_block_to_window_fl(butregion, but->block, &block->rect.xmax, &block->rect.ymax);
|
|
|
|
//block->rect.xmin -= 2.0; block->rect.ymin -= 2.0;
|
|
//block->rect.xmax += 2.0; block->rect.ymax += 2.0;
|
|
|
|
xsize = BLI_rctf_size_x(&block->rect) + 0.2f * UI_UNIT_X; /* 4 for shadow */
|
|
ysize = BLI_rctf_size_y(&block->rect) + 0.2f * UI_UNIT_Y;
|
|
/* aspect /= (float)xsize;*/ /*UNUSED*/
|
|
|
|
{
|
|
int left = 0, right = 0, top = 0, down = 0;
|
|
int winx, winy;
|
|
// int offscreen;
|
|
|
|
winx = WM_window_pixels_x(window);
|
|
winy = WM_window_pixels_y(window);
|
|
// wm_window_get_size(window, &winx, &winy);
|
|
|
|
if (block->direction & UI_CENTER) center = ysize / 2;
|
|
else center = 0;
|
|
|
|
/* check if there's space at all */
|
|
if (butrct.xmin - xsize > 0.0f) left = 1;
|
|
if (butrct.xmax + xsize < winx) right = 1;
|
|
if (butrct.ymin - ysize + center > 0.0f) down = 1;
|
|
if (butrct.ymax + ysize - center < winy) top = 1;
|
|
|
|
if (top == 0 && down == 0) {
|
|
if (butrct.ymin - ysize < winy - butrct.ymax - ysize)
|
|
top = 1;
|
|
else
|
|
down = 1;
|
|
}
|
|
|
|
dir1 = block->direction & UI_DIRECTION;
|
|
|
|
/* secundary directions */
|
|
if (dir1 & (UI_TOP | UI_DOWN)) {
|
|
if (dir1 & UI_LEFT) dir2 = UI_LEFT;
|
|
else if (dir1 & UI_RIGHT) dir2 = UI_RIGHT;
|
|
dir1 &= (UI_TOP | UI_DOWN);
|
|
}
|
|
|
|
if ((dir2 == 0) && (dir1 == UI_LEFT || dir1 == UI_RIGHT)) dir2 = UI_DOWN;
|
|
if ((dir2 == 0) && (dir1 == UI_TOP || dir1 == UI_DOWN)) dir2 = UI_LEFT;
|
|
|
|
/* no space at all? don't change */
|
|
if (left || right) {
|
|
if (dir1 == UI_LEFT && left == 0) dir1 = UI_RIGHT;
|
|
if (dir1 == UI_RIGHT && right == 0) dir1 = UI_LEFT;
|
|
/* this is aligning, not append! */
|
|
if (dir2 == UI_LEFT && right == 0) dir2 = UI_RIGHT;
|
|
if (dir2 == UI_RIGHT && left == 0) dir2 = UI_LEFT;
|
|
}
|
|
if (down || top) {
|
|
if (dir1 == UI_TOP && top == 0) dir1 = UI_DOWN;
|
|
if (dir1 == UI_DOWN && down == 0) dir1 = UI_TOP;
|
|
if (dir2 == UI_TOP && top == 0) dir2 = UI_DOWN;
|
|
if (dir2 == UI_DOWN && down == 0) dir2 = UI_TOP;
|
|
}
|
|
|
|
if (dir1 == UI_LEFT) {
|
|
xof = butrct.xmin - block->rect.xmax;
|
|
if (dir2 == UI_TOP) yof = butrct.ymin - block->rect.ymin - center - MENU_PADDING;
|
|
else yof = butrct.ymax - block->rect.ymax + center + MENU_PADDING;
|
|
}
|
|
else if (dir1 == UI_RIGHT) {
|
|
xof = butrct.xmax - block->rect.xmin;
|
|
if (dir2 == UI_TOP) yof = butrct.ymin - block->rect.ymin - center - MENU_PADDING;
|
|
else yof = butrct.ymax - block->rect.ymax + center + MENU_PADDING;
|
|
}
|
|
else if (dir1 == UI_TOP) {
|
|
yof = butrct.ymax - block->rect.ymin;
|
|
if (dir2 == UI_RIGHT) xof = butrct.xmax - block->rect.xmax;
|
|
else xof = butrct.xmin - block->rect.xmin;
|
|
/* changed direction? */
|
|
if ((dir1 & block->direction) == 0) {
|
|
if (block->direction & UI_SHIFT_FLIPPED)
|
|
xof += dir2 == UI_LEFT ? 25 : -25;
|
|
uiBlockFlipOrder(block);
|
|
}
|
|
}
|
|
else if (dir1 == UI_DOWN) {
|
|
yof = butrct.ymin - block->rect.ymax;
|
|
if (dir2 == UI_RIGHT) xof = butrct.xmax - block->rect.xmax;
|
|
else xof = butrct.xmin - block->rect.xmin;
|
|
/* changed direction? */
|
|
if ((dir1 & block->direction) == 0) {
|
|
if (block->direction & UI_SHIFT_FLIPPED)
|
|
xof += dir2 == UI_LEFT ? 25 : -25;
|
|
uiBlockFlipOrder(block);
|
|
}
|
|
}
|
|
|
|
/* and now we handle the exception; no space below or to top */
|
|
if (top == 0 && down == 0) {
|
|
if (dir1 == UI_LEFT || dir1 == UI_RIGHT) {
|
|
/* align with bottom of screen */
|
|
// yof = ysize; (not with menu scrolls)
|
|
}
|
|
}
|
|
|
|
/* or no space left or right */
|
|
if (left == 0 && right == 0) {
|
|
if (dir1 == UI_TOP || dir1 == UI_DOWN) {
|
|
/* align with left size of screen */
|
|
xof = -block->rect.xmin + 5;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/* clamp to window bounds, could be made into an option if its ever annoying */
|
|
if ( (offscreen = (block->rect.ymin + yof)) < 0) yof -= offscreen; /* bottom */
|
|
else if ((offscreen = (block->rect.ymax + yof) - winy) > 0) yof -= offscreen; /* top */
|
|
if ( (offscreen = (block->rect.xmin + xof)) < 0) xof -= offscreen; /* left */
|
|
else if ((offscreen = (block->rect.xmax + xof) - winx) > 0) xof -= offscreen; /* right */
|
|
#endif
|
|
}
|
|
|
|
/* apply offset, buttons in window coords */
|
|
|
|
for (bt = block->buttons.first; bt; bt = bt->next) {
|
|
ui_block_to_window_fl(butregion, but->block, &bt->rect.xmin, &bt->rect.ymin);
|
|
ui_block_to_window_fl(butregion, but->block, &bt->rect.xmax, &bt->rect.ymax);
|
|
|
|
BLI_rctf_translate(&bt->rect, xof, yof);
|
|
|
|
bt->aspect = 1.0f;
|
|
/* ui_check_but recalculates drawstring size in pixels */
|
|
ui_check_but(bt);
|
|
}
|
|
|
|
BLI_rctf_translate(&block->rect, xof, yof);
|
|
|
|
/* safety calculus */
|
|
{
|
|
const float midx = BLI_rctf_cent_x(&butrct);
|
|
const float midy = BLI_rctf_cent_y(&butrct);
|
|
|
|
/* when you are outside parent button, safety there should be smaller */
|
|
|
|
/* parent button to left */
|
|
if (midx < block->rect.xmin) block->safety.xmin = block->rect.xmin - 3;
|
|
else block->safety.xmin = block->rect.xmin - 40;
|
|
/* parent button to right */
|
|
if (midx > block->rect.xmax) block->safety.xmax = block->rect.xmax + 3;
|
|
else block->safety.xmax = block->rect.xmax + 40;
|
|
|
|
/* parent button on bottom */
|
|
if (midy < block->rect.ymin) block->safety.ymin = block->rect.ymin - 3;
|
|
else block->safety.ymin = block->rect.ymin - 40;
|
|
/* parent button on top */
|
|
if (midy > block->rect.ymax) block->safety.ymax = block->rect.ymax + 3;
|
|
else block->safety.ymax = block->rect.ymax + 40;
|
|
|
|
/* exception for switched pulldowns... */
|
|
if (dir1 && (dir1 & block->direction) == 0) {
|
|
if (dir2 == UI_RIGHT) block->safety.xmax = block->rect.xmax + 3;
|
|
if (dir2 == UI_LEFT) block->safety.xmin = block->rect.xmin - 3;
|
|
}
|
|
block->direction = dir1;
|
|
}
|
|
|
|
/* keep a list of these, needed for pulldown menus */
|
|
saferct = MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct");
|
|
saferct->parent = butrct;
|
|
saferct->safety = block->safety;
|
|
BLI_freelistN(&block->saferct);
|
|
BLI_duplicatelist(&block->saferct, &but->block->saferct);
|
|
BLI_addhead(&block->saferct, saferct);
|
|
}
|
|
|
|
static void ui_block_region_draw(const bContext *C, ARegion *ar)
|
|
{
|
|
uiBlock *block;
|
|
|
|
for (block = ar->uiblocks.first; block; block = block->next)
|
|
uiDrawBlock(C, block);
|
|
}
|
|
|
|
static void ui_popup_block_clip(wmWindow *window, uiBlock *block)
|
|
{
|
|
uiBut *bt;
|
|
float xofs = 0.0f;
|
|
int width = UI_SCREEN_MARGIN;
|
|
int winx, winy;
|
|
|
|
if (block->flag & UI_BLOCK_NO_WIN_CLIP) {
|
|
return;
|
|
}
|
|
|
|
winx = WM_window_pixels_x(window);
|
|
winy = WM_window_pixels_y(window);
|
|
|
|
/* shift menus to right if outside of view */
|
|
if (block->rect.xmin < width) {
|
|
xofs = (width - block->rect.xmin);
|
|
block->rect.xmin += xofs;
|
|
block->rect.xmax += xofs;
|
|
}
|
|
/* or shift to left if outside of view */
|
|
if (block->rect.xmax > winx - width) {
|
|
xofs = winx - width - block->rect.xmax;
|
|
block->rect.xmin += xofs;
|
|
block->rect.xmax += xofs;
|
|
}
|
|
|
|
if (block->rect.ymin < width)
|
|
block->rect.ymin = width;
|
|
if (block->rect.ymax > winy - MENU_TOP)
|
|
block->rect.ymax = winy - MENU_TOP;
|
|
|
|
/* ensure menu items draw inside left/right boundary */
|
|
for (bt = block->buttons.first; bt; bt = bt->next) {
|
|
bt->rect.xmin += xofs;
|
|
bt->rect.xmax += xofs;
|
|
}
|
|
|
|
}
|
|
|
|
void ui_popup_block_scrolltest(uiBlock *block)
|
|
{
|
|
uiBut *bt;
|
|
|
|
block->flag &= ~(UI_BLOCK_CLIPBOTTOM | UI_BLOCK_CLIPTOP);
|
|
|
|
for (bt = block->buttons.first; bt; bt = bt->next)
|
|
bt->flag &= ~UI_SCROLLED;
|
|
|
|
if (block->buttons.first == block->buttons.last)
|
|
return;
|
|
|
|
/* mark buttons that are outside boundary */
|
|
for (bt = block->buttons.first; bt; bt = bt->next) {
|
|
if (bt->rect.ymin < block->rect.ymin) {
|
|
bt->flag |= UI_SCROLLED;
|
|
block->flag |= UI_BLOCK_CLIPBOTTOM;
|
|
}
|
|
if (bt->rect.ymax > block->rect.ymax) {
|
|
bt->flag |= UI_SCROLLED;
|
|
block->flag |= UI_BLOCK_CLIPTOP;
|
|
}
|
|
}
|
|
|
|
/* mark buttons overlapping arrows, if we have them */
|
|
for (bt = block->buttons.first; bt; bt = bt->next) {
|
|
if (block->flag & UI_BLOCK_CLIPBOTTOM) {
|
|
if (bt->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW)
|
|
bt->flag |= UI_SCROLLED;
|
|
}
|
|
if (block->flag & UI_BLOCK_CLIPTOP) {
|
|
if (bt->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW)
|
|
bt->flag |= UI_SCROLLED;
|
|
}
|
|
}
|
|
}
|
|
|
|
uiPopupBlockHandle *ui_popup_block_create(bContext *C, ARegion *butregion, uiBut *but,
|
|
uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func,
|
|
void *arg)
|
|
{
|
|
wmWindow *window = CTX_wm_window(C);
|
|
static ARegionType type;
|
|
ARegion *ar;
|
|
uiBlock *block;
|
|
uiPopupBlockHandle *handle;
|
|
uiSafetyRct *saferct;
|
|
int width = UI_ThemeMenuShadowWidth();
|
|
|
|
/* create handle */
|
|
handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
|
|
|
|
/* store context for operator */
|
|
handle->ctx_area = CTX_wm_area(C);
|
|
handle->ctx_region = CTX_wm_region(C);
|
|
|
|
/* create area region */
|
|
ar = ui_add_temporary_region(CTX_wm_screen(C));
|
|
handle->region = ar;
|
|
|
|
memset(&type, 0, sizeof(ARegionType));
|
|
type.draw = ui_block_region_draw;
|
|
type.regionid = RGN_TYPE_TEMPORARY;
|
|
ar->type = &type;
|
|
|
|
UI_add_region_handlers(&ar->handlers);
|
|
|
|
/* create ui block */
|
|
if (create_func)
|
|
block = create_func(C, handle->region, arg);
|
|
else
|
|
block = handle_create_func(C, handle, arg);
|
|
|
|
if (block->handle) {
|
|
memcpy(block->handle, handle, sizeof(uiPopupBlockHandle));
|
|
MEM_freeN(handle);
|
|
handle = block->handle;
|
|
}
|
|
else
|
|
block->handle = handle;
|
|
|
|
ar->regiondata = handle;
|
|
|
|
/* set UI_BLOCK_NUMSELECT before uiEndBlock() so we get alphanumeric keys assigned */
|
|
if (but) {
|
|
if (but->type == PULLDOWN) {
|
|
block->flag |= UI_BLOCK_NUMSELECT;
|
|
}
|
|
}
|
|
else {
|
|
block->flag |= UI_BLOCK_POPUP | UI_BLOCK_NUMSELECT;
|
|
}
|
|
|
|
block->flag |= UI_BLOCK_LOOP;
|
|
|
|
if (!block->endblock)
|
|
uiEndBlock(C, block);
|
|
|
|
/* if this is being created from a button */
|
|
if (but) {
|
|
block->aspect = but->block->aspect;
|
|
ui_block_position(window, butregion, but, block);
|
|
handle->direction = block->direction;
|
|
}
|
|
else {
|
|
/* keep a list of these, needed for pulldown menus */
|
|
saferct = MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct");
|
|
saferct->safety = block->safety;
|
|
BLI_addhead(&block->saferct, saferct);
|
|
}
|
|
|
|
/* clip block with window boundary */
|
|
ui_popup_block_clip(window, block);
|
|
|
|
/* the block and buttons were positioned in window space as in 2.4x, now
|
|
* these menu blocks are regions so we bring it back to region space.
|
|
* additionally we add some padding for the menu shadow or rounded menus */
|
|
ar->winrct.xmin = block->rect.xmin - width;
|
|
ar->winrct.xmax = block->rect.xmax + width;
|
|
ar->winrct.ymin = block->rect.ymin - width;
|
|
ar->winrct.ymax = block->rect.ymax + MENU_TOP;
|
|
|
|
ui_block_translate(block, -ar->winrct.xmin, -ar->winrct.ymin);
|
|
|
|
/* adds subwindow */
|
|
ED_region_init(C, ar);
|
|
|
|
/* checks which buttons are visible, sets flags to prevent draw (do after region init) */
|
|
ui_popup_block_scrolltest(block);
|
|
|
|
/* get winmat now that we actually have the subwindow */
|
|
wmSubWindowSet(window, ar->swinid);
|
|
|
|
wm_subwindow_getmatrix(window, ar->swinid, block->winmat);
|
|
|
|
/* notify change and redraw */
|
|
ED_region_tag_redraw(ar);
|
|
|
|
return handle;
|
|
}
|
|
|
|
void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle)
|
|
{
|
|
ui_remove_temporary_region(C, CTX_wm_screen(C), handle->region);
|
|
|
|
if (handle->scrolltimer)
|
|
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), handle->scrolltimer);
|
|
|
|
MEM_freeN(handle);
|
|
}
|
|
|
|
/***************************** Menu Button ***************************/
|
|
|
|
static void ui_block_func_MENUSTR(bContext *UNUSED(C), uiLayout *layout, void *arg_str)
|
|
{
|
|
uiBlock *block = uiLayoutGetBlock(layout);
|
|
uiPopupBlockHandle *handle = block->handle;
|
|
uiLayout *split, *column = NULL;
|
|
MenuData *md;
|
|
MenuEntry *entry;
|
|
const char *instr = arg_str;
|
|
int columns, rows, a, b;
|
|
int column_start = 0, column_end = 0;
|
|
int nbr_entries_nosepr = 0;
|
|
|
|
uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
|
|
|
|
/* compute menu data */
|
|
md = decompose_menu_string(instr);
|
|
|
|
/* Run some "tweaking" checks. */
|
|
entry = md->items;
|
|
for (a = 0; a < md->nitems; a++, entry++) {
|
|
if (entry->sepr) {
|
|
/* inconsistent, but menus with labels do not look good flipped */
|
|
if (entry->str[0]) {
|
|
block->flag |= UI_BLOCK_NO_FLIP;
|
|
nbr_entries_nosepr++;
|
|
}
|
|
/* We do not want simple separators in nbr_entries_nosepr count */
|
|
continue;
|
|
}
|
|
nbr_entries_nosepr++;
|
|
}
|
|
|
|
/* Columns and row estimation. Ignore simple separators here. */
|
|
columns = (nbr_entries_nosepr + 20) / 20;
|
|
if (columns < 1)
|
|
columns = 1;
|
|
if (columns > 8)
|
|
columns = (nbr_entries_nosepr + 25) / 25;
|
|
|
|
rows = md->nitems / columns;
|
|
if (rows < 1)
|
|
rows = 1;
|
|
while (rows * columns < md->nitems)
|
|
rows++;
|
|
|
|
/* create title */
|
|
if (md->title) {
|
|
if (md->titleicon) {
|
|
uiItemL(layout, md->title, md->titleicon);
|
|
}
|
|
else {
|
|
/* Do not use uiItemL here, as our root layout is a menu one, it will add a fake blank icon! */
|
|
uiDefBut(block, LABEL, 0, md->title, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
|
|
}
|
|
|
|
uiItemS(layout);
|
|
}
|
|
|
|
/* create items */
|
|
split = uiLayoutSplit(layout, 0.0f, false);
|
|
|
|
for (a = 0; a < md->nitems; a++) {
|
|
if (a == column_end) {
|
|
/* start new column, and find out where it ends in advance, so we
|
|
* can flip the order of items properly per column */
|
|
column_start = a;
|
|
column_end = md->nitems;
|
|
|
|
for (b = a + 1; b < md->nitems; b++) {
|
|
entry = &md->items[b];
|
|
|
|
/* new column on N rows or on separation label */
|
|
if (((b - a) % rows == 0) || (entry->sepr && entry->str[0])) {
|
|
column_end = b;
|
|
break;
|
|
}
|
|
}
|
|
|
|
column = uiLayoutColumn(split, false);
|
|
}
|
|
|
|
if (block->flag & UI_BLOCK_NO_FLIP)
|
|
entry = &md->items[a];
|
|
else
|
|
entry = &md->items[column_start + column_end - 1 - a];
|
|
|
|
if (entry->sepr) {
|
|
if (entry->str[0]) {
|
|
if (entry->icon) {
|
|
uiItemL(column, entry->str, entry->icon);
|
|
}
|
|
else {
|
|
/* Do not use uiItemL here, as our root layout is a menu one, it will add a fake blank icon! */
|
|
uiDefBut(block, LABEL, 0, entry->str, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
|
|
}
|
|
}
|
|
else {
|
|
uiItemS(column);
|
|
}
|
|
}
|
|
else if (entry->icon) {
|
|
uiDefIconTextButF(block, BUTM, B_NOP, entry->icon, entry->str, 0, 0,
|
|
UI_UNIT_X * 5, UI_UNIT_Y, &handle->retvalue, (float) entry->retval, 0.0, 0, -1, "");
|
|
}
|
|
else {
|
|
uiDefButF(block, BUTM, B_NOP, entry->str, 0, 0,
|
|
UI_UNIT_X * 5, UI_UNIT_X, &handle->retvalue, (float) entry->retval, 0.0, 0, -1, "");
|
|
}
|
|
}
|
|
|
|
menudata_free(md);
|
|
}
|
|
|
|
#if 0
|
|
static void ui_warp_pointer(int x, int y)
|
|
{
|
|
/* XXX 2.50 which function to use for this? */
|
|
/* OSX has very poor mouse-warp support, it sends events;
|
|
* this causes a menu being pressed immediately ... */
|
|
# ifndef __APPLE__
|
|
warp_pointer(x, y);
|
|
# endif
|
|
}
|
|
#endif
|
|
|
|
/********************* Color Button ****************/
|
|
|
|
/* for picker, while editing hsv */
|
|
void ui_set_but_hsv(uiBut *but)
|
|
{
|
|
float col[3];
|
|
float *hsv = ui_block_hsv_get(but->block);
|
|
|
|
hsv_to_rgb_v(hsv, col);
|
|
ui_set_but_vectorf(but, col);
|
|
}
|
|
|
|
/* also used by small picker, be careful with name checks below... */
|
|
static void ui_update_block_buts_rgb(uiBlock *block, const float rgb[3])
|
|
{
|
|
uiBut *bt;
|
|
float *hsv = ui_block_hsv_get(block);
|
|
struct ColorManagedDisplay *display = NULL;
|
|
|
|
/* this is to keep the H and S value when V is equal to zero
|
|
* and we are working in HSV mode, of course!
|
|
*/
|
|
rgb_to_hsv_compat_v(rgb, hsv);
|
|
|
|
if (block->color_profile)
|
|
display = ui_block_display_get(block);
|
|
|
|
/* this updates button strings, is hackish... but button pointers are on stack of caller function */
|
|
for (bt = block->buttons.first; bt; bt = bt->next) {
|
|
if (bt->rnaprop) {
|
|
|
|
ui_set_but_vectorf(bt, rgb);
|
|
|
|
}
|
|
else if (strcmp(bt->str, "Hex: ") == 0) {
|
|
float rgb_gamma[3];
|
|
double intpart;
|
|
char col[16];
|
|
|
|
/* Hex code is assumed to be in sRGB space (coming from other applications, web, etc) */
|
|
|
|
copy_v3_v3(rgb_gamma, rgb);
|
|
|
|
if (display) {
|
|
/* make a display version, for Hex code */
|
|
IMB_colormanagement_scene_linear_to_display_v3(rgb_gamma, display);
|
|
}
|
|
|
|
if (rgb_gamma[0] > 1.0f) rgb_gamma[0] = modf(rgb_gamma[0], &intpart);
|
|
if (rgb_gamma[1] > 1.0f) rgb_gamma[1] = modf(rgb_gamma[1], &intpart);
|
|
if (rgb_gamma[2] > 1.0f) rgb_gamma[2] = modf(rgb_gamma[2], &intpart);
|
|
|
|
BLI_snprintf(col, sizeof(col), "%02X%02X%02X",
|
|
FTOCHAR(rgb_gamma[0]), FTOCHAR(rgb_gamma[1]), FTOCHAR(rgb_gamma[2]));
|
|
|
|
strcpy(bt->poin, col);
|
|
}
|
|
else if (bt->str[1] == ' ') {
|
|
if (bt->str[0] == 'R') {
|
|
ui_set_but_val(bt, rgb[0]);
|
|
}
|
|
else if (bt->str[0] == 'G') {
|
|
ui_set_but_val(bt, rgb[1]);
|
|
}
|
|
else if (bt->str[0] == 'B') {
|
|
ui_set_but_val(bt, rgb[2]);
|
|
}
|
|
else if (bt->str[0] == 'H') {
|
|
ui_set_but_val(bt, hsv[0]);
|
|
}
|
|
else if (bt->str[0] == 'S') {
|
|
ui_set_but_val(bt, hsv[1]);
|
|
}
|
|
else if (bt->str[0] == 'V') {
|
|
ui_set_but_val(bt, hsv[2]);
|
|
}
|
|
}
|
|
|
|
ui_check_but(bt);
|
|
}
|
|
}
|
|
|
|
static void do_picker_rna_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
|
|
{
|
|
uiBut *but = (uiBut *)bt1;
|
|
uiPopupBlockHandle *popup = but->block->handle;
|
|
PropertyRNA *prop = but->rnaprop;
|
|
PointerRNA ptr = but->rnapoin;
|
|
float rgb[4];
|
|
|
|
if (prop) {
|
|
RNA_property_float_get_array(&ptr, prop, rgb);
|
|
ui_update_block_buts_rgb(but->block, rgb);
|
|
}
|
|
|
|
if (popup)
|
|
popup->menuretval = UI_RETURN_UPDATE;
|
|
}
|
|
|
|
static void do_hsv_rna_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
|
|
{
|
|
uiBut *but = (uiBut *)bt1;
|
|
uiPopupBlockHandle *popup = but->block->handle;
|
|
float rgb[3];
|
|
float *hsv = ui_block_hsv_get(but->block);
|
|
|
|
hsv_to_rgb_v(hsv, rgb);
|
|
|
|
ui_update_block_buts_rgb(but->block, rgb);
|
|
|
|
if (popup)
|
|
popup->menuretval = UI_RETURN_UPDATE;
|
|
}
|
|
|
|
static void do_hex_rna_cb(bContext *UNUSED(C), void *bt1, void *hexcl)
|
|
{
|
|
uiBut *but = (uiBut *)bt1;
|
|
uiPopupBlockHandle *popup = but->block->handle;
|
|
char *hexcol = (char *)hexcl;
|
|
float rgb[3];
|
|
|
|
hex_to_rgb(hexcol, rgb, rgb + 1, rgb + 2);
|
|
|
|
/* Hex code is assumed to be in sRGB space (coming from other applications, web, etc) */
|
|
if (but->block->color_profile) {
|
|
/* so we need to linearise it for Blender */
|
|
ui_block_to_scene_linear_v3(but->block, rgb);
|
|
}
|
|
|
|
ui_update_block_buts_rgb(but->block, rgb);
|
|
|
|
if (popup)
|
|
popup->menuretval = UI_RETURN_UPDATE;
|
|
}
|
|
|
|
static void close_popup_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
|
|
{
|
|
uiBut *but = (uiBut *)bt1;
|
|
uiPopupBlockHandle *popup = but->block->handle;
|
|
|
|
if (popup)
|
|
popup->menuretval = UI_RETURN_OK;
|
|
}
|
|
|
|
static void picker_new_hide_reveal(uiBlock *block, short colormode)
|
|
{
|
|
uiBut *bt;
|
|
|
|
/* tag buttons */
|
|
for (bt = block->buttons.first; bt; bt = bt->next) {
|
|
if (bt->func == do_picker_rna_cb && bt->type == NUMSLI && bt->rnaindex != 3) {
|
|
/* RGB sliders (color circle and alpha are always shown) */
|
|
if (colormode == 0) bt->flag &= ~UI_HIDDEN;
|
|
else bt->flag |= UI_HIDDEN;
|
|
}
|
|
else if (bt->func == do_hsv_rna_cb) {
|
|
/* HSV sliders */
|
|
if (colormode == 1) bt->flag &= ~UI_HIDDEN;
|
|
else bt->flag |= UI_HIDDEN;
|
|
}
|
|
else if (bt->func == do_hex_rna_cb || bt->type == LABEL) {
|
|
/* hex input or gamma correction status label */
|
|
if (colormode == 2) bt->flag &= ~UI_HIDDEN;
|
|
else bt->flag |= UI_HIDDEN;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_picker_new_mode_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg))
|
|
{
|
|
uiBut *bt = bt1;
|
|
short colormode = ui_get_but_val(bt);
|
|
picker_new_hide_reveal(bt->block, colormode);
|
|
}
|
|
|
|
#define PICKER_H (7.5f * U.widget_unit)
|
|
#define PICKER_W (7.5f * U.widget_unit)
|
|
#define PICKER_SPACE (0.3f * U.widget_unit)
|
|
#define PICKER_BAR (0.7f * U.widget_unit)
|
|
|
|
#define PICKER_TOTAL_W (PICKER_W + PICKER_SPACE + PICKER_BAR)
|
|
|
|
static void circle_picker(uiBlock *block, PointerRNA *ptr, PropertyRNA *prop)
|
|
{
|
|
uiBut *bt;
|
|
|
|
/* HS circle */
|
|
bt = uiDefButR_prop(block, HSVCIRCLE, 0, "", 0, 0, PICKER_H, PICKER_W, ptr, prop, -1, 0.0, 0.0, 0, 0, "Color");
|
|
uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
|
|
|
|
/* value */
|
|
bt = uiDefButR_prop(block, HSVCUBE, 0, "", PICKER_W + PICKER_SPACE, 0, PICKER_BAR, PICKER_H, ptr, prop, -1, 0.0, 0.0, UI_GRAD_V_ALT, 0, "Value");
|
|
uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
|
|
}
|
|
|
|
|
|
static void square_picker(uiBlock *block, PointerRNA *ptr, PropertyRNA *prop, int type)
|
|
{
|
|
uiBut *bt;
|
|
int bartype = type + 3;
|
|
|
|
/* HS square */
|
|
bt = uiDefButR_prop(block, HSVCUBE, 0, "", 0, PICKER_BAR + PICKER_SPACE, PICKER_TOTAL_W, PICKER_H, ptr, prop, -1, 0.0, 0.0, type, 0, "Color");
|
|
uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
|
|
|
|
/* value */
|
|
bt = uiDefButR_prop(block, HSVCUBE, 0, "", 0, 0, PICKER_TOTAL_W, PICKER_BAR, ptr, prop, -1, 0.0, 0.0, bartype, 0, "Value");
|
|
uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
|
|
}
|
|
|
|
|
|
/* a HS circle, V slider, rgb/hsv/hex sliders */
|
|
static void uiBlockPicker(uiBlock *block, float rgba[4], PointerRNA *ptr, PropertyRNA *prop, bool show_picker)
|
|
{
|
|
static short colormode = 0; /* temp? 0=rgb, 1=hsv, 2=hex */
|
|
uiBut *bt;
|
|
int width, butwidth;
|
|
static char tip[50];
|
|
static char hexcol[128];
|
|
float rgb_gamma[3];
|
|
float softmin, softmax, hardmin, hardmax, step, precision;
|
|
float *hsv = ui_block_hsv_get(block);
|
|
int yco;
|
|
|
|
ui_block_hsv_get(block);
|
|
|
|
width = PICKER_TOTAL_W;
|
|
butwidth = width - 1.5f * UI_UNIT_X;
|
|
|
|
/* existence of profile means storage is in linear color space, with display correction */
|
|
/* XXX That tip message is not use anywhere! */
|
|
if (!block->color_profile) {
|
|
BLI_strncpy(tip, N_("Value in Display Color Space"), sizeof(tip));
|
|
copy_v3_v3(rgb_gamma, rgba);
|
|
}
|
|
else {
|
|
BLI_strncpy(tip, N_("Value in Linear RGB Color Space"), sizeof(tip));
|
|
|
|
/* make a display version, for Hex code */
|
|
copy_v3_v3(rgb_gamma, rgba);
|
|
ui_block_to_display_space_v3(block, rgb_gamma);
|
|
}
|
|
|
|
/* sneaky way to check for alpha */
|
|
rgba[3] = FLT_MAX;
|
|
|
|
RNA_property_float_ui_range(ptr, prop, &softmin, &softmax, &step, &precision);
|
|
RNA_property_float_range(ptr, prop, &hardmin, &hardmax);
|
|
RNA_property_float_get_array(ptr, prop, rgba);
|
|
|
|
switch (U.color_picker_type) {
|
|
case USER_CP_CIRCLE:
|
|
circle_picker(block, ptr, prop);
|
|
break;
|
|
case USER_CP_SQUARE_SV:
|
|
square_picker(block, ptr, prop, UI_GRAD_SV);
|
|
break;
|
|
case USER_CP_SQUARE_HS:
|
|
square_picker(block, ptr, prop, UI_GRAD_HS);
|
|
break;
|
|
case USER_CP_SQUARE_HV:
|
|
square_picker(block, ptr, prop, UI_GRAD_HV);
|
|
break;
|
|
}
|
|
|
|
/* mode */
|
|
yco = -1.5f * UI_UNIT_Y;
|
|
uiBlockBeginAlign(block);
|
|
bt = uiDefButS(block, ROW, 0, IFACE_("RGB"), 0, yco, width / 3, UI_UNIT_Y, &colormode, 0.0, 0.0, 0, 0, "");
|
|
uiButSetFunc(bt, do_picker_new_mode_cb, bt, NULL);
|
|
bt = uiDefButS(block, ROW, 0, IFACE_("HSV"), width / 3, yco, width / 3, UI_UNIT_Y, &colormode, 0.0, 1.0, 0, 0, "");
|
|
uiButSetFunc(bt, do_picker_new_mode_cb, bt, NULL);
|
|
bt = uiDefButS(block, ROW, 0, IFACE_("Hex"), 2 * width / 3, yco, width / 3, UI_UNIT_Y, &colormode, 0.0, 2.0, 0, 0, "");
|
|
uiButSetFunc(bt, do_picker_new_mode_cb, bt, NULL);
|
|
uiBlockEndAlign(block);
|
|
|
|
yco = -3.0f * UI_UNIT_Y;
|
|
if (show_picker) {
|
|
bt = uiDefIconButO(block, BUT, "UI_OT_eyedropper_color", WM_OP_INVOKE_DEFAULT, ICON_EYEDROPPER, butwidth + 10, yco, UI_UNIT_X, UI_UNIT_Y, NULL);
|
|
uiButSetFunc(bt, close_popup_cb, bt, NULL);
|
|
}
|
|
|
|
/* RGB values */
|
|
uiBlockBeginAlign(block);
|
|
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"));
|
|
uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
|
|
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"));
|
|
uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
|
|
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"));
|
|
uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
|
|
|
|
/* could use uiItemFullR(col, ptr, prop, -1, 0, UI_ITEM_R_EXPAND|UI_ITEM_R_SLIDER, "", ICON_NONE);
|
|
* but need to use uiButSetFunc for updating other fake buttons */
|
|
|
|
/* HSV values */
|
|
yco = -3.0f * UI_UNIT_Y;
|
|
uiBlockBeginAlign(block);
|
|
bt = uiDefButF(block, NUMSLI, 0, IFACE_("H:"), 0, yco, butwidth, UI_UNIT_Y, hsv, 0.0, 1.0, 10, 3, TIP_("Hue"));
|
|
uiButSetFunc(bt, do_hsv_rna_cb, bt, hsv);
|
|
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"));
|
|
uiButSetFunc(bt, do_hsv_rna_cb, bt, hsv);
|
|
bt = uiDefButF(block, NUMSLI, 0, IFACE_("V:"), 0, yco -= UI_UNIT_Y, butwidth, UI_UNIT_Y, hsv + 2, 0.0, softmax, 10, 3, TIP_("Value"));
|
|
bt->hardmax = hardmax; /* not common but rgb may be over 1.0 */
|
|
uiButSetFunc(bt, do_hsv_rna_cb, bt, hsv);
|
|
uiBlockEndAlign(block);
|
|
|
|
if (rgba[3] != FLT_MAX) {
|
|
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, 3, TIP_("Alpha"));
|
|
uiButSetFunc(bt, do_picker_rna_cb, bt, NULL);
|
|
}
|
|
else {
|
|
rgba[3] = 1.0f;
|
|
}
|
|
|
|
BLI_snprintf(hexcol, sizeof(hexcol), "%02X%02X%02X", FTOCHAR(rgb_gamma[0]), FTOCHAR(rgb_gamma[1]), FTOCHAR(rgb_gamma[2]));
|
|
|
|
yco = -3.0f * UI_UNIT_Y;
|
|
bt = uiDefBut(block, TEX, 0, IFACE_("Hex: "), 0, yco, butwidth, UI_UNIT_Y, hexcol, 0, 8, 0, 0, TIP_("Hex triplet for color (#RRGGBB)"));
|
|
uiButSetFunc(bt, do_hex_rna_cb, bt, hexcol);
|
|
uiDefBut(block, LABEL, 0, IFACE_("(Gamma Corrected)"), 0, yco - UI_UNIT_Y, butwidth, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
|
|
|
|
rgb_to_hsv_v(rgba, hsv);
|
|
|
|
picker_new_hide_reveal(block, colormode);
|
|
}
|
|
|
|
|
|
static int ui_picker_small_wheel_cb(const bContext *UNUSED(C), uiBlock *block, const wmEvent *event)
|
|
{
|
|
float add = 0.0f;
|
|
|
|
if (event->type == WHEELUPMOUSE)
|
|
add = 0.05f;
|
|
else if (event->type == WHEELDOWNMOUSE)
|
|
add = -0.05f;
|
|
|
|
if (add != 0.0f) {
|
|
uiBut *but;
|
|
|
|
for (but = block->buttons.first; but; but = but->next) {
|
|
if (but->type == HSVCUBE && but->active == NULL) {
|
|
uiPopupBlockHandle *popup = block->handle;
|
|
float rgb[3];
|
|
float *hsv = ui_block_hsv_get(block);
|
|
|
|
ui_get_but_vectorf(but, rgb);
|
|
|
|
rgb_to_hsv_compat_v(rgb, hsv);
|
|
hsv[2] = CLAMPIS(hsv[2] + add, 0.0f, 1.0f);
|
|
hsv_to_rgb_v(hsv, rgb);
|
|
|
|
ui_set_but_vectorf(but, rgb);
|
|
|
|
ui_update_block_buts_rgb(block, rgb);
|
|
if (popup)
|
|
popup->menuretval = UI_RETURN_UPDATE;
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uiBlock *ui_block_func_COLOR(bContext *C, uiPopupBlockHandle *handle, void *arg_but)
|
|
{
|
|
uiBut *but = arg_but;
|
|
uiBlock *block;
|
|
bool show_picker = true;
|
|
|
|
block = uiBeginBlock(C, handle->region, __func__, UI_EMBOSS);
|
|
|
|
if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
|
|
block->color_profile = false;
|
|
}
|
|
|
|
if (but->block) {
|
|
/* if color block is invoked from a popup we wouldn't be able to set color properly
|
|
* this is because color picker will close popups first and then will try to figure
|
|
* out active button RNA, and of course it'll fail
|
|
*/
|
|
show_picker = (but->block->flag & UI_BLOCK_POPUP) == 0;
|
|
}
|
|
|
|
copy_v3_v3(handle->retvec, but->editvec);
|
|
|
|
uiBlockPicker(block, handle->retvec, &but->rnapoin, but->rnaprop, show_picker);
|
|
|
|
block->flag = UI_BLOCK_LOOP | UI_BLOCK_REDRAW | UI_BLOCK_KEEP_OPEN | UI_BLOCK_OUT_1 | UI_BLOCK_MOVEMOUSE_QUIT;
|
|
uiBoundsBlock(block, 0.5 * UI_UNIT_X);
|
|
|
|
block->block_event_func = ui_picker_small_wheel_cb;
|
|
|
|
/* and lets go */
|
|
block->direction = UI_TOP;
|
|
|
|
return block;
|
|
}
|
|
|
|
/************************ Popup Menu Memory ****************************/
|
|
|
|
static int ui_popup_string_hash(const char *str)
|
|
{
|
|
/* sometimes button contains hotkey, sometimes not, strip for proper compare */
|
|
int hash;
|
|
char *delimit = strchr(str, UI_SEP_CHAR);
|
|
|
|
if (delimit) *delimit = '\0';
|
|
hash = BLI_ghashutil_strhash(str);
|
|
if (delimit) *delimit = UI_SEP_CHAR;
|
|
|
|
return hash;
|
|
}
|
|
|
|
static int ui_popup_menu_hash(const char *str)
|
|
{
|
|
return BLI_ghashutil_strhash(str);
|
|
}
|
|
|
|
/* but == NULL read, otherwise set */
|
|
uiBut *ui_popup_menu_memory(uiBlock *block, uiBut *but)
|
|
{
|
|
static int mem[256], first = 1;
|
|
int hash = block->puphash;
|
|
|
|
if (first) {
|
|
/* init */
|
|
memset(mem, -1, sizeof(mem));
|
|
first = 0;
|
|
}
|
|
|
|
if (but) {
|
|
/* set */
|
|
mem[hash & 255] = ui_popup_string_hash(but->str);
|
|
return NULL;
|
|
}
|
|
else {
|
|
/* get */
|
|
for (but = block->buttons.first; but; but = but->next)
|
|
if (ui_popup_string_hash(but->str) == mem[hash & 255])
|
|
return but;
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/******************** Popup Menu with callback or string **********************/
|
|
|
|
struct uiPopupMenu {
|
|
uiBlock *block;
|
|
uiLayout *layout;
|
|
uiBut *but;
|
|
|
|
int mx, my;
|
|
bool popup, slideout;
|
|
|
|
uiMenuCreateFunc menu_func;
|
|
void *menu_arg;
|
|
};
|
|
|
|
static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup)
|
|
{
|
|
uiBlock *block;
|
|
uiBut *bt;
|
|
uiPopupMenu *pup = arg_pup;
|
|
int offset[2], direction, minwidth, width, height, flip;
|
|
|
|
if (pup->menu_func) {
|
|
pup->block->handle = handle;
|
|
pup->menu_func(C, pup->layout, pup->menu_arg);
|
|
pup->block->handle = NULL;
|
|
}
|
|
|
|
if (pup->but) {
|
|
/* minimum width to enforece */
|
|
minwidth = BLI_rctf_size_x(&pup->but->rect);
|
|
|
|
if (pup->but->type == PULLDOWN || pup->but->menu_create_func) {
|
|
direction = UI_DOWN;
|
|
flip = 1;
|
|
}
|
|
else {
|
|
direction = UI_TOP;
|
|
flip = 0;
|
|
}
|
|
}
|
|
else {
|
|
minwidth = 50;
|
|
direction = UI_DOWN;
|
|
flip = 1;
|
|
}
|
|
|
|
block = pup->block;
|
|
|
|
/* in some cases we create the block before the region,
|
|
* so we set it delayed here if necessary */
|
|
if (BLI_findindex(&handle->region->uiblocks, block) == -1)
|
|
uiBlockSetRegion(block, handle->region);
|
|
|
|
block->direction = direction;
|
|
|
|
uiBlockLayoutResolve(block, &width, &height);
|
|
|
|
uiBlockSetFlag(block, UI_BLOCK_MOVEMOUSE_QUIT);
|
|
|
|
if (pup->popup) {
|
|
uiBlockSetFlag(block, UI_BLOCK_LOOP | UI_BLOCK_REDRAW | UI_BLOCK_NUMSELECT);
|
|
uiBlockSetDirection(block, direction);
|
|
|
|
/* offset the mouse position, possibly based on earlier selection */
|
|
if ((block->flag & UI_BLOCK_POPUP_MEMORY) &&
|
|
(bt = ui_popup_menu_memory(block, NULL)))
|
|
{
|
|
/* position mouse on last clicked item, at 0.8*width of the
|
|
* button, so it doesn't overlap the text too much, also note
|
|
* the offset is negative because we are inverse moving the
|
|
* block to be under the mouse */
|
|
offset[0] = -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect));
|
|
offset[1] = -(bt->rect.ymin + 0.5f * UI_UNIT_Y);
|
|
}
|
|
else {
|
|
/* position mouse at 0.8*width of the button and below the tile
|
|
* on the first item */
|
|
offset[0] = 0;
|
|
for (bt = block->buttons.first; bt; bt = bt->next)
|
|
offset[0] = min_ii(offset[0], -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect)));
|
|
|
|
offset[1] = 2.1 * UI_UNIT_Y;
|
|
}
|
|
|
|
block->minbounds = minwidth;
|
|
uiMenuPopupBoundsBlock(block, 1, offset[0], offset[1]);
|
|
}
|
|
else {
|
|
/* for a header menu we set the direction automatic */
|
|
if (!pup->slideout && flip) {
|
|
ScrArea *sa = CTX_wm_area(C);
|
|
if (sa && sa->headertype == HEADERDOWN) {
|
|
ARegion *ar = CTX_wm_region(C);
|
|
if (ar && ar->regiontype == RGN_TYPE_HEADER) {
|
|
uiBlockSetDirection(block, UI_TOP);
|
|
uiBlockFlipOrder(block);
|
|
}
|
|
}
|
|
}
|
|
|
|
block->minbounds = minwidth;
|
|
uiTextBoundsBlock(block, 3.0f * UI_UNIT_X);
|
|
}
|
|
|
|
/* if menu slides out of other menu, override direction */
|
|
if (pup->slideout)
|
|
uiBlockSetDirection(block, UI_RIGHT);
|
|
|
|
uiEndBlock(C, block);
|
|
|
|
return pup->block;
|
|
}
|
|
|
|
uiPopupBlockHandle *ui_popup_menu_create(bContext *C, ARegion *butregion, uiBut *but,
|
|
uiMenuCreateFunc menu_func, void *arg, char *str)
|
|
{
|
|
wmWindow *window = CTX_wm_window(C);
|
|
uiStyle *style = UI_GetStyleDraw();
|
|
uiPopupBlockHandle *handle;
|
|
uiPopupMenu *pup;
|
|
|
|
pup = MEM_callocN(sizeof(uiPopupMenu), __func__);
|
|
pup->block = uiBeginBlock(C, NULL, __func__, UI_EMBOSSP);
|
|
pup->block->flag |= UI_BLOCK_NUMSELECT; /* default menus to numselect */
|
|
pup->layout = uiBlockLayout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, MENU_PADDING, style);
|
|
pup->slideout = but ? ui_block_is_menu(but->block) : false;
|
|
pup->but = but;
|
|
uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN);
|
|
|
|
if (!but) {
|
|
/* no button to start from, means we are a popup */
|
|
pup->mx = window->eventstate->x;
|
|
pup->my = window->eventstate->y;
|
|
pup->popup = true;
|
|
pup->block->flag |= UI_BLOCK_NO_FLIP;
|
|
}
|
|
/* some enums reversing is strange, currently we have no good way to
|
|
* reverse some enum's but not others, so reverse all so the first menu
|
|
* items are always close to the mouse cursor */
|
|
else {
|
|
#if 0
|
|
/* if this is an rna button then we can assume its an enum
|
|
* flipping enums is generally not good since the order can be
|
|
* important [#28786] */
|
|
if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) {
|
|
pup->block->flag |= UI_BLOCK_NO_FLIP;
|
|
}
|
|
#endif
|
|
if (but->context)
|
|
uiLayoutContextCopy(pup->layout, but->context);
|
|
}
|
|
|
|
if (str) {
|
|
/* menu is created from a string */
|
|
pup->menu_func = ui_block_func_MENUSTR;
|
|
pup->menu_arg = str;
|
|
}
|
|
else {
|
|
/* menu is created from a callback */
|
|
pup->menu_func = menu_func;
|
|
pup->menu_arg = arg;
|
|
}
|
|
|
|
handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup);
|
|
|
|
if (!but) {
|
|
handle->popup = true;
|
|
|
|
UI_add_popup_handlers(C, &window->modalhandlers, handle);
|
|
WM_event_add_mousemove(C);
|
|
}
|
|
|
|
MEM_freeN(pup);
|
|
|
|
return handle;
|
|
}
|
|
|
|
/******************** Popup Menu API with begin and end ***********************/
|
|
|
|
/* only return handler, and set optional title */
|
|
uiPopupMenu *uiPupMenuBegin(bContext *C, const char *title, int icon)
|
|
{
|
|
uiStyle *style = UI_GetStyleDraw();
|
|
uiPopupMenu *pup = MEM_callocN(sizeof(uiPopupMenu), "popup menu");
|
|
uiBut *but;
|
|
|
|
pup->block = uiBeginBlock(C, NULL, __func__, UI_EMBOSSP);
|
|
pup->block->flag |= UI_BLOCK_POPUP_MEMORY;
|
|
pup->block->puphash = ui_popup_menu_hash(title);
|
|
pup->layout = uiBlockLayout(pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, MENU_PADDING, style);
|
|
|
|
/* note, this intentionally differs from the menu & submenu default because many operators
|
|
* use popups like this to select one of their options - where having invoke doesn't make sense */
|
|
uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN);
|
|
|
|
/* create in advance so we can let buttons point to retval already */
|
|
pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle");
|
|
|
|
/* create title button */
|
|
if (title[0]) {
|
|
char titlestr[256];
|
|
|
|
if (icon) {
|
|
BLI_snprintf(titlestr, sizeof(titlestr), " %s", title);
|
|
uiDefIconTextBut(pup->block, LABEL, 0, icon, titlestr, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
|
|
}
|
|
else {
|
|
but = uiDefBut(pup->block, LABEL, 0, title, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
|
|
but->drawflag = UI_BUT_TEXT_LEFT;
|
|
}
|
|
|
|
uiItemS(pup->layout);
|
|
}
|
|
|
|
return pup;
|
|
}
|
|
|
|
/* set the whole structure to work */
|
|
void uiPupMenuEnd(bContext *C, uiPopupMenu *pup)
|
|
{
|
|
wmWindow *window = CTX_wm_window(C);
|
|
uiPopupBlockHandle *menu;
|
|
|
|
pup->popup = true;
|
|
pup->mx = window->eventstate->x;
|
|
pup->my = window->eventstate->y;
|
|
|
|
menu = ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_POPUP, pup);
|
|
menu->popup = true;
|
|
|
|
UI_add_popup_handlers(C, &window->modalhandlers, menu);
|
|
WM_event_add_mousemove(C);
|
|
|
|
MEM_freeN(pup);
|
|
}
|
|
|
|
uiLayout *uiPupMenuLayout(uiPopupMenu *pup)
|
|
{
|
|
return pup->layout;
|
|
}
|
|
|
|
/*************************** Standard Popup Menus ****************************/
|
|
|
|
static void operator_name_cb(bContext *C, void *arg, int retval)
|
|
{
|
|
const char *opname = arg;
|
|
|
|
if (opname && retval > 0)
|
|
WM_operator_name_call(C, opname, WM_OP_EXEC_DEFAULT, NULL);
|
|
}
|
|
|
|
static void operator_cb(bContext *C, void *arg, int retval)
|
|
{
|
|
wmOperator *op = arg;
|
|
|
|
if (op && retval > 0)
|
|
WM_operator_call(C, op);
|
|
else
|
|
WM_operator_free(op);
|
|
}
|
|
|
|
static void confirm_cancel_operator(bContext *UNUSED(C), void *opv)
|
|
{
|
|
WM_operator_free(opv);
|
|
}
|
|
|
|
static void vconfirm_opname(bContext *C, const char *opname, const char *title,
|
|
const char *itemfmt, va_list ap) ATTR_PRINTF_FORMAT(4, 0);
|
|
static void vconfirm_opname(bContext *C, const char *opname, const char *title,
|
|
const char *itemfmt, va_list ap)
|
|
{
|
|
uiPopupBlockHandle *handle;
|
|
char *s, buf[512];
|
|
|
|
s = buf;
|
|
if (title) s += sprintf(s, "%s%%t|", title);
|
|
vsnprintf(s, sizeof(buf) - (s - buf), itemfmt, ap);
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
|
|
handle = ui_popup_menu_create(C, NULL, NULL, NULL, NULL, buf);
|
|
|
|
handle->popup_func = operator_name_cb;
|
|
handle->popup_arg = (void *)opname;
|
|
}
|
|
|
|
static void confirm_operator(bContext *C, wmOperator *op, const char *title, const char *item)
|
|
{
|
|
uiPopupBlockHandle *handle;
|
|
char *s, buf[512];
|
|
|
|
s = buf;
|
|
if (title) s += BLI_snprintf(s, sizeof(buf), "%s%%t|%s", title, item);
|
|
(void)s;
|
|
|
|
handle = ui_popup_menu_create(C, NULL, NULL, NULL, NULL, buf);
|
|
|
|
handle->popup_func = operator_cb;
|
|
handle->popup_arg = op;
|
|
handle->cancel_func = confirm_cancel_operator;
|
|
}
|
|
|
|
void uiPupMenuOkee(bContext *C, const char *opname, const char *str, ...)
|
|
{
|
|
va_list ap;
|
|
char titlestr[256];
|
|
|
|
BLI_snprintf(titlestr, sizeof(titlestr), IFACE_("OK? %%i%d"), ICON_QUESTION);
|
|
|
|
va_start(ap, str);
|
|
vconfirm_opname(C, opname, titlestr, str, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/* note, only call this is the file exists,
|
|
* the case where the file does not exist so can be saved without a
|
|
* popup must be checked for already, since saving from here
|
|
* will free the operator which will break invoke().
|
|
* The operator state for this is implicitly OPERATOR_RUNNING_MODAL */
|
|
void uiPupMenuSaveOver(bContext *C, wmOperator *op, const char *filename)
|
|
{
|
|
confirm_operator(C, op, IFACE_("Save Over?"), filename);
|
|
}
|
|
|
|
void uiPupMenuNotice(bContext *C, const char *str, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, str);
|
|
vconfirm_opname(C, NULL, NULL, str, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void uiPupMenuError(bContext *C, const char *str, ...)
|
|
{
|
|
va_list ap;
|
|
char nfmt[256];
|
|
char titlestr[256];
|
|
|
|
BLI_snprintf(titlestr, sizeof(titlestr), IFACE_("Error %%i%d"), ICON_ERROR);
|
|
|
|
BLI_strncpy(nfmt, str, sizeof(nfmt));
|
|
|
|
va_start(ap, str);
|
|
vconfirm_opname(C, NULL, titlestr, nfmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void uiPupMenuReports(bContext *C, ReportList *reports)
|
|
{
|
|
Report *report;
|
|
DynStr *ds;
|
|
char *str;
|
|
|
|
if (!reports || !reports->list.first)
|
|
return;
|
|
if (!CTX_wm_window(C))
|
|
return;
|
|
|
|
ds = BLI_dynstr_new();
|
|
|
|
for (report = reports->list.first; report; report = report->next) {
|
|
if (report->type < reports->printlevel) {
|
|
/* pass */
|
|
}
|
|
else if (report->type >= RPT_ERROR) {
|
|
BLI_dynstr_appendf(ds, IFACE_("Error %%i%d%%t|%s"), ICON_ERROR, report->message);
|
|
}
|
|
else if (report->type >= RPT_WARNING) {
|
|
BLI_dynstr_appendf(ds, IFACE_("Warning %%i%d%%t|%s"), ICON_ERROR, report->message);
|
|
}
|
|
else if (report->type >= RPT_INFO) {
|
|
BLI_dynstr_appendf(ds, IFACE_("Info %%i%d%%t|%s"), ICON_INFO, report->message);
|
|
}
|
|
}
|
|
|
|
str = BLI_dynstr_get_cstring(ds);
|
|
if (str[0] != '\0')
|
|
ui_popup_menu_create(C, NULL, NULL, NULL, NULL, str);
|
|
MEM_freeN(str);
|
|
|
|
BLI_dynstr_free(ds);
|
|
}
|
|
|
|
void uiPupMenuInvoke(bContext *C, const char *idname)
|
|
{
|
|
uiPopupMenu *pup;
|
|
uiLayout *layout;
|
|
Menu menu;
|
|
MenuType *mt = WM_menutype_find(idname, true);
|
|
|
|
if (mt == NULL) {
|
|
printf("%s: named menu \"%s\" not found\n", __func__, idname);
|
|
return;
|
|
}
|
|
|
|
if (mt->poll && mt->poll(C, mt) == 0)
|
|
return;
|
|
|
|
pup = uiPupMenuBegin(C, IFACE_(mt->label), ICON_NONE);
|
|
layout = uiPupMenuLayout(pup);
|
|
|
|
menu.layout = layout;
|
|
menu.type = mt;
|
|
|
|
if (G.debug & G_DEBUG_WM) {
|
|
printf("%s: opening menu \"%s\"\n", __func__, idname);
|
|
}
|
|
|
|
mt->draw(C, &menu);
|
|
|
|
uiPupMenuEnd(C, pup);
|
|
}
|
|
|
|
|
|
/*************************** Popup Block API **************************/
|
|
|
|
void uiPupBlockO(bContext *C, uiBlockCreateFunc func, void *arg, const char *opname, int opcontext)
|
|
{
|
|
wmWindow *window = CTX_wm_window(C);
|
|
uiPopupBlockHandle *handle;
|
|
|
|
handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
|
|
handle->popup = true;
|
|
handle->optype = (opname) ? WM_operatortype_find(opname, 0) : NULL;
|
|
handle->opcontext = opcontext;
|
|
|
|
UI_add_popup_handlers(C, &window->modalhandlers, handle);
|
|
WM_event_add_mousemove(C);
|
|
}
|
|
|
|
void uiPupBlock(bContext *C, uiBlockCreateFunc func, void *arg)
|
|
{
|
|
uiPupBlockO(C, func, arg, NULL, 0);
|
|
}
|
|
|
|
void uiPupBlockEx(bContext *C, uiBlockCreateFunc func, uiBlockHandleFunc popup_func, uiBlockCancelFunc cancel_func, void *arg)
|
|
{
|
|
wmWindow *window = CTX_wm_window(C);
|
|
uiPopupBlockHandle *handle;
|
|
|
|
handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg);
|
|
handle->popup = true;
|
|
handle->retvalue = 1;
|
|
|
|
handle->popup_arg = arg;
|
|
handle->popup_func = popup_func;
|
|
handle->cancel_func = cancel_func;
|
|
// handle->opcontext = opcontext;
|
|
|
|
UI_add_popup_handlers(C, &window->modalhandlers, handle);
|
|
WM_event_add_mousemove(C);
|
|
}
|
|
|
|
#if 0 /* UNUSED */
|
|
void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext)
|
|
{
|
|
wmWindow *window = CTX_wm_window(C);
|
|
uiPopupBlockHandle *handle;
|
|
|
|
handle = ui_popup_block_create(C, NULL, NULL, func, NULL, op);
|
|
handle->popup = 1;
|
|
handle->retvalue = 1;
|
|
|
|
handle->popup_arg = op;
|
|
handle->popup_func = operator_cb;
|
|
handle->cancel_func = confirm_cancel_operator;
|
|
handle->opcontext = opcontext;
|
|
|
|
UI_add_popup_handlers(C, &window->modalhandlers, handle);
|
|
WM_event_add_mousemove(C);
|
|
}
|
|
#endif
|
|
|
|
void uiPupBlockClose(bContext *C, uiBlock *block)
|
|
{
|
|
if (block->handle) {
|
|
wmWindow *win = CTX_wm_window(C);
|
|
|
|
/* if loading new .blend while popup is open, window will be NULL */
|
|
if (win) {
|
|
UI_remove_popup_handlers(&win->modalhandlers, block->handle);
|
|
ui_popup_block_free(C, block->handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
float *ui_block_hsv_get(uiBlock *block)
|
|
{
|
|
return block->_hsv;
|
|
}
|