Last active
April 15, 2024 03:00
-
-
Save Kielan/57e224878aa744b7d6f5d1cec087f4c0 to your computer and use it in GitHub Desktop.
Ui btn cxt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* main/source/dune/editors/interface/interface_templates.cc */ | |
#include <ctype.h> | |
#include <stddef.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include "mem_guardedalloc.h" | |
#include "types_brush.h" | |
#include "types_cachefile.h" | |
#include "types_constraint.h" | |
#include "types_curveprofile.h" | |
#include "types_pen_mod.h" | |
#include "types_node.h" | |
#include "types_object_force.h" | |
#include "types_object.h" | |
#include "types_scene.h" | |
#include "types_shader_fx.h" | |
#include "types_texture.h" | |
#include "lib_alloca.h" | |
#include "lib_fnmatch.h" | |
#include "lib_list.h" | |
#include "lib_math.h" | |
#include "lib_path_util.h" | |
#include "lib_rect.h" | |
#include "lib_string.h" | |
#include "lib_string_search.h" | |
#include "lib_timecode.h" | |
#include "lib_utildefines.h" | |
#include "font_api.h" | |
#include "lang.h" | |
#include "dune_action.h" | |
#include "dune_colorband.h" | |
#include "dune_colortools.h" | |
#include "dune_constraint.h" | |
#include "dune_cxt.h" | |
#include "dune_curveprofile.h" | |
#include "dune_global.h" | |
#include "dune_pen_mod.h" | |
#include "dune_idprop.h" | |
#include "dune_idtype.h" | |
#include "dune_layer.h" | |
#include "dune_lib_id.h" | |
#include "dune_lib_override.h" | |
#include "dune_linestyle.h" | |
#include "dune_main.h" | |
#include "dune_mod.h" | |
#include "dune_object.h" | |
#include "dune_packedFile.h" | |
#include "dune_particle.h" | |
#include "dune_report.h" | |
#include "dune_scene.h" | |
#include "dune_screen.h" | |
#include "dune_shader_fx.h" | |
#include "graph.h" | |
#include "graph_build.h" | |
#include "ed_fileselect.h" | |
#include "ed_object.h" | |
#include "ed_render.h" | |
#include "ed_screen.h" | |
#include "ed_undo.h" | |
#include "render_engine.h" | |
#include "api_access.h" | |
#include "api_prototypes.h" | |
#include "win_api.h" | |
#include "win_types.h" | |
#include "loader_readfile.h" | |
#include "UI_interface.h" | |
#include "UI_interface_icons.h" | |
#include "UI_view2d.h" | |
#include "interface_intern.h" | |
#include "PIL_time.h" | |
/* we may want to make this optional, disable for now. */ | |
// #define USE_OP_RESET_BUT | |
/* defines for templateID/TemplateSearch */ | |
#define TEMPLATE_SEARCH_TEXTBUT_MIN_WIDTH (UI_UNIT_X * 6) | |
#define TEMPLATE_SEARCH_TEXTBUT_HEIGHT UI_UNIT_Y | |
void UI_template_fix_linking(void) | |
{ | |
} | |
/* Header Template */ | |
void uiTemplateHeader(uiLayout *layout, Cxt *C) | |
{ | |
uiBlock *block = uiLayoutAbsoluteBlock(layout); | |
ed_area_header_switchbutton(C, block, 0); | |
} | |
/* Search Menu Helpers */ | |
static int template_search_txtbtn_width(ApiPtr *ptr, ApiProp *name_prop) | |
{ | |
char str[UI_MAX_DRAW_STR]; | |
int buf_len = 0; | |
lib_assert(api_prop_type(name_prop) == PROP_STRING); | |
const char *name = api_prop_string_get_alloc(ptr, name_prop, str, sizeof(str), &buf_len); | |
const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; | |
const int margin = UI_UNIT_X * 0.75f; | |
const int estimated_width = ui_fontstyle_string_width(fstyle, name) + margin; | |
if (name != str) { | |
memfree((void *)name); | |
} | |
/* Clamp to some min/max width. */ | |
return CLAMPIS( | |
estimated_width, TEMPLATE_SEARCH_TEXTBUT_MIN_WIDTH, TEMPLATE_SEARCH_TEXTBUT_MIN_WIDTH * 3); | |
} | |
static int template_search_txtbtn_height(void) | |
{ | |
return TEMPLATE_SEARCH_TXTBTN_HEIGHT; | |
} | |
/* Add a block btn for the search menu for templateId and templateSearch. */ | |
static void template_add_btn_search_menu(const Cxt *C, | |
uiLayout *layout, | |
uiBlock *block, | |
ApiPtr *ptr, | |
ApiProp *prop, | |
uiBlockCreateFn block_fn, | |
void *block_arg, | |
const char *const tip, | |
const bool use_previews, | |
const bool editable, | |
const bool live_icon) | |
{ | |
const ApiPtr active_ptr = api_prop_ptr_get(ptr, prop); | |
Id *id = (active_ptr.data && api_struct_is_id(active_ptr.type)) ? active_ptr.data : NULL; | |
const Id *idfrom = ptr->owner_id; | |
const ApiStruct *type = active_ptr.type ? active_ptr.type : api_prop_ptr_type(ptr, prop); | |
uiBtn *btn; | |
if (use_previews) { | |
ARegion *region = cxt_wm_region(C); | |
/* Ugly tool header exception. */ | |
const bool use_big_size = (region->regiontype != RGN_TYPE_TOOL_HEADER); | |
/* Ugly exception for screens here, | |
* drawing their preview in icon size looks ugly/useless */ | |
const bool use_preview_icon = use_big_size || (id && (GS(id->name) != ID_SCR)); | |
const short width = UI_UNIT_X * (use_big_size ? 6 : 1.6f); | |
const short height = UI_UNIT_Y * (use_big_size ? 6 : 1); | |
uiLayout *col = NULL; | |
if (use_big_size) { | |
/* Assume column layout here. To be more correct, we should check if the layout passed to | |
* template_id is a column one, but this should work well in practice. */ | |
col = uiLayoutColumn(layout, true); | |
} | |
but = uiDefBtn(block, block_fn, block_argN, "", 0, 0, width, height, tip); | |
if (use_preview_icon) { | |
const int icon = id ? ui_id_icon_get(C, id, use_big_size) : api_struct_ui_icon(type); | |
ui_def_btn_icon(but, icon, UI_HAS_ICON | UI_BTN_ICON_PREVIEW); | |
} | |
else { | |
ui_def_btn_icon(but, api_struct_ui_icon(type), UI_HAS_ICON); | |
ui_btn_drawflag_enable(btn, UI_BTN_ICON_LEFT); | |
} | |
if ((idfrom && idfrom->lib) || !editable) { | |
ui_btn_flag_enable(but, UI_BTN_DISABLED); | |
} | |
if (use_big_size) { | |
uiLayoutRow(col ? col : layout, true); | |
} | |
} | |
else { | |
btn = uiDefBtn(block, block_fn, block_argN, "", 0, 0, UI_UNIT_X * 1.6, UI_UNIT_Y, tip); | |
if (live_icon) { | |
const int icon = id ? ui_id_icon_get(C, id, false) : api_struct_ui_icon(type); | |
ui_def_btn_icon(btn, icon, UI_HAS_ICON | UI_BTN_ICON_PREVIEW); | |
} | |
else { | |
ui_def_btn_icon(btn, api_struct_ui_icon(type), UI_HAS_ICON); | |
} | |
if (id) { | |
/* default dragging of icon for id browse buttons */ | |
UI_btn_drag_set_id(btn, id); | |
} | |
ui_btn_drawflag_enable(but, UI_BUT_ICON_LEFT); | |
if ((idfrom && idfrom->lib) || !editable) { | |
ui_btn_flag_enable(btn, UI_BUT_DISABLED); | |
} | |
} | |
} | |
static uiBlock *template_common_search_menu(const Cxt *C, | |
ARegion *region, | |
uiBtnSearchUpdateFn search_update_fn, | |
void *search_arg, | |
uiButHandleFn search_ex_fn, | |
void *active_item, | |
uiBtnSearchTooltipFn item_tooltip_fn, | |
const int preview_rows, | |
const int preview_cols, | |
float scale) | |
{ | |
static char search[256]; | |
Win *win = cxt_win(C); | |
uiBtn *but; | |
/* clear initial search string, then all items show */ | |
search[0] = 0; | |
uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); | |
ui_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_SEARCH_MENU); | |
ui_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); | |
/* preview thumbnails */ | |
if (preview_rows > 0 && preview_cols > 0) { | |
const int w = 4 * U.widget_unit * preview_cols * scale; | |
const int h = 5 * U.widget_unit * preview_rows * scale; | |
/* fake btn, it holds space for search items */ | |
uiDefBtn(block, UI_BTYPE_LABEL, 0, "", 10, 26, w, h, NULL, 0, 0, 0, 0, NULL); | |
but = uiDefBtn("block search viewzoom", | |
search, | |
sizeof(search), | |
preview_rows, | |
preview_cols); | |
} | |
/* list view */ | |
else { | |
const int searchbox_width = UI_searchbox_size_x(); | |
const int searchbox_height = UI_searchbox_size_y(); | |
/* fake btn, it holds space for search items */ | |
uiDefBtn("block label", | |
10, | |
15, | |
searchbox_width, | |
searchbox_height); | |
btn = uiDefBtn("block search icon_viewzoom", | |
search, | |
0, | |
sizeof(search), | |
10, | |
0, | |
searchbox_width, | |
UI_UNIT_Y - 1); | |
} | |
ui_btn_fn_search_set(btn, | |
ui_searchbox_create_generic, | |
search_update_fn, | |
search_arg, | |
false, | |
NULL, | |
search_ex_fn, | |
active_item); | |
ui_btn_fn_search_set_tooltip(btn, item_tooltip_fn); | |
ui_block_bounds_set_normal(block, 0.3f * U.widget_unit); | |
ui_block_direction_set(block, UI_DIR_DOWN); | |
/* give search-field focus */ | |
ui_btn_focus_on_enter_event(win, btn); | |
/* this type of search menu requires undo */ | |
btn->flag |= UI_BUT_UNDO; | |
return block; | |
} | |
/* Search Cbs */ | |
typedef struct TemplateId { | |
ApiPtr ptr; | |
ApiProp *prop; | |
List *idlist; | |
short idcode; | |
short filter; | |
int prv_rows, prv_cols; | |
bool preview; | |
float scale; | |
} TemplateId; | |
/* Search browse menu, assign. */ | |
static void template_id_set_prop_ex_fn(Cxt *C, void *arg_template, void *item) | |
{ | |
TemplateId *template_ui = (TemplateId *)arg_template; | |
/* Id */ | |
if (item) { | |
ApiPtr idptr; | |
api_id_ptr_create(item, &idptr); | |
api_prop_ptr_set(&template_ui->ptr, template_ui->prop, idptr, NULL); | |
api_prop_update(C, &template_ui->ptr, template_ui->prop); | |
} | |
} | |
static bool id_search_allows_id(TemplateId *template_ui, const int flag, Id *id, const char *query) | |
{ | |
Id *id_from = template_ui->ptr.owner_id; | |
/* Do self check. */ | |
if ((flag & PROP_ID_SELF_CHECK) && id == id_from) { | |
return false; | |
} | |
/* Use filter. */ | |
if (api_prop_type(template_ui->prop) == PROP_PTR) { | |
ApiPtr ptr; | |
api_id_ptr_create(id, &ptr); | |
if (api_prop_ptr_poll(&template_ui->ptr, template_ui->prop, &ptr) == 0) { | |
return false; | |
} | |
} | |
/* Hide dot prefixed data-blocks, but only if filter does not force them visible. */ | |
if (U.uiflag & USER_HIDE_DOT) { | |
if ((id->name[2] == '.') && (query[0] != '.')) { | |
return false; | |
} | |
} | |
return true; | |
} | |
static bool id_search_add(const Cxt *C, TemplateId *template_ui, uiSearchItems *items, Id *id) | |
{ | |
/* +1 is needed because dune_id_ui_prefix used 3 letter prefix | |
* followed by ID_NAME-2 characters from id->name */ | |
char name_ui[MAX_ID_FULL_NAME_UI]; | |
int iconid = ui_id_icon_get(C, id, template_ui->preview); | |
const bool use_lib_prefix = template_ui->preview || iconid; | |
const bool has_sep_char = ID_IS_LINKED(id); | |
/* When using previews, the lib hint (linked, overridden, missing) is added with a | |
* character prefix, otherwise we can use a icon. */ | |
int name_prefix_offset; | |
dune_id_full_name_ui_prefix_get(name_ui, id, use_lib_prefix, UI_SEP_CHAR, &name_prefix_offset); | |
if (!use_lib_prefix) { | |
iconid = ui_icon_from_lib(id); | |
} | |
if (!ui_search_item_add(items, | |
name_ui, | |
id, | |
iconid, | |
has_sep_char ? UI_BTN_HAS_SEP_CHAR : 0, | |
name_prefix_offset)) { | |
return false; | |
} | |
return true; | |
} | |
/* ID Search browse menu, do the search */ | |
static void id_search_cb(const bContext *C, | |
void *arg_template, | |
const char *str, | |
uiSearchItems *items, | |
const bool UNUSED(is_first)) | |
{ | |
TemplateId *template_ui = (TemplateId *)arg_template; | |
List *list = template_ui->idlist; | |
const int flag = api_prop_flag(template_ui->prop); | |
StringSearch *search = lib_string_search_new(); | |
/* Id list */ | |
LIST_FOREACH (Id *, id, list) { | |
if (id_search_allows_id(template_ui, flag, id, str)) { | |
lib_string_search_add(search, id->name + 2, id, 0); | |
} | |
} | |
ID **filtered_ids; | |
const int filtered_amount = lib_string_search_query(search, str, (void ***)&filtered_ids); | |
for (int i = 0; i < filtered_amount; i++) { | |
if (!id_search_add(C, template_ui, items, filtered_ids[i])) { | |
break; | |
} | |
} | |
MEM_freeN(filtered_ids); | |
BLI_string_search_free(search); | |
} | |
/* Use id tags for filtering. */ | |
static void id_search_cb_tagged(const Cxt *C, | |
void *arg_template, | |
const char *str, | |
uiSearchItems *items) | |
{ | |
TemplateId *template_ui = (TemplateId *)arg_template; | |
List *list = template_ui->idlb; | |
const int flag = api_prop_flag(template_ui->prop); | |
StringSearch *search = lib_string_search_new(); | |
/* Id list */ | |
LIST_FOREACH (ID *, id, lb) { | |
if (id->tag & LIB_TAG_DOIT) { | |
if (id_search_allows_id(template_ui, flag, id, str)) { | |
lib_string_search_add(search, id->name + 2, id, 0); | |
} | |
id->tag &= ~LIB_TAG_DOIT; | |
} | |
} | |
Id **filtered_ids; | |
const int filtered_amount = lib_string_search_query(search, str, (void ***)&filtered_ids); | |
for (int i = 0; i < filtered_amount; i++) { | |
if (!id_search_add(C, template_ui, items, filtered_ids[i])) { | |
break; | |
} | |
} | |
MEM_freeN(filtered_ids); | |
BLI_string_search_free(search); | |
} | |
/* A version of 'id_search_cb' that lists scene objects. */ | |
static void id_search_cb_objects_from_scene(const Cxt *C, | |
void *arg_template, | |
const char *str, | |
uiSearchItems *items, | |
const bool UNUSED(is_first)) | |
{ | |
TemplateId *template_ui = (TemplateId *)arg_template; | |
List *lb = template_ui->idlist; | |
Scene *scene = NULL; | |
Id *id_from = template_ui->ptr.owner_id; | |
if (id_from && GS(id_from->name) == ID_SCE) { | |
scene = (Scene *)id_from; | |
} | |
else { | |
scene = cxt_data_scene(C); | |
} | |
dune_main_id_flag_list(lb, LIB_TAG_DOIT, false); | |
FOREACH_SCENE_OBJECT_BEGIN (scene, ob_iter) { | |
ob_iter->id.tag |= LIB_TAG_DOIT; | |
} | |
FOREACH_SCENE_OBJECT_END; | |
id_search_cb_tagged(C, arg_template, str, items); | |
} | |
static ARegion *template_id_search_menu_item_tooltip( | |
Cxt *C, ARegion *region, const rcti *item_rect, void *arg, void *active) | |
{ | |
TemplateId *template_ui = arg; | |
Id *active_id = active; | |
StructRNA *type = api_prop_ptr_type(&template_ui->ptr, template_ui->prop); | |
uiSearchItemTooltipData tooltip_data = {0}; | |
tooltip_data.name = active_id->name + 2; | |
lib_snprintf(tooltip_data.description, | |
sizeof(tooltip_data.description), | |
TIP_("Choose %s data-block to be assigned to this user"), | |
api_struct_ui_name(type)); | |
if (ID_IS_LINKED(active_id)) { | |
lib_snprintf(tooltip_data.hint, | |
sizeof(tooltip_data.hint), | |
TIP_("Source lib: %s\n%s"), | |
active_id->lib->id.name + 2, | |
active_id->lib->filepath); | |
} | |
return UI_tooltip_create_from_search_item_generic(C, region, item_rect, &tooltip_data); | |
} | |
/* ID Search browse menu, open */ | |
static uiBlock *id_search_menu(Cxt *C, ARegion *region, void *arg_litem) | |
{ | |
static TemplateId template_ui; | |
ApiPtr active_item_ptr; | |
void (*id_search_update_fn)( | |
const Cxt *, void *, const char *, uiSearchItems *, const bool) = id_search_cb; | |
/* arg_litem is malloced, can be freed by parent btn */ | |
template_ui = *((TemplateId *)arg_litem); | |
active_item_ptr = api_prop_ptr_get(&template_ui.ptr, template_ui.prop); | |
if (template_ui.filter) { | |
/* Currently only used for objects. */ | |
if (template_ui.idcode == ID_OB) { | |
if (template_ui.filter == UI_TEMPLATE_ID_FILTER_AVAILABLE) { | |
id_search_update_fn = id_search_cb_objects_from_scene; | |
} | |
} | |
} | |
return template_common_search_menu(C, | |
region, | |
id_search_update_fn, | |
&template_ui, | |
template_id_set_prop_ex_fn, | |
active_item_ptr.data, | |
template_id_search_menu_item_tooltip, | |
template_ui.prv_rows, | |
template_ui.prv_cols, | |
template_ui.scale); | |
} | |
/* Id Template */ | |
/* This is for browsing and editing the ID-blocks used */ | |
void ui_cxt_active_btm_prop_get_templateId(Cxt *C, | |
ApiPtr *r_ptr, | |
ApiProp **r_prop) | |
{ | |
uiBtn *btn = ui_cxt_active_btn_get(C); | |
memset(r_ptr, 0, sizeof(*r_ptr)); | |
*r_prop = NULL; | |
if (btn && btn->fn_arg) { | |
TemplateId *template_ui = btn->fn_arg; | |
*r_ptr = template_ui->ptr; | |
*r_prop = template_ui->prop; | |
} | |
} | |
static void template_id_cb(Cxt *C, void *arg_litem, void *arg_event) | |
{ | |
TemplateId *template_ui = (TemplateId *)arg_litem; | |
ApiPtr idptr = api_prop_ptr_get(&template_ui->ptr, template_ui->prop); | |
ID *id = idptr.data; | |
const int event = PTR_AS_INT(arg_event); | |
const char *undo_push_label = NULL; | |
switch (event) { | |
case UI_ID_BROWSE: | |
case UI_ID_PIN: | |
api_warning("warning, id event %d shouldn't come here", event); | |
break; | |
case UI_ID_OPEN: | |
case UI_ID_ADD_NEW: | |
/* these call ui_cxt_active_btn_prop_get_templateId */ | |
break; | |
case UI_ID_DELETE: | |
memset(&idptr, 0, sizeof(idptr)); | |
api_prop_ptr_set(&template_ui->ptr, template_ui->prop, idptr, NULL); | |
api_prop_update(C, &template_ui->ptr, template_ui->prop); | |
if (id && cxt_wm_window(C)->eventstate->mod & KM_SHIFT) { | |
/* only way to force-remove data (on save) */ | |
id_us_clear_real(id); | |
id_fake_user_clear(id); | |
id->us = 0; | |
undo_push_label = "Delete Data-Block"; | |
} | |
break; | |
case UI_ID_FAKE_USER: | |
if (id) { | |
if (id->flag & LIB_FAKEUSER) { | |
id_us_plus(id); | |
} | |
else { | |
id_us_min(id); | |
} | |
undo_push_label = "Fake User"; | |
} | |
else { | |
return; | |
} | |
break; | |
case UI_ID_LOCAL: | |
if (id) { | |
Main *main = cxt_data_main(C); | |
if (cxt_win(C)->eventstate->mod & KM_SHIFT) { | |
if (ID_IS_OVERRIDABLE_LIB(id)) { | |
/* Only remap that specific Id usage to overriding local data-block. */ | |
Id *override_id = dune_lib_override_lib_create_from_id(main, id, false); | |
if (override_id != NULL) { | |
dune_main_id_newptr_and_tag_clear(main); | |
if (GS(override_id->name) == ID_OB) { | |
Scene *scene = cxt_data_scene(C); | |
if (!dune_collection_has_object_recursive(scene->master_collection, | |
(Object *)override_id)) { | |
dune_collection_object_add_from( | |
main, scene, (Object *)id, (Object *)override_id); | |
} | |
} | |
/* Assign new ptr, takes care of updates/notifiers */ | |
api_id_ptr_create(override_id, &idptr); | |
/* Insert into override hierarchy if possible. */ | |
Id *owner_id = template_ui->ptr.owner_id; | |
if (owner_id != NULL && ID_IS_OVERRIDE_LIB_REAL(owner_id)) { | |
override_id->override_lib->hierarchy_root = | |
owner_id->override_lib->hierarchy_root; | |
owner_id->override_lib->flag &= ~IDOVERRIDE_LIBRARY_FLAG_NO_HIERARCHY; | |
} | |
} | |
undo_push_label = "Make Library Override"; | |
} | |
} | |
else { | |
if (dune_lib_id_make_local(bmain, id, 0)) { | |
dune_id_newptr_and_tag_clear(id); | |
/* Reassign to get proper updates/notifiers. */ | |
idptr = api_prop_ptr_get(&template_ui->ptr, template_ui->prop); | |
undo_push_label = "Make Local"; | |
} | |
} | |
if (undo_push_label != NULL) { | |
RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); | |
RNA_property_update(C, &template_ui->ptr, template_ui->prop); | |
} | |
} | |
break; | |
case UI_ID_OVERRIDE: | |
if (id && ID_IS_OVERRIDE_LIBRARY(id)) { | |
BKE_lib_override_library_make_local(id); | |
/* Reassign to get proper updates/notifiers. */ | |
idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); | |
RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); | |
RNA_property_update(C, &template_ui->ptr, template_ui->prop); | |
undo_push_label = "Make Local"; | |
} | |
break; | |
case UI_ID_ALONE: | |
if (id) { | |
const bool do_scene_obj = ((GS(id->name) == ID_OB) && | |
(template_ui->ptr.type == &RNA_LayerObjects)); | |
/* make copy */ | |
if (do_scene_obj) { | |
Main *bmain = CTX_data_main(C); | |
Scene *scene = CTX_data_scene(C); | |
ED_object_single_user(bmain, scene, (struct Object *)id); | |
WM_event_add_notifier(C, NC_WINDOW, NULL); | |
DEG_relations_tag_update(bmain); | |
} | |
else { | |
Main *bmain = CTX_data_main(C); | |
id_single_user(C, id, &template_ui->ptr, template_ui->prop); | |
DEG_relations_tag_update(bmain); | |
} | |
undo_push_label = "Make Single User"; | |
} | |
break; | |
#if 0 | |
case UI_ID_AUTO_NAME: | |
break; | |
#endif | |
} | |
if (undo_push_label != NULL) { | |
ED_undo_push(C, undo_push_label); | |
} | |
} | |
static const char *template_id_browse_tip(const StructRNA *type) | |
{ | |
if (type) { | |
switch ((ID_Type)RNA_type_to_ID_code(type)) { | |
case ID_SCE: | |
return N_("Browse Scene to be linked"); | |
case ID_OB: | |
return N_("Browse Object to be linked"); | |
case ID_ME: | |
return N_("Browse Mesh Data to be linked"); | |
case ID_CU_LEGACY: | |
return N_("Browse Curve Data to be linked"); | |
case ID_MB: | |
return N_("Browse Metaball Data to be linked"); | |
case ID_MA: | |
return N_("Browse Material to be linked"); | |
case ID_TE: | |
return N_("Browse Texture to be linked"); | |
case ID_IM: | |
return N_("Browse Image to be linked"); | |
case ID_LS: | |
return N_("Browse Line Style Data to be linked"); | |
case ID_LT: | |
return N_("Browse Lattice Data to be linked"); | |
case ID_LA: | |
return N_("Browse Light Data to be linked"); | |
case ID_CA: | |
return N_("Browse Camera Data to be linked"); | |
case ID_WO: | |
return N_("Browse World Settings to be linked"); | |
case ID_SCR: | |
return N_("Choose Screen layout"); | |
case ID_TXT: | |
return N_("Browse Text to be linked"); | |
case ID_SPK: | |
return N_("Browse Speaker Data to be linked"); | |
case ID_SO: | |
return N_("Browse Sound to be linked"); | |
case ID_AR: | |
return N_("Browse Armature data to be linked"); | |
case ID_AC: | |
return N_("Browse Action to be linked"); | |
case ID_NT: | |
return N_("Browse Node Tree to be linked"); | |
case ID_BR: | |
return N_("Browse Brush to be linked"); | |
case ID_PA: | |
return N_("Browse Particle Settings to be linked"); | |
case ID_GD: | |
return N_("Browse Grease Pencil Data to be linked"); | |
case ID_MC: | |
return N_("Browse Movie Clip to be linked"); | |
case ID_MSK: | |
return N_("Browse Mask to be linked"); | |
case ID_PAL: | |
return N_("Browse Palette Data to be linked"); | |
case ID_PC: | |
return N_("Browse Paint Curve Data to be linked"); | |
case ID_CF: | |
return N_("Browse Cache Files to be linked"); | |
case ID_WS: | |
return N_("Browse Workspace to be linked"); | |
case ID_LP: | |
return N_("Browse LightProbe to be linked"); | |
case ID_CV: | |
return N_("Browse Hair Curves Data to be linked"); | |
case ID_PT: | |
return N_("Browse Point Cloud Data to be linked"); | |
case ID_VO: | |
return N_("Browse Volume Data to be linked"); | |
case ID_SIM: | |
return N_("Browse Simulation to be linked"); | |
/* Use generic text. */ | |
case ID_LI: | |
case ID_IP: | |
case ID_KE: | |
case ID_VF: | |
case ID_GR: | |
case ID_WM: | |
break; | |
} | |
} | |
return N_("Browse ID data to be linked"); | |
} | |
/** | |
* \return a type-based i18n context, needed e.g. by "New" button. | |
* In most languages, this adjective takes different form based on gender of type name... | |
*/ | |
#ifdef WITH_INTERNATIONAL | |
static const char *template_id_context(StructRNA *type) | |
{ | |
if (type) { | |
return BKE_idtype_idcode_to_translation_context(RNA_type_to_ID_code(type)); | |
} | |
return BLT_I18NCONTEXT_DEFAULT; | |
} | |
#else | |
# define template_id_context(type) 0 | |
#endif | |
static uiBut *template_id_def_new_but(uiBlock *block, | |
const ID *id, | |
const TemplateID *template_ui, | |
StructRNA *type, | |
const char *const newop, | |
const bool editable, | |
const bool id_open, | |
const bool use_tab_but, | |
int but_height) | |
{ | |
ID *idfrom = template_ui->ptr.owner_id; | |
uiBut *but; | |
const int w = id ? UI_UNIT_X : id_open ? UI_UNIT_X * 3 : UI_UNIT_X * 6; | |
const int but_type = use_tab_but ? UI_BTYPE_TAB : UI_BTYPE_BUT; | |
/* i18n markup, does nothing! */ | |
LANG_MSGID_MULTI_CXT("New", | |
BLT_CXT_DEFAULT, | |
BLT_CXT_ID_SCENE, | |
BLT_CXT_ID_OBJECT, | |
BLT_CXT_ID_MESH, | |
BLT_CXT_ID_CURVE_LEGACY, | |
BLT_CXT_ID_METABALL, | |
BLT_CXT_ID_MATERIAL, | |
BLT_CXT_ID_TEXTURE, | |
BLT_CXT_ID_IMAGE, | |
BLT_CXT_ID_LATTICE, | |
BLT_CXT_ID_LIGHT, | |
BLT_CXT_ID_CAMERA, | |
BLT_CXT_ID_WORLD, | |
BLT_CXT_ID_SCREEN, | |
BLT_CXT_ID_TEXT, ); | |
LANG_MSGID_MULTI_CXT("New", | |
BLT_CXT_ID_SPEAKER, | |
BLT_CXT_ID_SOUND, | |
BLT_CXT_ID_ARMATURE, | |
BLT_CXT_ID_ACTION, | |
BLT_CXT_ID_NODETREE, | |
BLT_CXT_ID_BRUSH, | |
BLT_CXT_ID_PARTICLESETTINGS, | |
BLT_CXT_ID_PEN, | |
BLT_CXT_ID_FREESTYLELINESTYLE, | |
BLT_CXT_ID_WORKSPACE, | |
BLT_CXT_ID_LIGHTPROBE, | |
BLT_CXT_ID_CURVES, | |
BLT_CXT_ID_POINTCLOUD, | |
BLT_CXT_ID_VOLUME, | |
BLT_CXT_ID_SIMULATION, ); | |
/* NOTE: LANG_CXT_MSGID_MULTI_CXT takes a maximum number of params, | |
* check the definition to see if a new call must be added when the limit | |
* is exceeded. */ | |
if (newop) { | |
btn = uiDefBtnO("block icon text", | |
but_type, | |
newop, | |
WM_OP_INVOKE_DEFAULT, | |
(id && !use_tab_btn) ? ICON_DUPLICATE : ICON_ADD, | |
(id) ? "" : CTX_IFACE_(template_id_cxt(type), "New"), | |
0, | |
0, | |
w, | |
but_height, | |
NULL); | |
ui_btn_fn_set( | |
btn, template_id_cb, mem_dupalloc(template_ui), PTR_FROM_INT(UI_ID_ADD_NEW)); | |
} | |
else { | |
btn = uiDefBtn("block icon text btn_height", | |
btn_type, | |
0, | |
(id && !use_tab_btn) ? ICON_DUPLICATE : ICON_ADD, | |
(id) ? "" : CXT_IFACE_(template_id_cxt(type), "New")); | |
ui_btn_fn_set( | |
btn, template_id_cb, mem_dupalloc(template_ui), PTR_FROM_INT(UI_ID_ADD_NEW)); | |
} | |
if ((idfrom && idfrom->lib) || !editable) { | |
ui_btn_flag_enable(btn, UI_BTN_DISABLED); | |
} | |
#ifndef WITH_INTERNATIONAL | |
UNUSED_VARS(type); | |
#endif | |
return btn; | |
} | |
static void template_id(const Cxt *C, | |
uiLayout *layout, | |
TemplateId *template_ui, | |
ApiStruct *type, | |
int flag, | |
const char *newop, | |
const char *openop, | |
const char *unlinkop, | |
const char *text, | |
const bool live_icon, | |
const bool hide_btns) | |
{ | |
uiBtn *btn; | |
const bool editable = api_prop_editable(&template_ui->ptr, template_ui->prop); | |
const bool use_previews = template_ui->preview = (flag & UI_ID_PREVIEWS) != 0; | |
ApiPtr idptr = api_prop_ptr_get(&template_ui->ptr, template_ui->prop); | |
Id *id = idptr.data; | |
Id *idfrom = template_ui->ptr.owner_id; | |
// lb = template_ui->idlb; | |
/* Allow ops to take the Id from cxt. */ | |
uiLayoutSetCxtPtr(layout, "id", &idptr); | |
uiBlock *block = uiLayoutGetBlock(layout); | |
ui_block_align_begin(block); | |
if (idptr.type) { | |
type = idptr.type; | |
} | |
if (text) { | |
/* Add label respecting the separated layout prop split state. */ | |
uiItemL_respect_prop_split(layout, text, ICON_NONE); | |
} | |
if (flag & UI_ID_BROWSE) { | |
template_add_btn_search_menu(C, | |
layout, | |
block, | |
&template_ui->ptr, | |
template_ui->prop, | |
id_search_menu, | |
MEM_dupallocN(template_ui), | |
TIP_(template_id_browse_tip(type)), | |
use_previews, | |
editable, | |
live_icon); | |
} | |
/* text button with name */ | |
if (id) { | |
char name[UI_MAX_NAME_STR]; | |
const bool user_alert = (id->us <= 0); | |
const int width = template_search_textbut_width(&idptr, | |
RNA_struct_find_property(&idptr, "name")); | |
const int height = template_search_textbut_height(); | |
// text_idbutton(id, name); | |
name[0] = '\0'; | |
btn = uiDefBtnR(block, | |
UI_BTYPE_TEXT, | |
0, | |
name, | |
0, | |
0, | |
width, | |
height, | |
&idptr, | |
"name", | |
-1, | |
0, | |
0, | |
-1, | |
-1, | |
RNA_struct_ui_description(type)); | |
UI_but_funcN_set( | |
but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_RENAME)); | |
if (user_alert) { | |
UI_but_flag_enable(but, UI_BUT_REDALERT); | |
} | |
if (ID_IS_LINKED(id)) { | |
if (id->tag & LIB_TAG_INDIRECT) { | |
btn = uiDefBtn("block icon icon-lib disabled", "Indirect library data-block, cannot change"); | |
} | |
else { | |
const bool disabled = (!dune_idtype_idcode_is_localizable(GS(id->name)) || | |
(idfrom && idfrom->lib)); | |
btn = uiDefBtn("block icon icon-lib-data-direct", | |
(("Direct linked library data-block, click to make local, ") | |
("Shift + Click to create a library override")); | |
if (disabled) {//in future disabled is automatic and render step enables | |
btn_flag_enable(btn, "disabled"); | |
} | |
else { | |
btn_fn_set( | |
btn, template_id_cb, mem_dupalloc(template_ui), PTR_FROM_INT(UI_ID_LOCAL)); | |
} | |
} | |
} | |
else if (ID_IS_OVERRIDE_LIB(id)) { | |
btn = uiDefBtn("block icon icon-lib-override", ("Library override of linked data-block, click to make fully local")); | |
btn_fn_set( | |
btn, template_id_cb, mem_dupalloc(template_ui), PTR_FROM_INT(UI_ID_OVERRIDE)); | |
} | |
if ((ID_REAL_USERS(id) > 1) && (hide_btns == false)) { | |
char numstr[32]; | |
short numstr_len; | |
numstr_len = lib_snprintf_rlen(numstr, sizeof(numstr), "%d", ID_REAL_USERS(id)); | |
btn = uiDefBtn("block width-(numstr_len * 0.2f * UI_UNIT_X + UI_UNIT_X)", | |
("Display number of users of this data (click to make a single-user copy)")); | |
btn->flag |= UI_BTN_UNDO; | |
ui_btn_fn_set( | |
btn, template_id_cb, mem_dupalloc(template_ui), PTR_FROM_INT(UI_ID_ALONE)); | |
if ((!dune_id_copy_is_allowed(id)) || (idfrom && idfrom->lib) || (!editable) || | |
/* object in editmode - don't change data */ | |
(idfrom && GS(idfrom->name) == ID_OB && (((Object *)idfrom)->mode & OB_MODE_EDIT))) { | |
btn_flag_set(btn, "disabled"); | |
} | |
} | |
if (user_alert) { | |
btn_flag_set(btn "redalert"); | |
} | |
if (!ID_IS_LINKED(id)) { | |
if (ID_IS_ASSET(id)) { | |
uiDefBtnO("block icon toggle", | |
/* Using `_N` version allows us to get the 'active' state by default. */ | |
UI_BTYPE_ICON_TOGGLE_N, | |
"ASSET_OT_clear", | |
WM_OP_INVOKE_DEFAULT, | |
/* 'active' state of a toggle button uses icon + 1, so to get proper asset | |
* icon we need to pass its value - 1 here. */ | |
ICON_ASSET_MANAGER - 1); | |
} | |
else if (!(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_OB, ID_WS)) && | |
(hide_btns == false)) { | |
uiDefBtnR("block icon toggle icon-fake_user_off", | |
&idptr, | |
"use_fake_user"); | |
} | |
} | |
} | |
if ((flag & UI_ID_ADD_NEW) && (hide_btns == false)) { | |
template_id_def_new_btn( | |
block, id, template_ui, type, newop, editable, flag & UI_ID_OPEN, false, UI_UNIT_X); | |
} | |
/* Due to space limit in UI - skip the "open" icon for packed data, and allow to unpack. | |
* Only for images, sound and fonts */ | |
if (id && dune_packedfile_id_check(id)) { | |
btn = uiDefBtnO("block icon icon-package", | |
UI_BTYPE_BUT, | |
"FILE_OT_unpack_item", | |
WM_OP_INVOKE_REGION_WIN, | |
TIP_("Packed File, click to unpack")); | |
btn_op_ptr_get(btn); | |
api_string_set(btn->opptr, "id_name", id->name + 2); | |
api_int_set(btn->opptr, "id_type", GS(id->name)); | |
} | |
else if (flag & UI_ID_OPEN) { | |
const int w = id ? UI_UNIT_X : (flag & UI_ID_ADD_NEW) ? UI_UNIT_X * 3 : UI_UNIT_X * 6; | |
if (openop) { | |
btn = uiDefBtnO("block icon text icon-filebrowser w-(w)",, | |
openop, | |
WIN_OP_INVOKE_DEFAULT); | |
btn_fn_set( | |
btn, template_id_cb, mem_dupalloc(template_ui), PTR_FROM_INT(UI_ID_OPEN)); | |
} | |
else { | |
but = uiDefIconTextBut("block icon-filebrowser", | |
(id) ? "" : IFACE_("Open"), | |
w); | |
btn_fn_set( | |
btn, template_id_cb, mem_dupalloc(template_ui), PTR_FROM_INT(UI_ID_OPEN)); | |
} | |
if ((idfrom && idfrom->lib) || !editable) { | |
btn_flag_enable(btn, UI_BTN_DISABLED); | |
} | |
} | |
/* delete button */ | |
/* don't use api_prop_is_unlink here */ | |
if (id && (flag & UI_ID_DELETE) && (hide_btns == false)) { | |
/* allow unlink if 'unlinkop' is passed, even when 'PROP_NEVER_UNLINK' is set */ | |
btn = NULL; | |
if (unlinkop) { | |
btn = uiDefBtnO("block icon-x", unlinkop, | |
WIN_OP_INVOKE_DEFAULT); | |
/* so we can access the template from ops, font unlinking needs this */ | |
btn_fn_set(but, NULL, mem_dupalloc(template_ui), NULL); | |
} | |
else { | |
if ((api_prop_flag(template_ui->prop) & PROP_NEVER_UNLINK) == 0) { | |
btn = uiDefBtn("block icon-x", ("Unlink data-block ", | |
"(Shift + Click to set users to zero, data will then not be saved)")); | |
btn_fn_set( | |
btn, template_id_cb, mem_dupalloc(template_ui), PTR_FROM_INT(UI_ID_DELETE)); | |
if (api_prop_flag(template_ui->prop) & PROP_NEVER_NULL) { | |
btn_flag_enable(btn, UI_BTN_DISABLED); | |
} | |
} | |
} | |
if (btn) { | |
if ((idfrom && idfrom->lib) || !editable) { | |
btn_flag_enable(btn, UI_BTN_DISABLED); | |
} | |
} | |
} | |
if (template_ui->idcode == ID_TE) { | |
uiTemplateTextureShow(layout, C, &template_ui->ptr, template_ui->prop); | |
} | |
ui_block_align_end(block); | |
} | |
Id *ui_cxt_active_btn_get_tab_id(Cxt *C) | |
{ | |
uiBtn *btn = ui_cxt_active_btn_get(C); | |
if (btn && btn->type == UI_BTYPE_TAB) { | |
return btn->custom_data; | |
} | |
return NULL; | |
} | |
static void template_id_tabs(const Cxt *C, | |
uiLayout *layout, | |
TemplateId *template, | |
ApiStruct *type, | |
int flag, | |
const char *newop, | |
const char *menu) | |
{ | |
const ARegion *region = cxt_wm_region(C); | |
const ApitPtr active_ptr = api_prop_ptr_get(&template->ptr, template->prop); | |
MenuType *mt = menu ? wm_menutype_find(menu, false) : NULL; | |
const int but_align = ui_but_align_opposite_to_area_align_get(region); | |
const int but_height = UI_UNIT_Y * 1.1; | |
uiBlock *block = uiLayoutGetBlock(layout); | |
const uiStyle *style = UI_style_get_dpi(); | |
List ordered; | |
dune_id_ordered_list(&ordered, template->idlb); | |
LIST_FOREACH (LinkData *, link, &ordered) { | |
Id *id = link->data; | |
const int name_width = ui_fontstyle_string_width(&style->widget, id->name + 2); | |
const int but_width = name_width + UI_UNIT_X; | |
uiBtnTab *tab = (uiButTab *)uiDefBtnR_prop(block, | |
UI_BTYPE_TAB, | |
0, | |
id->name + 2, | |
0, | |
0, | |
btn_width, | |
btn_height, | |
&template->ptr, | |
template->prop, | |
0, | |
0.0f, | |
sizeof(id->name) - 2, | |
0.0f, | |
0.0f, | |
""); | |
btn_fn_set(&tab->btn, template_id_set_prop_ex_fn, mem_dupalloc(template), id); | |
tab->btn.custom_data = (void *)id; | |
tab->btn.dragptr = id; | |
tab->menu = mt; | |
btn_drawflag_enable(&tab->btn, btn_align); | |
} | |
lib_freelist(&ordered); | |
if (flag & UI_ID_ADD_NEW) { | |
const bool editable = api_prop_editable(&template->ptr, template->prop); | |
uiBtn *btn; | |
if (active_ptr.type) { | |
type = active_ptr.type; | |
} | |
btn = template_id_def_new_btn(block, | |
active_ptr.data, | |
template, | |
type, | |
newop, | |
editable, | |
flag & UI_ID_OPEN, | |
true, | |
btn_height); | |
ui_btn_drawflag_enable(btn, btn_align); | |
} | |
} | |
static void ui_template_id(uiLayout *layout, | |
const Cxt *C, | |
ApiPtr *ptr, | |
const char *propname, | |
const char *newop, | |
const char *openop, | |
const char *unlinkop, | |
/* Only respected by tabs (use_tabs). */ | |
const char *menu, | |
const char *text, | |
int flag, | |
int prv_rows, | |
int prv_cols, | |
int filter, | |
bool use_tabs, | |
float scale, | |
const bool live_icon, | |
const bool hide_btns) | |
{ | |
ApiProp *prop = api_struct_find_prop(ptr, propname); | |
if (!prop || api_prop_type(prop) != PROP_PTR) { | |
api_warning("ptr prop not found: %s.%s", api_struct_id(ptr->type), propname); | |
return; | |
} | |
TemplateId *template_ui = mem_calloc(sizeof(TemplateId), "TemplateId"); | |
template_ui->ptr = *ptr; | |
template_ui->prop = prop; | |
template_ui->prv_rows = prv_rows; | |
template_ui->prv_cols = prv_cols; | |
template_ui->scale = scale; | |
if ((flag & UI_ID_PIN) == 0) { | |
template_ui->filter = filter; | |
} | |
else { | |
template_ui->filter = 0; | |
} | |
if (newop) { | |
flag |= UI_ID_ADD_NEW; | |
} | |
if (openop) { | |
flag |= UI_ID_OPEN; | |
} | |
ApiStruct *type = api_prop_ptr_type(ptr, prop); | |
short idcode = api_type_to_id_code(type); | |
template_ui->idcode = idcode; | |
template_ui->idlist = which_libbase(cxt_data_main(C), idcode); | |
/* create UI elements for this template | |
* - template_ID makes a copy of the template data and assigns it to the relevant buttons */ | |
if (template_ui->idlist) { | |
if (use_tabs) { | |
layout = uiLayoutRow(layout, true); | |
template_id_tabs(C, layout, template_ui, type, flag, newop, menu); | |
} | |
else { | |
layout = uiLayoutRow(layout, true); | |
template_id(C, | |
layout, | |
template_ui, | |
type, | |
flag, | |
newop, | |
openop, | |
unlinkop, | |
text, | |
live_icon, | |
hide_btns); | |
} | |
} | |
mem_free(template_ui); | |
} | |
void uiTemplateId(uiLayout *layout, | |
const Cxt *C, | |
ApiPtr *ptr, | |
const char *propname, | |
const char *newop, | |
const char *openop, | |
const char *unlinkop, | |
int filter, | |
const bool live_icon, | |
const char *text) | |
{ | |
ui_template_id(layout, | |
C, | |
ptr, | |
propname, | |
newop, | |
openop, | |
unlinkop, | |
NULL, | |
text, | |
UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE, | |
0, | |
0, | |
filter, | |
false, | |
1.0f, | |
live_icon, | |
false); | |
} | |
void uiTemplateIdBrowse(uiLayout *layout, | |
Cxt *C, | |
ApiPtr *ptr, | |
const char *propname, | |
const char *newop, | |
const char *openop, | |
const char *unlinkop, | |
int filter, | |
const char *text) | |
{ | |
ui_template_id(layout, | |
C, | |
ptr, | |
propname, | |
newop, | |
openop, | |
unlinkop, | |
NULL, | |
text, | |
UI_ID_BROWSE | UI_ID_RENAME, | |
0, | |
0, | |
filter, | |
false, | |
1.0f, | |
false, | |
false); | |
} | |
void uiTemplateIdPreview(uiLayout *layout, | |
Cxt *C, | |
ApiPtr *ptr, | |
const char *propname, | |
const char *newop, | |
const char *openop, | |
const char *unlinkop, | |
int rows, | |
int cols, | |
int filter, | |
const bool hide_btns) | |
{ | |
ui_template_id(layout, | |
C, | |
ptr, | |
propname, | |
newop, | |
openop, | |
unlinkop, | |
NULL, | |
NULL, | |
UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE | UI_ID_PREVIEWS, | |
rows, | |
cols, | |
filter, | |
false, | |
1.0f, | |
false, | |
hide_btns); | |
} | |
void uiTemplatePenColorPreview(uiLayout *layout, | |
Cxt *C, | |
ApitPtr *ptr, | |
const char *propname, | |
int rows, | |
int cols, | |
float scale, | |
int filter) | |
{ | |
ui_template_id(layout, | |
C, | |
ptr, | |
propname, | |
NULL, | |
NULL, | |
NULL, | |
NULL, | |
NULL, | |
UI_ID_BROWSE | UI_ID_PREVIEWS | UI_ID_DELETE, | |
rows, | |
cols, | |
filter, | |
false, | |
scale < 0.5f ? 0.5f : scale, | |
false, | |
false); | |
} | |
void uiTemplateIdTabs(uiLayout *layout, | |
Cxt *C, | |
ApiPtr *ptr, | |
const char *propname, | |
const char *newop, | |
const char *menu, | |
int filter) | |
{ | |
ui_template_id(layout, | |
C, | |
ptr, | |
propname, | |
newop, | |
NULL, | |
NULL, | |
menu, | |
NULL, | |
UI_ID_BROWSE | UI_ID_RENAME, | |
0, | |
0, | |
filter, | |
true, | |
1.0f, | |
false, | |
false); | |
} | |
/* ID Chooser Template */ | |
void uiTemplateAnyId(uiLayout *layout, | |
ApiPtr *ptr, | |
const char *propname, | |
const char *proptypename, | |
const char *text) | |
{ | |
/* get properties... */ | |
ApiProp *propId = api_struct_find_prop(ptr, propname); | |
ApiProp *propType = api_struct_find_prop(ptr, proptypename); | |
if (!propID || RNA_property_type(propID) != PROP_PTR) { | |
RNA_warning("pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); | |
return; | |
} | |
if (!propType || RNA_property_type(propType) != PROP_ENUM) { | |
RNA_warning( | |
"pointer-type property not found: %s.%s", RNA_struct_identifier(ptr->type), proptypename); | |
return; | |
} | |
/* Start drawing UI Elements using standard defines */ | |
/* NOTE: split amount here needs to be synced with normal labels */ | |
uiLayout *split = uiLayoutSplit(layout, 0.33f, false); | |
/* FIRST PART ................................................ */ | |
uiLayout *row = uiLayoutRow(split, false); | |
/* Label - either use the provided text, or will become "ID-Block:" */ | |
if (text) { | |
if (text[0]) { | |
uiItemL(row, text, ICON_NONE); | |
} | |
} | |
else { | |
uiItemL(row, IFACE_("ID-Block:"), ICON_NONE); | |
} | |
/* SECOND PART ................................................ */ | |
row = uiLayoutRow(split, true); | |
/* ID-Type Selector - just have a menu of icons */ | |
/* HACK: special group just for the enum, | |
* otherwise we get ugly layout with text included too... */ | |
uiLayout *sub = uiLayoutRow(row, true); | |
uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); | |
uiItemFullR(sub, ptr, propType, 0, 0, UI_ITEM_R_ICON_ONLY, "", ICON_NONE); | |
/* ID-Block Selector - just use pointer widget... */ | |
/* HACK: special group to counteract the effects of the previous enum, | |
* which now pushes everything too far right. */ | |
sub = uiLayoutRow(row, true); | |
uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_EXPAND); | |
uiItemFullR(sub, ptr, propID, 0, 0, 0, "", ICON_NONE); | |
} | |
/** \} */ | |
/* -------------------------------------------------------------------- */ | |
/** \name Search Template | |
* \{ */ | |
typedef struct TemplateSearch { | |
uiRNACollectionSearch search_data; | |
bool use_previews; | |
int preview_rows, preview_cols; | |
} TemplateSearch; | |
static void template_search_exec_fn(bContext *C, void *arg_template, void *item) | |
{ | |
TemplateSearch *template_search = arg_template; | |
uiRNACollectionSearch *coll_search = &template_search->search_data; | |
StructRNA *type = RNA_property_pointer_type(&coll_search->target_ptr, coll_search->target_prop); | |
PointerRNA item_ptr; | |
RNA_pointer_create(NULL, type, item, &item_ptr); | |
RNA_property_pointer_set(&coll_search->target_ptr, coll_search->target_prop, item_ptr, NULL); | |
RNA_property_update(C, &coll_search->target_ptr, coll_search->target_prop); | |
} | |
static uiBlock *template_search_menu(bContext *C, ARegion *region, void *arg_template) | |
{ | |
static TemplateSearch template_search; | |
/* arg_template is malloced, can be freed by parent button */ | |
template_search = *((TemplateSearch *)arg_template); | |
PointerRNA active_ptr = RNA_property_pointer_get(&template_search.search_data.target_ptr, | |
template_search.search_data.target_prop); | |
return template_common_search_menu(C, | |
region, | |
ui_rna_collection_search_update_fn, | |
&template_search, | |
template_search_exec_fn, | |
active_ptr.data, | |
NULL, | |
template_search.preview_rows, | |
template_search.preview_cols, | |
1.0f); | |
} | |
static void template_search_add_button_searchmenu(const bContext *C, | |
uiLayout *layout, | |
uiBlock *block, | |
TemplateSearch *template_search, | |
const bool editable, | |
const bool live_icon) | |
{ | |
const char *ui_description = RNA_property_ui_description( | |
template_search->search_data.target_prop); | |
template_add_button_search_menu(C, | |
layout, | |
block, | |
&template_search->search_data.target_ptr, | |
template_search->search_data.target_prop, | |
template_search_menu, | |
MEM_dupallocN(template_search), | |
ui_description, | |
template_search->use_previews, | |
editable, | |
live_icon); | |
} | |
static void template_search_add_button_name(uiBlock *block, | |
PointerRNA *active_ptr, | |
const StructRNA *type) | |
{ | |
PropertyRNA *name_prop = RNA_struct_name_property(type); | |
const int width = template_search_textbut_width(active_ptr, name_prop); | |
const int height = template_search_textbut_height(); | |
uiDefAutoButR(block, active_ptr, name_prop, 0, "", ICON_NONE, 0, 0, width, height); | |
} | |
static void template_search_add_button_operator(uiBlock *block, | |
const char *const operator_name, | |
const wmOperatorCallContext opcontext, | |
const int icon, | |
const bool editable) | |
{ | |
if (!operator_name) { | |
return; | |
} | |
uiBut *but = uiDefIconButO( | |
block, UI_BTYPE_BUT, operator_name, opcontext, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, NULL); | |
if (!editable) { | |
UI_but_drawflag_enable(but, UI_BUT_DISABLED); | |
} | |
} | |
static void template_search_buttons(const bContext *C, | |
uiLayout *layout, | |
TemplateSearch *template_search, | |
const char *newop, | |
const char *unlinkop) | |
{ | |
uiBlock *block = uiLayoutGetBlock(layout); | |
uiRNACollectionSearch *search_data = &template_search->search_data; | |
StructRNA *type = RNA_property_pointer_type(&search_data->target_ptr, search_data->target_prop); | |
const bool editable = RNA_property_editable(&search_data->target_ptr, search_data->target_prop); | |
PointerRNA active_ptr = RNA_property_pointer_get(&search_data->target_ptr, | |
search_data->target_prop); | |
if (active_ptr.type) { | |
/* can only get correct type when there is an active item */ | |
type = active_ptr.type; | |
} | |
uiLayoutRow(layout, true); | |
UI_block_align_begin(block); | |
template_search_add_button_searchmenu(C, layout, block, template_search, editable, false); | |
template_search_add_button_name(block, &active_ptr, type); | |
template_search_add_button_operator( | |
block, newop, WM_OP_INVOKE_DEFAULT, ICON_DUPLICATE, editable); | |
template_search_add_button_operator(block, unlinkop, WM_OP_INVOKE_REGION_WIN, ICON_X, editable); | |
UI_block_align_end(block); | |
} | |
static PropertyRNA *template_search_get_searchprop(PointerRNA *targetptr, | |
PropertyRNA *targetprop, | |
PointerRNA *searchptr, | |
const char *const searchpropname) | |
{ | |
PropertyRNA *searchprop; | |
if (searchptr && !searchptr->data) { | |
searchptr = NULL; | |
} | |
if (!searchptr && !searchpropname) { | |
/* both NULL means we don't use a custom rna collection to search in */ | |
} | |
else if (!searchptr && searchpropname) { | |
RNA_warning("searchpropname defined (%s) but searchptr is missing", searchpropname); | |
} | |
else if (searchptr && !searchpropname) { | |
RNA_warning("searchptr defined (%s) but searchpropname is missing", | |
RNA_struct_identifier(searchptr->type)); | |
} | |
else if (!(searchprop = RNA_struct_find_property(searchptr, searchpropname))) { | |
RNA_warning("search collection property not found: %s.%s", | |
RNA_struct_identifier(searchptr->type), | |
searchpropname); | |
} | |
else if (RNA_property_type(searchprop) != PROP_COLLECTION) { | |
RNA_warning("search collection property is not a collection type: %s.%s", | |
RNA_struct_identifier(searchptr->type), | |
searchpropname); | |
} | |
/* check if searchprop has same type as targetprop */ | |
else if (RNA_property_pointer_type(searchptr, searchprop) != | |
RNA_property_pointer_type(targetptr, targetprop)) { | |
RNA_warning("search collection items from %s.%s are not of type %s", | |
RNA_struct_identifier(searchptr->type), | |
searchpropname, | |
RNA_struct_identifier(RNA_property_pointer_type(targetptr, targetprop))); | |
} | |
else { | |
return searchprop; | |
} | |
return NULL; | |
} | |
static TemplateSearch *template_search_setup(PointerRNA *ptr, | |
const char *const propname, | |
PointerRNA *searchptr, | |
const char *const searchpropname) | |
{ | |
PropertyRNA *prop = RNA_struct_find_property(ptr, propname); | |
if (!prop || RNA_property_type(prop) != PROP_POINTER) { | |
RNA_warning("pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); | |
return NULL; | |
} | |
PropertyRNA *searchprop = template_search_get_searchprop(ptr, prop, searchptr, searchpropname); | |
TemplateSearch *template_search = MEM_callocN(sizeof(*template_search), __func__); | |
template_search->search_data.target_ptr = *ptr; | |
template_search->search_data.target_prop = prop; | |
template_search->search_data.search_ptr = *searchptr; | |
template_search->search_data.search_prop = searchprop; | |
return template_search; | |
} | |
void uiTemplateSearch(uiLayout *layout, | |
Cxt *C, | |
ApiPtr *ptr, | |
const char *propname, | |
ApiPtr *searchptr, | |
const char *searchpropname, | |
const char *newop, | |
const char *unlinkop) | |
{ | |
TemplateSearch *template_search = template_search_setup( | |
ptr, propname, searchptr, searchpropname); | |
if (template_search != NULL) { | |
template_search_btns(C, layout, template_search, newop, unlinkop); | |
mem_free(template_search); | |
} | |
} | |
void uiTemplateSearchPreview(uiLayout *layout, | |
Cxt *C, | |
ApiPtr *ptr, | |
const char *propname, | |
ApiPtr *searchptr, | |
const char *searchpropname, | |
const char *newop, | |
const char *unlinkop, | |
const int rows, | |
const int cols) | |
{ | |
TemplateSearch *template_search = template_search_setup( | |
ptr, propname, searchptr, searchpropname); | |
if (template_search != NULL) { | |
template_search->use_previews = true; | |
template_search->preview_rows = rows; | |
template_search->preview_cols = cols; | |
template_search_btns(C, layout, template_search, newop, unlinkop); | |
mem_free(template_search); | |
} | |
} | |
/* Api Path Builder Template */ | |
void uiTemplatePathBuilder(uiLayout *layout, | |
ApiPtr *ptr, | |
const char *propname, | |
ApiPtr *UNUSED(root_ptr), | |
const char *text) | |
{ | |
/* check that properties are valid */ | |
ApiProp *propPath = api_struct_find_prop(ptr, propname); | |
if (!propPath || api_prop_type(propPath) != PROP_STRING) { | |
api_warning("path prop not found: %s.%s", api_struct_id(ptr->type), propname); | |
return; | |
} | |
/* Start drawing UI Elements using standard defines */ | |
uiLayout *row = uiLayoutRow(layout, true); | |
/* Path (existing string) Widget */ | |
uiItemR(row, ptr, propname, 0, text, ICON_RNA); | |
/* TODO: attach something to this to make allow | |
* searching of nested props to 'build' the path */ | |
/* Mods Template | |
* For building the panel layout for the active object's mods. */ | |
static void mod_panel_id(void *md_link, char *r_name) | |
{ | |
ModData *md = (ModData *)md_link; | |
dune_mod_type_panel_id(md->type, r_name); | |
} | |
void uiTemplateMods(uiLayout *UNUSED(layout), Cxt *C) | |
{ | |
ARegion *region = cxt_wm_region(C); | |
Object *ob = ed_object_active_cxt(C); | |
List *mods = &ob->mods; | |
const bool panels_match = ui_panel_list_matches_data(region, mods, mod_panel_id); | |
if (!panels_match) { | |
ui_panels_free_instanced(C, region); | |
ModData *md = mods->first; | |
for (int i = 0; md; i++, md = md->next) { | |
const ModTypeInfo *mti = dune_mod_get_info(md->type); | |
if (mti->panelRegister == NULL) { | |
continue; | |
} | |
char panel_idname[MAX_NAME]; | |
mod_panel_id(md, panel_idname); | |
/* Create custom data api ptr. */ | |
ApiPtr *md_ptr = mem_malloc(sizeof(ApiPtr), "panel customdata"); | |
api_ptr_create(&ob->id, &ApiMod, md, md_ptr); | |
ui_panel_add_instanced(C, region, ®ion->panels, panel_idname, md_ptr); | |
} | |
} | |
else { | |
/* Assuming there's only one group of instanced panels, update the custom data ptrs. */ | |
Panel *panel = region->panels.first; | |
LIST_FOREACH (ModData *, md, mods) { | |
const ModTypeInfo *mti = dune_mod_get_info(md->type); | |
if (mti->panelRegister == NULL) { | |
continue; | |
} | |
/* Move to the next instanced panel corresponding to the next modifier. */ | |
while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { | |
panel = panel->next; | |
lib_assert(panel != NULL); /* There shouldn't be fewer panels than modifiers with UIs. */ | |
} | |
ApiPtr *md_ptr = mem_malloc(sizeof(ApiPtr), "panel customdata"); | |
RNA_pointer_create(&ob->id, &RNA_Modifier, md, md_ptr); | |
UI_panel_custom_data_set(panel, md_ptr); | |
panel = panel->next; | |
} | |
} | |
} | |
/* Constraints Template | |
* For building the panel layout for the active object or bone's constraints. */ | |
/* For building the panel UI for constraints. */ | |
#define CONSTRAINT_TYPE_PANEL_PREFIX "OBJECT_PT_" | |
#define CONSTRAINT_BONE_TYPE_PANEL_PREFIX "BONE_PT_" | |
/* Check if the panel's Id starts with 'BONE', meaning it is a bone constraint. */ | |
static bool constraint_panel_is_bone(Panel *panel) | |
{ | |
return (panel->panelname[0] == 'B') && (panel->panelname[1] == 'O') && | |
(panel->panelname[2] == 'N') && (panel->panelname[3] == 'E'); | |
} | |
/* Move a constraint to the index it's moved to after a drag and drop. */ | |
static void constraint_reorder(Cxt *C, Panel *panel, int new_index) | |
{ | |
const bool constraint_from_bone = constraint_panel_is_bone(panel); | |
ApiPtr *con_ptr = ui_panel_custom_data_get(panel); | |
Constraint *con = (Constraint *)con_ptr->data; | |
ApiPtr props_ptr; | |
WinOpType *ot = wm_optype_find("CONSTRAINT_OT_move_to_index", false); | |
wm_op_props_create_ptr(&props_ptr, ot); | |
api_string_set(&props_ptr, "constraint", con->name); | |
api_int_set(&props_ptr, "index", new_index); | |
/* Set owner to EDIT_CONSTRAINT_OWNER_OBJECT or EDIT_CONSTRAINT_OWNER_BONE. */ | |
api_enum_set(&props_ptr, "owner", constraint_from_bone ? 1 : 0); | |
wm_op_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, NULL); | |
wm_op_props_free(&props_ptr); | |
} | |
/* Get the expand flag from the active constraint to use for the panel. */ | |
static short get_constraint_expand_flag(const Cxt *UNUSED(C), Panel *panel) | |
{ | |
ApiPtr *con_ptr = ui_panel_custom_data_get(panel); | |
Constraint *con = (Constraint *)con_ptr->data; | |
return con->ui_expand_flag; | |
} | |
/* Save the expand flag for the panel and sub-panels to the constraint. */ | |
static void set_constraint_expand_flag(const Cxt *UNUSED(C), Panel *panel, short expand_flag) | |
{ | |
ApiPtr *con_ptr = ui_panel_custom_data_get(panel); | |
Constraint *con = (Constraint *)con_ptr->data; | |
con->ui_expand_flag = expand_flag; | |
} | |
/* Fn with void * argument for uiListPanelIdFromDataFn. | |
* Constraint panel types are assumed to be named with the struct name field | |
* concatenated to the defined prefix. */ | |
static void object_constraint_panel_id(void *md_link, char *r_name) | |
{ | |
Constraint *con = (bConstraint *)md_link; | |
const ConstraintTypeInfo *cti = dune_constraint_typeinfo_from_type(con->type); | |
/* Cannot get TypeInfo for invalid/legacy constraints. */ | |
if (cti == NULL) { | |
return; | |
} | |
strcpy(r_name, CONSTRAINT_TYPE_PANEL_PREFIX); | |
strcat(r_name, cti->structName); | |
} | |
static void bone_constraint_panel_id(void *md_link, char *r_name) | |
{ | |
Constraint *con = (Constraint *)md_link; | |
const ConstraintTypeInfo *cti = dune_constraint_typeinfo_from_type(con->type); | |
/* Cannot get TypeInfo for invalid/legacy constraints. */ | |
if (cti == NULL) { | |
return; | |
} | |
strcpy(r_name, CONSTRAINT_BONE_TYPE_PANEL_PREFIX); | |
strcat(r_name, cti->structName); | |
} | |
void uiTemplateConstraints(uiLayout *UNUSED(layout), Cxt *C, bool use_bone_constraints) | |
{ | |
ARegion *region = cxt_win_region(C); | |
Object *ob = ed_object_active_cxt(C); | |
List *constraints = {NULL}; | |
if (use_bone_constraints) { | |
constraints = ed_object_pose_constraint_list(C); | |
} | |
else if (ob != NULL) { | |
constraints = &ob->constraints; | |
} | |
/* Switch between the bone panel Id fn and the object panel Id fn. */ | |
uiListPanelIdFromDataFn panel_id_fn = use_bone_constraints ? bone_constraint_panel_id : | |
object_constraint_panel_id; | |
const bool panels_match = ui_panel_list_matches_data(region, constraints, panel_id_fn); | |
if (!panels_match) { | |
ui_panels_free_instanced(C, region); | |
Constraint *con = (constraints == NULL) ? NULL : constraints->first; | |
for (int i = 0; con; i++, con = con->next) { | |
/* Don't show invalid/legacy constraints. */ | |
if (con->type == CONSTRAINT_TYPE_NULL) { | |
continue; | |
} | |
/* Don't show temporary constraints (AutoIK and target-less IK constraints). */ | |
if (con->type == CONSTRAINT_TYPE_KINEMATIC) { | |
KinematicConstraint *data = con->data; | |
if (data->flag & CONSTRAINT_IK_TEMP) { | |
continue; | |
} | |
} | |
char panel_idname[MAX_NAME]; | |
panel_id_fn(con, panel_idname); | |
/* Create custom data api ptr */ | |
ApiPtr *con_ptr = mem_malloc(sizeof(ApiPtr), "panel customdata"); | |
api_ptr_create(&ob->id, &ApiConstraint, con, con_ptr); | |
Panel *new_panel = ui_panel_add_instanced(C, region, ®ion->panels, panel_idname, con_ptr); | |
if (new_panel) { | |
/* Set the list panel functionality function pointers since we don't do it with python. */ | |
new_panel->type->set_list_data_expand_flag = set_constraint_expand_flag; | |
new_panel->type->get_list_data_expand_flag = get_constraint_expand_flag; | |
new_panel->type->reorder = constraint_reorder; | |
} | |
} | |
} | |
else { | |
/* Assuming there's only one group of instanced panels, update the custom data pointers. */ | |
Panel *panel = region->panels.first; | |
LIST_FOREACH (Constraint *, con, constraints) { | |
/* Don't show invalid/legacy constraints. */ | |
if (con->type == CONSTRAINT_TYPE_NULL) { | |
continue; | |
} | |
/* Don't show temporary constraints (AutoIK and target-less IK constraints). */ | |
if (con->type == CONSTRAINT_TYPE_KINEMATIC) { | |
KinematicConstraint *data = con->data; | |
if (data->flag & CONSTRAINT_IK_TEMP) { | |
continue; | |
} | |
} | |
/* Move to the next instanced panel corresponding to the next constraint. */ | |
while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { | |
panel = panel->next; | |
lib_assert(panel != NULL); /* There shouldn't be fewer panels than constraint panels. */ | |
} | |
ApiPtr *con_ptr = mem_mallocn(sizeof(ApiPtr), "constraint panel customdata"); | |
api_ptr_create(&ob->id, &ApiConstraint, con, con_ptr); | |
ui_panel_custom_data_set(panel, con_ptr); | |
panel = panel->next; | |
} | |
} | |
} | |
#undef CONSTRAINT_TYPE_PANEL_PREFIX | |
#undef CONSTRAINT_BONE_TYPE_PANEL_PREFIX | |
/* Pen Mods Template */ | |
/* Fn with void * argument for uiListPanelIdFromDataFn. */ | |
static void pen_mod_panel_id(void *md_link, char *r_name) | |
{ | |
ModData *md = (ModData *)md_link; | |
dune_pen_modType_panel_id(md->type, r_name); | |
} | |
void uiTemplatePenMods(uiLayout *UNUSED(layout), Cxt *C) | |
{ | |
ARegion *region = cxt_wm_region(C); | |
Object *ob = ed_object_active_cxt(C); | |
List *mods = &ob->pen_mods; | |
const bool panels_match = ui_panel_list_matches_data( | |
region, mods, pen_mod_panel_id); | |
if (!panels_match) { | |
ui_panels_free_instanced(C, region); | |
PenModData *md = mods->first; | |
for (int i = 0; md; i++, md = md->next) { | |
const PenModTypeInfo *mti = dune_pen_mod_get_info(md->type); | |
if (mti->panelRegister == NULL) { | |
continue; | |
} | |
char panel_idname[MAX_NAME]; | |
pen_mod_panel_id(md, panel_idname); | |
/* Create custom data RNA pointer. */ | |
ApiPtr *md_ptr = mem_malloc(sizeof(ApiPtr), "panel customdata"); | |
api_ptr_create(&ob->id, &ApiPenMod, md, md_ptr); | |
ui_panel_add_instanced(C, region, ®ion->panels, panel_idname, md_ptr); | |
} | |
} | |
else { | |
/* Assuming there's only one group of instanced panels, update the custom data ptr */ | |
Panel *panel = region->panels.first; | |
LIST_FOREACH (ModData *, md, mods) { | |
const PenModTypeInfo *mti = dune_pen_mod_get_info(md->type); | |
if (mti->panelRegister == NULL) { | |
continue; | |
} | |
/* Move to the next instanced panel corresponding to the next mod */ | |
while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { | |
panel = panel->next; | |
lib_assert(panel != NULL); /* There shouldn't be fewer panels than mods with UIs */ | |
} | |
ApiPtr *md_ptr = mem_malloc(sizeof(ApiPtr), "panel customdata"); | |
api_ptr_create(&ob->id, &ApiPenMod, md, md_ptr); | |
ui_panel_custom_data_set(panel, md_ptr); | |
panel = panel->next; | |
} | |
} | |
} | |
#define ERROR_LIBDATA_MESSAGE TIP_("Can't edit external library data") | |
/* ShaderFx Template | |
* For building the panel layout for the active object's pen shader effects. | |
* Fn with void * argument for uiListPanelIdFromDataFn. */ | |
static void shaderfx_panel_id(void *fx_v, char *r_idname) | |
{ | |
ShaderFxData *fx = (ShaderFxData *)fx_v; | |
dune_shaderfxType_panel_id(fx->type, r_idname); | |
} | |
void uiTemplateShaderFx(uiLayout *UNUSED(layout), Cxt *C) | |
{ | |
ARegion *region = cxt_wm_region(C); | |
Object *ob = ed_object_active_cxt(C); | |
List *shaderfx = &ob->shader_fx; | |
const bool panels_match = ui_panel_list_matches_data(region, shaderfx, shaderfx_panel_id); | |
if (!panels_match) { | |
ui_panels_free_instanced(C, region); | |
ShaderFxData *fx = shaderfx->first; | |
for (int i = 0; fx; i++, fx = fx->next) { | |
char panel_idname[MAX_NAME]; | |
shaderfx_panel_id(fx, panel_idname); | |
/* Create custom data RNA pointer. */ | |
ApiPtr *fx_ptr = mem_malloc(sizeof(ApiPtr), "panel customdata"); | |
api_ptr_create(&ob->id, &ApiShaderFx, fx, fx_ptr); | |
ui_panel_add_instanced(C, region, ®ion->panels, panel_idname, fx_ptr); | |
} | |
} | |
else { | |
/* Assuming there's only one group of instanced panels, update the custom data pointers. */ | |
Panel *panel = region->panels.first; | |
LIST_FOREACH (ShaderFxData *, fx, shaderfx) { | |
const ShaderFxTypeInfo *fxi = dune_shaderfx_get_info(fx->type); | |
if (fxi->panelRegister == NULL) { | |
continue; | |
} | |
/* Move to the next instanced panel corresponding to the next mod */ | |
while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { | |
panel = panel->next; | |
lib_assert(panel != NULL); /* There shouldn't be fewer panels than mods with UIs. */ | |
} | |
ApiPtr *fx_ptr = mem_malloc(sizeof(ApiPtr), "panel customdata"); | |
api_ptr_create(&ob->id, &ApiShaderFx, fx, fx_ptr); | |
ui_panel_custom_data_set(panel, fx_ptr); | |
panel = panel->next; | |
} | |
} | |
} | |
/* Op Prop Btns Template */ | |
typedef struct uiTemplateOpPropPollParam { | |
const Cxt *C; | |
WinOp *op; | |
short flag; | |
} uiTemplateOpPropPollParam; | |
#ifdef USE_OP_RESET_BTN | |
static void ui_layout_op_btns__reset_cb(Cxt *UNUSED(C), | |
void *op_pt, | |
void *UNUSED(arg_dummy2)) | |
{ | |
win_op_props_reset((WinOp *)op_pt); | |
} | |
#endif | |
static bool ui_layout_op_btns_poll_prop(struct ApiPtr *UNUSED(ptr), | |
struct ApiProp *prop, | |
void *user_data) | |
{ | |
uiTemplateOpPropPollParam *params = user_data; | |
if ((params->flag & UI_TEMPLATE_OP_PROPS_HIDE_ADVANCED) && | |
(api_prop_tags(prop) & OP_PROP_TAG_ADVANCED)) { | |
return false; | |
} | |
return params->op->type->poll_prop(params->C, params->op, prop); | |
} | |
static eAutoPropBtnsReturn template_op_prop_btns_draw_single( | |
const Cxt *C, | |
WinOp *op, | |
uiLayout *layout, | |
const eBtnLabelAlign label_align, | |
int layout_flags) | |
{ | |
uiBlock *block = uiLayoutGetBlock(layout); | |
eAutoPropBtnsReturn return_info = 0; | |
if (!op->props) { | |
const IdPropTemplate val = {0}; | |
op->props = IDP_New(IDP_GROUP, &val, "wmOpProps"); | |
} | |
/* poll() on this op may still fail, | |
* at the moment there is no nice feedback when this happens just fails silently. */ | |
if (!win_op_repeat_check(C, op)) { | |
UI_block_lock_set(block, true, "Operator can't redo"); | |
return return_info; | |
} | |
/* useful for macros where only one of the steps can't be re-done */ | |
ui_block_lock_clear(block); | |
if (layout_flags & UI_TEMPLATE_OP_PROPS_SHOW_TITLE) { | |
uiItemL(layout, wm_optype_name(op->type, op->ptr), ICON_NONE); | |
} | |
/* menu */ | |
if (op->type->flag & OPTYPE_PRESET) { | |
/* No simple way to get WM_MT_op_presets.bl_label | |
* from python! Label remains the same always! */ | |
ApiPtr op_ptr; | |
uiLayout *row; | |
block->ui_op = op; | |
row = uiLayoutRow(layout, true); | |
uiItemM(row, "WM_MT_op_presets", NULL, ICON_NONE); | |
WinOpType *ot = win_optype_find("WM_OT_op_preset_add", false); | |
uiItemFullO_ptr(row, ot, "", ICON_ADD, NULL, WIN_OP_INVOKE_DEFAULT, 0, &op_ptr); | |
api_string_set(&op_ptr, "op", op->type->idname); | |
uiItemFullO_ptr(row, ot, "", ICON_REMOVE, NULL, WIN_OP_INVOKE_DEFAULT, 0, &op_ptr); | |
api_string_set(&op_ptr, "op", op->type->idname); | |
api_bool_set(&op_ptr, "remove_active", true); | |
} | |
if (op->type->ui) { | |
op->layout = layout; | |
op->type->ui((Cxt *)C, op); | |
op->layout = NULL; | |
/* UI_LAYOUT_OP_SHOW_EMPTY ignored. retun_info is ignored too. | |
* We could allow WinOpType.ui cb to return this, but not needed right now. */ | |
} | |
else { | |
WinManager *wm = cxt_wm(C); | |
uiTemplateOpPropPollParam user_data = {.C = C, .op = op, .flag = layout_flags}; | |
const bool use_prop_split = (layout_flags & UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT) == 0; | |
ApiPtr ptr; | |
api_ptr_create(&wm->id, op->type->sapi, op->props, &ptr); | |
uiLayoutSetPropSep(layout, use_prop_split); | |
uiLayoutSetPropDecorate(layout, false); | |
/* main draw call */ | |
return_info = uiDefAutoBtnsApi( | |
layout, | |
&ptr, | |
op->type->poll_prop ? ui_layout_op_btns_poll_prop : NULL, | |
op->type->poll_prop ? &user_data : NULL, | |
op->type->prop, | |
label_align, | |
(layout_flags & UI_TEMPLATE_OP_PROPS_COMPACT)); | |
if ((return_info & UI_PROP_BUTS_NONE_ADDED) && | |
(layout_flags & UI_TEMPLATE_OP_PROPS_SHOW_EMPTY)) { | |
uiItemL(layout, IFACE_("No Properties"), ICON_NONE); | |
} | |
} | |
#ifdef USE_OP_RESET_BTN | |
/* its possible that reset can do nothing if all have PROP_SKIP_SAVE enabled | |
* but this is not so important if this button is drawn in those cases | |
* (which isn't all that likely anyway) - campbell */ | |
if (op->props->len) { | |
uiBut *but; | |
uiLayout *col; /* needed to avoid alignment errors with previous buttons */ | |
col = uiLayoutColumn(layout, false); | |
block = uiLayoutGetBlock(col); | |
but = uiDefIconTextBtn("block icon-file_refresh iface-reset", | |
("Reset operator defaults")); | |
btn_fn_set(but, ui_layout_op_btns__reset_cb, op, NULL); | |
} | |
#endif | |
/* set various special settings for btns */ | |
/* Only do this if we're not refreshing an existing UI. */ | |
if (block->oldblock == NULL) { | |
const bool is_popup = (block->flag & UI_BLOCK_KEEP_OPEN) != 0; | |
LIST_FOREACH (uiBtn *, btn, &block->btns) { | |
/* no undo for buttons for op redo panels */ | |
btn_flag_disable(btn, UI_BTN_UNDO); | |
/* only for popups, see T36109. */ | |
/* if btn is op's default prop, and a text-field, enable focus for it | |
* - this is used for allowing ops with popups to rename stuff with fewer clicks */ | |
if (is_popup) { | |
if ((btn->apiprop == op->type->prop) && (btn->type == UI_BTYPE_TEXT)) { | |
btn_focus_on_enter_event(cxt_win(C), btn); | |
} | |
} | |
} | |
} | |
return return_info; | |
} | |
static void template_op_prop_btns_draw_recursive(const Cxt *C, | |
WinOp *op, | |
uiLayout *layout, | |
const eBtnLabelAlign label_align, | |
int layout_flags, | |
bool *r_has_advanced) | |
{ | |
if (op->type->flag & OPTYPE_MACRO) { | |
LIST_FOREACH (WinOp *, macro_op, &op->macro) { | |
template_op_prop_btns_draw_recursive( | |
C, macro_op, layout, label_align, layout_flags, r_has_advanced); | |
} | |
} | |
else { | |
/* Might want to make label_align adjustable somehow. */ | |
eAutoPropBtnsReturn return_info = template_op_prop_btns_draw_single( | |
C, op, layout, label_align, layout_flags); | |
if (return_info & UI_PROP_BTNS_ANY_FAILED_CHECK) { | |
if (r_has_advanced) { | |
*r_has_advanced = true; | |
} | |
} | |
} | |
} | |
static bool ui_layout_op_props_only_bools(const Cxt *C, | |
WinManager *wm, | |
WinOp *op, | |
int layout_flags) | |
{ | |
if (op->type->flag & OPTYPE_MACRO) { | |
LIST_FOREACH (WinOp *, macro_op, &op->macro) { | |
if (!ui_layout_op_props_only_bools(C, wm, macro_op, layout_flags)) { | |
return false; | |
} | |
} | |
} | |
else { | |
uiTemplateOpPropPollParam user_data = {.C = C, .op = op, .flag = layout_flags}; | |
ApiPtr ptr; | |
api_ptr_create(&wm->id, op->type->sapi, op->props, &ptr); | |
API_STRUCT_BEGIN (&ptr, prop) { | |
if (api_prop_flag(prop) & PROP_HIDDEN) { | |
continue; | |
} | |
if (op->type->poll_prop && | |
!ui_layout_op_buts_poll_prop(&ptr, prop, &user_data)) { | |
continue; | |
} | |
if (api_prop_type(prop) != PROP_BOOL) { | |
return false; | |
} | |
} | |
API_STRUCT_END; | |
} | |
return true; | |
} | |
void uiTemplateOpPropBtns( | |
const Cxt *C, uiLayout *layout, WinOp *op, eBtnLabelAlign label_align, short flag) | |
{ | |
WinManager *wm = cxt_WinManager(C); | |
/* If there are only checkbox items, don't use split layout by default. It looks weird if the | |
* check-boxes only use half the width. */ | |
if (ui_layout_op_props_only_bools(C, wm, op, flag)) { | |
flag |= UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT; | |
} | |
template_op_prop_btns_draw_recursive(C, op, layout, label_align, flag, NULL); | |
} | |
void uiTemplateOpRedoProps(uiLayout *layout, const Cxt *C) | |
{ | |
WinOp *op = win_op_last_redo(C); | |
uiBlock *block = uiLayoutGetBlock(layout); | |
if (op == NULL) { | |
return; | |
} | |
/* Disable for now, doesn't fit well in popover. */ | |
#if 0 | |
/* Repeat btn with op name as text. */ | |
uiItemFullO(layout, | |
"SCREEN_OT_repeat_last", | |
win_optype_name(op->type, op->ptr), | |
ICON_NONE, | |
NULL, | |
WM_OP_INVOKE_DEFAULT, | |
0, | |
NULL); | |
#endif | |
if (win_op_repeat_check(C, op)) { | |
int layout_flags = 0; | |
if (block->panel == NULL) { | |
layout_flags = UI_TEMPLATE_OP_PROPS_SHOW_TITLE; | |
} | |
#if 0 | |
bool has_advanced = false; | |
#endif | |
ui_block_fn_handle_set(block, ed_undo_op_repeat_cb_evt, op); | |
template_op_prop_btns_draw_recursive( | |
C, op, layout, UI_BUT_LABEL_ALIGN_NONE, layout_flags, NULL /* &has_advanced */); | |
/* Warning! this leaves the handle fn for any other users of this block. */ | |
#if 0 | |
if (has_advanced) { | |
uiItemO(layout, IFACE_("More..."), ICON_NONE, "SCREEN_OT_redo_last"); | |
} | |
#endif | |
} | |
} | |
/* Constraint Header Template */ | |
#define ERROR_LIBDATA_MESSAGE TIP_("Can't edit external library data") | |
static void constraint_active_fn(Cxt *UNUSED(C), void *ob_v, void *con_v) | |
{ | |
ed_object_constraint_active_set(ob_v, con_v); | |
} | |
static void constraint_ops_extra_draw(Cxt *C, uiLayout *layout, void *con_v) | |
{ | |
ApiPtr op_ptr; | |
uiLayout *row; | |
Constraint *con = (Constraint *)con_v; | |
ApiPtr ptr; | |
Object *ob = ed_object_active_cxt(C); | |
api_ptr_create(&ob->id, &ApiConstraint, con, &ptr); | |
uiLayoutSetCxtPtr(layout, "constraint", &ptr); | |
uiLayoutSetOpCxt(layout, WIN_OP_INVOKE_DEFAULT); | |
uiLayoutSetUnitsX(layout, 4.0f); | |
/* Apply. */ | |
uiItemO(layout, | |
CXT_IFACE_(LANG_CXT_OP_DEFAULT, "Apply"), | |
ICON_CHECKMARK, | |
"CONSTRAINT_OT_apply"); | |
/* Duplicate. */ | |
uiItemO(layout, | |
CTX_IFACE_(LANG_CXT_OP_DEFAULT, "Duplicate"), | |
ICON_DUPLICATE, | |
"CONSTRAINT_OT_copy"); | |
uiItemO(layout, | |
CXT_IFACE_(LANG_CXT_OPERATOR_DEFAULT, "Copy to Selected"), | |
0, | |
"CONSTRAINT_OT_copy_to_selected"); | |
uiItemS(layout); | |
/* Move to first. */ | |
row = uiLayoutColumn(layout, false); | |
uiItemFullO(row, | |
"CONSTRAINT_OT_move_to_index", | |
IFACE_("Move to First"), | |
ICON_TRIA_UP, | |
NULL, | |
WIN_OP_INVOKE_DEFAULT, | |
0, | |
&op_ptr); | |
api_int_set(&op_ptr, "index", 0); | |
if (!con->prev) { | |
uiLayoutSetEnabled(row, false); | |
} | |
/* Move to last. */ | |
row = uiLayoutColumn(layout, false); | |
uiItemFullO(row, | |
"CONSTRAINT_OT_move_to_index", | |
IFACE_("Move to Last"), | |
ICON_TRIA_DOWN, | |
NULL, | |
WIN_OP_INVOKE_DEFAULT, | |
0, | |
&op_ptr); | |
List *constraint_list = ed_object_constraint_list_from_constraint(ob, con, NULL); | |
api_int_set(&op_ptr, "index", lib_list_count(constraint_list) - 1); | |
if (!con->next) { | |
uiLayoutSetEnabled(row, false); | |
} | |
} | |
static void draw_constraint_header(uiLayout *layout, Object *ob, Constraint *con) | |
{ | |
/* unless btn has own cb, it adds this cb to btn */ | |
uiBlock *block = uiLayoutGetBlock(layout); | |
ui_block_fn_set(block, constraint_active_fn, ob, con); | |
ApiPtr ptr; | |
api_ptr_create(&ob->id, &ApiConstraint, con, &ptr); | |
if (block->panel) { | |
ui_panel_cxt_ptr_set(block->panel, "constraint", &ptr); | |
} | |
else { | |
uiLayoutSetCxtPtr(layout, "constraint", &ptr); | |
} | |
/* Constraint type icon. */ | |
uiLayout *sub = uiLayoutRow(layout, false); | |
uiLayoutSetEmboss(sub, false); | |
uiLayoutSetRedAlert(sub, (con->flag & CONSTRAINT_DISABLE)); | |
uiItemL(sub, "", api_struct_ui_icon(ptr.type)); | |
ui_block_emboss_set(block, UI_EMBOSS); | |
uiLayout *row = uiLayoutRow(layout, true); | |
uiItemR(row, &ptr, "name", 0, "", ICON_NONE); | |
/* Enabled eye icon. */ | |
uiItemR(row, &ptr, "enabled", 0, "", ICON_NONE); | |
/* Extra operators menu. */ | |
uiItemMenuF(row, "", ICON_DOWNARROW_HLT, constraint_ops_extra_draw, con); | |
/* Close 'button' - emboss calls here disable drawing of 'button' behind X */ | |
sub = uiLayoutRow(row, false); | |
uiLayoutSetEmboss(sub, UI_EMBOSS_NONE); | |
uiLayoutSetOpCxt(sub, WIN_OP_INVOKE_DEFAULT); | |
uiItemO(sub, "", ICON_X, "CONSTRAINT_OT_delete"); | |
/* Some extra padding at the end, so the 'x' icon isn't too close to drag button. */ | |
uiItemS(layout); | |
/* clear any locks set up for proxies/lib-linking */ | |
ui_block_lock_clear(block); | |
} | |
void uiTemplateConstraintHeader(uiLayout *layout, ApiPtr *ptr) | |
{ | |
/* verify we have valid data */ | |
if (!api_struct_is_a(ptr->type, &ApiConstraint)) { | |
api_warning("Expected constraint on object"); | |
return; | |
} | |
Object *ob = (Object *)ptr->owner_id; | |
Constraint *con = ptr->data; | |
if (!ob || !(GS(ob->id.name) == ID_OB)) { | |
api_warning("Expected constraint on object"); | |
return; | |
} | |
ui_block_lock_set(uiLayoutGetBlock(layout), (ob && ID_IS_LINKED(ob)), ERROR_LIBDATA_MESSAGE); | |
draw_constraint_header(layout, ob, con); | |
} | |
/* Preview Template */ | |
#include "types_light.h" | |
#include "types_material.h" | |
#include "types_world.h" | |
#define B_MATPRV 1 | |
static void do_preview_btns(Cxt *C, void *arg, int event) | |
{ | |
switch (event) { | |
case B_MATPRV: | |
win_event_add_notifier(C, NC_MATERIAL | ND_SHADING_PREVIEW, arg); | |
break; | |
} | |
} | |
void uiTemplatePreview(uiLayout *layout, | |
Cxt *C, | |
Id *id, | |
bool show_btns, | |
Id *parent, | |
MTex *slot, | |
const char *preview_id) | |
{ | |
Material *ma = NULL; | |
Tex *tex = (Tex *)id; | |
short *pr_texture = NULL; | |
PointerRNA material_ptr; | |
PointerRNA texture_ptr; | |
char _preview_id[UI_MAX_NAME_STR]; | |
if (id && !ELEM(GS(id->name), ID_MA, ID_TE, ID_WO, ID_LA, ID_LS)) { | |
RNA_warning("Expected ID of type material, texture, light, world or line style"); | |
return; | |
} | |
/* decide what to render */ | |
ID *pid = id; | |
ID *pparent = NULL; | |
if (id && (GS(id->name) == ID_TE)) { | |
if (parent && (GS(parent->name) == ID_MA)) { | |
pr_texture = &((Material *)parent)->pr_texture; | |
} | |
else if (parent && (GS(parent->name) == ID_WO)) { | |
pr_texture = &((World *)parent)->pr_texture; | |
} | |
else if (parent && (GS(parent->name) == ID_LA)) { | |
pr_texture = &((Light *)parent)->pr_texture; | |
} | |
else if (parent && (GS(parent->name) == ID_LS)) { | |
pr_texture = &((FreestyleLineStyle *)parent)->pr_texture; | |
} | |
if (pr_texture) { | |
if (*pr_texture == TEX_PR_OTHER) { | |
pid = parent; | |
} | |
else if (*pr_texture == TEX_PR_BOTH) { | |
pparent = parent; | |
} | |
} | |
} | |
if (!preview_id || (preview_id[0] == '\0')) { | |
/* If no identifier given, generate one from ID type. */ | |
BLI_snprintf( | |
_preview_id, UI_MAX_NAME_STR, "uiPreview_%s", BKE_idtype_idcode_to_name(GS(id->name))); | |
preview_id = _preview_id; | |
} | |
/* Find or add the uiPreview to the current Region. */ | |
ARegion *region = CTX_wm_region(C); | |
uiPreview *ui_preview = BLI_findstring( | |
®ion->ui_previews, preview_id, offsetof(uiPreview, preview_id)); | |
if (!ui_preview) { | |
ui_preview = MEM_callocN(sizeof(uiPreview), "uiPreview"); | |
BLI_strncpy(ui_preview->preview_id, preview_id, sizeof(ui_preview->preview_id)); | |
ui_preview->height = (short)(UI_UNIT_Y * 7.6f); | |
BLI_addtail(®ion->ui_previews, ui_preview); | |
} | |
if (ui_preview->height < UI_UNIT_Y) { | |
ui_preview->height = UI_UNIT_Y; | |
} | |
else if (ui_preview->height > UI_UNIT_Y * 50) { /* Rather high upper limit, yet not insane! */ | |
ui_preview->height = UI_UNIT_Y * 50; | |
} | |
/* layout */ | |
uiBlock *block = uiLayoutGetBlock(layout); | |
uiLayout *row = uiLayoutRow(layout, false); | |
uiLayout *col = uiLayoutColumn(row, false); | |
uiLayoutSetKeepAspect(col, true); | |
/* add preview */ | |
uiDefBut(block, | |
UI_BTYPE_EXTRA, | |
0, | |
"", | |
0, | |
0, | |
UI_UNIT_X * 10, | |
ui_preview->height, | |
pid, | |
0.0, | |
0.0, | |
0, | |
0, | |
""); | |
UI_but_func_drawextra_set(block, ED_preview_draw, pparent, slot); | |
UI_block_func_handle_set(block, do_preview_buttons, NULL); | |
uiDefIconButS(block, | |
UI_BTYPE_GRIP, | |
0, | |
ICON_GRIP, | |
0, | |
0, | |
UI_UNIT_X * 10, | |
(short)(UI_UNIT_Y * 0.3f), | |
&ui_preview->height, | |
UI_UNIT_Y, | |
UI_UNIT_Y * 50.0f, | |
0.0f, | |
0.0f, | |
""); | |
/* add buttons */ | |
if (pid && show_buttons) { | |
if (GS(pid->name) == ID_MA || (pparent && GS(pparent->name) == ID_MA)) { | |
if (GS(pid->name) == ID_MA) { | |
ma = (Material *)pid; | |
} | |
else { | |
ma = (Material *)pparent; | |
} | |
/* Create RNA Pointer */ | |
RNA_pointer_create(&ma->id, &RNA_Material, ma, &material_ptr); | |
col = uiLayoutColumn(row, true); | |
uiLayoutSetScaleX(col, 1.5); | |
uiItemR(col, &material_ptr, "preview_render_type", UI_ITEM_R_EXPAND, "", ICON_NONE); | |
/* EEVEE preview file has baked lighting so use_preview_world has no effect, | |
* just hide the option until this feature is supported. */ | |
if (!BKE_scene_uses_blender_eevee(CTX_data_scene(C))) { | |
uiItemS(col); | |
uiItemR(col, &material_ptr, "use_preview_world", 0, "", ICON_WORLD); | |
} | |
} | |
if (pr_texture) { | |
/* Create RNA Pointer */ | |
RNA_pointer_create(id, &RNA_Texture, tex, &texture_ptr); | |
uiLayoutRow(layout, true); | |
uiDefButS(block, | |
UI_BTYPE_ROW, | |
B_MATPRV, | |
IFACE_("Texture"), | |
0, | |
0, | |
UI_UNIT_X * 10, | |
UI_UNIT_Y, | |
pr_texture, | |
10, | |
TEX_PR_TEXTURE, | |
0, | |
0, | |
""); | |
if (GS(parent->name) == ID_MA) { | |
uiDefButS(block, | |
UI_BTYPE_ROW, | |
B_MATPRV, | |
IFACE_("Material"), | |
0, | |
0, | |
UI_UNIT_X * 10, | |
UI_UNIT_Y, | |
pr_texture, | |
10, | |
TEX_PR_OTHER, | |
0, | |
0, | |
""); | |
} | |
else if (GS(parent->name) == ID_LA) { | |
uiDefBtnS("block type-row x-*10", | |
UI_BTYPE_ROW, | |
B_MATPRV, | |
CXT_IFACE_(LANG_CXT_ID_LIGHT, "Light"), | |
UI_UNIT_X * 10, | |
UI_UNIT_Y, | |
pr_texture, | |
TEX_PR_OTHER, | |
""); | |
} | |
else if (GS(parent->name) == ID_WO) { | |
uiDefBtnS(block, | |
UI_BTYPE_ROW, | |
B_MATPRV, | |
IFACE_("World"), | |
pr_texture, | |
10, | |
TEX_PR_OTHER, | |
""); | |
} | |
else if (GS(parent->name) == ID_LS) { | |
uiDefBtnS(block, | |
UI_BTYPE_ROW, | |
B_MATPRV, | |
IFACE_("Line Style"), | |
0, | |
0, | |
UI_UNIT_X * 10, | |
UI_UNIT_Y, | |
pr_texture, | |
10, | |
TEX_PR_OTHER, | |
0, | |
0, | |
""); | |
} | |
uiDefBtnS(block, | |
UI_BTYPE_ROW, | |
B_MATPRV, | |
IFACE_("Both"), | |
0, | |
0, | |
UI_UNIT_X * 10, | |
UI_UNIT_Y, | |
pr_texture, | |
10, | |
TEX_PR_BOTH, | |
0, | |
0, | |
""); | |
/* Alpha button for texture preview */ | |
if (*pr_texture != TEX_PR_OTHER) { | |
row = uiLayoutRow(layout, false); | |
uiItemR(row, &texture_ptr, "use_preview_alpha", 0, NULL, ICON_NONE); | |
} | |
} | |
} | |
} | |
/* ColorRamp Template */ | |
typedef struct ApiUpdateCb { | |
ApiPtr ptr; | |
ApiProp *prop; | |
} ApiUpdateCb; | |
static void api_update_cb(Cxt *C, void *arg_cb, void *UNUSED(arg)) | |
{ | |
ApiUpdateCb *cb = (ApiUpdateCb *)arg_cb; | |
/* we call update here on the pointer property, this way the | |
* owner of the curve mapping can still define its own update | |
* and notifier, even if the CurveMapping struct is shared. */ | |
api_prop_update(C, &cb->ptr, cb->prop); | |
} | |
enum { | |
CB_FN_FLIP, | |
CB_FN_DISTRIBUTE_LR, | |
CB_FN_DISTRIBUTE_EVENLY, | |
CB_FN_RESET, | |
}; | |
static void colorband_flip_cb(Cxt *C, ColorBand *coba) | |
{ | |
CBData data_tmp[MAXCOLORBAND]; | |
for (int a = 0; a < coba->tot; a++) { | |
data_tmp[a] = coba->data[coba->tot - (a + 1)]; | |
} | |
for (int a = 0; a < coba->tot; a++) { | |
data_tmp[a].pos = 1.0f - data_tmp[a].pos; | |
coba->data[a] = data_tmp[a]; | |
} | |
/* May as well flip the `cur`. */ | |
coba->cur = coba->tot - (coba->cur + 1); | |
ed_undo_push(C, "Flip Color Ramp"); | |
} | |
static void colorband_distribute_cb(Cxt *C, ColorBand *coba, bool evenly) | |
{ | |
if (coba->tot > 1) { | |
const int tot = evenly ? coba->tot - 1 : coba->tot; | |
const float gap = 1.0f / tot; | |
float pos = 0.0f; | |
for (int a = 0; a < coba->tot; a++) { | |
coba->data[a].pos = pos; | |
pos += gap; | |
} | |
ed_undo_push(C, evenly ? "Distribute Stops Evenly" : "Distribute Stops from Left"); | |
} | |
} | |
static void colorband_tools_dofn(bContext *C, void *coba_v, int event) | |
{ | |
ColorBand *coba = coba_v; | |
switch (event) { | |
case CB_FN_FLIP: | |
colorband_flip_cb(C, coba); | |
break; | |
case CB_FN_DISTRIBUTE_LR: | |
colorband_distribute_cb(C, coba, false); | |
break; | |
case CB_FN_DISTRIBUTE_EVENLY: | |
colorband_distribute_cb(C, coba, true); | |
break; | |
case CB_FN_RESET: | |
dune_colorband_init(coba, true); | |
ed_undo_push(C, "Reset Color Ramp"); | |
break; | |
} | |
ed_region_tag_redraw(cxt_win_region(C)); | |
} | |
static uiBlock *colorband_tools_fn(Cxt *C, ARegion *region, void *coba_v) | |
{ | |
const uiStyle *style = ui_style_get_dpi(); | |
ColorBand *coba = coba_v; | |
short yco = 0; | |
const short menuwidth = 10 * UI_UNIT_X; | |
uiBlock *block = ui_block_begin(C, region, __func__, UI_EMBOSS_PULLDOWN); | |
ui_block_fn_btnmenu_set(block, colorband_tools_dofn, coba); | |
uiLayout *layout = ui_block_layout(block, | |
UI_LAYOUT_VERTICAL, | |
UI_LAYOUT_MENU, | |
0, | |
0, | |
UI_MENU_WIDTH_MIN, | |
0, | |
UI_MENU_PADDING, | |
style); | |
ui_block_layout_set_current(block, layout); | |
{ | |
ApiPtr coba_ptr; | |
api_ptr_create(NULL, &ApiColorRamp, coba, &coba_ptr); | |
uiLayoutSetCxtPtr(layout, "color_ramp", &coba_ptr); | |
} | |
/* We could move these to operators, | |
* although this isn't important unless we want to assign key shortcuts to them. */ | |
{ | |
uiDefBtn("block icontext menu icon-Blank1 iface-("Flip Color Ramp")" , | |
menuwidth, | |
UI_UNIT_Y, | |
CB_FUNC_FLIP, | |
""); | |
uiBtn("block icontext menu icon-blank1 iface-(Distribute stops from Left)", | |
yco -= UI_UNIT_Y, | |
menuwidth, | |
UI_UNIT_Y, | |
CB_FUNC_DISTRIBUTE_LR, | |
""); | |
uiBtn("block icontext menu icon-blank1 iface-(Distribute Stops Evenl)", | |
yco -= UI_UNIT_Y, | |
menuwidth, | |
UI_UNIT_Y, | |
CB_FN_DISTRIBUTE_EVENLY, | |
""); | |
uiItemO(layout, IFACE_("Eyedropper"), ICON_EYEDROPPER, "UI_OT_eyedropper_colorramp"); | |
uiBtn("block icontext menu icon-blank1 cb_fn_reset iface-(Reset Color Ramp)", | |
yco -= UI_UNIT_Y, | |
menuwidth, | |
UI_UNIT_Y, | |
""); | |
} | |
ui_block_direction_set(block, UI_DIR_DOWN); | |
ui_block_bounds_set_text(block, 3.0f * UI_UNIT_X); | |
return block; | |
} | |
static void colorband_add_cb(Cxt *C, void *cb_v, void *coba_v) | |
{ | |
ColorBand *coba = coba_v; | |
float pos = 0.5f; | |
if (coba->tot > 1) { | |
if (coba->cur > 0) { | |
pos = (coba->data[coba->cur - 1].pos + coba->data[coba->cur].pos) * 0.5f; | |
} | |
else { | |
pos = (coba->data[coba->cur + 1].pos + coba->data[coba->cur].pos) * 0.5f; | |
} | |
} | |
if (dune_colorband_element_add(coba, pos)) { | |
api_update_cb(C, cb_v, NULL); | |
ed_undo_push(C, "Add Color Ramp Stop"); | |
} | |
} | |
static void colorband_del_cb(Cxt *C, void *cb_v, void *coba_v) | |
{ | |
ColorBand *coba = coba_v; | |
if (dune_colorband_element_remove(coba, coba->cur)) { | |
ed_undo_push(C, "Delete Color Ramp Stop"); | |
api_update_cb(C, cb_v, NULL); | |
} | |
} | |
static void colorband_update_cb(Cxt *UNUSED(C), void *bt_v, void *coba_v) | |
{ | |
uiBtn *btn = bt_v; | |
ColorBand *coba = coba_v; | |
/* Sneaky update here, we need to sort the color-band points to be in order, | |
* however the api ptr then is wrong, so we update it */ | |
dune_colorband_update_sort(coba); | |
btn->apiptr.data = coba->data + coba->cur; | |
} | |
static void colorband_btns_layout(uiLayout *layout, | |
uiBlock *block, | |
ColorBand *coba, | |
const rctf *butr, | |
ApiUpdateCb *cb, | |
int expand) | |
{ | |
uiBtn *btn; | |
const float unit = lib_rctf_size_x(butr) / 14.0f; | |
const float xs = butr->xmin; | |
const float ys = butr->ymin; | |
ApiPtr ptr; | |
api_ptr_create(cb->ptr.owner_id, &ApiColorRamp, coba, &ptr); | |
uiLayout *split = uiLayoutSplit(layout, 0.4f, false); | |
ui_block_emboss_set(block, UI_EMBOSS_NONE); | |
ui_block_align_begin(block); | |
uiLayout *row = uiLayoutRow(split, false); | |
btn = uiBtn("block icontext icon_add", | |
2.0f * unit, | |
UI_UNIT_Y, | |
TIP_("Add a new color stop to the color ramp")); | |
btn_fn_set(bt, colorband_add_cb, MEM_dupallocN(cb), coba); | |
btn = uiBtn("block icontext icon_remove", | |
xs + 2.0f * unit, | |
ys + UI_UNIT_Y, | |
2.0f * unit, | |
UI_UNIT_Y, | |
TIP_("Delete the active position")); | |
ui_btn_fn_set(bt, colorband_del_cb, mem_dupalloc(cb), coba); | |
btn = uiBtn(block iconblock icon_downarrow_hlt, | |
colorband_tools_fn, | |
coba, | |
0, | |
xs + 4.0f * unit, | |
ys + UI_UNIT_Y, | |
2.0f * unit, | |
UI_UNIT_Y, | |
TIP_("Tools")); | |
btn_fn_set(bt, api_update_cb, mem_dupalloc(cb), coba); | |
ui_block_align_end(block); | |
ui_block_emboss_set(block, UI_EMBOSS); | |
row = uiLayoutRow(split, false); | |
ui_block_align_begin(block); | |
uiItemR(row, &ptr, "color_mode", 0, "", ICON_NONE); | |
if (ELEM(coba->color_mode, COLBAND_BLEND_HSV, COLBAND_BLEND_HSL)) { | |
uiItemR(row, &ptr, "hue_interpolation", 0, "", ICON_NONE); | |
} | |
else { /* COLBAND_BLEND_RGB */ | |
uiItemR(row, &ptr, "interpolation", 0, "", ICON_NONE); | |
} | |
ui_block_align_end(block); | |
row = uiLayoutRow(layout, false); | |
btn = uiBtn(block, | |
UI_BTYPE_COLORBAND, | |
0, | |
"", | |
xs, | |
ys, | |
lib_rctf_size_x(butr), | |
UI_UNIT_Y, | |
coba, | |
""); | |
btn_fn_set(btn, api_update_cb, mem_dupalloc(cb), NULL); | |
row = uiLayoutRow(layout, false); | |
if (coba->tot) { | |
CBData *cbd = coba->data + coba->cur; | |
api_ptr_create(cb->ptr.owner_id, &ApiColorRampElement, cbd, &ptr); | |
if (!expand) { | |
split = uiLayoutSplit(layout, 0.3f, false); | |
row = uiLayoutRow(split, false); | |
btn = uiBtnS(block number, | |
5.0f * UI_UNIT_X, | |
UI_UNIT_Y, | |
&coba->cur, | |
0.0, | |
(float)(MAX2(0, coba->tot - 1)), | |
TIP_("Choose active color stop")); | |
ui_btn_number_step_size_set(bt, 1); | |
row = uiLayoutRow(split, false); | |
uiItemR(row, &ptr, "position", 0, IFACE_("Pos"), ICON_NONE); | |
btn = block->btns.last; | |
btn_fn_set(bt, colorband_update_cb, bt, coba); | |
row = uiLayoutRow(layout, false); | |
uiItemR(row, &ptr, "color", 0, "", ICON_NONE); | |
btn = block->btns.last; | |
btn_fn_set(bt, api_update_cb, mem_dupalloc(cb), NULL); | |
} | |
else { | |
split = uiLayoutSplit(layout, 0.5f, false); | |
uiLayout *subsplit = uiLayoutSplit(split, 0.35f, false); | |
row = uiLayoutRow(subsplit, false); | |
btn = uiBtnS("block number", | |
5.0f * UI_UNIT_X, | |
UI_UNIT_Y, | |
&coba->cur, | |
0.0, | |
(float)(MAX2(0, coba->tot - 1)), | |
TIP_("Choose active color stop")); | |
btn_number_step_size_set(btn, 1); | |
row = uiLayoutRow(subsplit, false); | |
uiItemR(row, &ptr, "position", UI_ITEM_R_SLIDER, IFACE_("Pos"), ICON_NONE); | |
btn = block->btns.last; | |
btn_fn_set(btn, colorband_update_cb, btn, coba); | |
row = uiLayoutRow(split, false); | |
uiItemR(row, &ptr, "color", 0, "", ICON_NONE); | |
bt = block->btns.last; | |
btn_fn_set(bt, api_update_cb, mem_dupalloc(cb), NULL); | |
} | |
} | |
} | |
void uiTemplateColorRamp(uiLayout *layout, ApiPtr *ptr, const char *propname, bool expand) | |
{ | |
ApiProp *prop = api_struct_find_prop(ptr, propname); | |
if (!prop || api_prop_type(prop) != PROP_PTR) { | |
return; | |
} | |
const ApiPtr cptr = api_prop_ptr_get(ptr, prop); | |
if (!cptr.data || !api_struct_is_a(cptr.type, &ApiColorRamp)) { | |
return; | |
} | |
ApiUpdateCb *cb = mem_calloc(sizeof(ApiUpdateCb), "ApiUpdateCb"); | |
cb->ptr = *ptr; | |
cb->prop = prop; | |
rctf rect; | |
rect.xmin = 0; | |
rect.xmax = 10.0f * UI_UNIT_X; | |
rect.ymin = 0; | |
rect.ymax = 19.5f * UI_UNIT_X; | |
uiBlock *block = uiLayoutAbsoluteBlock(layout); | |
Id *id = cptr.owner_id; | |
ui_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE); | |
colorband_btns_layout(layout, block, cptr.data, &rect, cb, expand); | |
ui_block_lock_clear(block); | |
mem_free(cb); | |
} | |
/* Icon Template */ | |
void uiTemplateIcon(uiLayout *layout, int icon_value, float icon_scale) | |
{ | |
uiBlock *block = uiLayoutAbsoluteBlock(layout); | |
uiBtn *btn = uiDefIconBtn("block label icon_x", | |
UI_UNIT_X * icon_scale, | |
UI_UNIT_Y * icon_scale, | |
""); | |
ui_def_btn_icon(but, icon_value, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); | |
} | |
/* Icon viewer Template */ | |
typedef struct IconViewMenuArgs { | |
ApiPtr ptr; | |
ApiProp *prop; | |
bool show_labels; | |
float icon_scale; | |
} IconViewMenuArgs; | |
/* Id Search browse menu, open */ | |
static uiBlock *ui_icon_view_menu_cb(Cxt *C, ARegion *region, void *arg_litem) | |
{ | |
static IconViewMenuArgs args; | |
/* arg_litem is malloced, can be freed by parent button */ | |
args = *((IconViewMenuArgs *)arg_litem); | |
const int w = UI_UNIT_X * (args.icon_scale); | |
const int h = UI_UNIT_X * (args.icon_scale + args.show_labels); | |
uiBlock *block = ui_block_begin(C, region, "_popup", UI_EMBOSS_PULLDOWN); | |
ui_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NO_FLIP); | |
ui_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); | |
bool free; | |
const EnumPropItem *item; | |
api_prop_enum_items(C, &args.ptr, args.prop, &item, NULL, &free); | |
for (int a = 0; item[a].id; a++) { | |
const int x = (a % 8) * w; | |
const int y = -(a / 8) * h; | |
const int icon = item[a].icon; | |
const int value = item[a].value; | |
uiBut *but; | |
if (args.show_labels) { | |
but = uiDefIconTextButR_prop(block, | |
UI_BTYPE_ROW, | |
0, | |
icon, | |
item[a].name, | |
x, | |
y, | |
w, | |
h, | |
&args.ptr, | |
args.prop, | |
-1, | |
0, | |
value, | |
-1, | |
-1, | |
NULL); | |
} | |
else { | |
btn = uiDefBtnR_prop("block icon", | |
UI_BTYPE_ROW, | |
0, | |
icon, | |
x, | |
y, | |
w, | |
h, | |
&args.ptr, | |
args.prop, | |
-1, | |
0, | |
value, | |
-1, | |
-1, | |
NULL); | |
} | |
ui_def_btn_icon(btn, icon, UI_HAS_ICON | UI_BTN_ICON_PREVIEW); | |
} | |
ui_block_bounds_set_normal(block, 0.3f * U.widget_unit); | |
ui_block_direction_set(block, UI_DIR_DOWN); | |
if (free) { | |
mem_freen((void *)item); | |
} | |
return block; | |
} | |
void uiTemplateIconView(uiLayout *layout, | |
ApiPtr *ptr, | |
const char *propname, | |
bool show_labels, | |
float icon_scale, | |
float icon_scale_popup) | |
{ | |
ApiProp *prop = api_struct_find_prop(ptr, propname); | |
if (!prop || api_prop_type(prop) != PROP_ENUM) { | |
api_warning( | |
"prop of type Enum not found: %s.%s", api_struct_id(ptr->type), propname); | |
return; | |
} | |
uiBlock *block = uiLayoutAbsoluteBlock(layout); | |
int tot_items; | |
bool free_items; | |
const EnumPropItem *items; | |
api_prop_enum_items(block->evil_C, ptr, prop, &items, &tot_items, &free_items); | |
const int value = api_prop_enum_get(ptr, prop); | |
int icon = ICON_NONE; | |
api_enum_icon_from_value(items, value, &icon); | |
uiBtn *btn; | |
if (api_prop_editable(ptr, prop)) { | |
IconViewMenuArgs *cb_args = mem_calloc(sizeof(IconViewMenuArgs), __func__); | |
cb_args->ptr = *ptr; | |
cb_args->prop = prop; | |
cb_args->show_labels = show_labels; | |
cb_args->icon_scale = icon_scale_popup; | |
btn = uiBlockBtnN(block, | |
ui_icon_view_menu_cb, | |
cb_args, | |
UI_UNIT_X * icon_scale, | |
UI_UNIT_Y * icon_scale, | |
""); | |
} | |
else { | |
btn = uiBtn(block icon_x label, | |
UI_UNIT_X * icon_scale, | |
UI_UNIT_Y * icon_scale, | |
""); | |
} | |
ui_def_btn_icon(btn, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); | |
if (free_items) { | |
mem_free((void *)items); | |
} | |
} | |
/* Histogram Template */ | |
void uiTemplateHistogram(uiLayout *layout, ApiPtr *ptr, const char *propname) | |
{ | |
ApiProp *prop = api_struct_find_prop(ptr, propname); | |
if (!prop || api_prop_type(prop) != PROP_PTR) { | |
return; | |
} | |
const ApiPtr cptr = api_prop_ptr_get(ptr, prop); | |
if (!cptr.data || !api_struct_is_a(cptr.type, &RNA_Histogram)) { | |
return; | |
} | |
Histogram *hist = (Histogram *)cptr.data; | |
if (hist->height < UI_UNIT_Y) { | |
hist->height = UI_UNIT_Y; | |
} | |
else if (hist->height > UI_UNIT_Y * 20) { | |
hist->height = UI_UNIT_Y * 20; | |
} | |
uiLayout *col = uiLayoutColumn(layout, true); | |
uiBlock *block = uiLayoutGetBlock(col); | |
uiDefBut( | |
block, UI_BTYPE_HISTOGRAM, 0, "", 0, 0, UI_UNIT_X * 10, hist->height, hist, 0, 0, 0, 0, ""); | |
/* Resize grip. */ | |
uiBtnI("block icon grip icon_grip", | |
UI_UNIT_X * 10, | |
(short)(UI_UNIT_Y * 0.3f), | |
&hist->height, | |
UI_UNIT_Y, | |
UI_UNIT_Y * 20.0f, | |
""); | |
} | |
/* Waveform Template */ | |
void uiTemplateWaveform(uiLayout *layout, ApiPtr *ptr, const char *propname) | |
{ | |
ApiProp *prop = api_struct_find_prop(ptr, propname); | |
if (!prop || api_prop_type(prop) != PROP_PTR) { | |
return; | |
} | |
const ApiPtr cptr = api_prop_ptr_get(ptr, prop); | |
if (!cptr.data || !api_struct_is_a(cptr.type, &ApiScopes)) { | |
return; | |
} | |
Scopes *scopes = (Scopes *)cptr.data; | |
uiLayout *col = uiLayoutColumn(layout, true); | |
uiBlock *block = uiLayoutGetBlock(col); | |
if (scopes->wavefrm_height < UI_UNIT_Y) { | |
scopes->wavefrm_height = UI_UNIT_Y; | |
} | |
else if (scopes->wavefrm_height > UI_UNIT_Y * 20) { | |
scopes->wavefrm_height = UI_UNIT_Y * 20; | |
} | |
uiBtn("block waveform", | |
UI_UNIT_X * 10, | |
scopes->wavefrm_height, | |
scopes); | |
/* Resize grip. */ | |
uiBtnI("block icon grip icon_grip", | |
UI_UNIT_X * 10, | |
(short)(UI_UNIT_Y * 0.3f), | |
&scopes->wavefrm_height, | |
UI_UNIT_Y, | |
UI_UNIT_Y * 20.0f, | |
""); | |
} | |
/* Vector-Scope Template */ | |
void uiTemplateVectorscope(uiLayout *layout, PointerRNA *ptr, const char *propname) | |
{ | |
PropertyRNA *prop = RNA_struct_find_property(ptr, propname); | |
if (!prop || RNA_property_type(prop) != PROP_POINTER) { | |
return; | |
} | |
const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); | |
if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_Scopes)) { | |
return; | |
} | |
Scopes *scopes = (Scopes *)cptr.data; | |
if (scopes->vecscope_height < UI_UNIT_Y) { | |
scopes->vecscope_height = UI_UNIT_Y; | |
} | |
else if (scopes->vecscope_height > UI_UNIT_Y * 20) { | |
scopes->vecscope_height = UI_UNIT_Y * 20; | |
} | |
uiLayout *col = uiLayoutColumn(layout, true); | |
uiBlock *block = uiLayoutGetBlock(col); | |
uiDefBut(block, | |
UI_BTYPE_VECTORSCOPE, | |
0, | |
"", | |
0, | |
0, | |
UI_UNIT_X * 10, | |
scopes->vecscope_height, | |
scopes, | |
0, | |
0, | |
0, | |
0, | |
""); | |
/* Resize grip. */ | |
uiDefIconButI(block, | |
UI_BTYPE_GRIP, | |
0, | |
ICON_GRIP, | |
0, | |
0, | |
UI_UNIT_X * 10, | |
(short)(UI_UNIT_Y * 0.3f), | |
&scopes->vecscope_height, | |
UI_UNIT_Y, | |
UI_UNIT_Y * 20.0f, | |
0.0f, | |
0.0f, | |
""); | |
} | |
/** \} */ | |
/* -------------------------------------------------------------------- */ | |
/** \name CurveMapping Template | |
* \{ */ | |
static void curvemap_buttons_zoom_in(bContext *C, void *cumap_v, void *UNUSED(arg)) | |
{ | |
CurveMapping *cumap = cumap_v; | |
/* we allow 20 times zoom */ | |
if (BLI_rctf_size_x(&cumap->curr) > 0.04f * BLI_rctf_size_x(&cumap->clipr)) { | |
const float dx = 0.1154f * BLI_rctf_size_x(&cumap->curr); | |
cumap->curr.xmin += dx; | |
cumap->curr.xmax -= dx; | |
const float dy = 0.1154f * BLI_rctf_size_y(&cumap->curr); | |
cumap->curr.ymin += dy; | |
cumap->curr.ymax -= dy; | |
} | |
ED_region_tag_redraw(CTX_wm_region(C)); | |
} | |
static void curvemap_buttons_zoom_out(bContext *C, void *cumap_v, void *UNUSED(unused)) | |
{ | |
CurveMapping *cumap = cumap_v; | |
float d, d1; | |
/* we allow 20 times zoom, but don't view outside clip */ | |
if (BLI_rctf_size_x(&cumap->curr) < 20.0f * BLI_rctf_size_x(&cumap->clipr)) { | |
d = d1 = 0.15f * BLI_rctf_size_x(&cumap->curr); | |
if (cumap->flag & CUMA_DO_CLIP) { | |
if (cumap->curr.xmin - d < cumap->clipr.xmin) { | |
d1 = cumap->curr.xmin - cumap->clipr.xmin; | |
} | |
} | |
cumap->curr.xmin -= d1; | |
d1 = d; | |
if (cumap->flag & CUMA_DO_CLIP) { | |
if (cumap->curr.xmax + d > cumap->clipr.xmax) { | |
d1 = -cumap->curr.xmax + cumap->clipr.xmax; | |
} | |
} | |
cumap->curr.xmax += d1; | |
d = d1 = 0.15f * BLI_rctf_size_y(&cumap->curr); | |
if (cumap->flag & CUMA_DO_CLIP) { | |
if (cumap->curr.ymin - d < cumap->clipr.ymin) { | |
d1 = cumap->curr.ymin - cumap->clipr.ymin; | |
} | |
} | |
cumap->curr.ymin -= d1; | |
d1 = d; | |
if (cumap->flag & CUMA_DO_CLIP) { | |
if (cumap->curr.ymax + d > cumap->clipr.ymax) { | |
d1 = -cumap->curr.ymax + cumap->clipr.ymax; | |
} | |
} | |
cumap->curr.ymax += d1; | |
} | |
ED_region_tag_redraw(CTX_wm_region(C)); | |
} | |
static void curvemap_buttons_setclip(bContext *UNUSED(C), void *cumap_v, void *UNUSED(arg)) | |
{ | |
CurveMapping *cumap = cumap_v; | |
BKE_curvemapping_changed(cumap, false); | |
} | |
static void curvemap_btns_delete(Cxt *C, void *cb_v, void *cumap_v) | |
{ | |
CurveMapping *cumap = cumap_v; | |
dune_curvemap_remove(cumap->cm + cumap->cur, SELECT); | |
dune_curvemapping_changed(cumap, false); | |
rna_update_cb(C, cb_v, NULL); | |
} | |
/* NOTE: this is a block-menu, needs 0 events, otherwise the menu closes */ | |
static uiBlock *curvemap_clipping_fn(Cxt *C, ARegion *region, void *cumap_v) | |
{ | |
CurveMapping *cumap = cumap_v; | |
uiBtn *bt; | |
const float width = 8 * UI_UNIT_X; | |
uiBlock *block = ui_block_begin(C, region, __func__, UI_EMBOSS); | |
ui_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_MOVEMOUSE_QUIT); | |
ui_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); | |
btn = uiDefBtnBitI(block, | |
UI_BTYPE_CHECKBOX, | |
CUMA_DO_CLIP, | |
1, | |
IFACE_("Use Clipping"), | |
0, | |
5 * UI_UNIT_Y, | |
width, | |
UI_UNIT_Y, | |
&cumap->flag, | |
0.0, | |
0.0, | |
10, | |
0, | |
""); | |
btn_fn_set(btn, curvemap_btns_setclip, cumap, NULL); | |
ui_block_align_begin(block); | |
btn = uiDefBtnF(block, | |
UI_BTYPE_NUM, | |
0, | |
IFACE_("Min X:"), | |
0, | |
4 * UI_UNIT_Y, | |
width, | |
UI_UNIT_Y, | |
&cumap->clipr.xmin, | |
-100.0, | |
cumap->clipr.xmax, | |
0, | |
0, | |
""); | |
btn_number_step_size_set(bt, 10); | |
btn_number_precision_set(bt, 2); | |
btn = uiDefBtnF(block, | |
UI_BTYPE_NUM, | |
0, | |
IFACE_("Min Y:"), | |
0, | |
3 * UI_UNIT_Y, | |
width, | |
UI_UNIT_Y, | |
&cumap->clipr.ymin, | |
-100.0, | |
cumap->clipr.ymax, | |
0, | |
0, | |
""); | |
btn_number_step_size_set(bt, 10); | |
btn_number_precision_set(bt, 2); | |
btn = uiBtnF(block, | |
UI_BTYPE_NUM, | |
0, | |
IFACE_("Max X:"), | |
0, | |
2 * UI_UNIT_Y, | |
width, | |
UI_UNIT_Y, | |
&cumap->clipr.xmax, | |
cumap->clipr.xmin, | |
100.0, | |
0, | |
0, | |
""); | |
btn_number_step_size_set(bt, 10); | |
btn_number_precision_set(bt, 2); | |
btn = uiBtnF(block, | |
UI_BTYPE_NUM, | |
0, | |
IFACE_("Max Y:"), | |
0, | |
UI_UNIT_Y, | |
width, | |
UI_UNIT_Y, | |
&cumap->clipr.ymax, | |
cumap->clipr.ymin, | |
100.0, | |
0, | |
0, | |
""); | |
btn_number_step_size_set(btn, 10); | |
btn_number_precision_set(btn, 2); | |
ui_block_bounds_set_normal(block, 0.3f * U.widget_unit); | |
ui_block_direction_set(block, UI_DIR_DOWN); | |
return block; | |
} | |
/* only for dune_curvemap_tools_dofn */ | |
enum { | |
UICURVE_FN_RESET_NEG, | |
UICURVE_FN_RESET_POS, | |
UICURVE_FN_RESET_VIEW, | |
UICURVE_FN_HANDLE_VECTOR, | |
UICURVE_FN_HANDLE_AUTO, | |
UICURVE_FN_HANDLE_AUTO_ANIM, | |
UICURVE_FN_EXTEND_HOZ, | |
UICURVE_FN_EXTEND_EXP, | |
}; | |
static void curvemap_tools_dofn(Cxt *C, void *cumap_v, int event) | |
{ | |
CurveMapping *cumap = cumap_v; | |
CurveMap *cuma = cumap->cm + cumap->cur; | |
switch (event) { | |
case UICURVE_FN_RESET_NEG: | |
case UICURVE_FN_RESET_POS: /* reset */ | |
dune_curvemap_reset(cuma, | |
&cumap->clipr, | |
cumap->preset, | |
(event == UICURVE_FN_RESET_NEG) ? CURVEMAP_SLOPE_NEGATIVE : | |
CURVEMAP_SLOPE_POSITIVE); | |
dune_curvemapping_changed(cumap, false); | |
break; | |
case UICURVE_FN_RESET_VIEW: | |
dune_curvemapping_reset_view(cumap); | |
break; | |
case UICURVE_FN_HANDLE_VECTOR: /* Set vector. */ | |
dune_curvemap_handle_set(cuma, HD_VECT); | |
dune_curvemapping_changed(cumap, false); | |
break; | |
case UICURVE_FN_HANDLE_AUTO: /* Set auto. */ | |
dune_curvemap_handle_set(cuma, HD_AUTO); | |
dune_curvemapping_changed(cumap, false); | |
break; | |
case UICURVE_FN_HANDLE_AUTO_ANIM: /* Set auto-clamped. */ | |
dune_curvemap_handle_set(cuma, HD_AUTO_ANIM); | |
dune_curvemapping_changed(cumap, false); | |
break; | |
case UICURVE_FN_EXTEND_HOZ: /* Extend horizontal. */ | |
cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE; | |
dune_curvemapping_changed(cumap, false); | |
break; | |
case UICURVE_FN_EXTEND_EXP: /* Extend extrapolate. */ | |
cumap->flag |= CUMA_EXTEND_EXTRAPOLATE; | |
dune_curvemapping_changed(cumap, false); | |
break; | |
} | |
ed_undo_push(C, "CurveMap tools"); | |
ed_region_tag_redraw(cxt_win_region(C)); | |
} | |
static uiBlock *curvemap_tools_fn( | |
Cxt *C, ARegion *region, CurveMapping *cumap, bool show_extend, int reset_mode) | |
{ | |
short yco = 0; | |
const short menuwidth = 10 * UI_UNIT_X; | |
uiBlock *block = ui_block_begin(C, region, __func__, UI_EMBOSS); | |
ui_block_fn_btnmenu_set(block, curvemap_tools_dofn, cumap); | |
{ | |
uiDefIconTextBtn("block icontext menu", | |
UI_BTYPE_BTN_MENU, | |
1, | |
ICON_BLANK1, | |
IFACE_("Reset View"), | |
0, | |
yco -= UI_UNIT_Y, | |
menuwidth, | |
UI_UNIT_Y, | |
NULL, | |
0.0, | |
0.0, | |
0, | |
UICURVE_FN_RESET_VIEW, | |
""); | |
} | |
if (show_extend) { | |
uiBtn("block menu icon_blank1", | |
UI_BTYPE_BUT_MENU, | |
1, | |
ICON_BLANK1, | |
IFACE_("Extend Horizontal"), | |
0, | |
yco -= UI_UNIT_Y, | |
menuwidth, | |
UI_UNIT_Y, | |
NULL, | |
0.0, | |
0.0, | |
0, | |
UICURVE_FN_EXTEND_HOZ, | |
""); | |
uiDefIconTextBtn(block, | |
UI_BTYPE_BTN_MENU, | |
1, | |
ICON_BLANK1, | |
IFACE_("Extend Extrapolated"), | |
0, | |
yco -= UI_UNIT_Y, | |
menuwidth, | |
UI_UNIT_Y, | |
NULL, | |
0.0, | |
0.0, | |
0, | |
UICURVE_FN_EXTEND_EXP, | |
""); | |
} | |
{ | |
uiBtn("block icontext menu icon_blank", | |
IFACE_("Reset Curve"), | |
0, | |
yco -= UI_UNIT_Y, | |
menuwidth, | |
UI_UNIT_Y, | |
reset_mode); | |
} | |
ui_block_direction_set(block, UI_DIR_DOWN); | |
ui_block_bounds_set_text(block, 3.0f * UI_UNIT_X); | |
return block; | |
} | |
static uiBlock *curvemap_tools_posslope_fn(Cxt *C, ARegion *region, void *cumap_v) | |
{ | |
return curvemap_tools_fn(C, region, cumap_v, true, UICURVE_FN_RESET_POS); | |
} | |
static uiBlock *curvemap_tools_negslope_fn(Cxt *C, ARegion *region, void *cumap_v) | |
{ | |
return curvemap_tools_fn(C, region, cumap_v, true, UICURVE_FN_RESET_NEG); | |
} | |
static uiBlock *curvemap_brush_tools_fn(Cxt *C, ARegion *region, void *cumap_v) | |
{ | |
return curvemap_tools_fn(C, region, cumap_v, false, UICURVE_FN_RESET_NEG); | |
} | |
static uiBlock *curvemap_brush_tools_negslope_fn(Cxt *C, ARegion *region, void *cumap_v) | |
{ | |
return curvemap_tools_fn(C, region, cumap_v, false, UICURVE_FN_RESET_POS); | |
} | |
static void curvemap_tools_handle_vector(Cxt *C, void *cumap_v, void *UNUSED(arg)) | |
{ | |
curvemap_tools_dofn(C, cumap_v, UICURVE_FN_HANDLE_VECTOR); | |
} | |
static void curvemap_tools_handle_auto(Cxt *C, void *cumap_v, void *UNUSED(arg)) | |
{ | |
curvemap_tools_dofn(C, cumap_v, UICURVE_FN_HANDLE_AUTO); | |
} | |
static void curvemap_tools_handle_auto_clamped(Cxt *C, void *cumap_v, void *UNUSED(arg)) | |
{ | |
curvemap_tools_dofn(C, cumap_v, UICURVE_FN_HANDLE_AUTO_ANIM); | |
} | |
static void curvemap_btns_redraw(Cxt *C, void *UNUSED(arg1), void *UNUSED(arg2)) | |
{ | |
ed_region_tag_redraw(cxt_win_region(C)); | |
} | |
static void curvemap_btns_update(Cxt *C, void *arg1_v, void *cumap_v) | |
{ | |
CurveMapping *cumap = cumap_v; | |
dune_curvemapping_changed(cumap, true); | |
api_update_cb(C, arg1_v, NULL); | |
} | |
static void curvemap_btns_reset(Cxt *C, void *cb_v, void *cumap_v) | |
{ | |
CurveMapping *cumap = cumap_v; | |
cumap->preset = CURVE_PRESET_LINE; | |
for (int a = 0; a < CM_TOT; a++) { | |
dune_curvemap_reset(cumap->cm + a, &cumap->clipr, cumap->preset, CURVEMAP_SLOPE_POSITIVE); | |
} | |
cumap->black[0] = cumap->black[1] = cumap->black[2] = 0.0f; | |
cumap->white[0] = cumap->white[1] = cumap->white[2] = 1.0f; | |
dune_curvemapping_set_black_white(cumap, NULL, NULL); | |
dune_curvemapping_changed(cumap, false); | |
api_update_cb(C, cb_v, NULL); | |
} | |
/* Still unsure how this call evolves. | |
* param labeltype: Used for defining which curve-channels to show. */ | |
static void curvemap_btns_layout(uiLayout *layout, | |
ApiPtr *ptr, | |
char labeltype, | |
bool levels, | |
bool brush, | |
bool neg_slope, | |
bool tone, | |
ApiUpdateCb *cb) | |
{ | |
CurveMapping *cumap = ptr->data; | |
CurveMap *cm = &cumap->cm[cumap->cur]; | |
uiBut *bt; | |
const float dx = UI_UNIT_X; | |
int bg = -1; | |
uiBlock *block = uiLayoutGetBlock(layout); | |
ui_block_emboss_set(block, UI_EMBOSS); | |
if (tone) { | |
uiLayout *split = uiLayoutSplit(layout, 0.0f, false); | |
uiItemR(uiLayoutRow(split, false), ptr, "tone", UI_ITEM_R_EXPAND, NULL, ICON_NONE); | |
} | |
/* curve chooser */ | |
uiLayout *row = uiLayoutRow(layout, false); | |
if (labeltype == 'v') { | |
/* vector */ | |
uiLayout *sub = uiLayoutRow(row, true); | |
uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); | |
if (cumap->cm[0].curve) { | |
btn = uiBtnI( | |
block, UI_BTYPE_ROW, 0, "X", 0, 0, dx, dx, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); | |
btn_fn_set(btn, curvemap_buttons_redraw, NULL, NULL); | |
} | |
if (cumap->cm[1].curve) { | |
btn = uiBtnI( | |
block, UI_BTYPE_ROW, 0, "Y", 0, 0, dx, dx, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); | |
btn_fn_set(bt, curvemap_buttons_redraw, NULL, NULL); | |
} | |
if (cumap->cm[2].curve) { | |
btn = uiBtnI( | |
block, UI_BTYPE_ROW, 0, "Z", 0, 0, dx, dx, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); | |
UI_but_fn_set(bt, curvemap_buttons_redraw, NULL, NULL); | |
} | |
} | |
else if (labeltype == 'c') { | |
/* color */ | |
uiLayout *sub = uiLayoutRow(row, true); | |
uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); | |
if (cumap->cm[3].curve) { | |
bt = uiDefButI( | |
block, UI_BTYPE_ROW, 0, "C", 0, 0, dx, dx, &cumap->cur, 0.0, 3.0, 0.0, 0.0, ""); | |
UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); | |
} | |
if (cumap->cm[0].curve) { | |
bt = uiDefButI( | |
block, UI_BTYPE_ROW, 0, "R", 0, 0, dx, dx, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); | |
UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); | |
} | |
if (cumap->cm[1].curve) { | |
bt = uiDefButI( | |
block, UI_BTYPE_ROW, 0, "G", 0, 0, dx, dx, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); | |
UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); | |
} | |
if (cumap->cm[2].curve) { | |
bt = uiDefButI( | |
block, UI_BTYPE_ROW, 0, "B", 0, 0, dx, dx, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); | |
UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); | |
} | |
} | |
else if (labeltype == 'h') { | |
/* HSV */ | |
uiLayout *sub = uiLayoutRow(row, true); | |
uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); | |
if (cumap->cm[0].curve) { | |
bt = uiDefButI( | |
block, UI_BTYPE_ROW, 0, "H", 0, 0, dx, dx, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); | |
UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); | |
} | |
if (cumap->cm[1].curve) { | |
bt = uiDefButI( | |
block, UI_BTYPE_ROW, 0, "S", 0, 0, dx, dx, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); | |
UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); | |
} | |
if (cumap->cm[2].curve) { | |
bt = uiDefButI( | |
block, UI_BTYPE_ROW, 0, "V", 0, 0, dx, dx, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); | |
UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); | |
} | |
} | |
else { | |
uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT); | |
} | |
if (labeltype == 'h') { | |
bg = UI_GRAD_H; | |
} | |
/* operation buttons */ | |
/* (Right aligned) */ | |
uiLayout *sub = uiLayoutRow(row, true); | |
uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_RIGHT); | |
/* Zoom in */ | |
bt = uiDefIconBut(block, | |
UI_BTYPE_BUT, | |
0, | |
ICON_ZOOM_IN, | |
0, | |
0, | |
dx, | |
dx, | |
NULL, | |
0.0, | |
0.0, | |
0.0, | |
0.0, | |
TIP_("Zoom in")); | |
UI_but_func_set(bt, curvemap_buttons_zoom_in, cumap, NULL); | |
/* Zoom out */ | |
bt = uiDefIconBut(block, | |
UI_BTYPE_BUT, | |
0, | |
ICON_ZOOM_OUT, | |
0, | |
0, | |
dx, | |
dx, | |
NULL, | |
0.0, | |
0.0, | |
0.0, | |
0.0, | |
TIP_("Zoom out")); | |
UI_but_func_set(bt, curvemap_buttons_zoom_out, cumap, NULL); | |
/* Clippoing button. */ | |
const int icon = (cumap->flag & CUMA_DO_CLIP) ? ICON_CLIPUV_HLT : ICON_CLIPUV_DEHLT; | |
bt = uiDefIconBlockBut( | |
block, curvemap_clipping_func, cumap, 0, icon, 0, 0, dx, dx, TIP_("Clipping Options")); | |
bt->drawflag &= ~UI_BUT_ICON_LEFT; | |
UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); | |
if (brush && neg_slope) { | |
bt = uiDefIconBlockBut( | |
block, curvemap_brush_tools_negslope_func, cumap, 0, 0, 0, 0, dx, dx, TIP_("Tools")); | |
} | |
else if (brush) { | |
bt = uiDefIconBlockBut( | |
block, curvemap_brush_tools_func, cumap, 0, 0, 0, 0, dx, dx, TIP_("Tools")); | |
} | |
else if (neg_slope) { | |
bt = uiDefIconBlockBut( | |
block, curvemap_tools_negslope_func, cumap, 0, 0, 0, 0, dx, dx, TIP_("Tools")); | |
} | |
else { | |
bt = uiDefIconBlockBut( | |
block, curvemap_tools_posslope_func, cumap, 0, 0, 0, 0, dx, dx, TIP_("Tools")); | |
} | |
UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); | |
UI_block_funcN_set(block, rna_update_cb, MEM_dupallocN(cb), NULL); | |
/* Curve itself. */ | |
const int size = max_ii(uiLayoutGetWidth(layout), UI_UNIT_X); | |
row = uiLayoutRow(layout, false); | |
uiButCurveMapping *curve_but = (uiButCurveMapping *)uiDefBut( | |
block, UI_BTYPE_CURVE, 0, "", 0, 0, size, 8.0f * UI_UNIT_X, cumap, 0.0f, 1.0f, -1, 0, ""); | |
curve_but->gradient_type = bg; | |
/* Sliders for selected curve point. */ | |
int i; | |
CurveMapPoint *cmp = NULL; | |
bool point_last_or_first = false; | |
for (i = 0; i < cm->totpoint; i++) { | |
if (cm->curve[i].flag & CUMA_SELECT) { | |
cmp = &cm->curve[i]; | |
break; | |
} | |
} | |
if (ELEM(i, 0, cm->totpoint - 1)) { | |
point_last_or_first = true; | |
} | |
if (cmp) { | |
rctf bounds; | |
if (cumap->flag & CUMA_DO_CLIP) { | |
bounds = cumap->clipr; | |
} | |
else { | |
bounds.xmin = bounds.ymin = -1000.0; | |
bounds.xmax = bounds.ymax = 1000.0; | |
} | |
UI_block_emboss_set(block, UI_EMBOSS); | |
uiLayoutRow(layout, true); | |
/* Curve handle buttons. */ | |
bt = uiDefIconBut(block, | |
UI_BTYPE_BUT, | |
1, | |
ICON_HANDLE_AUTO, | |
0, | |
UI_UNIT_Y, | |
UI_UNIT_X, | |
UI_UNIT_Y, | |
NULL, | |
0.0, | |
0.0, | |
0.0, | |
0.0, | |
TIP_("Auto Handle")); | |
UI_but_func_set(bt, curvemap_tools_handle_auto, cumap, NULL); | |
if (((cmp->flag & CUMA_HANDLE_AUTO_ANIM) == false) && | |
((cmp->flag & CUMA_HANDLE_VECTOR) == false)) { | |
bt->flag |= UI_SELECT_DRAW; | |
} | |
bt | |
NULL, | |
0.0, | |
0.0, | |
0.0, | |
0.0, | |
TIP_("Vector Handle")); | |
ui_btn_fn_set(bt, curvemap_tools_handle_vector, cumap, NULL); | |
if (cmp->flag & CUMA_HANDLE_VECTOR) { | |
bt->flag |= UI_SELECT_DRAW; | |
} | |
bt = uiDefBtn("block, icon", | |
UI_BTYPE_BUT, | |
1, | |
ICON_HANDLE_AUTOCLAMPED, | |
0, | |
UI_UNIT_Y, | |
UI_UNIT_X, | |
UI_UNIT_Y, | |
NULL, | |
0.0, | |
0.0, | |
0.0, | |
0.0, | |
TIP_("Auto Clamped")); | |
UI_but_fn_set(bt, curvemap_tools_handle_auto_clamped, cumap, NULL); | |
if (cmp->flag & CUMA_HANDLE_AUTO_ANIM) { | |
bt->flag |= UI_SELECT_DRAW; | |
} | |
/* Curve handle position */ | |
UI_block_funcN_set(block, curvemap_buttons_update, MEM_dupallocN(cb), cumap); | |
bt = uiDefButF(block, | |
UI_BTYPE_NUM, | |
0, | |
"X:", | |
0, | |
2 * UI_UNIT_Y, | |
UI_UNIT_X * 10, | |
UI_UNIT_Y, | |
&cmp->x, | |
bounds.xmin, | |
bounds.xmax, | |
0, | |
0, | |
""); | |
UI_but_number_step_size_set(bt, 1); | |
UI_but_number_precision_set(bt, 5); | |
bt = uiDefButF(block, | |
UI_BTYPE_NUM, | |
0, | |
"Y:", | |
0, | |
1 * UI_UNIT_Y, | |
UI_UNIT_X * 10, | |
UI_UNIT_Y, | |
&cmp->y, | |
bounds.ymin, | |
bounds.ymax, | |
0, | |
0, | |
""); | |
UI_but_number_step_size_set(bt, 1); | |
UI_but_number_precision_set(bt, 5); | |
/* Curve handle delete point */ | |
bt = uiDefIconBtn(block, | |
UI_BTYPE_BUT, | |
0, | |
ICON_X, | |
0, | |
0, | |
dx, | |
dx, | |
NULL, | |
0.0, | |
0.0, | |
0.0, | |
0.0, | |
TIP_("Delete points")); | |
ui_but_fn_set(bt, curvemap_btns_delete, mem_dupallocN(cb), cumap); | |
if (point_last_or_first) { | |
ui_btn_flag_enable(bt, UI_BTN_DISABLED); | |
} | |
} | |
/* black/white levels */ | |
if (levels) { | |
uiLayout *split = uiLayoutSplit(layout, 0.0f, false); | |
uiItemR(uiLayoutColumn(split, false), ptr, "black_level", UI_ITEM_R_EXPAND, NULL, ICON_NONE); | |
uiItemR(uiLayoutColumn(split, false), ptr, "white_level", UI_ITEM_R_EXPAND, NULL, ICON_NONE); | |
uiLayoutRow(layout, false); | |
bt = uiDefBtn(block, | |
UI_BTYPE_BTN, | |
0, | |
IFACE_("Reset"), | |
0, | |
0, | |
UI_UNIT_X * 10, | |
UI_UNIT_Y, | |
NULL, | |
0.0f, | |
0.0f, | |
0, | |
0, | |
TIP_("Reset Black/White point and curves")); | |
UI_but_funcN_set(bt, curvemap_buttons_reset, MEM_dupallocN(cb), cumap); | |
} | |
ui_block_fn_set(block, NULL, NULL, NULL); | |
} | |
void uiTemplateCurveMapping(uiLayout *layout, | |
ApiPtr *ptr, | |
const char *propname, | |
int type, | |
bool levels, | |
bool brush, | |
bool neg_slope, | |
bool tone) | |
{ | |
ApiProp *prop = api_struct_find_prop(ptr, propname); | |
uiBlock *block = uiLayoutGetBlock(layout); | |
if (!prop) { | |
api_warning("curve prop not found: %s.%s", RNA_struct_identifier(ptr->type), propname); | |
return; | |
} | |
if (api_prop_type(prop) != PROP_POINTER) { | |
api_warning("curve is not a pointer: %s.%s", RNA_struct_identifier(ptr->type), propname); | |
return; | |
} | |
ApiPtr cptr = api_prop_ptr_get(ptr, prop); | |
if (!cptr.data || !api_struct_is_a(cptr.type, &RNA_CurveMapping)) { | |
return; | |
} | |
ApiUpdateCb *cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb"); | |
cb->ptr = *ptr; | |
cb->prop = prop; | |
Id *id = cptr.owner_id; | |
UI_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE); | |
curvemap_btns_layout(layout, &cptr, type, levels, brush, neg_slope, tone, cb); | |
UI_block_lock_clear(block); | |
MEM_freeN(cb); | |
} | |
/* -------------------------------------------------------------------- */ | |
/* Curve Profile Templat */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* source/blender/editors/interface/interface.cc */ | |
/** \file | |
* \ingroup edinterface | |
*/ | |
#include <cctype> | |
#include <cfloat> | |
#include <climits> | |
#include <cmath> | |
#include <cstddef> /* `offsetof()` */ | |
#include <cstring> | |
#include "guardedalloc.h" | |
#include "types_object.h" | |
#include "types_scene.h" | |
#include "types_screen.h" | |
#include "types_userdef.h" | |
#include "types_workspace.h" | |
#include "lib_ghash.h" | |
#include "lib_list.h" | |
#include "lib_rect.h" | |
#include "lib_string.h" | |
#include "lib_string_utf8.h" | |
#include "lib_vector.hh" | |
#include "lib_utildefines.h" | |
#include "loader_readfile.h" | |
#include "dune_animsys.h" | |
#include "dune_cxt.h" | |
#include "dune_idprop.h" | |
#include "dune_main.h" | |
#include "dune_report.h" | |
#include "dune_scene.h" | |
#include "dune_screen.hh" | |
#include "dune_unit.h" | |
#include "ed_asset.hh" | |
#include "GPU_matrix.h" | |
#include "GPU_state.h" | |
#include "BLF_api.h" | |
#include "lang.h" | |
#include "ui.hh" | |
#include "ui_icons.hh" | |
#include "ui_string_search.hh" | |
#include "ui_view2d.hh" | |
#include "imbuf.h" | |
#include "win_api.hh" | |
#include "win_message.hh" | |
#include "win_types.hh" | |
#include "api_access.hh" | |
#include "api_enum_types.hh" | |
#include "ed_numinput.hh" | |
#include "ed_screen.hh" | |
#include "imbuf_colormanagement.h" | |
#include "graph_query.hh" | |
#include "ui_intern.hh" | |
using dune::Vector; | |
/* prototypes. */ | |
static void ui_def_btn_api__menu(Cxt * /*C*/, uiLayout *layout, void *btn_p); | |
static void ui_def_btn_api__panel_type(Cxt * /*C*/, uiLayout *layout, void *btn_p); | |
static void ui_def_btn_api__menu_type(Cxt * /*C*/, uiLayout *layout, void *btn_p); | |
/* avoid unneeded calls to ui_btn_value_get */ | |
#define UI_BTN_VALUE_UNSET DBL_MAX | |
#define UI_GET_BTN_VALUE_INIT(_but, _value) \ | |
if (_value == DBL_MAX) { \ | |
(_value) = ui_btn_value_get(_but); \ | |
} \ | |
((void)0) | |
#define B_NOP -1 | |
/* a full doc with API notes can be found in 'dune/doc/guides/interface_API.txt' | |
* | |
* `uiBlahBlah()` external fn. | |
* `ui_blah_blah()` internal fn. | |
*/ | |
static void ui_btn_free(const Cxt *C, uiBtn *btn); | |
static bool ui_btn_is_unit_radians_ex(UnitSettings *unit, const int unit_type) | |
{ | |
return (unit->system_rotation == USER_UNIT_ROT_RADIANS && unit_type == PROP_UNIT_ROTATION); | |
} | |
static bool ui_btn_is_unit_radians(const uiBtn *btn) | |
{ | |
UnitSettings *unit = btn->block->unit; | |
const int unit_type = ui_btn_unit_type_get(but); | |
return ui_btn_is_unit_radians_ex(unit, unit_type); | |
} | |
/* window matrix */ | |
void ui_block_to_region_fl(const ARegion *region, const uiBlock *block, float *r_x, float *r_y) | |
{ | |
const int getsizex = lib_rcti_size_x(®ion->winrct) + 1; | |
const int getsizey = lib_rcti_size_y(®ion->winrct) + 1; | |
float gx = *r_x; | |
float gy = *r_y; | |
if (block->panel) { | |
gx += block->panel->ofsx; | |
gy += block->panel->ofsy; | |
} | |
*r_x = float(getsizex) * (0.5f + 0.5f * (gx * block->winmat[0][0] + gy * block->winmat[1][0] + | |
block->winmat[3][0])); | |
*r_y = float(getsizey) * (0.5f + 0.5f * (gx * block->winmat[0][1] + gy * block->winmat[1][1] + | |
block->winmat[3][1])); | |
} | |
void ui_block_to_window_fl(const ARegion *region, const uiBlock *block, float *r_x, float *r_y) | |
{ | |
ui_block_to_region_fl(region, block, r_x, r_y); | |
*r_x += region->winrct.xmin; | |
*r_y += region->winrct.ymin; | |
} | |
void ui_block_to_window(const ARegion *region, const uiBlock *block, int *r_x, int *r_y) | |
{ | |
float fx = *r_x; | |
float fy = *r_y; | |
ui_block_to_window_fl(region, block, &fx, &fy); | |
*r_x = int(lround(fx)); | |
*r_y = int(lround(fy)); | |
} | |
void ui_block_to_region_rctf(const ARegion *region, | |
const uiBlock *block, | |
rctf *rct_dst, | |
const rctf *rct_src) | |
{ | |
*rct_dst = *rct_src; | |
ui_block_to_region_fl(region, block, &rct_dst->xmin, &rct_dst->ymin); | |
ui_block_to_region_fl(region, block, &rct_dst->xmax, &rct_dst->ymax); | |
} | |
void ui_block_to_window_rctf(const ARegion *region, | |
const uiBlock *block, | |
rctf *rct_dst, | |
const rctf *rct_src) | |
{ | |
*rct_dst = *rct_src; | |
ui_block_to_window_fl(region, block, &rct_dst->xmin, &rct_dst->ymin); | |
ui_block_to_window_fl(region, block, &rct_dst->xmax, &rct_dst->ymax); | |
} | |
float ui_block_to_window_scale(const ARegion *region, const uiBlock *block) | |
{ | |
/* We could have function for this to avoid dummy arg. */ | |
float min_y = 0, max_y = 1; | |
float dummy_x = 0.0f; | |
ui_block_to_window_fl(region, block, &dummy_x, &min_y); | |
dummy_x = 0.0f; | |
ui_block_to_window_fl(region, block, &dummy_x, &max_y); | |
return max_y - min_y; | |
} | |
void ui_window_to_block_fl(const ARegion *region, const uiBlock *block, float *r_x, float *r_y) | |
{ | |
const int getsizex = lib_rcti_size_x(®ion->winrct) + 1; | |
const int getsizey = lib_rcti_size_y(®ion->winrct) + 1; | |
const int sx = region->winrct.xmin; | |
const int sy = region->winrct.ymin; | |
const float a = 0.5f * float(getsizex) * block->winmat[0][0]; | |
const float b = 0.5f * float(getsizex) * block->winmat[1][0]; | |
const float c = 0.5f * float(getsizex) * (1.0f + block->winmat[3][0]); | |
const float d = 0.5f * float(getsizey) * block->winmat[0][1]; | |
const float e = 0.5f * float(getsizey) * block->winmat[1][1]; | |
const float f = 0.5f * float(getsizey) * (1.0f + block->winmat[3][1]); | |
const float px = *r_x - sx; | |
const float py = *r_y - sy; | |
*r_y = (a * (py - f) + d * (c - px)) / (a * e - d * b); | |
*r_x = (px - b * (*r_y) - c) / a; | |
if (block->panel) { | |
*r_x -= block->panel->ofsx; | |
*r_y -= block->panel->ofsy; | |
} | |
} | |
void ui_window_to_block_rctf(const ARegion *region, | |
const uiBlock *block, | |
rctf *rct_dst, | |
const rctf *rct_src) | |
{ | |
*rct_dst = *rct_src; | |
ui_window_to_block_fl(region, block, &rct_dst->xmin, &rct_dst->ymin); | |
ui_window_to_block_fl(region, block, &rct_dst->xmax, &rct_dst->ymax); | |
} | |
void ui_window_to_block(const ARegion *region, const uiBlock *block, int *r_x, int *r_y) | |
{ | |
float fx = *r_x; | |
float fy = *r_y; | |
ui_window_to_block_fl(region, block, &fx, &fy); | |
*r_x = int(lround(fx)); | |
*r_y = int(lround(fy)); | |
} | |
void ui_window_to_region(const ARegion *region, int *r_x, int *r_y) | |
{ | |
*r_x -= region->winrct.xmin; | |
*r_y -= region->winrct.ymin; | |
} | |
void ui_window_to_region_rcti(const ARegion *region, rcti *rect_dst, const rcti *rct_src) | |
{ | |
rect_dst->xmin = rct_src->xmin - region->winrct.xmin; | |
rect_dst->xmax = rct_src->xmax - region->winrct.xmin; | |
rect_dst->ymin = rct_src->ymin - region->winrct.ymin; | |
rect_dst->ymax = rct_src->ymax - region->winrct.ymin; | |
} | |
void ui_window_to_region_rctf(const ARegion *region, rctf *rect_dst, const rctf *rct_src) | |
{ | |
rect_dst->xmin = rct_src->xmin - region->winrct.xmin; | |
rect_dst->xmax = rct_src->xmax - region->winrct.xmin; | |
rect_dst->ymin = rct_src->ymin - region->winrct.ymin; | |
rect_dst->ymax = rct_src->ymax - region->winrct.ymin; | |
} | |
void ui_region_to_window(const ARegion *region, int *r_x, int *r_y) | |
{ | |
*r_x += region->winrct.xmin; | |
*r_y += region->winrct.ymin; | |
} | |
static void ui_update_flexible_spacing(const ARegion *region, uiBlock *block) | |
{ | |
int sepr_flex_len = 0; | |
LIST_FOREACH (uiBtn *, btn, &block->btns) { | |
if (btn->type == UI_BTYPE_SEPR_SPACER) { | |
sepr_flex_len++; | |
} | |
} | |
if (sepr_flex_len == 0) { | |
return; | |
} | |
rcti rect; | |
ui_btn_to_pixelrect(&rect, region, block, static_cast<const uiBtn *>(block->btns.last)); | |
const float btns_width = float(rect.xmax) + UI_HEADER_OFFSET; | |
const float region_width = float(region->sizex) * UI_SCALE_FAC; | |
if (region_width <= btns_width) { | |
return; | |
} | |
/* We could get rid of this loop if we agree on a max number of spacer */ | |
Vector<int, 8> spacers_pos; | |
LIST_FOREACH (uiBtn *, but, &block->btns) { | |
if (btn->type == UI_BTYPE_SEPR_SPACER) { | |
ui_btn_to_pixelrect(&rect, region, block, btn); | |
spacers_pos.append(rect.xmax + UI_HEADER_OFFSET); | |
} | |
} | |
const float view_scale_x = ui_view2d_scale_get_x(®ion->v2d); | |
const float segment_width = region_width / float(sepr_flex_len); | |
float offset = 0, remaining_space = region_width - btns_width; | |
int i = 0; | |
LIST_FOREACH (uiBut *, btn, &block->buttons) { | |
lib_rctf_translate(&but->rect, offset / view_scale_x, 0); | |
if (btn->type == UI_BTYPE_SEPR_SPACER) { | |
/* How much the next block overlap with the current segment */ | |
int overlap = ((i == sepr_flex_len - 1) ? btns_width - spacers_pos[i] : | |
(spacers_pos[i + 1] - spacers_pos[i]) / 2); | |
const int segment_end = segment_width * (i + 1); | |
const int spacer_end = segment_end - overlap; | |
const int spacer_sta = spacers_pos[i] + offset; | |
if (spacer_end > spacer_sta) { | |
const float step = min_ff(remaining_space, spacer_end - spacer_sta); | |
remaining_space -= step; | |
offset += step; | |
} | |
i++; | |
} | |
} | |
ui_block_bounds_calc(block); | |
} | |
static void ui_update_window_matrix(const Win *window, const ARegion *region, uiBlock *block) | |
{ | |
/* window matrix and aspect */ | |
if (region && region->visible) { | |
/* Get projection matrix which includes View2D translation and zoom. */ | |
gpu_matrix_projection_get(block->winmat); | |
block->aspect = 2.0f / fabsf(region->winx * block->winmat[0][0]); | |
} | |
else { | |
/* No sub-window created yet, for menus for example, so we use the main | |
* window instead, since buttons are created there anyway. */ | |
const int width = win_pixels_x(window); | |
const int height = win_pixels_y(window); | |
const rcti winrct = {0, width - 1, 0, height - 1}; | |
winGetProjectionMatrix(block->winmat, &winrct); | |
block->aspect = 2.0f / fabsf(width * block->winmat[0][0]); | |
} | |
} | |
void ui_region_winrct_get_no_margin(const ARegion *region, rcti *r_rect) | |
{ | |
uiBlock *block = static_cast<uiBlock *>(region->uiblocks.first); | |
if (block && (block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_RADIAL) == 0) { | |
lib_rcti_rctf_copy_floor(r_rect, &block->rect); | |
lib_rcti_translate(r_rect, region->winrct.xmin, region->winrct.ymin); | |
} | |
else { | |
*r_rect = region->winrct; | |
} | |
} | |
/* block calc */ | |
void ui_block_translate(uiBlock *block, int x, int y) | |
{ | |
LIST_FOREACH (uiBtn *, btn, &block->btns) { | |
lib_rctf_translate(&btn->rect, x, y); | |
} | |
lib_rctf_translate(&block->rect, x, y); | |
} | |
static bool ui_btn_is_row_alignment_group(const uiBtn *left, const uiBtn *right) | |
{ | |
const bool is_same_align_group = (left->alignnr && (left->alignnr == right->alignnr)); | |
return is_same_align_group && (left->rect.xmin < right->rect.xmin); | |
} | |
static void ui_block_bounds_calc_text(uiBlock *block, float offset) | |
{ | |
const uiStyle *style = UI_style_get(); | |
uiBut *col_bt; | |
int i = 0, j, x1addval = offset; | |
ui_fontstyle_set(&style->widget); | |
uiBtn *init_col_bt = static_cast<uiBtn *>(block->btns.first); | |
LIST_FOREACH (uiBtn *, bt, &block->btns) { | |
if (!ELEM(bt->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR_SPACER)) { | |
j = font_width(style->widget.uifont_id, bt->drawstr, sizeof(bt->drawstr)); | |
if (j > i) { | |
i = j; | |
} | |
} | |
/* Skip all buttons that are in a horizontal alignment group. | |
* We don't want to split them apart (but still check the row's width and apply current | |
* offsets). */ | |
if (bt->next && ui_btn_is_row_alignment_group(bt, bt->next)) { | |
int width = 0; | |
const int alignnr = bt->alignnr; | |
for (col_bt = bt; col_bt && col_bt->alignnr == alignnr; col_bt = col_bt->next) { | |
width += lib_rctf_size_x(&col_bt->rect); | |
col_bt->rect.xmin += x1addval; | |
col_bt->rect.xmax += x1addval; | |
} | |
if (width > i) { | |
i = width; | |
} | |
/* Give the following code the last button in the alignment group, there might have to be a | |
* split immediately after. */ | |
bt = col_bt ? col_bt->prev : nullptr; | |
} | |
if (bt && bt->next && bt->rect.xmin < bt->next->rect.xmin) { | |
/* End of this column, and it's not the last one. */ | |
for (col_bt = init_col_bt; col_bt->prev != bt; col_bt = col_bt->next) { | |
col_bt->rect.xmin = x1addval; | |
col_bt->rect.xmax = x1addval + i + block->bounds; | |
ui_btn_update(col_bt); /* clips text again */ | |
} | |
/* And we prepare next column. */ | |
x1addval += i + block->bounds; | |
i = 0; | |
init_col_bt = col_bt; | |
} | |
} | |
/* Last column. */ | |
for (col_bt = init_col_bt; col_bt; col_bt = col_bt->next) { | |
/* Recognize a horizontally arranged alignment group and skip its items. */ | |
if (col_bt->next && ui_btn_is_row_alignment_group(col_bt, col_bt->next)) { | |
const int alignnr = col_bt->alignnr; | |
for (; col_bt && col_bt->alignnr == alignnr; col_bt = col_bt->next) { | |
/* pass */ | |
} | |
} | |
if (!col_bt) { | |
break; | |
} | |
col_bt->rect.xmin = x1addval; | |
col_bt->rect.xmax = max_ff(x1addval + i + block->bounds, offset + block->minbounds); | |
ui_btn_update(col_bt); /* clips text again */ | |
} | |
} | |
void ui_block_bounds_calc(uiBlock *block) | |
{ | |
if (lib_list_is_empty(&block->btns)) { | |
if (block->panel) { | |
block->rect.xmin = 0.0; | |
block->rect.xmax = block->panel->sizex; | |
block->rect.ymin = 0.0; | |
block->rect.ymax = block->panel->sizey; | |
} | |
} | |
else { | |
lib_rctf_init_minmax(&block->rect); | |
LIST_FOREACH (uiBtn *, bt, &block->btns) { | |
lib_rctf_union(&block->rect, &bt->rect); | |
} | |
block->rect.xmin -= block->bounds; | |
block->rect.ymin -= block->bounds; | |
block->rect.xmax += block->bounds; | |
block->rect.ymax += block->bounds; | |
} | |
block->rect.xmax = block->rect.xmin + max_ff(lib_rctf_size_x(&block->rect), block->minbounds); | |
/* hardcoded exception... one with larger safety */ | |
uiBtn *bt = static_cast<uiBtn *>(block->btns.first); | |
const int xof = ((bt && STRPREFIX(bt->str, "ERROR")) ? 10 : 40) * UI_SCALE_FAC; | |
block->safety.xmin = block->rect.xmin - xof; | |
block->safety.ymin = block->rect.ymin - xof; | |
block->safety.xmax = block->rect.xmax + xof; | |
block->safety.ymax = block->rect.ymax + xof; | |
} | |
static void ui_block_bounds_calc_centered(Win *window, uiBlock *block) | |
{ | |
/* NOTE: this is used for the splash where window bounds event has not been | |
* updated by ghost, get the window bounds from ghost directly */ | |
const int xmax = win_pixels_x(window); | |
const int ymax = win_pixels_y(window); | |
ui_block_bounds_calc(block); | |
const int width = lib_rctf_size_x(&block->rect); | |
const int height = lib_rctf_size_y(&block->rect); | |
const int startx = (xmax * 0.5f) - (width * 0.5f); | |
const int starty = (ymax * 0.5f) - (height * 0.5f); | |
ui_block_translate(block, startx - block->rect.xmin, starty - block->rect.ymin); | |
/* now recompute bounds and safety */ | |
ui_block_bounds_calc(block); | |
} | |
static void ui_block_bounds_calc_centered_pie(uiBlock *block) | |
{ | |
const int xy[2] = { | |
int(block->pie_data.pie_center_spawned[0]), | |
int(block->pie_data.pie_center_spawned[1]), | |
}; | |
ui_block_translate(block, xy[0], xy[1]); | |
/* now recompute bounds and safety */ | |
ui_block_bounds_calc(block); | |
} | |
static void ui_block_bounds_calc_popup( | |
Win *window, uiBlock *block, eBlockBoundsCalc bounds_calc, const int xy[2], int r_xy[2]) | |
{ | |
const int oldbounds = block->bounds; | |
/* compute mouse position with user defined offset */ | |
ui_block_bounds_calc(block); | |
const int xmax = win_pixels_x(window); | |
const int ymax = winpixels_y(window); | |
int oldwidth = lib_rctf_size_x(&block->rect); | |
int oldheight = lib_rctf_size_y(&block->rect); | |
/* first we ensure wide enough text bounds */ | |
if (bounds_calc == UI_BLOCK_BOUNDS_POPUP_MENU) { | |
if (block->flag & UI_BLOCK_LOOP) { | |
block->bounds = 2.5f * UI_UNIT_X; | |
ui_block_bounds_calc_text(block, block->rect.xmin); | |
} | |
} | |
/* next we recompute bounds */ | |
block->bounds = oldbounds; | |
ui_block_bounds_calc(block); | |
/* and we adjust the position to fit within window */ | |
const int width = lib_rctf_size_x(&block->rect); | |
const int height = lib_rctf_size_y(&block->rect); | |
/* avoid divide by zero below, caused by calling with no UI, but better not crash */ | |
oldwidth = oldwidth > 0 ? oldwidth : MAX2(1, width); | |
oldheight = oldheight > 0 ? oldheight : MAX2(1, height); | |
/* offset block based on mouse position, user offset is scaled | |
* along in case we resized the block in ui_block_bounds_calc_text */ | |
rcti rect; | |
const int raw_x = rect.xmin = xy[0] + block->rect.xmin + | |
(block->bounds_offset[0] * width) / oldwidth; | |
int raw_y = rect.ymin = xy[1] + block->rect.ymin + | |
(block->bounds_offset[1] * height) / oldheight; | |
rect.xmax = rect.xmin + width; | |
rect.ymax = rect.ymin + height; | |
rcti rect_bounds; | |
const int margin = UI_SCREEN_MARGIN; | |
rect_bounds.xmin = margin; | |
rect_bounds.ymin = margin; | |
rect_bounds.xmax = xmax - margin; | |
rect_bounds.ymax = ymax - UI_POPUP_MENU_TOP; | |
int ofs_dummy[2]; | |
lib_rcti_clamp(&rect, &rect_bounds, ofs_dummy); | |
lib_block_translate(block, rect.xmin - block->rect.xmin, rect.ymin - block->rect.ymin); | |
/* now recompute bounds and safety */ | |
ui_block_bounds_calc(block); | |
/* If given, adjust input coordinates such that they would generate real final popup position. | |
* Needed to handle correctly floating panels once they have been dragged around, | |
* see #52999. */ | |
if (r_xy) { | |
r_xy[0] = xy[0] + block->rect.xmin - raw_x; | |
r_xy[1] = xy[1] + block->rect.ymin - raw_y; | |
} | |
} | |
void ui_block_bounds_set_normal(uiBlock *block, int addval) | |
{ | |
if (block == nullptr) { | |
return; | |
} | |
block->bounds = addval; | |
block->bounds_type = UI_BLOCK_BOUNDS; | |
} | |
void ui_block_bounds_set_text(uiBlock *block, int addval) | |
{ | |
block->bounds = addval; | |
block->bounds_type = UI_BLOCK_BOUNDS_TEXT; | |
} | |
void ui_block_bounds_set_popup(uiBlock *block, int addval, const int bounds_offset[2]) | |
{ | |
block->bounds = addval; | |
block->bounds_type = UI_BLOCK_BOUNDS_POPUP_MOUSE; | |
if (bounds_offset != nullptr) { | |
block->bounds_offset[0] = bounds_offset[0]; | |
block->bounds_offset[1] = bounds_offset[1]; | |
} | |
else { | |
block->bounds_offset[0] = 0; | |
block->bounds_offset[1] = 0; | |
} | |
} | |
void ui_block_bounds_set_menu(uiBlock *block, int addval, const int bounds_offset[2]) | |
{ | |
block->bounds = addval; | |
block->bounds_type = UI_BLOCK_BOUNDS_POPUP_MENU; | |
if (bounds_offset != nullptr) { | |
copy_v2_v2_int(block->bounds_offset, bounds_offset); | |
} | |
else { | |
zero_v2_int(block->bounds_offset); | |
} | |
} | |
void ui_block_bounds_set_centered(uiBlock *block, int addval) | |
{ | |
block->bounds = addval; | |
block->bounds_type = UI_BLOCK_BOUNDS_POPUP_CENTER; | |
} | |
void ui_block_bounds_set_explicit(uiBlock *block, int minx, int miny, int maxx, int maxy) | |
{ | |
block->rect.xmin = minx; | |
block->rect.ymin = miny; | |
block->rect.xmax = maxx; | |
block->rect.ymax = maxy; | |
block->bounds_type = UI_BLOCK_BOUNDS_NONE; | |
} | |
static float ui_btn_get_float_precision(uiBtn *btn) | |
{ | |
if (btn->type == UI_BTYPE_NUM) { | |
return ((uiButNumber *)but)->precision; | |
} | |
return btn->a2; | |
} | |
static float ui_btn_get_float_step_size(uiBtn *btn) | |
{ | |
if (btn->type == UI_BTYPE_NUM) { | |
return ((uiBtnNumber *)btn)->step_size; | |
} | |
return btn->a1; | |
} | |
static bool ui_btn_hide_fraction(uiBtn *btn, double value) | |
{ | |
/* Hide the fraction if both the value and the step are exact integers. */ | |
if (floor(value) == value) { | |
const float step = ui_btn_get_float_step_size(but) * UI_PRECISION_FLOAT_SCALE; | |
if (floorf(step) == step) { | |
/* Don't hide if it has any unit except frame count. */ | |
switch (UI_but_unit_type_get(but)) { | |
case PROP_UNIT_NONE: | |
case PROP_UNIT_TIME: | |
return true; | |
default: | |
return false; | |
} | |
} | |
} | |
return false; | |
} | |
static int ui_btn_calc_float_precision(uiBtn *btn, double value) | |
{ | |
if (ui_btn_hide_fraction(btn, value)) { | |
return 0; | |
} | |
int prec = int(ui_btn_get_float_precision(but)); | |
/* first check for various special cases: | |
* * If button is radians, we want additional precision (see #39861). | |
* * If prec is not set, we fallback to a simple default */ | |
if (ui_but_is_unit_radians(btn) && prec < 5) { | |
prec = 5; | |
} | |
else if (prec == -1) { | |
prec = (btn->hardmax < 10.001f) ? 3 : 2; | |
} | |
else { | |
CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX); | |
} | |
return ui_calc_float_precision(prec, value); | |
} | |
/* BLOCK ENDING FN */ | |
bool ui_btn_api_equals(const uiBtn *a, const uiBtn *b) | |
{ | |
return ui_btn_api_equals_ex(a, &b->apipoin, b->apiprop, b->apiindex); | |
} | |
bool ui_btn_api_equals_ex(const uiBtn *btn, | |
const ApiPtr *ptr, | |
const ApiProp *prop, | |
int index) | |
{ | |
if (btn->apipoin.data != ptr->data) { | |
return false; | |
} | |
if (btn->apiprop != prop || btn->apiindex != index) { | |
return false; | |
} | |
return true; | |
} | |
/* NOTE: if `btn->poin` is allocated memory for every `uiDefBtn*`, things fail. */ | |
static bool ui_btn_equals_old(const uiBtn *btn, const uiBtn *oldbtn) | |
{ | |
if (btn->id_cmp_fn) { | |
/* If the btns have own id comparator cbs (and they match), use this to | |
* determine equality. */ | |
if (btn->id_cmp_fn && (btn->type == oldbtn->type) && | |
(btn->id_cmp_fn == oldbtn->identity_cmp_fn)) | |
{ | |
/* Test if the comparison is symmetrical (if a == b then b == a), may help catch some issues. */ | |
lib_assert(btn->id_cmp_fn(btn, oldbtn) == btn->id_cmp_fn(oldtn, btn)); | |
return btn->id_cmp_fn(btn, oldbtn); | |
} | |
} | |
/* various props are being compared here, hopefully sufficient | |
* to catch all cases, but it is simple to add more checks later */ | |
if (btn->retval != oldbtn->retval) { | |
return false; | |
} | |
if (!ui_btn_api_equals(btn, oldbtn)) { | |
return false; | |
} | |
if (btn->fn != oldbtn->fn) { | |
return false; | |
} | |
/* Compares the contained fn ptrs. Btns with different apply fns can be | |
* considered to do different things, and as such do not equal each other. */ | |
if (btn->apply_fn.target<void(Cxt &)>() != oldbtn->apply_fn.target<void(Cxt &)>()) | |
{ | |
return false; | |
} | |
if (btn->fnN != oldBtn->fnN) { | |
return false; | |
} | |
if (!ELEM(oldbtn->fn_arg1, oldbtn, btn->fn_arg1)) { | |
return false; | |
} | |
if (!ELEM(oldbtn->fn_arg2, oldbtn, btn->fn_arg2)) { | |
return false; | |
} | |
if (!btn->fnN && ((btn->ptr != oldbtn->ptr && (uiBtn *)oldbtn->ptr != oldbtn) || | |
(btn->pointype != oldbtn->pointype))) | |
{ | |
return false; | |
} | |
if (btn->optype != oldbtn->optype) { | |
return false; | |
} | |
if (btn->dragtype != oldbtn->dragtype) { | |
return false; | |
} | |
if ((btn->type == UI_BTYPE_VIEW_ITEM) && (oldbtn->type == UI_BTYPE_VIEW_ITEM)) { | |
uiBtnViewItem *btn_item = (uiBtnViewItem *)btn; | |
uiBtnViewItem *oldbtn_item = (uiBtnViewItem *)oldbtn; | |
if (!btn_item->view_item || !oldbtn_item->view_item || | |
!ui_view_item_matches(btn_item->view_item, oldbtn_item->view_item)) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
uiBtn *ui_btn_find_old(uiBlock *block_old, const uiBtn *btn_new) | |
{ | |
LIST_FOREACH (uiBtn *, btn, &block_old->btns) { | |
if (ui_btn_equals_old(btn_new, btn)) { | |
return btn; | |
} | |
} | |
return nullptr; | |
} | |
uiBtn *ui_btn_find_new(uiBlock *block_new, const uiBtn *btn_old) | |
{ | |
LIST_FOREACH (uiBtn *, btn, &block_new->btns) { | |
if (ui_btn_equals_old(btn, btn_old)) { | |
return but; | |
} | |
} | |
return nullptr; | |
} | |
static bool ui_btn_extra_icons_equals_old(const uiBtnExtraOpIcon *new_extra_icon, | |
const uiBtnExtraOpIcon *old_extra_icon) | |
{ | |
return (new_extra_icon->optype_params->optype == old_extra_icon->optype_params->optype) && | |
(new_extra_icon->icon == old_extra_icon->icon); | |
} | |
static uiBtnExtraOpIcon *ui_but_extra_icon_find_old(const uiBtnExtraOpIcon *new_extra_icon, | |
const uiBtn *old_btn) | |
{ | |
LIST_FOREACH (uiBtnExtraOpIcon *, op_icon, &old_btn->extra_op_icons) { | |
if (ui_btn_extra_icons_equals_old(new_extra_icon, op_icon)) { | |
return op_icon; | |
} | |
} | |
return nullptr; | |
} | |
static void ui_btn_extra_icons_update_from_old_btn(const uiBtn *new_btn, const uiBtn *old_btn) | |
{ | |
/* Specifically for keeping some state info for the active button. */ | |
lib_assert(old_but->active); | |
LIST_FOREACH (uiButExtraOpIcon *, new_extra_icon, &new_btn->extra_op_icons) { | |
uiBtnExtraOpIcon *old_extra_icon = ui_btn_extra_icon_find_old(new_extra_icon, old_btn); | |
/* Keep the highlighting state, and let handling update it later. */ | |
if (old_extra_icon) { | |
new_extra_icon->highlighted = old_extra_icon->highlighted; | |
} | |
} | |
} | |
/* Update ptrs and other info in the old active btn based on new information in the | |
* corresponding new button from the current layout pass. | |
* | |
* param oldbtn: The button from the last layout pass that will be moved to the new block. | |
* param btn: The newly added button with much of the up to date information, to be feed later. | |
* | |
* note uiBtn has ownership of many of its pointers. When the btn is freed all these | |
* pointers are freed as well, so ownership has to be moved out of btn in order to free it. | |
*/ | |
static void ui_btn_update_old_active_from_new(uiBtn *oldbtn, uiBtn *btn) | |
{ | |
lib_assert(oldbtn->active); | |
/* flags from the buttons we want to refresh, may want to add more here... */ | |
const int flag_copy = UI_BTN_REDALERT | UI_HAS_ICON | UI_SELECT_DRAW; | |
const int drawflag_copy = UI_BTN_HAS_TOOLTIP_LABEL; | |
/* still stuff needs to be copied */ | |
oldbtn->rect = btn->rect; | |
oldbtn->cxt = btn->cxt; /* set by Layout */ | |
/* drawing */ | |
oldbtn->icon = btn->icon; | |
oldbtn->iconadd = btn->iconadd; | |
oldbtn->alignnr = btn->alignnr; | |
/* typically the same ptrs, btn not on undo/redo */ | |
/* some menu btns store btn itself in btn->ptr. Ugly */ | |
if (oldbtn->ptr != (char *)oldbtn) { | |
std::swap(oldbut->poin, but->poin); | |
std::swap(oldbut->func_argN, but->func_argN); | |
} | |
/* Move tooltip from new to old. */ | |
std::swap(oldbtn->tip_fn, but->tip_fn); | |
std::swap(oldbtn->tip_arg, but->tip_arg); | |
std::swap(oldbtn->tip_arg_free, but->tip_arg_free); | |
std::swap(oldbtn->tip_label_fn, but->tip_label_fn); | |
oldbtn->flag = (oldbtn->flag & ~flag_copy) | (btn->flag & flag_copy); | |
oldbtn->drawflag = (oldbtn->drawflag & ~drawflag_copy) | (btn->drawflag & drawflag_copy); | |
ui_btn_extra_icons_update_from_old_btn(btn, oldbtn); | |
std::swap(btn->extra_op_icons, oldbtn->extra_op_icons); | |
if (oldbtn->type == UI_BTYPE_SEARCH_MENU) { | |
uiBtnSearch *search_oldbtn = (uiBtnSearch *)oldbtn, *search_btn = (uiBtnSearch *)but; | |
std::swap(search_oldbtn->arg_free_fn, search_btn->arg_free_fn); | |
std::swap(search_oldbtn->arg, search_btn->arg); | |
} | |
/* copy hardmin for list rows to prevent 'sticking' highlight to mouse position | |
* when scrolling without moving mouse (see #28432) */ | |
if (ELEM(oldbtn->type, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) { | |
oldbtn->hardmax = btn->hardmax; | |
} | |
switch (oldbtn->type) { | |
case UI_BTYPE_PROGRESS: { | |
uiBtnProgress *progress_oldbtn = (uiBtnProgress *)oldbtn; | |
uiBtnProgress *progress_btn = (uiBtnProgress *)btn; | |
progress_oldbtn->progress_factor = progress_btn->progress_factor; | |
break; | |
} | |
case UI_BTYPE_VIEW_ITEM: { | |
uiBtnViewItem *view_item_oldbtn = (uiBtnViewItem *)oldbtn; | |
uiBtnViewItem *view_item_newbtn = (uiBtnViewItem *)btn; | |
ui_view_item_swap_btn_ptrs(view_item_newbtn->view_item, view_item_oldbtn->view_item); | |
std::swap(view_item_newbtn->view_item, view_item_oldbtn->view_item); | |
break; | |
} | |
default: | |
break; | |
} | |
/* move/copy string from the new button to the old */ | |
/* needed for alt+mouse wheel over enums */ | |
if (btn->str != btn->strdata) { | |
if (oldbtn->str != oldbtn->strdata) { | |
std::swap(btn->str, oldbtn->str); | |
} | |
else { | |
oldbtn->str = btn->str; | |
btn->str = btn->strdata; | |
} | |
} | |
else { | |
if (oldbtn->str != oldbtn->strdata) { | |
mem_free(oldbtn->str); | |
oldbtn->str = oldbtn->strdata; | |
} | |
STRNCPY(oldbtn->strdata, btn->strdata); | |
} | |
if (btn->dragptr) { | |
std::swap(btn->dragptr, oldbtn->dragptr); | |
} | |
if (btn->imb) { | |
std::swap(btn->imb, oldbtn->imb); | |
} | |
/* NOTE: if layout hasn't been applied yet, it uses old button pointers... */ | |
} | |
/* return true when btn_p is set (only done for active btns) */ | |
static bool ui_btn_update_from_old_block(const Cxt *C, | |
uiBlock *block, | |
uiBut **btn_p, | |
uiBut **btn_old_p) | |
{ | |
uiBlock *oldblock = block->oldblock; | |
uiBtn *btn = *btn_p; | |
#if 0 | |
/* Simple method - search every time. Keep this for easy testing of the "fast path." */ | |
uiBtn *oldbtn = ui_btn_find_old(oldblock, btn); | |
UNUSED_VARS(btn_old_p); | |
#else | |
lib_assert(*btn_old_p == nullptr || lib_findindex(&oldblock->btns, *btn_old_p) != -1); | |
/* As long as old and new buttons are aligned, avoid loop-in-loop (calling #ui_but_find_old). */ | |
uiBtn *oldbtn; | |
if (LIKELY(*btn_old_p && ui_btn_equals_old(btn, *btn_old_p))) { | |
oldbtn = *btn_old_p; | |
} | |
else { | |
/* Fallback to block search. */ | |
oldbtn = ui_btn_find_old(oldblock, btn); | |
} | |
(*btn_old_p) = oldbtn ? oldbtn->next : nullptr; | |
#endif | |
bool found_active = false; | |
if (!oldbtn) { | |
return false; | |
} | |
if (oldbtn->active) { | |
/* Move button over from oldblock to new block. */ | |
lib_remlink(&oldblock->btns, oldbtn); | |
lib_insertlinkafter(&block->btns, but, oldbtn); | |
/* Add the old button to the button groups in the new block. */ | |
ui_btn_group_replace_btn_ptr(block, btn, oldbtn); | |
oldbtn->block = block; | |
*btn_p = oldbtn; | |
ui_btn_update_old_active_from_new(oldbtn, btn); | |
if (!lib_list_is_empty(&block->btnstore)) { | |
ui_btnstore_register_update(block, oldbtn, btn); | |
} | |
lib_remlink(&block->btns, btn); | |
ui_btn_free(C, btn); | |
found_active = true; | |
} | |
else { | |
int flag_copy = UI_BTN_DRAG_MULTI; | |
/* Stupid special case: The active button may be inside (as in, overlapped on top) a row | |
* button which we also want to keep highlighted then. */ | |
if (ELEM(btn->type, UI_BTYPE_VIEW_ITEM, UI_BTYPE_LISTROW)) { | |
flag_copy |= UI_ACTIVE; | |
} | |
btn->flag = (btn->flag & ~flag_copy) | (oldbtn->flag & flag_copy); | |
/* ensures one button can get activated, and in case the buttons | |
* draw are the same this gives O(1) lookup for each button */ | |
lib_remlink(&oldblock->btns, oldbtn); | |
ui_btn_free(C, oldbtn); | |
} | |
return found_active; | |
} | |
bool ui_btn_active_only_ex( | |
const Cxt *C, ARegion *region, uiBlock *block, uiBtn *btn, const bool remove_on_failure) | |
{ | |
bool activate = false, found = false, isactive = false; | |
uiBlock *oldblock = block->oldblock; | |
if (!oldblock) { | |
activate = true; | |
} | |
else { | |
uiBtn *oldbtn = ui_btn_find_old(oldblock, btn); | |
if (oldbtn) { | |
found = true; | |
if (oldbut->active) { | |
isactive = true; | |
} | |
} | |
} | |
if ((activate == true) || (found == false)) { | |
/* There might still be another active button. */ | |
uiBut *old_active = ui_region_find_active_but(region); | |
if (old_active) { | |
ui_but_active_free(C, old_active); | |
} | |
ui_but_activate_event((bContext *)C, region, but); | |
} | |
else if ((found == true) && (isactive == false)) { | |
if (remove_on_failure) { | |
BLI_remlink(&block->buttons, but); | |
if (but->layout) { | |
ui_layout_remove_but(but->layout, but); | |
} | |
ui_but_free(C, but); | |
} | |
return false; | |
} | |
return true; | |
} | |
bool UI_but_active_only(const bContext *C, ARegion *region, uiBlock *block, uiBut *but) | |
{ | |
return UI_but_active_only_ex(C, region, block, but, true); | |
} | |
bool UI_block_active_only_flagged_buttons(const bContext *C, ARegion *region, uiBlock *block) | |
{ | |
/* Running this command before end-block has run, means buttons that open menus | |
* won't have those menus correctly positioned, see #83539. */ | |
BLI_assert(block->endblock); | |
bool done = false; | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
if (but->flag & UI_BUT_ACTIVATE_ON_INIT) { | |
but->flag &= ~UI_BUT_ACTIVATE_ON_INIT; | |
if (ui_but_is_editable(but)) { | |
if (UI_but_active_only_ex(C, region, block, but, false)) { | |
done = true; | |
break; | |
} | |
} | |
} | |
} | |
if (done) { | |
/* Run this in a second pass since it's possible activating the button | |
* removes the buttons being looped over. */ | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
but->flag &= ~UI_BUT_ACTIVATE_ON_INIT; | |
} | |
} | |
return done; | |
} | |
void UI_but_execute(const bContext *C, ARegion *region, uiBut *but) | |
{ | |
void *active_back; | |
ui_but_execute_begin((bContext *)C, region, but, &active_back); | |
/* Value is applied in begin. No further action required. */ | |
ui_but_execute_end((bContext *)C, region, but, active_back); | |
} | |
/* use to check if we need to disable undo, but don't make any changes | |
* returns false if undo needs to be disabled. */ | |
static bool ui_but_is_rna_undo(const uiBut *but) | |
{ | |
if (but->rnapoin.owner_id) { | |
/* avoid undo push for buttons who's ID are screen or wm level | |
* we could disable undo for buttons with no ID too but may have | |
* unforeseen consequences, so best check for ID's we _know_ are not | |
* handled by undo - campbell */ | |
ID *id = but->rnapoin.owner_id; | |
if (ID_CHECK_UNDO(id) == false) { | |
return false; | |
} | |
} | |
if (but->rnapoin.type && !RNA_struct_undo_check(but->rnapoin.type)) { | |
return false; | |
} | |
return true; | |
} | |
/* assigns automatic keybindings to menu items for fast access | |
* (underline key in menu) */ | |
static void ui_menu_block_set_keyaccels(uiBlock *block) | |
{ | |
uint menu_key_mask = 0; | |
int tot_missing = 0; | |
/* only do it before bounding */ | |
if (block->rect.xmin != block->rect.xmax) { | |
return; | |
} | |
for (int pass = 0; pass < 2; pass++) { | |
/* 2 Passes: One for first letter only, second for any letter if the first pass fails. | |
* Run first pass on all buttons so first word chars always get first priority. */ | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
if (!ELEM(but->type, | |
UI_BTYPE_BUT, | |
UI_BTYPE_BUT_MENU, | |
UI_BTYPE_MENU, | |
UI_BTYPE_BLOCK, | |
UI_BTYPE_PULLDOWN, | |
/* For PIE-menus. */ | |
UI_BTYPE_ROW) || | |
(but->flag & UI_HIDDEN)) | |
{ | |
continue; | |
} | |
if (but->menu_key != '\0') { | |
continue; | |
} | |
if (but->str == nullptr || but->str[0] == '\0') { | |
continue; | |
} | |
const char *str_pt = but->str; | |
uchar menu_key; | |
do { | |
menu_key = tolower(*str_pt); | |
if ((menu_key >= 'a' && menu_key <= 'z') && !(menu_key_mask & 1 << (menu_key - 'a'))) { | |
menu_key_mask |= 1 << (menu_key - 'a'); | |
break; | |
} | |
if (pass == 0) { | |
/* Skip to next delimiter on first pass (be picky) */ | |
while (isalpha(*str_pt)) { | |
str_pt++; | |
} | |
if (*str_pt) { | |
str_pt++; | |
} | |
} | |
else { | |
/* just step over every char second pass and find first usable key */ | |
str_pt++; | |
} | |
} while (*str_pt); | |
if (*str_pt) { | |
but->menu_key = menu_key; | |
} | |
else { | |
/* run second pass */ | |
tot_missing++; | |
} | |
/* if all keys have been used just exit, unlikely */ | |
if (menu_key_mask == (1 << 26) - 1) { | |
return; | |
} | |
} | |
/* check if second pass is needed */ | |
if (!tot_missing) { | |
break; | |
} | |
} | |
} | |
void ui_but_add_shortcut(uiBut *but, const char *shortcut_str, const bool do_strip) | |
{ | |
if (do_strip && (but->flag & UI_BUT_HAS_SEP_CHAR)) { | |
char *cpoin = strrchr(but->str, UI_SEP_CHAR); | |
if (cpoin) { | |
*cpoin = '\0'; | |
} | |
but->flag &= ~UI_BUT_HAS_SEP_CHAR; | |
} | |
/* without this, just allow stripping of the shortcut */ | |
if (shortcut_str == nullptr) { | |
return; | |
} | |
char *butstr_orig; | |
if (but->str != but->strdata) { | |
butstr_orig = but->str; /* free after using as source buffer */ | |
} | |
else { | |
butstr_orig = BLI_strdup(but->str); | |
} | |
SNPRINTF(but->strdata, "%s" UI_SEP_CHAR_S "%s", butstr_orig, shortcut_str); | |
MEM_freeN(butstr_orig); | |
but->str = but->strdata; | |
but->flag |= UI_BUT_HAS_SEP_CHAR; | |
ui_but_update(but); | |
} | |
/* -------------------------------------------------------------------- */ | |
/** \name Find Key Shortcut for Button | |
* | |
* - #ui_but_event_operator_string (and helpers) | |
* - #ui_but_event_property_operator_string | |
* \{ */ | |
static bool ui_but_event_operator_string_from_operator(const bContext *C, | |
wmOperatorCallParams *op_call_params, | |
char *buf, | |
const size_t buf_maxncpy) | |
{ | |
BLI_assert(op_call_params->optype != nullptr); | |
bool found = false; | |
IDProperty *prop = reinterpret_cast<IDProperty *>(op_call_params->opptr) ? | |
static_cast<IDProperty *>(op_call_params->opptr->data) : | |
nullptr; | |
if (WM_key_event_operator_string(C, | |
op_call_params->optype->idname, | |
op_call_params->opcontext, | |
prop, | |
true, | |
buf, | |
buf_maxncpy)) | |
{ | |
found = true; | |
} | |
return found; | |
} | |
static bool ui_but_event_operator_string_from_menu(const bContext *C, | |
uiBut *but, | |
char *buf, | |
const size_t buf_maxncpy) | |
{ | |
MenuType *mt = UI_but_menutype_get(but); | |
BLI_assert(mt != nullptr); | |
bool found = false; | |
/* annoying, create a property */ | |
const IDPropertyTemplate val = {0}; | |
IDProperty *prop_menu = IDP_New(IDP_GROUP, &val, __func__); /* Dummy, name is unimportant. */ | |
IDP_AddToGroup(prop_menu, IDP_NewStringMaxSize(mt->idname, "name", sizeof(mt->idname))); | |
if (WM_key_event_operator_string( | |
C, "WM_OT_call_menu", WM_OP_INVOKE_REGION_WIN, prop_menu, true, buf, buf_maxncpy)) | |
{ | |
found = true; | |
} | |
IDP_FreeProperty(prop_menu); | |
return found; | |
} | |
static bool ui_but_event_operator_string_from_panel(const bContext *C, | |
uiBut *but, | |
char *buf, | |
const size_t buf_maxncpy) | |
{ | |
/** Nearly exact copy of #ui_but_event_operator_string_from_menu */ | |
PanelType *pt = UI_but_paneltype_get(but); | |
BLI_assert(pt != nullptr); | |
bool found = false; | |
/* annoying, create a property */ | |
const IDPropertyTemplate group_val = {0}; | |
IDProperty *prop_panel = IDP_New( | |
IDP_GROUP, &group_val, __func__); /* Dummy, name is unimportant. */ | |
IDP_AddToGroup(prop_panel, IDP_NewStringMaxSize(pt->idname, "name", sizeof(pt->idname))); | |
IDPropertyTemplate space_type_val = {0}; | |
space_type_val.i = pt->space_type; | |
IDP_AddToGroup(prop_panel, IDP_New(IDP_INT, &space_type_val, "space_type")); | |
IDPropertyTemplate region_type_val = {0}; | |
region_type_val.i = pt->region_type; | |
IDP_AddToGroup(prop_panel, IDP_New(IDP_INT, ®ion_type_val, "region_type")); | |
for (int i = 0; i < 2; i++) { | |
/* FIXME(@ideasman42): We can't reasonably search all configurations - long term. */ | |
IDPropertyTemplate val = {0}; | |
val.i = i; | |
IDP_ReplaceInGroup(prop_panel, IDP_New(IDP_INT, &val, "keep_open")); | |
if (WM_key_event_operator_string( | |
C, "WM_OT_call_panel", WM_OP_INVOKE_REGION_WIN, prop_panel, true, buf, buf_maxncpy)) | |
{ | |
found = true; | |
break; | |
} | |
} | |
IDP_FreeProperty(prop_panel); | |
return found; | |
} | |
static bool ui_but_event_operator_string(const bContext *C, | |
uiBut *but, | |
char *buf, | |
const size_t buf_maxncpy) | |
{ | |
bool found = false; | |
if (but->optype != nullptr) { | |
wmOperatorCallParams params = {}; | |
params.optype = but->optype; | |
params.opptr = but->opptr; | |
params.opcontext = but->opcontext; | |
found = ui_but_event_operator_string_from_operator(C, ¶ms, buf, buf_maxncpy); | |
} | |
else if (UI_but_menutype_get(but) != nullptr) { | |
found = ui_but_event_operator_string_from_menu(C, but, buf, buf_maxncpy); | |
} | |
else if (UI_but_paneltype_get(but) != nullptr) { | |
found = ui_but_event_operator_string_from_panel(C, but, buf, buf_maxncpy); | |
} | |
return found; | |
} | |
static bool ui_but_extra_icon_event_operator_string(const bContext *C, | |
uiButExtraOpIcon *extra_icon, | |
char *buf, | |
const size_t buf_maxncpy) | |
{ | |
wmOperatorType *extra_icon_optype = UI_but_extra_operator_icon_optype_get(extra_icon); | |
if (extra_icon_optype) { | |
return ui_but_event_operator_string_from_operator( | |
C, extra_icon->optype_params, buf, buf_maxncpy); | |
} | |
return false; | |
} | |
static bool ui_but_event_property_operator_string(const bContext *C, | |
uiBut *but, | |
char *buf, | |
const size_t buf_maxncpy) | |
{ | |
/* Context toggle operator names to check. */ | |
/* This function could use a refactor to generalize button type to operator relationship | |
* as well as which operators use properties. - Campbell */ | |
const char *ctx_toggle_opnames[] = { | |
"WM_OT_context_toggle", | |
"WM_OT_context_toggle_enum", | |
"WM_OT_context_cycle_int", | |
"WM_OT_context_cycle_enum", | |
"WM_OT_context_cycle_array", | |
"WM_OT_context_menu_enum", | |
nullptr, | |
}; | |
const char *ctx_enum_opnames[] = { | |
"WM_OT_context_set_enum", | |
nullptr, | |
}; | |
const char *ctx_enum_opnames_for_Area_ui_type[] = { | |
"SCREEN_OT_space_type_set_or_cycle", | |
nullptr, | |
}; | |
const char **opnames = ctx_toggle_opnames; | |
int opnames_len = ARRAY_SIZE(ctx_toggle_opnames); | |
int prop_enum_value = -1; | |
bool prop_enum_value_ok = false; | |
bool prop_enum_value_is_int = false; | |
const char *prop_enum_value_id = "value"; | |
PointerRNA *ptr = &but->rnapoin; | |
PropertyRNA *prop = but->rnaprop; | |
int prop_index = but->rnaindex; | |
if ((but->type == UI_BTYPE_BUT_MENU) && (but->block->handle != nullptr)) { | |
uiBut *but_parent = but->block->handle->popup_create_vars.but; | |
if ((but->type == UI_BTYPE_BUT_MENU) && (but_parent && but_parent->rnaprop) && | |
(RNA_property_type(but_parent->rnaprop) == PROP_ENUM) && | |
ELEM(but_parent->menu_create_func, | |
ui_def_but_rna__menu, | |
ui_def_but_rna__panel_type, | |
ui_def_but_rna__menu_type)) | |
{ | |
prop_enum_value = int(but->hardmin); | |
ptr = &but_parent->rnapoin; | |
prop = but_parent->rnaprop; | |
prop_enum_value_ok = true; | |
opnames = ctx_enum_opnames; | |
opnames_len = ARRAY_SIZE(ctx_enum_opnames); | |
} | |
} | |
/* Don't use the button again. */ | |
but = nullptr; | |
if (prop == nullptr) { | |
return false; | |
} | |
/* This version is only for finding hotkeys for properties. | |
* These are set via a data-path which is appended to the context, | |
* manipulated using operators (see #ctx_toggle_opnames). */ | |
if (ptr->owner_id) { | |
ID *id = ptr->owner_id; | |
if (GS(id->name) == ID_SCR) { | |
if (RNA_struct_is_a(ptr->type, &RNA_Area)) { | |
/* data should be directly on here... */ | |
const char *prop_id = RNA_property_identifier(prop); | |
/* Hack since keys access 'type', UI shows 'ui_type'. */ | |
if (STREQ(prop_id, "ui_type")) { | |
prop_id = "type"; | |
prop_enum_value >>= 16; | |
prop = RNA_struct_find_property(ptr, prop_id); | |
prop_index = -1; | |
opnames = ctx_enum_opnames_for_Area_ui_type; | |
opnames_len = ARRAY_SIZE(ctx_enum_opnames_for_Area_ui_type); | |
prop_enum_value_id = "space_type"; | |
prop_enum_value_is_int = true; | |
} | |
} | |
} | |
} | |
/* There may be multiple data-paths to the same properties, | |
* support different variations so key bindings are properly detected no matter which are used. | |
*/ | |
char *data_path_variations[2] = {nullptr}; | |
int data_path_variations_num = 0; | |
{ | |
char *data_path = WM_context_path_resolve_property_full(C, ptr, prop, prop_index); | |
/* Always iterate once, even if data-path isn't set. */ | |
data_path_variations[data_path_variations_num++] = data_path; | |
if (data_path) { | |
if (STRPREFIX(data_path, "scene.tool_settings.")) { | |
data_path_variations[data_path_variations_num++] = BLI_strdup(data_path + 6); | |
} | |
} | |
} | |
/* We have a data-path! */ | |
bool found = false; | |
for (int data_path_index = 0; data_path_index < data_path_variations_num && (found == false); | |
data_path_index++) | |
{ | |
const char *data_path = data_path_variations[data_path_index]; | |
if (data_path || (prop_enum_value_ok && prop_enum_value_id)) { | |
/* Create a property to host the "data_path" property we're sending to the operators. */ | |
IDProperty *prop_path; | |
const IDPropertyTemplate group_val = {0}; | |
prop_path = IDP_New(IDP_GROUP, &group_val, __func__); | |
if (data_path) { | |
IDP_AddToGroup(prop_path, IDP_NewString(data_path, "data_path")); | |
} | |
if (prop_enum_value_ok) { | |
const EnumPropertyItem *item; | |
bool free; | |
RNA_property_enum_items((bContext *)C, ptr, prop, &item, nullptr, &free); | |
const int index = RNA_enum_from_value(item, prop_enum_value); | |
if (index != -1) { | |
IDProperty *prop_value; | |
if (prop_enum_value_is_int) { | |
const int value = item[index].value; | |
IDPropertyTemplate val = {}; | |
val.i = value; | |
prop_value = IDP_New(IDP_INT, &val, prop_enum_value_id); | |
} | |
else { | |
const char *id = item[index].identifier; | |
prop_value = IDP_NewString(id, prop_enum_value_id); | |
} | |
IDP_AddToGroup(prop_path, prop_value); | |
} | |
else { | |
opnames_len = 0; /* Do nothing. */ | |
} | |
if (free) { | |
MEM_freeN((void *)item); | |
} | |
} | |
/* check each until one works... */ | |
for (int i = 0; (i < opnames_len) && (opnames[i]); i++) { | |
if (WM_key_event_operator_string( | |
C, opnames[i], WM_OP_INVOKE_REGION_WIN, prop_path, false, buf, buf_maxncpy)) | |
{ | |
found = true; | |
break; | |
} | |
} | |
/* cleanup */ | |
IDP_FreeProperty(prop_path); | |
} | |
} | |
for (int data_path_index = 0; data_path_index < data_path_variations_num; data_path_index++) { | |
char *data_path = data_path_variations[data_path_index]; | |
if (data_path) { | |
MEM_freeN(data_path); | |
} | |
} | |
return found; | |
} | |
/** \} */ | |
/* -------------------------------------------------------------------- */ | |
/** \name Pie Menu Direction | |
* | |
* This goes in a seemingly weird pattern: | |
* | |
* <pre> | |
* 4 | |
* 5 6 | |
* 1 2 | |
* 7 8 | |
* 3 | |
* </pre> | |
* | |
* but it's actually quite logical. It's designed to be 'upwards compatible' | |
* for muscle memory so that the menu item locations are fixed and don't move | |
* as new items are added to the menu later on. It also optimizes efficiency - | |
* a radial menu is best kept symmetrical, with as large an angle between | |
* items as possible, so that the gestural mouse movements can be fast and inexact. | |
* | |
* It starts off with two opposite sides for the first two items | |
* then joined by the one below for the third (this way, even with three items, | |
* the menu seems to still be 'in order' reading left to right). Then the fourth is | |
* added to complete the compass directions. From here, it's just a matter of | |
* subdividing the rest of the angles for the last 4 items. | |
* | |
* --Matt 07/2006 | |
* \{ */ | |
const char ui_radial_dir_order[8] = { | |
UI_RADIAL_W, | |
UI_RADIAL_E, | |
UI_RADIAL_S, | |
UI_RADIAL_N, | |
UI_RADIAL_NW, | |
UI_RADIAL_NE, | |
UI_RADIAL_SW, | |
UI_RADIAL_SE, | |
}; | |
const char ui_radial_dir_to_numpad[8] = {8, 9, 6, 3, 2, 1, 4, 7}; | |
const short ui_radial_dir_to_angle[8] = {90, 45, 0, 315, 270, 225, 180, 135}; | |
static void ui_but_pie_direction_string(uiBut *but, char *buf, int size) | |
{ | |
BLI_assert(but->pie_dir < ARRAY_SIZE(ui_radial_dir_to_numpad)); | |
BLI_snprintf(buf, size, "%d", ui_radial_dir_to_numpad[but->pie_dir]); | |
} | |
/** \} */ | |
static void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block) | |
{ | |
char buf[128]; | |
BLI_assert(block->flag & (UI_BLOCK_LOOP | UI_BLOCK_SHOW_SHORTCUT_ALWAYS)); | |
/* only do it before bounding */ | |
if (block->rect.xmin != block->rect.xmax) { | |
return; | |
} | |
if (STREQ(block->name, "splash")) { | |
return; | |
} | |
if (block->flag & UI_BLOCK_RADIAL) { | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
if (but->pie_dir != UI_RADIAL_NONE) { | |
ui_but_pie_direction_string(but, buf, sizeof(buf)); | |
ui_but_add_shortcut(but, buf, false); | |
} | |
} | |
} | |
else { | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
if (block->flag & UI_BLOCK_SHOW_SHORTCUT_ALWAYS) { | |
/* Skip icon-only buttons (as used in the toolbar). */ | |
if (but->drawstr[0] == '\0') { | |
continue; | |
} | |
if (((block->flag & UI_BLOCK_POPOVER) == 0) && UI_but_is_tool(but)) { | |
/* For non-popovers, shown in shortcut only | |
* (has special shortcut handling code). */ | |
continue; | |
} | |
} | |
else if (but->emboss != UI_EMBOSS_PULLDOWN) { | |
continue; | |
} | |
if (ui_but_event_operator_string(C, but, buf, sizeof(buf))) { | |
ui_but_add_shortcut(but, buf, false); | |
} | |
else if (ui_but_event_property_operator_string(C, but, buf, sizeof(buf))) { | |
ui_but_add_shortcut(but, buf, false); | |
} | |
} | |
} | |
} | |
void ui_but_override_flag(Main *bmain, uiBut *but) | |
{ | |
const uint override_status = RNA_property_override_library_status( | |
bmain, &but->rnapoin, but->rnaprop, but->rnaindex); | |
if (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN) { | |
but->flag |= UI_BUT_OVERRIDDEN; | |
} | |
else { | |
but->flag &= ~UI_BUT_OVERRIDDEN; | |
} | |
} | |
/* -------------------------------------------------------------------- */ | |
/** \name Button Extra Operator Icons | |
* | |
* Extra icons are shown on the right hand side of buttons. They can be clicked to invoke custom | |
* operators. | |
* There are some predefined here, which get added to buttons automatically based on button data | |
* (type, flags, state, etc). | |
* \{ */ | |
/** | |
* Predefined types for generic extra operator icons (uiButExtraOpIcon). | |
*/ | |
enum PredefinedExtraOpIconType { | |
PREDEFINED_EXTRA_OP_ICON_NONE = 1, | |
PREDEFINED_EXTRA_OP_ICON_CLEAR, | |
PREDEFINED_EXTRA_OP_ICON_EYEDROPPER, | |
}; | |
static PointerRNA *ui_but_extra_operator_icon_add_ptr(uiBut *but, | |
wmOperatorType *optype, | |
wmOperatorCallContext opcontext, | |
int icon) | |
{ | |
uiButExtraOpIcon *extra_op_icon = MEM_new<uiButExtraOpIcon>(__func__); | |
extra_op_icon->icon = icon; | |
extra_op_icon->optype_params = MEM_cnew<wmOperatorCallParams>(__func__); | |
extra_op_icon->optype_params->optype = optype; | |
extra_op_icon->optype_params->opptr = MEM_cnew<PointerRNA>(__func__); | |
WM_operator_properties_create_ptr(extra_op_icon->optype_params->opptr, | |
extra_op_icon->optype_params->optype); | |
extra_op_icon->optype_params->opcontext = opcontext; | |
extra_op_icon->highlighted = false; | |
extra_op_icon->disabled = false; | |
BLI_addtail(&but->extra_op_icons, extra_op_icon); | |
return extra_op_icon->optype_params->opptr; | |
} | |
static void ui_but_extra_operator_icon_free(uiButExtraOpIcon *extra_icon) | |
{ | |
WM_operator_properties_free(extra_icon->optype_params->opptr); | |
MEM_freeN(extra_icon->optype_params->opptr); | |
MEM_freeN(extra_icon->optype_params); | |
MEM_freeN(extra_icon); | |
} | |
void ui_but_extra_operator_icons_free(uiBut *but) | |
{ | |
LISTBASE_FOREACH_MUTABLE (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { | |
ui_but_extra_operator_icon_free(op_icon); | |
} | |
BLI_listbase_clear(&but->extra_op_icons); | |
} | |
PointerRNA *UI_but_extra_operator_icon_add(uiBut *but, | |
const char *opname, | |
wmOperatorCallContext opcontext, | |
int icon) | |
{ | |
wmOperatorType *optype = WM_operatortype_find(opname, false); | |
if (optype) { | |
return ui_but_extra_operator_icon_add_ptr(but, optype, opcontext, icon); | |
} | |
return nullptr; | |
} | |
wmOperatorType *UI_but_extra_operator_icon_optype_get(uiButExtraOpIcon *extra_icon) | |
{ | |
return extra_icon ? extra_icon->optype_params->optype : nullptr; | |
} | |
PointerRNA *UI_but_extra_operator_icon_opptr_get(uiButExtraOpIcon *extra_icon) | |
{ | |
return extra_icon->optype_params->opptr; | |
} | |
static bool ui_but_icon_extra_is_visible_text_clear(const uiBut *but) | |
{ | |
BLI_assert(but->type == UI_BTYPE_TEXT); | |
return ((but->flag & UI_BUT_VALUE_CLEAR) && but->drawstr[0]); | |
} | |
static bool ui_but_icon_extra_is_visible_search_unlink(const uiBut *but) | |
{ | |
BLI_assert(ELEM(but->type, UI_BTYPE_SEARCH_MENU)); | |
return ((but->editstr == nullptr) && (but->drawstr[0] != '\0') && | |
(but->flag & UI_BUT_VALUE_CLEAR)); | |
} | |
static bool ui_but_icon_extra_is_visible_search_eyedropper(uiBut *but) | |
{ | |
BLI_assert(but->type == UI_BTYPE_SEARCH_MENU && (but->flag & UI_BUT_VALUE_CLEAR)); | |
if (but->rnaprop == nullptr) { | |
return false; | |
} | |
StructRNA *type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); | |
const short idcode = RNA_type_to_ID_code(type); | |
return ((but->editstr == nullptr) && (idcode == ID_OB || OB_DATA_SUPPORT_ID(idcode))); | |
} | |
static PredefinedExtraOpIconType ui_but_icon_extra_get(uiBut *but) | |
{ | |
switch (but->type) { | |
case UI_BTYPE_TEXT: | |
if (ui_but_icon_extra_is_visible_text_clear(but)) { | |
return PREDEFINED_EXTRA_OP_ICON_CLEAR; | |
} | |
break; | |
case UI_BTYPE_SEARCH_MENU: | |
if ((but->flag & UI_BUT_VALUE_CLEAR) == 0) { | |
/* pass */ | |
} | |
else if (ui_but_icon_extra_is_visible_search_unlink(but)) { | |
return PREDEFINED_EXTRA_OP_ICON_CLEAR; | |
} | |
else if (ui_but_icon_extra_is_visible_search_eyedropper(but)) { | |
return PREDEFINED_EXTRA_OP_ICON_EYEDROPPER; | |
} | |
break; | |
default: | |
break; | |
} | |
return PREDEFINED_EXTRA_OP_ICON_NONE; | |
} | |
/** | |
* While some extra operator icons have to be set explicitly upon button creating, this code adds | |
* some generic ones based on button data. Currently these are mutually exclusive, so there's only | |
* ever one predefined extra icon. | |
*/ | |
static void ui_but_predefined_extra_operator_icons_add(uiBut *but) | |
{ | |
const PredefinedExtraOpIconType extra_icon = ui_but_icon_extra_get(but); | |
wmOperatorType *optype = nullptr; | |
BIFIconID icon = ICON_NONE; | |
switch (extra_icon) { | |
case PREDEFINED_EXTRA_OP_ICON_EYEDROPPER: { | |
static wmOperatorType *id_eyedropper_ot = nullptr; | |
if (!id_eyedropper_ot) { | |
id_eyedropper_ot = WM_operatortype_find("UI_OT_eyedropper_id", false); | |
} | |
BLI_assert(id_eyedropper_ot); | |
optype = id_eyedropper_ot; | |
icon = ICON_EYEDROPPER; | |
break; | |
} | |
case PREDEFINED_EXTRA_OP_ICON_CLEAR: { | |
static wmOperatorType *clear_ot = nullptr; | |
if (!clear_ot) { | |
clear_ot = WM_operatortype_find("UI_OT_button_string_clear", false); | |
} | |
BLI_assert(clear_ot); | |
optype = clear_ot; | |
icon = ICON_PANEL_CLOSE; | |
break; | |
} | |
default: | |
break; | |
} | |
if (optype) { | |
LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { | |
if ((op_icon->optype_params->optype == optype) && (op_icon->icon == icon)) { | |
/* Don't add the same operator icon twice (happens if button is kept alive while active). | |
*/ | |
return; | |
} | |
} | |
ui_but_extra_operator_icon_add_ptr(but, optype, WM_OP_INVOKE_DEFAULT, icon); | |
} | |
} | |
/** \} */ | |
void UI_block_update_from_old(const bContext *C, uiBlock *block) | |
{ | |
if (!block->oldblock) { | |
return; | |
} | |
uiBut *but_old = static_cast<uiBut *>(block->oldblock->buttons.first); | |
if (BLI_listbase_is_empty(&block->oldblock->butstore) == false) { | |
UI_butstore_update(block); | |
} | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
if (ui_but_update_from_old_block(C, block, &but, &but_old)) { | |
ui_but_update(but); | |
/* redraw dynamic tooltip if we have one open */ | |
if (but->tip_func) { | |
UI_but_tooltip_refresh((bContext *)C, but); | |
} | |
} | |
} | |
block->auto_open = block->oldblock->auto_open; | |
block->auto_open_last = block->oldblock->auto_open_last; | |
block->tooltipdisabled = block->oldblock->tooltipdisabled; | |
BLI_movelisttolist(&block->color_pickers.list, &block->oldblock->color_pickers.list); | |
block->oldblock = nullptr; | |
} | |
#ifndef NDEBUG | |
/** | |
* Extra sanity checks for invariants (debug builds only). | |
*/ | |
static void ui_but_validate(const uiBut *but) | |
{ | |
/* Number buttons must have a click-step, | |
* assert instead of correcting the value to ensure the caller knows what they're doing. */ | |
if (but->type == UI_BTYPE_NUM) { | |
uiButNumber *number_but = (uiButNumber *)but; | |
if (ELEM(but->pointype, UI_BUT_POIN_CHAR, UI_BUT_POIN_SHORT, UI_BUT_POIN_INT)) { | |
BLI_assert(int(number_but->step_size) > 0); | |
} | |
} | |
} | |
#endif | |
bool ui_but_context_poll_operator_ex(bContext *C, | |
const uiBut *but, | |
const wmOperatorCallParams *optype_params) | |
{ | |
bool result; | |
int old_but_flag = 0; | |
if (but) { | |
old_but_flag = but->flag; | |
/* Temporarily make this button override the active one, in case the poll acts on the active | |
* button. */ | |
const_cast<uiBut *>(but)->flag |= UI_BUT_ACTIVE_OVERRIDE; | |
if (but->context) { | |
CTX_store_set(C, but->context); | |
} | |
} | |
result = WM_operator_poll_context(C, optype_params->optype, optype_params->opcontext); | |
if (but) { | |
BLI_assert_msg((but->flag & ~UI_BUT_ACTIVE_OVERRIDE) == | |
(old_but_flag & ~UI_BUT_ACTIVE_OVERRIDE), | |
"Operator polls shouldn't change button flags"); | |
const_cast<uiBut *>(but)->flag = old_but_flag; | |
if (but->context) { | |
CTX_store_set(C, nullptr); | |
} | |
} | |
return result; | |
} | |
bool ui_but_context_poll_operator(bContext *C, wmOperatorType *ot, const uiBut *but) | |
{ | |
const wmOperatorCallContext opcontext = but ? but->opcontext : WM_OP_INVOKE_DEFAULT; | |
wmOperatorCallParams params = {}; | |
params.optype = ot; | |
params.opcontext = opcontext; | |
return ui_but_context_poll_operator_ex(C, but, ¶ms); | |
} | |
void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_xy[2]) | |
{ | |
wmWindow *window = CTX_wm_window(C); | |
Scene *scene = CTX_data_scene(C); | |
ARegion *region = CTX_wm_region(C); | |
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); | |
BLI_assert(block->active); | |
/* Extend button data. This needs to be done before the block updating. */ | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
ui_but_predefined_extra_operator_icons_add(but); | |
} | |
UI_block_update_from_old(C, block); | |
/* inherit flags from 'old' buttons that was drawn here previous, based | |
* on matching buttons, we need this to make button event handling non | |
* blocking, while still allowing buttons to be remade each redraw as it | |
* is expected by blender code */ | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
/* temp? Proper check for graying out */ | |
if (but->optype) { | |
wmOperatorType *ot = but->optype; | |
if (ot == nullptr || !ui_but_context_poll_operator((bContext *)C, ot, but)) { | |
but->flag |= UI_BUT_DISABLED; | |
} | |
} | |
LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { | |
if (!ui_but_context_poll_operator_ex((bContext *)C, but, op_icon->optype_params)) { | |
op_icon->disabled = true; | |
} | |
} | |
const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( | |
depsgraph, (scene) ? scene->r.cfra : 0.0f); | |
ui_but_anim_flag(but, &anim_eval_context); | |
ui_but_override_flag(CTX_data_main(C), but); | |
if (UI_but_is_decorator(but)) { | |
ui_but_anim_decorate_update_from_flag((uiButDecorator *)but); | |
} | |
#ifndef NDEBUG | |
ui_but_validate(but); | |
#endif | |
} | |
/* handle pending stuff */ | |
if (block->layouts.first) { | |
UI_block_layout_resolve(block, nullptr, nullptr); | |
} | |
ui_block_align_calc(block, CTX_wm_region(C)); | |
if ((block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_NUMSELECT) && | |
(block->flag & UI_BLOCK_NO_ACCELERATOR_KEYS) == 0) | |
{ | |
ui_menu_block_set_keyaccels(block); /* could use a different flag to check */ | |
} | |
if (block->flag & (UI_BLOCK_LOOP | UI_BLOCK_SHOW_SHORTCUT_ALWAYS)) { | |
ui_menu_block_set_keymaps(C, block); | |
} | |
/* after keymaps! */ | |
switch (block->bounds_type) { | |
case UI_BLOCK_BOUNDS_NONE: | |
break; | |
case UI_BLOCK_BOUNDS: | |
ui_block_bounds_calc(block); | |
break; | |
case UI_BLOCK_BOUNDS_TEXT: | |
ui_block_bounds_calc_text(block, 0.0f); | |
break; | |
case UI_BLOCK_BOUNDS_POPUP_CENTER: | |
ui_block_bounds_calc_centered(window, block); | |
break; | |
case UI_BLOCK_BOUNDS_PIE_CENTER: | |
ui_block_bounds_calc_centered_pie(block); | |
break; | |
/* fallback */ | |
case UI_BLOCK_BOUNDS_POPUP_MOUSE: | |
case UI_BLOCK_BOUNDS_POPUP_MENU: | |
ui_block_bounds_calc_popup(window, block, block->bounds_type, xy, r_xy); | |
break; | |
} | |
/* Update bounds of all views in this block. If this block is a panel, this will be done later in | |
* #UI_panels_end(), because buttons are offset there. */ | |
if (!block->panel) { | |
ui_block_views_bounds_calc(block); | |
} | |
if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) { | |
UI_block_bounds_set_normal(block, 0); | |
} | |
if (block->flag & UI_BUT_ALIGN) { | |
UI_block_align_end(block); | |
} | |
ui_update_flexible_spacing(region, block); | |
block->endblock = true; | |
} | |
void UI_block_end(const bContext *C, uiBlock *block) | |
{ | |
wmWindow *window = CTX_wm_window(C); | |
UI_block_end_ex(C, block, window->eventstate->xy, nullptr); | |
} | |
/* ************** BLOCK DRAWING FUNCTION ************* */ | |
void ui_fontscale(float *points, float aspect) | |
{ | |
*points /= aspect; | |
} | |
void ui_but_to_pixelrect(rcti *rect, const ARegion *region, const uiBlock *block, const uiBut *but) | |
{ | |
rctf rectf; | |
ui_block_to_window_rctf(region, block, &rectf, (but) ? &but->rect : &block->rect); | |
BLI_rcti_rctf_copy_round(rect, &rectf); | |
BLI_rcti_translate(rect, -region->winrct.xmin, -region->winrct.ymin); | |
} | |
static bool ui_but_pixelrect_in_view(const ARegion *region, const rcti *rect) | |
{ | |
rcti rect_winspace = *rect; | |
BLI_rcti_translate(&rect_winspace, region->winrct.xmin, region->winrct.ymin); | |
return BLI_rcti_isect(®ion->winrct, &rect_winspace, nullptr); | |
} | |
void UI_block_draw(const bContext *C, uiBlock *block) | |
{ | |
uiStyle style = *UI_style_get_dpi(); /* XXX pass on as arg */ | |
/* get menu region or area region */ | |
ARegion *region = CTX_wm_menu(C); | |
if (!region) { | |
region = CTX_wm_region(C); | |
} | |
if (!block->endblock) { | |
UI_block_end(C, block); | |
} | |
/* we set this only once */ | |
GPU_blend(GPU_BLEND_ALPHA); | |
/* scale fonts */ | |
ui_fontscale(&style.paneltitle.points, block->aspect); | |
ui_fontscale(&style.grouplabel.points, block->aspect); | |
ui_fontscale(&style.widgetlabel.points, block->aspect); | |
ui_fontscale(&style.widget.points, block->aspect); | |
/* scale block min/max to rect */ | |
rcti rect; | |
ui_but_to_pixelrect(&rect, region, block, nullptr); | |
/* pixel space for AA widgets */ | |
GPU_matrix_push_projection(); | |
GPU_matrix_push(); | |
GPU_matrix_identity_set(); | |
wmOrtho2_region_pixelspace(region); | |
/* back */ | |
if (block->flag & UI_BLOCK_RADIAL) { | |
ui_draw_pie_center(block); | |
} | |
else if (block->flag & UI_BLOCK_POPOVER) { | |
ui_draw_popover_back(region, &style, block, &rect); | |
} | |
else if (block->flag & UI_BLOCK_LOOP) { | |
ui_draw_menu_back(&style, block, &rect); | |
} | |
else if (block->panel) { | |
ui_draw_aligned_panel(&style, | |
block, | |
&rect, | |
UI_panel_category_is_visible(region), | |
UI_panel_should_show_background(region, block->panel->type), | |
region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE); | |
} | |
BLF_batch_draw_begin(); | |
UI_icon_draw_cache_begin(); | |
UI_widgetbase_draw_cache_begin(); | |
/* widgets */ | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
if (but->flag & (UI_HIDDEN | UI_SCROLLED)) { | |
continue; | |
} | |
ui_but_to_pixelrect(&rect, region, block, but); | |
/* Optimization: Don't draw buttons that are not visible (outside view bounds). */ | |
if (!ui_but_pixelrect_in_view(region, &rect)) { | |
continue; | |
} | |
/* XXX: figure out why invalid coordinates happen when closing render window */ | |
/* and material preview is redrawn in main window (temp fix for bug #23848) */ | |
if (rect.xmin < rect.xmax && rect.ymin < rect.ymax) { | |
ui_draw_but(C, region, &style, but, &rect); | |
} | |
} | |
UI_widgetbase_draw_cache_end(); | |
UI_icon_draw_cache_end(); | |
BLF_batch_draw_end(); | |
ui_block_views_draw_overlays(region, block); | |
/* restore matrix */ | |
GPU_matrix_pop_projection(); | |
GPU_matrix_pop(); | |
} | |
static void ui_block_message_subscribe(ARegion *region, wmMsgBus *mbus, uiBlock *block) | |
{ | |
uiBut *but_prev = nullptr; | |
/* possibly we should keep the region this block is contained in? */ | |
LISTBASE_FOREACH (uiBut *, but, &block->buttons) { | |
if (but->rnapoin.type && but->rnaprop) { | |
/* quick check to avoid adding buttons representing a vector, multiple times. */ | |
if ((but_prev && (but_prev->rnaprop == but->rnaprop) && | |
(but_prev->rnapoin.type == but->rnapoin.type) && | |
(but_prev->rnapoin.data == but->rnapoin.data) && | |
(but_prev->rnapoin.owner_id == but->rnapoin.owner_id)) == false) | |
{ | |
/* TODO: could make this into utility function. */ | |
wmMsgSubscribeValue value = {}; | |
value.owner = region; | |
value.user_data = region; | |
value.notify = ED_region_do_msg_notify_tag_redraw; | |
WM_msg_subscribe_rna(mbus, &but->rnapoin, but->rnaprop, &value, __func__); | |
but_prev = but; | |
} | |
} | |
} | |
} | |
void UI_region_message_subscribe(ARegion *region, wmMsgBus *mbus) | |
{ | |
LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { | |
ui_block_message_subscribe(region, mbus, block); | |
} | |
} | |
/* ************* EVENTS ************* */ | |
int ui_but_is_pushed_ex(uiBut *but, double *value) | |
{ | |
int is_push = 0; | |
if (but->pushed_state_func) { | |
return but->pushed_state_func(*but); | |
} | |
if (but->bit) { | |
const bool state = !ELEM( | |
but->type, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_CHECKBOX_N); | |
int lvalue; | |
UI_GET_BUT_VALUE_INIT(but, *value); | |
lvalue = int(*value); | |
if (UI_BITBUT_TEST(lvalue, (but->bitnr))) { | |
is_push = state; | |
} | |
else { | |
is_push = !state; | |
} | |
} | |
else { | |
switch (but->type) { | |
case UI_BTYPE_BUT: | |
case UI_BTYPE_HOTKEY_EVENT: | |
case UI_BTYPE_KEY_EVENT: | |
case UI_BTYPE_COLOR: | |
case UI_BTYPE_DECORATOR: | |
is_push = -1; | |
break; | |
case UI_BTYPE_BUT_TOGGLE: | |
case UI_BTYPE_TOGGLE: | |
case UI_BTYPE_ICON_TOGGLE: | |
case UI_BTYPE_CHECKBOX: | |
UI_GET_BUT_VALUE_INIT(but, *value); | |
if (*value != double(but->hardmin)) { | |
is_push = true; | |
} | |
break; | |
case UI_BTYPE_ICON_TOGGLE_N: | |
case UI_BTYPE_TOGGLE_N: | |
case UI_BTYPE_CHECKBOX_N: | |
UI_GET_BUT_VALUE_INIT(but, *value); | |
if (*value == 0.0) { | |
is_push = true; | |
} | |
break; | |
case UI_BTYPE_ROW: | |
case UI_BTYPE_LISTROW: | |
case UI_BTYPE_TAB: | |
if ((but->type == UI_BTYPE_TAB) && but->rnaprop && but->custom_data) { | |
/* uiBut.custom_data points to data this tab represents (e.g. workspace). | |
* uiBut.rnapoin/prop store an active value (e.g. active workspace). */ | |
if (RNA_property_type(but->rnaprop) == PROP_POINTER) { | |
const PointerRNA active_ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); | |
if (active_ptr.data == but->custom_data) { | |
is_push = true; | |
} | |
} | |
break; | |
} | |
else if (but->optype) { | |
break; | |
} | |
UI_GET_BUT_VALUE_INIT(but, *value); | |
/* support for rna enum buts */ | |
if (but->rnaprop && (RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG)) { | |
if (int(*value) & int(but->hardmax)) { | |
is_push = true; | |
} | |
} | |
else { | |
if (*value == double(but->hardmax)) { | |
is_push = true; | |
} | |
} | |
break; | |
case UI_BTYPE_VIEW_ITEM: { | |
const uiButViewItem *view_item_but = (const uiButViewItem *)but; | |
is_push = -1; | |
if (view_item_but->view_item) { | |
is_push = UI_view_item_is_active(view_item_but->view_item); | |
} | |
break; | |
} | |
default: | |
is_push = -1; | |
break; | |
} | |
} | |
if ((but->drawflag & UI_BUT_CHECKBOX_INVERT) && (is_push != -1)) { | |
is_push = !bool(is_push); | |
} | |
return is_push; | |
} | |
int ui_but_is_pushed(uiBut *but) | |
{ | |
double value = UI_BUT_VALUE_UNSET; | |
return ui_but_is_pushed_ex(but, &value); | |
} | |
static void ui_but_update_select_flag(uiBut *but, double *value) | |
{ | |
switch (ui_but_is_pushed_ex(but, value)) { | |
case true: | |
but->flag |= UI_SELECT; | |
break; | |
case false: | |
but->flag &= ~UI_SELECT; | |
break; | |
} | |
} | |
/* ************************************************ */ | |
void UI_block_lock_set(uiBlock *block, bool val, const char *lockstr) | |
{ | |
if (val) { | |
block->lock = val; | |
block->lockstr = lockstr; | |
} | |
} | |
void UI_block_lock_clear(uiBlock *block) | |
{ | |
block->lock = false; | |
block->lockstr = nullptr; | |
} | |
/* *********************** data get/set *********************** | |
* this either works with the pointed to data, or can work with | |
* an edit override pointer while dragging for example */ | |
void ui_but_v3_get(uiBut *but, float vec[3]) | |
{ | |
if (but->editvec) { | |
copy_v3_v3(vec, but->editvec); | |
} | |
if (but->rnaprop) { | |
PropertyRNA *prop = but->rnaprop; | |
zero_v3(vec); | |
if (RNA_property_type(prop) == PROP_FLOAT) { | |
int tot = RNA_property_array_length(&but->rnapoin, prop); | |
BLI_assert(tot > 0); | |
if (tot == 3) { | |
RNA_property_float_get_array(&but->rnapoin, prop, vec); | |
} | |
else { | |
tot = min_ii(tot, 3); | |
for (int a = 0; a < tot; a++) { | |
vec[a] = RNA_property_float_get_index(&but->rnapoin, prop, a); | |
} | |
} | |
} | |
} | |
else if (but->pointype == UI_BUT_POIN_CHAR) { | |
const char *cp = (char *)but->poin; | |
vec[0] = float(cp[0]) / 255.0f; | |
vec[1] = float(cp[1]) / 255.0f; | |
vec[2] = float(cp[2]) / 255.0f; | |
} | |
else if (but->pointype == UI_BUT_POIN_FLOAT) { | |
const float *fp = (float *)but->poin; | |
copy_v3_v3(vec, fp); | |
} | |
else { | |
if (but->editvec == nullptr) { | |
fprintf(stderr, "%s: can't get color, should never happen\n", __func__); | |
zero_v3(vec); | |
} | |
} | |
if (but->type == UI_BTYPE_UNITVEC) { | |
normalize_v3(vec); | |
} | |
} | |
void ui_but_v3_set(uiBut *but, const float vec[3]) | |
{ | |
if (but->editvec) { | |
copy_v3_v3(but->editvec, vec); | |
} | |
if (but->rnaprop) { | |
PropertyRNA *prop = but->rnaprop; | |
if (RNA_property_type(prop) == PROP_FLOAT) { | |
int tot; | |
int a; | |
tot = RNA_property_array_length(&but->rnapoin, prop); | |
BLI_assert(tot > 0); | |
if (tot == 3) { | |
RNA_property_float_set_array(&but->rnapoin, prop, vec); | |
} | |
else { | |
tot = min_ii(tot, 3); | |
for (a = 0; a < tot; a++) { | |
RNA_property_float_set_index(&but->rnapoin, prop, a, vec[a]); | |
} | |
} | |
} | |
} | |
else if (but->pointype == UI_BUT_POIN_CHAR) { | |
char *cp = (char *)but->poin; | |
cp[0] = char(lround(vec[0] * 255.0f)); | |
cp[1] = char(lround(vec[1] * 255.0f)); | |
cp[2] = char(lround(vec[2] * 255.0f)); | |
} | |
else if (but->pointype == UI_BUT_POIN_FLOAT) { | |
float *fp = (float *)but->poin; | |
copy_v3_v3(fp, vec); | |
} | |
} | |
bool ui_but_is_float(const uiBut *but) | |
{ | |
if (but->pointype == UI_BUT_POIN_FLOAT && but->poin) { | |
return true; | |
} | |
if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_FLOAT) { | |
return true; | |
} | |
return false; | |
} | |
PropertyScaleType ui_but_scale_type(const uiBut *but) | |
{ | |
if (but->rnaprop) { | |
return RNA_property_ui_scale(but->rnaprop); | |
} | |
return PROP_SCALE_LINEAR; | |
} | |
bool ui_but_is_bool(const uiBut *but) | |
{ | |
if (ELEM(but->type, | |
UI_BTYPE_TOGGLE, | |
UI_BTYPE_TOGGLE_N, | |
UI_BTYPE_ICON_TOGGLE, | |
UI_BTYPE_ICON_TOGGLE_N, | |
UI_BTYPE_TAB)) | |
{ | |
return true; | |
} | |
if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_BOOLEAN) { | |
return true; | |
} | |
if ((but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) && | |
(but->type == UI_BTYPE_ROW)) { | |
return true; | |
} | |
return false; | |
} | |
bool ui_but_is_unit(const uiBut *but) | |
{ | |
UnitSettings *unit = but->block->unit; | |
const int unit_type = UI_but_unit_type_get(but); | |
if (unit_type == PROP_UNIT_NONE) { | |
return false; | |
} | |
#if 1 /* removed so angle buttons get correct snapping */ | |
if (ui_but_is_unit_radians_ex(unit, unit_type)) { | |
return false; | |
} | |
#endif | |
/* for now disable time unit conversion */ | |
if (unit_type == PROP_UNIT_TIME) { | |
return false; | |
} | |
if (unit->system == USER_UNIT_NONE) { | |
if (unit_type != PROP_UNIT_ROTATION) { | |
return false; | |
} | |
} | |
return true; | |
} | |
bool ui_but_is_compatible(const uiBut *but_a, const uiBut *but_b) | |
{ | |
if (but_a->type != but_b->type) { | |
return false; | |
} | |
if (but_a->pointype != but_b->pointype) { | |
return false; | |
} | |
if (but_a->rnaprop) { | |
/* skip 'rnapoin.data', 'rnapoin.owner_id' | |
* allow different data to have the same props edited at once */ | |
if (but_a->rnapoin.type != but_b->rnapoin.type) { | |
return false; | |
} | |
if (RNA_property_type(but_a->rnaprop) != RNA_property_type(but_b->rnaprop)) { | |
return false; | |
} | |
if (RNA_property_subtype(but_a->rnaprop) != RNA_property_subtype(but_b->rnaprop)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
bool ui_but_is_rna_valid(uiBut *but) | |
{ | |
if (but->rnaprop == nullptr || RNA_struct_contains_property(&but->rnapoin, but->rnaprop)) { | |
return true; | |
} | |
printf("property removed %s: %p\n", but->drawstr, but->rnaprop); | |
return false; | |
} | |
bool ui_but_supports_cycling(const uiBut *but) | |
{ | |
return (ELEM(but->type, UI_BTYPE_ROW, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_LISTBOX) || | |
(but->type == UI_BTYPE_MENU && ui_but_menu_step_poll(but)) || | |
(but->type == UI_BTYPE_COLOR && ((uiButColor *)but)->is_pallete_color) || | |
(but->menu_step_func != nullptr)); | |
} | |
double ui_but_value_get(uiBut *but) | |
{ | |
double value = 0.0; | |
if (but->editval) { | |
return *(but->editval); | |
} | |
if (but->poin == nullptr && but->rnapoin.data == nullptr) { | |
return 0.0; | |
} | |
if (but->rnaprop) { | |
PropertyRNA *prop = but->rnaprop; | |
BLI_assert(but->rnaindex != -1); | |
switch (RNA_property_type(prop)) { | |
case PROP_BOOLEAN: | |
if (RNA_property_array_check(prop)) { | |
value = RNA_property_boolean_get_index(&but->rnapoin, prop, but->rnaindex); | |
} | |
else { | |
value = RNA_property_boolean_get(&but->rnapoin, prop); | |
} | |
break; | |
case PROP_INT: | |
if (RNA_property_array_check(prop)) { | |
value = RNA_property_int_get_index(&but->rnapoin, prop, but->rnaindex); | |
} | |
else { | |
value = RNA_property_int_get(&but->rnapoin, prop); | |
} | |
break; | |
case PROP_FLOAT: | |
if (RNA_property_array_check(prop)) { | |
value = RNA_property_float_get_index(&but->rnapoin, prop, but->rnaindex); | |
} | |
else { | |
value = RNA_property_float_get(&but->rnapoin, prop); | |
} | |
break; | |
case PROP_ENUM: | |
value = RNA_property_enum_get(&but->rnapoin, prop); | |
break; | |
default: | |
value = 0.0; | |
break; | |
} | |
} | |
else if (but->pointype == UI_BUT_POIN_CHAR) { | |
value = *(char *)but->poin; | |
} | |
else if (but->pointype == UI_BUT_POIN_SHORT) { | |
value = *(short *)but->poin; | |
} | |
else if (but->pointype == UI_BUT_POIN_INT) { | |
value = *(int *)but->poin; | |
} | |
else if (but->pointype == UI_BUT_POIN_FLOAT) { | |
value = *(float *)but->poin; | |
} | |
return value; | |
} | |
void ui_but_value_set(uiBut *but, double value) | |
{ | |
/* Value is a HSV value: convert to RGB. */ | |
if (but->rnaprop) { | |
PropertyRNA *prop = but->rnaprop; | |
if (RNA_property_editable(&but->rnapoin, prop)) { | |
switch (RNA_property_type(prop)) { | |
case PROP_BOOLEAN: | |
if (RNA_property_array_check(prop)) { | |
RNA_property_boolean_set_index(&but->rnapoin, prop, but->rnaindex, value); | |
} | |
else { | |
RNA_property_boolean_set(&but->rnapoin, prop, value); | |
} | |
break; | |
case PROP_INT: | |
if (RNA_property_array_check(prop)) { | |
RNA_property_int_set_index(&but->rnapoin, prop, but->rnaindex, int(value)); | |
} | |
else { | |
RNA_property_int_set(&but->rnapoin, prop, int(value)); | |
} | |
break; | |
case PROP_FLOAT: | |
if (RNA_property_array_check(prop)) { | |
RNA_property_float_set_index(&but->rnapoin, prop, but->rnaindex, value); | |
} | |
else { | |
RNA_property_float_set(&but->rnapoin, prop, value); | |
} | |
break; | |
case PROP_ENUM: | |
if (RNA_property_flag(prop) & PROP_ENUM_FLAG) { | |
int ivalue = int(value); | |
/* toggle for enum/flag buttons */ | |
ivalue ^= RNA_property_enum_get(&but->rnapoin, prop); | |
RNA_property_enum_set(&but->rnapoin, prop, ivalue); | |
} | |
else { | |
RNA_property_enum_set(&but->rnapoin, prop, value); | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
/* we can't be sure what RNA set functions actually do, | |
* so leave this unset */ | |
value = UI_BUT_VALUE_UNSET; | |
} | |
else if (but->pointype == 0) { | |
/* pass */ | |
} | |
else { | |
/* first do rounding */ | |
if (but->pointype == UI_BUT_POIN_CHAR) { | |
value = round_db_to_uchar_clamp(value); | |
} | |
else if (but->pointype == UI_BUT_POIN_SHORT) { | |
value = round_db_to_short_clamp(value); | |
} | |
else if (but->pointype == UI_BUT_POIN_INT) { | |
value = round_db_to_int_clamp(value); | |
} | |
else if (but->pointype == UI_BUT_POIN_FLOAT) { | |
float fval = float(value); | |
if (fval >= -0.00001f && fval <= 0.00001f) { | |
/* prevent negative zero */ | |
fval = 0.0f; | |
} | |
value = fval; | |
} | |
/* then set value with possible edit override */ | |
if (but->editval) { | |
value = *but->editval = value; | |
} | |
else if (but->pointype == UI_BUT_POIN_CHAR) { | |
value = *((char *)but->poin) = char(value); | |
} | |
else if (but->pointype == UI_BUT_POIN_SHORT) { | |
value = *((short *)but->poin) = short(value); | |
} | |
else if (but->pointype == UI_BUT_POIN_INT) { | |
value = *((int *)but->poin) = int(value); | |
} | |
else if (but->pointype == UI_BUT_POIN_FLOAT) { | |
value = *((float *)but->poin) = float(value); | |
} | |
} | |
ui_but_update_select_flag(but, &value); | |
} | |
int ui_but_string_get_maxncpy(uiBut *but) | |
{ | |
if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { | |
return but->hardmax; | |
} | |
return UI_MAX_DRAW_STR; | |
} | |
uiBut *ui_but_drag_multi_edit_get(uiBut *but) | |
{ | |
uiBut *return_but = nullptr; | |
BLI_assert(but->flag & UI_BUT_DRAG_MULTI); | |
LISTBASE_FOREACH (uiBut *, but_iter, &but->block->buttons) { | |
if (but_iter->editstr) { | |
return_but = but_iter; | |
break; | |
} | |
} | |
return return_but; | |
} | |
static double ui_get_but_scale_unit(uiBut *but, double value) | |
{ | |
UnitSettings *unit = but->block->unit; | |
const int unit_type = UI_but_unit_type_get(but); | |
/* Time unit is a bit special, not handled by BKE_scene_unit_scale() for now. */ | |
if (unit_type == PROP_UNIT_TIME) { /* WARNING: using evil_C :| */ | |
Scene *scene = CTX_data_scene(static_cast<const bContext *>(but->block->evil_C)); | |
return FRA2TIME(value); | |
} | |
return BKE_scene_unit_scale(unit, RNA_SUBTYPE_UNIT_VALUE(unit_type), value); | |
} | |
void ui_but_convert_to_unit_alt_name(uiBut *but, char *str, size_t str_maxncpy) | |
{ | |
if (!ui_but_is_unit(but)) { | |
return; | |
} | |
UnitSettings *unit = but->block->unit; | |
const int unit_type = UI_but_unit_type_get(but); | |
char *orig_str; | |
orig_str = BLI_strdup(str); | |
BKE_unit_name_to_alt( | |
str, str_maxncpy, orig_str, unit->system, RNA_SUBTYPE_UNIT_VALUE(unit_type)); | |
MEM_freeN(orig_str); | |
} | |
/** | |
* \param float_precision: Override the button precision. | |
*/ | |
static void ui_get_but_string_unit( | |
uiBut *but, char *str, int str_maxncpy, double value, bool pad, int float_precision) | |
{ | |
UnitSettings *unit = but->block->unit; | |
const int unit_type = UI_but_unit_type_get(but); | |
int precision; | |
if (unit->scale_length < 0.0001f) { | |
unit->scale_length = 1.0f; /* XXX do_versions */ | |
} | |
/* Use precision override? */ | |
if (float_precision == -1) { | |
/* Sanity checks */ | |
precision = int(ui_but_get_float_precision(but)); | |
if (precision > UI_PRECISION_FLOAT_MAX) { | |
precision = UI_PRECISION_FLOAT_MAX; | |
} | |
else if (precision == -1) { | |
precision = 2; | |
} | |
} | |
else { | |
precision = float_precision; | |
} | |
BKE_unit_value_as_string(str, | |
str_maxncpy, | |
ui_get_but_scale_unit(but, value), | |
precision, | |
RNA_SUBTYPE_UNIT_VALUE(unit_type), | |
unit, | |
pad); | |
} | |
static float ui_get_but_step_unit(uiBut *but, float step_default) | |
{ | |
const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but)); | |
const double step_orig = step_default * UI_PRECISION_FLOAT_SCALE; | |
/* Scaling up 'step_origg ' here is a bit arbitrary, | |
* its just giving better scales from user POV */ | |
const double scale_step = ui_get_but_scale_unit(but, step_orig * 10); | |
const double step = BKE_unit_closest_scalar(scale_step, but->block->unit->system, unit_type); | |
/* -1 is an error value */ | |
if (step == -1.0f) { | |
return step_default; | |
} | |
const double scale_unit = ui_get_but_scale_unit(but, 1.0); | |
const double step_unit = BKE_unit_closest_scalar( | |
scale_unit, but->block->unit->system, unit_type); | |
double step_final; | |
BLI_assert(step > 0.0); | |
step_final = (step / scale_unit) / double(UI_PRECISION_FLOAT_SCALE); | |
if (step == step_unit) { | |
/* Logic here is to scale by the original 'step_orig' | |
* only when the unit step matches the scaled step. | |
* | |
* This is needed for units that don't have a wide range of scales (degrees for eg.). | |
* Without this we can't select between a single degree, or a 10th of a degree. | |
*/ | |
step_final *= step_orig; | |
} | |
return float(step_final); | |
} | |
void ui_but_string_get_ex(uiBut *but, | |
char *str, | |
const size_t str_maxncpy, | |
const int float_precision, | |
const bool use_exp_float, | |
bool *r_use_exp_float) | |
{ | |
if (r_use_exp_float) { | |
*r_use_exp_float = false; | |
} | |
if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU, UI_BTYPE_TAB)) { | |
const PropertyType type = RNA_property_type(but->rnaprop); | |
int buf_len; | |
const char *buf = nullptr; | |
if ((but->type == UI_BTYPE_TAB) && (but->custom_data)) { | |
StructRNA *ptr_type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); | |
/* uiBut.custom_data points to data this tab represents (e.g. workspace). | |
* uiBut.rnapoin/prop store an active value (e.g. active workspace). */ | |
PointerRNA ptr = RNA_pointer_create(but->rnapoin.owner_id, ptr_type, but->custom_data); | |
buf = RNA_struct_name_get_alloc(&ptr, str, str_maxncpy, &buf_len); | |
} | |
else if (type == PROP_STRING) { | |
/* RNA string */ | |
buf = RNA_property_string_get_alloc(&but->rnapoin, but->rnaprop, str, str_maxncpy, &buf_len); | |
} | |
else if (type == PROP_ENUM) { | |
/* RNA enum */ | |
const int value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); | |
if (RNA_property_enum_name(static_cast<bContext *>(but->block->evil_C), | |
&but->rnapoin, | |
but->rnaprop, | |
value, | |
&buf)) | |
{ | |
BLI_strncpy(str, buf, str_maxncpy); | |
buf = str; | |
} | |
} | |
else if (type == PROP_POINTER) { | |
/* RNA pointer */ | |
PointerRNA ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); | |
buf = RNA_struct_name_get_alloc(&ptr, str, str_maxncpy, &buf_len); | |
} | |
else { | |
BLI_assert(0); | |
} | |
if (buf == nullptr) { | |
str[0] = '\0'; | |
} | |
else if (buf != str) { | |
BLI_assert(str_maxncpy <= buf_len + 1); | |
/* string was too long, we have to truncate */ | |
if (UI_but_is_utf8(but)) { | |
BLI_strncpy_utf8(str, buf, str_maxncpy); | |
} | |
else { | |
BLI_strncpy(str, buf, str_maxncpy); | |
} | |
MEM_freeN((void *)buf); | |
} | |
} | |
else if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { | |
/* string */ | |
BLI_strncpy(str, but->poin, str_maxncpy); | |
return; | |
} | |
else if (ui_but_anim_expression_get(but, str, str_maxncpy)) { | |
/* driver expression */ | |
} | |
else { | |
/* number editing */ | |
const double value = ui_but_value_get(but); | |
PropertySubType subtype = PROP_NONE; | |
if (but->rnaprop) { | |
subtype = RNA_property_subtype(but->rnaprop); | |
} | |
if (ui_but_is_float(but)) { | |
int prec = float_precision; | |
if (float_precision == -1) { | |
prec = ui_but_calc_float_precision(but, value); | |
} | |
else if (!use_exp_float && ui_but_hide_fraction(but, value)) { | |
prec = 0; | |
} | |
if (ui_but_is_unit(but)) { | |
ui_get_but_string_unit(but, str, str_maxncpy, value, false, prec); | |
} | |
else if (subtype == PROP_FACTOR) { | |
if (U.factor_display_type == USER_FACTOR_AS_FACTOR) { | |
BLI_snprintf(str, str_maxncpy, "%.*f", prec, value); | |
} | |
else { | |
BLI_snprintf(str, str_maxncpy, "%.*f", MAX2(0, prec - 2), value * 100); | |
} | |
} | |
else { | |
const int int_digits_num = integer_digits_f(value); | |
if (use_exp_float) { | |
if (int_digits_num < -6 || int_digits_num > 12) { | |
BLI_snprintf(str, str_maxncpy, "%.*g", prec, value); | |
if (r_use_exp_float) { | |
*r_use_exp_float = true; | |
} | |
} | |
else { | |
prec -= int_digits_num; | |
CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX); | |
BLI_snprintf(str, str_maxncpy, "%.*f", prec, value); | |
} | |
} | |
else { | |
prec -= int_digits_num; | |
CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX); | |
BLI_snprintf(str, str_maxncpy, "%.*f", prec, value); | |
} | |
} | |
} | |
else { | |
BLI_snprintf(str, str_maxncpy, "%d", int(value)); | |
} | |
} | |
} | |
void ui_but_string_get(uiBut *but, char *str, const size_t str_maxncpy) | |
{ | |
ui_but_string_get_ex(but, str, str_maxncpy, -1, false, nullptr); | |
} | |
char *ui_but_string_get_dynamic(uiBut *but, int *r_str_size) | |
{ | |
char *str = nullptr; | |
*r_str_size = 1; | |
if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { | |
const PropertyType type = RNA_property_type(but->rnaprop); | |
if (type == PROP_STRING) { | |
/* RNA string */ | |
str = RNA_property_string_get_alloc(&but->rnapoin, but->rnaprop, nullptr, 0, r_str_size); | |
(*r_str_size) += 1; | |
} | |
else if (type == PROP_ENUM) { | |
/* RNA enum */ | |
const int value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); | |
const char *value_id; | |
if (!RNA_property_enum_name(static_cast<bContext *>(but->block->evil_C), | |
&but->rnapoin, | |
but->rnaprop, | |
value, | |
&value_id)) | |
{ | |
value_id = ""; | |
} | |
*r_str_size = strlen(value_id) + 1; | |
str = BLI_strdupn(value_id, *r_str_size); | |
} | |
else if (type == PROP_POINTER) { | |
/* RNA pointer */ | |
PointerRNA ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); | |
str = RNA_struct_name_get_alloc(&ptr, nullptr, 0, r_str_size); | |
(*r_str_size) += 1; | |
} | |
else { | |
BLI_assert(0); | |
} | |
} | |
else { | |
BLI_assert(0); | |
} | |
if (UNLIKELY(str == nullptr)) { | |
/* should never happen, paranoid check */ | |
*r_str_size = 1; | |
str = BLI_strdup(""); | |
BLI_assert(0); | |
} | |
return str; | |
} | |
/** | |
* Report a generic error prefix when evaluating a string with #BPY_run_string_as_number | |
* as the Python error on its own doesn't provide enough context. | |
*/ | |
#define UI_NUMBER_EVAL_ERROR_PREFIX IFACE_("Error evaluating number, see Info editor for details") | |
static bool ui_number_from_string_units( | |
bContext *C, const char *str, const int unit_type, const UnitSettings *unit, double *r_value) | |
{ | |
char *error = nullptr; | |
const bool ok = user_string_to_number(C, str, unit, unit_type, r_value, true, &error); | |
if (error) { | |
ReportList *reports = CTX_wm_reports(C); | |
BKE_reportf(reports, RPT_ERROR, "%s: %s", UI_NUMBER_EVAL_ERROR_PREFIX, error); | |
MEM_freeN(error); | |
} | |
return ok; | |
} | |
static bool ui_number_from_string_units_with_but(bContext *C, | |
const char *str, | |
const uiBut *but, | |
double *r_value) | |
{ | |
const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but)); | |
const UnitSettings *unit = but->block->unit; | |
return ui_number_from_string_units(C, str, unit_type, unit, r_value); | |
} | |
static bool ui_number_from_string(bContext *C, const char *str, double *r_value) | |
{ | |
bool ok; | |
#ifdef WITH_PYTHON | |
BPy_RunErrInfo err_info = {}; | |
err_info.reports = CTX_wm_reports(C); | |
err_info.report_prefix = UI_NUMBER_EVAL_ERROR_PREFIX; | |
ok = BPY_run_string_as_number(C, nullptr, str, &err_info, r_value); | |
#else | |
UNUSED_VARS(C); | |
*r_value = atof(str); | |
ok = true; | |
#endif | |
return ok; | |
} | |
static bool ui_number_from_string_factor(bContext *C, const char *str, double *r_value) | |
{ | |
const int len = strlen(str); | |
if (BLI_strn_endswith(str, "%", len)) { | |
char *str_new = BLI_strdupn(str, len - 1); | |
const bool success = ui_number_from_string(C, str_new, r_value); | |
MEM_freeN(str_new); | |
*r_value /= 100.0; | |
return success; | |
} | |
if (!ui_number_from_string(C, str, r_value)) { | |
return false; | |
} | |
if (U.factor_display_type == USER_FACTOR_AS_PERCENTAGE) { | |
*r_value /= 100.0; | |
} | |
return true; | |
} | |
static bool ui_number_from_string_percentage(bContext *C, const char *str, double *r_value) | |
{ | |
const int len = strlen(str); | |
if (BLI_strn_endswith(str, "%", len)) { | |
char *str_new = BLI_strdupn(str, len - 1); | |
const bool success = ui_number_from_string(C, str_new, r_value); | |
MEM_freeN(str_new); | |
return success; | |
} | |
return ui_number_from_string(C, str, r_value); | |
} | |
bool ui_but_string_eval_number(bContext *C, const uiBut *but, const char *str, double *r_value) | |
{ | |
if (str[0] == '\0') { | |
*r_value = 0.0; | |
return true; | |
} | |
PropertySubType subtype = PROP_NONE; | |
if (but->rnaprop) { | |
subtype = RNA_property_subtype(but->rnaprop); | |
} | |
if (ui_but_is_float(but)) { | |
if (ui_but_is_unit(but)) { | |
return ui_number_from_string_units_with_but(C, str, but, r_value); | |
} | |
if (subtype == PROP_FACTOR) { | |
return ui_number_from_string_factor(C, str, r_value); | |
} | |
if (subtype == PROP_PERCENTAGE) { | |
return ui_number_from_string_percentage(C, str, r_value); | |
} | |
return ui_number_from_string(C, str, r_value); | |
} | |
return ui_number_from_string(C, str, r_value); | |
} | |
/* just the assignment/free part */ | |
static void ui_but_string_set_internal(uiBut *but, const char *str, size_t str_len) | |
{ | |
BLI_assert(str_len == strlen(str)); | |
BLI_assert(but->str == nullptr); | |
str_len += 1; | |
if (str_len > UI_MAX_NAME_STR) { | |
but->str = static_cast<char *>(MEM_mallocN(str_len, "ui_def_but str")); | |
} | |
else { | |
but->str = but->strdata; | |
} | |
memcpy(but->str, str, str_len); | |
} | |
static void ui_but_string_free_internal(uiBut *but) | |
{ | |
if (but->str) { | |
if (but->str != but->strdata) { | |
MEM_freeN(but->str); | |
} | |
/* must call 'ui_but_string_set_internal' after */ | |
but->str = nullptr; | |
} | |
} | |
bool ui_but_string_set(bContext *C, uiBut *but, const char *str) | |
{ | |
if (but->rnaprop && but->rnapoin.data && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { | |
if (RNA_property_editable(&but->rnapoin, but->rnaprop)) { | |
const PropertyType type = RNA_property_type(but->rnaprop); | |
if (type == PROP_STRING) { | |
/* RNA string */ | |
RNA_property_string_set(&but->rnapoin, but->rnaprop, str); | |
return true; | |
} | |
if (type == PROP_POINTER) { | |
if (str[0] == '\0') { | |
RNA_property_pointer_set(&but->rnapoin, but->rnaprop, PointerRNA_NULL, nullptr); | |
return true; | |
} | |
uiButSearch *search_but = (but->type == UI_BTYPE_SEARCH_MENU) ? (uiButSearch *)but : | |
nullptr; | |
/* RNA pointer */ | |
PointerRNA rptr; | |
/* This is kind of hackish, in theory think we could only ever use the second member of | |
* this if/else, since #ui_searchbox_apply() is supposed to always set that pointer when | |
* we are storing pointers... But keeping str search first for now, | |
* to try to break as little as possible existing code. All this is band-aids anyway. | |
* Fact remains, using `editstr` as main 'reference' over whole search button thingy | |
* is utterly weak and should be redesigned IMHO, but that's not a simple task. */ | |
if (search_but && search_but->rnasearchprop && | |
RNA_property_collection_lookup_string( | |
&search_but->rnasearchpoin, search_but->rnasearchprop, str, &rptr)) | |
{ | |
RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr, nullptr); | |
} | |
else if (search_but->item_active != nullptr) { | |
rptr = RNA_pointer_create(nullptr, | |
RNA_property_pointer_type(&but->rnapoin, but->rnaprop), | |
search_but->item_active); | |
RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr, nullptr); | |
} | |
return true; | |
} | |
if (type == PROP_ENUM) { | |
int value; | |
if (RNA_property_enum_value(static_cast<bContext *>(but->block->evil_C), | |
&but->rnapoin, | |
but->rnaprop, | |
str, | |
&value)) | |
{ | |
RNA_property_enum_set(&but->rnapoin, but->rnaprop, value); | |
return true; | |
} | |
return false; | |
} | |
BLI_assert(0); | |
} | |
} | |
else if (but->type == UI_BTYPE_TAB) { | |
if (but->rnaprop && but->custom_data) { | |
StructRNA *ptr_type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); | |
PropertyRNA *prop; | |
/* uiBut.custom_data points to data this tab represents (e.g. workspace). | |
* uiBut.rnapoin/prop store an active value (e.g. active workspace). */ | |
PointerRNA ptr = RNA_pointer_create(but->rnapoin.owner_id, ptr_type, but->custom_data); | |
prop = RNA_struct_name_property(ptr_type); | |
if (RNA_property_editable(&ptr, prop)) { | |
RNA_property_string_set(&ptr, prop, str); | |
} | |
} | |
} | |
else if (but->type == UI_BTYPE_TEXT) { | |
/* string */ | |
if (!but->poin) { | |
str = ""; | |
} | |
else if (UI_but_is_utf8(but)) { | |
BLI_strncpy_utf8(but->poin, str, but->hardmax); | |
} | |
else { | |
BLI_strncpy(but->poin, str, but->hardmax); | |
} | |
return true; | |
} | |
else if (but->type == UI_BTYPE_SEARCH_MENU) { | |
/* string */ | |
BLI_strncpy(but->poin, str, but->hardmax); | |
return true; | |
} | |
else if (ui_but_anim_expression_set(but, str)) { | |
/* driver expression */ | |
return true; | |
} | |
else if (str[0] == '#') { | |
/* Shortcut to create new driver expression (versus immediate Python-execution). */ | |
return ui_but_anim_expression_create(but, str + 1); | |
} | |
else { | |
/* number editing */ | |
double value; | |
if (ui_but_string_eval_number(C, but, str, &value) == false) { | |
WM_report_banner_show(CTX_wm_manager(C), CTX_wm_window(C)); | |
return false; | |
} | |
if (!ui_but_is_float(but)) { | |
value = floor(value + 0.5); | |
} | |
/* not that we use hard limits here */ | |
if (value < double(but->hardmin)) { | |
value = but->hardmin; | |
} | |
if (value > double(but->hardmax)) { | |
value = but->hardmax; | |
} | |
ui_but_value_set(but, value); | |
return true; | |
} | |
return false; | |
} | |
static double soft_range_round_up(double value, double max) | |
{ | |
/* round up to .., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, .. | |
* checking for 0.0 prevents floating point exceptions */ | |
const double newmax = (value != 0.0) ? pow(10.0, ceil(log(value) / M_LN10)) : 0.0; | |
if (newmax * 0.2 >= max && newmax * 0.2 >= value) { | |
return newmax * 0.2; | |
} | |
if (newmax * 0.5 >= max && newmax * 0.5 >= value) { | |
return newmax * 0.5; | |
} | |
return newmax; | |
} | |
static double soft_range_round_down(double value, double max) | |
{ | |
/* round down to .., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, .. | |
* checking for 0.0 prevents floating point exceptions */ | |
const double newmax = (value != 0.0) ? pow(10.0, floor(log(value) / M_LN10)) : 0.0; | |
if (newmax * 5.0 <= max && newmax * 5.0 <= value) { | |
return newmax * 5.0; | |
} | |
if (newmax * 2.0 <= max && newmax * 2.0 <= value) { | |
return newmax * 2.0; | |
} | |
return newmax; | |
} | |
void ui_but_range_set_hard(uiBut *but) | |
{ | |
if (but->rnaprop == nullptr) { | |
return; | |
} | |
const PropertyType type = RNA_property_type(but->rnaprop); | |
if (type == PROP_INT) { | |
int imin, imax; | |
RNA_property_int_range(&but->rnapoin, but->rnaprop, &imin, &imax); | |
but->hardmin = imin; | |
but->hardmax = imax; | |
} | |
else if (type == PROP_FLOAT) { | |
float fmin, fmax; | |
RNA_property_float_range(&but->rnapoin, but->rnaprop, &fmin, &fmax); | |
but->hardmin = fmin; | |
but->hardmax = fmax; | |
} | |
} | |
void ui_but_range_set_soft(uiBut *but) | |
{ | |
/* This could be split up into functions which handle arrays and not. */ | |
/* Ideally we would not limit this, but practically it's more than | |
* enough. Worst case is very long vectors won't use a smart soft-range, | |
* which isn't so bad. */ | |
if (but->rnaprop) { | |
const PropertyType type = RNA_property_type(but->rnaprop); | |
const PropertySubType subtype = RNA_property_subtype(but->rnaprop); | |
double softmin, softmax; | |
// double step, precision; /* UNUSED. */ | |
double value_min; | |
double value_max; | |
/* clamp button range to something reasonable in case | |
* we get -inf/inf from RNA properties */ | |
if (type == PROP_INT) { | |
const bool is_array = RNA_property_array_check(but->rnaprop); | |
int imin, imax, istep; | |
RNA_property_int_ui_range(&but->rnapoin, but->rnaprop, &imin, &imax, &istep); | |
softmin = (imin == INT_MIN) ? -1e4 : imin; | |
softmax = (imin == INT_MAX) ? 1e4 : imax; | |
// step = istep; /* UNUSED */ | |
// precision = 1; /* UNUSED */ | |
if (is_array) { | |
int value_range[2]; | |
RNA_property_int_get_array_range(&but->rnapoin, but->rnaprop, value_range); | |
value_min = double(value_range[0]); | |
value_max = double(value_range[1]); | |
} | |
else { | |
value_min = value_max = ui_but_value_get(but); | |
} | |
} | |
else if (type == PROP_FLOAT) { | |
const bool is_array = RNA_property_array_check(but->rnaprop); | |
float fmin, fmax, fstep, fprecision; | |
RNA_property_float_ui_range(&but->rnapoin, but->rnaprop, &fmin, &fmax, &fstep, &fprecision); | |
softmin = (fmin == -FLT_MAX) ? float(-1e4) : fmin; | |
softmax = (fmax == FLT_MAX) ? float(1e4) : fmax; | |
// step = fstep; /* UNUSED */ | |
// precision = fprecision; /* UNUSED */ | |
/* Use shared min/max for array values, except for color alpha. */ | |
if (is_array && !(subtype == PROP_COLOR && but->rnaindex == 3)) { | |
float value_range[2]; | |
RNA_property_float_get_array_range(&but->rnapoin, but->rnaprop, value_range); | |
value_min = double(value_range[0]); | |
value_max = double(value_range[1]); | |
} | |
else { | |
value_min = value_max = ui_but_value_get(but); | |
} | |
} | |
else { | |
return; | |
} | |
/* if the value goes out of the soft/max range, adapt the range */ | |
if (value_min + 1e-10 < softmin) { | |
if (value_min < 0.0) { | |
softmin = -soft_range_round_up(-value_min, -softmin); | |
} | |
else { | |
softmin = soft_range_round_down(value_min, softmin); | |
} | |
if (softmin < double(but->hardmin)) { | |
softmin = double(but->hardmin); | |
} | |
} | |
if (value_max - 1e-10 > softmax) { | |
if (value_max < 0.0) { | |
softmax = -soft_range_round_down(-value_max, -softmax); | |
} | |
else { | |
softmax = soft_range_round_up(value_max, softmax); | |
} | |
if (softmax > double(but->hardmax)) { | |
softmax = but->hardmax; | |
} | |
} | |
but->softmin = softmin; | |
but->softmax = softmax; | |
} | |
else if (but->poin && (but->pointype & UI_BUT_POIN_TYPES)) { | |
float value = ui_but_value_get(but); | |
if (isfinite(value)) { | |
CLAMP(value, but->hardmin, but->hardmax); | |
but->softmin = min_ff(but->softmin, value); | |
but->softmax = max_ff(but->softmax, value); | |
} | |
} | |
} | |
/* ******************* Free ********************/ | |
/** | |
* Free data specific to a certain button type. | |
* For now just do in a switch-case, we could instead have a callback stored in #uiBut and set that | |
* in #ui_but_alloc_info(). | |
*/ | |
static void ui_but_free_type_specific(uiBut *but) | |
{ | |
switch (but->type) { | |
case UI_BTYPE_SEARCH_MENU: { | |
uiButSearch *search_but = (uiButSearch *)but; | |
MEM_SAFE_FREE(search_but->item_active_str); | |
if (search_but->arg_free_fn) { | |
search_but->arg_free_fn(search_but->arg); | |
search_but->arg = nullptr; | |
} | |
break; | |
} | |
default: | |
break; | |
} | |
} | |
/* can be called with C==nullptr */ | |
static void ui_but_free(const bContext *C, uiBut *but) | |
{ | |
if (but->opptr) { | |
WM_operator_properties_free(but->opptr); | |
MEM_freeN(but->opptr); | |
} | |
if (but->func_argN) { | |
MEM_freeN(but->func_argN); | |
} | |
if (but->tip_arg_free) { | |
but->tip_arg_free(but->tip_arg); | |
} | |
if (but->hold_argN) { | |
MEM_freeN(but->hold_argN); | |
} | |
if (but->placeholder) { | |
MEM_freeN(but->placeholder); | |
} | |
ui_but_free_type_specific(but); | |
if (but->active) { | |
/* XXX solve later, buttons should be free-able without context ideally, | |
* however they may have open tooltips or popup windows, which need to | |
* be closed using a context pointer */ | |
if (C) { | |
ui_but_active_free(C, but); | |
} | |
else { | |
if (but->active) { | |
MEM_freeN(but->active); | |
} | |
} | |
} | |
if (but->str && but->str != but->strdata) { | |
MEM_freeN(but->str); | |
} | |
if ((but->type == UI_BTYPE_IMAGE) && but->poin) { | |
IMB_freeImBuf((ImBuf *)but->poin); | |
} | |
ui_but_drag_free(but); | |
ui_but_extra_operator_icons_free(but); | |
BLI_assert(UI_butstore_is_registered(but->block, but) == false); | |
MEM_delete(but); | |
} | |
void UI_block_free(const bContext *C, uiBlock *block) | |
{ | |
UI_butstore_clear(block); | |
while (uiBut *but = static_cast<uiBut *>(BLI_pophead(&block->buttons))) { | |
ui_but_free(C, but); | |
} | |
if (block->unit) { | |
MEM_freeN(block->unit); | |
} | |
if (block->func_argN) { | |
MEM_freeN(block->func_argN); | |
} | |
BLI_freelistN(&block->saferct); | |
BLI_freelistN(&block->color_pickers.list); | |
BLI_freelistN(&block->dynamic_listeners); | |
ui_block_free_views(block); | |
MEM_delete(block); | |
} | |
void UI_block_listen(const uiBlock *block, const wmRegionListenerParams *listener_params) | |
{ | |
/* Note that #uiBlock.active shouldn't be checked here, since notifier listening happens before | |
* drawing, so there are no active blocks at this point. */ | |
LISTBASE_FOREACH (uiBlockDynamicListener *, listener, &block->dynamic_listeners) { | |
listener->listener_func(listener_params); | |
} | |
ui_block_views_listen(block, listener_params); | |
} | |
void UI_blocklist_update_window_matrix(const bContext *C, const ListBase *lb) | |
{ | |
ARegion *region = CTX_wm_region(C); | |
wmWindow *window = CTX_wm_window(C); | |
LISTBASE_FOREACH (uiBlock *, block, lb) { | |
if (block->active) { | |
ui_update_window_matrix(window, region, block); | |
} | |
} | |
} | |
void UI_blocklist_update_view_for_buttons(const bContext *C, const ListBase *lb) | |
{ | |
LISTBASE_FOREACH (uiBlock *, block, lb) { | |
if (block->active) { | |
ui_but_update_view_for_active(C, block); | |
} | |
} | |
} | |
void UI_blocklist_draw(const bContext *C, const ListBase *lb) | |
{ | |
LISTBASE_FOREACH (uiBlock *, block, lb) { | |
if (block->active) { | |
UI_block_draw(C, block); | |
} | |
} | |
} | |
void UI_blocklist_free(const bContext *C, ARegion *region) | |
{ | |
ListBase *lb = ®ion->uiblocks; | |
while (uiBlock *block = static_cast<uiBlock *>(BLI_pophead(lb))) { | |
UI_block_free(C, block); | |
} | |
if (region->runtime.block_name_map != nullptr) { | |
BLI_ghash_free(region->runtime.block_name_map, nullptr, nullptr); | |
region->runtime.block_name_map = nullptr; | |
} | |
} | |
void UI_blocklist_free_inactive(const bContext *C, ARegion *region) | |
{ | |
ListBase *lb = ®ion->uiblocks; | |
LISTBASE_FOREACH_MUTABLE (uiBlock *, block, lb) { | |
if (!block->handle) { | |
if (block->active) { | |
block->active = false; | |
} | |
else { | |
if (region->runtime.block_name_map != nullptr) { | |
uiBlock *b = static_cast<uiBlock *>( | |
BLI_ghash_lookup(region->runtime.block_name_map, block->name)); | |
if (b == block) { | |
BLI_ghash_remove(region->runtime.block_name_map, b->name, nullptr, nullptr); | |
} | |
} | |
BLI_remlink(lb, block); | |
UI_block_free(C, block); | |
} | |
} | |
} | |
} | |
void UI_block_region_set(uiBlock *block, ARegion *region) | |
{ | |
ListBase *lb = ®ion->uiblocks; | |
uiBlock *oldblock = nullptr; | |
/* each listbase only has one block with this name, free block | |
* if is already there so it can be rebuilt from scratch */ | |
if (lb) { | |
if (region->runtime.block_name_map == nullptr) { | |
region->runtime.block_name_map = BLI_ghash_str_new(__func__); | |
} | |
oldblock = (uiBlock *)BLI_ghash_lookup(region->runtime.block_name_map, block->name); | |
if (oldblock) { | |
oldblock->active = false; | |
oldblock->panel = nullptr; | |
oldblock->handle = nullptr; | |
} | |
/* at the beginning of the list! for dynamical menus/blocks */ | |
BLI_addhead(lb, block); | |
BLI_ghash_reinsert(region->runtime.block_name_map, block->name, block, nullptr, nullptr); | |
} | |
block->oldblock = oldblock; | |
} | |
uiBlock *UI_block_begin(const bContext *C, ARegion *region, const char *name, eUIEmbossType emboss) | |
{ | |
wmWindow *window = CTX_wm_window(C); | |
Scene *scene = CTX_data_scene(C); | |
uiBlock *block = MEM_new<uiBlock>(__func__); | |
block->active = true; | |
block->emboss = emboss; | |
block->evil_C = (void *)C; /* XXX */ | |
if (scene) { | |
/* store display device name, don't lookup for transformations yet | |
* block could be used for non-color displays where looking up for transformation | |
* would slow down redraw, so only lookup for actual transform when it's indeed | |
* needed | |
*/ | |
STRNCPY(block->display_device, scene->display_settings.display_device); | |
/* copy to avoid crash when scene gets deleted with ui still open */ | |
block->unit = MEM_new<UnitSettings>(__func__); | |
memcpy(block->unit, &scene->unit, sizeof(scene->unit)); | |
} | |
else { | |
STRNCPY(block->display_device, IMB_colormanagement_display_get_default_name()); | |
} | |
STRNCPY(block->name, name); | |
if (region) { | |
UI_block_region_set(block, region); | |
} | |
/* Set window matrix and aspect for region and OpenGL state. */ | |
ui_update_window_matrix(window, region, block); | |
/* Tag as popup menu if not created within a region. */ | |
if (!(region && region->visible)) { | |
block->auto_open = true; | |
block->flag |= UI_BLOCK_LOOP; | |
} | |
return block; | |
} | |
void ui_block_add_dynamic_listener(uiBlock *block, | |
void (*listener_func)(const wmRegionListenerParams *params)) | |
{ | |
uiBlockDynamicListener *listener = static_cast<uiBlockDynamicListener *>( | |
MEM_mallocN(sizeof(*listener), __func__)); | |
listener->listener_func = listener_func; | |
BLI_addtail(&block->dynamic_listeners, listener); | |
} | |
eUIEmbossType UI_block_emboss_get(uiBlock *block) | |
{ | |
return block->emboss; | |
} | |
void UI_block_emboss_set(uiBlock *block, eUIEmbossType emboss) | |
{ | |
block->emboss = emboss; | |
} | |
void UI_block_theme_style_set(uiBlock *block, char theme_style) | |
{ | |
block->theme_style = theme_style; | |
} | |
bool UI_block_is_search_only(const uiBlock *block) | |
{ | |
return block->flag & UI_BLOCK_SEARCH_ONLY; | |
} | |
void UI_block_set_search_only(uiBlock *block, bool search_only) | |
{ | |
SET_FLAG_FROM_TEST(block->flag, search_only, UI_BLOCK_SEARCH_ONLY); | |
} | |
static void ui_but_build_drawstr_float(uiBut *but, double value) | |
{ | |
size_t slen = 0; | |
STR_CONCAT(but->drawstr, slen, but->str); | |
PropertySubType subtype = PROP_NONE; | |
if (but->rnaprop) { | |
subtype = RNA_property_subtype(but->rnaprop); | |
} | |
/* Change negative zero to regular zero, without altering anything else. */ | |
value += +0.0f; | |
if (value == double(FLT_MAX)) { | |
STR_CONCAT(but->drawstr, slen, "inf"); | |
} | |
else if (value == double(-FLT_MAX)) { | |
STR_CONCAT(but->drawstr, slen, "-inf"); | |
} | |
else if (subtype == PROP_PERCENTAGE) { | |
const int prec = ui_but_calc_float_precision(but, value); | |
STR_CONCATF(but->drawstr, slen, "%.*f%%", prec, value); | |
} | |
else if (subtype == PROP_PIXEL) { | |
const int prec = ui_but_calc_float_precision(but, value); | |
STR_CONCATF(but->drawstr, slen, "%.*f px", prec, value); | |
} | |
else if (subtype == PROP_FACTOR) { | |
const int precision = ui_but_calc_float_precision(but, value); | |
if (U.factor_display_type == USER_FACTOR_AS_FACTOR) { | |
STR_CONCATF(but->drawstr, slen, "%.*f", precision, value); | |
} | |
else { | |
STR_CONCATF(but->drawstr, slen, "%.*f%%", MAX2(0, precision - 2), value * 100); | |
} | |
} | |
else if (ui_but_is_unit(but)) { | |
char new_str[sizeof(but->drawstr)]; | |
ui_get_but_string_unit(but, new_str, sizeof(new_str), value, true, -1); | |
STR_CONCAT(but->drawstr, slen, new_str); | |
} | |
else { | |
const int prec = ui_but_calc_float_precision(but, value); | |
STR_CONCATF(but->drawstr, slen, "%.*f", prec, value); | |
} | |
} | |
static void ui_but_build_drawstr_int(uiBut *but, int value) | |
{ | |
size_t slen = 0; | |
STR_CONCAT(but->drawstr, slen, but->str); | |
PropertySubType subtype = PROP_NONE; | |
if (but->rnaprop) { | |
subtype = RNA_property_subtype(but->rnaprop); | |
} | |
STR_CONCATF(but->drawstr, slen, "%d", value); | |
if (subtype == PROP_PERCENTAGE) { | |
STR_CONCAT(but->drawstr, slen, "%"); | |
} | |
else if (subtype == PROP_PIXEL) { | |
STR_CONCAT(but->drawstr, slen, " px"); | |
} | |
} | |
/** | |
* \param but: Button to update. | |
* \param validate: When set, this function may change the button value. | |
* Otherwise treat the button value as read-only. | |
*/ | |
static void ui_but_update_ex(uiBut *but, const bool validate) | |
{ | |
/* if something changed in the button */ | |
double value = UI_BUT_VALUE_UNSET; | |
ui_but_update_select_flag(but, &value); | |
/* only update soft range while not editing */ | |
if (!ui_but_is_editing(but)) { | |
if ((but->rnaprop != nullptr) || (but->poin && (but->pointype & UI_BUT_POIN_TYPES))) { | |
ui_but_range_set_soft(but); | |
} | |
} | |
/* test for min and max, icon sliders, etc */ | |
switch (but->type) { | |
case UI_BTYPE_NUM: | |
case UI_BTYPE_SCROLL: | |
case UI_BTYPE_NUM_SLIDER: | |
if (validate) { | |
UI_GET_BUT_VALUE_INIT(but, value); | |
if (value < double(but->hardmin)) { | |
ui_but_value_set(but, but->hardmin); | |
} | |
else if (value > double(but->hardmax)) { | |
ui_but_value_set(but, but->hardmax); | |
} | |
/* max must never be smaller than min! Both being equal is allowed though */ | |
BLI_assert(but->softmin <= but->softmax && but->hardmin <= but->hardmax); | |
} | |
break; | |
case UI_BTYPE_ICON_TOGGLE: | |
case UI_BTYPE_ICON_TOGGLE_N: | |
if ((but->rnaprop == nullptr) || (RNA_property_flag(but->rnaprop) & PROP_ICONS_CONSECUTIVE)) | |
{ | |
if (but->rnaprop && RNA_property_flag(but->rnaprop) & PROP_ICONS_REVERSE) { | |
but->drawflag |= UI_BUT_ICON_REVERSE; | |
} | |
but->iconadd = (but->flag & UI_SELECT) ? 1 : 0; | |
} | |
break; | |
/* quiet warnings for unhandled types */ | |
default: | |
break; | |
} | |
/* safety is 4 to enable small number buttons (like 'users') */ | |
// okwidth = -4 + (BLI_rcti_size_x(&but->rect)); /* UNUSED */ | |
/* name: */ | |
switch (but->type) { | |
case UI_BTYPE_MENU: | |
if (BLI_rctf_size_x(&but->rect) >= (UI_UNIT_X * 2)) { | |
/* only needed for menus in popup blocks that don't recreate buttons on redraw */ | |
if (but->block->flag & UI_BLOCK_LOOP) { | |
if (but->rnaprop && (RNA_property_type(but->rnaprop) == PROP_ENUM)) { | |
const int value_enum = RNA_property_enum_get(&but->rnapoin, but->rnaprop); | |
EnumPropertyItem item; | |
if (RNA_property_enum_item_from_value_gettexted( | |
static_cast<bContext *>(but->block->evil_C), | |
&but->rnapoin, | |
but->rnaprop, | |
value_enum, | |
&item)) | |
{ | |
const size_t slen = strlen(item.name); | |
ui_but_string_free_internal(but); | |
ui_but_string_set_internal(but, item.name, slen); | |
but->icon = item.icon; | |
} | |
} | |
} | |
STRNCPY(but->drawstr, but->str); | |
} | |
break; | |
case UI_BTYPE_NUM: | |
case UI_BTYPE_NUM_SLIDER: | |
if (but->editstr) { | |
break; | |
} | |
UI_GET_BUT_VALUE_INIT(but, value); | |
if (ui_but_is_float(but)) { | |
ui_but_build_drawstr_float(but, value); | |
} | |
else { | |
ui_but_build_drawstr_int(but, int(value)); | |
} | |
break; | |
case UI_BTYPE_LABEL: | |
if (ui_but_is_float(but)) { | |
UI_GET_BUT_VALUE_INIT(but, value); | |
const int prec = ui_but_calc_float_precision(but, value); | |
SNPRINTF(but->drawstr, "%s%.*f", but->str, prec, value); | |
} | |
else { | |
STRNCPY(but->drawstr, but->str); | |
} | |
break; | |
case UI_BTYPE_TEXT: | |
case UI_BTYPE_SEARCH_MENU: | |
if (!but->editstr) { | |
char str[UI_MAX_DRAW_STR]; | |
ui_but_string_get(but, str, UI_MAX_DRAW_STR); | |
SNPRINTF(but->drawstr, "%s%s", but->str, str); | |
} | |
break; | |
case UI_BTYPE_KEY_EVENT: { | |
const char *str; | |
if (but->flag & UI_SELECT) { | |
str = IFACE_("Press a key"); | |
} | |
else { | |
UI_GET_BUT_VALUE_INIT(but, value); | |
str = WM_key_event_string(short(value), false); | |
} | |
SNPRINTF(but->drawstr, "%s%s", but->str, str); | |
break; | |
} | |
case UI_BTYPE_HOTKEY_EVENT: | |
if (but->flag & UI_SELECT) { | |
const uiButHotkeyEvent *hotkey_but = (uiButHotkeyEvent *)but; | |
if (hotkey_but->modifier_key) { | |
/* Rely on #KM_NOTHING being zero for `type`, `val` ... etc. */ | |
wmKeyMapItem kmi_dummy = {nullptr}; | |
kmi_dummy.shift = (hotkey_but->modifier_key & KM_SHIFT) ? KM_PRESS : KM_NOTHING; | |
kmi_dummy.ctrl = (hotkey_but->modifier_key & KM_CTRL) ? KM_PRESS : KM_NOTHING; | |
kmi_dummy.alt = (hotkey_but->modifier_key & KM_ALT) ? KM_PRESS : KM_NOTHING; | |
kmi_dummy.oskey = (hotkey_but->modifier_key & KM_OSKEY) ? KM_PRESS : KM_NOTHING; | |
WM_keymap_item_to_string(&kmi_dummy, true, but->drawstr, sizeof(but->drawstr)); | |
} | |
else { | |
STRNCPY_UTF8(but->drawstr, IFACE_("Press a key")); | |
} | |
} | |
else { | |
STRNCPY_UTF8(but->drawstr, but->str); | |
} | |
break; | |
case UI_BTYPE_HSVCUBE: | |
case UI_BTYPE_HSVCIRCLE: | |
break; | |
default: | |
STRNCPY(but->drawstr, but->str); | |
break; | |
} | |
/* if we are doing text editing, this will override the drawstr */ | |
if (but->editstr) { | |
but->drawstr[0] = '\0'; | |
} | |
/* text clipping moved to widget drawing code itself */ | |
} | |
void ui_but_update(uiBut *but) | |
{ | |
ui_but_update_ex(but, false); | |
} | |
void ui_but_update_edited(uiBut *but) | |
{ | |
ui_but_update_ex(but, true); | |
} | |
void UI_block_align_begin(uiBlock *block) | |
{ | |
/* if other align was active, end it */ | |
if (block->flag & UI_BUT_ALIGN) { | |
UI_block_align_end(block); | |
} | |
block->flag |= UI_BUT_ALIGN_DOWN; | |
block->alignnr++; | |
/* Buttons declared after this call will get this `alignnr`. */ /* XXX flag? */ | |
} | |
void UI_block_align_end(uiBlock *block) | |
{ | |
block->flag &= ~UI_BUT_ALIGN; /* all 4 flags */ | |
} | |
ColorManagedDisplay *ui_block_cm_display_get(uiBlock *block) | |
{ | |
return IMB_colormanagement_display_get_named(block->display_device); | |
} | |
void ui_block_cm_to_display_space_v3(uiBlock *block, float pixel[3]) | |
{ | |
ColorManagedDisplay *display = ui_block_cm_display_get(block); | |
IMB_colormanagement_scene_linear_to_display_v3(pixel, display); | |
} | |
/** | |
* Factory function: Allocate button and set #uiBut.type. | |
*/ | |
static uiBut *ui_but_new(const eButType type) | |
{ | |
uiBut *but = nullptr; | |
switch (type) { | |
case UI_BTYPE_NUM: | |
but = MEM_new<uiButNumber>("uiButNumber"); | |
break; | |
case UI_BTYPE_COLOR: | |
but = MEM_new<uiButColor>("uiButColor"); | |
break; | |
case UI_BTYPE_DECORATOR: | |
but = MEM_new<uiButDecorator>("uiButDecorator"); | |
break; | |
case UI_BTYPE_TAB: | |
but = MEM_new<uiButTab>("uiButTab"); | |
break; | |
case UI_BTYPE_SEARCH_MENU: | |
but = MEM_new<uiButSearch>("uiButSearch"); | |
break; | |
case UI_BTYPE_PROGRESS: | |
but = MEM_new<uiButProgress>("uiButProgress"); | |
break; | |
case UI_BTYPE_HSVCUBE: | |
but = MEM_new<uiButHSVCube>("uiButHSVCube"); | |
break; | |
case UI_BTYPE_COLORBAND: | |
but = MEM_new<uiButColorBand>("uiButColorBand"); | |
break; | |
case UI_BTYPE_CURVE: | |
but = MEM_new<uiButCurveMapping>("uiButCurveMapping"); | |
break; | |
case UI_BTYPE_CURVEPROFILE: | |
but = MEM_new<uiButCurveProfile>("uiButCurveProfile"); | |
break; | |
case UI_BTYPE_HOTKEY_EVENT: | |
but = MEM_new<uiButHotkeyEvent>("uiButHotkeyEvent"); | |
break; | |
case UI_BTYPE_VIEW_ITEM: | |
but = MEM_new<uiButViewItem>("uiButViewItem"); | |
break; | |
default: | |
but = MEM_new<uiBut>("uiBut"); | |
break; | |
} | |
but->type = type; | |
return but; | |
} | |
uiBut *ui_but_change_type(uiBut *but, eButType new_type) | |
{ | |
if (but->type == new_type) { | |
/* Nothing to do. */ | |
return but; | |
} | |
uiBut *insert_after_but = but->prev; | |
/* Remove old button address */ | |
BLI_remlink(&but->block->buttons, but); | |
const uiBut *old_but_ptr = but; | |
/* Button may have pointer to a member within itself, this will have to be updated. */ | |
const bool has_str_ptr_to_self = but->str == but->strdata; | |
const bool has_poin_ptr_to_self = but->poin == (char *)but; | |
/* Copy construct button with the new type. */ | |
but = ui_but_new(new_type); | |
*but = *old_but_ptr; | |
/* We didn't mean to override this :) */ | |
but->type = new_type; | |
if (has_str_ptr_to_self) { | |
but->str = but->strdata; | |
} | |
if (has_poin_ptr_to_self) { | |
but->poin = (char *)but; | |
} | |
BLI_insertlinkafter(&but->block->buttons, insert_after_but, but); | |
if (but->layout) { | |
const bool found_layout = ui_layout_replace_but_ptr(but->layout, old_but_ptr, but); | |
BLI_assert(found_layout); | |
UNUSED_VARS_NDEBUG(found_layout); | |
ui_button_group_replace_but_ptr(uiLayoutGetBlock(but->layout), old_but_ptr, but); | |
} | |
#ifdef WITH_PYTHON | |
if (UI_editsource_enable_check()) { | |
UI_editsource_but_replace(old_but_ptr, but); | |
} | |
#endif | |
MEM_delete(old_but_ptr); | |
return but; | |
} | |
/** | |
* \brief ui_def_but is the function that draws many button types | |
* | |
* \param x, y: The lower left hand corner of the button (X axis) | |
* \param width, height: The size of the button. | |
* | |
* for float buttons: | |
* \param a1: Click Step (how much to change the value each click) | |
* \param a2: Number of decimal point values to display. 0 defaults to 3 (0.000) | |
* 1,2,3, and a maximum of 4, all greater values will be clamped to 4. | |
*/ | |
static uiBut *ui_def_but(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
void *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
BLI_assert(width >= 0 && height >= 0); | |
/* we could do some more error checks here */ | |
if ((type & BUTTYPE) == UI_BTYPE_LABEL) { | |
BLI_assert((poin != nullptr || min != 0.0f || max != 0.0f || (a1 == 0.0f && a2 != 0.0f) || | |
(a1 != 0.0f && a1 != 1.0f)) == false); | |
} | |
if (type & UI_BUT_POIN_TYPES) { /* a pointer is required */ | |
if (poin == nullptr) { | |
BLI_assert(0); | |
return nullptr; | |
} | |
} | |
uiBut *but = ui_but_new((eButType)(type & BUTTYPE)); | |
but->pointype = (eButPointerType)(type & UI_BUT_POIN_TYPES); | |
but->bit = type & UI_BUT_POIN_BIT; | |
but->bitnr = type & 31; | |
but->retval = retval; | |
const int slen = strlen(str); | |
ui_but_string_set_internal(but, str, slen); | |
but->rect.xmin = x; | |
but->rect.ymin = y; | |
but->rect.xmax = but->rect.xmin + width; | |
but->rect.ymax = but->rect.ymin + height; | |
but->poin = (char *)poin; | |
but->hardmin = but->softmin = min; | |
but->hardmax = but->softmax = max; | |
but->a1 = a1; | |
but->a2 = a2; | |
but->tip = tip; | |
but->disabled_info = block->lockstr; | |
but->emboss = block->emboss; | |
but->block = block; /* pointer back, used for front-buffer status, and picker. */ | |
if ((block->flag & UI_BUT_ALIGN) && ui_but_can_align(but)) { | |
but->alignnr = block->alignnr; | |
} | |
but->func = block->func; | |
but->func_arg1 = block->func_arg1; | |
but->func_arg2 = block->func_arg2; | |
but->funcN = block->funcN; | |
if (block->func_argN) { | |
but->func_argN = MEM_dupallocN(block->func_argN); | |
} | |
but->pos = -1; /* cursor invisible */ | |
if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* add a space to name */ | |
/* slen remains unchanged from previous assignment, ensure this stays true */ | |
if (slen > 0 && slen < UI_MAX_NAME_STR - 2) { | |
if (but->str[slen - 1] != ' ') { | |
but->str[slen] = ' '; | |
but->str[slen + 1] = 0; | |
} | |
} | |
} | |
if (block->flag & UI_BLOCK_RADIAL) { | |
but->drawflag |= UI_BUT_TEXT_LEFT; | |
if (but->str && but->str[0]) { | |
but->drawflag |= UI_BUT_ICON_LEFT; | |
} | |
} | |
else if (((block->flag & UI_BLOCK_LOOP) && !ui_block_is_popover(block) && | |
!(block->flag & UI_BLOCK_QUICK_SETUP)) || | |
ELEM(but->type, | |
UI_BTYPE_MENU, | |
UI_BTYPE_TEXT, | |
UI_BTYPE_LABEL, | |
UI_BTYPE_BLOCK, | |
UI_BTYPE_BUT_MENU, | |
UI_BTYPE_SEARCH_MENU, | |
UI_BTYPE_POPOVER)) | |
{ | |
but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); | |
} | |
#ifdef USE_NUMBUTS_LR_ALIGN | |
else if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { | |
if (slen != 0) { | |
but->drawflag |= UI_BUT_TEXT_LEFT; | |
} | |
} | |
#endif | |
but->drawflag |= (block->flag & UI_BUT_ALIGN); | |
if (block->lock == true) { | |
but->flag |= UI_BUT_DISABLED; | |
} | |
/* keep track of UI_interface.hh */ | |
if (ELEM(but->type, | |
UI_BTYPE_BLOCK, | |
UI_BTYPE_BUT, | |
UI_BTYPE_DECORATOR, | |
UI_BTYPE_LABEL, | |
UI_BTYPE_PULLDOWN, | |
UI_BTYPE_ROUNDBOX, | |
UI_BTYPE_LISTBOX, | |
UI_BTYPE_BUT_MENU, | |
UI_BTYPE_SCROLL, | |
UI_BTYPE_GRIP, | |
UI_BTYPE_SEPR, | |
UI_BTYPE_SEPR_LINE, | |
UI_BTYPE_SEPR_SPACER) || | |
(but->type >= UI_BTYPE_SEARCH_MENU)) | |
{ | |
/* pass */ | |
} | |
else { | |
but->flag |= UI_BUT_UNDO; | |
} | |
if (ELEM(but->type, UI_BTYPE_COLOR)) { | |
but->dragflag |= UI_BUT_DRAG_FULL_BUT; | |
} | |
BLI_addtail(&block->buttons, but); | |
if (block->curlayout) { | |
ui_layout_add_but(block->curlayout, but); | |
} | |
#ifdef WITH_PYTHON | |
/* If the 'UI_OT_editsource' is running, extract the source info from the button. */ | |
if (UI_editsource_enable_check()) { | |
UI_editsource_active_but_test(but); | |
} | |
#endif | |
return but; | |
} | |
void ui_def_but_icon(uiBut *but, const int icon, const int flag) | |
{ | |
if (icon) { | |
ui_icon_ensure_deferred(static_cast<const bContext *>(but->block->evil_C), | |
icon, | |
(flag & UI_BUT_ICON_PREVIEW) != 0); | |
} | |
but->icon = icon; | |
but->flag |= flag; | |
if (but->str && but->str[0]) { | |
but->drawflag |= UI_BUT_ICON_LEFT; | |
} | |
} | |
void ui_def_but_icon_clear(uiBut *but) | |
{ | |
but->icon = ICON_NONE; | |
but->flag &= ~UI_HAS_ICON; | |
but->drawflag &= ~UI_BUT_ICON_LEFT; | |
} | |
static void ui_def_but_rna__menu(bContext * /*C*/, uiLayout *layout, void *but_p) | |
{ | |
uiBlock *block = uiLayoutGetBlock(layout); | |
uiPopupBlockHandle *handle = block->handle; | |
uiBut *but = (uiBut *)but_p; | |
const int current_value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); | |
/* see comment in ui_item_enum_expand, re: `uiname`. */ | |
const EnumPropertyItem *item_array; | |
UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT); | |
bool free; | |
RNA_property_enum_items_gettexted(static_cast<bContext *>(block->evil_C), | |
&but->rnapoin, | |
but->rnaprop, | |
&item_array, | |
nullptr, | |
&free); | |
/* We don't want nested rows, cols in menus. */ | |
UI_block_layout_set_current(block, layout); | |
int totitems = 0; | |
int categories = 0; | |
int entries_nosepr_count = 0; | |
bool has_item_with_icon = false; | |
for (const EnumPropertyItem *item = item_array; item->identifier; item++, totitems++) { | |
if (!item->identifier[0]) { | |
/* inconsistent, but menus with categories do not look good flipped */ | |
if (item->name) { | |
categories++; | |
entries_nosepr_count++; | |
} | |
/* We do not want simple separators in `entries_nosepr_count`. */ | |
continue; | |
} | |
if (item->icon) { | |
has_item_with_icon = true; | |
} | |
entries_nosepr_count++; | |
} | |
/* Columns and row estimation. Ignore simple separators here. */ | |
int columns = (entries_nosepr_count + 20) / 20; | |
if (columns < 1) { | |
columns = 1; | |
} | |
if (columns > 8) { | |
columns = (entries_nosepr_count + 25) / 25; | |
} | |
int rows = totitems / columns; | |
if (rows < 1) { | |
rows = 1; | |
} | |
while (rows * columns < totitems) { | |
rows++; | |
} | |
const char *title = RNA_property_ui_name(but->rnaprop); | |
/* Is there a non-blank label before this button on the same row? */ | |
const bool prior_label = but->prev && but->prev->type == UI_BTYPE_LABEL && but->prev->str[0] && | |
but->prev->alignnr == but->alignnr; | |
if (title[0] && (categories == 0) && (!but->str[0] || !prior_label)) { | |
/* Show title when no categories and calling button has no text or prior label. */ | |
uiDefBut(block, | |
UI_BTYPE_LABEL, | |
0, | |
title, | |
0, | |
0, | |
UI_UNIT_X * 5, | |
UI_UNIT_Y, | |
nullptr, | |
0.0, | |
0.0, | |
0, | |
0, | |
""); | |
uiItemS(layout); | |
} | |
/* NOTE: `item_array[...]` is reversed on access. */ | |
/* create items */ | |
uiLayout *split = uiLayoutSplit(layout, 0.0f, false); | |
bool new_column; | |
int column_end = 0; | |
uiLayout *column = nullptr; | |
for (int a = 0; a < totitems; a++) { | |
new_column = (a == column_end); | |
if (new_column) { | |
/* start new column, and find out where it ends in advance, so we | |
* can flip the order of items properly per column */ | |
column_end = totitems; | |
for (int b = a + 1; b < totitems; b++) { | |
const EnumPropertyItem *item = &item_array[b]; | |
/* new column on N rows or on separation label */ | |
if (((b - a) % rows == 0) || (!item->identifier[0] && item->name)) { | |
column_end = b; | |
break; | |
} | |
} | |
column = uiLayoutColumn(split, false); | |
} | |
const EnumPropertyItem *item = &item_array[a]; | |
if (new_column && (categories > 0) && item->identifier[0]) { | |
uiItemL(column, "", ICON_NONE); | |
uiItemS(column); | |
} | |
if (!item->identifier[0]) { | |
if (item->name) { | |
if (item->icon) { | |
uiItemL(column, item->name, item->icon); | |
} | |
else { | |
/* Do not use uiItemL here, as our root layout is a menu one, | |
* it will add a fake blank icon! */ | |
uiDefBut(block, | |
UI_BTYPE_LABEL, | |
0, | |
item->name, | |
0, | |
0, | |
UI_UNIT_X * 5, | |
UI_UNIT_Y, | |
nullptr, | |
0.0, | |
0.0, | |
0, | |
0, | |
""); | |
} | |
} | |
uiItemS(column); | |
} | |
else { | |
int icon = item->icon; | |
/* Use blank icon if there is none for this item (but for some other one) to make sure labels | |
* align. */ | |
if (icon == ICON_NONE && has_item_with_icon) { | |
icon = ICON_BLANK1; | |
} | |
uiBut *item_but; | |
if (icon) { | |
item_but = uiDefIconTextButI(block, | |
UI_BTYPE_BUT_MENU, | |
B_NOP, | |
icon, | |
item->name, | |
0, | |
0, | |
UI_UNIT_X * 5, | |
UI_UNIT_Y, | |
&handle->retvalue, | |
item->value, | |
0.0, | |
0, | |
-1, | |
item->description); | |
} | |
else { | |
item_but = uiDefButI(block, | |
UI_BTYPE_BUT_MENU, | |
B_NOP, | |
item->name, | |
0, | |
0, | |
UI_UNIT_X * 5, | |
UI_UNIT_X, | |
&handle->retvalue, | |
item->value, | |
0.0, | |
0, | |
-1, | |
item->description); | |
} | |
if (item->value == current_value) { | |
item_but->flag |= UI_SELECT_DRAW; | |
} | |
} | |
} | |
UI_block_layout_set_current(block, layout); | |
if (free) { | |
MEM_freeN((void *)item_array); | |
} | |
} | |
static void ui_def_but_rna__panel_type(bContext *C, uiLayout *layout, void *but_p) | |
{ | |
uiBut *but = static_cast<uiBut *>(but_p); | |
const char *panel_type = static_cast<const char *>(but->func_argN); | |
PanelType *pt = WM_paneltype_find(panel_type, true); | |
if (pt) { | |
ui_item_paneltype_func(C, layout, pt); | |
} | |
else { | |
char msg[256]; | |
SNPRINTF(msg, TIP_("Missing Panel: %s"), panel_type); | |
uiItemL(layout, msg, ICON_NONE); | |
} | |
} | |
void ui_but_rna_menu_convert_to_panel_type(uiBut *but, const char *panel_type) | |
{ | |
BLI_assert(ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_COLOR)); | |
// BLI_assert(but->menu_create_func == ui_def_but_rna__menu); | |
// BLI_assert((void *)but->poin == but); | |
but->menu_create_func = ui_def_but_rna__panel_type; | |
but->func_argN = BLI_strdup(panel_type); | |
} | |
bool ui_but_menu_draw_as_popover(const uiBut *but) | |
{ | |
return (but->menu_create_func == ui_def_but_rna__panel_type); | |
} | |
static void ui_def_but_rna__menu_type(bContext *C, uiLayout *layout, void *but_p) | |
{ | |
uiBut *but = static_cast<uiBut *>(but_p); | |
const char *menu_type = static_cast<const char *>(but->func_argN); | |
MenuType *mt = WM_menutype_find(menu_type, true); | |
if (mt) { | |
ui_item_menutype_func(C, layout, mt); | |
} | |
else { | |
char msg[256]; | |
SNPRINTF(msg, TIP_("Missing Menu: %s"), menu_type); | |
uiItemL(layout, msg, ICON_NONE); | |
} | |
} | |
void ui_but_rna_menu_convert_to_menu_type(uiBut *but, const char *menu_type) | |
{ | |
BLI_assert(but->type == UI_BTYPE_MENU); | |
BLI_assert(but->menu_create_func == ui_def_but_rna__menu); | |
BLI_assert((void *)but->poin == but); | |
but->menu_create_func = ui_def_but_rna__menu_type; | |
but->func_argN = BLI_strdup(menu_type); | |
} | |
static void ui_but_submenu_enable(uiBlock *block, uiBut *but) | |
{ | |
but->flag |= UI_BUT_ICON_SUBMENU; | |
block->content_hints |= UI_BLOCK_CONTAINS_SUBMENU_BUT; | |
} | |
/** | |
* ui_def_but_rna_propname and ui_def_but_rna | |
* both take the same args except for propname vs prop, this is done so we can | |
* avoid an extra lookup on 'prop' when its already available. | |
* | |
* When this kind of change won't disrupt branches, best look into making more | |
* of our UI functions take prop rather than propname. | |
*/ | |
static uiBut *ui_def_but_rna(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
PointerRNA *ptr, | |
PropertyRNA *prop, | |
int index, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
const PropertyType proptype = RNA_property_type(prop); | |
int icon = 0; | |
uiMenuCreateFunc func = nullptr; | |
const bool always_set_a1_a2 = ELEM(type, UI_BTYPE_NUM); | |
if (ELEM(type, UI_BTYPE_COLOR, UI_BTYPE_HSVCIRCLE, UI_BTYPE_HSVCUBE)) { | |
BLI_assert(index == -1); | |
} | |
/* use rna values if parameters are not specified */ | |
if ((proptype == PROP_ENUM) && ELEM(type, UI_BTYPE_MENU, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) { | |
bool free; | |
const EnumPropertyItem *item; | |
RNA_property_enum_items( | |
static_cast<bContext *>(block->evil_C), ptr, prop, &item, nullptr, &free); | |
int value; | |
/* UI_BTYPE_MENU is handled a little differently here */ | |
if (type == UI_BTYPE_MENU) { | |
value = RNA_property_enum_get(ptr, prop); | |
} | |
else { | |
value = int(max); | |
} | |
const int i = RNA_enum_from_value(item, value); | |
if (i != -1) { | |
if (!str) { | |
str = item[i].name; | |
#ifdef WITH_INTERNATIONAL | |
str = CTX_IFACE_(RNA_property_translation_context(prop), str); | |
#endif | |
} | |
icon = item[i].icon; | |
} | |
else { | |
if (!str) { | |
if (type == UI_BTYPE_MENU) { | |
str = ""; | |
} | |
else { | |
str = RNA_property_ui_name(prop); | |
} | |
} | |
} | |
if (type == UI_BTYPE_MENU) { | |
func = ui_def_but_rna__menu; | |
} | |
if (free) { | |
MEM_freeN((void *)item); | |
} | |
} | |
else { | |
if (!str) { | |
str = RNA_property_ui_name(prop); | |
} | |
icon = RNA_property_ui_icon(prop); | |
} | |
if (!tip && proptype != PROP_ENUM) { | |
tip = RNA_property_ui_description(prop); | |
} | |
if (min == max || a1 == -1 || a2 == -1 || always_set_a1_a2) { | |
if (proptype == PROP_INT) { | |
int hardmin, hardmax, softmin, softmax, step; | |
RNA_property_int_range(ptr, prop, &hardmin, &hardmax); | |
RNA_property_int_ui_range(ptr, prop, &softmin, &softmax, &step); | |
if (!ELEM(type, UI_BTYPE_ROW, UI_BTYPE_LISTROW) && min == max) { | |
min = hardmin; | |
max = hardmax; | |
} | |
if (a1 == -1 || always_set_a1_a2) { | |
a1 = step; | |
} | |
if (a2 == -1 || always_set_a1_a2) { | |
a2 = 0; | |
} | |
} | |
else if (proptype == PROP_FLOAT) { | |
float hardmin, hardmax, softmin, softmax, step, precision; | |
RNA_property_float_range(ptr, prop, &hardmin, &hardmax); | |
RNA_property_float_ui_range(ptr, prop, &softmin, &softmax, &step, &precision); | |
if (!ELEM(type, UI_BTYPE_ROW, UI_BTYPE_LISTROW) && min == max) { | |
min = hardmin; | |
max = hardmax; | |
} | |
if (a1 == -1 || always_set_a1_a2) { | |
a1 = step; | |
} | |
if (a2 == -1 || always_set_a1_a2) { | |
a2 = precision; | |
} | |
} | |
else if (proptype == PROP_STRING) { | |
min = 0; | |
max = RNA_property_string_maxlength(prop); | |
/* NOTE: 'max' may be zero (code for dynamically resized array). */ | |
} | |
} | |
/* now create button */ | |
uiBut *but = ui_def_but( | |
block, type, retval, str, x, y, width, height, nullptr, min, max, a1, a2, tip); | |
if (but->type == UI_BTYPE_NUM) { | |
/* Set default values, can be overridden later. */ | |
UI_but_number_step_size_set(but, a1); | |
UI_but_number_precision_set(but, a2); | |
} | |
but->rnapoin = *ptr; | |
but->rnaprop = prop; | |
if (RNA_property_array_check(but->rnaprop)) { | |
but->rnaindex = index; | |
} | |
else { | |
but->rnaindex = 0; | |
} | |
if (icon) { | |
ui_def_but_icon(but, icon, UI_HAS_ICON); | |
} | |
if (type == UI_BTYPE_MENU) { | |
if (but->emboss == UI_EMBOSS_PULLDOWN) { | |
ui_but_submenu_enable(block, but); | |
} | |
} | |
else if (type == UI_BTYPE_SEARCH_MENU) { | |
if (proptype == PROP_POINTER) { | |
/* Search buttons normally don't get undo, see: #54580. */ | |
but->flag |= UI_BUT_UNDO; | |
} | |
} | |
const char *info; | |
if (but->rnapoin.data && !RNA_property_editable_info(&but->rnapoin, prop, &info)) { | |
UI_but_disable(but, info); | |
} | |
if (proptype == PROP_POINTER) { | |
/* If the button shows an ID, automatically set it as focused in context so operators can | |
* access it. */ | |
const PointerRNA pptr = RNA_property_pointer_get(ptr, prop); | |
if (pptr.data && RNA_struct_is_ID(pptr.type)) { | |
but->context = CTX_store_add(block->contexts, "id", &pptr); | |
} | |
} | |
if (but->flag & UI_BUT_UNDO && (ui_but_is_rna_undo(but) == false)) { | |
but->flag &= ~UI_BUT_UNDO; | |
} | |
/* If this button uses units, calculate the step from this */ | |
if ((proptype == PROP_FLOAT) && ui_but_is_unit(but)) { | |
if (type == UI_BTYPE_NUM) { | |
uiButNumber *number_but = (uiButNumber *)but; | |
number_but->step_size = ui_get_but_step_unit(but, number_but->step_size); | |
} | |
else { | |
but->a1 = ui_get_but_step_unit(but, but->a1); | |
} | |
} | |
if (func) { | |
but->menu_create_func = func; | |
but->poin = (char *)but; | |
} | |
return but; | |
} | |
static uiBut *ui_def_but_rna_propname(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
PointerRNA *ptr, | |
const char *propname, | |
int index, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
PropertyRNA *prop = RNA_struct_find_property(ptr, propname); | |
uiBut *but; | |
if (prop) { | |
but = ui_def_but_rna( | |
block, type, retval, str, x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); | |
} | |
else { | |
but = ui_def_but( | |
block, type, retval, propname, x, y, width, height, nullptr, min, max, a1, a2, tip); | |
UI_but_disable(but, "Unknown Property."); | |
} | |
return but; | |
} | |
static uiBut *ui_def_but_operator_ptr(uiBlock *block, | |
int type, | |
wmOperatorType *ot, | |
wmOperatorCallContext opcontext, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
std::string operator_name; | |
if (!str) { | |
if (ot && ot->srna) { | |
operator_name = WM_operatortype_name(ot, nullptr); | |
str = operator_name.c_str(); | |
} | |
else { | |
str = ""; | |
} | |
} | |
if ((!tip || tip[0] == '\0') && ot && ot->srna && !ot->get_description) { | |
tip = RNA_struct_ui_description(ot->srna); | |
} | |
uiBut *but = ui_def_but(block, type, -1, str, x, y, width, height, nullptr, 0, 0, 0, 0, tip); | |
but->optype = ot; | |
but->opcontext = opcontext; | |
but->flag &= ~UI_BUT_UNDO; /* no need for ui_but_is_rna_undo(), we never need undo here */ | |
const bool has_label = str && str[0]; | |
/* Enable quick tooltip label if this is a tool button without a label. */ | |
if (!has_label && !ui_block_is_popover(block) && UI_but_is_tool(but)) { | |
UI_but_drawflag_enable(but, UI_BUT_HAS_TOOLTIP_LABEL); | |
} | |
if (!ot) { | |
UI_but_disable(but, ""); | |
} | |
return but; | |
} | |
uiBut *uiDefBut(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
void *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, type, retval, str, x, y, width, height, poin, min, max, a1, a2, tip); | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefButImage( | |
uiBlock *block, void *imbuf, int x, int y, short width, short height, const uchar color[4]) | |
{ | |
uiBut *but = ui_def_but( | |
block, UI_BTYPE_IMAGE, 0, "", x, y, width, height, imbuf, 0, 0, 0, 0, ""); | |
if (color) { | |
copy_v4_v4_uchar(but->col, color); | |
} | |
else { | |
but->col[0] = 255; | |
but->col[1] = 255; | |
but->col[2] = 255; | |
but->col[3] = 255; | |
} | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefButAlert(uiBlock *block, int icon, int x, int y, short width, short height) | |
{ | |
ImBuf *ibuf = UI_icon_alert_imbuf_get((eAlertIcon)icon); | |
bTheme *btheme = UI_GetTheme(); | |
return uiDefButImage(block, ibuf, x, y, width, height, btheme->tui.wcol_menu_back.text); | |
} | |
/** | |
* if \a _x_ is a power of two (only one bit) return the power, | |
* otherwise return -1. | |
* | |
* for powers of two: | |
* \code{.c} | |
* ((1 << findBitIndex(x)) == x); | |
* \endcode | |
*/ | |
static int findBitIndex(uint x) | |
{ | |
if (!x || !is_power_of_2_i(x)) { /* is_power_of_2_i(x) strips lowest bit */ | |
return -1; | |
} | |
int idx = 0; | |
if (x & 0xFFFF0000) { | |
idx += 16; | |
x >>= 16; | |
} | |
if (x & 0xFF00) { | |
idx += 8; | |
x >>= 8; | |
} | |
if (x & 0xF0) { | |
idx += 4; | |
x >>= 4; | |
} | |
if (x & 0xC) { | |
idx += 2; | |
x >>= 2; | |
} | |
if (x & 0x2) { | |
idx += 1; | |
} | |
return idx; | |
} | |
/* Auto-complete helper functions. */ | |
struct AutoComplete { | |
size_t maxncpy; | |
int matches; | |
char *truncate; | |
const char *startname; | |
}; | |
AutoComplete *UI_autocomplete_begin(const char *startname, size_t maxncpy) | |
{ | |
AutoComplete *autocpl; | |
autocpl = MEM_cnew<AutoComplete>(__func__); | |
autocpl->maxncpy = maxncpy; | |
autocpl->matches = 0; | |
autocpl->truncate = static_cast<char *>(MEM_callocN(sizeof(char) * maxncpy, __func__)); | |
autocpl->startname = startname; | |
return autocpl; | |
} | |
void UI_autocomplete_update_name(AutoComplete *autocpl, const char *name) | |
{ | |
char *truncate = autocpl->truncate; | |
const char *startname = autocpl->startname; | |
int match_index = 0; | |
for (int a = 0; a < autocpl->maxncpy - 1; a++) { | |
if (startname[a] == 0 || startname[a] != name[a]) { | |
match_index = a; | |
break; | |
} | |
} | |
/* found a match */ | |
if (startname[match_index] == 0) { | |
autocpl->matches++; | |
/* first match */ | |
if (truncate[0] == 0) { | |
BLI_strncpy(truncate, name, autocpl->maxncpy); | |
} | |
else { | |
/* remove from truncate what is not in bone->name */ | |
for (int a = 0; a < autocpl->maxncpy - 1; a++) { | |
if (name[a] == 0) { | |
truncate[a] = 0; | |
break; | |
} | |
if (truncate[a] != name[a]) { | |
truncate[a] = 0; | |
} | |
} | |
} | |
} | |
} | |
int UI_autocomplete_end(AutoComplete *autocpl, char *autoname) | |
{ | |
int match = AUTOCOMPLETE_NO_MATCH; | |
if (autocpl->truncate[0]) { | |
if (autocpl->matches == 1) { | |
match = AUTOCOMPLETE_FULL_MATCH; | |
} | |
else { | |
match = AUTOCOMPLETE_PARTIAL_MATCH; | |
} | |
BLI_strncpy(autoname, autocpl->truncate, autocpl->maxncpy); | |
} | |
else { | |
if (autoname != autocpl->startname) { /* don't copy a string over itself */ | |
BLI_strncpy(autoname, autocpl->startname, autocpl->maxncpy); | |
} | |
} | |
MEM_freeN(autocpl->truncate); | |
MEM_freeN(autocpl); | |
return match; | |
} | |
#define PREVIEW_TILE_PAD (0.15f * UI_UNIT_X) | |
int UI_preview_tile_size_x(const int size_px) | |
{ | |
const float pad = PREVIEW_TILE_PAD; | |
return round_fl_to_int((size_px / 20.0f) * UI_UNIT_X + 2.0f * pad); | |
} | |
int UI_preview_tile_size_y(const int size_px) | |
{ | |
const float font_height = UI_UNIT_Y; | |
/* Add some extra padding to make things less tight vertically. */ | |
const float pad = PREVIEW_TILE_PAD; | |
return round_fl_to_int(UI_preview_tile_size_y_no_label(size_px) + font_height + pad); | |
} | |
int UI_preview_tile_size_y_no_label(const int size_px) | |
{ | |
const float pad = PREVIEW_TILE_PAD; | |
return round_fl_to_int((size_px / 20.0f) * UI_UNIT_Y + 2.0f * pad); | |
} | |
#undef PREVIEW_TILE_PAD | |
static void ui_but_update_and_icon_set(uiBut *but, int icon) | |
{ | |
if (icon) { | |
ui_def_but_icon(but, icon, UI_HAS_ICON); | |
} | |
ui_but_update(but); | |
} | |
static uiBut *uiDefButBit(uiBlock *block, | |
int type, | |
int bit, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
void *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
const int bitIdx = findBitIndex(bit); | |
if (bitIdx == -1) { | |
return nullptr; | |
} | |
return uiDefBut(block, | |
type | UI_BUT_POIN_BIT | bitIdx, | |
retval, | |
str, | |
x, | |
y, | |
width, | |
height, | |
poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefButF(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
float *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefBut(block, | |
type | UI_BUT_POIN_FLOAT, | |
retval, | |
str, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefButI(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
int *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefBut(block, | |
type | UI_BUT_POIN_INT, | |
retval, | |
str, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefButBitI(uiBlock *block, | |
int type, | |
int bit, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
int *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefButBit(block, | |
type | UI_BUT_POIN_INT, | |
bit, | |
retval, | |
str, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefButS(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
short *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefBut(block, | |
type | UI_BUT_POIN_SHORT, | |
retval, | |
str, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefButBitS(uiBlock *block, | |
int type, | |
int bit, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
short *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefButBit(block, | |
type | UI_BUT_POIN_SHORT, | |
bit, | |
retval, | |
str, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefButC(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
char *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefBut(block, | |
type | UI_BUT_POIN_CHAR, | |
retval, | |
str, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefButBitC(uiBlock *block, | |
int type, | |
int bit, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
char *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefButBit(block, | |
type | UI_BUT_POIN_CHAR, | |
bit, | |
retval, | |
str, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefButR(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
PointerRNA *ptr, | |
const char *propname, | |
int index, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but_rna_propname( | |
block, type, retval, str, x, y, width, height, ptr, propname, index, min, max, a1, a2, tip); | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefButR_prop(uiBlock *block, | |
int type, | |
int retval, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
PointerRNA *ptr, | |
PropertyRNA *prop, | |
int index, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but_rna( | |
block, type, retval, str, x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefButO_ptr(uiBlock *block, | |
int type, | |
wmOperatorType *ot, | |
wmOperatorCallContext opcontext, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but_operator_ptr(block, type, ot, opcontext, str, x, y, width, height, tip); | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefButO(uiBlock *block, | |
int type, | |
const char *opname, | |
wmOperatorCallContext opcontext, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
wmOperatorType *ot = WM_operatortype_find(opname, false); | |
if (str == nullptr && ot == nullptr) { | |
str = opname; | |
} | |
return uiDefButO_ptr(block, type, ot, opcontext, str, x, y, width, height, tip); | |
} | |
uiBut *uiDefIconBut(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
void *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, type, retval, "", x, y, width, height, poin, min, max, a1, a2, tip); | |
ui_but_update_and_icon_set(but, icon); | |
return but; | |
} | |
static uiBut *uiDefIconButBit(uiBlock *block, | |
int type, | |
int bit, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
void *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
const int bitIdx = findBitIndex(bit); | |
if (bitIdx == -1) { | |
return nullptr; | |
} | |
return uiDefIconBut(block, | |
type | UI_BUT_POIN_BIT | bitIdx, | |
retval, | |
icon, | |
x, | |
y, | |
width, | |
height, | |
poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefIconButI(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
int *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefIconBut(block, | |
type | UI_BUT_POIN_INT, | |
retval, | |
icon, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefIconButBitI(uiBlock *block, | |
int type, | |
int bit, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
int *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefIconButBit(block, | |
type | UI_BUT_POIN_INT, | |
bit, | |
retval, | |
icon, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefIconButS(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
short *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefIconBut(block, | |
type | UI_BUT_POIN_SHORT, | |
retval, | |
icon, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefIconButBitS(uiBlock *block, | |
int type, | |
int bit, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
short *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefIconButBit(block, | |
type | UI_BUT_POIN_SHORT, | |
bit, | |
retval, | |
icon, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefIconButBitC(uiBlock *block, | |
int type, | |
int bit, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
char *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefIconButBit(block, | |
type | UI_BUT_POIN_CHAR, | |
bit, | |
retval, | |
icon, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefIconButR(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
PointerRNA *ptr, | |
const char *propname, | |
int index, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but_rna_propname( | |
block, type, retval, "", x, y, width, height, ptr, propname, index, min, max, a1, a2, tip); | |
ui_but_update_and_icon_set(but, icon); | |
return but; | |
} | |
uiBut *uiDefIconButR_prop(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
PointerRNA *ptr, | |
PropertyRNA *prop, | |
int index, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but_rna( | |
block, type, retval, "", x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); | |
ui_but_update_and_icon_set(but, icon); | |
return but; | |
} | |
uiBut *uiDefIconButO_ptr(uiBlock *block, | |
int type, | |
wmOperatorType *ot, | |
wmOperatorCallContext opcontext, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but_operator_ptr(block, type, ot, opcontext, "", x, y, width, height, tip); | |
ui_but_update_and_icon_set(but, icon); | |
return but; | |
} | |
uiBut *uiDefIconButO(uiBlock *block, | |
int type, | |
const char *opname, | |
wmOperatorCallContext opcontext, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
wmOperatorType *ot = WM_operatortype_find(opname, false); | |
return uiDefIconButO_ptr(block, type, ot, opcontext, icon, x, y, width, height, tip); | |
} | |
uiBut *uiDefIconTextBut(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
void *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, type, retval, str, x, y, width, height, poin, min, max, a1, a2, tip); | |
ui_but_update_and_icon_set(but, icon); | |
but->drawflag |= UI_BUT_ICON_LEFT; | |
return but; | |
} | |
uiBut *uiDefIconTextButF(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
float *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefIconTextBut(block, | |
type | UI_BUT_POIN_FLOAT, | |
retval, | |
icon, | |
str, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefIconTextButI(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
int *poin, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
return uiDefIconTextBut(block, | |
type | UI_BUT_POIN_INT, | |
retval, | |
icon, | |
str, | |
x, | |
y, | |
width, | |
height, | |
(void *)poin, | |
min, | |
max, | |
a1, | |
a2, | |
tip); | |
} | |
uiBut *uiDefIconTextButR(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
PointerRNA *ptr, | |
const char *propname, | |
int index, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but_rna_propname( | |
block, type, retval, str, x, y, width, height, ptr, propname, index, min, max, a1, a2, tip); | |
ui_but_update_and_icon_set(but, icon); | |
but->drawflag |= UI_BUT_ICON_LEFT; | |
return but; | |
} | |
uiBut *uiDefIconTextButR_prop(uiBlock *block, | |
int type, | |
int retval, | |
int icon, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
PointerRNA *ptr, | |
PropertyRNA *prop, | |
int index, | |
float min, | |
float max, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but_rna( | |
block, type, retval, str, x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); | |
ui_but_update_and_icon_set(but, icon); | |
but->drawflag |= UI_BUT_ICON_LEFT; | |
return but; | |
} | |
uiBut *uiDefIconTextButO_ptr(uiBlock *block, | |
int type, | |
wmOperatorType *ot, | |
wmOperatorCallContext opcontext, | |
int icon, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but_operator_ptr(block, type, ot, opcontext, str, x, y, width, height, tip); | |
ui_but_update_and_icon_set(but, icon); | |
but->drawflag |= UI_BUT_ICON_LEFT; | |
return but; | |
} | |
uiBut *uiDefIconTextButO(uiBlock *block, | |
int type, | |
const char *opname, | |
wmOperatorCallContext opcontext, | |
int icon, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
wmOperatorType *ot = WM_operatortype_find(opname, false); | |
if (str && str[0] == '\0') { | |
return uiDefIconButO_ptr(block, type, ot, opcontext, icon, x, y, width, height, tip); | |
} | |
return uiDefIconTextButO_ptr(block, type, ot, opcontext, icon, str, x, y, width, height, tip); | |
} | |
/* END Button containing both string label and icon */ | |
/* cruft to make uiBlock and uiBut private */ | |
int UI_blocklist_min_y_get(ListBase *lb) | |
{ | |
int min = 0; | |
LISTBASE_FOREACH (uiBlock *, block, lb) { | |
if (block == lb->first || block->rect.ymin < min) { | |
min = block->rect.ymin; | |
} | |
} | |
return min; | |
} | |
void UI_block_direction_set(uiBlock *block, char direction) | |
{ | |
block->direction = direction; | |
} | |
void UI_block_flag_enable(uiBlock *block, int flag) | |
{ | |
block->flag |= flag; | |
} | |
void UI_block_flag_disable(uiBlock *block, int flag) | |
{ | |
block->flag &= ~flag; | |
} | |
void UI_but_flag_enable(uiBut *but, int flag) | |
{ | |
but->flag |= flag; | |
} | |
void UI_but_flag2_enable(uiBut *but, int flag) | |
{ | |
but->flag2 |= flag; | |
} | |
void UI_but_flag_disable(uiBut *but, int flag) | |
{ | |
but->flag &= ~flag; | |
} | |
bool UI_but_flag_is_set(uiBut *but, int flag) | |
{ | |
return (but->flag & flag) != 0; | |
} | |
void UI_but_drawflag_enable(uiBut *but, int flag) | |
{ | |
but->drawflag |= flag; | |
} | |
void UI_but_drawflag_disable(uiBut *but, int flag) | |
{ | |
but->drawflag &= ~flag; | |
} | |
void UI_but_dragflag_enable(uiBut *but, int flag) | |
{ | |
but->dragflag |= flag; | |
} | |
void UI_but_dragflag_disable(uiBut *but, int flag) | |
{ | |
but->dragflag &= ~flag; | |
} | |
void UI_but_disable(uiBut *but, const char *disabled_hint) | |
{ | |
UI_but_flag_enable(but, UI_BUT_DISABLED); | |
/* Only one disabled hint at a time currently. Don't override the previous one here. */ | |
if (but->disabled_info && but->disabled_info[0]) { | |
return; | |
} | |
but->disabled_info = disabled_hint; | |
} | |
void UI_but_placeholder_set(uiBut *but, const char *placeholder_text) | |
{ | |
MEM_SAFE_FREE(but->placeholder); | |
but->placeholder = BLI_strdup_null(placeholder_text); | |
} | |
const char *ui_but_placeholder_get(uiBut *but) | |
{ | |
const char *placeholder = (but->placeholder) ? but->placeholder : nullptr; | |
if (!placeholder && but->rnaprop) { | |
if (but->type == UI_BTYPE_SEARCH_MENU) { | |
StructRNA *type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); | |
const short idcode = RNA_type_to_ID_code(type); | |
if (idcode != 0) { | |
RNA_enum_name(rna_enum_id_type_items, idcode, &placeholder); | |
placeholder = CTX_IFACE_(BLT_I18NCONTEXT_ID_ID, placeholder); | |
} | |
else if (type && !STREQ(RNA_struct_identifier(type), "UnknownType")) { | |
placeholder = RNA_struct_ui_name(type); | |
} | |
} | |
else if (but->type == UI_BTYPE_TEXT && but->icon == ICON_VIEWZOOM) { | |
placeholder = CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "Search"); | |
} | |
} | |
return placeholder; | |
} | |
void UI_but_type_set_menu_from_pulldown(uiBut *but) | |
{ | |
BLI_assert(but->type == UI_BTYPE_PULLDOWN); | |
but->type = UI_BTYPE_MENU; | |
UI_but_drawflag_disable(but, UI_BUT_TEXT_RIGHT); | |
UI_but_drawflag_enable(but, UI_BUT_TEXT_LEFT); | |
} | |
int UI_but_return_value_get(uiBut *but) | |
{ | |
return but->retval; | |
} | |
PointerRNA *UI_but_operator_ptr_get(uiBut *but) | |
{ | |
if (but->optype && !but->opptr) { | |
but->opptr = MEM_cnew<PointerRNA>(__func__); | |
WM_operator_properties_create_ptr(but->opptr, but->optype); | |
} | |
return but->opptr; | |
} | |
void UI_but_context_ptr_set(uiBlock *block, uiBut *but, const char *name, const PointerRNA *ptr) | |
{ | |
bContextStore *ctx = CTX_store_add(block->contexts, name, ptr); | |
ctx->used = true; | |
but->context = ctx; | |
} | |
const PointerRNA *UI_but_context_ptr_get(const uiBut *but, const char *name, const StructRNA *type) | |
{ | |
return CTX_store_ptr_lookup(but->context, name, type); | |
} | |
const bContextStore *UI_but_context_get(const uiBut *but) | |
{ | |
return but->context; | |
} | |
void UI_but_unit_type_set(uiBut *but, const int unit_type) | |
{ | |
but->unit_type = uchar(RNA_SUBTYPE_UNIT_VALUE(unit_type)); | |
} | |
int UI_but_unit_type_get(const uiBut *but) | |
{ | |
const int ownUnit = int(but->unit_type); | |
/* own unit define always takes precedence over RNA provided, allowing for overriding | |
* default value provided in RNA in a few special cases (i.e. Active Keyframe in Graph Edit) | |
*/ | |
/* XXX: this doesn't allow clearing unit completely, though the same could be said for icons */ | |
if ((ownUnit != 0) || (but->rnaprop == nullptr)) { | |
return ownUnit << 16; | |
} | |
return RNA_SUBTYPE_UNIT(RNA_property_subtype(but->rnaprop)); | |
} | |
void UI_block_func_handle_set(uiBlock *block, uiBlockHandleFunc func, void *arg) | |
{ | |
block->handle_func = func; | |
block->handle_func_arg = arg; | |
} | |
void UI_block_func_butmenu_set(uiBlock *block, uiMenuHandleFunc func, void *arg) | |
{ | |
block->butm_func = func; | |
block->butm_func_arg = arg; | |
} | |
void UI_block_func_set(uiBlock *block, uiButHandleFunc func, void *arg1, void *arg2) | |
{ | |
block->func = func; | |
block->func_arg1 = arg1; | |
block->func_arg2 = arg2; | |
} | |
void UI_block_funcN_set(uiBlock *block, uiButHandleNFunc funcN, void *argN, void *arg2) | |
{ | |
if (block->func_argN) { | |
MEM_freeN(block->func_argN); | |
} | |
block->funcN = funcN; | |
block->func_argN = argN; | |
block->func_arg2 = arg2; | |
} | |
void UI_but_func_rename_set(uiBut *but, uiButHandleRenameFunc func, void *arg1) | |
{ | |
but->rename_func = func; | |
but->rename_arg1 = arg1; | |
} | |
void UI_but_func_drawextra_set( | |
uiBlock *block, | |
void (*func)(const bContext *C, void *idv, void *arg1, void *arg2, rcti *rect), | |
void *arg1, | |
void *arg2) | |
{ | |
block->drawextra = func; | |
block->drawextra_arg1 = arg1; | |
block->drawextra_arg2 = arg2; | |
} | |
void UI_but_func_set(uiBut *but, uiButHandleFunc func, void *arg1, void *arg2) | |
{ | |
but->func = func; | |
but->func_arg1 = arg1; | |
but->func_arg2 = arg2; | |
} | |
void UI_but_func_set(uiBut *but, std::function<void(bContext &)> func) | |
{ | |
but->apply_func = std::move(func); | |
} | |
void UI_but_funcN_set(uiBut *but, uiButHandleNFunc funcN, void *argN, void *arg2) | |
{ | |
if (but->func_argN) { | |
MEM_freeN(but->func_argN); | |
} | |
but->funcN = funcN; | |
but->func_argN = argN; | |
but->func_arg2 = arg2; | |
} | |
void UI_but_func_complete_set(uiBut *but, uiButCompleteFunc func, void *arg) | |
{ | |
but->autocomplete_func = func; | |
but->autofunc_arg = arg; | |
} | |
void UI_but_func_menu_step_set(uiBut *but, uiMenuStepFunc func) | |
{ | |
but->menu_step_func = func; | |
} | |
void UI_but_func_tooltip_label_set(uiBut *but, std::function<std::string(const uiBut *but)> func) | |
{ | |
but->tip_label_func = std::move(func); | |
UI_but_drawflag_enable(but, UI_BUT_HAS_TOOLTIP_LABEL); | |
} | |
void UI_but_func_tooltip_set(uiBut *but, uiButToolTipFunc func, void *arg, uiFreeArgFunc free_arg) | |
{ | |
but->tip_func = func; | |
if (but->tip_arg_free) { | |
but->tip_arg_free(but->tip_arg); | |
} | |
but->tip_arg = arg; | |
but->tip_arg_free = free_arg; | |
} | |
void UI_but_func_tooltip_custom_set(uiBut *but, | |
uiButToolTipCustomFunc func, | |
void *arg, | |
uiFreeArgFunc free_arg) | |
{ | |
but->tip_custom_func = func; | |
if (but->tip_arg_free) { | |
but->tip_arg_free(but->tip_arg); | |
} | |
but->tip_arg = arg; | |
but->tip_arg_free = free_arg; | |
} | |
void UI_but_func_pushed_state_set(uiBut *but, std::function<bool(const uiBut &)> func) | |
{ | |
but->pushed_state_func = func; | |
ui_but_update(but); | |
} | |
uiBut *uiDefBlockBut(uiBlock *block, | |
uiBlockCreateFunc func, | |
void *arg, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); | |
but->block_create_func = func; | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefBlockButN(uiBlock *block, | |
uiBlockCreateFunc func, | |
void *argN, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, nullptr, 0.0, 0.0, 0.0, 0.0, tip); | |
but->block_create_func = func; | |
if (but->func_argN) { | |
MEM_freeN(but->func_argN); | |
} | |
but->func_argN = argN; | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefPulldownBut(uiBlock *block, | |
uiBlockCreateFunc func, | |
void *arg, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); | |
but->block_create_func = func; | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefMenuBut(uiBlock *block, | |
uiMenuCreateFunc func, | |
void *arg, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); | |
but->menu_create_func = func; | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefIconTextMenuBut(uiBlock *block, | |
uiMenuCreateFunc func, | |
void *arg, | |
int icon, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); | |
ui_def_but_icon(but, icon, UI_HAS_ICON); | |
but->drawflag |= UI_BUT_ICON_LEFT; | |
ui_but_submenu_enable(block, but); | |
but->menu_create_func = func; | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefIconMenuBut(uiBlock *block, | |
uiMenuCreateFunc func, | |
void *arg, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, UI_BTYPE_PULLDOWN, 0, "", x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); | |
ui_def_but_icon(but, icon, UI_HAS_ICON); | |
but->drawflag &= ~UI_BUT_ICON_LEFT; | |
but->menu_create_func = func; | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefIconTextBlockBut(uiBlock *block, | |
uiBlockCreateFunc func, | |
void *arg, | |
int icon, | |
const char *str, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); | |
/* XXX temp, old menu calls pass on icon arrow, which is now UI_BUT_ICON_SUBMENU flag */ | |
if (icon != ICON_RIGHTARROW_THIN) { | |
ui_def_but_icon(but, icon, 0); | |
but->drawflag |= UI_BUT_ICON_LEFT; | |
} | |
but->flag |= UI_HAS_ICON; | |
ui_but_submenu_enable(block, but); | |
but->block_create_func = func; | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefIconBlockBut(uiBlock *block, | |
uiBlockCreateFunc func, | |
void *arg, | |
int retval, | |
int icon, | |
int x, | |
int y, | |
short width, | |
short height, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but( | |
block, UI_BTYPE_BLOCK, retval, "", x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); | |
ui_def_but_icon(but, icon, UI_HAS_ICON); | |
but->drawflag |= UI_BUT_ICON_LEFT; | |
but->block_create_func = func; | |
ui_but_update(but); | |
return but; | |
} | |
uiBut *uiDefSearchBut(uiBlock *block, | |
void *arg, | |
int retval, | |
int icon, | |
int maxncpy, | |
int x, | |
int y, | |
short width, | |
short height, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = ui_def_but(block, | |
UI_BTYPE_SEARCH_MENU, | |
retval, | |
"", | |
x, | |
y, | |
width, | |
height, | |
arg, | |
0.0, | |
maxncpy, | |
a1, | |
a2, | |
tip); | |
ui_def_but_icon(but, icon, UI_HAS_ICON); | |
but->drawflag |= UI_BUT_ICON_LEFT | UI_BUT_TEXT_LEFT; | |
ui_but_update(but); | |
return but; | |
} | |
void UI_but_func_search_set(uiBut *but, | |
uiButSearchCreateFn search_create_fn, | |
uiButSearchUpdateFn search_update_fn, | |
void *arg, | |
const bool free_arg, | |
uiFreeArgFunc search_arg_free_fn, | |
uiButHandleFunc search_exec_fn, | |
void *active) | |
{ | |
uiButSearch *search_but = (uiButSearch *)but; | |
BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); | |
/* needed since callers don't have access to internal functions | |
* (as an alternative we could expose it) */ | |
if (search_create_fn == nullptr) { | |
search_create_fn = ui_searchbox_create_generic; | |
} | |
if (search_but->arg_free_fn != nullptr) { | |
search_but->arg_free_fn(search_but->arg); | |
search_but->arg = nullptr; | |
} | |
search_but->popup_create_fn = search_create_fn; | |
search_but->items_update_fn = search_update_fn; | |
search_but->item_active = active; | |
search_but->arg = arg; | |
search_but->arg_free_fn = search_arg_free_fn; | |
if (search_exec_fn) { | |
#ifdef DEBUG | |
if (but->func) { | |
/* watch this, can be cause of much confusion, see: #47691 */ | |
printf("%s: warning, overwriting button callback with search function callback!\n", | |
__func__); | |
} | |
#endif | |
/* Handling will pass the active item as arg2 later, so keep it nullptr here. */ | |
if (free_arg) { | |
UI_but_funcN_set(but, search_exec_fn, search_but->arg, nullptr); | |
} | |
else { | |
UI_but_func_set(but, search_exec_fn, search_but->arg, nullptr); | |
} | |
} | |
/* search buttons show red-alert if item doesn't exist, not for menus. Don't do this for | |
* buttons where any result is valid anyway, since any string will be valid anyway. */ | |
if (0 == (but->block->flag & UI_BLOCK_LOOP) && !search_but->results_are_suggestions) { | |
/* skip empty buttons, not all buttons need input, we only show invalid */ | |
if (but->drawstr[0]) { | |
ui_but_search_refresh(search_but); | |
} | |
} | |
} | |
void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn) | |
{ | |
uiButSearch *but_search = (uiButSearch *)but; | |
BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); | |
but_search->item_context_menu_fn = context_menu_fn; | |
} | |
void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string) | |
{ | |
uiButSearch *but_search = (uiButSearch *)but; | |
BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); | |
but_search->item_sep_string = search_sep_string; | |
} | |
void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn) | |
{ | |
uiButSearch *but_search = (uiButSearch *)but; | |
BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); | |
but_search->item_tooltip_fn = tooltip_fn; | |
} | |
void UI_but_func_search_set_listen(uiBut *but, uiButSearchListenFn listen_fn) | |
{ | |
uiButSearch *but_search = (uiButSearch *)but; | |
BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); | |
but_search->listen_fn = listen_fn; | |
} | |
void UI_but_func_search_set_results_are_suggestions(uiBut *but, const bool value) | |
{ | |
uiButSearch *but_search = (uiButSearch *)but; | |
BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); | |
but_search->results_are_suggestions = value; | |
} | |
/* Callbacks for operator search button. */ | |
static void operator_enum_search_update_fn( | |
const bContext *C, void *but, const char *str, uiSearchItems *items, const bool /*is_first*/) | |
{ | |
wmOperatorType *ot = ((uiBut *)but)->optype; | |
PropertyRNA *prop = ot->prop; | |
if (prop == nullptr) { | |
printf("%s: %s has no enum property set\n", __func__, ot->idname); | |
} | |
else if (RNA_property_type(prop) != PROP_ENUM) { | |
printf("%s: %s \"%s\" is not an enum property\n", | |
__func__, | |
ot->idname, | |
RNA_property_identifier(prop)); | |
} | |
else { | |
/* Will create it if needed! */ | |
PointerRNA *ptr = UI_but_operator_ptr_get(static_cast<uiBut *>(but)); | |
bool do_free; | |
const EnumPropertyItem *all_items; | |
RNA_property_enum_items_gettexted((bContext *)C, ptr, prop, &all_items, nullptr, &do_free); | |
blender::ui::string_search::StringSearch<const EnumPropertyItem> search; | |
for (const EnumPropertyItem *item = all_items; item->identifier; item++) { | |
search.add(item->name, item); | |
} | |
const blender::Vector<const EnumPropertyItem *> filtered_items = search.query(str); | |
for (const EnumPropertyItem *item : filtered_items) { | |
/* NOTE: need to give the index rather than the | |
* identifier because the enum can be freed */ | |
if (!UI_search_item_add(items, item->name, POINTER_FROM_INT(item->value), item->icon, 0, 0)) | |
{ | |
break; | |
} | |
} | |
if (do_free) { | |
MEM_freeN((void *)all_items); | |
} | |
} | |
} | |
static void operator_enum_search_exec_fn(bContext * /*C*/, void *but, void *arg2) | |
{ | |
wmOperatorType *ot = ((uiBut *)but)->optype; | |
/* Will create it if needed! */ | |
PointerRNA *opptr = UI_but_operator_ptr_get(static_cast<uiBut *>(but)); | |
if (ot) { | |
if (ot->prop) { | |
RNA_property_enum_set(opptr, ot->prop, POINTER_AS_INT(arg2)); | |
/* We do not call op from here, will be called by button code. | |
* ui_apply_but_funcs_after() (in `interface_handlers.cc`) | |
* called this func before checking operators, | |
* because one of its parameters is the button itself! */ | |
} | |
else { | |
printf("%s: op->prop for '%s' is nullptr\n", __func__, ot->idname); | |
} | |
} | |
} | |
uiBut *uiDefSearchButO_ptr(uiBlock *block, | |
wmOperatorType *ot, | |
IDProperty *properties, | |
void *arg, | |
int retval, | |
int icon, | |
int maxncpy, | |
int x, | |
int y, | |
short width, | |
short height, | |
float a1, | |
float a2, | |
const char *tip) | |
{ | |
uiBut *but = uiDefSearchBut(block, arg, retval, icon, maxncpy, x, y, width, height, a1, a2, tip); | |
UI_but_func_search_set(but, | |
ui_searchbox_create_generic, | |
operator_enum_search_update_fn, | |
but, | |
false, | |
nullptr, | |
operator_enum_search_exec_fn, | |
nullptr); | |
but->optype = ot; | |
but->opcontext = WM_OP_EXEC_DEFAULT; | |
if (properties) { | |
PointerRNA *ptr = UI_but_operator_ptr_get(but); | |
/* Copy id-properties. */ | |
ptr->data = IDP_CopyProperty(properties); | |
} | |
return but; | |
} | |
void UI_but_hint_drawstr_set(uiBut *but, const char *string) | |
{ | |
ui_but_add_shortcut(but, string, false); | |
} | |
void UI_but_icon_indicator_number_set(uiBut *but, const int indicator_number) | |
{ | |
UI_icon_text_overlay_init_from_count(&but->icon_overlay_text, indicator_number); | |
} | |
void UI_but_node_link_set(uiBut *but, bNodeSocket *socket, const float draw_color[4]) | |
{ | |
but->flag |= UI_BUT_NODE_LINK; | |
but->custom_data = socket; | |
rgba_float_to_uchar(but->col, draw_color); | |
} | |
void UI_but_number_step_size_set(uiBut *but, float step_size) | |
{ | |
uiButNumber *but_number = (uiButNumber *)but; | |
BLI_assert(but->type == UI_BTYPE_NUM); | |
but_number->step_size = step_size; | |
BLI_assert(step_size > 0); | |
} | |
void UI_but_number_precision_set(uiBut *but, float precision) | |
{ | |
uiButNumber *but_number = (uiButNumber *)but; | |
BLI_assert(but->type == UI_BTYPE_NUM); | |
but_number->precision = precision; | |
/* -1 is a valid value, UI code figures out an appropriate precision then. */ | |
BLI_assert(precision > -2); | |
} | |
void UI_but_focus_on_enter_event(wmWindow *win, uiBut *but) | |
{ | |
wmEvent event; | |
wm_event_init_from_window(win, &event); | |
event.type = EVT_BUT_OPEN; | |
event.val = KM_PRESS; | |
event.flag = static_cast<eWM_EventFlag>(0); | |
event.customdata = but; | |
event.customdata_free = false; | |
wm_event_add(win, &event); | |
} | |
void UI_but_func_hold_set(uiBut *but, uiButHandleHoldFunc func, void *argN) | |
{ | |
but->hold_func = func; | |
but->hold_argN = argN; | |
} | |
void UI_but_string_info_get(bContext *C, uiBut *but, ...) | |
{ | |
va_list args; | |
uiStringInfo *si; | |
PointerRNA *opptr = UI_but_operator_ptr_get(but); | |
const EnumPropertyItem *items = nullptr, *item = nullptr; | |
int totitems; | |
bool free_items = false; | |
va_start(args, but); | |
while ((si = (uiStringInfo *)va_arg(args, void *))) { | |
uiStringInfoType type = si->type; | |
char *tmp = nullptr; | |
if (type == BUT_GET_TIP_LABEL) { | |
if (but->tip_label_func) { | |
const std::string tooltip_label = but->tip_label_func(but); | |
tmp = BLI_strdupn(tooltip_label.c_str(), tooltip_label.size()); | |
} | |
} | |
if (type == BUT_GET_LABEL) { | |
if (but->str && but->str[0]) { | |
const char *str_sep; | |
size_t str_len; | |
if ((but->flag & UI_BUT_HAS_SEP_CHAR) && (str_sep = strrchr(but->str, UI_SEP_CHAR))) { | |
str_len = (str_sep - but->str); | |
} | |
else { | |
str_len = strlen(but->str); | |
} | |
tmp = BLI_strdupn(but->str, str_len); | |
} | |
else { | |
type = BUT_GET_RNA_LABEL; /* Fail-safe solution... */ | |
} | |
} | |
else if (type == BUT_GET_TIP) { | |
if (but->tip_func) { | |
tmp = but->tip_func(C, but->tip_arg, but->tip); | |
} | |
else if (but->tip && but->tip[0]) { | |
tmp = BLI_strdup(but->tip); | |
} | |
else { | |
type = BUT_GET_RNA_TIP; /* Fail-safe solution... */ | |
} | |
} | |
if (type == BUT_GET_RNAPROP_IDENTIFIER) { | |
if (but->rnaprop) { | |
tmp = BLI_strdup(RNA_property_identifier(but->rnaprop)); | |
} | |
} | |
else if (type == BUT_GET_RNASTRUCT_IDENTIFIER) { | |
if (but->rnaprop && but->rnapoin.data) { | |
tmp = BLI_strdup(RNA_struct_identifier(but->rnapoin.type)); | |
} | |
else if (but->optype) { | |
tmp = BLI_strdup(but->optype->idname); | |
} | |
else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) { | |
MenuType *mt = UI_but_menutype_get(but); | |
if (mt) { | |
tmp = BLI_strdup(mt->idname); | |
} | |
} | |
else if (but->type == UI_BTYPE_POPOVER) { | |
PanelType *pt = UI_but_paneltype_get(but); | |
if (pt) { | |
tmp = BLI_strdup(pt->idname); | |
} | |
} | |
} | |
else if (ELEM(type, BUT_GET_RNA_LABEL, BUT_GET_RNA_TIP)) { | |
if (but->rnaprop) { | |
if (type == BUT_GET_RNA_LABEL) { | |
tmp = BLI_strdup(RNA_property_ui_name(but->rnaprop)); | |
} | |
else { | |
const char *t = RNA_property_ui_description(but->rnaprop); | |
if (t && t[0]) { | |
tmp = BLI_strdup(t); | |
} | |
} | |
} | |
else if (but->optype) { | |
if (type == BUT_GET_RNA_LABEL) { | |
tmp = BLI_strdup(WM_operatortype_name(but->optype, opptr).c_str()); | |
} | |
else { | |
const bContextStore *previous_ctx = CTX_store_get(C); | |
CTX_store_set(C, but->context); | |
tmp = BLI_strdup(WM_operatortype_description(C, but->optype, opptr).c_str()); | |
CTX_store_set(C, previous_ctx); | |
} | |
} | |
else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER)) { | |
{ | |
MenuType *mt = UI_but_menutype_get(but); | |
if (mt) { | |
if (type == BUT_GET_RNA_LABEL) { | |
tmp = BLI_strdup(CTX_TIP_(mt->translation_context, mt->label)); | |
} | |
else { | |
/* Not all menus are from Python. */ | |
if (mt->rna_ext.srna) { | |
const char *t = RNA_struct_ui_description(mt->rna_ext.srna); | |
if (t && t[0]) { | |
tmp = BLI_strdup(t); | |
} | |
} | |
} | |
} | |
} | |
if (tmp == nullptr) { | |
wmOperatorType *ot = UI_but_operatortype_get_from_enum_menu(but, nullptr); | |
if (ot) { | |
if (type == BUT_GET_RNA_LABEL) { | |
tmp = BLI_strdup(WM_operatortype_name(ot, nullptr).c_str()); | |
} | |
else { | |
tmp = BLI_strdup(WM_operatortype_description(C, ot, nullptr).c_str()); | |
} | |
} | |
} | |
if (tmp == nullptr) { | |
PanelType *pt = UI_but_paneltype_get(but); | |
if (pt) { | |
if (type == BUT_GET_RNA_LABEL) { | |
tmp = BLI_strdup(CTX_TIP_(pt->translation_context, pt->label)); | |
} | |
else { | |
/* Not all panels are from Python. */ | |
if (pt->rna_ext.srna) { | |
/* Panels don't yet have descriptions, this may be added. */ | |
} | |
} | |
} | |
} | |
} | |
} | |
else if (type == BUT_GET_RNA_LABEL_CONTEXT) { | |
const char *_tmp = BLT_I18NCONTEXT_DEFAULT; | |
if (but->rnaprop) { | |
_tmp = RNA_property_translation_context(but->rnaprop); | |
} | |
else if (but->optype) { | |
_tmp = RNA_struct_translation_context(but->optype->srna); | |
} | |
else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) { | |
MenuType *mt = UI_but_menutype_get(but); | |
if (mt) { | |
_tmp = RNA_struct_translation_context(mt->rna_ext.srna); | |
} | |
} | |
if (BLT_is_default_context(_tmp)) { | |
_tmp = BLT_I18NCONTEXT_DEFAULT_BPYRNA; | |
} | |
tmp = BLI_strdup(_tmp); | |
} | |
else if (ELEM(type, BUT_GET_RNAENUM_IDENTIFIER, BUT_GET_RNAENUM_LABEL, BUT_GET_RNAENUM_TIP)) { | |
PointerRNA *ptr = nullptr; | |
PropertyRNA *prop = nullptr; | |
int value = 0; | |
/* get the enum property... */ | |
if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) { | |
/* enum property */ | |
ptr = &but->rnapoin; | |
prop = but->rnaprop; | |
value = ELEM(but->type, UI_BTYPE_ROW, UI_BTYPE_TAB) ? int(but->hardmax) : | |
int(ui_but_value_get(but)); | |
} | |
else if (but->optype) { | |
wmOperatorType *ot = but->optype; | |
/* So the context is passed to `itemf` functions. */ | |
WM_operator_properties_sanitize(opptr, false); | |
/* if the default property of the operator is enum and it is set, | |
* fetch the tooltip of the selected value so that "Snap" and "Mirror" | |
* operator menus in the Anim Editors will show tooltips for the different | |
* operations instead of the meaningless generic operator tooltip | |
*/ | |
if (ot->prop && RNA_property_type(ot->prop) == PROP_ENUM) { | |
if (RNA_struct_contains_property(opptr, ot->prop)) { | |
ptr = opptr; | |
prop = ot->prop; | |
value = RNA_property_enum_get(opptr, ot->prop); | |
} | |
} | |
} | |
/* get strings from matching enum item */ | |
if (ptr && prop) { | |
if (!item) { | |
int i; | |
RNA_property_enum_items_gettexted(C, ptr, prop, &items, &totitems, &free_items); | |
for (i = 0, item = items; i < totitems; i++, item++) { | |
if (item->identifier[0] && item->value == value) { | |
break; | |
} | |
} | |
} | |
if (item && item->identifier) { | |
if (type == BUT_GET_RNAENUM_IDENTIFIER) { | |
tmp = BLI_strdup(item->identifier); | |
} | |
else if (type == BUT_GET_RNAENUM_LABEL) { | |
tmp = BLI_strdup(item->name); | |
} | |
else if (item->description && item->description[0]) { | |
tmp = BLI_strdup(item->description); | |
} | |
} | |
} | |
} | |
else if (type == BUT_GET_OP_KEYMAP) { | |
if (!ui_block_is_menu(but->block)) { | |
char buf[128]; | |
if (ui_but_event_operator_string(C, but, buf, sizeof(buf))) { | |
tmp = BLI_strdup(buf); | |
} | |
} | |
} | |
else if (type == BUT_GET_PROP_KEYMAP) { | |
/* for properties that are bound to one of the context cycle, etc. keys... */ | |
char buf[128]; | |
if (ui_but_event_property_operator_string(C, but, buf, sizeof(buf))) { | |
tmp = BLI_strdup(buf); | |
} | |
} | |
si->strinfo = tmp; | |
} | |
va_end(args); | |
if (free_items && items) { | |
MEM_freeN((void *)items); | |
} | |
} | |
void UI_but_extra_icon_string_info_get(bContext *C, uiButExtraOpIcon *extra_icon, ...) | |
{ | |
va_list args; | |
uiStringInfo *si; | |
WinOpType *optype = UI_but_extra_op_icon_optype_get(extra_icon); | |
ApiPtr *opptr = UI_but_extra_op_icon_opptr_get(extra_icon); | |
va_start(args, extra_icon); | |
while ((si = (uiStringInfo *)va_arg(args, void *))) { | |
char *tmp = nullptr; | |
switch (si->type) { | |
case BUT_GET_LABEL: | |
tmp = BLI_strdup(WM_operatortype_name(optype, opptr).c_str()); | |
break; | |
case BUT_GET_TIP: | |
tmp = lib_strdup(win_optype_description(C, optype, opptr).c_str()); | |
break; | |
case BTN_GET_OP_KEYMAP: { | |
char buf[128]; | |
if (ui_btn_extra_icon_event_op_string(C, extra_icon, buf, sizeof(buf))) { | |
tmp = lib_strdup(buf); | |
} | |
break; | |
} | |
default: | |
/* Other types not supported. The caller should expect that outcome, no need to message or | |
* assert here. */ | |
break; | |
} | |
si->strinfo = tmp; | |
} | |
va_end(args); | |
} | |
/* Program Init/Exit */ | |
void ui_init() | |
{ | |
ui_resources_init(); | |
} | |
void ui_init_userdef() | |
{ | |
/* Initialize UI variables from values set in the preferences. */ | |
uiStyleInit(); | |
} | |
void ui_reinit_font() | |
{ | |
uiStyleInit(); | |
} | |
void ui_exit() | |
{ | |
ui_resources_free(); | |
ui_but_clipboard_free(); | |
} | |
void ui_tag_script_reload() | |
{ | |
ui_tag_script_reload_queries(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment