diff options
Diffstat (limited to 'source/blender/editors/interface')
46 files changed, 6213 insertions, 2602 deletions
diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 5011a50ed73..84172c7efce 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -25,9 +25,11 @@ set(INC ../../depsgraph ../../draw ../../gpu + ../../functions ../../imbuf ../../makesdna ../../makesrna + ../../nodes ../../python ../../render ../../windowmanager @@ -41,7 +43,9 @@ set(SRC interface_anim.c interface_button_group.c interface_context_menu.c + interface_context_path.cc interface_draw.c + interface_dropboxes.cc interface_eyedropper.c interface_eyedropper_color.c interface_eyedropper_colorband.c @@ -66,13 +70,18 @@ set(SRC interface_region_tooltip.c interface_regions.c interface_style.c + interface_template_asset_view.cc + interface_template_list.cc + interface_template_attribute_search.cc interface_template_search_menu.c interface_template_search_operator.c interface_templates.c interface_undo.c interface_utils.c + interface_view.cc interface_widgets.c resources.c + tree_view.cc view2d.c view2d_draw.c view2d_edge_pan.c @@ -102,7 +111,7 @@ if(WITH_PYTHON) add_definitions(-DWITH_PYTHON) endif() -if(WIN32) +if(WIN32 OR APPLE) if(WITH_INPUT_IME) add_definitions(-DWITH_INPUT_IME) endif() diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index d2204c17e62..82ea218baba 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -57,6 +57,8 @@ #include "BKE_screen.h" #include "BKE_unit.h" +#include "ED_asset.h" + #include "GPU_matrix.h" #include "GPU_state.h" @@ -131,12 +133,10 @@ static bool ui_but_is_unit_radians(const uiBut *but) /* ************* window matrix ************** */ -void ui_block_to_window_fl(const ARegion *region, uiBlock *block, float *r_x, float *r_y) +void ui_block_to_region_fl(const ARegion *region, uiBlock *block, float *r_x, float *r_y) { const int getsizex = BLI_rcti_size_x(®ion->winrct) + 1; const int getsizey = BLI_rcti_size_y(®ion->winrct) + 1; - const int sx = region->winrct.xmin; - const int sy = region->winrct.ymin; float gx = *r_x; float gy = *r_y; @@ -146,14 +146,19 @@ void ui_block_to_window_fl(const ARegion *region, uiBlock *block, float *r_x, fl gy += block->panel->ofsy; } - *r_x = ((float)sx) + - ((float)getsizex) * (0.5f + 0.5f * (gx * block->winmat[0][0] + gy * block->winmat[1][0] + + *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)sy) + - ((float)getsizey) * (0.5f + 0.5f * (gx * block->winmat[0][1] + gy * block->winmat[1][1] + + *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, 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, uiBlock *block, int *r_x, int *r_y) { float fx = *r_x; @@ -165,6 +170,16 @@ void ui_block_to_window(const ARegion *region, uiBlock *block, int *r_x, int *r_ *r_y = (int)(fy + 0.5f); } +void ui_block_to_region_rctf(const ARegion *region, + 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, uiBlock *block, rctf *rct_dst, @@ -249,6 +264,14 @@ void ui_window_to_region_rcti(const ARegion *region, rcti *rect_dst, const rcti 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; @@ -476,7 +499,7 @@ void ui_block_bounds_calc(uiBlock *block) static void ui_block_bounds_calc_centered(wmWindow *window, uiBlock *block) { - /* note: this is used for the splash where window bounds event has not been + /* 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 = WM_window_pixels_x(window); @@ -587,7 +610,7 @@ void UI_block_bounds_set_normal(uiBlock *block, int addval) block->bounds_type = UI_BLOCK_BOUNDS; } -/* used for pulldowns */ +/* Used for pull-downs. */ void UI_block_bounds_set_text(uiBlock *block, int addval) { block->bounds = addval; @@ -719,6 +742,18 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut) if (but->optype != oldbut->optype) { return false; } + if (but->dragtype != oldbut->dragtype) { + return false; + } + + if ((but->type == UI_BTYPE_TREEROW) && (oldbut->type == UI_BTYPE_TREEROW)) { + uiButTreeRow *but_treerow = (uiButTreeRow *)but; + uiButTreeRow *oldbut_treerow = (uiButTreeRow *)oldbut; + if (!but_treerow->tree_item || !oldbut_treerow->tree_item || + !UI_tree_view_item_matches(but_treerow->tree_item, oldbut_treerow->tree_item)) { + return false; + } + } return true; } @@ -811,7 +846,8 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) /* Move tooltip from new to old. */ SWAP(uiButToolTipFunc, oldbut->tip_func, but->tip_func); - SWAP(void *, oldbut->tip_argN, but->tip_argN); + SWAP(void *, oldbut->tip_arg, but->tip_arg); + SWAP(uiFreeArgFunc, oldbut->tip_arg_free, but->tip_arg_free); oldbut->flag = (oldbut->flag & ~flag_copy) | (but->flag & flag_copy); oldbut->drawflag = (oldbut->drawflag & ~drawflag_copy) | (but->drawflag & drawflag_copy); @@ -822,7 +858,7 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) if (oldbut->type == UI_BTYPE_SEARCH_MENU) { uiButSearch *search_oldbut = (uiButSearch *)oldbut, *search_but = (uiButSearch *)but; - SWAP(uiButSearchArgFreeFn, search_oldbut->arg_free_fn, search_but->arg_free_fn); + SWAP(uiFreeArgFunc, search_oldbut->arg_free_fn, search_but->arg_free_fn); SWAP(void *, search_oldbut->arg, search_but->arg); } @@ -832,10 +868,21 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) oldbut->hardmax = but->hardmax; } - if (oldbut->type == UI_BTYPE_PROGRESS_BAR) { - uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut; - uiButProgressbar *progress_but = (uiButProgressbar *)but; - progress_oldbut->progress = progress_but->progress; + switch (oldbut->type) { + case UI_BTYPE_PROGRESS_BAR: { + uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut; + uiButProgressbar *progress_but = (uiButProgressbar *)but; + progress_oldbut->progress = progress_but->progress; + break; + } + case UI_BTYPE_TREEROW: { + uiButTreeRow *treerow_oldbut = (uiButTreeRow *)oldbut; + uiButTreeRow *treerow_newbut = (uiButTreeRow *)but; + SWAP(uiTreeViewItemHandle *, treerow_newbut->tree_item, treerow_oldbut->tree_item); + break; + } + default: + break; } /* move/copy string from the new button to the old */ @@ -861,7 +908,7 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) SWAP(void *, but->dragpoin, oldbut->dragpoin); } - /* note: if layout hasn't been applied yet, it uses old button pointers... */ + /* NOTE: if layout hasn't been applied yet, it uses old button pointers... */ } /** @@ -921,7 +968,13 @@ static bool ui_but_update_from_old_block(const bContext *C, found_active = true; } else { - const int flag_copy = UI_BUT_DRAG_MULTI; + int flag_copy = UI_BUT_DRAG_MULTI; + + /* Stupid special case: The active button may be inside (as in, overlapped on top) a tree-row + * button which we also want to keep highlighted then. */ + if (but->type == UI_BTYPE_TREEROW) { + flag_copy |= UI_ACTIVE; + } but->flag = (but->flag & ~flag_copy) | (oldbut->flag & flag_copy); @@ -970,6 +1023,9 @@ bool UI_but_active_only_ex( 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; @@ -1171,16 +1227,21 @@ void ui_but_add_shortcut(uiBut *but, const char *shortcut_str, const bool do_str * \{ */ static bool ui_but_event_operator_string_from_operator(const bContext *C, - uiBut *but, + wmOperatorCallParams *op_call_params, char *buf, const size_t buf_len) { - BLI_assert(but->optype != NULL); + BLI_assert(op_call_params->optype != NULL); bool found = false; - IDProperty *prop = (but->opptr) ? but->opptr->data : NULL; - - if (WM_key_event_operator_string( - C, but->optype->idname, but->opcontext, prop, true, buf, buf_len)) { + IDProperty *prop = (op_call_params->opptr) ? op_call_params->opptr->data : NULL; + + if (WM_key_event_operator_string(C, + op_call_params->optype->idname, + op_call_params->opcontext, + prop, + true, + buf, + buf_len)) { found = true; } return found; @@ -1265,7 +1326,12 @@ static bool ui_but_event_operator_string(const bContext *C, bool found = false; if (but->optype != NULL) { - found = ui_but_event_operator_string_from_operator(C, but, buf, buf_len); + found = ui_but_event_operator_string_from_operator( + C, + &(wmOperatorCallParams){ + .optype = but->optype, .opptr = but->opptr, .opcontext = but->opcontext}, + buf, + buf_len); } else if (UI_but_menutype_get(but) != NULL) { found = ui_but_event_operator_string_from_menu(C, but, buf, buf_len); @@ -1277,6 +1343,20 @@ static bool ui_but_event_operator_string(const bContext *C, return found; } +static bool ui_but_extra_icon_event_operator_string(const bContext *C, + uiButExtraOpIcon *extra_icon, + char *buf, + const size_t buf_len) +{ + 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_len); + } + + return false; +} + static bool ui_but_event_property_operator_string(const bContext *C, uiBut *but, char *buf, @@ -1315,6 +1395,7 @@ static bool ui_but_event_property_operator_string(const bContext *C, 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 != NULL)) { uiBut *but_parent = but->block->handle->popup_create_vars.but; if ((but->type == UI_BTYPE_BUT_MENU) && (but_parent && but_parent->rnaprop) && @@ -1339,28 +1420,15 @@ static bool ui_but_event_property_operator_string(const bContext *C, return false; } - /* this version is only for finding hotkeys for properties - * (which get set via context using operators) */ - /* to avoid massive slowdowns on property panels, for now, we only check the - * hotkeys for Editor / Scene settings... - * - * TODO: userpref settings? - */ - char *data_path = NULL; + /* This version is only for finding hotkeys for properties. + * These are set 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) { - /* screen/editor property - * NOTE: in most cases, there is actually no info for backwards tracing - * how to get back to ID from the editor data we may be dealing with - */ - if (RNA_struct_is_a(ptr->type, &RNA_Space)) { - /* data should be directly on here... */ - data_path = BLI_sprintfN("space_data.%s", RNA_property_identifier(prop)); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Area)) { + 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'. */ @@ -1368,58 +1436,19 @@ static bool ui_but_event_property_operator_string(const bContext *C, 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; } - else { - data_path = BLI_sprintfN("area.%s", prop_id); - } - } - else { - /* special exceptions for common nested data in editors... */ - if (RNA_struct_is_a(ptr->type, &RNA_DopeSheet)) { - /* dopesheet filtering options... */ - data_path = BLI_sprintfN("space_data.dopesheet.%s", RNA_property_identifier(prop)); - } - else if (RNA_struct_is_a(ptr->type, &RNA_FileSelectParams)) { - /* Filebrowser options... */ - data_path = BLI_sprintfN("space_data.params.%s", RNA_property_identifier(prop)); - } } } - else if (GS(id->name) == ID_SCE) { - if (RNA_struct_is_a(ptr->type, &RNA_ToolSettings)) { - /* Tool-settings property: - * NOTE: tool-settings is usually accessed directly (i.e. not through scene). */ - data_path = RNA_path_from_ID_to_property(ptr, prop); - } - else { - /* scene property */ - char *path = RNA_path_from_ID_to_property(ptr, prop); - - if (path) { - data_path = BLI_sprintfN("scene.%s", path); - MEM_freeN(path); - } -#if 0 - else { - printf("ERROR in %s(): Couldn't get path for scene property - %s\n", - __func__, - RNA_property_identifier(prop)); - } -#endif - } - } - else { - // puts("other id"); - } - - // printf("prop shortcut: '%s' (%s)\n", RNA_property_identifier(prop), data_path); } + char *data_path = WM_context_path_resolve_property_full(C, ptr, prop, prop_index); + /* We have a data-path! */ bool found = false; if (data_path || (prop_enum_value_ok && prop_enum_value_id)) { @@ -1609,7 +1638,7 @@ typedef enum PredefinedExtraOpIconType { static PointerRNA *ui_but_extra_operator_icon_add_ptr(uiBut *but, wmOperatorType *optype, - short opcontext, + wmOperatorCallContext opcontext, int icon) { uiButExtraOpIcon *extra_op_icon = MEM_mallocN(sizeof(*extra_op_icon), __func__); @@ -1624,6 +1653,7 @@ static PointerRNA *ui_but_extra_operator_icon_add_ptr(uiBut *but, 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); @@ -1648,7 +1678,7 @@ void ui_but_extra_operator_icons_free(uiBut *but) PointerRNA *UI_but_extra_operator_icon_add(uiBut *but, const char *opname, - short opcontext, + wmOperatorCallContext opcontext, int icon) { wmOperatorType *optype = WM_operatortype_find(opname, false); @@ -1660,6 +1690,16 @@ PointerRNA *UI_but_extra_operator_icon_add(uiBut *but, return NULL; } +wmOperatorType *UI_but_extra_operator_icon_optype_get(uiButExtraOpIcon *extra_icon) +{ + return extra_icon ? extra_icon->optype_params->optype : NULL; +} + +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); @@ -1818,18 +1858,19 @@ static void ui_but_validate(const uiBut *but) /** * Check if the operator \a ot poll is successful with the context given by \a but (optionally). * \param but: The button that might store context. Can be NULL for convenience (e.g. if there is - * no button to take context from, but we still want to poll the operator). + * no button to take context from, but we still want to poll the operator). */ -bool ui_but_context_poll_operator(bContext *C, wmOperatorType *ot, const uiBut *but) +bool ui_but_context_poll_operator_ex(bContext *C, + const uiBut *but, + const wmOperatorCallParams *optype_params) { bool result; - int opcontext = but ? but->opcontext : WM_OP_INVOKE_DEFAULT; if (but && but->context) { CTX_store_set(C, but->context); } - result = WM_operator_poll_context(C, ot, opcontext); + result = WM_operator_poll_context(C, optype_params->optype, optype_params->opcontext); if (but && but->context) { CTX_store_set(C, NULL); @@ -1838,6 +1879,13 @@ bool ui_but_context_poll_operator(bContext *C, wmOperatorType *ot, const uiBut * 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; + return ui_but_context_poll_operator_ex( + C, but, &(wmOperatorCallParams){.optype = ot, .opcontext = opcontext}); +} + void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_xy[2]) { wmWindow *window = CTX_wm_window(C); @@ -1868,6 +1916,12 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x } } + 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); @@ -1934,7 +1988,7 @@ void UI_block_end(const bContext *C, uiBlock *block) { wmWindow *window = CTX_wm_window(C); - UI_block_end_ex(C, block, &window->eventstate->x, NULL); + UI_block_end_ex(C, block, window->eventstate->xy, NULL); } /* ************** BLOCK DRAWING FUNCTION ************* */ @@ -1944,8 +1998,8 @@ void ui_fontscale(short *points, float aspect) if (aspect < 0.9f || aspect > 1.1f) { float pointsf = *points; - /* for some reason scaling fonts goes too fast compared to widget size */ - /* XXX not true anymore? (ton) */ + /* For some reason scaling fonts goes too fast compared to widget size. */ + /* XXX(ton): not true anymore? */ // aspect = sqrt(aspect); pointsf /= aspect; @@ -2179,6 +2233,15 @@ int ui_but_is_pushed_ex(uiBut *but, double *value) } } break; + case UI_BTYPE_TREEROW: { + uiButTreeRow *tree_row_but = (uiButTreeRow *)but; + + is_push = -1; + if (tree_row_but->tree_item) { + is_push = UI_tree_view_item_is_active(tree_row_but->tree_item); + } + break; + } default: is_push = -1; break; @@ -2431,7 +2494,7 @@ bool ui_but_is_rna_valid(uiBut *but) */ 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)) || + 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 != NULL)); @@ -2504,7 +2567,7 @@ double ui_but_value_get(uiBut *but) void ui_but_value_set(uiBut *but, double value) { - /* value is a hsv value: convert to rgb */ + /* Value is a HSV value: convert to RGB. */ if (but->rnaprop) { PropertyRNA *prop = but->rnaprop; @@ -2628,7 +2691,7 @@ static double ui_get_but_scale_unit(uiBut *but, double value) 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 :| */ + if (unit_type == PROP_UNIT_TIME) { /* WARNING: using evil_C :| */ Scene *scene = CTX_data_scene(but->block->evil_C); return FRA2TIME(value); } @@ -3139,7 +3202,7 @@ bool ui_but_string_set(bContext *C, uiBut *but, const char *str) return true; } else if (str[0] == '#') { - /* shortcut to create new driver expression (versus immediate Py-execution) */ + /* Shortcut to create new driver expression (versus immediate Python-execution). */ return ui_but_anim_expression_create(but, str + 1); } else { @@ -3222,7 +3285,7 @@ void ui_but_range_set_hard(uiBut *but) } } -/* note: this could be split up into functions which handle arrays and not */ +/* NOTE: this could be split up into functions which handle arrays and not. */ void ui_but_range_set_soft(uiBut *but) { /* Ideally we would not limit this, but practically it's more than @@ -3358,8 +3421,8 @@ static void ui_but_free(const bContext *C, uiBut *but) MEM_freeN(but->func_argN); } - if (but->tip_argN) { - MEM_freeN(but->tip_argN); + if (but->tip_arg_free) { + but->tip_arg_free(but->tip_arg); } if (but->hold_argN) { @@ -3423,6 +3486,7 @@ void UI_block_free(const bContext *C, uiBlock *block) BLI_freelistN(&block->color_pickers.list); ui_block_free_button_groups(block); + ui_block_free_views(block); MEM_freeN(block); } @@ -3439,6 +3503,15 @@ void UI_blocklist_update_window_matrix(const bContext *C, const ListBase *lb) } } +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) { @@ -3541,7 +3614,7 @@ uiBlock *UI_block_begin(const bContext *C, ARegion *region, const char *name, eU return block; } -char UI_block_emboss_get(uiBlock *block) +eUIEmbossType UI_block_emboss_get(uiBlock *block) { return block->emboss; } @@ -3909,6 +3982,10 @@ static void ui_but_alloc_info(const eButType type, alloc_size = sizeof(uiButDatasetRow); alloc_str = "uiButDatasetRow"; break; + case UI_BTYPE_TREEROW: + alloc_size = sizeof(uiButTreeRow); + alloc_str = "uiButTreeRow"; + break; default: alloc_size = sizeof(uiBut); alloc_str = "uiBut"; @@ -3984,9 +4061,11 @@ uiBut *ui_but_change_type(uiBut *but, eButType new_type) 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 } return but; @@ -4105,8 +4184,8 @@ static uiBut *ui_def_but(uiBlock *block, UI_BTYPE_BLOCK, UI_BTYPE_BUT_MENU, UI_BTYPE_SEARCH_MENU, - UI_BTYPE_PROGRESS_BAR, UI_BTYPE_DATASETROW, + UI_BTYPE_TREEROW, UI_BTYPE_POPOVER)) { but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); } @@ -4246,7 +4325,7 @@ static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *bu uiItemS(layout); } - /* note, item_array[...] is reversed on access */ + /* NOTE: `item_array[...]` is reversed on access. */ /* create items */ uiLayout *split = uiLayoutSplit(layout, 0.0f, false); @@ -4371,7 +4450,7 @@ static void ui_def_but_rna__panel_type(bContext *C, uiLayout *layout, void *but_ } else { char msg[256]; - SNPRINTF(msg, "Missing Panel: %s", panel_type); + SNPRINTF(msg, TIP_("Missing Panel: %s"), panel_type); uiItemL(layout, msg, ICON_NONE); } } @@ -4400,7 +4479,7 @@ static void ui_def_but_rna__menu_type(bContext *C, uiLayout *layout, void *but_p } else { char msg[256]; - SNPRINTF(msg, "Missing Menu: %s", menu_type); + SNPRINTF(msg, TIP_("Missing Menu: %s"), menu_type); uiItemL(layout, msg, ICON_NONE); } } @@ -4549,7 +4628,7 @@ static uiBut *ui_def_but_rna(uiBlock *block, else if (proptype == PROP_STRING) { min = 0; max = RNA_property_string_maxlength(prop); - /* note, 'max' may be zero (code for dynamically resized array) */ + /* NOTE: 'max' may be zero (code for dynamically resized array). */ } } @@ -4663,7 +4742,7 @@ static uiBut *ui_def_but_rna_propname(uiBlock *block, static uiBut *ui_def_but_operator_ptr(uiBlock *block, int type, wmOperatorType *ot, - int opcontext, + wmOperatorCallContext opcontext, const char *str, int x, int y, @@ -5201,7 +5280,7 @@ uiBut *uiDefButR_prop(uiBlock *block, uiBut *uiDefButO_ptr(uiBlock *block, int type, wmOperatorType *ot, - int opcontext, + wmOperatorCallContext opcontext, const char *str, int x, int y, @@ -5216,7 +5295,7 @@ uiBut *uiDefButO_ptr(uiBlock *block, uiBut *uiDefButO(uiBlock *block, int type, const char *opname, - int opcontext, + wmOperatorCallContext opcontext, const char *str, int x, int y, @@ -5584,7 +5663,7 @@ uiBut *uiDefIconButR_prop(uiBlock *block, uiBut *uiDefIconButO_ptr(uiBlock *block, int type, wmOperatorType *ot, - int opcontext, + wmOperatorCallContext opcontext, int icon, int x, int y, @@ -5599,7 +5678,7 @@ uiBut *uiDefIconButO_ptr(uiBlock *block, uiBut *uiDefIconButO(uiBlock *block, int type, const char *opname, - int opcontext, + wmOperatorCallContext opcontext, int icon, int x, int y, @@ -5987,7 +6066,7 @@ uiBut *uiDefIconTextButR_prop(uiBlock *block, uiBut *uiDefIconTextButO_ptr(uiBlock *block, int type, wmOperatorType *ot, - int opcontext, + wmOperatorCallContext opcontext, int icon, const char *str, int x, @@ -6004,7 +6083,7 @@ uiBut *uiDefIconTextButO_ptr(uiBlock *block, uiBut *uiDefIconTextButO(uiBlock *block, int type, const char *opname, - int opcontext, + wmOperatorCallContext opcontext, int icon, const char *str, int x, @@ -6139,32 +6218,37 @@ int UI_but_return_value_get(uiBut *but) void UI_but_drag_set_id(uiBut *but, ID *id) { but->dragtype = WM_DRAG_ID; - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + if (but->dragflag & UI_BUT_DRAGPOIN_FREE) { WM_drag_data_free(but->dragtype, but->dragpoin); but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; } but->dragpoin = (void *)id; } +/** + * \param asset: May be passed from a temporary variable, drag data only stores a copy of this. + */ void UI_but_drag_set_asset(uiBut *but, - const char *name, + const AssetHandle *asset, const char *path, - int id_type, + struct AssetMetaData *metadata, int import_type, int icon, struct ImBuf *imb, float scale) { - wmDragAsset *asset_drag = MEM_mallocN(sizeof(*asset_drag), "wmDragAsset"); + wmDragAsset *asset_drag = WM_drag_create_asset_data(asset, metadata, path, import_type); - BLI_strncpy(asset_drag->name, name, sizeof(asset_drag->name)); - asset_drag->path = path; - asset_drag->id_type = id_type; - asset_drag->import_type = import_type; + /* FIXME: This is temporary evil solution to get scene/viewlayer/etc in the copy callback of the + * #wmDropBox. + * TODO: Handle link/append in operator called at the end of the drop process, and NOT in its + * copy callback. + * */ + asset_drag->evil_C = but->block->evil_C; but->dragtype = WM_DRAG_ASSET; ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesn't draw in button */ - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + if (but->dragflag & UI_BUT_DRAGPOIN_FREE) { WM_drag_data_free(but->dragtype, but->dragpoin); } but->dragpoin = asset_drag; @@ -6176,7 +6260,7 @@ void UI_but_drag_set_asset(uiBut *but, void UI_but_drag_set_rna(uiBut *but, PointerRNA *ptr) { but->dragtype = WM_DRAG_RNA; - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + if (but->dragflag & UI_BUT_DRAGPOIN_FREE) { WM_drag_data_free(but->dragtype, but->dragpoin); but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; } @@ -6186,7 +6270,7 @@ void UI_but_drag_set_rna(uiBut *but, PointerRNA *ptr) void UI_but_drag_set_path(uiBut *but, const char *path, const bool use_free) { but->dragtype = WM_DRAG_PATH; - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + if (but->dragflag & UI_BUT_DRAGPOIN_FREE) { WM_drag_data_free(but->dragtype, but->dragpoin); but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; } @@ -6199,7 +6283,7 @@ void UI_but_drag_set_path(uiBut *but, const char *path, const bool use_free) void UI_but_drag_set_name(uiBut *but, const char *name) { but->dragtype = WM_DRAG_NAME; - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + if (but->dragflag & UI_BUT_DRAGPOIN_FREE) { WM_drag_data_free(but->dragtype, but->dragpoin); but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; } @@ -6217,7 +6301,7 @@ void UI_but_drag_set_image( { but->dragtype = WM_DRAG_PATH; ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesn't draw in button */ - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + if (but->dragflag & UI_BUT_DRAGPOIN_FREE) { WM_drag_data_free(but->dragtype, but->dragpoin); but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; } @@ -6334,13 +6418,14 @@ void UI_but_func_menu_step_set(uiBut *but, uiMenuStepFunc func) but->menu_step_func = func; } -void UI_but_func_tooltip_set(uiBut *but, uiButToolTipFunc func, void *argN) +void UI_but_func_tooltip_set(uiBut *but, uiButToolTipFunc func, void *arg, uiFreeArgFunc free_arg) { but->tip_func = func; - if (but->tip_argN) { - MEM_freeN(but->tip_argN); + if (but->tip_arg_free) { + but->tip_arg_free(but->tip_arg); } - but->tip_argN = argN; + but->tip_arg = arg; + but->tip_arg_free = free_arg; } void UI_but_func_pushed_state_set(uiBut *but, uiButPushedStateFunc func, const void *arg) @@ -6630,7 +6715,7 @@ void UI_but_func_search_set(uiBut *but, uiButSearchUpdateFn search_update_fn, void *arg, const bool free_arg, - uiButSearchArgFreeFn search_arg_free_fn, + uiFreeArgFunc search_arg_free_fn, uiButHandleFunc search_exec_fn, void *active) { @@ -6755,7 +6840,7 @@ static void operator_enum_search_update_fn(const struct bContext *C, for (int i = 0; i < filtered_amount; i++) { const EnumPropertyItem *item = filtered_items[i]; - /* note: need to give the index rather than the + /* 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)) { @@ -6841,6 +6926,15 @@ void UI_but_datasetrow_indentation_set(uiBut *but, int indentation) BLI_assert(indentation >= 0); } +void UI_but_treerow_indentation_set(uiBut *but, int indentation) +{ + uiButTreeRow *but_row = (uiButTreeRow *)but; + BLI_assert(but->type == UI_BTYPE_TREEROW); + + but_row->indentation = indentation; + BLI_assert(indentation >= 0); +} + /** * Adds a hint to the button which draws right aligned, grayed out and never clipped. */ @@ -6920,7 +7014,7 @@ void UI_but_focus_on_enter_event(wmWindow *win, uiBut *but) event.val = KM_PRESS; event.is_repeat = false; event.customdata = but; - event.customdatafree = false; + event.customdata_free = false; wm_event_add(win, &event); } @@ -6965,7 +7059,7 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...) } else if (type == BUT_GET_TIP) { if (but->tip_func) { - tmp = but->tip_func(C, but->tip_argN, but->tip); + tmp = but->tip_func(C, but->tip_arg, but->tip); } else if (but->tip && but->tip[0]) { tmp = BLI_strdup(but->tip); @@ -7170,6 +7264,42 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...) } } +void UI_but_extra_icon_string_info_get(struct bContext *C, uiButExtraOpIcon *extra_icon, ...) +{ + va_list args; + uiStringInfo *si; + + wmOperatorType *optype = UI_but_extra_operator_icon_optype_get(extra_icon); + PointerRNA *opptr = UI_but_extra_operator_icon_opptr_get(extra_icon); + + va_start(args, extra_icon); + while ((si = (uiStringInfo *)va_arg(args, void *))) { + char *tmp = NULL; + + switch (si->type) { + case BUT_GET_LABEL: + tmp = BLI_strdup(WM_operatortype_name(optype, opptr)); + break; + case BUT_GET_TIP: + tmp = WM_operatortype_description(C, optype, opptr); + break; + case BUT_GET_OP_KEYMAP: { + char buf[128]; + if (ui_but_extra_icon_event_operator_string(C, extra_icon, buf, sizeof(buf))) { + tmp = BLI_strdup(buf); + } + } + /* Other types not supported. The caller should expect that outcome, no need to message or + * assert here. */ + default: + break; + } + + si->strinfo = tmp; + } + va_end(args); +} + /* Program Init/Exit */ void UI_init(void) diff --git a/source/blender/editors/interface/interface_align.c b/source/blender/editors/interface/interface_align.c index dbfdfbf7950..3149675ac04 100644 --- a/source/blender/editors/interface/interface_align.c +++ b/source/blender/editors/interface/interface_align.c @@ -343,7 +343,7 @@ static int ui_block_align_butal_cmp(const void *a, const void *b) * stupid UI code produces widgets which have the same TOP and LEFT positions... * We do not care really, * because this happens when UI is way too small to be usable anyway. */ - /* BLI_assert(0); */ + // BLI_assert(0); return 0; } diff --git a/source/blender/editors/interface/interface_button_group.c b/source/blender/editors/interface/interface_button_group.c index 4e7da4ada33..7054498d469 100644 --- a/source/blender/editors/interface/interface_button_group.c +++ b/source/blender/editors/interface/interface_button_group.c @@ -57,7 +57,7 @@ void ui_button_group_add_but(uiBlock *block, uiBut *but) uiButtonGroup *current_button_group = block->button_groups.last; /* We can't use the button directly because adding it to - * this list would mess with its prev and next pointers. */ + * this list would mess with its `prev` and `next` pointers. */ LinkData *button_link = BLI_genericNodeN(but); BLI_addtail(¤t_button_group->buttons, button_link); } diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c index 775e3923edc..72e7203c6ea 100644 --- a/source/blender/editors/interface/interface_context_menu.c +++ b/source/blender/editors/interface/interface_context_menu.c @@ -70,26 +70,12 @@ static IDProperty *shortcut_property_from_rna(bContext *C, uiBut *but) /* If this returns null, we won't be able to bind shortcuts to these RNA properties. * Support can be added at #wm_context_member_from_ptr. */ - const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin); - if (member_id == NULL) { + char *final_data_path = WM_context_path_resolve_property_full( + C, &but->rnapoin, but->rnaprop, but->rnaindex); + if (final_data_path == NULL) { return NULL; } - const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin); - const char *member_id_data_path = member_id; - - if (data_path) { - member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); - MEM_freeN((void *)data_path); - } - - const char *prop_id = RNA_property_identifier(but->rnaprop); - const char *final_data_path = BLI_sprintfN("%s.%s", member_id_data_path, prop_id); - - if (member_id != member_id_data_path) { - MEM_freeN((void *)member_id_data_path); - } - /* Create ID property of data path, to pass to the operator. */ const IDPropertyTemplate val = {0}; IDProperty *prop = IDP_New(IDP_GROUP, &val, __func__); @@ -329,10 +315,24 @@ static void popup_add_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2)) static bool ui_but_is_user_menu_compatible(bContext *C, uiBut *but) { - return (but->optype || - (but->rnaprop && (RNA_property_type(but->rnaprop) == PROP_BOOLEAN) && - (WM_context_member_from_ptr(C, &but->rnapoin) != NULL)) || - UI_but_menutype_get(but)); + bool result = false; + if (but->optype) { + result = true; + } + else if (but->rnaprop) { + if (RNA_property_type(but->rnaprop) == PROP_BOOLEAN) { + char *data_path = WM_context_path_resolve_full(C, &but->rnapoin); + if (data_path != NULL) { + MEM_freeN(data_path); + result = true; + } + } + } + else if (UI_but_menutype_get(but)) { + result = true; + } + + return result; } static bUserMenuItem *ui_but_user_menu_find(bContext *C, uiBut *but, bUserMenu *um) @@ -343,21 +343,11 @@ static bUserMenuItem *ui_but_user_menu_find(bContext *C, uiBut *but, bUserMenu * &um->items, but->optype, prop, but->opcontext); } if (but->rnaprop) { - const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin); - const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin); - const char *member_id_data_path = member_id; - if (data_path) { - member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); - } + char *member_id_data_path = WM_context_path_resolve_full(C, &but->rnapoin); const char *prop_id = RNA_property_identifier(but->rnaprop); bUserMenuItem *umi = (bUserMenuItem *)ED_screen_user_menu_item_find_prop( &um->items, member_id_data_path, prop_id, but->rnaindex); - if (data_path) { - MEM_freeN((void *)data_path); - } - if (member_id != member_id_data_path) { - MEM_freeN((void *)member_id_data_path); - } + MEM_freeN(member_id_data_path); return umi; } @@ -373,13 +363,7 @@ static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um) BLI_assert(ui_but_is_user_menu_compatible(C, but)); char drawstr[sizeof(but->drawstr)]; - STRNCPY(drawstr, but->drawstr); - if (but->flag & UI_BUT_HAS_SEP_CHAR) { - char *sep = strrchr(drawstr, UI_SEP_CHAR); - if (sep) { - *sep = '\0'; - } - } + ui_but_drawstr_without_sep_char(but, drawstr, sizeof(drawstr)); MenuType *mt = NULL; if (but->optype) { @@ -417,22 +401,12 @@ static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um) &um->items, drawstr, but->optype, but->opptr ? but->opptr->data : NULL, but->opcontext); } else if (but->rnaprop) { - /* Note: 'member_id' may be a path. */ - const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin); - const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin); - const char *member_id_data_path = member_id; - if (data_path) { - member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); - } + /* NOTE: 'member_id' may be a path. */ + char *member_id_data_path = WM_context_path_resolve_full(C, &but->rnapoin); const char *prop_id = RNA_property_identifier(but->rnaprop); - /* Note, ignore 'drawstr', use property idname always. */ + /* NOTE: ignore 'drawstr', use property idname always. */ ED_screen_user_menu_item_add_prop(&um->items, "", member_id_data_path, prop_id, but->rnaindex); - if (data_path) { - MEM_freeN((void *)data_path); - } - if (member_id != member_id_data_path) { - MEM_freeN((void *)member_id_data_path); - } + MEM_freeN(member_id_data_path); } else if ((mt = UI_but_menutype_get(but))) { ED_screen_user_menu_item_add_menu(&um->items, drawstr, mt); @@ -494,7 +468,7 @@ static void ui_but_menu_add_path_operators(uiLayout *layout, PointerRNA *ptr, Pr RNA_string_set(&props_ptr, "filepath", dir); } -bool ui_popup_context_menu_for_button(bContext *C, uiBut *but) +bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *event) { /* ui_but_is_interactive() may let some buttons through that should not get a context menu - it * doesn't make sense for them. */ @@ -560,7 +534,7 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but) const bool is_overridable = (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE) != 0; /* Set the (button_pointer, button_prop) - * and pointer data for Python access to the hovered ui element. */ + * and pointer data for Python access to the hovered UI element. */ uiLayoutSetContextFromBut(layout, but); /* Keyframes */ @@ -951,8 +925,19 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but) } } + { + const ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C); + uiButTreeRow *treerow_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, event->xy); + if (treerow_but) { + BLI_assert(treerow_but->but.type == UI_BTYPE_TREEROW); + UI_tree_view_item_context_menu_build( + C, treerow_but->tree_item, uiLayoutColumn(layout, false)); + uiItemS(layout); + } + } + /* If the button represents an id, it can set the "id" context pointer. */ - if (U.experimental.use_asset_browser && ED_asset_can_make_single_from_context(C)) { + if (ED_asset_can_mark_single_from_context(C)) { ID *id = CTX_data_pointer_get_type(C, "id", &RNA_ID).data; /* Gray out items depending on if data-block is an asset. Preferably this could be done via @@ -1226,6 +1211,19 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but) } } + /* UI List item context menu. Scripts can add items to it, by default there's nothing shown. */ + const ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C); + const bool is_inside_listbox = ui_list_find_mouse_over(region, event) != NULL; + const bool is_inside_listrow = is_inside_listbox ? + ui_list_row_find_mouse_over(region, event->xy) != NULL : + false; + if (is_inside_listrow) { + MenuType *mt = WM_menutype_find("UI_MT_list_item_context_menu", true); + if (mt) { + UI_menutype_draw(C, mt, uiLayoutColumn(layout, false)); + } + } + MenuType *mt = WM_menutype_find("WM_MT_button_context", true); if (mt) { UI_menutype_draw(C, mt, uiLayoutColumn(layout, false)); @@ -1259,6 +1257,9 @@ void ui_popup_context_menu_for_panel(bContext *C, ARegion *region, Panel *panel) if (panel->type->parent != NULL) { return; } + if (!UI_panel_can_be_pinned(panel)) { + return; + } PointerRNA ptr; RNA_pointer_create(&screen->id, &RNA_Panel, panel, &ptr); diff --git a/source/blender/editors/interface/interface_context_path.cc b/source/blender/editors/interface/interface_context_path.cc new file mode 100644 index 00000000000..b0f8d186afa --- /dev/null +++ b/source/blender/editors/interface/interface_context_path.cc @@ -0,0 +1,85 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + */ + +#include "BLI_vector.hh" + +#include "BKE_screen.h" + +#include "RNA_access.h" + +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_interface.hh" +#include "UI_resources.h" + +#include "WM_api.h" + +namespace blender::ui { + +void context_path_add_generic(Vector<ContextPathItem> &path, + StructRNA &rna_type, + void *ptr, + const BIFIconID icon_override) +{ + /* Add the null check here to make calling functions less verbose. */ + if (!ptr) { + return; + } + + PointerRNA rna_ptr; + RNA_pointer_create(nullptr, &rna_type, ptr, &rna_ptr); + char name[128]; + RNA_struct_name_get_alloc(&rna_ptr, name, sizeof(name), nullptr); + + /* Use a blank icon by default to check whether to retrieve it automatically from the type. */ + const BIFIconID icon = icon_override == ICON_NONE ? + static_cast<BIFIconID>(RNA_struct_ui_icon(rna_ptr.type)) : + icon_override; + + path.append({name, static_cast<int>(icon)}); +} + +/* -------------------------------------------------------------------- */ +/** \name Breadcrumb Template + * \{ */ + +void template_breadcrumbs(uiLayout &layout, Span<ContextPathItem> context_path) +{ + uiLayout *row = uiLayoutRow(&layout, true); + uiLayoutSetAlignment(&layout, UI_LAYOUT_ALIGN_LEFT); + + for (const int i : context_path.index_range()) { + uiLayout *sub_row = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub_row, UI_LAYOUT_ALIGN_LEFT); + + if (i > 0) { + uiItemL(sub_row, "", ICON_RIGHTARROW_THIN); + } + uiItemL(sub_row, context_path[i].name.c_str(), context_path[i].icon); + } +} + +} // namespace blender::ui + +/** \} */
\ No newline at end of file diff --git a/source/blender/editors/interface/interface_draw.c b/source/blender/editors/interface/interface_draw.c index 05b6fcdded1..e45a5fc61c6 100644 --- a/source/blender/editors/interface/interface_draw.c +++ b/source/blender/editors/interface/interface_draw.c @@ -106,14 +106,30 @@ void UI_draw_roundbox_4fv_ex(const rctf *rect, .color_inner1[1] = inner1 ? inner1[1] : 0.0f, .color_inner1[2] = inner1 ? inner1[2] : 0.0f, .color_inner1[3] = inner1 ? inner1[3] : 0.0f, - .color_inner2[0] = inner2 ? inner2[0] : inner1 ? inner1[0] : 0.0f, - .color_inner2[1] = inner2 ? inner2[1] : inner1 ? inner1[1] : 0.0f, - .color_inner2[2] = inner2 ? inner2[2] : inner1 ? inner1[2] : 0.0f, - .color_inner2[3] = inner2 ? inner2[3] : inner1 ? inner1[3] : 0.0f, - .color_outline[0] = outline ? outline[0] : inner1 ? inner1[0] : 0.0f, - .color_outline[1] = outline ? outline[1] : inner1 ? inner1[1] : 0.0f, - .color_outline[2] = outline ? outline[2] : inner1 ? inner1[2] : 0.0f, - .color_outline[3] = outline ? outline[3] : inner1 ? inner1[3] : 0.0f, + .color_inner2[0] = inner2 ? inner2[0] : + inner1 ? inner1[0] : + 0.0f, + .color_inner2[1] = inner2 ? inner2[1] : + inner1 ? inner1[1] : + 0.0f, + .color_inner2[2] = inner2 ? inner2[2] : + inner1 ? inner1[2] : + 0.0f, + .color_inner2[3] = inner2 ? inner2[3] : + inner1 ? inner1[3] : + 0.0f, + .color_outline[0] = outline ? outline[0] : + inner1 ? inner1[0] : + 0.0f, + .color_outline[1] = outline ? outline[1] : + inner1 ? inner1[1] : + 0.0f, + .color_outline[2] = outline ? outline[2] : + inner1 ? inner1[2] : + 0.0f, + .color_outline[3] = outline ? outline[3] : + inner1 ? inner1[3] : + 0.0f, .shade_dir = shade_dir, .alpha_discard = 1.0f, }; @@ -162,35 +178,6 @@ void UI_draw_roundbox_4fv(const rctf *rect, bool filled, float rad, const float UI_draw_roundbox_4fv_ex(rect, (filled) ? col : NULL, NULL, 1.0f, col, U.pixelsize, rad); } -/* linear horizontal shade within button or in outline */ -/* view2d scrollers use it */ -void UI_draw_roundbox_shade_x( - const rctf *rect, bool filled, float rad, float shadetop, float shadedown, const float col[4]) -{ - float inner1[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - float inner2[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - float outline[4]; - - if (filled) { - inner1[0] = min_ff(1.0f, col[0] + shadetop); - inner1[1] = min_ff(1.0f, col[1] + shadetop); - inner1[2] = min_ff(1.0f, col[2] + shadetop); - inner1[3] = 1.0f; - inner2[0] = max_ff(0.0f, col[0] + shadedown); - inner2[1] = max_ff(0.0f, col[1] + shadedown); - inner2[2] = max_ff(0.0f, col[2] + shadedown); - inner2[3] = 1.0f; - } - - /* TODO: non-filled box don't have gradients. Just use middle color. */ - outline[0] = clamp_f(col[0] + shadetop + shadedown, 0.0f, 1.0f); - outline[1] = clamp_f(col[1] + shadetop + shadedown, 0.0f, 1.0f); - outline[2] = clamp_f(col[2] + shadetop + shadedown, 0.0f, 1.0f); - outline[3] = clamp_f(col[3] + shadetop + shadedown, 0.0f, 1.0f); - - UI_draw_roundbox_4fv_ex(rect, inner1, inner2, 1.0f, outline, U.pixelsize, rad); -} - void UI_draw_text_underline(int pos_x, int pos_y, int len, int height, const float color[4]) { const int ofs_y = 4 * U.pixelsize; @@ -229,7 +216,7 @@ void ui_draw_but_TAB_outline(const rcti *rect, {0.98, 0.805}, }; - /* mult */ + /* Multiply. */ for (a = 0; a < 4; a++) { mul_v2_fl(vec[a], rad); } @@ -592,7 +579,7 @@ static void waveform_draw_one(float *waveform, int nbr, const float col[3]) GPU_vertbuf_attr_fill(vbo, pos_id, waveform); - /* TODO store the GPUBatch inside the scope */ + /* TODO: store the #GPUBatch inside the scope. */ GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO); GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_UNIFORM_COLOR); GPU_batch_uniform_4f(batch, "color", col[0], col[1], col[2], 1.0f); @@ -1844,13 +1831,13 @@ void ui_draw_but_CURVEPROFILE(ARegion *region, /* Also add the last points on the right and bottom edges to close off the fill polygon. */ const bool add_left_tri = profile->view_rect.xmin < 0.0f; const bool add_bottom_tri = profile->view_rect.ymin < 0.0f; - uint tot_points = (uint)PROF_TABLE_LEN(profile->path_len) + 1 + add_left_tri + add_bottom_tri; + int tot_points = BKE_curveprofile_table_size(profile) + 1 + add_left_tri + add_bottom_tri; const uint tot_triangles = tot_points - 2; /* Create array of the positions of the table's points. */ float(*table_coords)[2] = MEM_mallocN(sizeof(*table_coords) * tot_points, "table x coords"); - for (uint i = 0; i < (uint)PROF_TABLE_LEN(profile->path_len); - i++) { /* Only add the points from the table here. */ + for (uint i = 0; i < (uint)BKE_curveprofile_table_size(profile); i++) { + /* Only add the points from the table here. */ table_coords[i][0] = pts[i].x; table_coords[i][1] = pts[i].y; } @@ -1887,44 +1874,50 @@ void ui_draw_but_CURVEPROFILE(ARegion *region, } /* Calculate the table point indices of the triangles for the profile's fill. */ - uint(*tri_indices)[3] = MEM_mallocN(sizeof(*tri_indices) * tot_triangles, "return tri indices"); - BLI_polyfill_calc(table_coords, tot_points, -1, tri_indices); + if (tot_triangles > 0) { + uint(*tri_indices)[3] = MEM_mallocN(sizeof(*tri_indices) * tot_triangles, __func__); + BLI_polyfill_calc(table_coords, tot_points, -1, tri_indices); - /* Draw the triangles for the profile fill. */ - immUniformColor3ubvAlpha((const uchar *)wcol->item, 128); - GPU_blend(GPU_BLEND_ALPHA); - GPU_polygon_smooth(false); - immBegin(GPU_PRIM_TRIS, 3 * tot_triangles); - for (uint i = 0; i < tot_triangles; i++) { - for (uint j = 0; j < 3; j++) { - uint *tri = tri_indices[i]; - fx = rect->xmin + zoomx * (table_coords[tri[j]][0] - offsx); - fy = rect->ymin + zoomy * (table_coords[tri[j]][1] - offsy); - immVertex2f(pos, fx, fy); + /* Draw the triangles for the profile fill. */ + immUniformColor3ubvAlpha((const uchar *)wcol->item, 128); + GPU_blend(GPU_BLEND_ALPHA); + GPU_polygon_smooth(false); + immBegin(GPU_PRIM_TRIS, 3 * tot_triangles); + for (uint i = 0; i < tot_triangles; i++) { + for (uint j = 0; j < 3; j++) { + uint *tri = tri_indices[i]; + fx = rect->xmin + zoomx * (table_coords[tri[j]][0] - offsx); + fy = rect->ymin + zoomy * (table_coords[tri[j]][1] - offsy); + immVertex2f(pos, fx, fy); + } } + immEnd(); + MEM_freeN(tri_indices); } - immEnd(); - MEM_freeN(tri_indices); /* Draw the profile's path so the edge stands out a bit. */ tot_points -= (add_left_tri + add_left_tri); - GPU_line_width(1.0f); - immUniformColor3ubvAlpha((const uchar *)wcol->item, 255); - GPU_line_smooth(true); - immBegin(GPU_PRIM_LINE_STRIP, tot_points - 1); - for (uint i = 0; i < tot_points - 1; i++) { - fx = rect->xmin + zoomx * (table_coords[i][0] - offsx); - fy = rect->ymin + zoomy * (table_coords[i][1] - offsy); - immVertex2f(pos, fx, fy); + const int edges_len = tot_points - 1; + if (edges_len > 0) { + GPU_line_width(1.0f); + immUniformColor3ubvAlpha((const uchar *)wcol->item, 255); + GPU_line_smooth(true); + immBegin(GPU_PRIM_LINE_STRIP, tot_points); + for (int i = 0; i < tot_points; i++) { + fx = rect->xmin + zoomx * (table_coords[i][0] - offsx); + fy = rect->ymin + zoomy * (table_coords[i][1] - offsy); + immVertex2f(pos, fx, fy); + } + immEnd(); } - immEnd(); - MEM_freeN(table_coords); + + MEM_SAFE_FREE(table_coords); /* Draw the handles for the selected control points. */ pts = profile->path; - tot_points = (uint)profile->path_len; + const int path_len = tot_points = (uint)profile->path_len; int selected_free_points = 0; - for (uint i = 0; i < tot_points; i++) { + for (int i = 0; i < path_len; i++) { if (point_draw_handles(&pts[i])) { selected_free_points++; } @@ -1936,7 +1929,7 @@ void ui_draw_but_CURVEPROFILE(ARegion *region, GPU_line_smooth(true); immBegin(GPU_PRIM_LINES, selected_free_points * 4); float ptx, pty; - for (uint i = 0; i < tot_points; i++) { + for (int i = 0; i < path_len; i++) { if (point_draw_handles(&pts[i])) { ptx = rect->xmin + zoomx * (pts[i].x - offsx); pty = rect->ymin + zoomy * (pts[i].y - offsy); @@ -1980,16 +1973,18 @@ void ui_draw_but_CURVEPROFILE(ARegion *region, /* Draw the control points. */ GPU_line_smooth(false); - GPU_blend(GPU_BLEND_NONE); - GPU_point_size(max_ff(3.0f, min_ff(UI_DPI_FAC / but->block->aspect * 5.0f, 5.0f))); - immBegin(GPU_PRIM_POINTS, tot_points); - for (uint i = 0; i < tot_points; i++) { - fx = rect->xmin + zoomx * (pts[i].x - offsx); - fy = rect->ymin + zoomy * (pts[i].y - offsy); - immAttr4fv(col, (pts[i].flag & PROF_SELECT) ? color_vert_select : color_vert); - immVertex2f(pos, fx, fy); + if (path_len > 0) { + GPU_blend(GPU_BLEND_NONE); + GPU_point_size(max_ff(3.0f, min_ff(UI_DPI_FAC / but->block->aspect * 5.0f, 5.0f))); + immBegin(GPU_PRIM_POINTS, path_len); + for (int i = 0; i < path_len; i++) { + fx = rect->xmin + zoomx * (pts[i].x - offsx); + fy = rect->ymin + zoomy * (pts[i].y - offsy); + immAttr4fv(col, (pts[i].flag & PROF_SELECT) ? color_vert_select : color_vert); + immVertex2f(pos, fx, fy); + } + immEnd(); } - immEnd(); /* Draw the handle points. */ if (selected_free_points > 0) { @@ -1997,7 +1992,7 @@ void ui_draw_but_CURVEPROFILE(ARegion *region, GPU_blend(GPU_BLEND_NONE); GPU_point_size(max_ff(2.0f, min_ff(UI_DPI_FAC / but->block->aspect * 4.0f, 4.0f))); immBegin(GPU_PRIM_POINTS, selected_free_points * 2); - for (uint i = 0; i < tot_points; i++) { + for (int i = 0; i < path_len; i++) { if (point_draw_handles(&pts[i])) { fx = rect->xmin + zoomx * (pts[i].h1_loc[0] - offsx); fy = rect->ymin + zoomy * (pts[i].h1_loc[1] - offsy); @@ -2015,11 +2010,11 @@ void ui_draw_but_CURVEPROFILE(ARegion *region, /* Draw the sampled points in addition to the control points if they have been created */ pts = profile->segments; - tot_points = (uint)profile->segments_len; - if (tot_points > 0 && pts) { + const int segments_len = (uint)profile->segments_len; + if (segments_len > 0 && pts) { GPU_point_size(max_ff(2.0f, min_ff(UI_DPI_FAC / but->block->aspect * 3.0f, 3.0f))); - immBegin(GPU_PRIM_POINTS, tot_points); - for (uint i = 0; i < tot_points; i++) { + immBegin(GPU_PRIM_POINTS, segments_len); + for (int i = 0; i < segments_len; i++) { fx = rect->xmin + zoomx * (pts[i].x - offsx); fy = rect->ymin + zoomy * (pts[i].y - offsy); immAttr4fv(col, color_sample); @@ -2223,9 +2218,8 @@ void ui_draw_but_TRACKPREVIEW(ARegion *UNUSED(region), /* ****************************************************** */ -/* TODO: high quality UI drop shadows using GLSL shader and single draw call - * would replace / modify the following 3 functions - merwin - */ +/* TODO(merwin): high quality UI drop shadows using GLSL shader and single draw call + * would replace / modify the following 3 functions. */ static void ui_shadowbox(const rctf *rect, uint pos, uint color, float shadsize, uchar alpha) { @@ -2266,7 +2260,7 @@ static void ui_shadowbox(const rctf *rect, uint pos, uint color, float shadsize, immVertex2fv(pos, v3); /* corner shape */ - /* immAttr4ub(color, 0, 0, 0, alpha); */ /* Not needed, done above in previous tri */ + // immAttr4ub(color, 0, 0, 0, alpha); /* Not needed, done above in previous tri. */ immVertex2fv(pos, v3); immAttr4ub(color, 0, 0, 0, 0); immVertex2fv(pos, v4); @@ -2278,7 +2272,7 @@ static void ui_shadowbox(const rctf *rect, uint pos, uint color, float shadsize, immVertex2fv(pos, v3); /* bottom quad */ - /* immAttr4ub(color, 0, 0, 0, alpha); */ /* Not needed, done above in previous tri */ + // immAttr4ub(color, 0, 0, 0, alpha); /* Not needed, done above in previous tri. */ immVertex2fv(pos, v3); immAttr4ub(color, 0, 0, 0, 0); immVertex2fv(pos, v6); @@ -2350,7 +2344,7 @@ void ui_draw_dropshadow( true, rct->xmin - a, rct->ymin - a, rct->xmax + a, rct->ymax - 10.0f + a, rad + a, color); #endif /* Compute final visibility to match old method result. */ - /* TODO we could just find a better fit function inside the shader instead of this. */ + /* TODO: we could just find a better fit function inside the shader instead of this. */ visibility = visibility * (1.0f - calpha); calpha += dalpha; } diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc new file mode 100644 index 00000000000..369efa7c52e --- /dev/null +++ b/source/blender/editors/interface/interface_dropboxes.cc @@ -0,0 +1,95 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include "BKE_context.h" + +#include "DNA_space_types.h" + +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" + +#include "WM_api.h" + +#include "UI_interface.h" + +static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) +{ + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, + event->xy); + if (!hovered_tree_item) { + return false; + } + + if (drag->drop_state.free_disabled_info) { + MEM_SAFE_FREE(drag->drop_state.disabled_info); + } + + drag->drop_state.free_disabled_info = false; + return UI_tree_view_item_can_drop(hovered_tree_item, drag, &drag->drop_state.disabled_info); +} + +static char *ui_tree_view_drop_tooltip(bContext *C, + wmDrag *drag, + const int xy[2], + wmDropBox *UNUSED(drop)) +{ + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, xy); + if (!hovered_tree_item) { + return nullptr; + } + + return UI_tree_view_item_drop_tooltip(hovered_tree_item, drag); +} + +/* ---------------------------------------------------------------------- */ + +static bool ui_drop_name_poll(struct bContext *C, wmDrag *drag, const wmEvent *UNUSED(event)) +{ + return UI_but_active_drop_name(C) && (drag->type == WM_DRAG_ID); +} + +static void ui_drop_name_copy(wmDrag *drag, wmDropBox *drop) +{ + const ID *id = WM_drag_get_local_ID(drag, 0); + RNA_string_set(drop->ptr, "string", id->name + 2); +} + +/* ---------------------------------------------------------------------- */ + +void ED_dropboxes_ui() +{ + ListBase *lb = WM_dropboxmap_find("User Interface", SPACE_EMPTY, 0); + + WM_dropbox_add(lb, + "UI_OT_tree_view_drop", + ui_tree_view_drop_poll, + nullptr, + nullptr, + ui_tree_view_drop_tooltip); + WM_dropbox_add(lb, + "UI_OT_drop_name", + ui_drop_name_poll, + ui_drop_name_copy, + WM_drag_free_imported_drag_ID, + nullptr); +} diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c index b52bfc81b7a..395ecc77ef4 100644 --- a/source/blender/editors/interface/interface_eyedropper.c +++ b/source/blender/editors/interface/interface_eyedropper.c @@ -24,6 +24,8 @@ #include "DNA_screen_types.h" #include "DNA_space_types.h" +#include "BLI_math_color.h" + #include "BKE_context.h" #include "BKE_screen.h" @@ -107,8 +109,13 @@ static void eyedropper_draw_cursor_text_ex(const int x, const int y, const char { const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - const float col_fg[4] = {1.0f, 1.0f, 1.0f, 1.0f}; - const float col_bg[4] = {0.0f, 0.0f, 0.0f, 0.2f}; + /* Use the theme settings from tooltips. */ + const bTheme *btheme = UI_GetTheme(); + const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip; + + float col_fg[4], col_bg[4]; + rgba_uchar_to_float(col_fg, wcol->text); + rgba_uchar_to_float(col_bg, wcol->inner); UI_fontstyle_draw_simple_backdrop(fstyle, x, y + U.widget_unit, name, col_fg, col_bg); } @@ -119,30 +126,19 @@ void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const cha return; } - const int x = window->eventstate->x; - const int y = window->eventstate->y; + const int x = window->eventstate->xy[0]; + const int y = window->eventstate->xy[1]; eyedropper_draw_cursor_text_ex(x, y, name); } -void eyedropper_draw_cursor_text_region(const struct bContext *C, - const ARegion *region, - const char *name) +void eyedropper_draw_cursor_text_region(const int x, const int y, const char *name) { - wmWindow *win = CTX_wm_window(C); - const int x = win->eventstate->x; - const int y = win->eventstate->y; - - if ((name[0] == '\0') || (BLI_rcti_isect_pt(®ion->winrct, x, y) == false)) { + if (name[0] == '\0') { return; } - const int mval[2] = { - x - region->winrct.xmin, - y - region->winrct.ymin, - }; - - eyedropper_draw_cursor_text_ex(mval[0], mval[1], name); + eyedropper_draw_cursor_text_ex(x, y, name); } /** @@ -157,8 +153,8 @@ void eyedropper_draw_cursor_text_region(const struct bContext *C, uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event) { bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y); - const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->x, event->y); + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->xy[0], event->xy[1]); + const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->xy[0], event->xy[1]); uiBut *but = ui_but_find_mouse_over(region, event); @@ -168,4 +164,25 @@ uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *ev return but; } +void datadropper_win_area_find( + const bContext *C, const int mval[2], int r_mval[2], wmWindow **r_win, ScrArea **r_area) +{ + bScreen *screen = CTX_wm_screen(C); + + *r_win = CTX_wm_window(C); + *r_area = BKE_screen_find_area_xy(screen, -1, mval[0], mval[1]); + if (*r_area == NULL) { + wmWindowManager *wm = CTX_wm_manager(C); + *r_win = WM_window_find_under_cursor(wm, NULL, *r_win, mval, r_mval); + if (*r_win) { + screen = WM_window_get_active_screen(*r_win); + *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, r_mval[0], r_mval[1]); + } + } + else if (mval != r_mval) { + r_mval[0] = mval[0]; + r_mval[1] = mval[1]; + } +} + /** \} */ diff --git a/source/blender/editors/interface/interface_eyedropper_color.c b/source/blender/editors/interface/interface_eyedropper_color.c index ba72cecc514..c3633e11f83 100644 --- a/source/blender/editors/interface/interface_eyedropper_color.c +++ b/source/blender/editors/interface/interface_eyedropper_color.c @@ -337,52 +337,41 @@ void eyedropper_color_sample_fl(bContext *C, int mx, int my, float r_col[3]) const char *display_device = CTX_data_scene(C)->display_settings.display_device; struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); - wmWindow *win = CTX_wm_window(C); - bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mx, my); - if (area == NULL) { - int mval[2] = {mx, my}; - if (WM_window_find_under_cursor(wm, NULL, win, mval, &win, mval)) { - mx = mval[0]; - my = mval[1]; - screen = WM_window_get_active_screen(win); - area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mx, my); - } - else { - win = NULL; - } - } + wmWindow *win; + ScrArea *area; + int mval[2] = {mx, my}; + datadropper_win_area_find(C, mval, mval, &win, &area); if (area) { if (area->spacetype == SPACE_IMAGE) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mx, my); + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval[0], mval[1]); if (region) { SpaceImage *sima = area->spacedata.first; - int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin}; + int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - if (ED_space_image_color_sample(sima, region, mval, r_col, NULL)) { + if (ED_space_image_color_sample(sima, region, region_mval, r_col, NULL)) { return; } } } else if (area->spacetype == SPACE_NODE) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mx, my); + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval[0], mval[1]); if (region) { SpaceNode *snode = area->spacedata.first; - const int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin}; + int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - if (ED_space_node_color_sample(bmain, snode, region, mval, r_col)) { + if (ED_space_node_color_sample(bmain, snode, region, region_mval, r_col)) { return; } } } else if (area->spacetype == SPACE_CLIP) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mx, my); + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval[0], mval[1]); if (region) { SpaceClip *sc = area->spacedata.first; - int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin}; + int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - if (ED_space_clip_color_sample(sc, region, mval, r_col)) { + if (ED_space_clip_color_sample(sc, region, region_mval, r_col)) { return; } } @@ -391,7 +380,6 @@ void eyedropper_color_sample_fl(bContext *C, int mx, int my, float r_col[3]) if (win) { /* Fallback to simple opengl picker. */ - const int mval[2] = {mx, my}; WM_window_pixel_sample_read(wm, win, mval, r_col); IMB_colormanagement_display_to_scene_linear_v3(r_col, display); } @@ -493,7 +481,7 @@ static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event) case EYE_MODAL_SAMPLE_CONFIRM: { const bool is_undo = eye->is_undo; if (eye->accum_tot == 0) { - eyedropper_color_sample(C, eye, event->x, event->y); + eyedropper_color_sample(C, eye, event->xy[0], event->xy[1]); } eyedropper_exit(C, op); /* Could support finished & undo-skip. */ @@ -502,23 +490,23 @@ static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event) case EYE_MODAL_SAMPLE_BEGIN: /* enable accum and make first sample */ eye->accum_start = true; - eyedropper_color_sample(C, eye, event->x, event->y); + eyedropper_color_sample(C, eye, event->xy[0], event->xy[1]); break; case EYE_MODAL_SAMPLE_RESET: eye->accum_tot = 0; zero_v3(eye->accum_col); - eyedropper_color_sample(C, eye, event->x, event->y); + eyedropper_color_sample(C, eye, event->xy[0], event->xy[1]); break; } } else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { if (eye->accum_start) { /* button is pressed so keep sampling */ - eyedropper_color_sample(C, eye, event->x, event->y); + eyedropper_color_sample(C, eye, event->xy[0], event->xy[1]); } if (eye->draw_handle_sample_text) { - eyedropper_color_sample_text_update(C, eye, event->x, event->y); + eyedropper_color_sample_text_update(C, eye, event->xy[0], event->xy[1]); ED_region_tag_redraw(CTX_wm_region(C)); } } diff --git a/source/blender/editors/interface/interface_eyedropper_colorband.c b/source/blender/editors/interface/interface_eyedropper_colorband.c index d32eb415b19..22320282766 100644 --- a/source/blender/editors/interface/interface_eyedropper_colorband.c +++ b/source/blender/editors/interface/interface_eyedropper_colorband.c @@ -233,7 +233,7 @@ static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent return OPERATOR_CANCELLED; case EYE_MODAL_SAMPLE_CONFIRM: { const bool is_undo = eye->is_undo; - eyedropper_colorband_sample_segment(C, eye, event->x, event->y); + eyedropper_colorband_sample_segment(C, eye, event->xy[0], event->xy[1]); eyedropper_colorband_apply(C, op); eyedropper_colorband_exit(C, op); /* Could support finished & undo-skip. */ @@ -242,10 +242,10 @@ static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent case EYE_MODAL_SAMPLE_BEGIN: /* enable accum and make first sample */ eye->sample_start = true; - eyedropper_colorband_sample_point(C, eye, event->x, event->y); + eyedropper_colorband_sample_point(C, eye, event->xy[0], event->xy[1]); eyedropper_colorband_apply(C, op); - eye->last_x = event->x; - eye->last_y = event->y; + eye->last_x = event->xy[0]; + eye->last_y = event->xy[1]; break; case EYE_MODAL_SAMPLE_RESET: break; @@ -253,7 +253,7 @@ static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent } else if (event->type == MOUSEMOVE) { if (eye->sample_start) { - eyedropper_colorband_sample_segment(C, eye, event->x, event->y); + eyedropper_colorband_sample_segment(C, eye, event->xy[0], event->xy[1]); eyedropper_colorband_apply(C, op); } } @@ -280,7 +280,7 @@ static int eyedropper_colorband_point_modal(bContext *C, wmOperator *op, const w } break; case EYE_MODAL_POINT_SAMPLE: - eyedropper_colorband_sample_point(C, eye, event->x, event->y); + eyedropper_colorband_sample_point(C, eye, event->xy[0], event->xy[1]); eyedropper_colorband_apply(C, op); if (eye->color_buffer_len == MAXCOLORBAND) { eyedropper_colorband_exit(C, op); diff --git a/source/blender/editors/interface/interface_eyedropper_datablock.c b/source/blender/editors/interface/interface_eyedropper_datablock.c index 8c605598cbc..261aa997d06 100644 --- a/source/blender/editors/interface/interface_eyedropper_datablock.c +++ b/source/blender/editors/interface/interface_eyedropper_datablock.c @@ -71,13 +71,16 @@ typedef struct DataDropper { ScrArea *cursor_area; /* Area under the cursor */ ARegionType *art; void *draw_handle_pixel; + int name_pos[2]; char name[200]; } DataDropper; -static void datadropper_draw_cb(const struct bContext *C, ARegion *region, void *arg) +static void datadropper_draw_cb(const struct bContext *UNUSED(C), + ARegion *UNUSED(region), + void *arg) { DataDropper *ddr = arg; - eyedropper_draw_cursor_text_region(C, region, ddr->name); + eyedropper_draw_cursor_text_region(UNPACK2(ddr->name_pos), ddr->name); } static int datadropper_init(bContext *C, wmOperator *op) @@ -148,12 +151,10 @@ static void datadropper_exit(bContext *C, wmOperator *op) /** * \brief get the ID from the 3D view or outliner. */ -static void datadropper_id_sample_pt(bContext *C, DataDropper *ddr, int mx, int my, ID **r_id) +static void datadropper_id_sample_pt( + bContext *C, wmWindow *win, ScrArea *area, DataDropper *ddr, int mx, int my, ID **r_id) { - /* we could use some clever */ - bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, -1, mx, my); - + wmWindow *win_prev = CTX_wm_window(C); ScrArea *area_prev = CTX_wm_area(C); ARegion *region_prev = CTX_wm_region(C); @@ -166,6 +167,7 @@ static void datadropper_id_sample_pt(bContext *C, DataDropper *ddr, int mx, int const int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin}; Base *base; + CTX_wm_window_set(C, win); CTX_wm_area_set(C, area); CTX_wm_region_set(C, region); @@ -202,11 +204,15 @@ static void datadropper_id_sample_pt(bContext *C, DataDropper *ddr, int mx, int BLI_snprintf(ddr->name, sizeof(ddr->name), "%s: %s", ddr->idcode_name, id->name + 2); *r_id = id; } + + ddr->name_pos[0] = mval[0]; + ddr->name_pos[1] = mval[1]; } } } } + CTX_wm_window_set(C, win_prev); CTX_wm_area_set(C, area_prev); CTX_wm_region_set(C, region_prev); } @@ -232,7 +238,13 @@ static bool datadropper_id_sample(bContext *C, DataDropper *ddr, int mx, int my) { ID *id = NULL; - datadropper_id_sample_pt(C, ddr, mx, my, &id); + wmWindow *win; + ScrArea *area; + + int mval[] = {mx, my}; + datadropper_win_area_find(C, mval, mval, &win, &area); + + datadropper_id_sample_pt(C, win, area, ddr, mval[0], mval[1], &id); return datadropper_id_set(C, ddr, id); } @@ -244,14 +256,8 @@ static void datadropper_cancel(bContext *C, wmOperator *op) } /* To switch the draw callback when region under mouse event changes */ -static void datadropper_set_draw_callback_region(bContext *C, - DataDropper *ddr, - const int mx, - const int my) +static void datadropper_set_draw_callback_region(ScrArea *area, DataDropper *ddr) { - bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, -1, mx, my); - if (area) { /* If spacetype changed */ if (area->spacetype != ddr->cursor_area->spacetype) { @@ -286,7 +292,7 @@ static int datadropper_modal(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_CANCELLED; case EYE_MODAL_SAMPLE_CONFIRM: { const bool is_undo = ddr->is_undo; - const bool success = datadropper_id_sample(C, ddr, event->x, event->y); + const bool success = datadropper_id_sample(C, ddr, event->xy[0], event->xy[1]); datadropper_exit(C, op); if (success) { /* Could support finished & undo-skip. */ @@ -300,10 +306,16 @@ static int datadropper_modal(bContext *C, wmOperator *op, const wmEvent *event) else if (event->type == MOUSEMOVE) { ID *id = NULL; + wmWindow *win; + ScrArea *area; + + int mval[] = {event->xy[0], event->xy[1]}; + datadropper_win_area_find(C, mval, mval, &win, &area); + /* Set the region for eyedropper cursor text drawing */ - datadropper_set_draw_callback_region(C, ddr, event->x, event->y); + datadropper_set_draw_callback_region(area, ddr); - datadropper_id_sample_pt(C, ddr, event->x, event->y, &id); + datadropper_id_sample_pt(C, win, area, ddr, mval[0], mval[1], &id); } return OPERATOR_RUNNING_MODAL; diff --git a/source/blender/editors/interface/interface_eyedropper_depth.c b/source/blender/editors/interface/interface_eyedropper_depth.c index a64fad8c333..4172c474f4a 100644 --- a/source/blender/editors/interface/interface_eyedropper_depth.c +++ b/source/blender/editors/interface/interface_eyedropper_depth.c @@ -72,13 +72,16 @@ typedef struct DepthDropper { ARegionType *art; void *draw_handle_pixel; + int name_pos[2]; char name[200]; } DepthDropper; -static void depthdropper_draw_cb(const struct bContext *C, ARegion *region, void *arg) +static void depthdropper_draw_cb(const struct bContext *UNUSED(C), + ARegion *UNUSED(region), + void *arg) { DepthDropper *ddr = arg; - eyedropper_draw_cursor_text_region(C, region, ddr->name); + eyedropper_draw_cursor_text_region(UNPACK2(ddr->name_pos), ddr->name); } static int depthdropper_init(bContext *C, wmOperator *op) @@ -172,6 +175,8 @@ static void depthdropper_depth_sample_pt( /* weak, we could pass in some reference point */ const float *view_co = v3d->camera ? v3d->camera->obmat[3] : rv3d->viewinv[3]; const int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin}; + copy_v2_v2_int(ddr->name_pos, mval); + float co[3]; CTX_wm_area_set(C, area); @@ -271,7 +276,7 @@ static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) case EYE_MODAL_SAMPLE_CONFIRM: { const bool is_undo = ddr->is_undo; if (ddr->accum_tot == 0) { - depthdropper_depth_sample(C, ddr, event->x, event->y); + depthdropper_depth_sample(C, ddr, event->xy[0], event->xy[1]); } else { depthdropper_depth_set_accum(C, ddr); @@ -283,12 +288,12 @@ static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) case EYE_MODAL_SAMPLE_BEGIN: /* enable accum and make first sample */ ddr->accum_start = true; - depthdropper_depth_sample_accum(C, ddr, event->x, event->y); + depthdropper_depth_sample_accum(C, ddr, event->xy[0], event->xy[1]); break; case EYE_MODAL_SAMPLE_RESET: ddr->accum_tot = 0; ddr->accum_depth = 0.0f; - depthdropper_depth_sample_accum(C, ddr, event->x, event->y); + depthdropper_depth_sample_accum(C, ddr, event->xy[0], event->xy[1]); depthdropper_depth_set_accum(C, ddr); break; } @@ -296,7 +301,7 @@ static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) else if (event->type == MOUSEMOVE) { if (ddr->accum_start) { /* button is pressed so keep sampling */ - depthdropper_depth_sample_accum(C, ddr, event->x, event->y); + depthdropper_depth_sample_accum(C, ddr, event->xy[0], event->xy[1]); depthdropper_depth_set_accum(C, ddr); } } diff --git a/source/blender/editors/interface/interface_eyedropper_driver.c b/source/blender/editors/interface/interface_eyedropper_driver.c index 8762a4819d4..ccf0e727da8 100644 --- a/source/blender/editors/interface/interface_eyedropper_driver.c +++ b/source/blender/editors/interface/interface_eyedropper_driver.c @@ -84,10 +84,7 @@ static void driverdropper_exit(bContext *C, wmOperator *op) { WM_cursor_modal_restore(CTX_wm_window(C)); - if (op->customdata) { - MEM_freeN(op->customdata); - op->customdata = NULL; - } + MEM_SAFE_FREE(op->customdata); } static void driverdropper_sample(bContext *C, wmOperator *op, const wmEvent *event) diff --git a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c index 417807afff1..d76ff84bcad 100644 --- a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c +++ b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c @@ -292,7 +292,7 @@ static int eyedropper_gpencil_modal(bContext *C, wmOperator *op, const wmEvent * return OPERATOR_CANCELLED; } case EYE_MODAL_SAMPLE_CONFIRM: { - eyedropper_gpencil_color_sample(C, eye, event->x, event->y); + eyedropper_gpencil_color_sample(C, eye, event->xy[0], event->xy[1]); /* Create material. */ eyedropper_gpencil_color_set(C, event, eye); @@ -309,7 +309,7 @@ static int eyedropper_gpencil_modal(bContext *C, wmOperator *op, const wmEvent * } case MOUSEMOVE: case INBETWEEN_MOUSEMOVE: { - eyedropper_gpencil_color_sample(C, eye, event->x, event->y); + eyedropper_gpencil_color_sample(C, eye, event->xy[0], event->xy[1]); break; } default: { diff --git a/source/blender/editors/interface/interface_eyedropper_intern.h b/source/blender/editors/interface/interface_eyedropper_intern.h index 96a2c6ed111..f9f3fcfb5d1 100644 --- a/source/blender/editors/interface/interface_eyedropper_intern.h +++ b/source/blender/editors/interface/interface_eyedropper_intern.h @@ -24,10 +24,13 @@ /* interface_eyedropper.c */ void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name); -void eyedropper_draw_cursor_text_region(const struct bContext *C, - const struct ARegion *region, - const char *name); +void eyedropper_draw_cursor_text_region(const int x, const int y, const char *name); uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event); +void datadropper_win_area_find(const struct bContext *C, + const int mval[2], + int r_mval[2], + struct wmWindow **r_win, + struct ScrArea **r_area); /* interface_eyedropper_color.c (expose for color-band picker) */ void eyedropper_color_sample_fl(bContext *C, int mx, int my, float r_col[3]); diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 542a226ee68..35e1526d079 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -35,10 +35,12 @@ #include "DNA_scene_types.h" #include "DNA_screen_types.h" +#include "BLI_array_utils.h" #include "BLI_linklist.h" #include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_rect.h" +#include "BLI_sort_utils.h" #include "BLI_string.h" #include "BLI_string_cursor_utf8.h" #include "BLI_string_utf8.h" @@ -170,6 +172,20 @@ static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but static void ui_textedit_string_set(uiBut *but, struct uiHandleButtonData *data, const char *str); static void button_tooltip_timer_reset(bContext *C, uiBut *but); +static void ui_block_interaction_begin_ensure(bContext *C, + uiBlock *block, + struct uiHandleButtonData *data, + const bool is_click); +static struct uiBlockInteraction_Handle *ui_block_interaction_begin(struct bContext *C, + uiBlock *block, + const bool is_click); +static void ui_block_interaction_end(struct bContext *C, + uiBlockInteraction_CallbackData *callbacks, + struct uiBlockInteraction_Handle *interaction); +static void ui_block_interaction_update(struct bContext *C, + uiBlockInteraction_CallbackData *callbacks, + struct uiBlockInteraction_Handle *interaction); + #ifdef USE_KEYNAV_LIMIT static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event); static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event); @@ -225,6 +241,19 @@ typedef enum uiMenuScrollType { MENU_SCROLL_BOTTOM, } uiMenuScrollType; +typedef struct uiBlockInteraction_Handle { + struct uiBlockInteraction_Params params; + void *user_data; + /** + * This is shared between #uiHandleButtonData and #uiAfterFunc, + * the last user runs the end callback and frees the data. + * + * This is needed as the order of freeing changes depending on + * accepting/canceling the operation. + */ + int user_count; +} uiBlockInteraction_Handle; + #ifdef USE_ALLSELECT /* Unfortunately there's no good way handle more generally: @@ -273,7 +302,7 @@ static void ui_selectcontext_apply(bContext *C, /** * how far to drag before we check for gesture direction (in pixels), - * note: half the height of a button is about right... */ + * NOTE: half the height of a button is about right... */ # define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4) /** @@ -329,7 +358,7 @@ typedef struct uiHandleButtonMulti { * here so we can tell if this is a vertical motion or not. */ float drag_dir[2]; - /* values copied direct from event->x,y + /* values copied direct from event->xy * used to detect buttons between the current and initial mouse position */ int drag_start[2]; @@ -355,6 +384,8 @@ typedef struct uiHandleButtonData { /* booleans (could be made into flags) */ bool cancel, escapecancel; bool applied, applied_interactive; + /* Button is being applied through an extra icon. */ + bool apply_through_extra_icon; bool changed_cursor; wmTimer *flashtimer; @@ -364,9 +395,6 @@ typedef struct uiHandleButtonData { char *origstr; double value, origvalue, startvalue; float vec[3], origvec[3]; -#if 0 /* UNUSED */ - int togdual, togonly; -#endif ColorBand *coba; /* Tool-tip. */ @@ -430,6 +458,8 @@ typedef struct uiHandleButtonData { uiSelectContextStore select_others; #endif + struct uiBlockInteraction_Handle *custom_interaction_handle; + /* Text field undo. */ struct uiUndoStack_Text *undo_stack_text; @@ -462,18 +492,22 @@ typedef struct uiAfterFunc { wmOperator *popup_op; wmOperatorType *optype; - int opcontext; + wmOperatorCallContext opcontext; PointerRNA *opptr; PointerRNA rnapoin; PropertyRNA *rnaprop; void *search_arg; - uiButSearchArgFreeFn search_arg_free_fn; + uiFreeArgFunc search_arg_free_fn; + + uiBlockInteraction_CallbackData custom_interaction_callbacks; + uiBlockInteraction_Handle *custom_interaction_handle; bContextStore *context; char undostr[BKE_UNDO_STR_MAX]; + char drawstr[UI_MAX_DRAW_STR]; } uiAfterFunc; static void button_activate_init(bContext *C, @@ -733,23 +767,38 @@ static uiAfterFunc *ui_afterfunc_new(void) * For executing operators after the button is pressed. * (some non operator buttons need to trigger operators), see: T37795. * + * \param context_but: A button from which to get the context from (`uiBut.context`) for the + * operator execution. + * + * \note Ownership over \a properties is moved here. The #uiAfterFunc owns it now. * \note Can only call while handling buttons. */ -PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props) +static void ui_handle_afterfunc_add_operator_ex(wmOperatorType *ot, + PointerRNA **properties, + wmOperatorCallContext opcontext, + const uiBut *context_but) { - PointerRNA *ptr = NULL; uiAfterFunc *after = ui_afterfunc_new(); after->optype = ot; after->opcontext = opcontext; + if (properties) { + after->opptr = *properties; + *properties = NULL; + } - if (create_props) { - ptr = MEM_callocN(sizeof(PointerRNA), __func__); - WM_operator_properties_create_ptr(ptr, ot); - after->opptr = ptr; + if (context_but && context_but->context) { + after->context = CTX_store_copy(context_but->context); } - return ptr; + if (context_but) { + ui_but_drawstr_without_sep_char(context_but, after->drawstr, sizeof(after->drawstr)); + } +} + +void ui_handle_afterfunc_add_operator(wmOperatorType *ot, wmOperatorCallContext opcontext) +{ + ui_handle_afterfunc_add_operator_ex(ot, NULL, opcontext, NULL); } static void popup_check(bContext *C, wmOperator *op) @@ -769,72 +818,97 @@ static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but) (block->handle && block->handle->popup_op)); } +/** + * These functions are postponed and only executed after all other + * handling is done, i.e. menus are closed, in order to avoid conflicts + * with these functions removing the buttons we are working with. + */ static void ui_apply_but_func(bContext *C, uiBut *but) { uiBlock *block = but->block; + if (!ui_afterfunc_check(block, but)) { + return; + } - /* these functions are postponed and only executed after all other - * handling is done, i.e. menus are closed, in order to avoid conflicts - * with these functions removing the buttons we are working with */ - - if (ui_afterfunc_check(block, but)) { - uiAfterFunc *after = ui_afterfunc_new(); + uiAfterFunc *after = ui_afterfunc_new(); - if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) { - /* exception, this will crash due to removed button otherwise */ - but->func(C, but->func_arg1, but->func_arg2); - } - else { - after->func = but->func; - } + if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) { + /* exception, this will crash due to removed button otherwise */ + but->func(C, but->func_arg1, but->func_arg2); + } + else { + after->func = but->func; + } - after->func_arg1 = but->func_arg1; - after->func_arg2 = but->func_arg2; + after->func_arg1 = but->func_arg1; + after->func_arg2 = but->func_arg2; - after->funcN = but->funcN; - after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : NULL; + after->funcN = but->funcN; + after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : NULL; - after->rename_func = but->rename_func; - after->rename_arg1 = but->rename_arg1; - after->rename_orig = but->rename_orig; /* needs free! */ + after->rename_func = but->rename_func; + after->rename_arg1 = but->rename_arg1; + after->rename_orig = but->rename_orig; /* needs free! */ - after->handle_func = block->handle_func; - after->handle_func_arg = block->handle_func_arg; - after->retval = but->retval; + after->handle_func = block->handle_func; + after->handle_func_arg = block->handle_func_arg; + after->retval = but->retval; - if (but->type == UI_BTYPE_BUT_MENU) { - after->butm_func = block->butm_func; - after->butm_func_arg = block->butm_func_arg; - after->a2 = but->a2; - } + if (but->type == UI_BTYPE_BUT_MENU) { + after->butm_func = block->butm_func; + after->butm_func_arg = block->butm_func_arg; + after->a2 = but->a2; + } - if (block->handle) { - after->popup_op = block->handle->popup_op; - } + if (block->handle) { + after->popup_op = block->handle->popup_op; + } - after->optype = but->optype; - after->opcontext = but->opcontext; - after->opptr = but->opptr; + after->optype = but->optype; + after->opcontext = but->opcontext; + after->opptr = but->opptr; - after->rnapoin = but->rnapoin; - after->rnaprop = but->rnaprop; + after->rnapoin = but->rnapoin; + after->rnaprop = but->rnaprop; - if (but->type == UI_BTYPE_SEARCH_MENU) { - uiButSearch *search_but = (uiButSearch *)but; - after->search_arg_free_fn = search_but->arg_free_fn; - after->search_arg = search_but->arg; - search_but->arg_free_fn = NULL; - search_but->arg = NULL; - } + if (but->type == UI_BTYPE_SEARCH_MENU) { + uiButSearch *search_but = (uiButSearch *)but; + after->search_arg_free_fn = search_but->arg_free_fn; + after->search_arg = search_but->arg; + search_but->arg_free_fn = NULL; + search_but->arg = NULL; + } - if (but->context) { - after->context = CTX_store_copy(but->context); + if (but->active != NULL) { + uiHandleButtonData *data = but->active; + if (data->custom_interaction_handle != NULL) { + after->custom_interaction_callbacks = block->custom_interaction_callbacks; + after->custom_interaction_handle = data->custom_interaction_handle; + + /* Ensure this callback runs once and last. */ + uiAfterFunc *after_prev = after->prev; + if (after_prev && + (after_prev->custom_interaction_handle == data->custom_interaction_handle)) { + after_prev->custom_interaction_handle = NULL; + memset(&after_prev->custom_interaction_callbacks, + 0x0, + sizeof(after_prev->custom_interaction_callbacks)); + } + else { + after->custom_interaction_handle->user_count++; + } } + } - but->optype = NULL; - but->opcontext = 0; - but->opptr = NULL; + if (but->context) { + after->context = CTX_store_copy(but->context); } + + ui_but_drawstr_without_sep_char(but, after->drawstr, sizeof(after->drawstr)); + + but->optype = NULL; + but->opcontext = 0; + but->opptr = NULL; } /* typically call ui_apply_but_undo(), ui_apply_but_autokey() */ @@ -953,7 +1027,8 @@ static void ui_apply_but_funcs_after(bContext *C) } if (after.optype) { - WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL); + WM_operator_name_call_ptr_with_depends_on_cursor( + C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL, after.drawstr); } if (after.opptr) { @@ -997,6 +1072,18 @@ static void ui_apply_but_funcs_after(bContext *C) after.search_arg_free_fn(after.search_arg); } + if (after.custom_interaction_handle != NULL) { + after.custom_interaction_handle->user_count--; + BLI_assert(after.custom_interaction_handle->user_count >= 0); + if (after.custom_interaction_handle->user_count == 0) { + ui_block_interaction_update( + C, &after.custom_interaction_callbacks, after.custom_interaction_handle); + ui_block_interaction_end( + C, &after.custom_interaction_callbacks, after.custom_interaction_handle); + } + after.custom_interaction_handle = NULL; + } + ui_afterfunc_update_preferences_dirty(&after); if (after.undostr[0]) { @@ -1076,6 +1163,52 @@ static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleBu data->applied = true; } +static void ui_apply_but_TREEROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +{ + if (data->apply_through_extra_icon) { + /* Don't apply this, it would cause unintended tree-row toggling when clicking on extra icons. + */ + return; + } + ui_apply_but_ROW(C, block, but, data); +} + +/** + * \note Ownership of \a properties is moved here. The #uiAfterFunc owns it now. + * + * \param context_but: The button to use context from when calling or polling the operator. + * + * \returns true if the operator was executed, otherwise false. + */ +static bool ui_list_invoke_item_operator(bContext *C, + const uiBut *context_but, + wmOperatorType *ot, + PointerRNA **properties) +{ + if (!ui_but_context_poll_operator(C, ot, context_but)) { + return false; + } + + /* Allow the context to be set from the hovered button, so the list item draw callback can set + * context for the operators. */ + ui_handle_afterfunc_add_operator_ex(ot, properties, WM_OP_INVOKE_DEFAULT, context_but); + return true; +} + +static void ui_apply_but_LISTROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +{ + uiBut *listbox = ui_list_find_from_row(data->region, but); + if (listbox) { + uiList *list = listbox->custom_data; + if (list && list->dyn_data->custom_activate_optype) { + ui_list_invoke_item_operator( + C, but, list->dyn_data->custom_activate_optype, &list->dyn_data->custom_activate_opptr); + } + } + + ui_apply_but_ROW(C, block, but, data); +} + static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data) { if (!data->str) { @@ -1290,8 +1423,8 @@ static bool ui_multibut_states_tag(uiBut *but_active, seg[0][0] = data->multi_data.drag_start[0]; seg[0][1] = data->multi_data.drag_start[1]; - seg[1][0] = event->x; - seg[1][1] = event->y; + seg[1][0] = event->xy[0]; + seg[1][1] = event->xy[1]; BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP); @@ -1350,7 +1483,7 @@ static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *dat } /* edit buttons proportionally to eachother - * note: if we mix buttons which are proportional and others which are not, + * NOTE: if we mix buttons which are proportional and others which are not, * this may work a bit strangely */ if ((but_active->rnaprop && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) || ELEM(but_active->unit_type, RNA_SUBTYPE_UNIT_VALUE(PROP_UNIT_LENGTH))) { @@ -1502,7 +1635,7 @@ static bool ui_drag_toggle_set_xy_xy( ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]); LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - /* Note: ctrl is always true here because (at least for now) + /* NOTE: ctrl is always true here because (at least for now) * we always want to consider text control in this case, even when not embossed. */ if (ui_but_is_interactive(but, true)) { if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) { @@ -1548,7 +1681,7 @@ static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const */ if (drag_info->is_xy_lock_init == false) { /* first store the buttons original coords */ - uiBut *but = ui_but_find_mouse_over_ex(region, xy_input[0], xy_input[1], true); + uiBut *but = ui_but_find_mouse_over_ex(region, xy_input, true, NULL, NULL); if (but) { if (but->flag & UI_BUT_DRAG_LOCK) { @@ -1610,7 +1743,7 @@ static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void break; } case MOUSEMOVE: { - ui_drag_toggle_set(C, drag_info, &event->x); + ui_drag_toggle_set(C, drag_info, event->xy); break; } } @@ -1618,8 +1751,7 @@ static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void if (done) { wmWindow *win = CTX_wm_window(C); const ARegion *region = CTX_wm_region(C); - uiBut *but = ui_but_find_mouse_over_ex( - region, drag_info->xy_init[0], drag_info->xy_init[1], true); + uiBut *but = ui_but_find_mouse_over_ex(region, drag_info->xy_init, true, NULL, NULL); if (but) { ui_apply_but_undo(but); @@ -1686,7 +1818,7 @@ static bool ui_selectcontext_begin(bContext *C, uiBut *but, uiSelectContextStore break; } uiSelectContextElem *other = &selctx_data->elems[i]; - /* TODO,. de-duplicate copy_to_selected_button */ + /* TODO: de-duplicate copy_to_selected_button. */ if (link->ptr.data != ptr.data) { if (use_path_from_id) { /* Path relative to ID. */ @@ -1928,7 +2060,8 @@ static bool ui_but_drag_init(bContext *C, WM_event_drag_threshold(event), (int)((UI_UNIT_Y / 2) * ui_block_to_window_scale(data->region, but->block))); - if (abs(data->dragstartx - event->x) + abs(data->dragstarty - event->y) > drag_threshold) { + if (abs(data->dragstartx - event->xy[0]) + abs(data->dragstarty - event->xy[1]) > + drag_threshold) { button_activate_state(C, but, BUTTON_STATE_EXIT); data->cancel = true; #ifdef USE_DRAG_TOGGLE @@ -1943,8 +2076,8 @@ static bool ui_but_drag_init(bContext *C, drag_info->pushed_state = ui_drag_toggle_but_pushed_state(but); drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect); drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect); - copy_v2_v2_int(drag_info->xy_init, &event->x); - copy_v2_v2_int(drag_info->xy_last, &event->x); + copy_v2_v2_int(drag_info->xy_init, event->xy); + copy_v2_v2_int(drag_info->xy_last, event->xy); /* needed for toggle drag on popups */ region_prev = CTX_wm_region(C); @@ -1987,7 +2120,7 @@ static bool ui_but_drag_init(bContext *C, bool valid = false; uiDragColorHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__); - /* TODO support more button pointer types */ + /* TODO: support more button pointer types. */ if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { ui_but_v3_get(but, drag_info->color); drag_info->gamma_corrected = true; @@ -2012,6 +2145,12 @@ static bool ui_but_drag_init(bContext *C, return false; } } + else if (but->type == UI_BTYPE_TREEROW) { + uiButTreeRow *tree_row_but = (uiButTreeRow *)but; + if (tree_row_but->tree_item) { + UI_tree_view_item_drag_start(C, tree_row_but->tree_item); + } + } else { wmDrag *drag = WM_event_start_drag( C, @@ -2030,6 +2169,12 @@ static bool ui_but_drag_init(bContext *C, BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect)); } + + /* Special feature for assets: We add another drag item that supports multiple assets. It + * gets the assets from context. */ + if (ELEM(but->dragtype, WM_DRAG_ASSET, WM_DRAG_ID)) { + WM_event_start_drag(C, ICON_NONE, WM_DRAG_ASSET_LIST, NULL, 0, WM_DRAG_NOP); + } } return true; } @@ -2181,9 +2326,14 @@ static void ui_apply_but( ui_apply_but_TOG(C, but, data); break; case UI_BTYPE_ROW: - case UI_BTYPE_LISTROW: ui_apply_but_ROW(C, block, but, data); break; + case UI_BTYPE_TREEROW: + ui_apply_but_TREEROW(C, block, but, data); + break; + case UI_BTYPE_LISTROW: + ui_apply_but_LISTROW(C, block, but, data); + break; case UI_BTYPE_DATASETROW: ui_apply_but_ROW(C, block, but, data); break; @@ -2283,38 +2433,10 @@ static void ui_apply_but( uiButCurveProfile *but_profile = (uiButCurveProfile *)but; but_profile->edit_profile = editprofile; } -} - -/** \} */ -/* -------------------------------------------------------------------- */ -/** \name Button Drop Event - * \{ */ - -/* only call if event type is EVT_DROP */ -static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data) -{ - ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */ - - LISTBASE_FOREACH (wmDrag *, wmd, drags) { - /* TODO asset dropping. */ - if (wmd->type == WM_DRAG_ID) { - /* align these types with UI_but_active_drop_name */ - if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - ID *id = WM_drag_get_local_ID(wmd, 0); - - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - - ui_textedit_string_set(but, data, id->name + 2); - - if (ELEM(but->type, UI_BTYPE_SEARCH_MENU)) { - but->changed = true; - ui_searchbox_update(C, data->searchbox, but, true); - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } + if (data->custom_interaction_handle != NULL) { + ui_block_interaction_update( + C, &block->custom_interaction_callbacks, data->custom_interaction_handle); } } @@ -2428,7 +2550,7 @@ static void ui_but_paste_numeric_array(bContext *C, static void ui_but_copy_numeric_value(uiBut *but, char *output, int output_len_max) { /* Get many decimal places, then strip trailing zeros. - * note: too high values start to give strange results */ + * NOTE: too high values start to give strange results. */ ui_but_string_get_ex(but, output, output_len_max, UI_PRECISION_FLOAT_MAX, false, NULL); BLI_str_rstrip_float_zero(output, '\0'); } @@ -2517,15 +2639,9 @@ static void ui_but_copy_text(uiBut *but, char *output, int output_len_max) static void ui_but_paste_text(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste) { - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - ui_textedit_string_set(but, but->active, buf_paste); - - if (but->type == UI_BTYPE_SEARCH_MENU) { - but->changed = true; - ui_searchbox_update(C, data->searchbox, but, true); - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); + BLI_assert(but->active == data); + UNUSED_VARS_NDEBUG(data); + ui_but_set_string_interactive(C, but, buf_paste); } static void ui_but_copy_colorband(uiBut *but) @@ -2810,8 +2926,9 @@ static int ui_text_position_from_hidden(uiBut *but, int pos) { const char *butstr = (but->editstr) ? but->editstr : but->drawstr; const char *strpos = butstr; + const char *str_end = butstr + strlen(butstr); for (int i = 0; i < pos; i++) { - strpos = BLI_str_find_next_char_utf8(strpos, NULL); + strpos = BLI_str_find_next_char_utf8(strpos, str_end); } return (strpos - butstr); @@ -2868,6 +2985,24 @@ void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR], /** \name Button Text Selection/Editing * \{ */ +/** + * Use handling code to set a string for the button. Handles the case where the string is set for a + * search button while the search menu is open, so the results are updated accordingly. + * This is basically the same as pasting the string into the button. + */ +void ui_but_set_string_interactive(bContext *C, uiBut *but, const char *value) +{ + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + ui_textedit_string_set(but, but->active, value); + + if (but->type == UI_BTYPE_SEARCH_MENU && but->active) { + but->changed = true; + ui_searchbox_update(C, but->active->searchbox, but, true); + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); +} + void ui_but_active_string_clear_and_exit(bContext *C, uiBut *but) { if (!but->active) { @@ -2943,7 +3078,7 @@ static bool ui_textedit_set_cursor_pos_foreach_glyph(const char *UNUSED(str), /** * \param x: Screen space cursor location - #wmEvent.x * - * \note ``but->block->aspect`` is used here, so drawing button style is getting scaled too. + * \note `but->block->aspect` is used here, so drawing button style is getting scaled too. */ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x) { @@ -2963,11 +3098,6 @@ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, con UI_fontstyle_set(&fstyle); - if (fstyle.kerning == 1) { - /* for BLF_width */ - BLF_enable(fstyle.uifont_id, BLF_KERNING_DEFAULT); - } - ui_but_text_password_hide(password_str, but, false); if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { @@ -3018,10 +3148,6 @@ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, con but->pos = glyph_data[1] + but->ofs; } - if (fstyle.kerning == 1) { - BLF_disable(fstyle.uifont_id, BLF_KERNING_DEFAULT); - } - ui_but_text_password_hide(password_str, but, true); } @@ -3236,7 +3362,7 @@ static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const in if (pbuf) { if (UI_but_is_utf8(but)) { - buf_len -= BLI_utf8_invalid_strip(pbuf, (size_t)buf_len); + buf_len -= BLI_str_utf8_invalid_strip(pbuf, (size_t)buf_len); } ui_textedit_insert_buf(but, data, pbuf, buf_len); @@ -3268,7 +3394,7 @@ static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const in } #ifdef WITH_INPUT_IME -/* enable ime, and set up uibut ime data */ +/* Enable IME, and setup #uiBut IME data. */ static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but)) { /* XXX Is this really needed? */ @@ -3277,14 +3403,14 @@ static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but)) BLI_assert(win->ime_data == NULL); /* enable IME and position to cursor, it's a trick */ - x = win->eventstate->x; + x = win->eventstate->xy[0]; /* flip y and move down a bit, prevent the IME panel cover the edit button */ - y = win->eventstate->y - 12; + y = win->eventstate->xy[1] - 12; wm_window_IME_begin(win, x, y, 0, 0, true); } -/* disable ime, and clear uibut ime data */ +/* Disable IME, and clear #uiBut IME data. */ static void ui_textedit_ime_end(wmWindow *win, uiBut *UNUSED(but)) { wm_window_IME_end(win); @@ -3315,10 +3441,7 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) const bool is_num_but = ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER); bool no_zero_strip = false; - if (data->str) { - MEM_freeN(data->str); - data->str = NULL; - } + MEM_SAFE_FREE(data->str); #ifdef USE_DRAG_MULTINUM /* this can happen from multi-drag */ @@ -3396,6 +3519,11 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) ui_but_update(but); + /* Popup blocks don't support moving after creation, so don't change the view for them. */ + if (!data->searchbox) { + UI_but_ensure_in_view(C, data->region, but); + } + WM_cursor_modal_set(win, WM_CURSOR_TEXT_EDIT); #ifdef WITH_INPUT_IME @@ -3411,7 +3539,7 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data) if (but) { if (UI_but_is_utf8(but)) { - const int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr)); + const int strip = BLI_str_utf8_invalid_strip(but->editstr, strlen(but->editstr)); /* not a file?, strip non utf-8 chars */ if (strip) { /* won't happen often so isn't that annoying to keep it here for a while */ @@ -3591,18 +3719,18 @@ static void ui_do_but_textedit( /* exit on LMB only on RELEASE for searchbox, to mimic other popups, * and allow multiple menu levels */ if (data->searchbox) { - inbox = ui_searchbox_inside(data->searchbox, event->x, event->y); + inbox = ui_searchbox_inside(data->searchbox, event->xy); } /* for double click: we do a press again for when you first click on button * (selects all text, no cursor pos) */ if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) { - float mx = event->x; - float my = event->y; + float mx = event->xy[0]; + float my = event->xy[1]; ui_window_to_block_fl(data->region, block, &mx, &my); if (ui_but_contains_pt(but, mx, my)) { - ui_textedit_set_cursor_pos(but, data, event->x); + ui_textedit_set_cursor_pos(but, data, event->xy[0]); but->selsta = but->selend = but->pos; data->sel_pos_init = but->pos; @@ -3801,7 +3929,7 @@ static void ui_do_but_textedit( /* exception that's useful for number buttons, some keyboard * numpads have a comma instead of a period */ - if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* could use data->min*/ + if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* Could use `data->min`. */ if (event->type == EVT_PADPERIOD && ascii == ',') { ascii = '.'; utf8_buf = NULL; /* force ascii fallback */ @@ -3873,11 +4001,11 @@ static void ui_do_but_textedit_select( switch (event->type) { case MOUSEMOVE: { - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); - ui_textedit_set_cursor_select(but, data, event->x); + ui_textedit_set_cursor_select(but, data, event->xy[0]); retval = WM_UI_HANDLER_BREAK; break; } @@ -4069,14 +4197,17 @@ static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleBu static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon) { + but->active->apply_through_extra_icon = true; + if (but->active->interactive) { ui_apply_but(C, but->block, but, but->active, true); } button_activate_state(C, but, BUTTON_STATE_EXIT); - WM_operator_name_call_ptr(C, - op_icon->optype_params->optype, - op_icon->optype_params->opcontext, - op_icon->optype_params->opptr); + WM_operator_name_call_ptr_with_depends_on_cursor(C, + op_icon->optype_params->optype, + op_icon->optype_params->opcontext, + op_icon->optype_params->opptr, + NULL); /* Force recreation of extra operator icons (pseudo update). */ ui_but_extra_operator_icons_free(but); @@ -4210,7 +4341,7 @@ static uiBut *ui_but_list_row_text_activate(bContext *C, uiButtonActivateType activate_type) { ARegion *region = CTX_wm_region(C); - uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->x, event->y, true); + uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->xy, true, NULL, NULL); if (labelbut && labelbut->type == UI_BTYPE_TEXT && !(labelbut->flag & UI_BUT_DISABLED)) { /* exit listrow */ @@ -4237,7 +4368,7 @@ static uiButExtraOpIcon *ui_but_extra_operator_icon_mouse_over_get(uiBut *but, { float xmax = but->rect.xmax; const float icon_size = 0.8f * BLI_rctf_size_y(&but->rect); /* ICON_SIZE_FROM_BUTRECT */ - int x = event->x, y = event->y; + int x = event->xy[0], y = event->xy[1]; ui_window_to_block(data->region, but->block, &x, &y); if (!BLI_rctf_isect_pt(&but->rect, x, y)) { @@ -4284,7 +4415,7 @@ static bool ui_do_but_extra_operator_icon(bContext *C, button_tooltip_timer_reset(C, but); ui_but_extra_operator_icon_apply(C, but, op_icon); - /* Note: 'but', 'data' may now be freed, don't access. */ + /* NOTE: 'but', 'data' may now be freed, don't access. */ return true; } @@ -4321,20 +4452,16 @@ static bool ui_do_but_ANY_drag_toggle( { if (data->state == BUTTON_STATE_HIGHLIGHT) { if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_but_is_drag_toggle(but)) { -# if 0 /* UNUSED */ - data->togdual = event->ctrl; - data->togonly = !event->shift; -# endif ui_apply_but(C, but->block, but, data, true); button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; *r_retval = WM_UI_HANDLER_BREAK; return true; } } else if (data->state == BUTTON_STATE_WAIT_DRAG) { - /* note: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into + /* NOTE: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into * its own function */ data->applied = false; *r_retval = ui_do_but_EXIT(C, but, data, event); @@ -4414,7 +4541,7 @@ static int ui_do_but_HOTKEYEVT(bContext *C, if (event->type == LEFTMOUSE && event->val == KM_PRESS) { /* only cancel if click outside the button */ - if (ui_but_contains_point_px(but, but->active->region, event->x, event->y) == false) { + if (ui_but_contains_point_px(but, but->active->region, event->xy) == false) { /* data->cancel doesn't work, this button opens immediate */ if (but->flag & UI_BUT_IMMEDIATE) { ui_but_value_set(but, 0); @@ -4611,17 +4738,13 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons /* Behave like other menu items. */ do_activate = (event->val == KM_RELEASE); } - else { + else if (!ui_do_but_extra_operator_icon(C, but, data, event)) { /* Also use double-clicks to prevent fast clicks to leak to other handlers (T76481). */ do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK); } } if (do_activate) { -#if 0 /* UNUSED */ - data->togdual = event->ctrl; - data->togonly = !event->shift; -#endif button_activate_state(C, but, BUTTON_STATE_EXIT); return WM_UI_HANDLER_BREAK; } @@ -4673,37 +4796,87 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons return WM_UI_HANDLER_CONTINUE; } +static int ui_do_but_TREEROW(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)but; + BLI_assert(tree_row_but->but.type == UI_BTYPE_TREEROW); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE) { + switch (event->val) { + case KM_PRESS: + /* Extra icons have priority, don't mess with them. */ + if (ui_but_extra_operator_icon_mouse_over_get(but, data, event)) { + return WM_UI_HANDLER_BREAK; + } + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; + return WM_UI_HANDLER_CONTINUE; + + case KM_CLICK: + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + + case KM_DBL_CLICK: + data->cancel = true; + UI_tree_view_item_begin_rename(tree_row_but->tree_item); + ED_region_tag_redraw(CTX_wm_region(C)); + return WM_UI_HANDLER_BREAK; + } + } + } + else if (data->state == BUTTON_STATE_WAIT_DRAG) { + /* Let "default" button handling take care of the drag logic. */ + return ui_do_but_EXIT(C, but, data, event); + } + + return WM_UI_HANDLER_CONTINUE; +} + static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { if (data->state == BUTTON_STATE_HIGHLIGHT) { - /* first handle click on icondrag type button */ + /* First handle click on icon-drag type button. */ if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && but->dragpoin) { if (ui_but_contains_point_px_icon(but, data->region, event)) { /* tell the button to wait and keep checking further events to * see if it should start dragging */ button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; return WM_UI_HANDLER_CONTINUE; } } #ifdef USE_DRAG_TOGGLE if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && ui_but_is_drag_toggle(but)) { button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; return WM_UI_HANDLER_CONTINUE; } #endif if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { int ret = WM_UI_HANDLER_BREAK; - /* XXX (a bit ugly) Special case handling for filebrowser drag button */ + /* XXX: (a bit ugly) Special case handling for file-browser drag button. */ if (but->dragpoin && but->imb && ui_but_contains_point_px_icon(but, data->region, event)) { ret = WM_UI_HANDLER_CONTINUE; } + /* Same special case handling for UI lists. Return CONTINUE so that a tweak or CLICK event + * will be sent for the list to work with. */ + const uiBut *listbox = ui_list_find_mouse_over(data->region, event); + if (listbox) { + const uiList *ui_list = listbox->custom_data; + if (ui_list && ui_list->dyn_data->custom_drag_optype) { + ret = WM_UI_HANDLER_CONTINUE; + } + } button_activate_state(C, but, BUTTON_STATE_EXIT); return ret; } @@ -4852,6 +5025,8 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but, return changed; } + ui_block_interaction_begin_ensure(but->block->evil_C, but->block, data, false); + if (ui_but_is_cursor_warp(but)) { const float softmin = but->softmin; const float softmax = but->softmax; @@ -5104,8 +5279,8 @@ static void ui_numedit_set_active(uiBut *but) BLI_rctf_size_y(&but->rect) * 0.7f); /* we can click on the side arrows to increment/decrement, * or click inside to edit the value directly */ - int mx = data->window->eventstate->x; - int my = data->window->eventstate->y; + int mx = data->window->eventstate->xy[0]; + int my = data->window->eventstate->xy[1]; ui_window_to_block(data->region, but->block, &mx, &my); if (mx < (but->rect.xmin + handle_width)) { @@ -5145,10 +5320,10 @@ static int ui_do_but_NUM( int retval = WM_UI_HANDLER_CONTINUE; /* mouse location scaled to fit the UI */ - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; /* mouse location kept at screen pixel coords */ - const int screen_mx = event->x; + const int screen_mx = event->xy[0]; BLI_assert(but->type == UI_BTYPE_NUM); @@ -5200,7 +5375,7 @@ static int ui_do_but_NUM( } #ifdef USE_DRAG_MULTINUM - copy_v2_v2_int(data->multi_data.drag_start, &event->x); + copy_v2_v2_int(data->multi_data.drag_start, event->xy); #endif } } @@ -5362,6 +5537,8 @@ static bool ui_numedit_but_SLI(uiBut *but, return changed; } + ui_block_interaction_begin_ensure(but->block->evil_C, but->block, data, false); + const PropertyScaleType scale_type = ui_but_scale_type(but); softmin = but->softmin; @@ -5492,8 +5669,8 @@ static int ui_do_but_SLI( int click = 0; int retval = WM_UI_HANDLER_CONTINUE; - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -5552,7 +5729,7 @@ static int ui_do_but_SLI( } } #ifdef USE_DRAG_MULTINUM - copy_v2_v2_int(data->multi_data.drag_start, &event->x); + copy_v2_v2_int(data->multi_data.drag_start, event->xy); #endif } else if (data->state == BUTTON_STATE_NUM_EDITING) { @@ -5712,8 +5889,8 @@ static int ui_do_but_SCROLL( int retval = WM_UI_HANDLER_CONTINUE; const bool horizontal = (BLI_rctf_size_x(&but->rect) > BLI_rctf_size_y(&but->rect)); - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -5763,21 +5940,21 @@ static int ui_do_but_GRIP( int retval = WM_UI_HANDLER_CONTINUE; const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect)); - /* Note: Having to store org point in window space and recompute it to block "space" each time + /* NOTE: Having to store org point in window space and recompute it to block "space" each time * is not ideal, but this is a way to hack around behavior of ui_window_to_block(), which * returns different results when the block is inside a panel or not... * See T37739. */ - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { if (event->val == KM_PRESS) { if (event->type == LEFTMOUSE) { - data->dragstartx = event->x; - data->dragstarty = event->y; + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); retval = WM_UI_HANDLER_BREAK; } @@ -5836,20 +6013,20 @@ static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, co { if (data->state == BUTTON_STATE_HIGHLIGHT) { - /* first handle click on icondrag type button */ + /* First handle click on icon-drag type button. */ if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) { if (ui_but_contains_point_px_icon(but, data->region, event)) { button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; return WM_UI_HANDLER_BREAK; } } #ifdef USE_DRAG_TOGGLE if (event->type == LEFTMOUSE && event->val == KM_PRESS && (ui_but_is_drag_toggle(but))) { button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; return WM_UI_HANDLER_BREAK; } #endif @@ -5896,7 +6073,7 @@ static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, co * the slot menu fails to switch a second time. * * The active state of the button could be maintained some other way - * and remove this mousemove event. + * and remove this mouse-move event. */ WM_event_add_mousemove(data->window); @@ -6021,13 +6198,13 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co uiButColor *color_but = (uiButColor *)but; if (data->state == BUTTON_STATE_HIGHLIGHT) { - /* first handle click on icondrag type button */ + /* First handle click on icon-drag type button. */ if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) { ui_palette_set_active(color_but); if (ui_but_contains_point_px_icon(but, data->region, event)) { button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; return WM_UI_HANDLER_BREAK; } } @@ -6035,8 +6212,8 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co if (event->type == LEFTMOUSE && event->val == KM_PRESS) { ui_palette_set_active(color_but); button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; return WM_UI_HANDLER_BREAK; } #endif @@ -6062,7 +6239,7 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f); } else { - const float fac = 0.005 * (event->y - event->prevy); + const float fac = 0.005 * (event->xy[1] - event->prev_xy[1]); hsv[2] = clamp_f(hsv[2] + fac, 0.0f, 1.0f); } @@ -6166,8 +6343,8 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co static int ui_do_but_UNITVEC( bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -6435,7 +6612,7 @@ static void ui_ndofedit_but_HSVCUBE(uiButHSVCube *hsv_but, CLAMP(hsv[2], hsv_but->but.softmin, hsv_but->but.softmax); break; default: - BLI_assert(!"invalid hsv type"); + BLI_assert_msg(0, "invalid hsv type"); break; } @@ -6460,8 +6637,8 @@ static int ui_do_but_HSVCUBE( bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { uiButHSVCube *hsv_but = (uiButHSVCube *)but; - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -6638,7 +6815,7 @@ static bool ui_numedit_but_HSVCIRCLE(uiBut *but, ui_color_picker_hsv_to_rgb(hsv, rgb); - if ((cpicker->use_luminosity_lock)) { + if (cpicker->use_luminosity_lock) { if (!is_zero_v3(rgb)) { normalize_v3_length(rgb, cpicker->luminosity_lock_value); } @@ -6736,8 +6913,8 @@ static int ui_do_but_HSVCIRCLE( { ColorPicker *cpicker = but->custom_data; float *hsv = cpicker->hsv_perceptual; - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -6865,8 +7042,8 @@ static bool ui_numedit_but_COLORBAND(uiBut *but, uiHandleButtonData *data, int m static int ui_do_but_COLORBAND( bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -6951,8 +7128,8 @@ static bool ui_numedit_but_CURVE(uiBlock *block, CurveMapPoint *cmp = cuma->curve; bool changed = false; - /* evtx evty and drag coords are absolute mousecoords, - * prevents errors when editing when layout changes */ + /* evtx evty and drag coords are absolute mouse-coords, + * prevents errors when editing when layout changes. */ int mx = evtx; int my = evty; ui_window_to_block(data->region, block, &mx, &my); @@ -7010,7 +7187,7 @@ static bool ui_numedit_but_CURVE(uiBlock *block, changed = true; #ifdef USE_CONT_MOUSE_CORRECT - /* note: using 'cmp_last' is weak since there may be multiple points selected, + /* NOTE: using 'cmp_last' is weak since there may be multiple points selected, * but in practice this isn't really an issue */ if (ui_but_is_cursor_warp(but)) { /* OK but can go outside bounds */ @@ -7061,8 +7238,8 @@ static int ui_do_but_CURVE( Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -7154,10 +7331,10 @@ static int ui_do_but_CURVE( data->dragsel = sel; - data->dragstartx = event->x; - data->dragstarty = event->y; - data->draglastx = event->x; - data->draglasty = event->y; + data->dragstartx = event->xy[0]; + data->dragstarty = event->xy[1]; + data->draglastx = event->xy[0]; + data->draglasty = event->xy[1]; button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); return WM_UI_HANDLER_BREAK; @@ -7165,10 +7342,15 @@ static int ui_do_but_CURVE( } else if (data->state == BUTTON_STATE_NUM_EDITING) { if (event->type == MOUSEMOVE) { - if (event->x != data->draglastx || event->y != data->draglasty) { - - if (ui_numedit_but_CURVE( - block, but, data, event->x, event->y, event->ctrl != 0, event->shift != 0)) { + if (event->xy[0] != data->draglastx || event->xy[1] != data->draglasty) { + + if (ui_numedit_but_CURVE(block, + but, + data, + event->xy[0], + event->xy[1], + event->ctrl != 0, + event->shift != 0)) { ui_numedit_apply(C, block, but, data); } } @@ -7219,8 +7401,8 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block, CurveProfilePoint *pts = profile->path; bool changed = false; - /* evtx evty and drag coords are absolute mousecoords, - * prevents errors when editing when layout changes */ + /* evtx evty and drag coords are absolute mouse-coords, + * prevents errors when editing when layout changes. */ int mx = evtx; int my = evty; ui_window_to_block(data->region, block, &mx, &my); @@ -7281,7 +7463,7 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block, data->draglasty = evty; changed = true; #ifdef USE_CONT_MOUSE_CORRECT - /* note: using 'cmp_last' is weak since there may be multiple points selected, + /* NOTE: using 'cmp_last' is weak since there may be multiple points selected, * but in practice this isn't really an issue */ if (ui_but_is_cursor_warp(but)) { /* OK but can go outside bounds */ @@ -7342,8 +7524,8 @@ static int ui_do_but_CURVEPROFILE( bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { CurveProfile *profile = (CurveProfile *)but->poin; - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); @@ -7425,7 +7607,7 @@ static int ui_do_but_CURVEPROFILE( dist_min_sq = square_f(U.dpi_fac * 8.0f); /* 8 pixel radius from each table point. */ /* Loop through the path's high resolution table and find what's near the click. */ - for (int i = 1; i <= PROF_TABLE_LEN(profile->path_len); i++) { + for (int i = 1; i <= BKE_curveprofile_table_size(profile); i++) { copy_v2_v2(f_xy_prev, f_xy); BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[i].x); @@ -7525,8 +7707,8 @@ static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int m static int ui_do_but_HISTOGRAM( bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -7598,8 +7780,8 @@ static bool ui_numedit_but_WAVEFORM(uiBut *but, uiHandleButtonData *data, int mx static int ui_do_but_WAVEFORM( bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -7689,8 +7871,8 @@ static bool ui_numedit_but_TRACKPREVIEW( static int ui_do_but_TRACKPREVIEW( bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(data->region, block, &mx, &my); if (data->state == BUTTON_STATE_HIGHLIGHT) { @@ -7746,7 +7928,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * /* Only hard-coded stuff here, button interactions with configurable * keymaps are handled using operators (see #ED_keymap_ui). */ - if ((data->state == BUTTON_STATE_HIGHLIGHT) || (event->type == EVT_DROP)) { + if (data->state == BUTTON_STATE_HIGHLIGHT) { /* handle copy and paste */ bool is_press_ctrl_but_no_shift = event->val == KM_PRESS && IS_EVENT_MOD(event, ctrl, oskey) && @@ -7772,8 +7954,16 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * /* handle menu */ if ((event->type == RIGHTMOUSE) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) && (event->val == KM_PRESS)) { + /* For some button types that are typically representing entire sets of data, right-clicking + * to spawn the context menu should also activate the item. This makes it clear which item + * will be operated on. + * Apply the button immediately, so context menu polls get the right active item. */ + if (ELEM(but->type, UI_BTYPE_TREEROW)) { + ui_apply_but(C, but->block, but, but->active, true); + } + /* RMB has two options now */ - if (ui_popup_context_menu_for_button(C, but)) { + if (ui_popup_context_menu_for_button(C, but, event)) { return WM_UI_HANDLER_BREAK; } } @@ -7787,11 +7977,6 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * return WM_UI_HANDLER_BREAK; } - /* handle drop */ - if (event->type == EVT_DROP) { - ui_but_drop(C, event, but, data); - } - if ((data->state == BUTTON_STATE_HIGHLIGHT) && ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY) && (event->val == KM_RELEASE) && @@ -7830,6 +8015,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * case UI_BTYPE_DATASETROW: retval = ui_do_but_TOG(C, but, data, event); break; + case UI_BTYPE_TREEROW: + retval = ui_do_but_TREEROW(C, but, data, event); + break; case UI_BTYPE_SCROLL: retval = ui_do_but_SCROLL(C, block, but, data, event); break; @@ -7853,6 +8041,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * case UI_BTYPE_IMAGE: case UI_BTYPE_PROGRESS_BAR: case UI_BTYPE_NODE_SOCKET: + case UI_BTYPE_PREVIEW_TILE: retval = ui_do_but_EXIT(C, but, data, event); break; case UI_BTYPE_HISTOGRAM: @@ -7939,7 +8128,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * if (fabsf(dot_v2v2(dir_nor_drag, dir_nor_y)) > DRAG_MULTINUM_THRESHOLD_VERTICAL) { data->multi_data.init = BUTTON_MULTI_INIT_SETUP; - data->multi_data.drag_lock_x = event->x; + data->multi_data.drag_lock_x = event->xy[0]; } else { data->multi_data.init = BUTTON_MULTI_INIT_DISABLE; @@ -7952,9 +8141,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * /* Check if we're don't setting buttons. */ if ((data->str && ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) || - ((abs(data->multi_data.drag_lock_x - event->x) > margin_x) && + ((abs(data->multi_data.drag_lock_x - event->xy[0]) > margin_x) && /* Just to be sure, check we're dragging more horizontally then vertically. */ - abs(event->prevx - event->x) > abs(event->prevy - event->y))) { + abs(event->prev_xy[0] - event->xy[0]) > abs(event->prev_xy[1] - event->xy[1]))) { if (data->multi_data.has_mbuts) { ui_multibut_states_create(but, data); data->multi_data.init = BUTTON_MULTI_INIT_ENABLE; @@ -8042,7 +8231,11 @@ static ARegion *ui_but_tooltip_init( uiBut *but = UI_region_active_but_get(region); *r_exit_on_event = false; if (but) { - return UI_tooltip_create_from_button(C, region, but, is_label); + const wmWindow *win = CTX_wm_window(C); + uiButExtraOpIcon *extra_icon = ui_but_extra_operator_icon_mouse_over_get( + but, but->active, win->eventstate); + + return UI_tooltip_create_from_button_or_extra_icon(C, region, but, extra_icon, is_label); } return NULL; } @@ -8234,11 +8427,21 @@ static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState s } } - /* wait for mousemove to enable drag */ + /* Wait for mouse-move to enable drag. */ if (state == BUTTON_STATE_WAIT_DRAG) { but->flag &= ~UI_SELECT; } + if (state == BUTTON_STATE_TEXT_EDITING) { + ui_block_interaction_begin_ensure(C, but->block, data, true); + } + else if (state == BUTTON_STATE_EXIT) { + if (data->state == BUTTON_STATE_NUM_EDITING) { + /* This happens on pasting values for example. */ + ui_block_interaction_begin_ensure(C, but->block, data, true); + } + } + data->state = state; if (state != BUTTON_STATE_EXIT) { @@ -8467,21 +8670,33 @@ static void button_activate_exit( ED_region_tag_redraw_no_rebuild(data->region); ED_region_tag_refresh_ui(data->region); - /* clean up button */ - if (but->active) { - MEM_freeN(but->active); - but->active = NULL; + if ((but->flag & UI_BUT_DRAG_MULTI) == 0) { + if (data->custom_interaction_handle != NULL) { + /* Should only set when the button is modal. */ + BLI_assert(but->active != NULL); + data->custom_interaction_handle->user_count--; + + BLI_assert(data->custom_interaction_handle->user_count >= 0); + if (data->custom_interaction_handle->user_count == 0) { + ui_block_interaction_end( + C, &but->block->custom_interaction_callbacks, data->custom_interaction_handle); + } + data->custom_interaction_handle = NULL; + } } + /* clean up button */ + MEM_SAFE_FREE(but->active); + but->flag &= ~(UI_ACTIVE | UI_SELECT); but->flag |= UI_BUT_LAST_ACTIVE; if (!onfree) { ui_but_update(but); } - /* adds empty mousemove in queue for re-init handler, in case mouse is + /* Adds empty mouse-move in queue for re-initialize handler, in case mouse is * still over a button. We cannot just check for this ourselves because - * at this point the mouse may be over a button in another region */ + * at this point the mouse may be over a button in another region. */ if (mousemove) { WM_event_add_mousemove(CTX_wm_window(C)); } @@ -8580,7 +8795,7 @@ uiBlock *UI_region_block_find_mouse_over(const struct ARegion *region, const int xy[2], bool only_clip) { - return ui_block_find_mouse_over_ex(region, xy[0], xy[1], only_clip); + return ui_block_find_mouse_over_ex(region, xy, only_clip); } /** @@ -8614,9 +8829,9 @@ void UI_context_active_but_prop_handle(bContext *C) { uiBut *activebut = ui_context_rna_button_active(C); if (activebut) { - /* TODO, look into a better way to handle the button change + /* TODO(campbell): look into a better way to handle the button change * currently this is mainly so reset defaults works for the - * operator redo panel - campbell */ + * operator redo panel. */ uiBlock *block = activebut->block; if (block->handle_func) { block->handle_func(C, block->handle_func_arg, activebut->retval); @@ -8722,6 +8937,26 @@ void UI_context_update_anim_flag(const bContext *C) } } +/** + * In some cases we may want to update the view (#View2D) in-between layout definition and drawing. + * E.g. to make sure a button is visible while editing. + */ +void ui_but_update_view_for_active(const bContext *C, const uiBlock *block) +{ + uiBut *active_but = ui_block_active_but_get(block); + if (!active_but || !active_but->active || !active_but->changed || active_but->block != block) { + return; + } + /* If there is a search popup attached to the button, don't change the view. The popups don't + * support updating the position to the button position nicely. */ + uiHandleButtonData *data = active_but->active; + if (data->searchbox) { + return; + } + + UI_but_ensure_in_view(C, active_but->active->region, active_but); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -8782,7 +9017,7 @@ void ui_but_activate_event(bContext *C, ARegion *region, uiBut *but) event.val = KM_PRESS; event.is_repeat = false; event.customdata = but; - event.customdatafree = false; + event.customdata_free = false; ui_do_button(C, but->block, but, &event); } @@ -8805,7 +9040,7 @@ void ui_but_execute_begin(struct bContext *UNUSED(C), { BLI_assert(region != NULL); BLI_assert(BLI_findindex(®ion->uiblocks, but->block) != -1); - /* note: ideally we would not have to change 'but->active' however + /* NOTE: ideally we would not have to change 'but->active' however * some functions we call don't use data (as they should be doing) */ uiHandleButtonData *data; *active_back = but->active; @@ -8959,7 +9194,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) /* always deactivate button for pie menus, * else moving to blank space will leave activated */ if ((!ui_block_is_menu(block) || ui_block_is_pie_menu(block)) && - !ui_but_contains_point_px(but, region, event->x, event->y)) { + !ui_but_contains_point_px(but, region, event->xy)) { exit = true; } else if (but_other && ui_but_is_editable(but_other) && (but_other != but)) { @@ -8970,7 +9205,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) data->cancel = true; button_activate_state(C, but, BUTTON_STATE_EXIT); } - else if (event->x != event->prevx || event->y != event->prevy) { + else if (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1]) { /* Re-enable tool-tip on mouse move. */ ui_blocks_set_tooltips(region, true); button_tooltip_timer_reset(C, but); @@ -8987,7 +9222,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) WM_event_remove_timer(data->wm, data->window, data->autoopentimer); data->autoopentimer = NULL; - if (ui_but_contains_point_px(but, region, event->x, event->y) || but->active) { + if (ui_but_contains_point_px(but, region, event->xy) || but->active) { button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); } } @@ -9037,12 +9272,12 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) case MOUSEMOVE: { /* deselect the button when moving the mouse away */ /* also de-activate for buttons that only show highlights */ - if (ui_but_contains_point_px(but, region, event->x, event->y)) { + if (ui_but_contains_point_px(but, region, event->xy)) { /* Drag on a hold button (used in the toolbar) now opens it immediately. */ if (data->hold_action_timer) { if (but->flag & UI_SELECT) { - if (len_manhattan_v2v2_int(&event->x, &event->prevx) <= + if (len_manhattan_v2v2_int(event->xy, event->prev_xy) <= WM_EVENT_CURSOR_MOTION_THRESHOLD) { /* pass */ } @@ -9095,7 +9330,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) uiBut *bt; if (data->menu && data->menu->region) { - if (ui_region_contains_point_px(data->menu->region, event->x, event->y)) { + if (ui_region_contains_point_px(data->menu->region, event->xy)) { break; } } @@ -9174,7 +9409,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) * * This is needed to make sure if a button was active, * it stays active while the mouse is over it. - * This avoids adding mousemoves, see: T33466. */ + * This avoids adding mouse-moves, see: T33466. */ if (ELEM(state_orig, BUTTON_STATE_INIT, BUTTON_STATE_HIGHLIGHT, BUTTON_STATE_WAIT_DRAG)) { if (ui_but_find_mouse_over(region, event) == but) { button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); @@ -9186,6 +9421,148 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) return retval; } +/** + * Activate the underlying list-row button, so the row is highlighted. + * Early exits if \a activate_dragging is true, but the custom drag operator fails to execute. + * Gives the wanted behavior where the item is activated on a tweak event when the custom drag + * operator is executed. + */ +static int ui_list_activate_hovered_row(bContext *C, + ARegion *region, + const uiList *ui_list, + const wmEvent *event, + bool activate_dragging) +{ + const bool do_drag = activate_dragging && ui_list->dyn_data->custom_drag_optype; + + if (do_drag) { + const uiBut *hovered_but = ui_but_find_mouse_over(region, event); + if (!ui_list_invoke_item_operator(C, + hovered_but, + ui_list->dyn_data->custom_drag_optype, + &ui_list->dyn_data->custom_drag_opptr)) { + return WM_UI_HANDLER_CONTINUE; + } + } + + const int *mouse_xy = ISTWEAK(event->type) ? event->prev_click_xy : event->xy; + uiBut *listrow = ui_list_row_find_mouse_over(region, mouse_xy); + if (listrow) { + wmOperatorType *custom_activate_optype = ui_list->dyn_data->custom_activate_optype; + + /* Hacky: Ensure the custom activate operator is not called when the custom drag operator + * was. Only one should run! */ + if (activate_dragging && do_drag) { + ((uiList *)ui_list)->dyn_data->custom_activate_optype = NULL; + } + + /* Simulate click on listrow button itself (which may be overlapped by another button). Also + * calls the custom activate operator (ui_list->custom_activate_opname). */ + UI_but_execute(C, region, listrow); + + ((uiList *)ui_list)->dyn_data->custom_activate_optype = custom_activate_optype; + } + + return WM_UI_HANDLER_BREAK; +} + +static bool ui_list_is_hovering_draggable_but(bContext *C, + const uiList *list, + const ARegion *region, + const wmEvent *event) +{ + /* On a tweak event, uses the coordinates from where tweaking was started. */ + const int *mouse_xy = ISTWEAK(event->type) ? event->prev_click_xy : event->xy; + const uiBut *hovered_but = ui_but_find_mouse_over_ex(region, mouse_xy, false, NULL, NULL); + + if (list->dyn_data->custom_drag_optype) { + if (ui_but_context_poll_operator(C, list->dyn_data->custom_drag_optype, hovered_but)) { + return true; + } + } + + return (hovered_but && hovered_but->dragpoin); +} + +static int ui_list_handle_click_drag(bContext *C, + const uiList *ui_list, + ARegion *region, + const wmEvent *event) +{ + if (!ELEM(event->type, LEFTMOUSE, EVT_TWEAK_L)) { + return WM_HANDLER_CONTINUE; + } + + int retval = WM_HANDLER_CONTINUE; + + const bool is_draggable = ui_list_is_hovering_draggable_but(C, ui_list, region, event); + bool activate = false; + bool activate_dragging = false; + + if (event->type == EVT_TWEAK_L) { + if (is_draggable) { + activate_dragging = true; + activate = true; + } + } + /* #KM_CLICK is only sent after an uncaught release event, so the foreground button gets all + * regular events (including mouse presses to start dragging) and this part only kicks in if it + * hasn't handled the release event. Note that if there's no overlaid button, the row selects + * on the press event already via regular #UI_BTYPE_LISTROW handling. */ + else if ((event->type == LEFTMOUSE) && (event->val == KM_CLICK)) { + activate = true; + } + + if (activate) { + retval = ui_list_activate_hovered_row(C, region, ui_list, event, activate_dragging); + } + + return retval; +} + +static void ui_list_activate_row_from_index( + bContext *C, ARegion *region, uiBut *listbox, uiList *ui_list, int index) +{ + uiBut *new_active_row = ui_list_row_find_from_index(region, index, listbox); + if (new_active_row) { + /* Preferred way to update the active item, also calls the custom activate operator + * (#uiList.custom_activate_opname). */ + UI_but_execute(C, region, new_active_row); + } + else { + /* A bit ugly, set the active index in RNA directly. That's because a button that's + * scrolled away in the list box isn't created at all. + * The custom activate operator (#uiList.custom_activate_opname) is not called in this case + * (which may need the row button context). */ + RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, index); + RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop); + ui_apply_but_undo(listbox); + } + + ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; +} + +static int ui_list_get_increment(const uiList *ui_list, const int type, const int columns) +{ + int increment = 0; + + /* Handle column offsets for grid layouts. */ + if (ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) && + ELEM(ui_list->layout_type, UILST_LAYOUT_GRID, UILST_LAYOUT_BIG_PREVIEW_GRID)) { + increment = (type == EVT_UPARROWKEY) ? -columns : columns; + } + else { + /* Left or right in grid layouts or any direction in single column layouts increments by 1. */ + increment = ELEM(type, EVT_UPARROWKEY, EVT_LEFTARROWKEY, WHEELUPMOUSE) ? -1 : 1; + } + + if ((ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0) { + increment *= -1; + } + + return increment; +} + static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *region, uiBut *listbox) { int retval = WM_UI_HANDLER_CONTINUE; @@ -9199,8 +9576,8 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi } uiListDyn *dyn_data = ui_list->dyn_data; - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(region, listbox->block, &mx, &my); /* Convert pan to scroll-wheel. */ @@ -9219,22 +9596,19 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi } } - if (val == KM_PRESS) { - if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) && + if (ELEM(event->type, LEFTMOUSE, EVT_TWEAK_L)) { + retval = ui_list_handle_click_drag(C, ui_list, region, event); + } + else if (val == KM_PRESS) { + if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY, EVT_LEFTARROWKEY, EVT_RIGHTARROWKEY) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) || ((ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey)))) { const int value_orig = RNA_property_int_get(&listbox->rnapoin, listbox->rnaprop); - int value, min, max, inc; + int value, min, max; - /* activate up/down the list */ value = value_orig; - if ((ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0) { - inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? 1 : -1; - } - else { - inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? -1 : 1; - } + const int inc = ui_list_get_increment(ui_list, type, dyn_data->columns); if (dyn_data->items_filter_neworder || dyn_data->items_filter_flags) { /* If we have a display order different from @@ -9281,12 +9655,7 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi CLAMP(value, min, max); if (value != value_orig) { - RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, value); - RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop); - - ui_apply_but_undo(listbox); - - ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; + ui_list_activate_row_from_index(C, region, listbox, ui_list, value); redraw = true; } retval = WM_UI_HANDLER_BREAK; @@ -9322,6 +9691,38 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi return retval; } +static int ui_handle_tree_hover(const wmEvent *event, const ARegion *region) +{ + bool has_treerows = false; + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + /* Avoid unnecessary work: Tree-rows are assumed to be inside tree-views. */ + if (BLI_listbase_is_empty(&block->views)) { + continue; + } + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->type == UI_BTYPE_TREEROW) { + but->flag &= ~UI_ACTIVE; + has_treerows = true; + } + } + } + + if (!has_treerows) { + /* Avoid unnecessary lookup. */ + return WM_UI_HANDLER_CONTINUE; + } + + /* Always highlight the hovered tree-row, even if the mouse hovers another button inside of it. + */ + uiBut *hovered_row_but = ui_tree_row_find_mouse_over(region, event->xy); + if (hovered_row_but) { + hovered_row_but->flag |= UI_ACTIVE; + } + + return WM_UI_HANDLER_CONTINUE; +} + static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, uiBut *but) { uiHandleButtonData *data = but->active; @@ -9357,8 +9758,7 @@ static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, u button_activate_exit(C, but, data, true, false); } else if (menu->menuretval & UI_RETURN_OUT) { - if (event->type == MOUSEMOVE && - ui_but_contains_point_px(but, data->region, event->x, event->y)) { + if (event->type == MOUSEMOVE && ui_but_contains_point_px(but, data->region, event->xy)) { button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT); } else { @@ -9498,7 +9898,7 @@ static bool ui_mouse_motion_towards_check(uiBlock *block, static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event) { keynav->is_keynav = true; - copy_v2_v2_int(keynav->event_xy, &event->x); + copy_v2_v2_int(keynav->event_xy, event->xy); } /** * Return true if key-input isn't blocking mouse-motion, @@ -9507,7 +9907,7 @@ static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEve static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event) { if (keynav->is_keynav && - (len_manhattan_v2v2_int(keynav->event_xy, &event->x) > BUTTON_KEYNAV_PX_LIMIT)) { + (len_manhattan_v2v2_int(keynav->event_xy, event->xy) > BUTTON_KEYNAV_PX_LIMIT)) { keynav->is_keynav = false; } @@ -9697,13 +10097,13 @@ static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlock else if (!ui_block_is_menu(but->block) || ui_block_is_pie_menu(but->block)) { /* pass, skip for dialogs */ } - else if (!ui_region_contains_point_px(but->active->region, event->x, event->y)) { + else if (!ui_region_contains_point_px(but->active->region, event->xy)) { /* Pass, needed to click-exit outside of non-floating menus. */ ui_region_auto_open_clear(but->active->region); } else if ((!ELEM(event->type, MOUSEMOVE, WHEELUPMOUSE, WHEELDOWNMOUSE, MOUSEPAN)) && ISMOUSE(event->type)) { - if (!ui_but_contains_point_px(but, but->active->region, event->x, event->y)) { + if (!ui_but_contains_point_px(but, but->active->region, event->xy)) { but = NULL; } } @@ -9777,8 +10177,8 @@ static int ui_handle_menu_event(bContext *C, int retval = WM_UI_HANDLER_CONTINUE; - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(region, block, &mx, &my); /* check if mouse is inside block */ @@ -9799,8 +10199,8 @@ static int ui_handle_menu_event(bContext *C, if (event->type == MOUSEMOVE) { int mdiff[2]; - sub_v2_v2v2_int(mdiff, &event->x, menu->grab_xy_prev); - copy_v2_v2_int(menu->grab_xy_prev, &event->x); + sub_v2_v2v2_int(mdiff, event->xy, menu->grab_xy_prev); + copy_v2_v2_int(menu->grab_xy_prev, event->xy); add_v2_v2v2_int(menu->popup_create_vars.event_xy, menu->popup_create_vars.event_xy, mdiff); @@ -9817,7 +10217,7 @@ static int ui_handle_menu_event(bContext *C, /* if a button is activated modal, always reset the start mouse * position of the towards mechanism to avoid losing focus, * and don't handle events */ - ui_mouse_motion_towards_reinit(menu, &event->x); + ui_mouse_motion_towards_reinit(menu, event->xy); } } else if (event->type == TIMER) { @@ -9829,7 +10229,7 @@ static int ui_handle_menu_event(bContext *C, /* for ui_mouse_motion_towards_block */ if (event->type == MOUSEMOVE) { if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { - ui_mouse_motion_towards_init(menu, &event->x); + ui_mouse_motion_towards_init(menu, event->xy); } /* add menu scroll timer, if needed */ @@ -9912,14 +10312,14 @@ static int ui_handle_menu_event(bContext *C, retval = WM_UI_HANDLER_BREAK; break; - /* Smooth scrolling for popovers. */ + /* Smooth scrolling for pocopy_v2_v2_int(&povers. */ case MOUSEPAN: { if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) { /* pass */ } else if (!ui_block_is_menu(block)) { if (block->flag & (UI_BLOCK_CLIPTOP | UI_BLOCK_CLIPBOTTOM)) { - const float dy = event->y - event->prevy; + const float dy = event->xy[1] - event->prev_xy[1]; if (dy != 0.0f) { ui_menu_scroll_apply_offset_y(region, block, dy); @@ -10242,7 +10642,8 @@ static int ui_handle_menu_event(bContext *C, menu->menuretval = UI_RETURN_OUT; } } - else if (saferct && !BLI_rctf_isect_pt(&saferct->parent, event->x, event->y)) { + else if (saferct && !BLI_rctf_isect_pt( + &saferct->parent, (float)event->xy[0], (float)event->xy[1])) { if (block->flag & UI_BLOCK_OUT_1) { menu->menuretval = UI_RETURN_OK; } @@ -10255,7 +10656,7 @@ static int ui_handle_menu_event(bContext *C, /* For buttons that use a hold function, * exit when mouse-up outside the menu. */ if (block->flag & UI_BLOCK_POPUP_HOLD) { - /* Note, we could check the cursor is over the parent button. */ + /* NOTE: we could check the cursor is over the parent button. */ menu->menuretval = UI_RETURN_CANCEL; retval = WM_UI_HANDLER_CONTINUE; } @@ -10301,13 +10702,13 @@ static int ui_handle_menu_event(bContext *C, #ifdef USE_DRAG_POPUP else if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && (inside && is_floating && inside_title)) { - if (!but || !ui_but_contains_point_px(but, region, event->x, event->y)) { + if (!but || !ui_but_contains_point_px(but, region, event->xy)) { if (but) { UI_but_tooltip_timer_remove(C, but); } menu->is_grab = true; - copy_v2_v2_int(menu->grab_xy_prev, &event->x); + copy_v2_v2_int(menu->grab_xy_prev, event->xy); retval = WM_UI_HANDLER_BREAK; } } @@ -10318,7 +10719,7 @@ static int ui_handle_menu_event(bContext *C, if (inside == false && (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER))) { uiSafetyRct *saferct; - ui_mouse_motion_towards_check(block, menu, &event->x, is_parent_inside == false); + ui_mouse_motion_towards_check(block, menu, event->xy, is_parent_inside == false); /* Check for all parent rects, enables arrow-keys to be used. */ for (saferct = block->saferct.first; saferct; saferct = saferct->next) { @@ -10326,10 +10727,10 @@ static int ui_handle_menu_event(bContext *C, * events we check all preceding block rects too to make * arrow keys navigation work */ if (event->type != MOUSEMOVE || saferct == block->saferct.first) { - if (BLI_rctf_isect_pt(&saferct->parent, (float)event->x, (float)event->y)) { + if (BLI_rctf_isect_pt(&saferct->parent, (float)event->xy[0], (float)event->xy[1])) { break; } - if (BLI_rctf_isect_pt(&saferct->safety, (float)event->x, (float)event->y)) { + if (BLI_rctf_isect_pt(&saferct->safety, (float)event->xy[0], (float)event->xy[1])) { break; } } @@ -10373,7 +10774,7 @@ static int ui_handle_menu_event(bContext *C, } #endif - /* Don't handle double click events, rehandle as regular press/release. */ + /* Don't handle double click events, re-handle as regular press/release. */ if (retval == WM_UI_HANDLER_CONTINUE && event->val == KM_DBL_CLICK) { return retval; } @@ -10430,7 +10831,7 @@ static int ui_handle_menu_return_submenu(bContext *C, if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { /* for cases where close does not cascade, allow the user to * move the mouse back towards the menu without closing */ - ui_mouse_motion_towards_reinit(menu, &event->x); + ui_mouse_motion_towards_reinit(menu, event->xy); } if (menu->menuretval) { @@ -10538,7 +10939,7 @@ static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle const double duration = menu->scrolltimer->duration; - float event_xy[2] = {event->x, event->y}; + float event_xy[2] = {UNPACK2(event->xy)}; ui_window_to_block_fl(region, block, &event_xy[0], &event_xy[1]); @@ -10715,7 +11116,7 @@ static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle case EVT_XKEY: case EVT_YKEY: case EVT_ZKEY: { - if ((event->val == KM_PRESS || event->val == KM_DBL_CLICK) && + if ((ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) && !IS_EVENT_MOD(event, shift, ctrl, oskey)) { LISTBASE_FOREACH (uiBut *, but, &block->buttons) { if (but->menu_key == event->type) { @@ -10793,8 +11194,8 @@ static int ui_handle_menus_recursive(bContext *C, if (do_recursion) { if (is_parent_inside == false) { - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(menu->region, block, &mx, &my); inside = BLI_rctf_isect_pt(&block->rect, mx, my); } @@ -10860,7 +11261,7 @@ static int ui_handle_menus_recursive(bContext *C, } if (do_towards_reinit) { - ui_mouse_motion_towards_reinit(menu, &event->x); + ui_mouse_motion_towards_reinit(menu, event->xy); } return retval; @@ -10921,10 +11322,15 @@ static int ui_region_handler(bContext *C, const wmEvent *event, void *UNUSED(use } /* Re-enable tool-tips. */ - if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) { + if (event->type == MOUSEMOVE && + (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1])) { ui_blocks_set_tooltips(region, true); } + /* Always do this, to reliably update tree-row highlighting, even if the mouse hovers a button + * inside the row (it's an overlapping layout). */ + ui_handle_tree_hover(event, region); + /* delayed apply callbacks */ ui_apply_but_funcs_after(C); @@ -11018,7 +11424,8 @@ static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *UNUSE } /* Re-enable tool-tips. */ - if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) { + if (event->type == MOUSEMOVE && + (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1])) { ui_blocks_set_tooltips(region, true); } @@ -11112,7 +11519,8 @@ static int ui_popup_handler(bContext *C, const wmEvent *event, void *userdata) } else { /* Re-enable tool-tips */ - if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) { + if (event->type == MOUSEMOVE && + (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1])) { ui_blocks_set_tooltips(menu->region, true); } } @@ -11282,20 +11690,25 @@ void UI_screen_free_active_but(const bContext *C, bScreen *screen) } } -/* returns true if highlighted button allows drop of names */ -/* called in region context */ -bool UI_but_active_drop_name(bContext *C) +uiBut *UI_but_active_drop_name_button(const bContext *C) { ARegion *region = CTX_wm_region(C); uiBut *but = ui_region_find_active_but(region); if (but) { if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - return true; + return but; } } - return false; + return NULL; +} + +/* returns true if highlighted button allows drop of names */ +/* called in region context */ +bool UI_but_active_drop_name(const bContext *C) +{ + return UI_but_active_drop_name_button(C) != NULL; } bool UI_but_active_drop_color(bContext *C) @@ -11314,3 +11727,100 @@ bool UI_but_active_drop_color(bContext *C) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI Block Interaction API + * \{ */ + +void UI_block_interaction_set(uiBlock *block, uiBlockInteraction_CallbackData *callbacks) +{ + block->custom_interaction_callbacks = *callbacks; +} + +static uiBlockInteraction_Handle *ui_block_interaction_begin(bContext *C, + uiBlock *block, + const bool is_click) +{ + BLI_assert(block->custom_interaction_callbacks.begin_fn != NULL); + uiBlockInteraction_Handle *interaction = MEM_callocN(sizeof(*interaction), __func__); + + int unique_retval_ids_len = 0; + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->active || (but->flag & UI_BUT_DRAG_MULTI)) { + unique_retval_ids_len++; + } + } + + int *unique_retval_ids = MEM_mallocN(sizeof(*unique_retval_ids) * unique_retval_ids_len, + __func__); + unique_retval_ids_len = 0; + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->active || (but->flag & UI_BUT_DRAG_MULTI)) { + unique_retval_ids[unique_retval_ids_len++] = but->retval; + } + } + + if (unique_retval_ids_len > 1) { + qsort(unique_retval_ids, unique_retval_ids_len, sizeof(int), BLI_sortutil_cmp_int); + unique_retval_ids_len = BLI_array_deduplicate_ordered(unique_retval_ids, + unique_retval_ids_len); + unique_retval_ids = MEM_reallocN(unique_retval_ids, + sizeof(*unique_retval_ids) * unique_retval_ids_len); + } + + interaction->params.is_click = is_click; + interaction->params.unique_retval_ids = unique_retval_ids; + interaction->params.unique_retval_ids_len = unique_retval_ids_len; + + interaction->user_data = block->custom_interaction_callbacks.begin_fn( + C, &interaction->params, block->custom_interaction_callbacks.arg1); + return interaction; +} + +static void ui_block_interaction_end(bContext *C, + uiBlockInteraction_CallbackData *callbacks, + uiBlockInteraction_Handle *interaction) +{ + BLI_assert(callbacks->end_fn != NULL); + callbacks->end_fn(C, &interaction->params, callbacks->arg1, interaction->user_data); + MEM_freeN(interaction->params.unique_retval_ids); + MEM_freeN(interaction); +} + +static void ui_block_interaction_update(bContext *C, + uiBlockInteraction_CallbackData *callbacks, + uiBlockInteraction_Handle *interaction) +{ + BLI_assert(callbacks->update_fn != NULL); + callbacks->update_fn(C, &interaction->params, callbacks->arg1, interaction->user_data); +} + +/** + * \note #ui_block_interaction_begin cannot be called when setting the button state + * (e.g. #BUTTON_STATE_NUM_EDITING) for the following reasons. + * + * - Other buttons may still be activated using #UI_BUT_DRAG_MULTI + * which is necessary before gathering all the #uiBut.retval values to initialize + * #uiBlockInteraction_Params.unique_retval_ids. + * - When clicking on a number button it's not known if the event is a click or a drag. + * + * Instead, it must be called immediately before the drag action begins. + */ +static void ui_block_interaction_begin_ensure(bContext *C, + uiBlock *block, + uiHandleButtonData *data, + const bool is_click) +{ + if (data->custom_interaction_handle) { + return; + } + if (block->custom_interaction_callbacks.begin_fn == NULL) { + return; + } + + uiBlockInteraction_Handle *interaction = ui_block_interaction_begin(C, block, is_click); + interaction->user_count = 1; + data->custom_interaction_handle = interaction; +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index 4defbed940e..f849ec55e4f 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -47,6 +47,7 @@ #include "DNA_gpencil_types.h" #include "DNA_object_types.h" #include "DNA_screen_types.h" +#include "DNA_sequence_types.h" #include "DNA_space_types.h" #include "RNA_access.h" @@ -66,6 +67,7 @@ #include "ED_datafiles.h" #include "ED_keyframes_draw.h" +#include "ED_keyframes_keylist.h" #include "ED_render.h" #include "UI_interface.h" @@ -251,66 +253,33 @@ static void def_internal_vicon(int icon_id, VectorDrawFunc drawFunc) /* Utilities */ -static void viconutil_set_point(int pt[2], int x, int y) -{ - pt[0] = x; - pt[1] = y; -} - -static void vicon_small_tri_right_draw(int x, int y, int w, int UNUSED(h), float alpha) -{ - int pts[3][2]; - const int cx = x + w / 2 - 4; - const int cy = y + w / 2; - const int d = w / 5, d2 = w / 7; - - viconutil_set_point(pts[0], cx - d2, cy + d); - viconutil_set_point(pts[1], cx - d2, cy - d); - viconutil_set_point(pts[2], cx + d2, cy); - - uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformColor4f(0.2f, 0.2f, 0.2f, alpha); - - immBegin(GPU_PRIM_TRIS, 3); - immVertex2iv(pos, pts[0]); - immVertex2iv(pos, pts[1]); - immVertex2iv(pos, pts[2]); - immEnd(); - - immUnbindProgram(); -} - static void vicon_keytype_draw_wrapper( int x, int y, int w, int h, float alpha, short key_type, short handle_type) { - /* init dummy theme state for Action Editor - where these colors are defined - * (since we're doing this offscreen, free from any particular space_id) - */ + /* Initialize dummy theme state for Action Editor - where these colors are defined + * (since we're doing this off-screen, free from any particular space_id). */ struct bThemeState theme_state; UI_Theme_Store(&theme_state); UI_SetTheme(SPACE_ACTION, RGN_TYPE_WINDOW); - /* the "x" and "y" given are the bottom-left coordinates of the icon, - * while the draw_keyframe_shape() function needs the midpoint for - * the keyframe - */ + /* The "x" and "y" given are the bottom-left coordinates of the icon, + * while the #draw_keyframe_shape() function needs the midpoint for the keyframe. */ const float xco = x + w / 2 + 0.5f; const float yco = y + h / 2 + 0.5f; GPUVertFormat *format = immVertexFormat(); - const uint pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - const uint size_id = GPU_vertformat_attr_add(format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); - uint color_id = GPU_vertformat_attr_add( + KeyframeShaderBindings sh_bindings; + sh_bindings.pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + sh_bindings.size_id = GPU_vertformat_attr_add(format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); + sh_bindings.color_id = GPU_vertformat_attr_add( format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); - uint outline_color_id = GPU_vertformat_attr_add( + sh_bindings.outline_color_id = GPU_vertformat_attr_add( format, "outlineColor", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); - const uint flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); + sh_bindings.flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 1.0f); immUniform2f("ViewportSize", -1.0f, -1.0f); immBegin(GPU_PRIM_POINTS, 1); @@ -328,11 +297,7 @@ static void vicon_keytype_draw_wrapper( key_type, KEYFRAME_SHAPE_BOTH, alpha, - pos_id, - size_id, - color_id, - outline_color_id, - flags_id, + &sh_bindings, handle_type, KEYFRAME_EXTREME_NONE); @@ -485,6 +450,35 @@ DEF_ICON_COLLECTION_COLOR_DRAW(08, COLLECTION_COLOR_08); # undef DEF_ICON_COLLECTION_COLOR_DRAW +static void vicon_strip_color_draw( + short color_tag, int x, int y, int w, int UNUSED(h), float UNUSED(alpha)) +{ + bTheme *btheme = UI_GetTheme(); + const ThemeStripColor *strip_color = &btheme->strip_color[color_tag]; + + const float aspect = (float)ICON_DEFAULT_WIDTH / (float)w; + + UI_icon_draw_ex(x, y, ICON_SNAP_FACE, aspect, 1.0f, 0.0f, strip_color->color, true); +} + +# define DEF_ICON_STRIP_COLOR_DRAW(index, color) \ + static void vicon_strip_color_draw_##index(int x, int y, int w, int h, float alpha) \ + { \ + vicon_strip_color_draw(color, x, y, w, h, alpha); \ + } + +DEF_ICON_STRIP_COLOR_DRAW(01, SEQUENCE_COLOR_01); +DEF_ICON_STRIP_COLOR_DRAW(02, SEQUENCE_COLOR_02); +DEF_ICON_STRIP_COLOR_DRAW(03, SEQUENCE_COLOR_03); +DEF_ICON_STRIP_COLOR_DRAW(04, SEQUENCE_COLOR_04); +DEF_ICON_STRIP_COLOR_DRAW(05, SEQUENCE_COLOR_05); +DEF_ICON_STRIP_COLOR_DRAW(06, SEQUENCE_COLOR_06); +DEF_ICON_STRIP_COLOR_DRAW(07, SEQUENCE_COLOR_07); +DEF_ICON_STRIP_COLOR_DRAW(08, SEQUENCE_COLOR_08); +DEF_ICON_STRIP_COLOR_DRAW(09, SEQUENCE_COLOR_09); + +# undef DEF_ICON_STRIP_COLOR_DRAW + /* Dynamically render icon instead of rendering a plain color to a texture/buffer * This is not strictly a "vicon", as it needs access to icon->obj to get the color info, * but it works in a very similar way. @@ -957,8 +951,6 @@ static void init_internal_icons(void) } } - def_internal_vicon(ICON_SMALL_TRI_RIGHT_VEC, vicon_small_tri_right_draw); - def_internal_vicon(ICON_KEYTYPE_KEYFRAME_VEC, vicon_keytype_keyframe_draw); def_internal_vicon(ICON_KEYTYPE_BREAKDOWN_VEC, vicon_keytype_breakdown_draw); def_internal_vicon(ICON_KEYTYPE_EXTREME_VEC, vicon_keytype_extreme_draw); @@ -1000,6 +992,16 @@ static void init_internal_icons(void) def_internal_vicon(ICON_COLLECTION_COLOR_06, vicon_collection_color_draw_06); def_internal_vicon(ICON_COLLECTION_COLOR_07, vicon_collection_color_draw_07); def_internal_vicon(ICON_COLLECTION_COLOR_08, vicon_collection_color_draw_08); + + def_internal_vicon(ICON_SEQUENCE_COLOR_01, vicon_strip_color_draw_01); + def_internal_vicon(ICON_SEQUENCE_COLOR_02, vicon_strip_color_draw_02); + def_internal_vicon(ICON_SEQUENCE_COLOR_03, vicon_strip_color_draw_03); + def_internal_vicon(ICON_SEQUENCE_COLOR_04, vicon_strip_color_draw_04); + def_internal_vicon(ICON_SEQUENCE_COLOR_05, vicon_strip_color_draw_05); + def_internal_vicon(ICON_SEQUENCE_COLOR_06, vicon_strip_color_draw_06); + def_internal_vicon(ICON_SEQUENCE_COLOR_07, vicon_strip_color_draw_07); + def_internal_vicon(ICON_SEQUENCE_COLOR_08, vicon_strip_color_draw_08); + def_internal_vicon(ICON_SEQUENCE_COLOR_09, vicon_strip_color_draw_09); } static void init_iconfile_list(struct ListBase *list) @@ -1016,7 +1018,7 @@ static void init_iconfile_list(struct ListBase *list) int index = 1; for (int i = 0; i < totfile; i++) { - if ((dir[i].type & S_IFREG)) { + if (dir[i].type & S_IFREG) { const char *filename = dir[i].relname; if (BLI_path_extension_check(filename, ".png")) { @@ -1180,7 +1182,7 @@ static DrawInfo *icon_ensure_drawinfo(Icon *icon) return di; } -/* note!, returns unscaled by DPI */ +/* NOTE:, returns unscaled by DPI. */ int UI_icon_get_width(int icon_id) { Icon *icon = BKE_icon_get(icon_id); @@ -1346,8 +1348,8 @@ void ui_icon_ensure_deferred(const bContext *C, const int icon_id, const bool bi case ICON_TYPE_PREVIEW: { ID *id = (icon->id_type != 0) ? icon->obj : NULL; PreviewImage *prv = id ? BKE_previewimg_id_ensure(id) : icon->obj; - /* Using jobs for screen previews crashes due to offscreen rendering. - * XXX would be nicer if PreviewImage could store if it supports jobs */ + /* Using jobs for screen previews crashes due to off-screen rendering. + * XXX: would be nicer if #PreviewImage could store if it supports jobs. */ const bool use_jobs = !id || (GS(id->name) != ID_SCR); if (prv) { @@ -1480,6 +1482,78 @@ PreviewImage *UI_icon_to_preview(int icon_id) return NULL; } +/** + * Version of #icon_draw_rect() that uses the GPU for scaling. This is only used for + * #ICON_TYPE_IMBUF because it's a backported fix for performance issues, see T92922. Only + * File/Asset Browser use #ICON_TYPE_IMBUF right now, which makes implications more predictable. + * + * TODO(Julian): This code is mostly duplicated. #icon_draw_rect() should be ported to use the GPU + * instead (D13144). + */ +static void icon_draw_rect_fast(float x, + float y, + int w, + int h, + float UNUSED(aspect), + int rw, + int rh, + uint *rect, + float alpha, + const float desaturate) +{ + int draw_w = w; + int draw_h = h; + int draw_x = x; + /* We need to round y, to avoid the icon jittering in some cases. */ + int draw_y = round_fl_to_int(y); + + /* sanity check */ + if (w <= 0 || h <= 0 || w > 2000 || h > 2000) { + printf("%s: icons are %i x %i pixels?\n", __func__, w, h); + BLI_assert_msg(0, "invalid icon size"); + return; + } + /* modulate color */ + const float col[4] = {alpha, alpha, alpha, alpha}; + + float scale_x = 1.0f; + float scale_y = 1.0f; + /* rect contains image in 'rendersize', we only scale if needed */ + if (rw != w || rh != h) { + /* preserve aspect ratio and center */ + if (rw > rh) { + draw_w = w; + draw_h = (int)(((float)rh / (float)rw) * (float)w); + draw_y += (h - draw_h) / 2; + } + else if (rw < rh) { + draw_w = (int)(((float)rw / (float)rh) * (float)h); + draw_h = h; + draw_x += (w - draw_w) / 2; + } + scale_x = draw_w / (float)rw; + scale_y = draw_h / (float)rh; + /* If the image is squared, the `draw_*` initialization values are good. */ + } + + /* draw */ + eGPUBuiltinShader shader; + if (desaturate != 0.0f) { + shader = GPU_SHADER_2D_IMAGE_DESATURATE_COLOR; + } + else { + shader = GPU_SHADER_2D_IMAGE_COLOR; + } + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(shader); + + if (shader == GPU_SHADER_2D_IMAGE_DESATURATE_COLOR) { + immUniform1f("factor", desaturate); + } + + immDrawPixelsTexScaled( + &state, draw_x, draw_y, rw, rh, GPU_RGBA8, true, rect, scale_x, scale_y, 1.0f, 1.0f, col); +} + static void icon_draw_rect(float x, float y, int w, @@ -1495,12 +1569,13 @@ static void icon_draw_rect(float x, int draw_w = w; int draw_h = h; int draw_x = x; - int draw_y = y; + /* We need to round y, to avoid the icon jittering in some cases. */ + int draw_y = round_fl_to_int(y); /* sanity check */ if (w <= 0 || h <= 0 || w > 2000 || h > 2000) { printf("%s: icons are %i x %i pixels?\n", __func__, w, h); - BLI_assert(!"invalid icon size"); + BLI_assert_msg(0, "invalid icon size"); return; } /* modulate color */ @@ -1519,7 +1594,7 @@ static void icon_draw_rect(float x, draw_h = h; draw_x += (w - draw_w) / 2; } - /* if the image is squared, the draw_ initialization values are good */ + /* If the image is squared, the `draw_*` initialization values are good. */ /* first allocate imbuf for scaling and copy preview into it */ ima = IMB_allocImBuf(rw, rh, 32, IB_rect); @@ -1803,7 +1878,9 @@ static void icon_draw_size(float x, ImBuf *ibuf = icon->obj; GPU_blend(GPU_BLEND_ALPHA_PREMULT); - icon_draw_rect(x, y, w, h, aspect, ibuf->x, ibuf->y, ibuf->rect, alpha, desaturate); + /* These icons are only used by the File/Asset Browser currently. Without this `_fast()` + * version, there may be performance issues, see T92922. */ + icon_draw_rect_fast(x, y, w, h, aspect, ibuf->x, ibuf->y, ibuf->rect, alpha, desaturate); GPU_blend(GPU_BLEND_ALPHA); } else if (di->type == ICON_TYPE_VECTOR) { @@ -2143,7 +2220,7 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id) static int ui_id_screen_get_icon(const bContext *C, ID *id) { BKE_icon_id_ensure(id); - /* Don't use jobs here, offscreen rendering doesn't like this and crashes. */ + /* Don't use jobs here, off-screen rendering doesn't like this and crashes. */ ui_id_icon_render(C, id, false); return id->icon_id; @@ -2201,7 +2278,7 @@ int UI_icon_from_library(const ID *id) return ICON_NONE; } -int UI_icon_from_rnaptr(bContext *C, PointerRNA *ptr, int rnaicon, const bool big) +int UI_icon_from_rnaptr(const bContext *C, PointerRNA *ptr, int rnaicon, const bool big) { ID *id = NULL; @@ -2294,7 +2371,7 @@ int UI_icon_from_idcode(const int idcode) case ID_ME: return ICON_MESH_DATA; case ID_MSK: - return ICON_MOD_MASK; /* TODO! this would need its own icon! */ + return ICON_MOD_MASK; /* TODO: this would need its own icon! */ case ID_NT: return ICON_NODETREE; case ID_OB: @@ -2302,9 +2379,9 @@ int UI_icon_from_idcode(const int idcode) case ID_PA: return ICON_PARTICLE_DATA; case ID_PAL: - return ICON_COLOR; /* TODO! this would need its own icon! */ + return ICON_COLOR; /* TODO: this would need its own icon! */ case ID_PC: - return ICON_CURVE_BEZCURVE; /* TODO! this would need its own icon! */ + return ICON_CURVE_BEZCURVE; /* TODO: this would need its own icon! */ case ID_LP: return ICON_OUTLINER_DATA_LIGHTPROBE; case ID_SCE: @@ -2427,6 +2504,7 @@ void UI_icon_draw_ex(float x, ImBuf *UI_icon_alert_imbuf_get(eAlertIcon icon) { #ifdef WITH_HEADLESS + UNUSED_VARS(icon); return NULL; #else const int ALERT_IMG_SIZE = 256; diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 97b9bb66f07..c7c6a88de01 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -116,7 +116,7 @@ extern const char ui_radial_dir_to_numpad[8]; extern const short ui_radial_dir_to_angle[8]; /* internal panel drawing defines */ -#define PNL_HEADER (UI_UNIT_Y * 1.2) /* 24 default */ +#define PNL_HEADER (UI_UNIT_Y * 1.25) /* 24 default */ /* bit button defines */ /* Bit operations */ @@ -221,7 +221,8 @@ struct uiBut { const char *tip; uiButToolTipFunc tip_func; - void *tip_argN; + void *tip_arg; + uiFreeArgFunc tip_arg_free; /** info on why button is disabled, displayed in tooltip */ const char *disabled_info; @@ -254,14 +255,14 @@ struct uiBut { /* Operator data */ struct wmOperatorType *optype; struct PointerRNA *opptr; - short opcontext; + wmOperatorCallContext opcontext; /** When non-zero, this is the key used to activate a menu items (`a-z` always lower case). */ uchar menu_key; ListBase extra_op_icons; /** #uiButExtraOpIcon */ - /* Draggable data, type is WM_DRAG_... */ + /* Drag-able data, type is WM_DRAG_... */ char dragtype; short dragflag; void *dragpoin; @@ -316,7 +317,7 @@ typedef struct uiButSearch { void *item_active; void *arg; - uiButSearchArgFreeFn arg_free_fn; + uiFreeArgFunc arg_free_fn; uiButSearchContextMenuFn item_context_menu_fn; uiButSearchTooltipFn item_tooltip_fn; @@ -359,6 +360,14 @@ typedef struct uiButDatasetRow { int indentation; } uiButDatasetRow; +/** Derived struct for #UI_BTYPE_TREEROW. */ +typedef struct uiButTreeRow { + uiBut but; + + uiTreeViewItemHandle *tree_item; + int indentation; +} uiButTreeRow; + /** Derived struct for #UI_BTYPE_HSVCUBE. */ typedef struct uiButHSVCube { uiBut but; @@ -398,6 +407,7 @@ typedef struct uiButExtraOpIcon { struct wmOperatorCallParams *optype_params; bool highlighted; + bool disabled; } uiButExtraOpIcon; typedef struct ColorPicker { @@ -487,6 +497,11 @@ struct uiBlock { ListBase contexts; + /** A block can store "views" on data-sets. Currently tree-views (#AbstractTreeView) only. + * Others are imaginable, e.g. table-views, grid-views, etc. These are stored here to support + * state that is persistent over redraws (e.g. collapsed tree-view items). */ + ListBase views; + char name[UI_MAX_NAME_STR]; float winmat[4][4]; @@ -510,6 +525,9 @@ struct uiBlock { uiBlockHandleFunc handle_func; void *handle_func_arg; + /** Custom interaction data. */ + uiBlockInteraction_CallbackData custom_interaction_callbacks; + /** Custom extra event handling. */ int (*block_event_func)(const struct bContext *C, struct uiBlock *, const struct wmEvent *); @@ -593,11 +611,19 @@ typedef struct uiSafetyRct { void ui_fontscale(short *points, float aspect); +extern void ui_block_to_region_fl(const struct ARegion *region, + uiBlock *block, + float *r_x, + float *r_y); extern void ui_block_to_window_fl(const struct ARegion *region, uiBlock *block, float *x, float *y); extern void ui_block_to_window(const struct ARegion *region, uiBlock *block, int *x, int *y); +extern void ui_block_to_region_rctf(const struct ARegion *region, + uiBlock *block, + rctf *rct_dst, + const rctf *rct_src); extern void ui_block_to_window_rctf(const struct ARegion *region, uiBlock *block, rctf *rct_dst, @@ -616,6 +642,9 @@ extern void ui_window_to_region(const struct ARegion *region, int *x, int *y); extern void ui_window_to_region_rcti(const struct ARegion *region, rcti *rect_dst, const rcti *rct_src); +extern void ui_window_to_region_rctf(const struct ARegion *region, + rctf *rect_dst, + const rctf *rct_src); extern void ui_region_to_window(const struct ARegion *region, int *x, int *y); extern void ui_region_winrct_get_no_margin(const struct ARegion *region, struct rcti *r_rect); @@ -651,6 +680,7 @@ extern bool ui_but_string_eval_number(struct bContext *C, extern int ui_but_string_get_max_length(uiBut *but); /* Clear & exit the active button's string. */ extern void ui_but_active_string_clear_and_exit(struct bContext *C, uiBut *but) ATTR_NONNULL(); +extern void ui_but_set_string_interactive(struct bContext *C, uiBut *but, const char *value); extern uiBut *ui_but_drag_multi_edit_get(uiBut *but); void ui_def_but_icon(uiBut *but, const int icon, const int flag); @@ -666,6 +696,9 @@ void ui_but_range_set_hard(uiBut *but); void ui_but_range_set_soft(uiBut *but); bool ui_but_context_poll_operator(struct bContext *C, struct wmOperatorType *ot, const uiBut *but); +bool ui_but_context_poll_operator_ex(struct bContext *C, + const uiBut *but, + const struct wmOperatorCallParams *optype_params); extern void ui_but_update(uiBut *but); extern void ui_but_update_edited(uiBut *but); @@ -704,7 +737,7 @@ struct uiPopupBlockCreate { uiBlockCreateFunc create_func; uiBlockHandleCreateFunc handle_create_func; void *arg; - void (*arg_free)(void *arg); + uiFreeArgFunc arg_free; int event_xy[2]; @@ -798,7 +831,7 @@ struct ARegion *ui_searchbox_create_menu(struct bContext *C, struct ARegion *butregion, uiButSearch *search_but); -bool ui_searchbox_inside(struct ARegion *region, int x, int y); +bool ui_searchbox_inside(struct ARegion *region, const int xy[2]) ATTR_NONNULL(1, 2); int ui_searchbox_find_index(struct ARegion *region, const char *name); void ui_searchbox_update(struct bContext *C, struct ARegion *region, uiBut *but, const bool reset); int ui_searchbox_autocomplete(struct bContext *C, struct ARegion *region, uiBut *but, char *str); @@ -828,7 +861,7 @@ uiPopupBlockHandle *ui_popup_block_create(struct bContext *C, uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func, void *arg, - void (*arg_free)(void *arg)); + uiFreeArgFunc arg_free); uiPopupBlockHandle *ui_popup_menu_create(struct bContext *C, struct ARegion *butregion, uiBut *but, @@ -849,7 +882,7 @@ void ui_pie_menu_level_create(uiBlock *block, struct IDProperty *properties, const EnumPropertyItem *items, int totitem, - int context, + wmOperatorCallContext context, int flag); /* interface_region_popup.c */ @@ -927,9 +960,8 @@ const char *ui_textedit_undo(struct uiUndoStack_Text *undo_stack, int *r_cursor_index); /* interface_handlers.c */ -PointerRNA *ui_handle_afterfunc_add_operator(struct wmOperatorType *ot, - int opcontext, - bool create_props); +extern void ui_handle_afterfunc_add_operator(struct wmOperatorType *ot, + wmOperatorCallContext opcontext); extern void ui_pan_to_scroll(const struct wmEvent *event, int *type, int *val); extern void ui_but_activate_event(struct bContext *C, struct ARegion *region, uiBut *but); extern void ui_but_activate_over(struct bContext *C, struct ARegion *region, uiBut *but); @@ -942,6 +974,7 @@ extern void ui_but_execute_end(struct bContext *C, uiBut *but, void *active_back); extern void ui_but_active_free(const struct bContext *C, uiBut *but); +extern void ui_but_update_view_for_active(const struct bContext *C, const uiBlock *block); extern int ui_but_menu_direction(uiBut *but); extern void ui_but_text_password_hide(char password_str[128], uiBut *but, const bool restore); extern uiBut *ui_but_find_select_in_enum(uiBut *but, int direction); @@ -1000,9 +1033,7 @@ enum { struct GPUBatch *ui_batch_roundbox_widget_get(void); struct GPUBatch *ui_batch_roundbox_shadow_get(void); -void ui_draw_anti_tria_rect(const rctf *rect, char dir, const float color[4]); void ui_draw_menu_back(struct uiStyle *style, uiBlock *block, rcti *rect); -void ui_draw_box_opaque(rcti *rect, int roundboxalign); void ui_draw_popover_back(struct ARegion *region, struct uiStyle *style, uiBlock *block, @@ -1041,13 +1072,23 @@ void ui_draw_menu_item(const struct uiFontStyle *fstyle, int state, uiMenuItemSeparatorType separator_type, int *r_xmax); -void ui_draw_preview_item( - const struct uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, int state); +void ui_draw_preview_item(const struct uiFontStyle *fstyle, + rcti *rect, + const char *name, + int iconid, + int state, + eFontStyle_Align text_align); +void ui_draw_preview_item_stateless(const struct uiFontStyle *fstyle, + rcti *rect, + const char *name, + int iconid, + const uchar text_col[4], + eFontStyle_Align text_align); #define UI_TEXT_MARGIN_X 0.4f #define UI_POPUP_MARGIN (UI_DPI_FAC * 12) /* Margin at top of screen for popups. Note this value must be sufficient - to draw a popover arrow to avoid cropping it. */ + * to draw a popover arrow to avoid cropping it. */ #define UI_POPUP_MENU_TOP (int)(10 * UI_DPI_FAC) #define UI_PIXEL_AA_JITTER 8 @@ -1070,6 +1111,7 @@ void ui_resources_free(void); /* interface_layout.c */ void ui_layout_add_but(uiLayout *layout, uiBut *but); +void ui_layout_remove_but(uiLayout *layout, const uiBut *but); bool ui_layout_replace_but_ptr(uiLayout *layout, const void *old_but_ptr, uiBut *new_but); uiBut *ui_but_add_search(uiBut *but, PointerRNA *ptr, @@ -1121,25 +1163,41 @@ bool ui_but_contains_rect(const uiBut *but, const rctf *rect); bool ui_but_contains_point_px_icon(const uiBut *but, struct ARegion *region, const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT; -bool ui_but_contains_point_px(const uiBut *but, const struct ARegion *region, int x, int y) - ATTR_WARN_UNUSED_RESULT; +bool ui_but_contains_point_px(const uiBut *but, const struct ARegion *region, const int xy[2]) + ATTR_NONNULL(1, 2, 3) ATTR_WARN_UNUSED_RESULT; -uiBut *ui_list_find_mouse_over(struct ARegion *region, +uiBut *ui_list_find_mouse_over(const struct ARegion *region, const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT; - +uiBut *ui_list_find_from_row(const struct ARegion *region, + const uiBut *row_but) ATTR_WARN_UNUSED_RESULT; +uiBut *ui_list_row_find_mouse_over(const struct ARegion *region, const int xy[2]) + ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT; +uiBut *ui_list_row_find_from_index(const struct ARegion *region, + const int index, + uiBut *listbox) ATTR_WARN_UNUSED_RESULT; +uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int xy[2]) + ATTR_NONNULL(1, 2); +uiBut *ui_tree_row_find_active(const struct ARegion *region); + +typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata); uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region, - const int x, - const int y, - const bool labeledit) ATTR_WARN_UNUSED_RESULT; + const int xy[2], + const bool labeledit, + const uiButFindPollFn find_poll, + const void *find_custom_data) + ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT; uiBut *ui_but_find_mouse_over(const struct ARegion *region, const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT; uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px) ATTR_WARN_UNUSED_RESULT; -uiBut *ui_list_find_mouse_over_ex(struct ARegion *region, int x, int y) ATTR_WARN_UNUSED_RESULT; +uiBut *ui_list_find_mouse_over_ex(const struct ARegion *region, const int xy[2]) + ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT; bool ui_but_contains_password(const uiBut *but) ATTR_WARN_UNUSED_RESULT; +size_t ui_but_drawstr_without_sep_char(const uiBut *but, char *str, size_t str_maxlen) + ATTR_NONNULL(1, 2); size_t ui_but_drawstr_len_without_sep_char(const uiBut *but); size_t ui_but_tip_len_only_first_line(const uiBut *but); @@ -1148,15 +1206,14 @@ uiBut *ui_but_next(uiBut *but) ATTR_WARN_UNUSED_RESULT; uiBut *ui_but_first(uiBlock *block) ATTR_WARN_UNUSED_RESULT; uiBut *ui_but_last(uiBlock *block) ATTR_WARN_UNUSED_RESULT; +uiBut *ui_block_active_but_get(const uiBlock *block); bool ui_block_is_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT; bool ui_block_is_popover(const uiBlock *block) ATTR_WARN_UNUSED_RESULT; bool ui_block_is_pie_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT; bool ui_block_is_popup_any(const uiBlock *block) ATTR_WARN_UNUSED_RESULT; -uiBlock *ui_block_find_mouse_over_ex(const struct ARegion *region, - const int x, - const int y, - bool only_clip); +uiBlock *ui_block_find_mouse_over_ex(const struct ARegion *region, const int xy[2], bool only_clip) + ATTR_NONNULL(1, 2); uiBlock *ui_block_find_mouse_over(const struct ARegion *region, const struct wmEvent *event, bool only_clip); @@ -1165,17 +1222,17 @@ uiBut *ui_region_find_first_but_test_flag(struct ARegion *region, int flag_include, int flag_exclude); uiBut *ui_region_find_active_but(struct ARegion *region) ATTR_WARN_UNUSED_RESULT; -bool ui_region_contains_point_px(const struct ARegion *region, - int x, - int y) ATTR_WARN_UNUSED_RESULT; +bool ui_region_contains_point_px(const struct ARegion *region, const int xy[2]) + ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT; bool ui_region_contains_rect_px(const struct ARegion *region, const rcti *rect_px); -struct ARegion *ui_screen_region_find_mouse_over_ex(struct bScreen *screen, int x, int y); +struct ARegion *ui_screen_region_find_mouse_over_ex(struct bScreen *screen, const int xy[2]) + ATTR_NONNULL(1, 2); struct ARegion *ui_screen_region_find_mouse_over(struct bScreen *screen, const struct wmEvent *event); /* interface_context_menu.c */ -bool ui_popup_context_menu_for_button(struct bContext *C, uiBut *but); +bool ui_popup_context_menu_for_button(struct bContext *C, uiBut *but, const struct wmEvent *event); void ui_popup_context_menu_for_panel(struct bContext *C, struct ARegion *region, struct Panel *panel); @@ -1203,6 +1260,9 @@ void UI_OT_eyedropper_driver(struct wmOperatorType *ot); /* interface_eyedropper_gpencil_color.c */ void UI_OT_eyedropper_gpencil_color(struct wmOperatorType *ot); +/* interface_template_asset_view.cc */ +struct uiListType *UI_UL_asset_view(void); + /** * For use with #ui_rna_collection_search_update_fn. */ @@ -1231,6 +1291,13 @@ bool ui_jump_to_target_button_poll(struct bContext *C); /* interface_queries.c */ void ui_interface_tag_script_reload_queries(void); +/* interface_view.cc */ +void ui_block_free_views(struct uiBlock *block); +uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, + const uiTreeViewHandle *new_view); +uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block, + const uiTreeViewItemHandle *new_item_handle); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index cf3abc9be4a..b792c59481c 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -82,7 +82,7 @@ typedef struct uiLayoutRoot { struct uiLayoutRoot *next, *prev; int type; - int opcontext; + wmOperatorCallContext opcontext; int emw, emh; int padding; @@ -169,7 +169,7 @@ struct uiLayout { bool enabled; bool redalert; bool keepaspect; - /** For layouts inside gridflow, they and their items shall never have a fixed maximal size. */ + /** For layouts inside grid-flow, they and their items shall never have a fixed maximal size. */ bool variable_size; char alignment; eUIEmbossType emboss; @@ -282,41 +282,95 @@ static int ui_layout_vary_direction(uiLayout *layout) static bool ui_layout_variable_size(uiLayout *layout) { - /* Note that this code is probably a bit flakey, we'd probably want to know whether it's + /* Note that this code is probably a bit flaky, we'd probably want to know whether it's * variable in X and/or Y, etc. But for now it mimics previous one, * with addition of variable flag set for children of grid-flow layouts. */ return ui_layout_vary_direction(layout) == UI_ITEM_VARY_X || layout->variable_size; } -/* estimated size of text + icon */ -static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact) +/** + * Factors to apply to #UI_UNIT_X when calculating button width. + * This is used when the layout is a varying size, see #ui_layout_variable_size. + */ +struct uiTextIconPadFactor { + float text; + float icon; + float icon_only; +}; + +/** + * This adds over an icons width of padding even when no icon is used, + * this is done because most buttons need additional space (drop-down chevron for example). + * menus and labels use much smaller `text` values compared to this default. + * + * \note It may seem odd that the icon only adds 0.25 + * but taking margins into account its fine, + * except for #ui_text_pad_compact where a bit more margin is required. + */ +static const struct uiTextIconPadFactor ui_text_pad_default = { + .text = 1.50f, + .icon = 0.25f, + .icon_only = 0.0f, +}; + +/** #ui_text_pad_default scaled down. */ +static const struct uiTextIconPadFactor ui_text_pad_compact = { + .text = 1.25f, + .icon = 0.35f, + .icon_only = 0.0f, +}; + +/** Least amount of padding not to clip the text or icon. */ +static const struct uiTextIconPadFactor ui_text_pad_none = { + .text = 0.25f, + .icon = 1.50f, + .icon_only = 0.0f, +}; + +/** + * Estimated size of text + icon. + */ +static int ui_text_icon_width_ex(uiLayout *layout, + const char *name, + int icon, + const struct uiTextIconPadFactor *pad_factor) { const int unit_x = UI_UNIT_X * (layout->scale[0] ? layout->scale[0] : 1.0f); + /* When there is no text, always behave as if this is an icon-only button + * since it's not useful to return empty space. */ if (icon && !name[0]) { - return unit_x; /* icon only */ + return unit_x * (1.0f + pad_factor->icon_only); } if (ui_layout_variable_size(layout)) { if (!icon && !name[0]) { - return unit_x; /* No icon or name. */ + return unit_x * (1.0f + pad_factor->icon_only); } + if (layout->alignment != UI_LAYOUT_ALIGN_EXPAND) { layout->item.flag |= UI_ITEM_FIXED_SIZE; } - const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - float margin = compact ? 1.25 : 1.50; + + float margin = pad_factor->text; if (icon) { - /* It may seem odd that the icon only adds (unit_x / 4) - * but taking margins into account its fine, except - * in compact mode a bit more margin is required. */ - margin += compact ? 0.35 : 0.25; + margin += pad_factor->icon; } - return UI_fontstyle_string_width(fstyle, name) + (unit_x * margin); + + const float aspect = layout->root->block->aspect; + const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; + return UI_fontstyle_string_width_with_block_aspect(fstyle, name, aspect) + + (int)ceilf(unit_x * margin); } return unit_x * 10; } +static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact) +{ + return ui_text_icon_width_ex( + layout, name, icon, compact ? &ui_text_pad_compact : &ui_text_pad_default); +} + static void ui_item_size(uiItem *item, int *r_w, int *r_h) { if (item->type == ITEM_BUTTON) { @@ -643,7 +697,7 @@ static void ui_item_array(uiLayout *layout, NULL); } else { - /* note, this block of code is a bit arbitrary and has just been made + /* NOTE: this block of code is a bit arbitrary and has just been made * to work with common cases, but may need to be re-worked */ /* special case, boolean array in a menu, this could be used in a more generic way too */ @@ -662,7 +716,7 @@ static void ui_item_array(uiLayout *layout, } } - /* show checkboxes for rna on a non-emboss block (menu for eg) */ + /* Show check-boxes for rna on a non-emboss block (menu for eg). */ bool *boolarr = NULL; if (type == PROP_BOOLEAN && ELEM(layout->root->block->emboss, UI_EMBOSS_NONE, UI_EMBOSS_PULLDOWN)) { @@ -921,10 +975,10 @@ static void ui_keymap_but_cb(bContext *UNUSED(C), void *but_v, void *UNUSED(key_ { uiBut *but = but_v; - RNA_boolean_set(&but->rnapoin, "shift", (but->modifier_key & KM_SHIFT) != 0); - RNA_boolean_set(&but->rnapoin, "ctrl", (but->modifier_key & KM_CTRL) != 0); - RNA_boolean_set(&but->rnapoin, "alt", (but->modifier_key & KM_ALT) != 0); - RNA_boolean_set(&but->rnapoin, "oskey", (but->modifier_key & KM_OSKEY) != 0); + RNA_int_set(&but->rnapoin, "shift", (but->modifier_key & KM_SHIFT) ? KM_MOD_HELD : KM_NOTHING); + RNA_int_set(&but->rnapoin, "ctrl", (but->modifier_key & KM_CTRL) ? KM_MOD_HELD : KM_NOTHING); + RNA_int_set(&but->rnapoin, "alt", (but->modifier_key & KM_ALT) ? KM_MOD_HELD : KM_NOTHING); + RNA_int_set(&but->rnapoin, "oskey", (but->modifier_key & KM_OSKEY) ? KM_MOD_HELD : KM_NOTHING); } /** @@ -1164,7 +1218,7 @@ static uiBut *uiItemFullO_ptr_ex(uiLayout *layout, const char *name, int icon, IDProperty *properties, - int context, + wmOperatorCallContext context, int flag, PointerRNA *r_opptr) { @@ -1285,7 +1339,7 @@ static void ui_item_menu_hold(struct bContext *C, ARegion *butregion, uiBut *but UI_menutype_draw(C, mt, layout); } else { - uiItemL(layout, "Menu Missing:", ICON_NONE); + uiItemL(layout, TIP_("Menu Missing:"), ICON_NONE); uiItemL(layout, menu_id, ICON_NONE); } UI_popup_menu_end(C, pup); @@ -1296,7 +1350,7 @@ void uiItemFullO_ptr(uiLayout *layout, const char *name, int icon, IDProperty *properties, - int context, + wmOperatorCallContext context, int flag, PointerRNA *r_opptr) { @@ -1308,7 +1362,7 @@ void uiItemFullOMenuHold_ptr(uiLayout *layout, const char *name, int icon, IDProperty *properties, - int context, + wmOperatorCallContext context, int flag, const char *menu_id, PointerRNA *r_opptr) @@ -1322,7 +1376,7 @@ void uiItemFullO(uiLayout *layout, const char *name, int icon, IDProperty *properties, - int context, + wmOperatorCallContext context, int flag, PointerRNA *r_opptr) { @@ -1411,7 +1465,7 @@ BLI_INLINE bool ui_layout_is_radial(const uiLayout *layout) } /** - * Create ui items for enum items in \a item_array. + * Create UI items for enum items in \a item_array. * * A version of #uiItemsFullEnumO that takes pre-calculated item array. */ @@ -1420,7 +1474,7 @@ void uiItemsFullEnumO_items(uiLayout *layout, PointerRNA ptr, PropertyRNA *prop, IDProperty *properties, - int context, + wmOperatorCallContext context, int flag, const EnumPropertyItem *item_array, int totitem) @@ -1569,7 +1623,7 @@ void uiItemsFullEnumO(uiLayout *layout, const char *opname, const char *propname, IDProperty *properties, - int context, + wmOperatorCallContext context, int flag) { wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ @@ -1818,7 +1872,7 @@ static void ui_item_rna_size(uiLayout *layout, } else if (type == PROP_BOOLEAN) { if (icon == ICON_NONE) { - /* Exception for checkboxes, they need a little less space to align nicely. */ + /* Exception for check-boxes, they need a little less space to align nicely. */ is_checkbox_only = true; } icon = ICON_DOT; @@ -1984,7 +2038,7 @@ void uiItemFullR(uiLayout *layout, * a label to display in the first column, the heading is inserted there. Otherwise it's inserted * as a new row before the first item. */ uiLayout *heading_layout = ui_layout_heading_find(layout); - /* Although checkboxes use the split layout, they are an exception and should only place their + /* Although check-boxes use the split layout, they are an exception and should only place their * label in the second column, to not make that almost empty. * * Keep using 'use_prop_sep' instead of disabling it entirely because @@ -2062,7 +2116,7 @@ void uiItemFullR(uiLayout *layout, /* Menus and pie-menus don't show checkbox without this. */ if ((layout->root->type == UI_LAYOUT_MENU) || - /* Use checkboxes only as a fallback in pie-menu's, when no icon is defined. */ + /* Use check-boxes only as a fallback in pie-menu's, when no icon is defined. */ ((layout->root->type == UI_LAYOUT_PIEMENU) && (icon == ICON_NONE))) { const int prop_flag = RNA_property_flag(prop); if (type == PROP_BOOLEAN) { @@ -2353,7 +2407,7 @@ void uiItemFullR(uiLayout *layout, } } - /* Mark non-embossed textfields inside a listbox. */ + /* Mark non-embossed text-fields inside a list-box. */ if (but && (block->flag & UI_BLOCK_LIST_ITEM) && (but->type == UI_BTYPE_TEXT) && ELEM(but->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) { UI_but_flag_enable(but, UI_BUT_LIST_ITEM); @@ -2831,7 +2885,7 @@ void ui_item_paneltype_func(bContext *C, uiLayout *layout, void *arg_pt) PanelType *pt = (PanelType *)arg_pt; UI_paneltype_draw(C, pt, layout); - /* panels are created flipped (from event handling pov) */ + /* Panels are created flipped (from event handling POV). */ layout->root->block->flag ^= UI_BLOCK_IS_FLIP; } @@ -2857,23 +2911,23 @@ static uiBut *ui_item_menu(uiLayout *layout, icon = ICON_BLANK1; } - int w = ui_text_icon_width(layout, name, icon, 1); - const int h = UI_UNIT_Y; - + struct uiTextIconPadFactor pad_factor = ui_text_pad_compact; if (layout->root->type == UI_LAYOUT_HEADER) { /* Ugly! */ if (icon == ICON_NONE && force_menu) { /* pass */ } else if (force_menu) { - w += 0.6f * UI_UNIT_X; + pad_factor.text = 1.85; + pad_factor.icon_only = 0.6f; } else { - if (name[0]) { - w -= UI_UNIT_X / 2; - } + pad_factor.text = 0.75f; } } + const int w = ui_text_icon_width_ex(layout, name, icon, &pad_factor); + const int h = UI_UNIT_Y; + if (heading_layout) { ui_layout_heading_label_add(layout, heading_layout, true, true); } @@ -2911,6 +2965,12 @@ static uiBut *ui_item_menu(uiLayout *layout, void uiItemM_ptr(uiLayout *layout, MenuType *mt, const char *name, int icon) { + uiBlock *block = layout->root->block; + bContext *C = block->evil_C; + if (WM_menutype_poll(C, mt) == false) { + return; + } + if (!name) { name = CTX_IFACE_(mt->translation_context, mt->label); } @@ -2949,6 +3009,9 @@ void uiItemMContents(uiLayout *layout, const char *menuname) uiBlock *block = layout->root->block; bContext *C = block->evil_C; + if (WM_menutype_poll(C, mt) == false) { + return; + } UI_menutype_draw(C, mt, layout); } @@ -3124,8 +3187,7 @@ static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon) icon = ICON_BLANK1; } - const int w = ui_text_icon_width(layout, name, icon, 0); - + const int w = ui_text_icon_width_ex(layout, name, icon, &ui_text_pad_none); uiBut *but; if (icon && name[0]) { but = uiDefIconTextBut( @@ -3147,7 +3209,7 @@ static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon) but->drawflag |= UI_BUT_TEXT_RIGHT; } - /* Mark as a label inside a listbox. */ + /* Mark as a label inside a list-box. */ if (block->flag & UI_BLOCK_LIST_ITEM) { but->flag |= UI_BUT_LIST_ITEM; } @@ -3371,7 +3433,7 @@ void uiItemMenuFN(uiLayout *layout, const char *name, int icon, uiMenuCreateFunc } typedef struct MenuItemLevel { - int opcontext; + wmOperatorCallContext opcontext; /* don't use pointers to the strings because python can dynamically * allocate strings and free before the menu draws, see T27304. */ char opname[OP_MAX_TYPENAME]; @@ -3381,10 +3443,13 @@ typedef struct MenuItemLevel { static void menu_item_enum_opname_menu(bContext *UNUSED(C), uiLayout *layout, void *arg) { - MenuItemLevel *lvl = (MenuItemLevel *)(((uiBut *)arg)->func_argN); + uiBut *but = arg; + MenuItemLevel *lvl = but->func_argN; + /* Use the operator properties from the button owning the menu. */ + IDProperty *op_props = but->opptr ? but->opptr->data : NULL; uiLayoutSetOperatorContext(layout, lvl->opcontext); - uiItemsEnumO(layout, lvl->opname, lvl->propname); + uiItemsFullEnumO(layout, lvl->opname, lvl->propname, op_props, lvl->opcontext, 0); layout->root->block->flag |= UI_BLOCK_IS_FLIP; @@ -3392,12 +3457,13 @@ static void menu_item_enum_opname_menu(bContext *UNUSED(C), uiLayout *layout, vo UI_block_direction_set(layout->root->block, UI_DIR_DOWN); } -void uiItemMenuEnumO_ptr(uiLayout *layout, - bContext *C, - wmOperatorType *ot, - const char *propname, - const char *name, - int icon) +void uiItemMenuEnumFullO_ptr(uiLayout *layout, + bContext *C, + wmOperatorType *ot, + const char *propname, + const char *name, + int icon, + PointerRNA *r_opptr) { /* Caller must check */ BLI_assert(ot->srna != NULL); @@ -3416,6 +3482,15 @@ void uiItemMenuEnumO_ptr(uiLayout *layout, lvl->opcontext = layout->root->opcontext; uiBut *but = ui_item_menu(layout, name, icon, menu_item_enum_opname_menu, NULL, lvl, NULL, true); + /* Use the menu button as owner for the operator properties, which will then be passed to the + * individual menu items. */ + if (r_opptr) { + but->opptr = MEM_callocN(sizeof(PointerRNA), "uiButOpPtr"); + WM_operator_properties_create_ptr(but->opptr, ot); + BLI_assert(but->opptr->data == NULL); + WM_operator_properties_alloc(&but->opptr, (IDProperty **)&but->opptr->data, ot->idname); + *r_opptr = *but->opptr; + } /* add hotkey here, lower UI code can't detect it */ if ((layout->root->block->flag & UI_BLOCK_LOOP) && (ot->prop && ot->invoke)) { @@ -3427,12 +3502,13 @@ void uiItemMenuEnumO_ptr(uiLayout *layout, } } -void uiItemMenuEnumO(uiLayout *layout, - bContext *C, - const char *opname, - const char *propname, - const char *name, - int icon) +void uiItemMenuEnumFullO(uiLayout *layout, + bContext *C, + const char *opname, + const char *propname, + const char *name, + int icon, + PointerRNA *r_opptr) { wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ @@ -3444,7 +3520,17 @@ void uiItemMenuEnumO(uiLayout *layout, return; } - uiItemMenuEnumO_ptr(layout, C, ot, propname, name, icon); + uiItemMenuEnumFullO_ptr(layout, C, ot, propname, name, icon, r_opptr); +} + +void uiItemMenuEnumO(uiLayout *layout, + bContext *C, + const char *opname, + const char *propname, + const char *name, + int icon) +{ + uiItemMenuEnumFullO(layout, C, opname, propname, name, icon, NULL); } static void menu_item_enum_rna_menu(bContext *UNUSED(C), uiLayout *layout, void *arg) @@ -4615,7 +4701,7 @@ static void ui_litem_init_from_parent(uiLayout *litem, uiLayout *layout, int ali { litem->root = layout->root; litem->align = align; - /* Children of gridflow layout shall never have "ideal big size" returned as estimated size. */ + /* Children of grid-flow layout shall never have "ideal big size" returned as estimated size. */ litem->variable_size = layout->variable_size || layout->item.type == ITEM_LAYOUT_GRID_FLOW; litem->active = true; litem->enabled = true; @@ -5523,28 +5609,52 @@ void ui_layout_add_but(uiLayout *layout, uiBut *but) ui_button_group_add_but(uiLayoutGetBlock(layout), but); } -bool ui_layout_replace_but_ptr(uiLayout *layout, const void *old_but_ptr, uiBut *new_but) +static uiButtonItem *ui_layout_find_button_item(const uiLayout *layout, const uiBut *but) { - ListBase *child_list = layout->child_items_layout ? &layout->child_items_layout->items : - &layout->items; + const ListBase *child_list = layout->child_items_layout ? &layout->child_items_layout->items : + &layout->items; LISTBASE_FOREACH (uiItem *, item, child_list) { if (item->type == ITEM_BUTTON) { uiButtonItem *bitem = (uiButtonItem *)item; - if (bitem->but == old_but_ptr) { - bitem->but = new_but; - return true; + if (bitem->but == but) { + return bitem; } } else { - if (ui_layout_replace_but_ptr((uiLayout *)item, old_but_ptr, new_but)) { - return true; + uiButtonItem *nested_item = ui_layout_find_button_item((uiLayout *)item, but); + if (nested_item) { + return nested_item; } } } - return false; + return NULL; +} + +void ui_layout_remove_but(uiLayout *layout, const uiBut *but) +{ + uiButtonItem *bitem = ui_layout_find_button_item(layout, but); + if (!bitem) { + return; + } + + BLI_freelinkN(&layout->items, bitem); +} + +/** + * \return true if the button was successfully replaced. + */ +bool ui_layout_replace_but_ptr(uiLayout *layout, const void *old_but_ptr, uiBut *new_but) +{ + uiButtonItem *bitem = ui_layout_find_button_item(layout, old_but_ptr); + if (!bitem) { + return false; + } + + bitem->but = new_but; + return true; } void uiLayoutSetFixedSize(uiLayout *layout, bool fixed_size) @@ -5562,7 +5672,7 @@ bool uiLayoutGetFixedSize(uiLayout *layout) return (layout->item.flag & UI_ITEM_FIXED_SIZE) != 0; } -void uiLayoutSetOperatorContext(uiLayout *layout, int opcontext) +void uiLayoutSetOperatorContext(uiLayout *layout, wmOperatorCallContext opcontext) { layout->root->opcontext = opcontext; } @@ -5932,8 +6042,8 @@ uiLayout *uiItemsAlertBox(uiBlock *block, const int size, const eAlertIcon icon) const int text_points_max = MAX2(style->widget.points, style->widgetlabel.points); const int dialog_width = icon_size + (text_points_max * size * U.dpi_fac); /* By default, the space between icon and text/buttons will be equal to the 'columnspace', - this extra padding will add some space by increasing the left column width, - making the icon placement more symmetrical, between the block edge and the text. */ + * this extra padding will add some space by increasing the left column width, + * making the icon placement more symmetrical, between the block edge and the text. */ const float icon_padding = 5.0f * U.dpi_fac; /* Calculate the factor of the fixed icon column depending on the block width. */ const float split_factor = ((float)icon_size + icon_padding) / diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index ce5c17a0718..c962a1107ae 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -75,6 +75,32 @@ #include "ED_text.h" /* -------------------------------------------------------------------- */ +/** \name Immediate redraw helper + * + * Generally handlers shouldn't do any redrawing, that includes the layout/button definitions. That + * violates the Model-View-Controller pattern. + * + * But there are some operators which really need to re-run the layout definitions for various + * reasons. For example, "Edit Source" does it to find out which exact Python code added a button. + * Other operators may need to access buttons that aren't currently visible. In Blender's UI code + * design that typically means just not adding the button in the first place, for a particular + * redraw. So the operator needs to change context and re-create the layout, so the button becomes + * available to act on. + * + * \{ */ + +static void ui_region_redraw_immediately(bContext *C, ARegion *region) +{ + ED_region_do_layout(C, region); + WM_draw_region_viewport_bind(region); + ED_region_do_draw(C, region); + WM_draw_region_viewport_unbind(region); + region->do_draw = false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Copy Data Path Operator * \{ */ @@ -649,7 +675,7 @@ static int override_remove_button_exec(bContext *C, wmOperator *op) PropertyRNA *src_prop; RNA_id_pointer_create(id->override_library->reference, &id_refptr); if (!RNA_path_resolve_property(&id_refptr, oprop->rna_path, &src, &src_prop)) { - BLI_assert(0 && "Failed to create matching source (linked data) RNA pointer"); + BLI_assert_msg(0, "Failed to create matching source (linked data) RNA pointer"); } } @@ -823,6 +849,9 @@ bool UI_context_copy_to_selected_list(bContext *C, else if (RNA_struct_is_a(ptr->type, &RNA_NlaStrip)) { *r_lb = CTX_data_collection_get(C, "selected_nla_strips"); } + else if (RNA_struct_is_a(ptr->type, &RNA_MovieTrackingTrack)) { + *r_lb = CTX_data_collection_get(C, "selected_movieclip_tracks"); + } else if (RNA_struct_is_a(ptr->type, &RNA_Constraint) && (path_from_bone = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_PoseBone)) != NULL) { @@ -972,55 +1001,69 @@ static bool copy_to_selected_button(bContext *C, bool all, bool poll) UI_context_active_but_prop_get(C, &ptr, &prop, &index); /* if there is a valid property that is editable... */ - if (ptr.data && prop) { - char *path = NULL; - bool use_path_from_id; - ListBase lb = {NULL}; - - if (UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path) && - !BLI_listbase_is_empty(&lb)) { - LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { - if (link->ptr.data != ptr.data) { - if (use_path_from_id) { - /* Path relative to ID. */ - lprop = NULL; - RNA_id_pointer_create(link->ptr.owner_id, &idptr); - RNA_path_resolve_property(&idptr, path, &lptr, &lprop); - } - else if (path) { - /* Path relative to elements from list. */ - lprop = NULL; - RNA_path_resolve_property(&link->ptr, path, &lptr, &lprop); - } - else { - lptr = link->ptr; - lprop = prop; - } + if (ptr.data == NULL || prop == NULL) { + return false; + } - if (lptr.data == ptr.data) { - /* lptr might not be the same as link->ptr! */ - continue; - } + char *path = NULL; + bool use_path_from_id; + ListBase lb = {NULL}; - if (lprop == prop) { - if (RNA_property_editable(&lptr, lprop)) { - if (poll) { - success = true; - break; - } - if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) { - RNA_property_update(C, &lptr, prop); - success = true; - } - } - } - } - } - } + if (!UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path)) { + return false; + } + if (BLI_listbase_is_empty(&lb)) { MEM_SAFE_FREE(path); - BLI_freelistN(&lb); + return false; } + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + if (link->ptr.data == ptr.data) { + continue; + } + + if (use_path_from_id) { + /* Path relative to ID. */ + lprop = NULL; + RNA_id_pointer_create(link->ptr.owner_id, &idptr); + RNA_path_resolve_property(&idptr, path, &lptr, &lprop); + } + else if (path) { + /* Path relative to elements from list. */ + lprop = NULL; + RNA_path_resolve_property(&link->ptr, path, &lptr, &lprop); + } + else { + lptr = link->ptr; + lprop = prop; + } + + if (lptr.data == ptr.data) { + /* lptr might not be the same as link->ptr! */ + continue; + } + + if (lprop != prop) { + continue; + } + + if (!RNA_property_editable(&lptr, lprop)) { + continue; + } + + if (poll) { + success = true; + break; + } + if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) { + RNA_property_update(C, &lptr, prop); + success = true; + } + } + + MEM_SAFE_FREE(path); + BLI_freelistN(&lb); + return success; } @@ -1215,7 +1258,7 @@ static void UI_OT_jump_to_target_button(wmOperatorType *ot) /* ------------------------------------------------------------------------- */ /* EditSource Utility funcs and operator, - * note, this includes utility functions and button matching checks */ + * NOTE: this includes utility functions and button matching checks. */ typedef struct uiEditSourceStore { uiBut but_orig; @@ -1340,17 +1383,8 @@ static int editsource_text_edit(bContext *C, txt_move_toline(text, line - 1, false); /* naughty!, find text area to set, not good behavior - * but since this is a dev tool lets allow it - campbell */ - ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TEXT, 0); - if (area) { - SpaceText *st = area->spacedata.first; - ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); - st->text = text; - if (region) { - ED_text_scroll_to_cursor(st, region, true); - } - } - else { + * but since this is a developer tool lets allow it - campbell */ + if (!ED_text_activate_in_screen(C, text)) { BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2); } @@ -1379,11 +1413,7 @@ static int editsource_exec(bContext *C, wmOperator *op) ui_editsource_active_but_set(but); /* redraw and get active button python info */ - ED_region_do_layout(C, region); - WM_draw_region_viewport_bind(region); - ED_region_do_draw(C, region); - WM_draw_region_viewport_unbind(region); - region->do_draw = false; + ui_region_redraw_immediately(C, region); for (BLI_ghashIterator_init(&ghi, ui_editsource_info->hash); BLI_ghashIterator_done(&ghi) == false; @@ -1535,7 +1565,7 @@ static int edittranslation_exec(bContext *C, wmOperator *op) } /* Try to find a valid po file for current language... */ edittranslation_find_po_file(root, uilng, popath, FILE_MAX); - /* printf("po path: %s\n", popath); */ + // printf("po path: %s\n", popath); if (popath[0] == '\0') { BKE_reportf( op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root); @@ -1656,7 +1686,8 @@ static int ui_button_press_invoke(bContext *C, wmOperator *op, const wmEvent *ev bScreen *screen = CTX_wm_screen(C); const bool skip_depressed = RNA_boolean_get(op->ptr, "skip_depressed"); ARegion *region_prev = CTX_wm_region(C); - ARegion *region = screen ? BKE_screen_find_region_xy(screen, RGN_TYPE_ANY, event->x, event->y) : + ARegion *region = screen ? BKE_screen_find_region_xy( + screen, RGN_TYPE_ANY, event->xy[0], event->xy[1]) : NULL; if (region == NULL) { @@ -1737,10 +1768,7 @@ static void UI_OT_button_string_clear(wmOperatorType *ot) /** \name Drop Color Operator * \{ */ -bool UI_drop_color_poll(struct bContext *C, - wmDrag *drag, - const wmEvent *UNUSED(event), - const char **UNUSED(r_tooltip)) +bool UI_drop_color_poll(struct bContext *C, wmDrag *drag, const wmEvent *UNUSED(event)) { /* should only return true for regions that include buttons, for now * return true always */ @@ -1836,6 +1864,184 @@ static void UI_OT_drop_color(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Drop Name Operator + * \{ */ + +static int drop_name_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + uiBut *but = UI_but_active_drop_name_button(C); + char *str = RNA_string_get_alloc(op->ptr, "string", NULL, 0, NULL); + + if (str) { + ui_but_set_string_interactive(C, but, str); + MEM_freeN(str); + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_name(wmOperatorType *ot) +{ + ot->name = "Drop Name"; + ot->idname = "UI_OT_drop_name"; + ot->description = "Drop name to button"; + + ot->poll = ED_operator_regionactive; + ot->invoke = drop_name_invoke; + ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; + + RNA_def_string( + ot->srna, "string", NULL, 0, "String", "The string value to drop into the button"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI List Search Operator + * \{ */ + +static bool ui_list_focused_poll(bContext *C) +{ + const ARegion *region = CTX_wm_region(C); + const wmWindow *win = CTX_wm_window(C); + const uiList *list = UI_list_find_mouse_over(region, win->eventstate); + + return list != NULL; +} + +/** + * Ensure the filter options are set to be visible in the UI list. + * \return if the visibility changed, requiring a redraw. + */ +static bool ui_list_unhide_filter_options(uiList *list) +{ + if (list->filter_flag & UILST_FLT_SHOW) { + /* Nothing to be done. */ + return false; + } + + list->filter_flag |= UILST_FLT_SHOW; + return true; +} + +static int ui_list_start_filter_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + uiList *list = UI_list_find_mouse_over(region, event); + /* Poll should check. */ + BLI_assert(list != NULL); + + if (ui_list_unhide_filter_options(list)) { + ui_region_redraw_immediately(C, region); + } + + if (!UI_textbutton_activate_rna(C, region, list, "filter_name")) { + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_list_start_filter(wmOperatorType *ot) +{ + ot->name = "List Filter"; + ot->idname = "UI_OT_list_start_filter"; + ot->description = "Start entering filter text for the list in focus"; + + ot->invoke = ui_list_start_filter_invoke; + ot->poll = ui_list_focused_poll; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI Tree-View Drop Operator + * \{ */ + +static bool ui_tree_view_drop_poll(bContext *C) +{ + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( + region, win->eventstate->xy); + + return hovered_tree_item != NULL; +} + +static int ui_tree_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + if (event->custom != EVT_DATA_DRAGDROP) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + const ARegion *region = CTX_wm_region(C); + uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, event->xy); + + if (!UI_tree_view_item_drop_handle(hovered_tree_item, event->customdata)) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_tree_view_drop(wmOperatorType *ot) +{ + ot->name = "Tree View drop"; + ot->idname = "UI_OT_tree_view_drop"; + ot->description = "Drag and drop items onto a tree item"; + + ot->invoke = ui_tree_view_drop_invoke; + ot->poll = ui_tree_view_drop_poll; + + ot->flag = OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI Tree-View Item Rename Operator + * + * General purpose renaming operator for tree-views. Thanks to this, to add a rename button to + * context menus for example, tree-view API users don't have to implement own renaming operators + * with the same logic as they already have for their #ui::AbstractTreeViewItem::rename() override. + * + * \{ */ + +static bool ui_tree_view_item_rename_poll(bContext *C) +{ + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region); + return active_item != NULL && UI_tree_view_item_can_rename(active_item); +} + +static int ui_tree_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ARegion *region = CTX_wm_region(C); + uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region); + + UI_tree_view_item_begin_rename(active_item); + ED_region_tag_redraw(region); + + return OPERATOR_FINISHED; +} + +static void UI_OT_tree_view_item_rename(wmOperatorType *ot) +{ + ot->name = "Rename Tree-View Item"; + ot->idname = "UI_OT_tree_view_item_rename"; + ot->description = "Rename the active item in the tree"; + + ot->exec = ui_tree_view_item_rename_exec; + ot->poll = ui_tree_view_item_rename_poll; + /* Could get a custom tooltip via the `get_description()` callback and another overridable + * function of the tree-view. */ + + ot->flag = OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Operator & Keymap Registration * \{ */ @@ -1852,6 +2058,7 @@ void ED_operatortypes_ui(void) WM_operatortype_append(UI_OT_copy_to_selected_button); WM_operatortype_append(UI_OT_jump_to_target_button); WM_operatortype_append(UI_OT_drop_color); + WM_operatortype_append(UI_OT_drop_name); #ifdef WITH_PYTHON WM_operatortype_append(UI_OT_editsource); WM_operatortype_append(UI_OT_edittranslation_init); @@ -1860,6 +2067,11 @@ void ED_operatortypes_ui(void) WM_operatortype_append(UI_OT_button_execute); WM_operatortype_append(UI_OT_button_string_clear); + WM_operatortype_append(UI_OT_list_start_filter); + + WM_operatortype_append(UI_OT_tree_view_drop); + WM_operatortype_append(UI_OT_tree_view_item_rename); + /* external */ WM_operatortype_append(UI_OT_eyedropper_color); WM_operatortype_append(UI_OT_eyedropper_colorramp); diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c index dc0650af7a7..072362492d8 100644 --- a/source/blender/editors/interface/interface_panel.c +++ b/source/blender/editors/interface/interface_panel.c @@ -258,7 +258,7 @@ static Panel *panel_add_instanced(ARegion *region, /* Make sure the panel is added to the end of the display-order as well. This is needed for * loading existing files. * - * Note: We could use special behavior to place it after the panel that starts the list of + * NOTE: We could use special behavior to place it after the panel that starts the list of * instanced panels, but that would add complexity that isn't needed for now. */ int max_sortorder = 0; LISTBASE_FOREACH (Panel *, existing_panel, panels) { @@ -1112,23 +1112,14 @@ static void panel_draw_highlight_border(const Panel *panel, const rcti *rect, const rcti *header_rect) { - const bool draw_box_style = panel->type->flag & PANEL_TYPE_DRAW_BOX; const bool is_subpanel = panel->type->parent != NULL; if (is_subpanel) { return; } - float radius; - if (draw_box_style) { - /* Use the theme for box widgets. */ - const uiWidgetColors *box_wcol = &UI_GetTheme()->tui.wcol_box; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - radius = box_wcol->roundness * U.widget_unit; - } - else { - UI_draw_roundbox_corner_set(UI_CNR_NONE); - radius = 0.0f; - } + const bTheme *btheme = UI_GetTheme(); + const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f; + UI_draw_roundbox_corner_set(UI_CNR_ALL); float color[4]; UI_GetThemeColor4fv(TH_SELECT_ACTIVE, color); @@ -1172,18 +1163,17 @@ static void panel_draw_aligned_widgets(const uiStyle *style, /* Draw collapse icon. */ { - rctf collapse_rect = { - .xmin = widget_rect.xmin, - .xmax = widget_rect.xmin + header_height, - .ymin = widget_rect.ymin, - .ymax = widget_rect.ymax, - }; - BLI_rctf_scale(&collapse_rect, 0.25f); - - float triangle_color[4]; - rgba_uchar_to_float(triangle_color, title_color); - - ui_draw_anti_tria_rect(&collapse_rect, UI_panel_is_closed(panel) ? 'h' : 'v', triangle_color); + const float size_y = BLI_rcti_size_y(&widget_rect); + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw_ex(widget_rect.xmin + size_y * 0.2f, + widget_rect.ymin + size_y * 0.2f, + UI_panel_is_closed(panel) ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT, + aspect * U.inv_dpi_fac, + 0.7f, + 0.0f, + title_color, + false); + GPU_blend(GPU_BLEND_NONE); } /* Draw text label. */ @@ -1243,7 +1233,6 @@ static void panel_draw_aligned_backdrop(const Panel *panel, const rcti *rect, const rcti *header_rect) { - const bool draw_box_style = panel->type->flag & PANEL_TYPE_DRAW_BOX; const bool is_subpanel = panel->type->parent != NULL; const bool is_open = !UI_panel_is_closed(panel); @@ -1251,90 +1240,52 @@ static void panel_draw_aligned_backdrop(const Panel *panel, return; } - const uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - - /* Draw with an opaque box backdrop for box style panels. */ - if (draw_box_style) { - /* Use the theme for box widgets. */ - const uiWidgetColors *box_wcol = &UI_GetTheme()->tui.wcol_box; - - if (is_subpanel) { - /* Use rounded bottom corners for the last subpanel. */ - if (panel->next == NULL) { - UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT); - float color[4]; - UI_GetThemeColor4fv(TH_PANEL_SUB_BACK, color); - /* Change the width a little bit to line up with sides. */ - UI_draw_roundbox_aa( - &(const rctf){ - .xmin = rect->xmin + U.pixelsize, - .xmax = rect->xmax - U.pixelsize, - .ymin = rect->ymin + U.pixelsize, - .ymax = rect->ymax, - }, - true, - box_wcol->roundness * U.widget_unit, - color); - } - else { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformThemeColor(TH_PANEL_SUB_BACK); - immRectf(pos, rect->xmin + U.pixelsize, rect->ymin, rect->xmax - U.pixelsize, rect->ymax); - immUnbindProgram(); - } - } - else { - /* Expand the top a tiny bit to give header buttons equal size above and below. */ - rcti box_rect = { - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = is_open ? rect->ymin : header_rect->ymin, - .ymax = header_rect->ymax + U.pixelsize, - }; - ui_draw_box_opaque(&box_rect, UI_CNR_ALL); - - /* Mimic the border between aligned box widgets for the bottom of the header. */ - if (is_open) { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - GPU_blend(GPU_BLEND_ALPHA); - - /* Top line. */ - immUniformColor4ubv(box_wcol->outline); - immRectf(pos, rect->xmin, header_rect->ymin - U.pixelsize, rect->xmax, header_rect->ymin); - - /* Bottom "shadow" line. */ - immUniformThemeColor(TH_WIDGET_EMBOSS); - immRectf(pos, - rect->xmin, - header_rect->ymin - U.pixelsize, - rect->xmax, - header_rect->ymin - U.pixelsize - 1); - - GPU_blend(GPU_BLEND_NONE); - immUnbindProgram(); - } - } - } - else { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - GPU_blend(GPU_BLEND_ALPHA); - - /* Panel backdrop. */ - if (is_open || panel->type->flag & PANEL_TYPE_NO_HEADER) { - immUniformThemeColor(is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK); - immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); - } + const bTheme *btheme = UI_GetTheme(); + const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f; - /* Panel header backdrops for non sub-panels. */ - if (!is_subpanel) { - immUniformThemeColor(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER); - immRectf(pos, rect->xmin, header_rect->ymin, rect->xmax, header_rect->ymax); - } - - GPU_blend(GPU_BLEND_NONE); - immUnbindProgram(); - } + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + GPU_blend(GPU_BLEND_ALPHA); + + /* Panel backdrop. */ + if (is_open || panel->type->flag & PANEL_TYPE_NO_HEADER) { + float panel_backcolor[4]; + UI_draw_roundbox_corner_set(is_open ? UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT : UI_CNR_ALL); + UI_GetThemeColor4fv((is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK), panel_backcolor); + + UI_draw_roundbox_4fv( + &(const rctf){ + .xmin = rect->xmin, + .xmax = rect->xmax, + .ymin = rect->ymin, + .ymax = rect->ymax, + }, + true, + radius, + panel_backcolor); + } + + /* Panel header backdrops for non sub-panels. */ + if (!is_subpanel) { + float panel_headercolor[4]; + UI_GetThemeColor4fv(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER, + panel_headercolor); + UI_draw_roundbox_corner_set(is_open ? UI_CNR_TOP_RIGHT | UI_CNR_TOP_LEFT : UI_CNR_ALL); + + /* Change the width a little bit to line up with the sides. */ + UI_draw_roundbox_4fv( + &(const rctf){ + .xmin = rect->xmin, + .xmax = rect->xmax, + .ymin = header_rect->ymin, + .ymax = header_rect->ymax, + }, + true, + radius, + panel_headercolor); + } + + GPU_blend(GPU_BLEND_NONE); + immUnbindProgram(); } /** @@ -1447,10 +1398,6 @@ void UI_panel_category_draw_all(ARegion *region, const char *category_id_active) is_alpha = (region->overlap && (theme_col_back[3] != 255)); - if (fstyle->kerning == 1) { - BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - BLF_enable(fontid, BLF_ROTATION); BLF_rotation(fontid, M_PI_2); // UI_fontstyle_set(&style->widget); @@ -1603,7 +1550,7 @@ void UI_panel_category_draw_all(ARegion *region, const char *category_id_active) } BLF_position(fontid, rct->xmax - text_v_ofs, rct->ymin + tab_v_pad_text, 0.0f); - BLF_color3ubv(fontid, theme_col_text); + BLF_color3ubv(fontid, is_active ? theme_col_text_hi : theme_col_text); BLF_draw(fontid, category_id_draw, category_draw_len); GPU_blend(GPU_BLEND_NONE); @@ -1620,10 +1567,6 @@ void UI_panel_category_draw_all(ARegion *region, const char *category_id_active) GPU_line_smooth(false); BLF_disable(fontid, BLF_ROTATION); - - if (fstyle->kerning == 1) { - BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } } #undef TABS_PADDING_BETWEEN_FACTOR @@ -1797,9 +1740,9 @@ static bool uiAlignPanelStep(ARegion *region, const float factor, const bool dra const int region_offset_x = panel_region_offset_x_get(region); for (int i = 0; i < active_panels_len; i++) { PanelSort *ps = &panel_sort[i]; - const bool use_box = ps->panel->type->flag & PANEL_TYPE_DRAW_BOX; + const bool no_header = ps->panel->type->flag & PANEL_TYPE_NO_HEADER; ps->panel->runtime.region_ofsx = region_offset_x; - ps->new_offset_x = region_offset_x + ((use_box) ? UI_PANEL_BOX_STYLE_MARGIN : 0); + ps->new_offset_x = region_offset_x + (no_header ? 0 : UI_PANEL_MARGIN_X); } /* Y offset. */ @@ -1807,10 +1750,7 @@ static bool uiAlignPanelStep(ARegion *region, const float factor, const bool dra PanelSort *ps = &panel_sort[i]; y -= get_panel_real_size_y(ps->panel); - const bool use_box = ps->panel->type->flag & PANEL_TYPE_DRAW_BOX; - if (use_box) { - y -= UI_PANEL_BOX_STYLE_MARGIN; - } + y -= UI_PANEL_MARGIN_Y; ps->new_offset_y = y; /* The header still draws offset by the size of closed panels, so apply the offset here. */ if (UI_panel_is_closed(ps->panel)) { @@ -1892,7 +1832,7 @@ static void ui_do_animate(bContext *C, Panel *panel) } else { if (UI_panel_is_dragging(panel)) { - /* Note: doing this in #panel_activate_state would require + /* NOTE: doing this in #panel_activate_state would require * removing `const` for context in many other places. */ reorder_instanced_panel_list(C, region, panel); } @@ -1986,7 +1926,7 @@ static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel) ARegion *region = CTX_wm_region(C); /* Keep the drag position in the region with a small pad to keep the panel visible. */ - const int y = clamp_i(event->y, region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD); + const int y = clamp_i(event->xy[1], region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD); float dy = (float)(y - data->starty); @@ -2101,7 +2041,7 @@ static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, voi switch (event->type) { case MOUSEMOVE: - ui_panel_drag_collapse(C, dragcol_data, &event->x); + ui_panel_drag_collapse(C, dragcol_data, event->xy); retval = WM_UI_HANDLER_BREAK; break; @@ -2130,7 +2070,7 @@ static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was uiPanelDragCollapseHandle *dragcol_data = MEM_mallocN(sizeof(*dragcol_data), __func__); dragcol_data->was_first_open = was_open; - copy_v2_v2_int(dragcol_data->xy_init, &event->x); + copy_v2_v2_int(dragcol_data->xy_init, event->xy); WM_event_add_ui_handler(C, &win->modalhandlers, @@ -2160,7 +2100,7 @@ static void ui_handle_panel_header(const bContext *C, BLI_assert(!(panel->type->flag & PANEL_TYPE_NO_HEADER)); const bool is_subpanel = (panel->type->parent != NULL); - const bool use_pin = UI_panel_category_is_visible(region) && !is_subpanel; + const bool use_pin = UI_panel_category_is_visible(region) && UI_panel_can_be_pinned(panel); const bool show_pin = use_pin && (panel->flag & PNL_PIN); const bool show_drag = !is_subpanel; @@ -2423,7 +2363,7 @@ int ui_handler_panel_region(bContext *C, } /* Scroll-bars can overlap panels now, they have handling priority. */ - if (UI_view2d_mouse_in_scrollers(region, ®ion->v2d, event->x, event->y)) { + if (UI_view2d_mouse_in_scrollers(region, ®ion->v2d, event->xy)) { return WM_UI_HANDLER_CONTINUE; } @@ -2466,8 +2406,8 @@ int ui_handler_panel_region(bContext *C, continue; } - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(region, block, &mx, &my); const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); @@ -2545,8 +2485,8 @@ PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wm continue; } - int mx = event->x; - int my = event->y; + int mx = event->xy[0]; + int my = event->xy[1]; ui_window_to_block(region, block, &mx, &my); const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) { @@ -2557,13 +2497,18 @@ PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wm return NULL; } +bool UI_panel_can_be_pinned(const Panel *panel) +{ + return (panel->type->parent == NULL) && !(panel->type->flag & PANEL_TYPE_INSTANCED); +} + /** \} */ /* -------------------------------------------------------------------- */ /** \name Window Level Modal Panel Interaction * \{ */ -/* Note, this is modal handler and should not swallow events for animation. */ +/* NOTE: this is modal handler and should not swallow events for animation. */ static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata) { Panel *panel = userdata; @@ -2619,8 +2564,8 @@ static void panel_handle_data_ensure(const bContext *C, data->animtimer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, ANIMATION_INTERVAL); data->state = state; - data->startx = win->eventstate->x; - data->starty = win->eventstate->y; + data->startx = win->eventstate->xy[0]; + data->starty = win->eventstate->xy[1]; data->startofsx = panel->ofsx; data->startofsy = panel->ofsy; data->start_cur_xmin = region->v2d.cur.xmin; @@ -2653,7 +2598,7 @@ static void panel_activate_state(const bContext *C, Panel *panel, const uiHandle /* Initiate edge panning during drags for scrolling beyond the initial region view. */ wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true); - ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT, true); + ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT); } else if (state == PANEL_STATE_ANIMATION) { panel_set_flag_recursive(panel, PNL_SELECT, false); diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c index 7d561aa1c71..bdf93d7c82e 100644 --- a/source/blender/editors/interface/interface_query.c +++ b/source/blender/editors/interface/interface_query.c @@ -23,6 +23,7 @@ #include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_rect.h" +#include "BLI_string.h" #include "BLI_utildefines.h" #include "DNA_screen_types.h" @@ -68,7 +69,8 @@ bool ui_but_is_toggle(const uiBut *but) UI_BTYPE_CHECKBOX, UI_BTYPE_CHECKBOX_N, UI_BTYPE_ROW, - UI_BTYPE_DATASETROW); + UI_BTYPE_DATASETROW, + UI_BTYPE_TREEROW); } /** @@ -78,7 +80,7 @@ bool ui_but_is_toggle(const uiBut *but) */ bool ui_but_is_interactive(const uiBut *but, const bool labeledit) { - /* note, UI_BTYPE_LABEL is included for highlights, this allows drags */ + /* NOTE: #UI_BTYPE_LABEL is included for highlights, this allows drags. */ if ((but->type == UI_BTYPE_LABEL) && but->dragpoin == NULL) { return false; } @@ -220,14 +222,14 @@ bool ui_but_contains_rect(const uiBut *but, const rctf *rect) return BLI_rctf_isect(&but->rect, rect, NULL); } -bool ui_but_contains_point_px(const uiBut *but, const ARegion *region, int x, int y) +bool ui_but_contains_point_px(const uiBut *but, const ARegion *region, const int xy[2]) { uiBlock *block = but->block; - if (!ui_region_contains_point_px(region, x, y)) { + if (!ui_region_contains_point_px(region, xy)) { return false; } - float mx = x, my = y; + float mx = xy[0], my = xy[1]; ui_window_to_block_fl(region, block, &mx, &my); if (but->pie_dir != UI_RADIAL_NONE) { @@ -245,7 +247,7 @@ bool ui_but_contains_point_px(const uiBut *but, const ARegion *region, int x, in bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEvent *event) { rcti rect; - int x = event->x, y = event->y; + int x = event->xy[0], y = event->xy[1]; ui_window_to_block(region, but->block, &x, &y); @@ -266,22 +268,42 @@ bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEv return BLI_rcti_isect_pt(&rect, x, y); } +static uiBut *ui_but_find(const ARegion *region, + const uiButFindPollFn find_poll, + const void *find_custom_data) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { + if (find_poll && find_poll(but, find_custom_data) == false) { + continue; + } + return but; + } + } + + return NULL; +} + /* x and y are only used in case event is NULL... */ uiBut *ui_but_find_mouse_over_ex(const ARegion *region, - const int x, - const int y, - const bool labeledit) + const int xy[2], + const bool labeledit, + const uiButFindPollFn find_poll, + const void *find_custom_data) { uiBut *butover = NULL; - if (!ui_region_contains_point_px(region, x, y)) { + if (!ui_region_contains_point_px(region, xy)) { return NULL; } LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float mx = x, my = y; + float mx = xy[0], my = xy[1]; ui_window_to_block_fl(region, block, &mx, &my); LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { + if (find_poll && find_poll(but, find_custom_data) == false) { + continue; + } if (ui_but_is_interactive(but, labeledit)) { if (but->pie_dir != UI_RADIAL_NONE) { if (ui_but_isect_pie_seg(block, but)) { @@ -310,7 +332,7 @@ uiBut *ui_but_find_mouse_over_ex(const ARegion *region, uiBut *ui_but_find_mouse_over(const ARegion *region, const wmEvent *event) { - return ui_but_find_mouse_over_ex(region, event->x, event->y, event->ctrl != 0); + return ui_but_find_mouse_over_ex(region, event->xy, event->ctrl != 0, NULL, NULL); } uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px) @@ -351,13 +373,13 @@ uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px) return butover; } -uiBut *ui_list_find_mouse_over_ex(ARegion *region, int x, int y) +uiBut *ui_list_find_mouse_over_ex(const ARegion *region, const int xy[2]) { - if (!ui_region_contains_point_px(region, x, y)) { + if (!ui_region_contains_point_px(region, xy)) { return NULL; } LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float mx = x, my = y; + float mx = xy[0], my = xy[1]; ui_window_to_block_fl(region, block, &mx, &my); LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { if (but->type == UI_BTYPE_LISTBOX && ui_but_contains_pt(but, mx, my)) { @@ -369,9 +391,100 @@ uiBut *ui_list_find_mouse_over_ex(ARegion *region, int x, int y) return NULL; } -uiBut *ui_list_find_mouse_over(ARegion *region, const wmEvent *event) +uiBut *ui_list_find_mouse_over(const ARegion *region, const wmEvent *event) +{ + if (event == NULL) { + /* If there is no info about the mouse, just act as if there is nothing underneath it. */ + return NULL; + } + return ui_list_find_mouse_over_ex(region, event->xy); +} + +uiList *UI_list_find_mouse_over(const ARegion *region, const wmEvent *event) +{ + uiBut *list_but = ui_list_find_mouse_over(region, event); + if (!list_but) { + return NULL; + } + + return list_but->custom_data; +} + +static bool ui_list_contains_row(const uiBut *listbox_but, const uiBut *listrow_but) +{ + BLI_assert(listbox_but->type == UI_BTYPE_LISTBOX); + BLI_assert(listrow_but->type == UI_BTYPE_LISTROW); + /* The list box and its rows have the same RNA data (active data pointer/prop). */ + return ui_but_rna_equals(listbox_but, listrow_but); +} + +static bool ui_but_is_listbox_with_row(const uiBut *but, const void *customdata) +{ + const uiBut *row_but = customdata; + return (but->type == UI_BTYPE_LISTBOX) && ui_list_contains_row(but, row_but); +} + +uiBut *ui_list_find_from_row(const ARegion *region, const uiBut *row_but) +{ + return ui_but_find(region, ui_but_is_listbox_with_row, row_but); +} + +static bool ui_but_is_listrow(const uiBut *but, const void *UNUSED(customdata)) +{ + return but->type == UI_BTYPE_LISTROW; +} + +uiBut *ui_list_row_find_mouse_over(const ARegion *region, const int xy[2]) +{ + return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_listrow, NULL); +} + +struct ListRowFindIndexData { + int index; + uiBut *listbox; +}; + +static bool ui_but_is_listrow_at_index(const uiBut *but, const void *customdata) +{ + const struct ListRowFindIndexData *find_data = customdata; + + return ui_but_is_listrow(but, NULL) && ui_list_contains_row(find_data->listbox, but) && + (but->hardmax == find_data->index); +} + +uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut *listbox) +{ + BLI_assert(listbox->type == UI_BTYPE_LISTBOX); + struct ListRowFindIndexData data = { + .index = index, + .listbox = listbox, + }; + return ui_but_find(region, ui_but_is_listrow_at_index, &data); +} + +static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata)) +{ + return but->type == UI_BTYPE_TREEROW; +} + +uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int xy[2]) +{ + return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_treerow, NULL); +} + +static bool ui_but_is_active_treerow(const uiBut *but, const void *customdata) +{ + if (!ui_but_is_treerow(but, customdata)) { + return false; + } + + const uiButTreeRow *treerow_but = (const uiButTreeRow *)but; + return UI_tree_view_item_is_active(treerow_but->tree_item); +} + +uiBut *ui_tree_row_find_active(const ARegion *region) { - return ui_list_find_mouse_over_ex(region, event->x, event->y); + return ui_but_find(region, ui_but_is_active_treerow, NULL); } /** \} */ @@ -466,6 +579,12 @@ size_t ui_but_drawstr_len_without_sep_char(const uiBut *but) return strlen(but->drawstr); } +size_t ui_but_drawstr_without_sep_char(const uiBut *but, char *str, size_t str_maxlen) +{ + size_t str_len_clip = ui_but_drawstr_len_without_sep_char(but); + return BLI_strncpy_rlen(str, but->drawstr, min_zz(str_len_clip + 1, str_maxlen)); +} + size_t ui_but_tip_len_only_first_line(const uiBut *but) { if (but->tip == NULL) { @@ -485,6 +604,17 @@ size_t ui_but_tip_len_only_first_line(const uiBut *but) /** \name Block (#uiBlock) State * \{ */ +uiBut *ui_block_active_but_get(const uiBlock *block) +{ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->active) { + return but; + } + } + + return NULL; +} + bool ui_block_is_menu(const uiBlock *block) { return (((block->flag & UI_BLOCK_LOOP) != 0) && @@ -551,12 +681,9 @@ bool UI_block_can_add_separator(const uiBlock *block) /** \name Block (#uiBlock) Spatial * \{ */ -uiBlock *ui_block_find_mouse_over_ex(const ARegion *region, - const int x, - const int y, - bool only_clip) +uiBlock *ui_block_find_mouse_over_ex(const ARegion *region, const int xy[2], bool only_clip) { - if (!ui_region_contains_point_px(region, x, y)) { + if (!ui_region_contains_point_px(region, xy)) { return NULL; } LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { @@ -565,7 +692,7 @@ uiBlock *ui_block_find_mouse_over_ex(const ARegion *region, continue; } } - float mx = x, my = y; + float mx = xy[0], my = xy[1]; ui_window_to_block_fl(region, block, &mx, &my); if (BLI_rctf_isect_pt(&block->rect, mx, my)) { return block; @@ -576,7 +703,7 @@ uiBlock *ui_block_find_mouse_over_ex(const ARegion *region, uiBlock *ui_block_find_mouse_over(const ARegion *region, const wmEvent *event, bool only_clip) { - return ui_block_find_mouse_over_ex(region, event->x, event->y, only_clip); + return ui_block_find_mouse_over_ex(region, event->xy, only_clip); } /** \} */ @@ -588,10 +715,9 @@ uiBlock *ui_block_find_mouse_over(const ARegion *region, const wmEvent *event, b uiBut *ui_region_find_active_but(ARegion *region) { LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->active) { - return but; - } + uiBut *but = ui_block_active_but_get(block); + if (but) { + return but; } } @@ -617,11 +743,11 @@ uiBut *ui_region_find_first_but_test_flag(ARegion *region, int flag_include, int /** \name Region (#ARegion) Spatial * \{ */ -bool ui_region_contains_point_px(const ARegion *region, int x, int y) +bool ui_region_contains_point_px(const ARegion *region, const int xy[2]) { rcti winrct; ui_region_winrct_get_no_margin(region, &winrct); - if (!BLI_rcti_isect_pt(&winrct, x, y)) { + if (!BLI_rcti_isect_pt_v(&winrct, xy)) { return false; } @@ -632,11 +758,11 @@ bool ui_region_contains_point_px(const ARegion *region, int x, int y) */ if (region->v2d.mask.xmin != region->v2d.mask.xmax) { const View2D *v2d = ®ion->v2d; - int mx = x, my = y; + int mx = xy[0], my = xy[1]; ui_window_to_region(region, &mx, &my); if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) || - UI_view2d_mouse_in_scrollers(region, ®ion->v2d, x, y)) { + UI_view2d_mouse_in_scrollers(region, ®ion->v2d, xy)) { return false; } } @@ -673,14 +799,14 @@ bool ui_region_contains_rect_px(const ARegion *region, const rcti *rect_px) * \{ */ /** Check if the cursor is over any popups. */ -ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, int x, int y) +ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, const int xy[2]) { LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { rcti winrct; ui_region_winrct_get_no_margin(region, &winrct); - if (BLI_rcti_isect_pt(&winrct, x, y)) { + if (BLI_rcti_isect_pt_v(&winrct, xy)) { return region; } } @@ -689,7 +815,7 @@ ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, int x, int y) ARegion *ui_screen_region_find_mouse_over(bScreen *screen, const wmEvent *event) { - return ui_screen_region_find_mouse_over_ex(screen, event->x, event->y); + return ui_screen_region_find_mouse_over_ex(screen, event->xy); } /** \} */ diff --git a/source/blender/editors/interface/interface_region_color_picker.c b/source/blender/editors/interface/interface_region_color_picker.c index e68705e4321..48952c4f121 100644 --- a/source/blender/editors/interface/interface_region_color_picker.c +++ b/source/blender/editors/interface/interface_region_color_picker.c @@ -624,7 +624,7 @@ static void ui_block_colorpicker(uiBlock *block, bt->custom_data = cpicker; } - /* Note: don't disable UI_BUT_UNDO for RGBA values, since these don't add undo steps. */ + /* NOTE: don't disable UI_BUT_UNDO for RGBA values, since these don't add undo steps. */ /* RGB values */ UI_block_align_begin(block); diff --git a/source/blender/editors/interface/interface_region_menu_pie.c b/source/blender/editors/interface/interface_region_menu_pie.c index 05aa139e055..0ffbdd6911c 100644 --- a/source/blender/editors/interface/interface_region_menu_pie.c +++ b/source/blender/editors/interface/interface_region_menu_pie.c @@ -147,9 +147,9 @@ uiPieMenu *UI_pie_menu_begin(struct bContext *C, const char *title, int icon, co pie->layout = UI_block_layout( pie->block_radial, UI_LAYOUT_VERTICAL, UI_LAYOUT_PIEMENU, 0, 0, 200, 0, 0, style); - /* Note event->x/y is where we started dragging in case of KM_CLICK_DRAG. */ - pie->mx = event->x; - pie->my = event->y; + /* NOTE: #wmEvent.xy is where we started dragging in case of #KM_CLICK_DRAG. */ + pie->mx = event->xy[0]; + pie->my = event->xy[1]; /* create title button */ if (title[0]) { @@ -330,7 +330,7 @@ typedef struct PieMenuLevelData { wmOperatorType *ot; const char *propname; IDProperty *properties; - int context, flag; + wmOperatorCallContext context, flag; } PieMenuLevelData; /** @@ -381,7 +381,7 @@ void ui_pie_menu_level_create(uiBlock *block, IDProperty *properties, const EnumPropertyItem *items, int totitem, - int context, + wmOperatorCallContext context, int flag) { const int totitem_parent = PIE_MAX_ITEMS - 1; diff --git a/source/blender/editors/interface/interface_region_menu_popup.c b/source/blender/editors/interface/interface_region_menu_popup.c index 58a74a3473e..408953f8d0e 100644 --- a/source/blender/editors/interface/interface_region_menu_popup.c +++ b/source/blender/editors/interface/interface_region_menu_popup.c @@ -338,8 +338,8 @@ uiPopupBlockHandle *ui_popup_menu_create( if (!but) { /* no button to start from, means we are a popup */ - pup->mx = window->eventstate->x; - pup->my = window->eventstate->y; + pup->mx = window->eventstate->xy[0]; + pup->my = window->eventstate->xy[1]; pup->popup = true; pup->block->flag |= UI_BLOCK_NO_FLIP; } @@ -403,7 +403,7 @@ uiPopupMenu *UI_popup_menu_begin_ex(bContext *C, pup->layout = UI_block_layout( pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); - /* note, this intentionally differs from the menu & sub-menu default because many operators + /* NOTE: this intentionally differs from the menu & sub-menu default because many operators * use popups like this to select one of their options - * where having invoke doesn't make sense */ uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN); @@ -468,8 +468,8 @@ void UI_popup_menu_end(bContext *C, uiPopupMenu *pup) ARegion *butregion = NULL; pup->popup = true; - pup->mx = window->eventstate->x; - pup->my = window->eventstate->y; + pup->mx = window->eventstate->xy[0]; + pup->my = window->eventstate->xy[1]; if (pup->but) { but = pup->but; @@ -591,7 +591,7 @@ int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports) * \{ */ void UI_popup_block_invoke_ex( - bContext *C, uiBlockCreateFunc func, void *arg, void (*arg_free)(void *arg), bool can_refresh) + bContext *C, uiBlockCreateFunc func, void *arg, uiFreeArgFunc arg_free, bool can_refresh) { wmWindow *window = CTX_wm_window(C); uiPopupBlockHandle *handle; @@ -608,10 +608,7 @@ void UI_popup_block_invoke_ex( WM_event_add_mousemove(window); } -void UI_popup_block_invoke(bContext *C, - uiBlockCreateFunc func, - void *arg, - void (*arg_free)(void *arg)) +void UI_popup_block_invoke(bContext *C, uiBlockCreateFunc func, void *arg, uiFreeArgFunc arg_free) { UI_popup_block_invoke_ex(C, func, arg, arg_free, true); } @@ -643,7 +640,7 @@ void UI_popup_block_ex(bContext *C, } #if 0 /* UNUSED */ -void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext) +void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, wmOperatorCallContext opcontext) { wmWindow *window = CTX_wm_window(C); uiPopupBlockHandle *handle; diff --git a/source/blender/editors/interface/interface_region_popover.c b/source/blender/editors/interface/interface_region_popover.c index a9f72233cb1..5e7e0bfe9b5 100644 --- a/source/blender/editors/interface/interface_region_popover.c +++ b/source/blender/editors/interface/interface_region_popover.c @@ -90,7 +90,7 @@ struct uiPopover { #endif }; -static void ui_popover_create_block(bContext *C, uiPopover *pup, int opcontext) +static void ui_popover_create_block(bContext *C, uiPopover *pup, wmOperatorCallContext opcontext) { BLI_assert(pup->ui_size_x != 0); @@ -188,7 +188,7 @@ static uiBlock *ui_block_func_POPOVER(bContext *C, uiPopupBlockHandle *handle, v } } - /* Estimated a maximum size so we don't go offscreen for low height + /* Estimated a maximum size so we don't go off-screen for low height * areas near the bottom of the window on refreshes. */ handle->max_size_y = UI_UNIT_Y * 16.0f; } @@ -420,7 +420,7 @@ void UI_popover_end(bContext *C, uiPopover *pup, wmKeyMap *keymap) * For now close this style of popovers when accessed. */ UI_block_flag_disable(pup->block, UI_BLOCK_KEEP_OPEN); - /* panels are created flipped (from event handling pov) */ + /* Panels are created flipped (from event handling POV). */ pup->block->flag ^= UI_BLOCK_IS_FLIP; } diff --git a/source/blender/editors/interface/interface_region_popup.c b/source/blender/editors/interface/interface_region_popup.c index 8135f5a203e..0e53100f91b 100644 --- a/source/blender/editors/interface/interface_region_popup.c +++ b/source/blender/editors/interface/interface_region_popup.c @@ -184,10 +184,10 @@ static void ui_popup_block_position(wmWindow *window, dir1 &= (UI_DIR_UP | UI_DIR_DOWN); } - if ((dir2 == 0) && (dir1 == UI_DIR_LEFT || dir1 == UI_DIR_RIGHT)) { + if ((dir2 == 0) && (ELEM(dir1, UI_DIR_LEFT, UI_DIR_RIGHT))) { dir2 = UI_DIR_DOWN; } - if ((dir2 == 0) && (dir1 == UI_DIR_UP || dir1 == UI_DIR_DOWN)) { + if ((dir2 == 0) && (ELEM(dir1, UI_DIR_UP, UI_DIR_DOWN))) { dir2 = UI_DIR_LEFT; } @@ -341,7 +341,7 @@ static void ui_popup_block_position(wmWindow *window, block->safety.ymax = block->rect.ymax + s1; } - /* exception for switched pulldowns... */ + /* Exception for switched pull-downs. */ if (dir1 && (dir1 & block->direction) == 0) { if (dir2 == UI_DIR_RIGHT) { block->safety.xmax = block->rect.xmax + s2; @@ -773,7 +773,7 @@ uiPopupBlockHandle *ui_popup_block_create(bContext *C, uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func, void *arg, - void (*arg_free)(void *arg)) + uiFreeArgFunc arg_free) { wmWindow *window = CTX_wm_window(C); uiBut *activebut = UI_context_active_but_get(C); @@ -803,7 +803,7 @@ uiPopupBlockHandle *ui_popup_block_create(bContext *C, handle->popup_create_vars.arg_free = arg_free; handle->popup_create_vars.but = but; handle->popup_create_vars.butregion = but ? butregion : NULL; - copy_v2_v2_int(handle->popup_create_vars.event_xy, &window->eventstate->x); + copy_v2_v2_int(handle->popup_create_vars.event_xy, window->eventstate->xy); /* don't allow by default, only if popup type explicitly supports it */ handle->can_refresh = false; diff --git a/source/blender/editors/interface/interface_region_search.c b/source/blender/editors/interface/interface_region_search.c index c35dbc5d7a6..b8a19d06be1 100644 --- a/source/blender/editors/interface/interface_region_search.c +++ b/source/blender/editors/interface/interface_region_search.c @@ -290,11 +290,11 @@ int ui_searchbox_find_index(ARegion *region, const char *name) } /* x and y in screen-coords. */ -bool ui_searchbox_inside(ARegion *region, int x, int y) +bool ui_searchbox_inside(ARegion *region, const int xy[2]) { uiSearchboxData *data = region->regiondata; - return BLI_rcti_isect_pt(&data->bbox, x - region->winrct.xmin, y - region->winrct.ymin); + return BLI_rcti_isect_pt(&data->bbox, xy[0] - region->winrct.xmin, xy[1] - region->winrct.ymin); } /* string validated to be of correct length (but->hardmax) */ @@ -316,7 +316,11 @@ bool ui_searchbox_apply(uiBut *but, ARegion *region) const char *name_sep = data->use_shortcut_sep ? strrchr(name, UI_SEP_CHAR) : NULL; - BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) + 1 : data->items.maxstrlen); + /* Search button with dynamic string properties may have their own method of applying + * the search results, so only copy the result if there is a proper space for it. */ + if (but->hardmax != 0) { + BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) + 1 : data->items.maxstrlen); + } search_but->item_active = data->items.pointers[data->active]; @@ -400,8 +404,9 @@ bool ui_searchbox_event( * (a little confusing if this isn't the case, although it does work). */ rcti rect; ui_searchbox_butrect(&rect, data, data->active); - if (BLI_rcti_isect_pt( - &rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) { + if (BLI_rcti_isect_pt(&rect, + event->xy[0] - region->winrct.xmin, + event->xy[1] - region->winrct.ymin)) { void *active = data->items.pointers[data->active]; if (search_but->item_context_menu_fn(C, search_but->arg, active, event)) { @@ -415,14 +420,14 @@ bool ui_searchbox_event( case MOUSEMOVE: { bool is_inside = false; - if (BLI_rcti_isect_pt(®ion->winrct, event->x, event->y)) { + if (BLI_rcti_isect_pt(®ion->winrct, event->xy[0], event->xy[1])) { rcti rect; int a; for (a = 0; a < data->items.totitem; a++) { ui_searchbox_butrect(&rect, data, a); if (BLI_rcti_isect_pt( - &rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) { + &rect, event->xy[0] - region->winrct.xmin, event->xy[1] - region->winrct.ymin)) { is_inside = true; if (data->active != a) { data->active = a; @@ -528,9 +533,7 @@ void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool re /* handle case where editstr is equal to one of items */ if (reset && data->active == -1) { - int a; - - for (a = 0; a < data->items.totitem; a++) { + for (int a = 0; a < data->items.totitem; a++) { const char *name = data->items.names[a] + /* Never include the prefix in the button. */ (data->items.name_prefix_offsets ? data->items.name_prefix_offsets[a] : @@ -572,7 +575,7 @@ int ui_searchbox_autocomplete(bContext *C, ARegion *region, uiBut *but, char *st return match; } -static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region) +static void ui_searchbox_region_draw_fn(const bContext *C, ARegion *region) { uiSearchboxData *data = region->regiondata; @@ -586,11 +589,10 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region) /* draw text */ if (data->items.totitem) { rcti rect; - int a; if (data->preview) { /* draw items */ - for (a = 0; a < data->items.totitem; a++) { + for (int a = 0; a < data->items.totitem; a++) { const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a]; /* ensure icon is up-to-date */ @@ -599,8 +601,12 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region) ui_searchbox_butrect(&rect, data, a); /* widget itself */ - ui_draw_preview_item( - &data->fstyle, &rect, data->items.names[a], data->items.icons[a], state); + ui_draw_preview_item(&data->fstyle, + &rect, + data->items.names[a], + data->items.icons[a], + state, + UI_STYLE_TEXT_LEFT); } /* indicate more */ @@ -620,7 +626,7 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region) else { const int search_sep_len = data->sep_string ? strlen(data->sep_string) : 0; /* draw items */ - for (a = 0; a < data->items.totitem; a++) { + for (int a = 0; a < data->items.totitem; a++) { const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a]; char *name = data->items.names[a]; int icon = data->items.icons[a]; @@ -684,26 +690,25 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region) if (data->items.more) { ui_searchbox_butrect(&rect, data, data->items.maxitem - 1); GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN); + UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymin - 9, ICON_TRIA_DOWN); GPU_blend(GPU_BLEND_NONE); } if (data->items.offset) { ui_searchbox_butrect(&rect, data, 0); GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP); + UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymax - 7, ICON_TRIA_UP); GPU_blend(GPU_BLEND_NONE); } } } } -static void ui_searchbox_region_free_cb(ARegion *region) +static void ui_searchbox_region_free_fn(ARegion *region) { uiSearchboxData *data = region->regiondata; - int a; /* free search data */ - for (a = 0; a < data->items.maxitem; a++) { + for (int a = 0; a < data->items.maxitem; a++) { MEM_freeN(data->items.names[a]); } MEM_freeN(data->items.names); @@ -735,8 +740,8 @@ static ARegion *ui_searchbox_create_generic_ex(bContext *C, static ARegionType type; memset(&type, 0, sizeof(ARegionType)); - type.draw = ui_searchbox_region_draw_cb; - type.free = ui_searchbox_region_free_cb; + type.draw = ui_searchbox_region_draw_fn; + type.free = ui_searchbox_region_free_fn; type.regionid = RGN_TYPE_TEMPORARY; region->type = &type; @@ -873,7 +878,8 @@ static ARegion *ui_searchbox_create_generic_ex(bContext *C, else { data->items.maxitem = SEARCH_ITEMS; } - data->items.maxstrlen = but->hardmax; + /* In case the button's string is dynamic, make sure there are buffers available. */ + data->items.maxstrlen = but->hardmax == 0 ? UI_MAX_NAME_STR : but->hardmax; data->items.totitem = 0; data->items.names = MEM_callocN(data->items.maxitem * sizeof(void *), "search names"); data->items.pointers = MEM_callocN(data->items.maxitem * sizeof(void *), "search pointers"); @@ -881,7 +887,7 @@ static ARegion *ui_searchbox_create_generic_ex(bContext *C, data->items.states = MEM_callocN(data->items.maxitem * sizeof(int), "search flags"); data->items.name_prefix_offsets = NULL; /* Lazy initialized as needed. */ for (int i = 0; i < data->items.maxitem; i++) { - data->items.names[i] = MEM_callocN(but->hardmax + 1, "search pointers"); + data->items.names[i] = MEM_callocN(data->items.maxstrlen + 1, "search pointers"); } return region; @@ -900,10 +906,9 @@ ARegion *ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiButSearc */ static void str_tolower_titlecaps_ascii(char *str, const size_t len) { - size_t i; bool prev_delim = true; - for (i = 0; (i < len) && str[i]; i++) { + for (size_t i = 0; (i < len) && str[i]; i++) { if (str[i] >= 'A' && str[i] <= 'Z') { if (prev_delim == false) { str[i] += 'a' - 'A'; @@ -931,10 +936,9 @@ static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARe /* draw text */ if (data->items.totitem) { rcti rect; - int a; /* draw items */ - for (a = 0; a < data->items.totitem; a++) { + for (int a = 0; a < data->items.totitem; a++) { rcti rect_pre, rect_post; ui_searchbox_butrect(&rect, data, a); @@ -986,13 +990,13 @@ static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARe if (data->items.more) { ui_searchbox_butrect(&rect, data, data->items.maxitem - 1); GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN); + UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymin - 9, ICON_TRIA_DOWN); GPU_blend(GPU_BLEND_NONE); } if (data->items.offset) { ui_searchbox_butrect(&rect, data, 0); GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP); + UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymax - 7, ICON_TRIA_UP); GPU_blend(GPU_BLEND_NONE); } } @@ -1033,8 +1037,6 @@ ARegion *ui_searchbox_create_menu(bContext *C, ARegion *butregion, uiButSearch * void ui_but_search_refresh(uiButSearch *search_but) { uiBut *but = &search_but->but; - uiSearchItems *items; - int x1; /* possibly very large lists (such as ID datablocks) only * only validate string RNA buts (not pointers) */ @@ -1042,14 +1044,14 @@ void ui_but_search_refresh(uiButSearch *search_but) return; } - items = MEM_callocN(sizeof(uiSearchItems), "search items"); + uiSearchItems *items = MEM_callocN(sizeof(uiSearchItems), "search items"); /* setup search struct */ items->maxitem = 10; items->maxstrlen = 256; items->names = MEM_callocN(items->maxitem * sizeof(void *), "search names"); - for (x1 = 0; x1 < items->maxitem; x1++) { - items->names[x1] = MEM_callocN(but->hardmax + 1, "search names"); + for (int i = 0; i < items->maxitem; i++) { + items->names[i] = MEM_callocN(but->hardmax + 1, "search names"); } ui_searchbox_update_fn(but->block->evil_C, search_but, but->drawstr, items); @@ -1066,8 +1068,8 @@ void ui_but_search_refresh(uiButSearch *search_but) } } - for (x1 = 0; x1 < items->maxitem; x1++) { - MEM_freeN(items->names[x1]); + for (int i = 0; i < items->maxitem; i++) { + MEM_freeN(items->names[i]); } MEM_freeN(items->names); MEM_freeN(items); diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c index bf3425dd3eb..0d8bdfc5817 100644 --- a/source/blender/editors/interface/interface_region_tooltip.c +++ b/source/blender/editors/interface/interface_region_tooltip.c @@ -435,7 +435,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is } } else { - /* Note, this is an exceptional case, we could even remove it + /* NOTE: this is an exceptional case, we could even remove it * however there have been reports of tooltips failing, so keep it for now. */ expr_result = BLI_strdup(IFACE_("Internal error!")); is_error = true; @@ -492,7 +492,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is } } else { - /* Note, this is an exceptional case, we could even remove it + /* NOTE: this is an exceptional case, we could even remove it * however there have been reports of tooltips failing, so keep it for now. */ expr_result = BLI_strdup(TIP_("Internal error!")); is_error = true; @@ -574,7 +574,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is shortcut_toolbar, ARRAY_SIZE(shortcut_toolbar))) { /* Generate keymap in order to inspect it. - * Note, we could make a utility to avoid the keymap generation part of this. */ + * NOTE: we could make a utility to avoid the keymap generation part of this. */ const char *expr_imports[] = { "bpy", "bl_keymap_utils", "bl_keymap_utils.keymap_from_toolbar", NULL}; const char *expr = @@ -761,7 +761,9 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is return data; } -static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) +static uiTooltipData *ui_tooltip_data_from_button_or_extra_icon(bContext *C, + uiBut *but, + uiButExtraOpIcon *extra_icon) { uiStringInfo but_label = {BUT_GET_LABEL, NULL}; uiStringInfo but_tip = {BUT_GET_TIP, NULL}; @@ -774,20 +776,29 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) char buf[512]; + wmOperatorType *optype = extra_icon ? UI_but_extra_operator_icon_optype_get(extra_icon) : + but->optype; + PropertyRNA *rnaprop = extra_icon ? NULL : but->rnaprop; + /* create tooltip data */ uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData"); - UI_but_string_info_get(C, - but, - &but_label, - &but_tip, - &enum_label, - &enum_tip, - &op_keymap, - &prop_keymap, - &rna_struct, - &rna_prop, - NULL); + if (extra_icon) { + UI_but_extra_icon_string_info_get(C, extra_icon, &but_label, &but_tip, &op_keymap, NULL); + } + else { + UI_but_string_info_get(C, + but, + &but_label, + &but_tip, + &enum_label, + &enum_tip, + &op_keymap, + &prop_keymap, + &rna_struct, + &rna_prop, + NULL); + } /* Tip Label (only for buttons not already showing the label). * Check prefix instead of comparing because the button may include the shortcut. */ @@ -818,8 +829,7 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) } /* special case enum rna buttons */ - if ((but->type & UI_BTYPE_ROW) && but->rnaprop && - RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG) { + if ((but->type & UI_BTYPE_ROW) && rnaprop && RNA_property_flag(rnaprop) & PROP_ENUM_FLAG) { uiTooltipField *field = text_field_add(data, &(uiTooltipFormat){ .style = UI_TIP_STYLE_NORMAL, @@ -863,7 +873,7 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { /* better not show the value of a password */ - if ((but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) == 0) { + if ((rnaprop && (RNA_property_subtype(rnaprop) == PROP_PASSWORD)) == 0) { /* full string */ ui_but_string_get(but, buf, sizeof(buf)); if (buf[0]) { @@ -878,15 +888,14 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) } } - if (but->rnaprop) { + if (rnaprop) { const int unit_type = UI_but_unit_type_get(but); if (unit_type == PROP_UNIT_ROTATION) { - if (RNA_property_type(but->rnaprop) == PROP_FLOAT) { - float value = RNA_property_array_check(but->rnaprop) ? - RNA_property_float_get_index( - &but->rnapoin, but->rnaprop, but->rnaindex) : - RNA_property_float_get(&but->rnapoin, but->rnaprop); + if (RNA_property_type(rnaprop) == PROP_FLOAT) { + float value = RNA_property_array_check(rnaprop) ? + RNA_property_float_get_index(&but->rnapoin, rnaprop, but->rnaindex) : + RNA_property_float_get(&but->rnapoin, rnaprop); uiTooltipField *field = text_field_add(data, &(uiTooltipFormat){ @@ -920,15 +929,15 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) } } } - else if (but->optype) { - PointerRNA *opptr; - char *str; - opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */ + else if (optype) { + PointerRNA *opptr = extra_icon ? UI_but_extra_operator_icon_opptr_get(extra_icon) : + /* allocated when needed, the button owns it */ + UI_but_operator_ptr_get(but); /* so the context is passed to fieldf functions (some py fieldf functions use it) */ WM_operator_properties_sanitize(opptr, false); - str = ui_tooltip_text_python_from_op(C, but->optype, opptr); + char *str = ui_tooltip_text_python_from_op(C, optype, opptr); /* operator info */ if (U.flag & USER_TOOLTIPS_PYTHON) { @@ -945,18 +954,21 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) } /* button is disabled, we may be able to tell user why */ - if (but->flag & UI_BUT_DISABLED) { + if ((but->flag & UI_BUT_DISABLED) || extra_icon) { const char *disabled_msg = NULL; bool disabled_msg_free = false; /* if operator poll check failed, it can give pretty precise info why */ - if (but->optype) { + if (optype) { + const wmOperatorCallContext opcontext = extra_icon ? extra_icon->optype_params->opcontext : + but->opcontext; CTX_wm_operator_poll_msg_clear(C); - WM_operator_poll_context(C, but->optype, but->opcontext); + ui_but_context_poll_operator_ex( + C, but, &(wmOperatorCallParams){.optype = optype, .opcontext = opcontext}); disabled_msg = CTX_wm_operator_poll_msg_get(C, &disabled_msg_free); } /* alternatively, buttons can store some reasoning too */ - else if (but->disabled_info) { + else if (!extra_icon && but->disabled_info) { disabled_msg = TIP_(but->disabled_info); } @@ -973,7 +985,7 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) } } - if ((U.flag & USER_TOOLTIPS_PYTHON) && !but->optype && rna_struct.strinfo) { + if ((U.flag & USER_TOOLTIPS_PYTHON) && !optype && rna_struct.strinfo) { { uiTooltipField *field = text_field_add(data, &(uiTooltipFormat){ @@ -1002,9 +1014,9 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) /* never fails */ /* move ownership (no need for re-alloc) */ - if (but->rnaprop) { + if (rnaprop) { field->text = RNA_path_full_property_py_ex( - CTX_data_main(C), &but->rnapoin, but->rnaprop, but->rnaindex, true); + CTX_data_main(C), &but->rnapoin, rnaprop, but->rnaindex, true); } else { field->text = RNA_path_full_struct_py(CTX_data_main(C), &but->rnapoin); @@ -1175,9 +1187,6 @@ static ARegion *ui_tooltip_create_with_data(bContext *C, data->wrap_width = min_ii(UI_TIP_MAXWIDTH * U.pixelsize / aspect, winx - (UI_TIP_PADDING * 2)); font_flag |= BLF_WORD_WRAP; - if (data->fstyle.kerning == 1) { - font_flag |= BLF_KERNING_DEFAULT; - } BLF_enable(data->fstyle.uifont_id, font_flag); BLF_enable(blf_mono_font, font_flag); BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width); @@ -1397,11 +1406,8 @@ static ARegion *ui_tooltip_create_with_data(bContext *C, /** \name ToolTip Public API * \{ */ -/** - * \param is_label: When true, show a small tip that only shows the name, - * otherwise show the full tooltip. - */ -ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_label) +ARegion *UI_tooltip_create_from_button_or_extra_icon( + bContext *C, ARegion *butregion, uiBut *but, uiButExtraOpIcon *extra_icon, bool is_label) { wmWindow *win = CTX_wm_window(C); /* aspect values that shrink text are likely unreadable */ @@ -1418,7 +1424,11 @@ ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *b } if (data == NULL) { - data = ui_tooltip_data_from_button(C, but); + data = ui_tooltip_data_from_button_or_extra_icon(C, but, extra_icon); + } + + if (data == NULL) { + data = ui_tooltip_data_from_button_or_extra_icon(C, but, NULL); } if (data == NULL) { @@ -1445,7 +1455,7 @@ ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *b init_position[1] = but->rect.ymin; if (butregion) { ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]); - init_position[0] = win->eventstate->x; + init_position[0] = win->eventstate->xy[0]; } init_position[1] -= (UI_POPUP_MARGIN / 2); } @@ -1456,11 +1466,20 @@ ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *b return region; } +/** + * \param is_label: When true, show a small tip that only shows the name, otherwise show the full + * tooltip. + */ +ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_label) +{ + return UI_tooltip_create_from_button_or_extra_icon(C, butregion, but, NULL, is_label); +} + ARegion *UI_tooltip_create_from_gizmo(bContext *C, wmGizmo *gz) { wmWindow *win = CTX_wm_window(C); const float aspect = 1.0f; - float init_position[2] = {win->eventstate->x, win->eventstate->y}; + float init_position[2] = {win->eventstate->xy[0], win->eventstate->xy[1]}; uiTooltipData *data = ui_tooltip_data_from_gizmo(C, gz); if (data == NULL) { @@ -1544,7 +1563,7 @@ ARegion *UI_tooltip_create_from_search_item_generic( const float aspect = 1.0f; const wmWindow *win = CTX_wm_window(C); float init_position[2]; - init_position[0] = win->eventstate->x; + init_position[0] = win->eventstate->xy[0]; init_position[1] = item_rect->ymin + searchbox_region->winrct.ymin - (UI_POPUP_MARGIN / 2); return ui_tooltip_create_with_data(C, data, init_position, NULL, aspect); diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c index 88ab6a377d0..4c640851999 100644 --- a/source/blender/editors/interface/interface_style.c +++ b/source/blender/editors/interface/interface_style.c @@ -83,7 +83,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id style->paneltitle.uifont_id = uifont_id; style->paneltitle.points = UI_DEFAULT_TITLE_POINTS; - style->paneltitle.kerning = 1; style->paneltitle.shadow = 3; style->paneltitle.shadx = 0; style->paneltitle.shady = -1; @@ -92,7 +91,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id style->grouplabel.uifont_id = uifont_id; style->grouplabel.points = UI_DEFAULT_TITLE_POINTS; - style->grouplabel.kerning = 1; style->grouplabel.shadow = 3; style->grouplabel.shadx = 0; style->grouplabel.shady = -1; @@ -101,7 +99,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id style->widgetlabel.uifont_id = uifont_id; style->widgetlabel.points = UI_DEFAULT_TEXT_POINTS; - style->widgetlabel.kerning = 1; style->widgetlabel.shadow = 3; style->widgetlabel.shadx = 0; style->widgetlabel.shady = -1; @@ -110,7 +107,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id style->widget.uifont_id = uifont_id; style->widget.points = UI_DEFAULT_TEXT_POINTS; - style->widget.kerning = 1; style->widget.shadow = 1; style->widget.shady = -1; style->widget.shadowalpha = 0.5f; @@ -164,9 +160,6 @@ void UI_fontstyle_draw_ex(const uiFontStyle *fs, BLF_shadow(fs->uifont_id, fs->shadow, shadow_color); BLF_shadow_offset(fs->uifont_id, fs->shadx, fs->shady); } - if (fs->kerning == 1) { - font_flag |= BLF_KERNING_DEFAULT; - } if (fs_params->word_wrap == 1) { font_flag |= BLF_WORD_WRAP; } @@ -278,19 +271,12 @@ void UI_fontstyle_draw_rotated(const uiFontStyle *fs, BLF_shadow_offset(fs->uifont_id, fs->shadx, fs->shady); } - if (fs->kerning == 1) { - BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); - } - BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); BLF_disable(fs->uifont_id, BLF_ROTATION); BLF_disable(fs->uifont_id, BLF_CLIPPING); if (fs->shadow) { BLF_disable(fs->uifont_id, BLF_SHADOW); } - if (fs->kerning == 1) { - BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); - } } /** @@ -302,18 +288,10 @@ void UI_fontstyle_draw_rotated(const uiFontStyle *fs, void UI_fontstyle_draw_simple( const uiFontStyle *fs, float x, float y, const char *str, const uchar col[4]) { - if (fs->kerning == 1) { - BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); - } - UI_fontstyle_set(fs); BLF_position(fs->uifont_id, x, y, 0.0f); BLF_color4ubv(fs->uifont_id, col); BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); - - if (fs->kerning == 1) { - BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); - } } /** @@ -326,10 +304,6 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs, const float col_fg[4], const float col_bg[4]) { - if (fs->kerning == 1) { - BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); - } - UI_fontstyle_set(fs); { @@ -338,11 +312,8 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs, const float decent = BLF_descender(fs->uifont_id); const float margin = height / 4.0f; - /* backdrop */ - const float color[4] = {col_bg[0], col_bg[1], col_bg[2], 0.5f}; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_aa( + UI_draw_roundbox_4fv( &(const rctf){ .xmin = x - margin, .xmax = x + width + margin, @@ -351,16 +322,12 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs, }, true, margin, - color); + col_bg); } BLF_position(fs->uifont_id, x, y, 0.0f); BLF_color4fv(fs->uifont_id, col_fg); BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); - - if (fs->kerning == 1) { - BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); - } } /* ************** helpers ************************ */ @@ -405,20 +372,38 @@ const uiStyle *UI_style_get_dpi(void) int UI_fontstyle_string_width(const uiFontStyle *fs, const char *str) { - int width; + UI_fontstyle_set(fs); + return (int)BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); +} - if (fs->kerning == 1) { - /* for BLF_width */ - BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); +/** + * Return the width of `str` with the spacing & kerning of `fs` with `aspect` + * (representing #uiBlock.aspect) applied. + * + * When calculating text width, the UI layout logic calculate widths without scale, + * only applying scale when drawing. This causes problems for fonts since kerning at + * smaller sizes often makes them wider than a scaled down version of the larger text. + * Resolve this by calculating the text at the on-screen size, + * returning the result scaled back to 1:1. See T92361. + */ +int UI_fontstyle_string_width_with_block_aspect(const uiFontStyle *fs, + const char *str, + const float aspect) +{ + uiFontStyle fs_buf; + if (aspect != 1.0f) { + fs_buf = *fs; + ui_fontscale(&fs_buf.points, aspect); + fs = &fs_buf; } - UI_fontstyle_set(fs); - width = BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); + int width = UI_fontstyle_string_width(fs, str); - if (fs->kerning == 1) { - BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); + if (aspect != 1.0f) { + /* While in most cases rounding up isn't important, it can make a difference + * with small fonts (3px or less), zooming out in the node-editor for e.g. */ + width = (int)ceilf(width * aspect); } - return width; } @@ -434,7 +419,7 @@ int UI_fontstyle_height_max(const uiFontStyle *fs) /* reading without uifont will create one */ void uiStyleInit(void) { - uiStyle *style = U.uistyles.first; + const uiStyle *style = U.uistyles.first; /* recover from uninitialized dpi */ if (U.dpi == 0) { @@ -505,9 +490,13 @@ void uiStyleInit(void) } if (style == NULL) { - ui_style_new(&U.uistyles, "Default Style", UIFONT_DEFAULT); + style = ui_style_new(&U.uistyles, "Default Style", UIFONT_DEFAULT); } + BLF_cache_flush_set_fn(UI_widgetbase_draw_cache_flush); + + BLF_default_size(style->widgetlabel.points); + /* XXX, this should be moved into a style, * but for now best only load the monospaced font once. */ BLI_assert(blf_mono_font == -1); diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc new file mode 100644 index 00000000000..7b2fb8f784e --- /dev/null +++ b/source/blender/editors/interface/interface_template_asset_view.cc @@ -0,0 +1,292 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include "DNA_space_types.h" +#include "DNA_userdef_types.h" + +#include "BKE_screen.h" + +#include "BLI_path_util.h" +#include "BLI_string.h" +#include "BLI_string_ref.hh" + +#include "BLO_readfile.h" + +#include "ED_asset.h" +#include "ED_screen.h" + +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +struct AssetViewListData { + AssetLibraryReference asset_library_ref; + bScreen *screen; + bool show_names; +}; + +static void asset_view_item_but_drag_set(uiBut *but, + AssetViewListData *list_data, + AssetHandle *asset_handle) +{ + ID *id = ED_asset_handle_get_local_id(asset_handle); + if (id != nullptr) { + UI_but_drag_set_id(but, id); + return; + } + + char blend_path[FILE_MAX_LIBEXTRA]; + /* Context can be null here, it's only needed for a File Browser specific hack that should go + * away before too long. */ + ED_asset_handle_get_full_library_path( + nullptr, &list_data->asset_library_ref, asset_handle, blend_path); + + if (blend_path[0]) { + ImBuf *imbuf = ED_assetlist_asset_image_get(asset_handle); + UI_but_drag_set_asset(but, + asset_handle, + BLI_strdup(blend_path), + ED_asset_handle_get_metadata(asset_handle), + FILE_ASSET_IMPORT_APPEND, + ED_asset_handle_get_preview_icon_id(asset_handle), + imbuf, + 1.0f); + } +} + +static void asset_view_draw_item(uiList *ui_list, + bContext *UNUSED(C), + uiLayout *layout, + PointerRNA *UNUSED(dataptr), + PointerRNA *itemptr, + int UNUSED(icon), + PointerRNA *UNUSED(active_dataptr), + const char *UNUSED(active_propname), + int UNUSED(index), + int UNUSED(flt_flag)) +{ + AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata; + + BLI_assert(RNA_struct_is_a(itemptr->type, &RNA_AssetHandle)); + AssetHandle *asset_handle = (AssetHandle *)itemptr->data; + + uiLayoutSetContextPointer(layout, "asset_handle", itemptr); + + uiBlock *block = uiLayoutGetBlock(layout); + const bool show_names = list_data->show_names; + /* TODO ED_fileselect_init_layout(). Share somehow? */ + const float size_x = (96.0f / 20.0f) * UI_UNIT_X; + const float size_y = (96.0f / 20.0f) * UI_UNIT_Y - (show_names ? 0 : UI_UNIT_Y); + uiBut *but = uiDefIconTextBut(block, + UI_BTYPE_PREVIEW_TILE, + 0, + ED_asset_handle_get_preview_icon_id(asset_handle), + show_names ? ED_asset_handle_get_name(asset_handle) : "", + 0, + 0, + size_x, + size_y, + nullptr, + 0, + 0, + 0, + 0, + ""); + ui_def_but_icon(but, + ED_asset_handle_get_preview_icon_id(asset_handle), + /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */ + UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + if (!ui_list->dyn_data->custom_drag_optype) { + asset_view_item_but_drag_set(but, list_data, asset_handle); + } +} + +static void asset_view_listener(uiList *ui_list, wmRegionListenerParams *params) +{ + AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata; + const wmNotifier *notifier = params->notifier; + + switch (notifier->category) { + case NC_ID: { + if (ELEM(notifier->action, NA_RENAME)) { + ED_assetlist_storage_tag_main_data_dirty(); + } + break; + } + } + + if (ED_assetlist_listen(&list_data->asset_library_ref, params->notifier)) { + ED_region_tag_redraw(params->region); + } +} + +uiListType *UI_UL_asset_view() +{ + uiListType *list_type = (uiListType *)MEM_callocN(sizeof(*list_type), __func__); + + BLI_strncpy(list_type->idname, "UI_UL_asset_view", sizeof(list_type->idname)); + list_type->draw_item = asset_view_draw_item; + list_type->listener = asset_view_listener; + + return list_type; +} + +static void asset_view_template_refresh_asset_collection( + const AssetLibraryReference &asset_library_ref, + const AssetFilterSettings &filter_settings, + PointerRNA &assets_dataptr, + const char *assets_propname) +{ + PropertyRNA *assets_prop = RNA_struct_find_property(&assets_dataptr, assets_propname); + if (!assets_prop) { + RNA_warning("Asset collection not found"); + return; + } + if (RNA_property_type(assets_prop) != PROP_COLLECTION) { + RNA_warning("Expected a collection property"); + return; + } + if (!RNA_struct_is_a(RNA_property_pointer_type(&assets_dataptr, assets_prop), + &RNA_AssetHandle)) { + RNA_warning("Expected a collection property for AssetHandle items"); + return; + } + + RNA_property_collection_clear(&assets_dataptr, assets_prop); + + ED_assetlist_iterate(asset_library_ref, [&](AssetHandle asset) { + if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) { + /* Don't do anything else, but return true to continue iterating. */ + return true; + } + + PointerRNA itemptr, fileptr; + RNA_property_collection_add(&assets_dataptr, assets_prop, &itemptr); + + RNA_pointer_create( + nullptr, &RNA_FileSelectEntry, const_cast<FileDirEntry *>(asset.file_data), &fileptr); + RNA_pointer_set(&itemptr, "file_data", fileptr); + + return true; + }); +} + +void uiTemplateAssetView(uiLayout *layout, + bContext *C, + const char *list_id, + PointerRNA *asset_library_dataptr, + const char *asset_library_propname, + PointerRNA *assets_dataptr, + const char *assets_propname, + PointerRNA *active_dataptr, + const char *active_propname, + const AssetFilterSettings *filter_settings, + const int display_flags, + const char *activate_opname, + PointerRNA *r_activate_op_properties, + const char *drag_opname, + PointerRNA *r_drag_op_properties) +{ + if (!list_id || !list_id[0]) { + RNA_warning("Asset view needs a valid identifier"); + return; + } + + uiLayout *col = uiLayoutColumn(layout, false); + + PropertyRNA *asset_library_prop = RNA_struct_find_property(asset_library_dataptr, + asset_library_propname); + AssetLibraryReference asset_library_ref = ED_asset_library_reference_from_enum_value( + RNA_property_enum_get(asset_library_dataptr, asset_library_prop)); + + uiLayout *row = uiLayoutRow(col, true); + if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY) == 0) { + uiItemFullR(row, asset_library_dataptr, asset_library_prop, RNA_NO_INDEX, 0, 0, "", 0); + if (asset_library_ref.type != ASSET_LIBRARY_LOCAL) { + uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_list_refresh"); + } + } + + ED_assetlist_storage_fetch(&asset_library_ref, C); + ED_assetlist_ensure_previews_job(&asset_library_ref, C); + const int tot_items = ED_assetlist_size(&asset_library_ref); + + asset_view_template_refresh_asset_collection( + asset_library_ref, *filter_settings, *assets_dataptr, assets_propname); + + AssetViewListData *list_data = (AssetViewListData *)MEM_mallocN(sizeof(*list_data), + "AssetViewListData"); + list_data->asset_library_ref = asset_library_ref; + list_data->screen = CTX_wm_screen(C); + list_data->show_names = (display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) == 0; + + uiTemplateListFlags template_list_flags = UI_TEMPLATE_LIST_NO_GRIP; + if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) != 0) { + template_list_flags |= UI_TEMPLATE_LIST_NO_NAMES; + } + if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_FILTER) != 0) { + template_list_flags |= UI_TEMPLATE_LIST_NO_FILTER_OPTIONS; + } + + /* TODO can we have some kind of model-view API to handle referencing, filtering and lazy loading + * (of previews) of the items? */ + uiList *list = uiTemplateList_ex(col, + C, + "UI_UL_asset_view", + list_id, + assets_dataptr, + assets_propname, + active_dataptr, + active_propname, + nullptr, + tot_items, + 0, + UILST_LAYOUT_BIG_PREVIEW_GRID, + 0, + template_list_flags, + list_data); + if (!list) { + /* List creation failed. */ + MEM_freeN(list_data); + return; + } + + if (activate_opname) { + PointerRNA *ptr = UI_list_custom_activate_operator_set( + list, activate_opname, r_activate_op_properties != nullptr); + if (r_activate_op_properties && ptr) { + *r_activate_op_properties = *ptr; + } + } + if (drag_opname) { + PointerRNA *ptr = UI_list_custom_drag_operator_set( + list, drag_opname, r_drag_op_properties != nullptr); + if (r_drag_op_properties && ptr) { + *r_drag_op_properties = *ptr; + } + } +} diff --git a/source/blender/editors/interface/interface_template_attribute_search.cc b/source/blender/editors/interface/interface_template_attribute_search.cc new file mode 100644 index 00000000000..85a6147432b --- /dev/null +++ b/source/blender/editors/interface/interface_template_attribute_search.cc @@ -0,0 +1,126 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include "BLI_string_ref.hh" +#include "BLI_string_search.h" + +#include "DNA_customdata_types.h" + +#include "RNA_access.h" +#include "RNA_enum_types.h" + +#include "BLT_translation.h" + +#include "NOD_geometry_nodes_eval_log.hh" + +#include "UI_interface.h" +#include "UI_interface.hh" +#include "UI_resources.h" + +using blender::nodes::geometry_nodes_eval_log::GeometryAttributeInfo; + +namespace blender::ui { + +static StringRef attribute_data_type_string(const CustomDataType type) +{ + const char *name = nullptr; + RNA_enum_name_from_value(rna_enum_attribute_type_items, type, &name); + return StringRef(IFACE_(name)); +} + +static StringRef attribute_domain_string(const AttributeDomain domain) +{ + const char *name = nullptr; + RNA_enum_name_from_value(rna_enum_attribute_domain_items, domain, &name); + return StringRef(IFACE_(name)); +} + +static bool attribute_search_item_add(uiSearchItems *items, const GeometryAttributeInfo &item) +{ + const StringRef data_type_name = attribute_data_type_string(item.data_type); + const StringRef domain_name = attribute_domain_string(item.domain); + std::string search_item_text = domain_name + " " + UI_MENU_ARROW_SEP + item.name + UI_SEP_CHAR + + data_type_name; + + return UI_search_item_add( + items, search_item_text.c_str(), (void *)&item, ICON_NONE, UI_BUT_HAS_SEP_CHAR, 0); +} + +void attribute_search_add_items(StringRefNull str, + const bool is_output, + Span<const GeometryAttributeInfo *> infos, + uiSearchItems *seach_items, + const bool is_first) +{ + static GeometryAttributeInfo dummy_info; + + /* Any string may be valid, so add the current search string along with the hints. */ + if (str[0] != '\0') { + bool contained = false; + for (const GeometryAttributeInfo *attribute_info : infos) { + if (attribute_info->name == str) { + contained = true; + break; + } + } + if (!contained) { + dummy_info.name = str; + UI_search_item_add( + seach_items, str.c_str(), &dummy_info, is_output ? ICON_ADD : ICON_NONE, 0, 0); + } + } + + if (str[0] == '\0' && !is_first) { + /* Allow clearing the text field when the string is empty, but not on the first pass, + * or opening an attribute field for the first time would show this search item. */ + dummy_info.name = str; + UI_search_item_add(seach_items, str.c_str(), &dummy_info, ICON_X, 0, 0); + } + + /* Don't filter when the menu is first opened, but still run the search + * so the items are in the same order they will appear in while searching. */ + const char *string = is_first ? "" : str.c_str(); + + StringSearch *search = BLI_string_search_new(); + for (const GeometryAttributeInfo *item : infos) { + + /* Don't show the legacy "normal" attribute. */ + if (item->name == "normal" && item->domain == ATTR_DOMAIN_FACE) { + continue; + } + + BLI_string_search_add(search, item->name.c_str(), (void *)item); + } + + GeometryAttributeInfo **filtered_items; + const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items); + + for (const int i : IndexRange(filtered_amount)) { + const GeometryAttributeInfo *item = filtered_items[i]; + if (!attribute_search_item_add(seach_items, *item)) { + break; + } + } + + MEM_freeN(filtered_items); + BLI_string_search_free(search); +} + +} // namespace blender::ui diff --git a/source/blender/editors/interface/interface_template_list.cc b/source/blender/editors/interface/interface_template_list.cc new file mode 100644 index 00000000000..845a7813da2 --- /dev/null +++ b/source/blender/editors/interface/interface_template_list.cc @@ -0,0 +1,1333 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include <cstdlib> +#include <cstring> + +#include "BLI_fnmatch.h" +#include "BLI_listbase.h" +#include "BLI_math_base.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BKE_screen.h" + +#include "BLT_translation.h" + +#include "ED_asset.h" +#include "ED_screen.h" + +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "WM_api.h" + +#include "interface_intern.h" + +/** + * The validated data that was passed to #uiTemplateList (typically through Python). + * Populated through #ui_template_list_data_retrieve(). + */ +struct TemplateListInputData { + PointerRNA dataptr; + PropertyRNA *prop; + PointerRNA active_dataptr; + PropertyRNA *activeprop; + const char *item_dyntip_propname; + + /* Index as stored in the input property. I.e. the index before sorting. */ + int active_item_idx; +}; + +/** + * Internal wrapper for a single item in the list (well, actually stored as a vector). + */ +struct _uilist_item { + PointerRNA item; + int org_idx; + int flt_flag; +}; + +/** + * Container for the item vector and additional info. + */ +struct TemplateListItems { + _uilist_item *item_vec; + /* Index of the active item following visual order. I.e. unlike + * TemplateListInputData.active_item_idx, this is the index after sorting. */ + int active_item_idx; + int tot_items; +}; + +struct TemplateListLayoutDrawData { + uiListDrawItemFunc draw_item; + uiListDrawFilterFunc draw_filter; + + int rows; + int maxrows; + int columns; +}; + +struct TemplateListVisualInfo { + int visual_items; /* Visual number of items (i.e. number of items we have room to display). */ + int start_idx; /* Index of first item to display. */ + int end_idx; /* Index of last item to display + 1. */ +}; + +static void uilist_draw_item_default(struct uiList *ui_list, + struct bContext *UNUSED(C), + struct uiLayout *layout, + struct PointerRNA *UNUSED(dataptr), + struct PointerRNA *itemptr, + int icon, + struct PointerRNA *UNUSED(active_dataptr), + const char *UNUSED(active_propname), + int UNUSED(index), + int UNUSED(flt_flag)) +{ + PropertyRNA *nameprop = RNA_struct_name_property(itemptr->type); + + /* Simplest one! */ + switch (ui_list->layout_type) { + case UILST_LAYOUT_GRID: + uiItemL(layout, "", icon); + break; + case UILST_LAYOUT_DEFAULT: + case UILST_LAYOUT_COMPACT: + default: + if (nameprop) { + uiItemFullR(layout, itemptr, nameprop, RNA_NO_INDEX, 0, UI_ITEM_R_NO_BG, "", icon); + } + else { + uiItemL(layout, "", icon); + } + break; + } +} + +static void uilist_draw_filter_default(struct uiList *ui_list, + struct bContext *UNUSED(C), + struct uiLayout *layout) +{ + PointerRNA listptr; + RNA_pointer_create(nullptr, &RNA_UIList, ui_list, &listptr); + + uiLayout *row = uiLayoutRow(layout, false); + + uiLayout *subrow = uiLayoutRow(row, true); + uiItemR(subrow, &listptr, "filter_name", 0, "", ICON_NONE); + uiItemR(subrow, + &listptr, + "use_filter_invert", + UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, + "", + ICON_ARROW_LEFTRIGHT); + + if ((ui_list->filter_sort_flag & UILST_FLT_SORT_LOCK) == 0) { + subrow = uiLayoutRow(row, true); + uiItemR(subrow, + &listptr, + "use_filter_sort_alpha", + UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, + "", + ICON_NONE); + uiItemR(subrow, + &listptr, + "use_filter_sort_reverse", + UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, + "", + (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) ? ICON_SORT_DESC : ICON_SORT_ASC); + } +} + +struct StringCmp { + char name[MAX_IDPROP_NAME]; + int org_idx; +}; + +static int cmpstringp(const void *p1, const void *p2) +{ + /* Case-insensitive comparison. */ + return BLI_strcasecmp(((StringCmp *)p1)->name, ((StringCmp *)p2)->name); +} + +static void uilist_filter_items_default(struct uiList *ui_list, + struct bContext *UNUSED(C), + struct PointerRNA *dataptr, + const char *propname) +{ + uiListDyn *dyn_data = ui_list->dyn_data; + PropertyRNA *prop = RNA_struct_find_property(dataptr, propname); + + const char *filter_raw = ui_list->filter_byname; + char *filter = (char *)filter_raw, filter_buff[32], *filter_dyn = nullptr; + const bool filter_exclude = (ui_list->filter_flag & UILST_FLT_EXCLUDE) != 0; + const bool order_by_name = (ui_list->filter_sort_flag & UILST_FLT_SORT_MASK) == + UILST_FLT_SORT_ALPHA; + const int len = RNA_property_collection_length(dataptr, prop); + + dyn_data->items_shown = dyn_data->items_len = len; + + if (len && (order_by_name || filter_raw[0])) { + StringCmp *names = nullptr; + int order_idx = 0, i = 0; + + if (order_by_name) { + names = static_cast<StringCmp *>(MEM_callocN(sizeof(StringCmp) * len, "StringCmp")); + } + if (filter_raw[0]) { + const size_t slen = strlen(filter_raw); + + dyn_data->items_filter_flags = static_cast<int *>( + MEM_callocN(sizeof(int) * len, "items_filter_flags")); + dyn_data->items_shown = 0; + + /* Implicitly add heading/trailing wildcards if needed. */ + if (slen + 3 <= sizeof(filter_buff)) { + filter = filter_buff; + } + else { + filter = filter_dyn = static_cast<char *>( + MEM_mallocN((slen + 3) * sizeof(char), "filter_dyn")); + } + BLI_strncpy_ensure_pad(filter, filter_raw, '*', slen + 3); + } + + RNA_PROP_BEGIN (dataptr, itemptr, prop) { + bool do_order = false; + + char *namebuf; + if (RNA_struct_is_a(itemptr.type, &RNA_AssetHandle)) { + /* XXX The AssetHandle design is hacky and meant to be temporary. It can't have a proper + * name property, so for now this hardcoded exception is needed. */ + AssetHandle *asset_handle = (AssetHandle *)itemptr.data; + const char *asset_name = ED_asset_handle_get_name(asset_handle); + namebuf = BLI_strdup(asset_name); + } + else { + namebuf = RNA_struct_name_get_alloc(&itemptr, nullptr, 0, nullptr); + } + + const char *name = namebuf ? namebuf : ""; + + if (filter[0]) { + /* Case-insensitive! */ + if (fnmatch(filter, name, FNM_CASEFOLD) == 0) { + dyn_data->items_filter_flags[i] = UILST_FLT_ITEM; + if (!filter_exclude) { + dyn_data->items_shown++; + do_order = order_by_name; + } + // printf("%s: '%s' matches '%s'\n", __func__, name, filter); + } + else if (filter_exclude) { + dyn_data->items_shown++; + do_order = order_by_name; + } + } + else { + do_order = order_by_name; + } + + if (do_order) { + names[order_idx].org_idx = order_idx; + BLI_strncpy(names[order_idx++].name, name, MAX_IDPROP_NAME); + } + + /* free name */ + if (namebuf) { + MEM_freeN(namebuf); + } + i++; + } + RNA_PROP_END; + + if (order_by_name) { + int new_idx; + /* NOTE: order_idx equals either to ui_list->items_len if no filtering done, + * or to ui_list->items_shown if filter is enabled, + * or to (ui_list->items_len - ui_list->items_shown) if filtered items are excluded. + * This way, we only sort items we actually intend to draw! + */ + qsort(names, order_idx, sizeof(StringCmp), cmpstringp); + + dyn_data->items_filter_neworder = static_cast<int *>( + MEM_mallocN(sizeof(int) * order_idx, "items_filter_neworder")); + for (new_idx = 0; new_idx < order_idx; new_idx++) { + dyn_data->items_filter_neworder[names[new_idx].org_idx] = new_idx; + } + } + + if (filter_dyn) { + MEM_freeN(filter_dyn); + } + if (names) { + MEM_freeN(names); + } + } +} + +static void uilist_free_dyn_data(uiList *ui_list) +{ + uiListDyn *dyn_data = ui_list->dyn_data; + if (!dyn_data) { + return; + } + + if (dyn_data->custom_activate_opptr) { + WM_operator_properties_free(dyn_data->custom_activate_opptr); + MEM_freeN(dyn_data->custom_activate_opptr); + } + if (dyn_data->custom_drag_opptr) { + WM_operator_properties_free(dyn_data->custom_drag_opptr); + MEM_freeN(dyn_data->custom_drag_opptr); + } + + MEM_SAFE_FREE(dyn_data->items_filter_flags); + MEM_SAFE_FREE(dyn_data->items_filter_neworder); + MEM_SAFE_FREE(dyn_data->customdata); +} + +/** + * Validate input parameters and initialize \a r_data from that. Plus find the list-type and return + * it in \a r_list_type. + * + * \return false if the input data isn't valid. Will also raise an RNA warning in that case. + */ +static bool ui_template_list_data_retrieve(const char *listtype_name, + const char *list_id, + PointerRNA *dataptr, + const char *propname, + PointerRNA *active_dataptr, + const char *active_propname, + const char *item_dyntip_propname, + TemplateListInputData *r_input_data, + uiListType **r_list_type) +{ + memset(r_input_data, 0, sizeof(*r_input_data)); + + /* Forbid default UI_UL_DEFAULT_CLASS_NAME list class without a custom list_id! */ + if (STREQ(UI_UL_DEFAULT_CLASS_NAME, listtype_name) && !(list_id && list_id[0])) { + RNA_warning("template_list using default '%s' UIList class must provide a custom list_id", + UI_UL_DEFAULT_CLASS_NAME); + return false; + } + + if (!active_dataptr->data) { + RNA_warning("No active data"); + return false; + } + + r_input_data->dataptr = *dataptr; + if (dataptr->data) { + r_input_data->prop = RNA_struct_find_property(dataptr, propname); + if (!r_input_data->prop) { + RNA_warning("Property not found: %s.%s", RNA_struct_identifier(dataptr->type), propname); + return false; + } + } + + r_input_data->active_dataptr = *active_dataptr; + r_input_data->activeprop = RNA_struct_find_property(active_dataptr, active_propname); + if (!r_input_data->activeprop) { + RNA_warning( + "Property not found: %s.%s", RNA_struct_identifier(active_dataptr->type), active_propname); + return false; + } + + if (r_input_data->prop) { + const PropertyType type = RNA_property_type(r_input_data->prop); + if (type != PROP_COLLECTION) { + RNA_warning("Expected a collection data property"); + return false; + } + } + + const PropertyType activetype = RNA_property_type(r_input_data->activeprop); + if (activetype != PROP_INT) { + RNA_warning("Expected an integer active data property"); + return false; + } + + /* Find the uiList type. */ + if (!(*r_list_type = WM_uilisttype_find(listtype_name, false))) { + RNA_warning("List type %s not found", listtype_name); + return false; + } + + r_input_data->active_item_idx = RNA_property_int_get(&r_input_data->active_dataptr, + r_input_data->activeprop); + r_input_data->item_dyntip_propname = item_dyntip_propname; + + return true; +} + +static void ui_template_list_collect_items(PointerRNA *list_ptr, + PropertyRNA *list_prop, + uiListDyn *dyn_data, + int filter_exclude, + bool order_reverse, + int activei, + TemplateListItems *r_items) +{ + int i = 0; + int reorder_i = 0; + bool activei_mapping_pending = true; + + RNA_PROP_BEGIN (list_ptr, itemptr, list_prop) { + if (!dyn_data->items_filter_flags || + ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) { + int new_order_idx; + if (dyn_data->items_filter_neworder) { + new_order_idx = dyn_data->items_filter_neworder[reorder_i++]; + new_order_idx = order_reverse ? dyn_data->items_shown - new_order_idx - 1 : new_order_idx; + } + else { + new_order_idx = order_reverse ? dyn_data->items_shown - ++reorder_i : reorder_i++; + } + // printf("%s: ii: %d\n", __func__, ii); + r_items->item_vec[new_order_idx].item = itemptr; + r_items->item_vec[new_order_idx].org_idx = i; + r_items->item_vec[new_order_idx].flt_flag = dyn_data->items_filter_flags ? + dyn_data->items_filter_flags[i] : + 0; + + if (activei_mapping_pending && activei == i) { + activei = new_order_idx; + /* So that we do not map again activei! */ + activei_mapping_pending = false; + } +#if 0 /* For now, do not alter active element, even if it will be hidden... */ + else if (activei < i) { + /* We do not want an active but invisible item! + * Only exception is when all items are filtered out... + */ + if (prev_order_idx >= 0) { + activei = prev_order_idx; + RNA_property_int_set(active_dataptr, activeprop, prev_i); + } + else { + activei = new_order_idx; + RNA_property_int_set(active_dataptr, activeprop, i); + } + } + prev_i = i; + prev_ii = new_order_idx; +#endif + } + i++; + } + RNA_PROP_END; + + /* If mapping is still pending, no active item was found. Mark as invalid (-1) */ + r_items->active_item_idx = activei_mapping_pending ? -1 : activei; +} + +/** + * Create the UI-list representation of the list items, sorted and filtered if needed. + */ +static void ui_template_list_collect_display_items(bContext *C, + uiList *ui_list, + TemplateListInputData *input_data, + const uiListFilterItemsFunc filter_items_fn, + TemplateListItems *r_items) +{ + uiListDyn *dyn_data = ui_list->dyn_data; + memset(r_items, 0, sizeof(*r_items)); + + /* Filter list items! (not for compact layout, though) */ + if (input_data->dataptr.data && input_data->prop) { + const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE; + const bool order_reverse = (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0; + int items_shown; +#if 0 + int prev_ii = -1, prev_i; +#endif + + if (ui_list->layout_type == UILST_LAYOUT_COMPACT) { + dyn_data->items_len = dyn_data->items_shown = RNA_property_collection_length( + &input_data->dataptr, input_data->prop); + } + else { + // printf("%s: filtering...\n", __func__); + filter_items_fn(ui_list, C, &input_data->dataptr, RNA_property_identifier(input_data->prop)); + // printf("%s: filtering done.\n", __func__); + } + + items_shown = dyn_data->items_shown; + if (items_shown >= 0) { + r_items->item_vec = static_cast<_uilist_item *>( + MEM_mallocN(sizeof(*r_items->item_vec) * items_shown, __func__)); + // printf("%s: items shown: %d.\n", __func__, items_shown); + + ui_template_list_collect_items(&input_data->dataptr, + input_data->prop, + dyn_data, + filter_exclude, + order_reverse, + input_data->active_item_idx, + r_items); + } + if (dyn_data->items_shown >= 0) { + r_items->tot_items = dyn_data->items_shown; + } + else { + r_items->tot_items = dyn_data->items_len; + } + } +} + +static void ui_template_list_free_items(TemplateListItems *items) +{ + if (items->item_vec) { + MEM_freeN(items->item_vec); + } +} + +static void uilist_prepare(uiList *ui_list, + const TemplateListItems *items, + const TemplateListLayoutDrawData *layout_data, + TemplateListVisualInfo *r_visual_info) +{ + uiListDyn *dyn_data = ui_list->dyn_data; + const bool use_auto_size = (ui_list->list_grip < + (layout_data->rows - UI_LIST_AUTO_SIZE_THRESHOLD)); + + int actual_rows = layout_data->rows; + int actual_maxrows = layout_data->maxrows; + int columns = layout_data->columns; + + /* default rows */ + if (actual_rows <= 0) { + actual_rows = 5; + } + dyn_data->visual_height_min = actual_rows; + if (actual_maxrows < actual_rows) { + actual_maxrows = max_ii(actual_rows, 5); + } + if (columns <= 0) { + columns = 9; + } + + int activei_row; + if (columns > 1) { + dyn_data->height = (int)ceil((double)items->tot_items / (double)columns); + activei_row = (int)floor((double)items->active_item_idx / (double)columns); + } + else { + dyn_data->height = items->tot_items; + activei_row = items->active_item_idx; + } + + dyn_data->columns = columns; + + if (!use_auto_size) { + /* No auto-size, yet we clamp at min size! */ + actual_rows = max_ii(ui_list->list_grip, actual_rows); + } + else if ((actual_rows != actual_maxrows) && (dyn_data->height > actual_rows)) { + /* Expand size if needed and possible. */ + actual_rows = min_ii(dyn_data->height, actual_maxrows); + } + + /* If list length changes or list is tagged to check this, + * and active is out of view, scroll to it. */ + if ((ui_list->list_last_len != items->tot_items) || + (ui_list->flag & UILST_SCROLL_TO_ACTIVE_ITEM)) { + if (activei_row < ui_list->list_scroll) { + ui_list->list_scroll = activei_row; + } + else if (activei_row >= ui_list->list_scroll + actual_rows) { + ui_list->list_scroll = activei_row - actual_rows + 1; + } + ui_list->flag &= ~UILST_SCROLL_TO_ACTIVE_ITEM; + } + + const int max_scroll = max_ii(0, dyn_data->height - actual_rows); + CLAMP(ui_list->list_scroll, 0, max_scroll); + ui_list->list_last_len = items->tot_items; + dyn_data->visual_height = actual_rows; + r_visual_info->visual_items = actual_rows * columns; + r_visual_info->start_idx = ui_list->list_scroll * columns; + r_visual_info->end_idx = min_ii(r_visual_info->start_idx + actual_rows * columns, + items->tot_items); +} + +static void uilist_resize_update_cb(bContext *C, void *arg1, void *UNUSED(arg2)) +{ + uiList *ui_list = static_cast<uiList *>(arg1); + uiListDyn *dyn_data = ui_list->dyn_data; + + /* This way we get diff in number of additional items to show (positive) or hide (negative). */ + const int diff = round_fl_to_int((float)(dyn_data->resize - dyn_data->resize_prev) / + (float)UI_UNIT_Y); + + if (diff != 0) { + ui_list->list_grip += diff; + dyn_data->resize_prev += diff * UI_UNIT_Y; + ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; + } + + /* In case uilist is in popup, we need special refreshing */ + ED_region_tag_refresh_ui(CTX_wm_menu(C)); +} + +static void *uilist_item_use_dynamic_tooltip(PointerRNA *itemptr, const char *propname) +{ + if (propname && propname[0] && itemptr && itemptr->data) { + PropertyRNA *prop = RNA_struct_find_property(itemptr, propname); + + if (prop && (RNA_property_type(prop) == PROP_STRING)) { + return RNA_property_string_get_alloc(itemptr, prop, nullptr, 0, nullptr); + } + } + return nullptr; +} + +static char *uilist_item_tooltip_func(bContext *UNUSED(C), void *argN, const char *tip) +{ + char *dyn_tooltip = static_cast<char *>(argN); + return BLI_sprintfN("%s - %s", tip, dyn_tooltip); +} + +/** + * \note Note that \a layout_type may be null. + */ +static uiList *ui_list_ensure(bContext *C, + uiListType *ui_list_type, + const char *list_id, + int layout_type, + bool sort_reverse, + bool sort_lock) +{ + /* Allows to work in popups. */ + ARegion *region = CTX_wm_menu(C); + if (region == nullptr) { + region = CTX_wm_region(C); + } + + /* Find or add the uiList to the current Region. */ + + char full_list_id[UI_MAX_NAME_STR]; + WM_uilisttype_to_full_list_id(ui_list_type, list_id, full_list_id); + + uiList *ui_list = static_cast<uiList *>( + BLI_findstring(®ion->ui_lists, full_list_id, offsetof(uiList, list_id))); + + if (!ui_list) { + ui_list = static_cast<uiList *>(MEM_callocN(sizeof(uiList), "uiList")); + BLI_strncpy(ui_list->list_id, full_list_id, sizeof(ui_list->list_id)); + BLI_addtail(®ion->ui_lists, ui_list); + ui_list->list_grip = -UI_LIST_AUTO_SIZE_THRESHOLD; /* Force auto size by default. */ + if (sort_reverse) { + ui_list->filter_sort_flag |= UILST_FLT_SORT_REVERSE; + } + if (sort_lock) { + ui_list->filter_sort_flag |= UILST_FLT_SORT_LOCK; + } + } + + if (!ui_list->dyn_data) { + ui_list->dyn_data = static_cast<uiListDyn *>( + MEM_callocN(sizeof(uiListDyn), "uiList.dyn_data")); + } + uiListDyn *dyn_data = ui_list->dyn_data; + /* Note that this isn't a `uiListType` callback, it's stored in the runtime list data. Otherwise + * the runtime data could leak when the type is unregistered (e.g. on "Reload Scripts"). */ + dyn_data->free_runtime_data_fn = uilist_free_dyn_data; + + /* Because we can't actually pass type across save&load... */ + ui_list->type = ui_list_type; + ui_list->layout_type = layout_type; + + /* Reset filtering data. */ + MEM_SAFE_FREE(dyn_data->items_filter_flags); + MEM_SAFE_FREE(dyn_data->items_filter_neworder); + dyn_data->items_len = dyn_data->items_shown = -1; + + return ui_list; +} + +static void ui_template_list_layout_draw(bContext *C, + uiList *ui_list, + uiLayout *layout, + TemplateListInputData *input_data, + TemplateListItems *items, + const TemplateListLayoutDrawData *layout_data, + const enum uiTemplateListFlags flags) +{ + uiListDyn *dyn_data = ui_list->dyn_data; + const char *active_propname = RNA_property_identifier(input_data->activeprop); + + uiLayout *glob = nullptr, *box, *row, *col, *subrow, *sub, *overlap; + char numstr[32]; + int rnaicon = ICON_NONE, icon = ICON_NONE; + uiBut *but; + + uiBlock *block = uiLayoutGetBlock(layout); + + /* get icon */ + if (input_data->dataptr.data && input_data->prop) { + StructRNA *ptype = RNA_property_pointer_type(&input_data->dataptr, input_data->prop); + rnaicon = RNA_struct_ui_icon(ptype); + } + + TemplateListVisualInfo visual_info; + switch (ui_list->layout_type) { + case UILST_LAYOUT_DEFAULT: { + /* layout */ + box = uiLayoutListBox(layout, ui_list, &input_data->active_dataptr, input_data->activeprop); + glob = uiLayoutColumn(box, true); + row = uiLayoutRow(glob, false); + col = uiLayoutColumn(row, true); + + TemplateListLayoutDrawData adjusted_layout_data = *layout_data; + adjusted_layout_data.columns = 1; + /* init numbers */ + uilist_prepare(ui_list, items, &adjusted_layout_data, &visual_info); + + int i = 0; + if (input_data->dataptr.data && input_data->prop) { + /* create list items */ + for (i = visual_info.start_idx; i < visual_info.end_idx; i++) { + PointerRNA *itemptr = &items->item_vec[i].item; + void *dyntip_data; + const int org_i = items->item_vec[i].org_idx; + const int flt_flag = items->item_vec[i].flt_flag; + uiBlock *subblock = uiLayoutGetBlock(col); + + overlap = uiLayoutOverlap(col); + + UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM); + + /* list item behind label & other buttons */ + uiLayoutRow(overlap, false); + + but = uiDefButR_prop(subblock, + UI_BTYPE_LISTROW, + 0, + "", + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + &input_data->active_dataptr, + input_data->activeprop, + 0, + 0, + org_i, + 0, + 0, + TIP_("Double click to rename")); + if ((dyntip_data = uilist_item_use_dynamic_tooltip(itemptr, + input_data->item_dyntip_propname))) { + UI_but_func_tooltip_set(but, uilist_item_tooltip_func, dyntip_data, MEM_freeN); + } + + sub = uiLayoutRow(overlap, false); + + icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); + if (icon == ICON_DOT) { + icon = ICON_NONE; + } + layout_data->draw_item(ui_list, + C, + sub, + &input_data->dataptr, + itemptr, + icon, + &input_data->active_dataptr, + active_propname, + org_i, + flt_flag); + + /* Items should be able to set context pointers for the layout. But the list-row button + * swallows events, so it needs the context storage too for handlers to see it. */ + but->context = uiLayoutGetContextStore(sub); + + /* If we are "drawing" active item, set all labels as active. */ + if (i == items->active_item_idx) { + ui_layout_list_set_labels_active(sub); + } + + UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM); + } + } + + /* add dummy buttons to fill space */ + for (; i < visual_info.start_idx + visual_info.visual_items; i++) { + uiItemL(col, "", ICON_NONE); + } + + /* add scrollbar */ + if (items->tot_items > visual_info.visual_items) { + uiLayoutColumn(row, false); + uiDefButI(block, + UI_BTYPE_SCROLL, + 0, + "", + 0, + 0, + V2D_SCROLL_WIDTH, + UI_UNIT_Y * dyn_data->visual_height, + &ui_list->list_scroll, + 0, + dyn_data->height - dyn_data->visual_height, + dyn_data->visual_height, + 0, + ""); + } + } break; + case UILST_LAYOUT_COMPACT: + row = uiLayoutRow(layout, true); + + if ((input_data->dataptr.data && input_data->prop) && (dyn_data->items_shown > 0) && + (items->active_item_idx >= 0) && (items->active_item_idx < dyn_data->items_shown)) { + PointerRNA *itemptr = &items->item_vec[items->active_item_idx].item; + const int org_i = items->item_vec[items->active_item_idx].org_idx; + + icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); + if (icon == ICON_DOT) { + icon = ICON_NONE; + } + layout_data->draw_item(ui_list, + C, + row, + &input_data->dataptr, + itemptr, + icon, + &input_data->active_dataptr, + active_propname, + org_i, + 0); + } + /* if list is empty, add in dummy button */ + else { + uiItemL(row, "", ICON_NONE); + } + + /* next/prev button */ + BLI_snprintf(numstr, sizeof(numstr), "%d :", dyn_data->items_shown); + but = uiDefIconTextButR_prop(block, + UI_BTYPE_NUM, + 0, + 0, + numstr, + 0, + 0, + UI_UNIT_X * 5, + UI_UNIT_Y, + &input_data->active_dataptr, + input_data->activeprop, + 0, + 0, + 0, + 0, + 0, + ""); + if (dyn_data->items_shown == 0) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + break; + case UILST_LAYOUT_GRID: { + box = uiLayoutListBox(layout, ui_list, &input_data->active_dataptr, input_data->activeprop); + glob = uiLayoutColumn(box, true); + row = uiLayoutRow(glob, false); + col = uiLayoutColumn(row, true); + subrow = nullptr; /* Quite gcc warning! */ + + uilist_prepare(ui_list, items, layout_data, &visual_info); + + int i = 0; + if (input_data->dataptr.data && input_data->prop) { + /* create list items */ + for (i = visual_info.start_idx; i < visual_info.end_idx; i++) { + PointerRNA *itemptr = &items->item_vec[i].item; + const int org_i = items->item_vec[i].org_idx; + const int flt_flag = items->item_vec[i].flt_flag; + + /* create button */ + if (!(i % layout_data->columns)) { + subrow = uiLayoutRow(col, false); + } + + uiBlock *subblock = uiLayoutGetBlock(subrow); + overlap = uiLayoutOverlap(subrow); + + UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM); + + /* list item behind label & other buttons */ + uiLayoutRow(overlap, false); + + but = uiDefButR_prop(subblock, + UI_BTYPE_LISTROW, + 0, + "", + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + &input_data->active_dataptr, + input_data->activeprop, + 0, + 0, + org_i, + 0, + 0, + nullptr); + UI_but_drawflag_enable(but, UI_BUT_NO_TOOLTIP); + + sub = uiLayoutRow(overlap, false); + + icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); + layout_data->draw_item(ui_list, + C, + sub, + &input_data->dataptr, + itemptr, + icon, + &input_data->active_dataptr, + active_propname, + org_i, + flt_flag); + + /* If we are "drawing" active item, set all labels as active. */ + if (i == items->active_item_idx) { + ui_layout_list_set_labels_active(sub); + } + + UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM); + } + } + + /* add dummy buttons to fill space */ + for (; i < visual_info.start_idx + visual_info.visual_items; i++) { + if (!(i % layout_data->columns)) { + subrow = uiLayoutRow(col, false); + } + uiItemL(subrow, "", ICON_NONE); + } + + /* add scrollbar */ + if (items->tot_items > visual_info.visual_items) { + /* col = */ uiLayoutColumn(row, false); + uiDefButI(block, + UI_BTYPE_SCROLL, + 0, + "", + 0, + 0, + V2D_SCROLL_WIDTH, + UI_UNIT_Y * dyn_data->visual_height, + &ui_list->list_scroll, + 0, + dyn_data->height - dyn_data->visual_height, + dyn_data->visual_height, + 0, + ""); + } + break; + } + case UILST_LAYOUT_BIG_PREVIEW_GRID: + box = uiLayoutListBox(layout, ui_list, &input_data->active_dataptr, input_data->activeprop); + /* For grip button. */ + glob = uiLayoutColumn(box, true); + /* For scrollbar. */ + row = uiLayoutRow(glob, false); + + const bool show_names = (flags & UI_TEMPLATE_LIST_NO_NAMES) == 0; + + /* TODO ED_fileselect_init_layout(). Share somehow? */ + float size_x = (96.0f / 20.0f) * UI_UNIT_X; + float size_y = (96.0f / 20.0f) * UI_UNIT_Y; + + if (!show_names) { + size_y -= UI_UNIT_Y; + } + + const int cols_per_row = MAX2((uiLayoutGetWidth(box) - V2D_SCROLL_WIDTH) / size_x, 1); + uiLayout *grid = uiLayoutGridFlow(row, true, cols_per_row, true, true, true); + + TemplateListLayoutDrawData adjusted_layout_data = *layout_data; + adjusted_layout_data.columns = cols_per_row; + uilist_prepare(ui_list, items, &adjusted_layout_data, &visual_info); + + if (input_data->dataptr.data && input_data->prop) { + /* create list items */ + for (int i = visual_info.start_idx; i < visual_info.end_idx; i++) { + PointerRNA *itemptr = &items->item_vec[i].item; + const int org_i = items->item_vec[i].org_idx; + const int flt_flag = items->item_vec[i].flt_flag; + + overlap = uiLayoutOverlap(grid); + col = uiLayoutColumn(overlap, false); + + uiBlock *subblock = uiLayoutGetBlock(col); + UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM); + + but = uiDefButR_prop(subblock, + UI_BTYPE_LISTROW, + 0, + "", + 0, + 0, + size_x, + size_y, + &input_data->active_dataptr, + input_data->activeprop, + 0, + 0, + org_i, + 0, + 0, + nullptr); + UI_but_drawflag_enable(but, UI_BUT_NO_TOOLTIP); + + col = uiLayoutColumn(overlap, false); + + icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); + layout_data->draw_item(ui_list, + C, + col, + &input_data->dataptr, + itemptr, + icon, + &input_data->active_dataptr, + active_propname, + org_i, + flt_flag); + + /* Items should be able to set context pointers for the layout. But the list-row button + * swallows events, so it needs the context storage too for handlers to see it. */ + but->context = uiLayoutGetContextStore(col); + + /* If we are "drawing" active item, set all labels as active. */ + if (i == items->active_item_idx) { + ui_layout_list_set_labels_active(col); + } + + UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM); + } + } + + if (items->tot_items > visual_info.visual_items) { + /* col = */ uiLayoutColumn(row, false); + uiDefButI(block, + UI_BTYPE_SCROLL, + 0, + "", + 0, + 0, + V2D_SCROLL_WIDTH, + size_y * dyn_data->visual_height, + &ui_list->list_scroll, + 0, + dyn_data->height - dyn_data->visual_height, + dyn_data->visual_height, + 0, + ""); + } + break; + } + + const bool add_filters_but = (flags & UI_TEMPLATE_LIST_NO_FILTER_OPTIONS) == 0; + if (glob && add_filters_but) { + const bool add_grip_but = (flags & UI_TEMPLATE_LIST_NO_GRIP) == 0; + + /* About #UI_BTYPE_GRIP drag-resize: + * We can't directly use results from a grip button, since we have a + * rather complex behavior here (sizing by discrete steps and, overall, auto-size feature). + * Since we *never* know whether we are grip-resizing or not + * (because there is no callback for when a button enters/leaves its "edit mode"), + * we use the fact that grip-controlled value (dyn_data->resize) is completely handled + * by the grip during the grab resize, so settings its value here has no effect at all. + * + * It is only meaningful when we are not resizing, + * in which case this gives us the correct "init drag" value. + * Note we cannot affect `dyn_data->resize_prev here`, + * since this value is not controlled by the grip! + */ + dyn_data->resize = dyn_data->resize_prev + + (dyn_data->visual_height - ui_list->list_grip) * UI_UNIT_Y; + + row = uiLayoutRow(glob, true); + uiBlock *subblock = uiLayoutGetBlock(row); + UI_block_emboss_set(subblock, UI_EMBOSS_NONE); + + if (ui_list->filter_flag & UILST_FLT_SHOW) { + but = uiDefIconButBitI(subblock, + UI_BTYPE_TOGGLE, + UILST_FLT_SHOW, + 0, + ICON_DISCLOSURE_TRI_DOWN, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y * 0.5f, + &(ui_list->filter_flag), + 0, + 0, + 0, + 0, + TIP_("Hide filtering options")); + UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */ + + if (add_grip_but) { + but = uiDefIconButI(subblock, + UI_BTYPE_GRIP, + 0, + ICON_GRIP, + 0, + 0, + UI_UNIT_X * 10.0f, + UI_UNIT_Y * 0.5f, + &dyn_data->resize, + 0.0, + 0.0, + 0, + 0, + ""); + UI_but_func_set(but, uilist_resize_update_cb, ui_list, nullptr); + } + + UI_block_emboss_set(subblock, UI_EMBOSS); + + col = uiLayoutColumn(glob, false); + subblock = uiLayoutGetBlock(col); + uiDefBut(subblock, + UI_BTYPE_SEPR, + 0, + "", + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y * 0.05f, + nullptr, + 0.0, + 0.0, + 0, + 0, + ""); + + layout_data->draw_filter(ui_list, C, col); + } + else { + but = uiDefIconButBitI(subblock, + UI_BTYPE_TOGGLE, + UILST_FLT_SHOW, + 0, + ICON_DISCLOSURE_TRI_RIGHT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y * 0.5f, + &(ui_list->filter_flag), + 0, + 0, + 0, + 0, + TIP_("Show filtering options")); + UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */ + + if (add_grip_but) { + but = uiDefIconButI(subblock, + UI_BTYPE_GRIP, + 0, + ICON_GRIP, + 0, + 0, + UI_UNIT_X * 10.0f, + UI_UNIT_Y * 0.5f, + &dyn_data->resize, + 0.0, + 0.0, + 0, + 0, + ""); + UI_but_func_set(but, uilist_resize_update_cb, ui_list, nullptr); + } + + UI_block_emboss_set(subblock, UI_EMBOSS); + } + } +} + +uiList *uiTemplateList_ex(uiLayout *layout, + bContext *C, + const char *listtype_name, + const char *list_id, + PointerRNA *dataptr, + const char *propname, + PointerRNA *active_dataptr, + const char *active_propname, + const char *item_dyntip_propname, + int rows, + int maxrows, + int layout_type, + int columns, + enum uiTemplateListFlags flags, + void *customdata) +{ + TemplateListInputData input_data = {{nullptr}}; + uiListType *ui_list_type; + if (!ui_template_list_data_retrieve(listtype_name, + list_id, + dataptr, + propname, + active_dataptr, + active_propname, + item_dyntip_propname, + &input_data, + &ui_list_type)) { + return nullptr; + } + + uiListDrawItemFunc draw_item = ui_list_type->draw_item ? ui_list_type->draw_item : + uilist_draw_item_default; + uiListDrawFilterFunc draw_filter = ui_list_type->draw_filter ? ui_list_type->draw_filter : + uilist_draw_filter_default; + uiListFilterItemsFunc filter_items = ui_list_type->filter_items ? ui_list_type->filter_items : + uilist_filter_items_default; + + uiList *ui_list = ui_list_ensure(C, + ui_list_type, + list_id, + layout_type, + flags & UI_TEMPLATE_LIST_SORT_REVERSE, + flags & UI_TEMPLATE_LIST_SORT_LOCK); + uiListDyn *dyn_data = ui_list->dyn_data; + + MEM_SAFE_FREE(dyn_data->customdata); + dyn_data->customdata = customdata; + + /* When active item changed since last draw, scroll to it. */ + if (input_data.active_item_idx != ui_list->list_last_activei) { + ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; + ui_list->list_last_activei = input_data.active_item_idx; + } + + TemplateListItems items; + ui_template_list_collect_display_items(C, ui_list, &input_data, filter_items, &items); + + TemplateListLayoutDrawData layout_data; + layout_data.draw_item = draw_item; + layout_data.draw_filter = draw_filter; + layout_data.rows = rows; + layout_data.maxrows = maxrows; + layout_data.columns = columns; + + ui_template_list_layout_draw(C, ui_list, layout, &input_data, &items, &layout_data, flags); + + ui_template_list_free_items(&items); + + return ui_list; +} + +void uiTemplateList(uiLayout *layout, + bContext *C, + const char *listtype_name, + const char *list_id, + PointerRNA *dataptr, + const char *propname, + PointerRNA *active_dataptr, + const char *active_propname, + const char *item_dyntip_propname, + int rows, + int maxrows, + int layout_type, + int columns, + enum uiTemplateListFlags flags) +{ + uiTemplateList_ex(layout, + C, + listtype_name, + list_id, + dataptr, + propname, + active_dataptr, + active_propname, + item_dyntip_propname, + rows, + maxrows, + layout_type, + columns, + flags, + nullptr); +} + +/** + * \return: A RNA pointer for the operator properties. + */ +PointerRNA *UI_list_custom_activate_operator_set(uiList *ui_list, + const char *opname, + bool create_properties) +{ + uiListDyn *dyn_data = ui_list->dyn_data; + dyn_data->custom_activate_optype = WM_operatortype_find(opname, false); + if (!dyn_data->custom_activate_optype) { + return nullptr; + } + + if (create_properties) { + PointerRNA *opptr = dyn_data->custom_activate_opptr; + WM_operator_properties_alloc( + &dyn_data->custom_activate_opptr, opptr ? (IDProperty **)&opptr->data : nullptr, opname); + } + + return dyn_data->custom_activate_opptr; +} + +/** + * \return: A RNA pointer for the operator properties. + */ +PointerRNA *UI_list_custom_drag_operator_set(uiList *ui_list, + const char *opname, + bool create_properties) +{ + uiListDyn *dyn_data = ui_list->dyn_data; + dyn_data->custom_drag_optype = WM_operatortype_find(opname, false); + if (!dyn_data->custom_drag_optype) { + return nullptr; + } + + if (create_properties) { + PointerRNA *opptr = dyn_data->custom_drag_opptr; + WM_operator_properties_alloc( + &dyn_data->custom_drag_opptr, opptr ? (IDProperty **)&opptr->data : nullptr, opname); + } + + return dyn_data->custom_drag_opptr; +} + +/* -------------------------------------------------------------------- */ + +/** \name List-types Registration + * \{ */ + +void ED_uilisttypes_ui(void) +{ + WM_uilisttype_add(UI_UL_asset_view()); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c index 91ad6619889..26250e105eb 100644 --- a/source/blender/editors/interface/interface_template_search_menu.c +++ b/source/blender/editors/interface/interface_template_search_menu.c @@ -69,9 +69,6 @@ /** \name Menu Search Template Implementation * \{ */ -/* Unicode arrow. */ -#define MENU_SEP "\xe2\x96\xb6" - /** * Use when #menu_items_from_ui_create is called with `include_all_areas`. * so we can run the menu item in the area it was extracted from. @@ -118,7 +115,7 @@ struct MenuSearch_Item { struct { wmOperatorType *type; PointerRNA *opptr; - short opcontext; + wmOperatorCallContext opcontext; bContextStore *context; } op; @@ -350,24 +347,28 @@ static void menu_types_add_from_keymap_items(bContext *C, if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) { wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base; - wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler); - if (keymap && WM_keymap_poll(C, keymap)) { - LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { - if (kmi->flag & KMI_INACTIVE) { - continue; - } - if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) { - char menu_idname[MAX_NAME]; - RNA_string_get(kmi->ptr, "name", menu_idname); - MenuType *mt = WM_menutype_find(menu_idname, false); - - if (mt && BLI_gset_add(menu_tagged, mt)) { - /* Unlikely, but possible this will be included twice. */ - BLI_linklist_prepend(menuid_stack_p, mt); - - void **kmi_p; - if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) { - *kmi_p = kmi; + wmEventHandler_KeymapResult km_result; + WM_event_get_keymaps_from_handler(wm, win, handler, &km_result); + for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) { + wmKeyMap *keymap = km_result.keymaps[km_index]; + if (keymap && WM_keymap_poll(C, keymap)) { + LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { + if (kmi->flag & KMI_INACTIVE) { + continue; + } + if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) { + char menu_idname[MAX_NAME]; + RNA_string_get(kmi->ptr, "name", menu_idname); + MenuType *mt = WM_menutype_find(menu_idname, false); + + if (mt && BLI_gset_add(menu_tagged, mt)) { + /* Unlikely, but possible this will be included twice. */ + BLI_linklist_prepend(menuid_stack_p, mt); + + void **kmi_p; + if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) { + *kmi_p = kmi; + } } } } @@ -411,7 +412,7 @@ static void menu_items_from_all_operators(bContext *C, struct MenuSearch_Data *d char uiname[256]; WM_operator_py_idname(idname_as_py, ot->idname); - SNPRINTF(uiname, "%s " MENU_SEP "%s", idname_as_py, ot_ui_name); + SNPRINTF(uiname, "%s " UI_MENU_ARROW_SEP "%s", idname_as_py, ot_ui_name); item->drawwstr_full = strdup_memarena(memarena, uiname); item->drawstr = ot_ui_name; @@ -837,7 +838,7 @@ static struct MenuSearch_Data *menu_items_from_ui_create( } while (menu_parent) { BLI_dynstr_append(dyn_str, menu_parent->drawstr); - BLI_dynstr_append(dyn_str, " " MENU_SEP " "); + BLI_dynstr_append(dyn_str, " " UI_MENU_ARROW_SEP " "); menu_parent = menu_parent->temp_child; } } @@ -855,13 +856,13 @@ static struct MenuSearch_Data *menu_items_from_ui_create( BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str); } - BLI_dynstr_append(dyn_str, " " MENU_SEP " "); + BLI_dynstr_append(dyn_str, " " UI_MENU_ARROW_SEP " "); } /* Optional nested menu. */ if (item->drawstr_submenu != NULL) { BLI_dynstr_append(dyn_str, item->drawstr_submenu); - BLI_dynstr_append(dyn_str, " " MENU_SEP " "); + BLI_dynstr_append(dyn_str, " " UI_MENU_ARROW_SEP " "); } BLI_dynstr_append(dyn_str, item->drawstr); @@ -873,7 +874,7 @@ static struct MenuSearch_Data *menu_items_from_ui_create( /* Finally sort menu items. * - * Note: we might want to keep the in-menu order, for now sort all. */ + * NOTE: we might want to keep the in-menu order, for now sort all. */ BLI_listbase_sort(&data->items, menu_item_sort_by_drawstr_full); BLI_ghash_free(menu_parent_map, NULL, NULL); @@ -955,7 +956,8 @@ static void menu_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) switch (item->type) { case MENU_SEARCH_TYPE_OP: { CTX_store_set(C, item->op.context); - WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr); + WM_operator_name_call_ptr_with_depends_on_cursor( + C, item->op.type, item->op.opcontext, item->op.opptr, item->drawstr); CTX_store_set(C, NULL); break; } @@ -1037,7 +1039,7 @@ static void menu_search_update_fn(const bContext *UNUSED(C), static bool ui_search_menu_create_context_menu(struct bContext *C, void *arg, void *active, - const struct wmEvent *UNUSED(event)) + const struct wmEvent *event) { struct MenuSearch_Data *data = arg; struct MenuSearch_Item *item = active; @@ -1058,7 +1060,7 @@ static bool ui_search_menu_create_context_menu(struct bContext *C, CTX_wm_region_set(C, item->wm_context->region); } - if (ui_popup_context_menu_for_button(C, but)) { + if (ui_popup_context_menu_for_button(C, but, event)) { has_menu = true; } @@ -1097,8 +1099,8 @@ static struct ARegion *ui_search_menu_create_tooltip(struct bContext *C, /* Place the fake button at the cursor so the tool-tip is places properly. */ float tip_init[2]; const wmEvent *event = CTX_wm_window(C)->eventstate; - tip_init[0] = event->x; - tip_init[1] = event->y - (UI_UNIT_Y / 2); + tip_init[0] = event->xy[0]; + tip_init[1] = event->xy[1] - (UI_UNIT_Y / 2); ui_window_to_block_fl(region, block, &tip_init[0], &tip_init[1]); but->rect.xmin = tip_init[0]; @@ -1155,7 +1157,7 @@ void UI_but_func_menu_search(uiBut *but) UI_but_func_search_set_context_menu(but, ui_search_menu_create_context_menu); UI_but_func_search_set_tooltip(but, ui_search_menu_create_tooltip); - UI_but_func_search_set_sep_string(but, MENU_SEP); + UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP); } void uiTemplateMenuSearch(uiLayout *layout) @@ -1172,6 +1174,4 @@ void uiTemplateMenuSearch(uiLayout *layout) UI_but_func_menu_search(but); } -#undef MENU_SEP - /** \} */ diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 2c58277293d..b30a86c5fcf 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -84,6 +84,8 @@ #include "ED_screen.h" #include "ED_undo.h" +#include "RE_engine.h" + #include "RNA_access.h" #include "WM_api.h" @@ -395,7 +397,7 @@ static bool id_search_add(const bContext *C, TemplateID *template_ui, uiSearchIt 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->lib != NULL); + const bool has_sep_char = ID_IS_LINKED(id); /* When using previews, the library hint (linked, overridden, missing) is added with a * character prefix, otherwise we can use a icon. */ @@ -671,8 +673,8 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) } } else { - if (BKE_lib_id_make_local(bmain, id, false, 0)) { - BKE_main_id_newptr_and_tag_clear(bmain); + if (BKE_lib_id_make_local(bmain, id, 0)) { + BKE_id_newptr_and_tag_clear(id); /* Reassign to get proper updates/notifiers. */ idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); @@ -877,7 +879,7 @@ static uiBut *template_id_def_new_but(uiBlock *block, BLT_I18NCONTEXT_ID_POINTCLOUD, BLT_I18NCONTEXT_ID_VOLUME, BLT_I18NCONTEXT_ID_SIMULATION, ); - /* Note: BLT_I18N_MSGID_MULTI_CTXT takes a maximum number of parameters, + /* NOTE: BLT_I18N_MSGID_MULTI_CTXT takes a maximum number of parameters, * check the definition to see if a new call must be added when the limit * is exceeded. */ @@ -1029,7 +1031,7 @@ static void template_ID(const bContext *C, UI_but_flag_enable(but, UI_BUT_DISABLED); } else { - const bool disabled = (!BKE_lib_id_make_local(CTX_data_main(C), id, true /* test */, 0) || + const bool disabled = (!BKE_idtype_idcode_is_localizable(GS(id->name)) || (idfrom && idfrom->lib)); but = uiDefIconBut(block, UI_BTYPE_BUT, @@ -1110,24 +1112,41 @@ static void template_ID(const bContext *C, UI_but_flag_enable(but, UI_BUT_REDALERT); } - if (id->lib == NULL && !(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_OB, ID_WS)) && - (hide_buttons == false)) { - uiDefIconButR(block, - UI_BTYPE_ICON_TOGGLE, - 0, - ICON_FAKE_USER_OFF, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - &idptr, - "use_fake_user", - -1, - 0, - 0, - -1, - -1, - NULL); + if (!ID_IS_LINKED(id)) { + if (ID_IS_ASSET(id)) { + uiDefIconButO(block, + /* 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, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + } + else if (!(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_OB, ID_WS)) && + (hide_buttons == false)) { + uiDefIconButR(block, + UI_BTYPE_ICON_TOGGLE, + 0, + ICON_FAKE_USER_OFF, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + &idptr, + "use_fake_user", + -1, + 0, + 0, + -1, + -1, + NULL); + } } } @@ -1722,7 +1741,7 @@ static void template_search_add_button_name(uiBlock *block, static void template_search_add_button_operator(uiBlock *block, const char *const operator_name, - const int opcontext, + const wmOperatorCallContext opcontext, const int icon, const bool editable) { @@ -2362,7 +2381,7 @@ static eAutoPropButsReturn template_operator_property_buts_draw_single( /* poll() on this operator may still fail, * at the moment there is no nice feedback when this happens just fails silently. */ if (!WM_operator_repeat_check(C, op)) { - UI_block_lock_set(block, true, "Operator can't' redo"); + UI_block_lock_set(block, true, "Operator can't redo"); return return_info; } @@ -2399,8 +2418,8 @@ static eAutoPropButsReturn template_operator_property_buts_draw_single( op->type->ui((bContext *)C, op); op->layout = NULL; - /* UI_LAYOUT_OP_SHOW_EMPTY ignored. retun_info is ignored too. We could - * allow ot.ui callback to return this, but not needed right now. */ + /* #UI_LAYOUT_OP_SHOW_EMPTY ignored. retun_info is ignored too. + * We could allow #wmOperatorType.ui callback to return this, but not needed right now. */ } else { wmWindowManager *wm = CTX_wm_manager(C); @@ -2556,7 +2575,7 @@ void uiTemplateOperatorPropertyButs( wmWindowManager *wm = CTX_wm_manager(C); /* If there are only checkbox items, don't use split layout by default. It looks weird if the - * checkboxes only use half the width. */ + * check-boxes only use half the width. */ if (ui_layout_operator_properties_only_booleans(C, wm, op, flag)) { flag |= UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT; } @@ -2621,6 +2640,72 @@ static void constraint_active_func(bContext *UNUSED(C), void *ob_v, void *con_v) ED_object_constraint_active_set(ob_v, con_v); } +static void constraint_ops_extra_draw(bContext *C, uiLayout *layout, void *con_v) +{ + PointerRNA op_ptr; + uiLayout *row; + bConstraint *con = (bConstraint *)con_v; + + PointerRNA ptr; + Object *ob = ED_object_active_context(C); + + RNA_pointer_create(&ob->id, &RNA_Constraint, con, &ptr); + uiLayoutSetContextPointer(layout, "constraint", &ptr); + uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); + + uiLayoutSetUnitsX(layout, 4.0f); + + /* Apply. */ + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Apply"), + ICON_CHECKMARK, + "CONSTRAINT_OT_apply"); + + /* Duplicate. */ + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Duplicate"), + ICON_DUPLICATE, + "CONSTRAINT_OT_copy"); + + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_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, + WM_OP_INVOKE_DEFAULT, + 0, + &op_ptr); + RNA_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, + WM_OP_INVOKE_DEFAULT, + 0, + &op_ptr); + ListBase *constraint_list = ED_object_constraint_list_from_constraint(ob, con, NULL); + RNA_int_set(&op_ptr, "index", BLI_listbase_count(constraint_list) - 1); + if (!con->next) { + uiLayoutSetEnabled(row, false); + } +} + static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *con) { bPoseChannel *pchan = BKE_pose_channel_active(ob); @@ -2652,11 +2737,13 @@ static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *co UI_block_emboss_set(block, UI_EMBOSS); + uiLayout *row = uiLayoutRow(layout, true); + if (proxy_protected == 0) { - uiItemR(layout, &ptr, "name", 0, "", ICON_NONE); + uiItemR(row, &ptr, "name", 0, "", ICON_NONE); } else { - uiItemL(layout, con->name, ICON_NONE); + uiItemL(row, IFACE_(con->name), ICON_NONE); } /* proxy-protected constraints cannot be edited, so hide up/down + close buttons */ @@ -2697,22 +2784,22 @@ static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *co UI_block_emboss_set(block, UI_EMBOSS); } else { - /* enabled */ - UI_block_emboss_set(block, UI_EMBOSS_NONE_OR_STATUS); - uiItemR(layout, &ptr, "mute", 0, "", 0); - UI_block_emboss_set(block, UI_EMBOSS); + /* Enabled eye icon. */ + uiItemR(row, &ptr, "enabled", 0, "", ICON_NONE); - uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); + /* Extra operators menu. */ + uiItemMenuF(row, "", ICON_DOWNARROW_HLT, constraint_ops_extra_draw, con); /* Close 'button' - emboss calls here disable drawing of 'button' behind X */ - UI_block_emboss_set(block, UI_EMBOSS_NONE); - uiItemO(layout, "", ICON_X, "CONSTRAINT_OT_delete"); - UI_block_emboss_set(block, UI_EMBOSS); - - /* Some extra padding at the end, so the 'x' icon isn't too close to drag button. */ - uiItemS(layout); + sub = uiLayoutRow(row, false); + uiLayoutSetEmboss(sub, UI_EMBOSS_NONE); + uiLayoutSetOperatorContext(sub, WM_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); + /* Set but-locks for protected settings (magic numbers are used here!) */ if (proxy_protected) { UI_block_lock_set(block, true, TIP_("Cannot edit Proxy-Protected Constraint")); @@ -4015,23 +4102,23 @@ static void curvemap_tools_dofunc(bContext *C, void *cumap_v, int event) case UICURVE_FUNC_RESET_VIEW: BKE_curvemapping_reset_view(cumap); break; - case UICURVE_FUNC_HANDLE_VECTOR: /* set vector */ + case UICURVE_FUNC_HANDLE_VECTOR: /* Set vector. */ BKE_curvemap_handle_set(cuma, HD_VECT); BKE_curvemapping_changed(cumap, false); break; - case UICURVE_FUNC_HANDLE_AUTO: /* set auto */ + case UICURVE_FUNC_HANDLE_AUTO: /* Set auto. */ BKE_curvemap_handle_set(cuma, HD_AUTO); BKE_curvemapping_changed(cumap, false); break; - case UICURVE_FUNC_HANDLE_AUTO_ANIM: /* set auto-clamped */ + case UICURVE_FUNC_HANDLE_AUTO_ANIM: /* Set auto-clamped. */ BKE_curvemap_handle_set(cuma, HD_AUTO_ANIM); BKE_curvemapping_changed(cumap, false); break; - case UICURVE_FUNC_EXTEND_HOZ: /* extend horiz */ + case UICURVE_FUNC_EXTEND_HOZ: /* Extend horizontal. */ cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE; BKE_curvemapping_changed(cumap, false); break; - case UICURVE_FUNC_EXTEND_EXP: /* extend extrapolate */ + case UICURVE_FUNC_EXTEND_EXP: /* Extend extrapolate. */ cumap->flag |= CUMA_EXTEND_EXTRAPOLATE; BKE_curvemapping_changed(cumap, false); break; @@ -5645,887 +5732,6 @@ void uiTemplateLayers(uiLayout *layout, /** \} */ /* -------------------------------------------------------------------- */ -/** \name List Template - * \{ */ - -static void uilist_draw_item_default(struct uiList *ui_list, - struct bContext *UNUSED(C), - struct uiLayout *layout, - struct PointerRNA *UNUSED(dataptr), - struct PointerRNA *itemptr, - int icon, - struct PointerRNA *UNUSED(active_dataptr), - const char *UNUSED(active_propname), - int UNUSED(index), - int UNUSED(flt_flag)) -{ - PropertyRNA *nameprop = RNA_struct_name_property(itemptr->type); - - /* Simplest one! */ - switch (ui_list->layout_type) { - case UILST_LAYOUT_GRID: - uiItemL(layout, "", icon); - break; - case UILST_LAYOUT_DEFAULT: - case UILST_LAYOUT_COMPACT: - default: - if (nameprop) { - uiItemFullR(layout, itemptr, nameprop, RNA_NO_INDEX, 0, UI_ITEM_R_NO_BG, "", icon); - } - else { - uiItemL(layout, "", icon); - } - break; - } -} - -static void uilist_draw_filter_default(struct uiList *ui_list, - struct bContext *UNUSED(C), - struct uiLayout *layout) -{ - PointerRNA listptr; - RNA_pointer_create(NULL, &RNA_UIList, ui_list, &listptr); - - uiLayout *row = uiLayoutRow(layout, false); - - uiLayout *subrow = uiLayoutRow(row, true); - uiItemR(subrow, &listptr, "filter_name", 0, "", ICON_NONE); - uiItemR(subrow, - &listptr, - "use_filter_invert", - UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, - "", - ICON_ARROW_LEFTRIGHT); - - if ((ui_list->filter_sort_flag & UILST_FLT_SORT_LOCK) == 0) { - subrow = uiLayoutRow(row, true); - uiItemR(subrow, - &listptr, - "use_filter_sort_alpha", - UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, - "", - ICON_NONE); - uiItemR(subrow, - &listptr, - "use_filter_sort_reverse", - UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, - "", - (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) ? ICON_SORT_DESC : ICON_SORT_ASC); - } -} - -typedef struct { - char name[MAX_IDPROP_NAME]; - int org_idx; -} StringCmp; - -static int cmpstringp(const void *p1, const void *p2) -{ - /* Case-insensitive comparison. */ - return BLI_strcasecmp(((StringCmp *)p1)->name, ((StringCmp *)p2)->name); -} - -static void uilist_filter_items_default(struct uiList *ui_list, - struct bContext *UNUSED(C), - struct PointerRNA *dataptr, - const char *propname) -{ - uiListDyn *dyn_data = ui_list->dyn_data; - PropertyRNA *prop = RNA_struct_find_property(dataptr, propname); - - const char *filter_raw = ui_list->filter_byname; - char *filter = (char *)filter_raw, filter_buff[32], *filter_dyn = NULL; - const bool filter_exclude = (ui_list->filter_flag & UILST_FLT_EXCLUDE) != 0; - const bool order_by_name = (ui_list->filter_sort_flag & UILST_FLT_SORT_MASK) == - UILST_FLT_SORT_ALPHA; - const int len = RNA_property_collection_length(dataptr, prop); - - dyn_data->items_shown = dyn_data->items_len = len; - - if (len && (order_by_name || filter_raw[0])) { - StringCmp *names = NULL; - int order_idx = 0, i = 0; - - if (order_by_name) { - names = MEM_callocN(sizeof(StringCmp) * len, "StringCmp"); - } - if (filter_raw[0]) { - const size_t slen = strlen(filter_raw); - - dyn_data->items_filter_flags = MEM_callocN(sizeof(int) * len, "items_filter_flags"); - dyn_data->items_shown = 0; - - /* Implicitly add heading/trailing wildcards if needed. */ - if (slen + 3 <= sizeof(filter_buff)) { - filter = filter_buff; - } - else { - filter = filter_dyn = MEM_mallocN((slen + 3) * sizeof(char), "filter_dyn"); - } - BLI_strncpy_ensure_pad(filter, filter_raw, '*', slen + 3); - } - - RNA_PROP_BEGIN (dataptr, itemptr, prop) { - bool do_order = false; - - char *namebuf = RNA_struct_name_get_alloc(&itemptr, NULL, 0, NULL); - const char *name = namebuf ? namebuf : ""; - - if (filter[0]) { - /* Case-insensitive! */ - if (fnmatch(filter, name, FNM_CASEFOLD) == 0) { - dyn_data->items_filter_flags[i] = UILST_FLT_ITEM; - if (!filter_exclude) { - dyn_data->items_shown++; - do_order = order_by_name; - } - // printf("%s: '%s' matches '%s'\n", __func__, name, filter); - } - else if (filter_exclude) { - dyn_data->items_shown++; - do_order = order_by_name; - } - } - else { - do_order = order_by_name; - } - - if (do_order) { - names[order_idx].org_idx = order_idx; - BLI_strncpy(names[order_idx++].name, name, MAX_IDPROP_NAME); - } - - /* free name */ - if (namebuf) { - MEM_freeN(namebuf); - } - i++; - } - RNA_PROP_END; - - if (order_by_name) { - int new_idx; - /* note: order_idx equals either to ui_list->items_len if no filtering done, - * or to ui_list->items_shown if filter is enabled, - * or to (ui_list->items_len - ui_list->items_shown) if filtered items are excluded. - * This way, we only sort items we actually intend to draw! - */ - qsort(names, order_idx, sizeof(StringCmp), cmpstringp); - - dyn_data->items_filter_neworder = MEM_mallocN(sizeof(int) * order_idx, - "items_filter_neworder"); - for (new_idx = 0; new_idx < order_idx; new_idx++) { - dyn_data->items_filter_neworder[names[new_idx].org_idx] = new_idx; - } - } - - if (filter_dyn) { - MEM_freeN(filter_dyn); - } - if (names) { - MEM_freeN(names); - } - } -} - -typedef struct { - PointerRNA item; - int org_idx; - int flt_flag; -} _uilist_item; - -typedef struct { - int visual_items; /* Visual number of items (i.e. number of items we have room to display). */ - int start_idx; /* Index of first item to display. */ - int end_idx; /* Index of last item to display + 1. */ -} uiListLayoutdata; - -static void uilist_prepare(uiList *ui_list, - int len, - int activei, - int rows, - int maxrows, - int columns, - uiListLayoutdata *layoutdata) -{ - uiListDyn *dyn_data = ui_list->dyn_data; - const bool use_auto_size = (ui_list->list_grip < (rows - UI_LIST_AUTO_SIZE_THRESHOLD)); - - /* default rows */ - if (rows <= 0) { - rows = 5; - } - dyn_data->visual_height_min = rows; - if (maxrows < rows) { - maxrows = max_ii(rows, 5); - } - if (columns <= 0) { - columns = 9; - } - - int activei_row; - if (columns > 1) { - dyn_data->height = (int)ceil((double)len / (double)columns); - activei_row = (int)floor((double)activei / (double)columns); - } - else { - dyn_data->height = len; - activei_row = activei; - } - - if (!use_auto_size) { - /* No auto-size, yet we clamp at min size! */ - maxrows = rows = max_ii(ui_list->list_grip, rows); - } - else if ((rows != maxrows) && (dyn_data->height > rows)) { - /* Expand size if needed and possible. */ - rows = min_ii(dyn_data->height, maxrows); - } - - /* If list length changes or list is tagged to check this, - * and active is out of view, scroll to it. */ - if (ui_list->list_last_len != len || ui_list->flag & UILST_SCROLL_TO_ACTIVE_ITEM) { - if (activei_row < ui_list->list_scroll) { - ui_list->list_scroll = activei_row; - } - else if (activei_row >= ui_list->list_scroll + rows) { - ui_list->list_scroll = activei_row - rows + 1; - } - ui_list->flag &= ~UILST_SCROLL_TO_ACTIVE_ITEM; - } - - const int max_scroll = max_ii(0, dyn_data->height - rows); - CLAMP(ui_list->list_scroll, 0, max_scroll); - ui_list->list_last_len = len; - dyn_data->visual_height = rows; - layoutdata->visual_items = rows * columns; - layoutdata->start_idx = ui_list->list_scroll * columns; - layoutdata->end_idx = min_ii(layoutdata->start_idx + rows * columns, len); -} - -static void uilist_resize_update_cb(bContext *C, void *arg1, void *UNUSED(arg2)) -{ - uiList *ui_list = arg1; - uiListDyn *dyn_data = ui_list->dyn_data; - - /* This way we get diff in number of additional items to show (positive) or hide (negative). */ - const int diff = round_fl_to_int((float)(dyn_data->resize - dyn_data->resize_prev) / - (float)UI_UNIT_Y); - - if (diff != 0) { - ui_list->list_grip += diff; - dyn_data->resize_prev += diff * UI_UNIT_Y; - ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; - } - - /* In case uilist is in popup, we need special refreshing */ - ED_region_tag_refresh_ui(CTX_wm_menu(C)); -} - -static void *uilist_item_use_dynamic_tooltip(PointerRNA *itemptr, const char *propname) -{ - if (propname && propname[0] && itemptr && itemptr->data) { - PropertyRNA *prop = RNA_struct_find_property(itemptr, propname); - - if (prop && (RNA_property_type(prop) == PROP_STRING)) { - return RNA_property_string_get_alloc(itemptr, prop, NULL, 0, NULL); - } - } - return NULL; -} - -static char *uilist_item_tooltip_func(bContext *UNUSED(C), void *argN, const char *tip) -{ - char *dyn_tooltip = argN; - return BLI_sprintfN("%s - %s", tip, dyn_tooltip); -} - -void uiTemplateList(uiLayout *layout, - bContext *C, - const char *listtype_name, - const char *list_id, - PointerRNA *dataptr, - const char *propname, - PointerRNA *active_dataptr, - const char *active_propname, - const char *item_dyntip_propname, - int rows, - int maxrows, - int layout_type, - int columns, - bool sort_reverse, - bool sort_lock) -{ - PropertyRNA *prop = NULL, *activeprop; - _uilist_item *items_ptr = NULL; - uiLayout *glob = NULL, *box, *row, *col, *subrow, *sub, *overlap; - uiBut *but; - - uiListLayoutdata layoutdata; - char ui_list_id[UI_MAX_NAME_STR]; - char numstr[32]; - int rnaicon = ICON_NONE, icon = ICON_NONE; - int i = 0, activei = 0; - int len = 0; - - /* validate arguments */ - /* Forbid default UI_UL_DEFAULT_CLASS_NAME list class without a custom list_id! */ - if (STREQ(UI_UL_DEFAULT_CLASS_NAME, listtype_name) && !(list_id && list_id[0])) { - RNA_warning("template_list using default '%s' UIList class must provide a custom list_id", - UI_UL_DEFAULT_CLASS_NAME); - return; - } - - uiBlock *block = uiLayoutGetBlock(layout); - - if (!active_dataptr->data) { - RNA_warning("No active data"); - return; - } - - if (dataptr->data) { - prop = RNA_struct_find_property(dataptr, propname); - if (!prop) { - RNA_warning("Property not found: %s.%s", RNA_struct_identifier(dataptr->type), propname); - return; - } - } - - activeprop = RNA_struct_find_property(active_dataptr, active_propname); - if (!activeprop) { - RNA_warning( - "Property not found: %s.%s", RNA_struct_identifier(active_dataptr->type), active_propname); - return; - } - - if (prop) { - const PropertyType type = RNA_property_type(prop); - if (type != PROP_COLLECTION) { - RNA_warning("Expected a collection data property"); - return; - } - } - - const PropertyType activetype = RNA_property_type(activeprop); - if (activetype != PROP_INT) { - RNA_warning("Expected an integer active data property"); - return; - } - - /* get icon */ - if (dataptr->data && prop) { - StructRNA *ptype = RNA_property_pointer_type(dataptr, prop); - rnaicon = RNA_struct_ui_icon(ptype); - } - - /* get active data */ - activei = RNA_property_int_get(active_dataptr, activeprop); - - /* Find the uiList type. */ - uiListType *ui_list_type = WM_uilisttype_find(listtype_name, false); - - if (ui_list_type == NULL) { - RNA_warning("List type %s not found", listtype_name); - return; - } - - uiListDrawItemFunc draw_item = ui_list_type->draw_item ? ui_list_type->draw_item : - uilist_draw_item_default; - uiListDrawFilterFunc draw_filter = ui_list_type->draw_filter ? ui_list_type->draw_filter : - uilist_draw_filter_default; - uiListFilterItemsFunc filter_items = ui_list_type->filter_items ? ui_list_type->filter_items : - uilist_filter_items_default; - - /* Find or add the uiList to the current Region. */ - /* We tag the list id with the list type... */ - BLI_snprintf( - ui_list_id, sizeof(ui_list_id), "%s_%s", ui_list_type->idname, list_id ? list_id : ""); - - /* Allows to work in popups. */ - ARegion *region = CTX_wm_menu(C); - if (region == NULL) { - region = CTX_wm_region(C); - } - uiList *ui_list = BLI_findstring(®ion->ui_lists, ui_list_id, offsetof(uiList, list_id)); - - if (!ui_list) { - ui_list = MEM_callocN(sizeof(uiList), "uiList"); - BLI_strncpy(ui_list->list_id, ui_list_id, sizeof(ui_list->list_id)); - BLI_addtail(®ion->ui_lists, ui_list); - ui_list->list_grip = -UI_LIST_AUTO_SIZE_THRESHOLD; /* Force auto size by default. */ - if (sort_reverse) { - ui_list->filter_sort_flag |= UILST_FLT_SORT_REVERSE; - } - if (sort_lock) { - ui_list->filter_sort_flag |= UILST_FLT_SORT_LOCK; - } - } - - if (!ui_list->dyn_data) { - ui_list->dyn_data = MEM_callocN(sizeof(uiListDyn), "uiList.dyn_data"); - } - uiListDyn *dyn_data = ui_list->dyn_data; - - /* Because we can't actually pass type across save&load... */ - ui_list->type = ui_list_type; - ui_list->layout_type = layout_type; - - /* Reset filtering data. */ - MEM_SAFE_FREE(dyn_data->items_filter_flags); - MEM_SAFE_FREE(dyn_data->items_filter_neworder); - dyn_data->items_len = dyn_data->items_shown = -1; - - /* When active item changed since last draw, scroll to it. */ - if (activei != ui_list->list_last_activei) { - ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; - ui_list->list_last_activei = activei; - } - - /* Filter list items! (not for compact layout, though) */ - if (dataptr->data && prop) { - const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE; - const bool order_reverse = (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0; - int items_shown, idx = 0; -#if 0 - int prev_ii = -1, prev_i; -#endif - - if (layout_type == UILST_LAYOUT_COMPACT) { - dyn_data->items_len = dyn_data->items_shown = RNA_property_collection_length(dataptr, prop); - } - else { - // printf("%s: filtering...\n", __func__); - filter_items(ui_list, C, dataptr, propname); - // printf("%s: filtering done.\n", __func__); - } - - items_shown = dyn_data->items_shown; - if (items_shown >= 0) { - bool activei_mapping_pending = true; - items_ptr = MEM_mallocN(sizeof(_uilist_item) * items_shown, __func__); - // printf("%s: items shown: %d.\n", __func__, items_shown); - RNA_PROP_BEGIN (dataptr, itemptr, prop) { - if (!dyn_data->items_filter_flags || - ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) { - int ii; - if (dyn_data->items_filter_neworder) { - ii = dyn_data->items_filter_neworder[idx++]; - ii = order_reverse ? items_shown - ii - 1 : ii; - } - else { - ii = order_reverse ? items_shown - ++idx : idx++; - } - // printf("%s: ii: %d\n", __func__, ii); - items_ptr[ii].item = itemptr; - items_ptr[ii].org_idx = i; - items_ptr[ii].flt_flag = dyn_data->items_filter_flags ? dyn_data->items_filter_flags[i] : - 0; - - if (activei_mapping_pending && activei == i) { - activei = ii; - /* So that we do not map again activei! */ - activei_mapping_pending = false; - } -#if 0 /* For now, do not alter active element, even if it will be hidden... */ - else if (activei < i) { - /* We do not want an active but invisible item! - * Only exception is when all items are filtered out... - */ - if (prev_ii >= 0) { - activei = prev_ii; - RNA_property_int_set(active_dataptr, activeprop, prev_i); - } - else { - activei = ii; - RNA_property_int_set(active_dataptr, activeprop, i); - } - } - prev_i = i; - prev_ii = ii; -#endif - } - i++; - } - RNA_PROP_END; - - if (activei_mapping_pending) { - /* No active item found, set to 'invalid' -1 value... */ - activei = -1; - } - } - if (dyn_data->items_shown >= 0) { - len = dyn_data->items_shown; - } - else { - len = dyn_data->items_len; - } - } - - switch (layout_type) { - case UILST_LAYOUT_DEFAULT: - /* layout */ - box = uiLayoutListBox(layout, ui_list, active_dataptr, activeprop); - glob = uiLayoutColumn(box, true); - row = uiLayoutRow(glob, false); - col = uiLayoutColumn(row, true); - - /* init numbers */ - uilist_prepare(ui_list, len, activei, rows, maxrows, 1, &layoutdata); - - if (dataptr->data && prop) { - /* create list items */ - for (i = layoutdata.start_idx; i < layoutdata.end_idx; i++) { - PointerRNA *itemptr = &items_ptr[i].item; - void *dyntip_data; - const int org_i = items_ptr[i].org_idx; - const int flt_flag = items_ptr[i].flt_flag; - uiBlock *subblock = uiLayoutGetBlock(col); - - overlap = uiLayoutOverlap(col); - - UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM); - - /* list item behind label & other buttons */ - sub = uiLayoutRow(overlap, false); - - but = uiDefButR_prop(subblock, - UI_BTYPE_LISTROW, - 0, - "", - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - active_dataptr, - activeprop, - 0, - 0, - org_i, - 0, - 0, - TIP_("Double click to rename")); - if ((dyntip_data = uilist_item_use_dynamic_tooltip(itemptr, item_dyntip_propname))) { - UI_but_func_tooltip_set(but, uilist_item_tooltip_func, dyntip_data); - } - - sub = uiLayoutRow(overlap, false); - - icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); - if (icon == ICON_DOT) { - icon = ICON_NONE; - } - draw_item(ui_list, - C, - sub, - dataptr, - itemptr, - icon, - active_dataptr, - active_propname, - org_i, - flt_flag); - - /* Items should be able to set context pointers for the layout. But the list-row button - * swallows events, so it needs the context storage too for handlers to see it. */ - but->context = uiLayoutGetContextStore(sub); - - /* If we are "drawing" active item, set all labels as active. */ - if (i == activei) { - ui_layout_list_set_labels_active(sub); - } - - UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM); - } - } - - /* add dummy buttons to fill space */ - for (; i < layoutdata.start_idx + layoutdata.visual_items; i++) { - uiItemL(col, "", ICON_NONE); - } - - /* add scrollbar */ - if (len > layoutdata.visual_items) { - col = uiLayoutColumn(row, false); - uiDefButI(block, - UI_BTYPE_SCROLL, - 0, - "", - 0, - 0, - V2D_SCROLL_WIDTH, - UI_UNIT_Y * dyn_data->visual_height, - &ui_list->list_scroll, - 0, - dyn_data->height - dyn_data->visual_height, - dyn_data->visual_height, - 0, - ""); - } - break; - case UILST_LAYOUT_COMPACT: - row = uiLayoutRow(layout, true); - - if ((dataptr->data && prop) && (dyn_data->items_shown > 0) && (activei >= 0) && - (activei < dyn_data->items_shown)) { - PointerRNA *itemptr = &items_ptr[activei].item; - const int org_i = items_ptr[activei].org_idx; - - icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); - if (icon == ICON_DOT) { - icon = ICON_NONE; - } - draw_item( - ui_list, C, row, dataptr, itemptr, icon, active_dataptr, active_propname, org_i, 0); - } - /* if list is empty, add in dummy button */ - else { - uiItemL(row, "", ICON_NONE); - } - - /* next/prev button */ - BLI_snprintf(numstr, sizeof(numstr), "%d :", dyn_data->items_shown); - but = uiDefIconTextButR_prop(block, - UI_BTYPE_NUM, - 0, - 0, - numstr, - 0, - 0, - UI_UNIT_X * 5, - UI_UNIT_Y, - active_dataptr, - activeprop, - 0, - 0, - 0, - 0, - 0, - ""); - if (dyn_data->items_shown == 0) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - break; - case UILST_LAYOUT_GRID: - box = uiLayoutListBox(layout, ui_list, active_dataptr, activeprop); - glob = uiLayoutColumn(box, true); - row = uiLayoutRow(glob, false); - col = uiLayoutColumn(row, true); - subrow = NULL; /* Quite gcc warning! */ - - uilist_prepare(ui_list, len, activei, rows, maxrows, columns, &layoutdata); - - if (dataptr->data && prop) { - /* create list items */ - for (i = layoutdata.start_idx; i < layoutdata.end_idx; i++) { - PointerRNA *itemptr = &items_ptr[i].item; - const int org_i = items_ptr[i].org_idx; - const int flt_flag = items_ptr[i].flt_flag; - - /* create button */ - if (!(i % columns)) { - subrow = uiLayoutRow(col, false); - } - - uiBlock *subblock = uiLayoutGetBlock(subrow); - overlap = uiLayoutOverlap(subrow); - - UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM); - - /* list item behind label & other buttons */ - sub = uiLayoutRow(overlap, false); - - but = uiDefButR_prop(subblock, - UI_BTYPE_LISTROW, - 0, - "", - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - active_dataptr, - activeprop, - 0, - 0, - org_i, - 0, - 0, - NULL); - UI_but_drawflag_enable(but, UI_BUT_NO_TOOLTIP); - - sub = uiLayoutRow(overlap, false); - - icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); - draw_item(ui_list, - C, - sub, - dataptr, - itemptr, - icon, - active_dataptr, - active_propname, - org_i, - flt_flag); - - /* If we are "drawing" active item, set all labels as active. */ - if (i == activei) { - ui_layout_list_set_labels_active(sub); - } - - UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM); - } - } - - /* add dummy buttons to fill space */ - for (; i < layoutdata.start_idx + layoutdata.visual_items; i++) { - if (!(i % columns)) { - subrow = uiLayoutRow(col, false); - } - uiItemL(subrow, "", ICON_NONE); - } - - /* add scrollbar */ - if (len > layoutdata.visual_items) { - /* col = */ uiLayoutColumn(row, false); - uiDefButI(block, - UI_BTYPE_SCROLL, - 0, - "", - 0, - 0, - V2D_SCROLL_WIDTH, - UI_UNIT_Y * dyn_data->visual_height, - &ui_list->list_scroll, - 0, - dyn_data->height - dyn_data->visual_height, - dyn_data->visual_height, - 0, - ""); - } - break; - } - - if (glob) { - /* About #UI_BTYPE_GRIP drag-resize: - * We can't directly use results from a grip button, since we have a - * rather complex behavior here (sizing by discrete steps and, overall, auto-size feature). - * Since we *never* know whether we are grip-resizing or not - * (because there is no callback for when a button enters/leaves its "edit mode"), - * we use the fact that grip-controlled value (dyn_data->resize) is completely handled - * by the grip during the grab resize, so settings its value here has no effect at all. - * - * It is only meaningful when we are not resizing, - * in which case this gives us the correct "init drag" value. - * Note we cannot affect `dyn_data->resize_prev here`, - * since this value is not controlled by the grip! - */ - dyn_data->resize = dyn_data->resize_prev + - (dyn_data->visual_height - ui_list->list_grip) * UI_UNIT_Y; - - row = uiLayoutRow(glob, true); - uiBlock *subblock = uiLayoutGetBlock(row); - UI_block_emboss_set(subblock, UI_EMBOSS_NONE); - - if (ui_list->filter_flag & UILST_FLT_SHOW) { - but = uiDefIconButBitI(subblock, - UI_BTYPE_TOGGLE, - UILST_FLT_SHOW, - 0, - ICON_DISCLOSURE_TRI_DOWN, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y * 0.5f, - &(ui_list->filter_flag), - 0, - 0, - 0, - 0, - TIP_("Hide filtering options")); - UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */ - - but = uiDefIconButI(subblock, - UI_BTYPE_GRIP, - 0, - ICON_GRIP, - 0, - 0, - UI_UNIT_X * 10.0f, - UI_UNIT_Y * 0.5f, - &dyn_data->resize, - 0.0, - 0.0, - 0, - 0, - ""); - UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL); - - UI_block_emboss_set(subblock, UI_EMBOSS); - - col = uiLayoutColumn(glob, false); - subblock = uiLayoutGetBlock(col); - uiDefBut(subblock, - UI_BTYPE_SEPR, - 0, - "", - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y * 0.05f, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); - - draw_filter(ui_list, C, col); - } - else { - but = uiDefIconButBitI(subblock, - UI_BTYPE_TOGGLE, - UILST_FLT_SHOW, - 0, - ICON_DISCLOSURE_TRI_RIGHT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y * 0.5f, - &(ui_list->filter_flag), - 0, - 0, - 0, - 0, - TIP_("Show filtering options")); - UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */ - - but = uiDefIconButI(subblock, - UI_BTYPE_GRIP, - 0, - ICON_GRIP, - 0, - 0, - UI_UNIT_X * 10.0f, - UI_UNIT_Y * 0.5f, - &dyn_data->resize, - 0.0, - 0.0, - 0, - 0, - ""); - UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL); - - UI_block_emboss_set(subblock, UI_EMBOSS); - } - } - - if (items_ptr) { - MEM_freeN(items_ptr); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ /** \name Running Jobs Template * \{ */ @@ -6634,6 +5840,11 @@ void uiTemplateRunningJobs(uiLayout *layout, bContext *C) icon = ICON_SEQUENCE; break; } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL)) { + handle_event = B_STOPSEQ; + icon = ICON_SEQUENCE; + break; + } if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_BUILD_PROXY)) { handle_event = B_STOPCLIP; icon = ICON_TRACKER; @@ -6762,7 +5973,7 @@ void uiTemplateRunningJobs(uiLayout *layout, bContext *C) NULL); but_progress->progress = progress; - UI_but_func_tooltip_set(&but_progress->but, progress_tooltip_func, tip_arg); + UI_but_func_tooltip_set(&but_progress->but, progress_tooltip_func, tip_arg, MEM_freeN); } if (!wm->is_interface_locked) { @@ -6931,8 +6142,8 @@ void uiTemplateInputStatus(uiLayout *layout, struct bContext *C) uiLayout *row = uiLayoutRow(col, true); uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_LEFT); - const char *msg = WM_window_cursor_keymap_status_get(win, i, 0); - const char *msg_drag = WM_window_cursor_keymap_status_get(win, i, 1); + const char *msg = TIP_(WM_window_cursor_keymap_status_get(win, i, 0)); + const char *msg_drag = TIP_(WM_window_cursor_keymap_status_get(win, i, 1)); if (msg || (msg_drag == NULL)) { uiItemL(row, msg ? msg : "", (ICON_MOUSE_LMB + i)); @@ -7276,6 +6487,44 @@ void uiTemplateCacheFile(uiLayout *layout, row = uiLayoutRow(layout, false); uiItemR(row, &fileptr, "is_sequence", 0, NULL, ICON_NONE); + /* Only enable render procedural option if the active engine supports it. */ + const struct RenderEngineType *engine_type = CTX_data_engine_type(C); + + Scene *scene = CTX_data_scene(C); + const bool engine_supports_procedural = RE_engine_supports_alembic_procedural(engine_type, + scene); + + if (!engine_supports_procedural) { + row = uiLayoutRow(layout, false); + /* For Cycles, verify that experimental features are enabled. */ + if (BKE_scene_uses_cycles(scene) && !BKE_scene_uses_cycles_experimental_features(scene)) { + uiItemL( + row, + TIP_( + "The Cycles Alembic Procedural is only available with the experimental feature set"), + ICON_INFO); + } + else { + uiItemL( + row, TIP_("The active render engine does not have an Alembic Procedural"), ICON_INFO); + } + } + + row = uiLayoutRow(layout, false); + uiLayoutSetActive(row, engine_supports_procedural); + uiItemR(row, &fileptr, "use_render_procedural", 0, NULL, ICON_NONE); + + const bool use_render_procedural = RNA_boolean_get(&fileptr, "use_render_procedural"); + const bool use_prefetch = RNA_boolean_get(&fileptr, "use_prefetch"); + + row = uiLayoutRow(layout, false); + uiLayoutSetEnabled(row, use_render_procedural); + uiItemR(row, &fileptr, "use_prefetch", 0, NULL, ICON_NONE); + + sub = uiLayoutRow(layout, false); + uiLayoutSetEnabled(sub, use_prefetch && use_render_procedural); + uiItemR(sub, &fileptr, "prefetch_cache_size", 0, NULL, ICON_NONE); + row = uiLayoutRowWithHeading(layout, true, IFACE_("Override Frame")); sub = uiLayoutRow(row, true); uiLayoutSetPropDecorate(sub, false); diff --git a/source/blender/editors/interface/interface_utils.c b/source/blender/editors/interface/interface_utils.c index 6ad1de68a1f..1a41dc8e9fb 100644 --- a/source/blender/editors/interface/interface_utils.c +++ b/source/blender/editors/interface/interface_utils.c @@ -29,6 +29,8 @@ #include "DNA_object_types.h" #include "DNA_screen_types.h" +#include "ED_screen.h" + #include "BLI_alloca.h" #include "BLI_listbase.h" #include "BLI_math.h" @@ -38,6 +40,7 @@ #include "BLT_translation.h" +#include "BKE_context.h" #include "BKE_lib_id.h" #include "BKE_report.h" @@ -48,6 +51,7 @@ #include "UI_interface.h" #include "UI_interface_icons.h" #include "UI_resources.h" +#include "UI_view2d.h" #include "WM_api.h" #include "WM_types.h" @@ -531,7 +535,7 @@ void ui_rna_collection_search_update_fn(const struct bContext *C, BLI_STATIC_ASSERT(sizeof(name_buf) >= MAX_ID_FULL_NAME_UI, "Name string buffer should be big enough to hold full UI ID name"); name = name_buf; - has_sep_char = (id->lib != NULL); + has_sep_char = ID_IS_LINKED(id); } } else { @@ -701,7 +705,7 @@ int UI_calc_float_precision(int prec, double value) /* Check on the number of decimal places need to display the number, * this is so 0.00001 is not displayed as 0.00, - * _but_, this is only for small values si 10.0001 will not get the same treatment. + * _but_, this is only for small values as 10.0001 will not get the same treatment. */ value = fabs(value); if ((value < pow10_neg[prec]) && (value > (1.0 / max_pow))) { @@ -774,6 +778,98 @@ bool UI_but_online_manual_id_from_active(const struct bContext *C, char *r_str, } /* -------------------------------------------------------------------- */ + +static rctf ui_but_rect_to_view(const uiBut *but, const ARegion *region, const View2D *v2d) +{ + rctf region_rect; + ui_block_to_region_rctf(region, but->block, ®ion_rect, &but->rect); + + rctf view_rect; + UI_view2d_region_to_view_rctf(v2d, ®ion_rect, &view_rect); + + return view_rect; +} + +/** + * To get a margin (typically wanted), add the margin to \a rect directly. + * + * Based on #file_ensure_inside_viewbounds(), could probably share code. + * + * \return true if anything changed. + */ +static bool ui_view2d_cur_ensure_rect_in_view(View2D *v2d, const rctf *rect) +{ + const float rect_width = BLI_rctf_size_x(rect); + const float rect_height = BLI_rctf_size_y(rect); + + rctf *cur = &v2d->cur; + const float cur_width = BLI_rctf_size_x(cur); + const float cur_height = BLI_rctf_size_y(cur); + + bool changed = false; + + /* Snap to bottom edge. Also use if rect is higher than view bounds (could be a parameter). */ + if ((cur->ymin > rect->ymin) || (rect_height > cur_height)) { + cur->ymin = rect->ymin; + cur->ymax = cur->ymin + cur_height; + changed = true; + } + /* Snap to upper edge. */ + else if (cur->ymax < rect->ymax) { + cur->ymax = rect->ymax; + cur->ymin = cur->ymax - cur_height; + changed = true; + } + /* Snap to left edge. Also use if rect is wider than view bounds. */ + else if ((cur->xmin > rect->xmin) || (rect_width > cur_width)) { + cur->xmin = rect->xmin; + cur->xmax = cur->xmin + cur_width; + changed = true; + } + /* Snap to right edge. */ + else if (cur->xmax < rect->xmax) { + cur->xmax = rect->xmax; + cur->xmin = cur->xmax - cur_width; + changed = true; + } + else { + BLI_assert(BLI_rctf_inside_rctf(cur, rect)); + } + + return changed; +} + +/** + * Adjust the view so the rectangle of \a but is in view, with some extra margin. + * + * It's important that this is only executed after buttons received their final #uiBut.rect. E.g. + * #UI_panels_end() modifies them, so if that is executed, this function must not be called before + * it. + * + * \param region: The region the button is placed in. Make sure this is actually the one the button + * is placed in, not just the context region. + */ +void UI_but_ensure_in_view(const bContext *C, ARegion *region, const uiBut *but) +{ + View2D *v2d = ®ion->v2d; + /* Uninitialized view or region that doesn't use View2D. */ + if ((v2d->flag & V2D_IS_INIT) == 0) { + return; + } + + rctf rect = ui_but_rect_to_view(but, region, v2d); + + const int margin = UI_UNIT_X * 0.5f; + BLI_rctf_pad(&rect, margin, margin); + + const bool changed = ui_view2d_cur_ensure_rect_in_view(v2d, &rect); + if (changed) { + UI_view2d_curRect_changed(C, v2d); + ED_region_tag_redraw_no_rebuild(region); + } +} + +/* -------------------------------------------------------------------- */ /** \name Button Store * * Modal Button Store API. @@ -932,7 +1028,7 @@ void UI_butstore_update(uiBlock *block) uiBut *but_new = ui_but_find_new(block, *bs_elem->but_p); /* can be NULL if the buttons removed, - * note: we could allow passing in a callback when buttons are removed + * NOTE: we could allow passing in a callback when buttons are removed * so the caller can cleanup */ *bs_elem->but_p = but_new; } diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc new file mode 100644 index 00000000000..4e38f245155 --- /dev/null +++ b/source/blender/editors/interface/interface_view.cc @@ -0,0 +1,186 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + * + * This part of the UI-View API is mostly needed to support persistent state of items within the + * view. Views are stored in #uiBlock's, and kept alive with it until after the next redraw. So we + * can compare the old view items with the new view items and keep state persistent for matching + * ones. + */ + +#include <memory> +#include <variant> + +#include "DNA_screen_types.h" + +#include "BLI_listbase.h" + +#include "interface_intern.h" + +#include "UI_interface.hh" +#include "UI_tree_view.hh" + +using namespace blender; +using namespace blender::ui; + +/** + * Wrapper to store views in a #ListBase. There's no `uiView` base class, we just store views as a + * #std::variant. + */ +struct ViewLink : public Link { + using TreeViewPtr = std::unique_ptr<AbstractTreeView>; + + std::string idname; + /* Note: Can't use std::get() on this until minimum macOS deployment target is 10.14. */ + std::variant<TreeViewPtr> view; +}; + +template<class T> T *get_view_from_link(ViewLink &link) +{ + auto *t_uptr = std::get_if<std::unique_ptr<T>>(&link.view); + return t_uptr ? t_uptr->get() : nullptr; +} + +/** + * Override this for all available tree types. + */ +AbstractTreeView *UI_block_add_view(uiBlock &block, + StringRef idname, + std::unique_ptr<AbstractTreeView> tree_view) +{ + ViewLink *view_link = OBJECT_GUARDED_NEW(ViewLink); + BLI_addtail(&block.views, view_link); + + view_link->view = std::move(tree_view); + view_link->idname = idname; + + return get_view_from_link<AbstractTreeView>(*view_link); +} + +void ui_block_free_views(uiBlock *block) +{ + LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) { + OBJECT_GUARDED_DELETE(link, ViewLink); + } +} + +/** + * \param x, y: Coordinate to find a tree-row item at, in window space. + */ +uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region, const int xy[2]) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, xy); + if (!tree_row_but) { + return nullptr; + } + + return tree_row_but->tree_item; +} + +uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const ARegion *region) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_active(region); + if (!tree_row_but) { + return nullptr; + } + + return tree_row_but->tree_item; +} + +static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractTreeView &view) +{ + /* First get the idname the of the view we're looking for. */ + LISTBASE_FOREACH (ViewLink *, view_link, &block.views) { + if (get_view_from_link<AbstractTreeView>(*view_link) == &view) { + return view_link->idname; + } + } + + return {}; +} + +static AbstractTreeView *ui_block_view_find_matching_in_old_block(const uiBlock &new_block, + const AbstractTreeView &new_view) +{ + uiBlock *old_block = new_block.oldblock; + if (!old_block) { + return nullptr; + } + + StringRef idname = ui_block_view_find_idname(new_block, new_view); + if (idname.is_empty()) { + return nullptr; + } + + LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) { + if (old_view_link->idname == idname) { + return get_view_from_link<AbstractTreeView>(*old_view_link); + } + } + + return nullptr; +} + +uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, + const uiTreeViewHandle *new_view_handle) +{ + BLI_assert(new_block && new_view_handle); + const AbstractTreeView &new_view = reinterpret_cast<const AbstractTreeView &>(*new_view_handle); + + AbstractTreeView *old_view = ui_block_view_find_matching_in_old_block(*new_block, new_view); + return reinterpret_cast<uiTreeViewHandle *>(old_view); +} + +uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block, + const uiTreeViewItemHandle *new_item_handle) +{ + uiBlock *old_block = new_block->oldblock; + if (!old_block) { + return nullptr; + } + + const AbstractTreeViewItem &new_item = *reinterpret_cast<const AbstractTreeViewItem *>( + new_item_handle); + const AbstractTreeView *old_tree_view = ui_block_view_find_matching_in_old_block( + *new_block, new_item.get_tree_view()); + if (!old_tree_view) { + return nullptr; + } + + LISTBASE_FOREACH (uiBut *, old_but, &old_block->buttons) { + if (old_but->type != UI_BTYPE_TREEROW) { + continue; + } + uiButTreeRow *old_treerow_but = (uiButTreeRow *)old_but; + if (!old_treerow_but->tree_item) { + continue; + } + AbstractTreeViewItem &old_item = *reinterpret_cast<AbstractTreeViewItem *>( + old_treerow_but->tree_item); + /* Check if the row is from the expected tree-view. */ + if (&old_item.get_tree_view() != old_tree_view) { + continue; + } + + if (UI_tree_view_item_matches(new_item_handle, old_treerow_but->tree_item)) { + return old_treerow_but; + } + } + + return nullptr; +} diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index 91a4c430023..7d1b7b80ccd 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -105,6 +105,7 @@ typedef enum { /* specials */ UI_WTYPE_ICON, UI_WTYPE_ICON_LABEL, + UI_WTYPE_PREVIEW_TILE, UI_WTYPE_SWATCH, UI_WTYPE_RGB_PICKER, UI_WTYPE_UNITVEC, @@ -114,6 +115,7 @@ typedef enum { UI_WTYPE_PROGRESSBAR, UI_WTYPE_NODESOCKET, UI_WTYPE_DATASETROW, + UI_WTYPE_TREEROW, } uiWidgetTypeEnum; /* Button state argument shares bits with 'uiBut.flag'. @@ -237,7 +239,7 @@ typedef struct uiWidgetTrias { #define WIDGET_SIZE_MAX (WIDGET_CURVE_RESOLU * 4) typedef struct uiWidgetBase { - /* TODO remove these completely */ + /* TODO: remove these completely. */ int totvert, halfwayvert; float outer_v[WIDGET_SIZE_MAX][2]; float inner_v[WIDGET_SIZE_MAX][2]; @@ -399,7 +401,7 @@ static struct { GPUBatch *roundbox_widget; GPUBatch *roundbox_shadow; - /* TODO remove */ + /* TODO: remove. */ GPUVertFormat format; uint vflag_id; } g_ui_batch_cache = {0}; @@ -516,14 +518,14 @@ GPUBatch *ui_batch_roundbox_shadow_get(void) /** \name Draw Triangle Arrow * \{ */ -void UI_draw_anti_tria( +static void draw_anti_tria( float x1, float y1, float x2, float y2, float x3, float y3, const float color[4]) { const float tri_arr[3][2] = {{x1, y1}, {x2, y2}, {x3, y3}}; float draw_color[4]; copy_v4_v4(draw_color, color); - /* Note: This won't give back the original color. */ + /* NOTE: This won't give back the original color. */ draw_color[3] *= 1.0f / WIDGET_AA_JITTER; GPU_blend(GPU_BLEND_ALPHA); @@ -557,66 +559,31 @@ void UI_draw_icon_tri(float x, float y, char dir, const float color[4]) const float f7 = 0.25 * U.widget_unit; if (dir == 'h') { - UI_draw_anti_tria(x - f3, y - f5, x - f3, y + f5, x + f7, y, color); + draw_anti_tria(x - f3, y - f5, x - f3, y + f5, x + f7, y, color); } else if (dir == 't') { - UI_draw_anti_tria(x - f5, y - f7, x + f5, y - f7, x, y + f3, color); + draw_anti_tria(x - f5, y - f7, x + f5, y - f7, x, y + f3, color); } else { /* 'v' = vertical, down. */ - UI_draw_anti_tria(x - f5, y + f3, x + f5, y + f3, x, y - f7, color); + draw_anti_tria(x - f5, y + f3, x + f5, y + f3, x, y - f7, color); } } /* triangle 'icon' inside rect */ -void ui_draw_anti_tria_rect(const rctf *rect, char dir, const float color[4]) +static void draw_anti_tria_rect(const rctf *rect, char dir, const float color[4]) { if (dir == 'h') { const float half = 0.5f * BLI_rctf_size_y(rect); - UI_draw_anti_tria( + draw_anti_tria( rect->xmin, rect->ymin, rect->xmin, rect->ymax, rect->xmax, rect->ymin + half, color); } else { const float half = 0.5f * BLI_rctf_size_x(rect); - UI_draw_anti_tria( + draw_anti_tria( rect->xmin, rect->ymax, rect->xmax, rect->ymax, rect->xmin + half, rect->ymin, color); } } -void UI_draw_anti_fan(float tri_array[][2], uint length, const float color[4]) -{ - float draw_color[4]; - - copy_v4_v4(draw_color, color); - draw_color[3] *= 2.0f / WIDGET_AA_JITTER; - - GPU_blend(GPU_BLEND_ALPHA); - - const uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - immUniformColor4fv(draw_color); - - /* for each AA step */ - for (int j = 0; j < WIDGET_AA_JITTER; j++) { - immBegin(GPU_PRIM_TRI_FAN, length); - immVertex2f(pos, tri_array[0][0], tri_array[0][1]); - immVertex2f(pos, tri_array[1][0], tri_array[1][1]); - - /* We jitter only the middle of the fan, the extremes are pinned. */ - for (int i = 2; i < length - 1; i++) { - immVertex2f(pos, tri_array[i][0] + jit[j][0], tri_array[i][1] + jit[j][1]); - } - - immVertex2f(pos, tri_array[length - 1][0], tri_array[length - 1][1]); - immEnd(); - } - - immUnbindProgram(); - - GPU_blend(GPU_BLEND_NONE); -} - static void widget_init(uiWidgetBase *wtb) { wtb->totvert = wtb->halfwayvert = 0; @@ -768,7 +735,7 @@ static void round_box__edges( BLI_rctf_rcti_copy(&wt->uniform_params.rect, rect); BLI_rctf_init(&wt->uniform_params.recti, minxi, maxxi, minyi, maxyi); - /* mult */ + /* Multiply by radius. */ for (int a = 0; a < WIDGET_CURVE_RESOLU; a++) { veci[a][0] = radi * cornervec[a][0]; veci[a][1] = radi * cornervec[a][1]; @@ -1377,8 +1344,6 @@ static int ui_but_draw_menu_icon(const uiBut *but) static void widget_draw_icon( const uiBut *but, BIFIconID icon, float alpha, const rcti *rect, const uchar mono_color[4]) { - float xs = 0.0f, ys = 0.0f; - if (but->flag & UI_BUT_ICON_PREVIEW) { GPU_blend(GPU_BLEND_ALPHA); widget_draw_preview(icon, alpha, rect); @@ -1420,6 +1385,7 @@ static void widget_draw_icon( if (icon && icon != ICON_BLANK1) { const float ofs = 1.0f / aspect; + float xs, ys; if (but->drawflag & UI_BUT_ICON_LEFT) { /* special case - icon_only pie buttons */ @@ -1441,8 +1407,8 @@ static void widget_draw_icon( /* force positions to integers, for zoom levels near 1. draws icons crisp. */ if (aspect > 0.95f && aspect < 1.05f) { - xs = (int)(xs + 0.1f); - ys = (int)(ys + 0.1f); + xs = roundf(xs); + ys = roundf(ys); } /* Get theme color. */ @@ -1493,20 +1459,20 @@ static void widget_draw_submenu_tria(const uiBut *but, GPU_blend(GPU_BLEND_ALPHA); UI_widgetbase_draw_cache_flush(); GPU_blend(GPU_BLEND_NONE); - ui_draw_anti_tria_rect(&tria_rect, 'h', col); + draw_anti_tria_rect(&tria_rect, 'h', col); } static void ui_text_clip_give_prev_off(uiBut *but, const char *str) { - const char *prev_utf8 = BLI_str_find_prev_char_utf8(str, str + but->ofs); + const char *prev_utf8 = BLI_str_find_prev_char_utf8(str + but->ofs, str); const int bytes = str + but->ofs - prev_utf8; but->ofs -= bytes; } -static void ui_text_clip_give_next_off(uiBut *but, const char *str) +static void ui_text_clip_give_next_off(uiBut *but, const char *str, const char *str_end) { - const char *next_utf8 = BLI_str_find_next_char_utf8(str + but->ofs, NULL); + const char *next_utf8 = BLI_str_find_next_char_utf8(str + but->ofs, str_end); const int bytes = next_utf8 - (str + but->ofs); but->ofs += bytes; @@ -1576,11 +1542,6 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle, /* need to set this first */ UI_fontstyle_set(fstyle); - if (fstyle->kerning == 1) { - /* for BLF_width */ - BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - float strwidth = BLF_width(fstyle->uifont_id, str, max_len); if ((okwidth > 0.0f) && (strwidth > okwidth)) { @@ -1641,7 +1602,7 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle, /* Corner case, the str already takes all available mem, * and the ellipsis chars would actually add more chars. * Better to just trim one or two letters to the right in this case... - * Note: with a single-char ellipsis, this should never happen! But better be safe + * NOTE: with a single-char ellipsis, this should never happen! But better be safe * here... */ ui_text_clip_right_ex( @@ -1674,10 +1635,6 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle, strwidth = BLF_width(fstyle->uifont_id, str, max_len); } - if (fstyle->kerning == 1) { - BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - BLI_assert(strwidth <= okwidth); return strwidth; @@ -1736,11 +1693,6 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct /* need to set this first */ UI_fontstyle_set(fstyle); - if (fstyle->kerning == 1) { - /* for BLF_width */ - BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - /* define ofs dynamically */ if (but->ofs > but->pos) { but->ofs = but->pos; @@ -1753,7 +1705,8 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct but->strwidth = BLF_width(fstyle->uifont_id, but->editstr + but->ofs, INT_MAX); if (but->strwidth > okwidth) { - int len = strlen(but->editstr); + const int editstr_len = strlen(but->editstr); + int len = editstr_len; while (but->strwidth > okwidth) { float width; @@ -1763,7 +1716,7 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct /* if cursor is at 20 pixels of right side button we clip left */ if (width > okwidth - 20) { - ui_text_clip_give_next_off(but, but->editstr); + ui_text_clip_give_next_off(but, but->editstr, but->editstr + editstr_len); } else { int bytes; @@ -1771,7 +1724,7 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct if (width < 20 && but->ofs > 0) { ui_text_clip_give_prev_off(but, but->editstr); } - bytes = BLI_str_utf8_size(BLI_str_find_prev_char_utf8(but->editstr, but->editstr + len)); + bytes = BLI_str_utf8_size(BLI_str_find_prev_char_utf8(but->editstr + len, but->editstr)); if (bytes == -1) { bytes = 1; } @@ -1785,10 +1738,6 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct } } } - - if (fstyle->kerning == 1) { - BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } } /** @@ -1806,11 +1755,6 @@ static void ui_text_clip_right_label(const uiFontStyle *fstyle, uiBut *but, cons /* need to set this first */ UI_fontstyle_set(fstyle); - if (fstyle->kerning == 1) { - /* for BLF_width */ - BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - but->strwidth = BLF_width(fstyle->uifont_id, but->drawstr, sizeof(but->drawstr)); but->ofs = 0; @@ -1828,7 +1772,7 @@ static void ui_text_clip_right_label(const uiFontStyle *fstyle, uiBut *but, cons /* chop off the leading text, starting from the right */ while (but->strwidth > okwidth && cp2 > but->drawstr) { - const char *prev_utf8 = BLI_str_find_prev_char_utf8(but->drawstr, cp2); + const char *prev_utf8 = BLI_str_find_prev_char_utf8(cp2, but->drawstr); const int bytes = cp2 - prev_utf8; /* shift the text after and including cp2 back by 1 char, @@ -1848,7 +1792,7 @@ static void ui_text_clip_right_label(const uiFontStyle *fstyle, uiBut *but, cons /* after the leading text is gone, chop off the : and following space, with ofs */ while ((but->strwidth > okwidth) && (but->ofs < 2)) { - ui_text_clip_give_next_off(but, but->drawstr); + ui_text_clip_give_next_off(but, but->drawstr, but->drawstr + drawstr_len); but->strwidth = BLF_width( fstyle->uifont_id, but->drawstr + but->ofs, sizeof(but->drawstr) - but->ofs); if (but->strwidth < 10) { @@ -1870,10 +1814,6 @@ static void ui_text_clip_right_label(const uiFontStyle *fstyle, uiBut *but, cons but->strwidth = strwidth; but->drawstr[drawstr_len] = 0; } - - if (fstyle->kerning == 1) { - BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } } #ifdef WITH_INPUT_IME @@ -1985,11 +1925,6 @@ static void widget_draw_text(const uiFontStyle *fstyle, align = UI_STYLE_TEXT_CENTER; } - if (fstyle->kerning == 1) { - /* for BLF_width */ - BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - /* Special case: when we're entering text for multiple buttons, * don't draw the text for any of the multi-editing buttons */ if (UNLIKELY(but->flag & UI_BUT_DRAG_MULTI)) { @@ -2006,14 +1941,15 @@ static void widget_draw_text(const uiFontStyle *fstyle, drawstr_left_len = INT_MAX; #ifdef WITH_INPUT_IME - /* FIXME, IME is modifying 'const char *drawstr! */ + /* FIXME: IME is modifying `const char *drawstr`! */ ime_data = ui_but_ime_data_get(but); if (ime_data && ime_data->composite_len) { /* insert composite string into cursor pos */ BLI_snprintf((char *)drawstr, UI_MAX_DRAW_STR, - "%s%s%s", + "%.*s%s%s", + but->pos, but->editstr, ime_data->str_composite, but->editstr + but->pos); @@ -2029,8 +1965,11 @@ static void widget_draw_text(const uiFontStyle *fstyle, /* text button selection, cursor, composite underline */ if (but->editstr && but->pos != -1) { int but_pos_ofs; - /* Shape of the cursor for drawing. */ - rcti but_cursor_shape; + +#ifdef WITH_INPUT_IME + bool ime_reposition_window = false; + int ime_win_x, ime_win_y; +#endif /* text button selection */ if ((but->selend - but->selsta) > 0) { @@ -2055,14 +1994,28 @@ static void widget_draw_text(const uiFontStyle *fstyle, immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + rcti selection_shape; + selection_shape.xmin = rect->xmin + selsta_draw; + selection_shape.xmax = min_ii(rect->xmin + selwidth_draw, rect->xmax - 2); + selection_shape.ymin = rect->ymin + U.pixelsize; + selection_shape.ymax = rect->ymax - U.pixelsize; immUniformColor4ubv(wcol->item); immRecti(pos, - rect->xmin + selsta_draw, - rect->ymin + U.pixelsize, - min_ii(rect->xmin + selwidth_draw, rect->xmax - 2), - rect->ymax - U.pixelsize); + selection_shape.xmin, + selection_shape.ymin, + selection_shape.xmax, + selection_shape.ymax); immUnbindProgram(); + +#ifdef WITH_INPUT_IME + /* IME candidate window uses selection position. */ + if (!ime_reposition_window) { + ime_reposition_window = true; + ime_win_x = selection_shape.xmin; + ime_win_y = selection_shape.ymin; + } +#endif } } @@ -2070,7 +2023,7 @@ static void widget_draw_text(const uiFontStyle *fstyle, but_pos_ofs = but->pos; #ifdef WITH_INPUT_IME - /* if is ime compositing, move the cursor */ + /* If is IME compositing, move the cursor. */ if (ime_data && ime_data->composite_len && ime_data->cursor_pos != -1) { but_pos_ofs += ime_data->cursor_pos; } @@ -2095,6 +2048,8 @@ static void widget_draw_text(const uiFontStyle *fstyle, immUniformThemeColor(TH_WIDGET_TEXT_CURSOR); + /* Shape of the cursor for drawing. */ + rcti but_cursor_shape; but_cursor_shape.xmin = (rect->xmin + t) - U.pixelsize; but_cursor_shape.ymin = rect->ymin + U.pixelsize; but_cursor_shape.xmax = (rect->xmin + t) + U.pixelsize; @@ -2108,25 +2063,29 @@ static void widget_draw_text(const uiFontStyle *fstyle, but_cursor_shape.ymax); immUnbindProgram(); - } #ifdef WITH_INPUT_IME - if (ime_data && ime_data->composite_len) { - /* ime cursor following */ - if (but->pos >= but->ofs) { - ui_but_ime_reposition(but, but_cursor_shape.xmax + 5, but_cursor_shape.ymin + 3, false); + /* IME candidate window uses cursor position. */ + if (!ime_reposition_window) { + ime_reposition_window = true; + ime_win_x = but_cursor_shape.xmax + 5; + ime_win_y = but_cursor_shape.ymin + 3; } +#endif + } - /* composite underline */ +#ifdef WITH_INPUT_IME + /* IME cursor following. */ + if (ime_reposition_window) { + ui_but_ime_reposition(but, ime_win_x, ime_win_y, false); + } + if (ime_data && ime_data->composite_len) { + /* Composite underline. */ widget_draw_text_ime_underline(fstyle, wcol, but, rect, ime_data, drawstr); } #endif } - if (fstyle->kerning == 1) { - BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - #if 0 ui_rasterpos_safe(x, y, but->aspect); transopts = ui_translate_buttons(); @@ -2204,10 +2163,6 @@ static void widget_draw_text(const uiFontStyle *fstyle, } if (ul_index != -1) { - if (fstyle->kerning == 1) { - BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - int ul_width = round_fl_to_int(BLF_width(fstyle->uifont_id, "_", 2)); struct UnderlineData ul_data = { @@ -2228,10 +2183,6 @@ static void widget_draw_text(const uiFontStyle *fstyle, BLF_position(fstyle->uifont_id, pos_x, pos_y, 0.0f); BLF_color4ubv(fstyle->uifont_id, wcol->text); BLF_draw(fstyle->uifont_id, "_", 2); - - if (fstyle->kerning == 1) { - BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } } } } @@ -2264,7 +2215,7 @@ static void widget_draw_extra_icons(const uiWidgetColors *wcol, const float icon_size = ICON_SIZE_FROM_BUTRECT(rect); /* Offset of icons from the right edge. Keep in sync - with 'ui_but_extra_operator_icon_mouse_over_get'. */ + * with 'ui_but_extra_operator_icon_mouse_over_get'. */ if (!BLI_listbase_is_empty(&but->extra_op_icons)) { /* Eyeballed. */ rect->xmax -= 0.2 * icon_size; @@ -2277,7 +2228,10 @@ static void widget_draw_extra_icons(const uiWidgetColors *wcol, temp.xmin = temp.xmax - icon_size; - if (!op_icon->highlighted) { + if (op_icon->disabled) { + alpha_this *= 0.4f; + } + else if (!op_icon->highlighted) { alpha_this *= 0.75f; } @@ -2453,8 +2407,8 @@ static void widget_draw_text_icon(const uiFontStyle *fstyle, } else { /* In case a separate text label and some other button are placed under each other, - and the outline of the button does not contrast with the background. - Add an offset (thickness of the outline) so that the text does not stick out visually. */ + * and the outline of the button does not contrast with the background. + * Add an offset (thickness of the outline) so that the text does not stick out visually. */ if (but->drawflag & UI_BUT_TEXT_LEFT) { rect->xmin += U.pixelsize; } @@ -2493,7 +2447,7 @@ static void widget_draw_text_icon(const uiFontStyle *fstyle, ui_text_clip_middle(fstyle, but, rect); } - /* always draw text for textbutton cursor */ + /* Always draw text for text-button cursor. */ widget_draw_text(fstyle, wcol, but, rect); ui_but_text_password_hide(password_str, but, true); @@ -2572,7 +2526,7 @@ static void widget_state(uiWidgetType *wt, int state, int drawflag, eUIEmbossTyp { uiWidgetStateColors *wcol_state = wt->wcol_state; - if ((state & UI_BUT_LIST_ITEM) && !(state & UI_STATE_TEXT_INPUT)) { + if (state & UI_BUT_LIST_ITEM) { /* Override default widget's colors. */ bTheme *btheme = UI_GetTheme(); wt->wcol_theme = &btheme->tui.wcol_list_item; @@ -3088,7 +3042,7 @@ void ui_draw_gradient(const rcti *rect, copy_v3_v3(col1[3], col1[2]); break; default: - BLI_assert(!"invalid 'type' argument"); + BLI_assert_msg(0, "invalid 'type' argument"); hsv_to_rgb(1.0, 1.0, 1.0, &col1[2][0], &col1[2][1], &col1[2][2]); copy_v3_v3(col1[0], col1[2]); copy_v3_v3(col1[1], col1[2]); @@ -3489,7 +3443,7 @@ static void widget_menubut(uiWidgetColors *wcol, rcti *rect, int UNUSED(state), /** * Draw menu buttons still with triangles when field is not embossed */ -static void widget_menubut_embossn(uiBut *UNUSED(but), +static void widget_menubut_embossn(const uiBut *UNUSED(but), uiWidgetColors *wcol, rcti *rect, int UNUSED(state), @@ -3512,7 +3466,7 @@ static void widget_menubut_embossn(uiBut *UNUSED(but), * Draw number buttons still with triangles when field is not embossed */ static void widget_numbut_embossn( - uiBut *UNUSED(but), uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) + const uiBut *UNUSED(but), uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) { widget_numbut_draw(wcol, rect, state, roundboxalign, true); } @@ -3692,16 +3646,11 @@ static void widget_progressbar( /* "slider" bar color */ copy_v3_v3_uchar(wcol->inner, wcol->item); widgetbase_draw(&wtb_bar, wcol); - - /* raise text a bit */ - rect->xmin += (BLI_rcti_size_x(&rect_prog) / 2); - rect->xmax += (BLI_rcti_size_x(&rect_prog) / 2); } -static void widget_datasetrow( - uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign)) +static void widget_treerow_exec( + uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign), int indentation) { - uiButDatasetRow *but_componentrow = (uiButDatasetRow *)but; uiWidgetBase wtb; widget_init(&wtb); @@ -3714,16 +3663,30 @@ static void widget_datasetrow( widgetbase_draw(&wtb, wcol); } - BLI_rcti_resize(rect, - BLI_rcti_size_x(rect) - UI_UNIT_X * but_componentrow->indentation, - BLI_rcti_size_y(rect)); - BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * but_componentrow->indentation, 0); + BLI_rcti_resize(rect, BLI_rcti_size_x(rect) - UI_UNIT_X * indentation, BLI_rcti_size_y(rect)); + BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * indentation, 0); +} + +static void widget_treerow( + uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiButTreeRow *tree_row = (uiButTreeRow *)but; + BLI_assert(but->type == UI_BTYPE_TREEROW); + widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation); +} + +static void widget_datasetrow( + uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiButDatasetRow *dataset_row = (uiButDatasetRow *)but; + BLI_assert(but->type == UI_BTYPE_DATASETROW); + widget_treerow_exec(wcol, rect, state, roundboxalign, dataset_row->indentation); } static void widget_nodesocket( uiBut *but, uiWidgetColors *wcol, rcti *rect, int UNUSED(state), int UNUSED(roundboxalign)) { - const int radi = 5; + const int radi = 0.25f * BLI_rcti_size_y(rect); uiWidgetBase wtb; widget_init(&wtb); @@ -3995,6 +3958,14 @@ static void widget_textbut(uiWidgetColors *wcol, rcti *rect, int state, int roun widgetbase_draw(&wtb, wcol); } +static void widget_preview_tile( + uiBut *but, uiWidgetColors *wcol, rcti *rect, int UNUSED(state), int UNUSED(roundboxalign)) +{ + const uiStyle *style = UI_style_get(); + ui_draw_preview_item_stateless( + &style->widget, rect, but->drawstr, but->icon, wcol->text, UI_STYLE_TEXT_CENTER); +} + static void widget_menuiconbut(uiWidgetColors *wcol, rcti *rect, int UNUSED(state), @@ -4046,9 +4017,15 @@ static void widget_menu_itembut(uiWidgetColors *wcol, uiWidgetBase wtb; widget_init(&wtb); - /* not rounded, no outline */ + /* Padding on the sides. */ + const float padding = 0.125f * BLI_rcti_size_y(rect); + rect->xmin += padding; + rect->xmax -= padding; + + /* No outline. */ wtb.draw_outline = false; - round_box_edges(&wtb, 0, rect, 0.0f); + const float rad = wcol->roundness * BLI_rcti_size_y(rect); + round_box_edges(&wtb, UI_CNR_ALL, rect, rad); widgetbase_draw(&wtb, wcol); } @@ -4306,7 +4283,7 @@ static void widget_draw_extra_mask(const bContext *C, uiBut *but, uiWidgetType * widget_init(&wtb); if (but->block->drawextra) { - /* note: drawextra can change rect +1 or -1, to match round errors of existing previews */ + /* NOTE: drawextra can change rect +1 or -1, to match round errors of existing previews. */ but->block->drawextra( C, but->poin, but->block->drawextra_arg1, but->block->drawextra_arg2, rect); @@ -4460,6 +4437,13 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type) wt.custom = widget_icon_has_anim; break; + case UI_WTYPE_PREVIEW_TILE: + wt.draw = NULL; + /* Drawn via the `custom` callback. */ + wt.text = NULL; + wt.custom = widget_preview_tile; + break; + case UI_WTYPE_SWATCH: wt.custom = widget_swatch; break; @@ -4496,6 +4480,10 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type) wt.custom = widget_datasetrow; break; + case UI_WTYPE_TREEROW: + wt.custom = widget_treerow; + break; + case UI_WTYPE_NODESOCKET: wt.custom = widget_nodesocket; break; @@ -4609,6 +4597,9 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu switch (but->type) { case UI_BTYPE_LABEL: wt = widget_type(UI_WTYPE_ICON_LABEL); + if (!(but->flag & UI_HAS_ICON)) { + but->drawflag |= UI_BUT_NO_TEXT_PADDING; + } break; default: wt = widget_type(UI_WTYPE_ICON); @@ -4755,6 +4746,10 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu wt = widget_type(UI_WTYPE_BOX); break; + case UI_BTYPE_PREVIEW_TILE: + wt = widget_type(UI_WTYPE_PREVIEW_TILE); + break; + case UI_BTYPE_EXTRA: widget_draw_extra_mask(C, but, widget_type(UI_WTYPE_BOX), rect); break; @@ -4804,9 +4799,6 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu break; case UI_BTYPE_CURVE: - /* do not draw right to edge of rect */ - rect->xmin += (0.2f * UI_UNIT_X); - rect->xmax -= (0.2f * UI_UNIT_X); ui_draw_but_CURVE(region, but, &tui->wcol_regular, rect); break; @@ -4824,6 +4816,11 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu fstyle = &style->widgetlabel; break; + case UI_BTYPE_TREEROW: + wt = widget_type(UI_WTYPE_TREEROW); + fstyle = &style->widgetlabel; + break; + case UI_BTYPE_SCROLL: wt = widget_type(UI_WTYPE_SCROLL); break; @@ -4908,13 +4905,15 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu wt->draw(&wt->wcol, rect, state, roundboxalign); } - if (use_alpha_blend) { - GPU_blend(GPU_BLEND_ALPHA); - } + if (wt->text) { + if (use_alpha_blend) { + GPU_blend(GPU_BLEND_ALPHA); + } - wt->text(fstyle, &wt->wcol, but, rect); - if (use_alpha_blend) { - GPU_blend(GPU_BLEND_NONE); + wt->text(fstyle, &wt->wcol, but, rect); + if (use_alpha_blend) { + GPU_blend(GPU_BLEND_NONE); + } } } @@ -4956,30 +4955,6 @@ void ui_draw_menu_back(uiStyle *UNUSED(style), uiBlock *block, rcti *rect) } /** - * Uses the widget base drawing and colors from the box widget, but ensures an opaque - * inner color. - */ -void ui_draw_box_opaque(rcti *rect, int roundboxalign) -{ - uiWidgetType *wt = widget_type(UI_WTYPE_BOX); - - /* Alpha blend with the region's background color to force an opaque background. */ - uiWidgetColors *wcol = &wt->wcol; - wt->state(wt, 0, 0, UI_EMBOSS_UNDEFINED); - float background[4]; - UI_GetThemeColor4fv(TH_BACK, background); - float new_inner[4]; - rgba_uchar_to_float(new_inner, wcol->inner); - new_inner[0] = (new_inner[0] * new_inner[3]) + (background[0] * (1.0f - new_inner[3])); - new_inner[1] = (new_inner[1] * new_inner[3]) + (background[1] * (1.0f - new_inner[3])); - new_inner[2] = (new_inner[2] * new_inner[3]) + (background[2] * (1.0f - new_inner[3])); - new_inner[3] = 1.0f; - rgba_float_to_uchar(wcol->inner, new_inner); - - wt->custom(NULL, wcol, rect, 0, roundboxalign); -} - -/** * Similar to 'widget_menu_back', however we can't use the widget preset system * because we need to pass in the original location so we know where to show the arrow. */ @@ -5324,11 +5299,6 @@ void ui_draw_menu_item(const uiFontStyle *fstyle, /* need to set this first */ UI_fontstyle_set(fstyle); - if (fstyle->kerning == 1) { - /* for BLF_width */ - BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - if (separator_type == UI_MENU_ITEM_SEPARATOR_SHORTCUT) { /* Shrink rect to exclude the shortcut string. */ rect->xmax -= BLF_width(fstyle->uifont_id, cpoin + 1, INT_MAX) + UI_DPI_ICON_SIZE; @@ -5351,11 +5321,7 @@ void ui_draw_menu_item(const uiFontStyle *fstyle, } } else { - BLI_assert(!"Unknwon menu item separator type"); - } - - if (fstyle->kerning == 1) { - BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT); + BLI_assert_msg(0, "Unknown menu item separator type"); } } } @@ -5436,30 +5402,38 @@ void ui_draw_menu_item(const uiFontStyle *fstyle, } } -void ui_draw_preview_item( - const uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, int state) +/** + * Version of #ui_draw_preview_item() that does not draw the menu background and item text based on + * state. It just draws the preview and text directly. + */ +void ui_draw_preview_item_stateless(const uiFontStyle *fstyle, + rcti *rect, + const char *name, + int iconid, + const uchar text_col[4], + eFontStyle_Align text_align) { rcti trect = *rect; const float text_size = UI_UNIT_Y; float font_dims[2] = {0.0f, 0.0f}; - uiWidgetType *wt = widget_type(UI_WTYPE_MENU_ITEM); - - /* drawing button background */ - wt->state(wt, state, 0, UI_EMBOSS_UNDEFINED); - wt->draw(&wt->wcol, rect, 0, 0); + const bool has_text = name && name[0]; - /* draw icon in rect above the space reserved for the label */ - rect->ymin += text_size; + if (has_text) { + /* draw icon in rect above the space reserved for the label */ + rect->ymin += text_size; + } GPU_blend(GPU_BLEND_ALPHA); widget_draw_preview(iconid, 1.0f, rect); GPU_blend(GPU_BLEND_NONE); + if (!has_text) { + return; + } + BLF_width_and_height( fstyle->uifont_id, name, BLF_DRAW_STR_DUMMY_MAX, &font_dims[0], &font_dims[1]); /* text rect */ - trect.xmin += 0; - trect.xmax = trect.xmin + font_dims[0] + U.widget_unit / 2; trect.ymin += U.widget_unit / 2; trect.ymax = trect.ymin + font_dims[1]; if (trect.xmax > rect->xmax - PREVIEW_PAD) { @@ -5478,11 +5452,27 @@ void ui_draw_preview_item( UI_fontstyle_draw(fstyle, &trect, drawstr, - wt->wcol.text, + text_col, &(struct uiFontStyleDraw_Params){ - .align = UI_STYLE_TEXT_CENTER, + .align = text_align, }); } } +void ui_draw_preview_item(const uiFontStyle *fstyle, + rcti *rect, + const char *name, + int iconid, + int state, + eFontStyle_Align text_align) +{ + uiWidgetType *wt = widget_type(UI_WTYPE_MENU_ITEM); + + /* drawing button background */ + wt->state(wt, state, 0, UI_EMBOSS_UNDEFINED); + wt->draw(&wt->wcol, rect, 0, 0); + + ui_draw_preview_item_stateless(fstyle, rect, name, iconid, wt->wcol.text, text_align); +} + /** \} */ diff --git a/source/blender/editors/interface/resources.c b/source/blender/editors/interface/resources.c index afac254f542..aece2e58f1e 100644 --- a/source/blender/editors/interface/resources.c +++ b/source/blender/editors/interface/resources.c @@ -85,7 +85,7 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) ThemeSpace *ts = NULL; static uchar error[4] = {240, 0, 240, 255}; static uchar alert[4] = {240, 60, 60, 255}; - static uchar headerdesel[4] = {0, 0, 0, 255}; + static uchar header_active[4] = {0, 0, 0, 255}; static uchar back[4] = {0, 0, 0, 255}; static uchar setting = 0; const uchar *cp = error; @@ -249,15 +249,18 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) case TH_HEADER: cp = ts->header; break; - case TH_HEADERDESEL: - /* We calculate a dynamic builtin header deselect color, also for pull-downs. */ + + case TH_HEADER_ACTIVE: cp = ts->header; - headerdesel[0] = cp[0] > 10 ? cp[0] - 10 : 0; - headerdesel[1] = cp[1] > 10 ? cp[1] - 10 : 0; - headerdesel[2] = cp[2] > 10 ? cp[2] - 10 : 0; - headerdesel[3] = cp[3]; - cp = headerdesel; + const int factor = 5; + /* Lighten the header color when editor is active. */ + header_active[0] = cp[0] > 245 ? cp[0] - factor : cp[0] + factor; + header_active[1] = cp[1] > 245 ? cp[1] - factor : cp[1] + factor; + header_active[2] = cp[2] > 245 ? cp[2] - factor : cp[2] + factor; + header_active[3] = cp[3]; + cp = header_active; break; + case TH_HEADER_TEXT: cp = ts->header_text; break; @@ -631,7 +634,7 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) case TH_NODE_SHADER: cp = ts->nodeclass_shader; break; - case TH_NODE_CONVERTOR: + case TH_NODE_CONVERTER: cp = ts->syntaxv; break; case TH_NODE_GROUP: @@ -1089,7 +1092,7 @@ bTheme *UI_GetTheme(void) } /** - * for the rare case we need to temp swap in a different theme (offscreen render) + * For the rare case we need to temp swap in a different theme (off-screen render). */ void UI_Theme_Store(struct bThemeState *theme_state) { diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc new file mode 100644 index 00000000000..fcc878c440c --- /dev/null +++ b/source/blender/editors/interface/tree_view.cc @@ -0,0 +1,790 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_context.h" + +#include "BLT_translation.h" + +#include "interface_intern.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_tree_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ + +/** + * Add a tree-item to the container. This is the only place where items should be added, it handles + * important invariants! + */ +AbstractTreeViewItem &TreeViewItemContainer::add_tree_item( + std::unique_ptr<AbstractTreeViewItem> item) +{ + children_.append(std::move(item)); + + /* The first item that will be added to the root sets this. */ + if (root_ == nullptr) { + root_ = this; + } + + AbstractTreeViewItem &added_item = *children_.last(); + added_item.root_ = root_; + if (root_ != this) { + /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely + * nice to static_cast this, but well... */ + added_item.parent_ = static_cast<AbstractTreeViewItem *>(this); + } + + return added_item; +} + +void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const +{ + for (const auto &child : children_) { + iter_fn(*child); + if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) { + continue; + } + + child->foreach_item_recursive(iter_fn, options); + } +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const +{ + foreach_item_recursive(iter_fn, options); +} + +bool AbstractTreeView::is_renaming() const +{ + return rename_buffer_ != nullptr; +} + +void AbstractTreeView::build_layout_from_tree(const TreeViewLayoutBuilder &builder) +{ + uiLayout *prev_layout = builder.current_layout(); + + uiLayout *box = uiLayoutBox(prev_layout); + uiLayoutColumn(box, false); + + foreach_item([&builder](AbstractTreeViewItem &item) { builder.build_row(item); }, + IterOptions::SkipCollapsed); + + UI_block_layout_set_current(&builder.block(), prev_layout); +} + +void AbstractTreeView::update_from_old(uiBlock &new_block) +{ + uiBlock *old_block = new_block.oldblock; + if (!old_block) { + /* Initial construction, nothing to update. */ + is_reconstructed_ = true; + return; + } + + uiTreeViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block( + &new_block, reinterpret_cast<uiTreeViewHandle *>(this)); + BLI_assert(old_view_handle); + + AbstractTreeView &old_view = reinterpret_cast<AbstractTreeView &>(*old_view_handle); + + /* Update own persistent data. */ + /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's + * pointer to identify itself over redraws. */ + rename_buffer_ = std::move(old_view.rename_buffer_); + old_view.rename_buffer_ = nullptr; + + update_children_from_old_recursive(*this, old_view); + + /* Finished (re-)constructing the tree. */ + is_reconstructed_ = true; +} + +void AbstractTreeView::update_children_from_old_recursive(const TreeViewItemContainer &new_items, + const TreeViewItemContainer &old_items) +{ + for (const auto &new_item : new_items.children_) { + AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items); + if (!matching_old_item) { + continue; + } + + new_item->update_from_old(*matching_old_item); + + /* Recurse into children of the matched item. */ + update_children_from_old_recursive(*new_item, *matching_old_item); + } +} + +AbstractTreeViewItem *AbstractTreeView::find_matching_child( + const AbstractTreeViewItem &lookup_item, const TreeViewItemContainer &items) +{ + for (const auto &iter_item : items.children_) { + if (lookup_item.matches(*iter_item)) { + /* We have a matching item! */ + return iter_item.get(); + } + } + + return nullptr; +} + +bool AbstractTreeView::is_reconstructed() const +{ + return is_reconstructed_; +} + +void AbstractTreeView::change_state_delayed() +{ + BLI_assert_msg( + is_reconstructed(), + "These state changes are supposed to be delayed until reconstruction is completed"); + foreach_item([](AbstractTreeViewItem &item) { item.change_state_delayed(); }); +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/, + void *but_arg1, + void * /*arg2*/) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1; + AbstractTreeViewItem &tree_item = reinterpret_cast<AbstractTreeViewItem &>( + *tree_row_but->tree_item); + + tree_item.activate(); +} + +void AbstractTreeViewItem::add_treerow_button(uiBlock &block) +{ + /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */ + tree_row_but_ = (uiButTreeRow *)uiDefBut( + &block, UI_BTYPE_TREEROW, 0, "", 0, 0, UI_UNIT_X * 10, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); + + tree_row_but_->tree_item = reinterpret_cast<uiTreeViewItemHandle *>(this); + UI_but_func_set(&tree_row_but_->but, tree_row_click_fn, tree_row_but_, nullptr); +} + +void AbstractTreeViewItem::add_indent(uiLayout &row) const +{ + uiBlock *block = uiLayoutGetBlock(&row); + uiLayout *subrow = uiLayoutRow(&row, true); + uiLayoutSetFixedSize(subrow, true); + + const float indent_size = count_parents() * UI_DPI_ICON_SIZE; + uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, indent_size, 0, nullptr, 0.0, 0.0, 0, 0, ""); + + /* Indent items without collapsing icon some more within their parent. Makes it clear that they + * are actually nested and not just a row at the same level without a chevron. */ + if (!is_collapsible() && parent_) { + uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, 0.2f * UI_UNIT_X, 0, nullptr, 0.0, 0.0, 0, 0, ""); + } + + /* Restore. */ + UI_block_layout_set_current(block, &row); +} + +void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C, + void * /*but_arg1*/, + void * /*arg2*/) +{ + /* There's no data we could pass to this callback. It must be either the button itself or a + * consistent address to match buttons over redraws. So instead of passing it somehow, just + * lookup the hovered item via context here. */ + + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + uiTreeViewItemHandle *hovered_item_handle = UI_block_tree_view_find_item_at(region, + win->eventstate->xy); + AbstractTreeViewItem *hovered_item = reinterpret_cast<AbstractTreeViewItem *>( + hovered_item_handle); + BLI_assert(hovered_item != nullptr); + + hovered_item->toggle_collapsed(); + /* When collapsing an item with an active child, make this collapsed item active instead so the + * active item stays visible. */ + if (hovered_item->has_active_child()) { + hovered_item->activate(); + } +} + +bool AbstractTreeViewItem::is_collapse_chevron_but(const uiBut *but) +{ + return but->type == UI_BTYPE_BUT_TOGGLE && ELEM(but->icon, ICON_TRIA_RIGHT, ICON_TRIA_DOWN) && + (but->func == collapse_chevron_click_fn); +} + +void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const +{ + if (!is_collapsible()) { + return; + } + + const BIFIconID icon = is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN; + uiBut *but = uiDefIconBut( + &block, UI_BTYPE_BUT_TOGGLE, 0, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); + /* Note that we're passing the tree-row button here, not the chevron one. */ + UI_but_func_set(but, collapse_chevron_click_fn, nullptr, nullptr); + UI_but_flag_disable(but, UI_BUT_UNDO); + + /* Check if the query for the button matches the created button. */ + BLI_assert(is_collapse_chevron_but(but)); +} + +AbstractTreeViewItem *AbstractTreeViewItem::find_tree_item_from_rename_button( + const uiBut &rename_but) +{ + /* A minimal sanity check, can't do much more here. */ + BLI_assert(rename_but.type == UI_BTYPE_TEXT && rename_but.poin); + + LISTBASE_FOREACH (uiBut *, but, &rename_but.block->buttons) { + if (but->type != UI_BTYPE_TREEROW) { + continue; + } + + uiButTreeRow *tree_row_but = (uiButTreeRow *)but; + AbstractTreeViewItem *item = reinterpret_cast<AbstractTreeViewItem *>(tree_row_but->tree_item); + const AbstractTreeView &tree_view = item->get_tree_view(); + + if (item->is_renaming() && (tree_view.rename_buffer_->data() == rename_but.poin)) { + return item; + } + } + + return nullptr; +} + +void AbstractTreeViewItem::rename_button_fn(bContext *UNUSED(C), void *arg, char *UNUSED(origstr)) +{ + const uiBut *rename_but = static_cast<uiBut *>(arg); + AbstractTreeViewItem *item = find_tree_item_from_rename_button(*rename_but); + BLI_assert(item); + + const AbstractTreeView &tree_view = item->get_tree_view(); + item->rename(tree_view.rename_buffer_->data()); + item->end_renaming(); +} + +void AbstractTreeViewItem::add_rename_button(uiLayout &row) +{ + uiBlock *block = uiLayoutGetBlock(&row); + eUIEmbossType previous_emboss = UI_block_emboss_get(block); + + uiLayoutRow(&row, false); + /* Enable emboss for the text button. */ + UI_block_emboss_set(block, UI_EMBOSS); + + AbstractTreeView &tree_view = get_tree_view(); + uiBut *rename_but = uiDefBut(block, + UI_BTYPE_TEXT, + 1, + "", + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + tree_view.rename_buffer_->data(), + 1.0f, + tree_view.rename_buffer_->max_size(), + 0, + 0, + ""); + + /* Gotta be careful with what's passed to the `arg1` here. Any tree data will be freed once the + * callback is executed. */ + UI_but_func_rename_set(rename_but, AbstractTreeViewItem::rename_button_fn, rename_but); + UI_but_flag_disable(rename_but, UI_BUT_UNDO); + + const bContext *evil_C = static_cast<bContext *>(block->evil_C); + ARegion *region = CTX_wm_region(evil_C); + /* Returns false if the button was removed. */ + if (UI_but_active_only(evil_C, region, block, rename_but) == false) { + end_renaming(); + } + + UI_block_emboss_set(block, previous_emboss); + UI_block_layout_set_current(block, &row); +} + +bool AbstractTreeViewItem::has_active_child() const +{ + bool found = false; + foreach_item_recursive([&found](const AbstractTreeViewItem &item) { + if (item.is_active()) { + found = true; + } + }); + + return found; +} + +void AbstractTreeViewItem::on_activate() +{ + /* Do nothing by default. */ +} + +void AbstractTreeViewItem::is_active(IsActiveFn is_active_fn) +{ + is_active_fn_ = is_active_fn; +} + +std::unique_ptr<AbstractTreeViewItemDragController> AbstractTreeViewItem::create_drag_controller() + const +{ + /* There's no drag controller (and hence no drag support) by default. */ + return nullptr; +} + +std::unique_ptr<AbstractTreeViewItemDropController> AbstractTreeViewItem::create_drop_controller() + const +{ + /* There's no drop controller (and hence no drop support) by default. */ + return nullptr; +} + +bool AbstractTreeViewItem::can_rename() const +{ + /* No renaming by default. */ + return false; +} + +bool AbstractTreeViewItem::rename(StringRefNull new_name) +{ + /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches() + * recognizes the item. (It only compares labels by default.) */ + label_ = new_name; + return true; +} + +void AbstractTreeViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*/) const +{ + /* No context menu by default. */ +} + +void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old) +{ + is_open_ = old.is_open_; + is_active_ = old.is_active_; + is_renaming_ = old.is_renaming_; +} + +bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const +{ + return label_ == other.label_; +} + +void AbstractTreeViewItem::begin_renaming() +{ + AbstractTreeView &tree_view = get_tree_view(); + if (tree_view.is_renaming() || !can_rename()) { + return; + } + + is_renaming_ = true; + + tree_view.rename_buffer_ = std::make_unique<decltype(tree_view.rename_buffer_)::element_type>(); + std::copy(std::begin(label_), std::end(label_), std::begin(*tree_view.rename_buffer_)); +} + +void AbstractTreeViewItem::end_renaming() +{ + if (!is_renaming()) { + return; + } + + is_renaming_ = false; + + AbstractTreeView &tree_view = get_tree_view(); + tree_view.rename_buffer_ = nullptr; +} + +AbstractTreeView &AbstractTreeViewItem::get_tree_view() const +{ + return static_cast<AbstractTreeView &>(*root_); +} + +int AbstractTreeViewItem::count_parents() const +{ + int i = 0; + for (TreeViewItemContainer *parent = parent_; parent; parent = parent->parent_) { + i++; + } + return i; +} + +void AbstractTreeViewItem::activate() +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "Item activation can't be done until reconstruction is completed"); + + if (is_active()) { + return; + } + + /* Deactivate other items in the tree. */ + get_tree_view().foreach_item([](auto &item) { item.deactivate(); }); + + on_activate(); + /* Make sure the active item is always visible. */ + ensure_parents_uncollapsed(); + + is_active_ = true; +} + +void AbstractTreeViewItem::deactivate() +{ + is_active_ = false; +} + +bool AbstractTreeViewItem::is_active() const +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + return is_active_; +} + +bool AbstractTreeViewItem::is_hovered() const +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + BLI_assert_msg(tree_row_but_ != nullptr, + "Hovered state can't be queried before the tree row is being built"); + + const uiTreeViewItemHandle *this_handle = reinterpret_cast<const uiTreeViewItemHandle *>(this); + /* The new layout hasn't finished construction yet, so the final state of the button is unknown. + * Get the matching button from the previous redraw instead. */ + uiButTreeRow *old_treerow_but = ui_block_view_find_treerow_in_old_block(tree_row_but_->but.block, + this_handle); + return old_treerow_but && (old_treerow_but->but.flag & UI_ACTIVE); +} + +bool AbstractTreeViewItem::is_collapsed() const +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + return is_collapsible() && !is_open_; +} + +void AbstractTreeViewItem::toggle_collapsed() +{ + is_open_ = !is_open_; +} + +void AbstractTreeViewItem::set_collapsed(bool collapsed) +{ + is_open_ = !collapsed; +} + +bool AbstractTreeViewItem::is_collapsible() const +{ + return !children_.is_empty(); +} + +bool AbstractTreeViewItem::is_renaming() const +{ + return is_renaming_; +} + +void AbstractTreeViewItem::ensure_parents_uncollapsed() +{ + for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { + parent->set_collapsed(false); + } +} + +bool AbstractTreeViewItem::matches_including_parents(const AbstractTreeViewItem &other) const +{ + if (!matches(other)) { + return false; + } + if (count_parents() != other.count_parents()) { + return false; + } + + for (AbstractTreeViewItem *parent = parent_, *other_parent = other.parent_; + parent && other_parent; + parent = parent->parent_, other_parent = other_parent->parent_) { + if (!parent->matches(*other_parent)) { + return false; + } + } + + return true; +} + +uiButTreeRow *AbstractTreeViewItem::tree_row_button() +{ + return tree_row_but_; +} + +void AbstractTreeViewItem::change_state_delayed() +{ + if (is_active_fn_()) { + activate(); + } +} +/* ---------------------------------------------------------------------- */ + +AbstractTreeViewItemDropController::AbstractTreeViewItemDropController(AbstractTreeView &tree_view) + : tree_view_(tree_view) +{ +} + +/* ---------------------------------------------------------------------- */ + +TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block) +{ +} + +void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view) +{ + tree_view.build_tree(); + tree_view.update_from_old(block_); + tree_view.change_state_delayed(); + tree_view.build_layout_from_tree(TreeViewLayoutBuilder(block_)); +} + +/* ---------------------------------------------------------------------- */ + +TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block) +{ +} + +/** + * Moves the button following the last added chevron closer to the list item. + * + * Iterates backwards over buttons until finding the tree-row button, which is assumed to be the + * first button added for the row, and can act as a delimiter that way. + */ +void TreeViewLayoutBuilder::polish_layout(const uiBlock &block) +{ + LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block.buttons) { + if (AbstractTreeViewItem::is_collapse_chevron_but(but) && but->next && + /* Embossed buttons with padding-less text padding look weird, so don't touch them. */ + ELEM(but->next->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) { + UI_but_drawflag_enable(static_cast<uiBut *>(but->next), UI_BUT_NO_TEXT_PADDING); + } + + if (but->type == UI_BTYPE_TREEROW) { + break; + } + } +} + +void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const +{ + uiBlock &block_ = block(); + + uiLayout *prev_layout = current_layout(); + eUIEmbossType previous_emboss = UI_block_emboss_get(&block_); + + uiLayout *overlap = uiLayoutOverlap(prev_layout); + + uiLayoutRow(overlap, false); + /* Every item gets one! Other buttons can be overlapped on top. */ + item.add_treerow_button(block_); + + /* After adding tree-row button (would disable hover highlighting). */ + UI_block_emboss_set(&block_, UI_EMBOSS_NONE); + + uiLayout *row = uiLayoutRow(overlap, true); + item.add_indent(*row); + item.add_collapse_chevron(block_); + + if (item.is_renaming()) { + item.add_rename_button(*row); + } + else { + item.build_row(*row); + } + polish_layout(block_); + + UI_block_emboss_set(&block_, previous_emboss); + UI_block_layout_set_current(&block_, prev_layout); +} + +uiBlock &TreeViewLayoutBuilder::block() const +{ + return block_; +} + +uiLayout *TreeViewLayoutBuilder::current_layout() const +{ + return block().curlayout; +} + +/* ---------------------------------------------------------------------- */ + +BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_) : icon(icon_) +{ + label_ = label; +} + +void BasicTreeViewItem::build_row(uiLayout &row) +{ + add_label(row); +} + +void BasicTreeViewItem::add_label(uiLayout &layout, StringRefNull label_override) +{ + const StringRefNull label = label_override.is_empty() ? StringRefNull(label_) : label_override; + + /* Some padding for labels without collapse chevron and no icon. Looks weird without. */ + if (icon == ICON_NONE && !is_collapsible()) { + uiItemS_ex(&layout, 0.8f); + } + uiItemL(&layout, IFACE_(label.c_str()), icon); +} + +void BasicTreeViewItem::on_activate() +{ + if (activate_fn_) { + activate_fn_(*this); + } +} + +void BasicTreeViewItem::on_activate(ActivateFn fn) +{ + activate_fn_ = fn; +} + +} // namespace blender::ui + +using namespace blender::ui; + +bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle); + return item.is_active(); +} + +bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle, + const uiTreeViewItemHandle *b_handle) +{ + const AbstractTreeViewItem &a = reinterpret_cast<const AbstractTreeViewItem &>(*a_handle); + const AbstractTreeViewItem &b = reinterpret_cast<const AbstractTreeViewItem &>(*b_handle); + /* TODO should match the tree-view as well. */ + return a.matches_including_parents(b); +} + +/** + * Attempt to start dragging the tree-item \a item_. This will not work if the tree item doesn't + * support dragging, i.e. it won't create a drag-controller upon request. + * \return True if dragging started successfully, otherwise false. + */ +bool UI_tree_view_item_drag_start(bContext *C, uiTreeViewItemHandle *item_) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_); + const std::unique_ptr<AbstractTreeViewItemDragController> drag_controller = + item.create_drag_controller(); + if (!drag_controller) { + return false; + } + + WM_event_start_drag(C, + ICON_NONE, + drag_controller->get_drag_type(), + drag_controller->create_drag_data(), + 0, + WM_DRAG_FREE_DATA); + return true; +} + +bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, + const wmDrag *drag, + const char **r_disabled_hint) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_); + const std::unique_ptr<AbstractTreeViewItemDropController> drop_controller = + item.create_drop_controller(); + if (!drop_controller) { + return false; + } + + return drop_controller->can_drop(*drag, r_disabled_hint); +} + +char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_, const wmDrag *drag) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_); + const std::unique_ptr<AbstractTreeViewItemDropController> drop_controller = + item.create_drop_controller(); + if (!drop_controller) { + return nullptr; + } + + return BLI_strdup(drop_controller->drop_tooltip(*drag).c_str()); +} + +/** + * Let a tree-view item handle a drop event. + * \return True if the drop was handled by the tree-view item. + */ +bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const ListBase *drags) +{ + AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_); + std::unique_ptr<AbstractTreeViewItemDropController> drop_controller = + item.create_drop_controller(); + + const char *disabled_hint_dummy = nullptr; + LISTBASE_FOREACH (const wmDrag *, drag, drags) { + if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) { + return drop_controller->on_drop(*drag); + } + } + + return false; +} + +/** + * Can \a item_handle be renamed right now? Not that this isn't just a mere wrapper around + * #AbstractTreeViewItem::can_rename(). This also checks if there is another item being renamed, + * and returns false if so. + */ +bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle); + const AbstractTreeView &tree_view = item.get_tree_view(); + return !tree_view.is_renaming() && item.can_rename(); +} + +void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle) +{ + AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_handle); + item.begin_renaming(); +} + +void UI_tree_view_item_context_menu_build(bContext *C, + const uiTreeViewItemHandle *item_handle, + uiLayout *column) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle); + item.build_context_menu(*C, *column); +} diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c index 5eb20ae601b..eea6512f0f8 100644 --- a/source/blender/editors/interface/view2d.c +++ b/source/blender/editors/interface/view2d.c @@ -32,6 +32,7 @@ #include "DNA_userdef_types.h" #include "BLI_array.h" +#include "BLI_easing.h" #include "BLI_link_utils.h" #include "BLI_listbase.h" #include "BLI_math.h" @@ -166,7 +167,7 @@ static void view2d_masks(View2D *v2d, const rcti *mask_scroll) scroll = view2d_scroll_mapped(v2d->scroll); - /* scrollers are based off regionsize + /* Scrollers are based off region-size: * - they can only be on one to two edges of the region they define * - if they overlap, they must not occupy the corners (which are reserved for other widgets) */ @@ -347,7 +348,7 @@ void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy) v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y); v2d->keeptot = V2D_KEEPTOT_BOUNDS; - /* note, scroll is being flipped in ED_region_panels() drawing */ + /* NOTE: scroll is being flipped in #ED_region_panels() drawing. */ v2d->scroll |= (V2D_SCROLL_HORIZONTAL_HIDE | V2D_SCROLL_VERTICAL_HIDE); if (do_init) { @@ -717,7 +718,7 @@ static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize) * * So, resolution is to just shift view by the gap between the extremities. * We favor moving the 'minimum' across, as that's origin for most things. - * (XXX - in the past, max was favored... if there are bugs, swap!) + * (XXX: in the past, max was favored... if there are bugs, swap!) */ if ((cur->xmin < tot->xmin) && (cur->xmax > tot->xmax)) { /* outside boundaries on both sides, @@ -866,6 +867,11 @@ void UI_view2d_curRect_changed(const bContext *C, View2D *v2d) /* ------------------ */ +bool UI_view2d_area_supports_sync(ScrArea *area) +{ + return ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH); +} + /* Called by menus to activate it, or by view2d operators * to make sure 'related' views stay in synchrony */ void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag) @@ -903,6 +909,9 @@ void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag) /* check if doing whole screen syncing (i.e. time/horizontal) */ if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) { LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) { + if (!UI_view2d_area_supports_sync(area_iter)) { + continue; + } LISTBASE_FOREACH (ARegion *, region, &area_iter->regionbase) { /* don't operate on self */ if (v2dcur != ®ion->v2d) { @@ -1059,7 +1068,7 @@ void UI_view2d_zoom_cache_reset(void) /* While scaling we can accumulate fonts at many sizes (~20 or so). * Not an issue with embedded font, but can use over 500Mb with i18n ones! See T38244. */ - /* Note: only some views draw text, we could check for this case to avoid cleaning cache. */ + /* NOTE: only some views draw text, we could check for this case to avoid cleaning cache. */ BLF_cache_clear(); } @@ -1158,7 +1167,7 @@ void UI_view2d_view_orthoSpecial(ARegion *region, View2D *v2d, const bool xaxis) * correspondence with pixels for smooth UI drawing, * but only applied where requested. */ - /* XXX temp (ton) */ + /* XXX(ton): temp. */ xofs = 0.0f; // (v2d->flag & V2D_PIXELOFS_X) ? GLA_PIXEL_OFS : 0.0f; yofs = 0.0f; // (v2d->flag & V2D_PIXELOFS_Y) ? GLA_PIXEL_OFS : 0.0f; @@ -1194,78 +1203,6 @@ void UI_view2d_view_restore(const bContext *C) /** \name Grid-Line Drawing * \{ */ -/* Draw a constant grid in given 2d-region */ -void UI_view2d_constant_grid_draw(const View2D *v2d, float step) -{ - float start_x, start_y; - int count_x, count_y; - - start_x = v2d->cur.xmin; - if (start_x < 0.0) { - start_x += -(float)fmod(v2d->cur.xmin, step); - } - else { - start_x += (step - (float)fmod(v2d->cur.xmin, step)); - } - - if (start_x > v2d->cur.xmax) { - count_x = 0; - } - else { - count_x = (v2d->cur.xmax - start_x) / step + 1; - } - - start_y = v2d->cur.ymin; - if (start_y < 0.0) { - start_y += -(float)fmod(v2d->cur.ymin, step); - } - else { - start_y += (step - (float)fabs(fmod(v2d->cur.ymin, step))); - } - - if (start_y > v2d->cur.ymax) { - count_y = 0; - } - else { - count_y = (v2d->cur.ymax - start_y) / step + 1; - } - - if (count_x > 0 || count_y > 0) { - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - const uint color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); - float theme_color[3]; - - UI_GetThemeColorShade3fv(TH_BACK, -10, theme_color); - - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - immBegin(GPU_PRIM_LINES, count_x * 2 + count_y * 2 + 4); - - immAttr3fv(color, theme_color); - for (int i = 0; i < count_x; start_x += step, i++) { - immVertex2f(pos, start_x, v2d->cur.ymin); - immVertex2f(pos, start_x, v2d->cur.ymax); - } - - for (int i = 0; i < count_y; start_y += step, i++) { - immVertex2f(pos, v2d->cur.xmin, start_y); - immVertex2f(pos, v2d->cur.xmax, start_y); - } - - /* X and Y axis */ - UI_GetThemeColorShade3fv(TH_BACK, -18, theme_color); - - immAttr3fv(color, theme_color); - immVertex2f(pos, 0.0f, v2d->cur.ymin); - immVertex2f(pos, 0.0f, v2d->cur.ymax); - immVertex2f(pos, v2d->cur.xmin, 0.0f); - immVertex2f(pos, v2d->cur.xmax, 0.0f); - - immEnd(); - immUnbindProgram(); - } -} - /* Draw a multi-level grid in given 2d-region */ void UI_view2d_multi_grid_draw( const View2D *v2d, int colorid, float step, int level_size, int totlevels) @@ -1355,6 +1292,114 @@ void UI_view2d_multi_grid_draw( immUnbindProgram(); } +static void grid_axis_start_and_count( + const float step, const float min, const float max, float *r_start, int *r_count) +{ + *r_start = min; + if (*r_start < 0.0f) { + *r_start += -(float)fmod(min, step); + } + else { + *r_start += step - (float)fabs(fmod(min, step)); + } + + if (*r_start > max) { + *r_count = 0; + } + else { + *r_count = (max - *r_start) / step + 1; + } +} + +typedef struct DotGridLevelInfo { + /* The factor applied to the #min_step argument. This could be easily computed in runtime, + * but seeing it together with the other values is helpful. */ + float step_factor; + /* The normalized zoom level at which the grid level starts to fade in. + * At lower zoom levels, the points will not be visible and the level will be skipped. */ + float fade_in_start_zoom; + /* The normalized zoom level at which the grid finishes fading in. + * At higher zoom levels, the points will be opaque. */ + float fade_in_end_zoom; +} DotGridLevelInfo; + +static const DotGridLevelInfo level_info[9] = { + {6.4f, -0.1f, 0.01f}, + {3.2f, 0.0f, 0.025f}, + {1.6f, 0.025f, 0.15f}, + {0.8f, 0.05f, 0.2f}, + {0.4f, 0.1f, 0.25f}, + {0.2f, 0.125f, 0.3f}, + {0.1f, 0.25f, 0.5f}, + {0.05f, 0.7f, 0.9f}, + {0.025f, 0.6f, 0.9f}, +}; + +/** + * Draw a multi-level grid of dots, with a dynamic number of levels based on the fading. + * + * \param grid_color_id: The theme color used for the points. Faded dynamically based on zoom. + * \param min_step: The base size of the grid. At different zoom levels, the visible grid may have + * a larger step size. + * \param grid_levels: The maximum grid depth. Larger grid levels will subdivide the grid more. + */ +void UI_view2d_dot_grid_draw(const View2D *v2d, + const int grid_color_id, + const float min_step, + const int grid_levels) +{ + BLI_assert(grid_levels > 0 && grid_levels < 10); + const float zoom_x = (float)(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur); + const float zoom_normalized = (zoom_x - v2d->minzoom) / (v2d->maxzoom - v2d->minzoom); + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + const uint color_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + GPU_point_size(3.0f * UI_DPI_FAC); + + float color[4]; + UI_GetThemeColor3fv(grid_color_id, color); + + for (int level = 0; level < grid_levels; level++) { + const DotGridLevelInfo *info = &level_info[level]; + const float step = min_step * info->step_factor * U.widget_unit; + + const float alpha_factor = (zoom_normalized - info->fade_in_start_zoom) / + (info->fade_in_end_zoom - info->fade_in_start_zoom); + color[3] = clamp_f(BLI_easing_cubic_ease_in_out(alpha_factor, 0.0f, 1.0f, 1.0f), 0.0f, 1.0f); + if (color[3] == 0.0f) { + break; + } + + int count_x; + float start_x; + grid_axis_start_and_count(step, v2d->cur.xmin, v2d->cur.xmax, &start_x, &count_x); + int count_y; + float start_y; + grid_axis_start_and_count(step, v2d->cur.ymin, v2d->cur.ymax, &start_y, &count_y); + if (count_x == 0 || count_y == 0) { + continue; + } + + immBegin(GPU_PRIM_POINTS, count_x * count_y); + + /* Theoretically drawing on top of lower grid levels could be avoided, but it would also + * increase the complexity of this loop, which isn't worth the time at the moment. */ + for (int i_y = 0; i_y < count_y; i_y++) { + const float y = start_y + step * i_y; + for (int i_x = 0; i_x < count_x; i_x++) { + const float x = start_x + step * i_x; + immAttr4fv(color_id, color); + immVertex2f(pos, x, y); + } + } + + immEnd(); + } + + immUnbindProgram(); +} /** \} */ /* -------------------------------------------------------------------- */ @@ -1844,7 +1889,7 @@ View2D *UI_view2d_fromcontext(const bContext *C) return &(region->v2d); } -/* same as above, but it returns regionwindow. Utility for pulldowns or buttons */ +/* Same as above, but it returns region-window. Utility for pull-downs or buttons. */ View2D *UI_view2d_fromcontext_rwin(const bContext *C) { ScrArea *area = CTX_wm_area(C); @@ -1914,7 +1959,7 @@ float UI_view2d_scale_get_y(const View2D *v2d) return BLI_rcti_size_y(&v2d->mask) / BLI_rctf_size_y(&v2d->cur); } /** - * Same as ``UI_view2d_scale_get() - 1.0f / x, y`` + * Same as `UI_view2d_scale_get() - 1.0f / x, y`. */ void UI_view2d_scale_get_inverse(const View2D *v2d, float *r_x, float *r_y) { @@ -1988,8 +2033,10 @@ void UI_view2d_offset(struct View2D *v2d, float xfac, float yfac) * - 'v' = in vertical scroller. * - 0 = not in scroller. */ -char UI_view2d_mouse_in_scrollers_ex( - const ARegion *region, const View2D *v2d, int x, int y, int *r_scroll) +char UI_view2d_mouse_in_scrollers_ex(const ARegion *region, + const View2D *v2d, + const int xy[2], + int *r_scroll) { const int scroll = view2d_scroll_mapped(v2d->scroll); *r_scroll = scroll; @@ -1997,8 +2044,8 @@ char UI_view2d_mouse_in_scrollers_ex( if (scroll) { /* Move to region-coordinates. */ const int co[2] = { - x - region->winrct.xmin, - y - region->winrct.ymin, + xy[0] - region->winrct.xmin, + xy[1] - region->winrct.ymin, }; if (scroll & V2D_SCROLL_HORIZONTAL) { if (IN_2D_HORIZ_SCROLL(v2d, co)) { @@ -2042,10 +2089,10 @@ char UI_view2d_rect_in_scrollers_ex(const ARegion *region, return 0; } -char UI_view2d_mouse_in_scrollers(const ARegion *region, const View2D *v2d, int x, int y) +char UI_view2d_mouse_in_scrollers(const ARegion *region, const View2D *v2d, const int xy[2]) { int scroll_dummy = 0; - return UI_view2d_mouse_in_scrollers_ex(region, v2d, x, y, &scroll_dummy); + return UI_view2d_mouse_in_scrollers_ex(region, v2d, xy, &scroll_dummy); } char UI_view2d_rect_in_scrollers(const ARegion *region, const View2D *v2d, const rcti *rect) diff --git a/source/blender/editors/interface/view2d_draw.c b/source/blender/editors/interface/view2d_draw.c index 5801b7cdbdb..b1869fbf2f9 100644 --- a/source/blender/editors/interface/view2d_draw.c +++ b/source/blender/editors/interface/view2d_draw.c @@ -216,7 +216,7 @@ static void draw_parallel_lines(const ParallelLinesSet *lines, immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); immUniform2fv("viewportSize", &viewport[2]); /* -1.0f offset here is because the line is too fat due to the builtin anti-aliasing. - * TODO make a variant or a uniform to toggle it off. */ + * TODO: make a variant or a uniform to toggle it off. */ immUniform1f("lineWidth", U.pixelsize - 1.0f); } else { @@ -326,20 +326,35 @@ static void draw_horizontal_scale_indicators(const ARegion *region, const float xmin = rect->xmin; const float xmax = rect->xmax; - for (uint i = 0; i < steps; i++) { - const float xpos_view = start + i * distance; - const float xpos_region = UI_view2d_view_to_region_x(v2d, xpos_view); - char text[32]; - to_string(to_string_data, xpos_view, distance, sizeof(text), text); - const float text_width = BLF_width(font_id, text, strlen(text)); + char text[32]; - if (xpos_region - text_width / 2.0f >= xmin && xpos_region + text_width / 2.0f <= xmax) { - BLF_draw_default_ascii(xpos_region - text_width / 2.0f, ypos, 0.0f, text, sizeof(text)); + /* Calculate max_label_count and draw_frequency based on largest visible label. */ + int draw_frequency; + { + to_string(to_string_data, start, 0, sizeof(text), text); + const float left_text_width = BLF_width(font_id, text, strlen(text)); + to_string(to_string_data, start + steps * distance, 0, sizeof(text), text); + const float right_text_width = BLF_width(font_id, text, strlen(text)); + const float max_text_width = max_ff(left_text_width, right_text_width); + const float max_label_count = BLI_rcti_size_x(&v2d->mask) / (max_text_width + 10.0f); + draw_frequency = ceil((float)steps / max_label_count); + } + + if (draw_frequency != 0) { + const int start_index = abs((int)(start / distance)) % draw_frequency; + for (uint i = start_index; i < steps; i += draw_frequency) { + const float xpos_view = start + i * distance; + const float xpos_region = UI_view2d_view_to_region_x(v2d, xpos_view); + to_string(to_string_data, xpos_view, distance, sizeof(text), text); + const float text_width = BLF_width(font_id, text, strlen(text)); + + if (xpos_region - text_width / 2.0f >= xmin && xpos_region + text_width / 2.0f <= xmax) { + BLF_draw_default(xpos_region - text_width / 2.0f, ypos, 0.0f, text, sizeof(text)); + } } } BLF_batch_draw_end(); - GPU_matrix_pop_projection(); } @@ -379,29 +394,33 @@ static void draw_vertical_scale_indicators(const ARegion *region, const int font_id = BLF_default(); UI_FontThemeColor(font_id, colorid); - BLF_enable(font_id, BLF_ROTATION); - BLF_rotation(font_id, M_PI_2); - BLF_batch_draw_begin(); - const float xpos = rect->xmax - 2.0f * UI_DPI_FAC; + BLF_enable(font_id, BLF_SHADOW); + const float shadow_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + BLF_shadow(font_id, 5, shadow_color); + BLF_shadow_offset(font_id, 1, -1); + + const float x_offset = 8.0f; + const float xpos = (rect->xmin + x_offset) * UI_DPI_FAC; const float ymin = rect->ymin; const float ymax = rect->ymax; + const float y_offset = (BLF_height(font_id, "0", 1) / 2.0f) - U.pixelsize; for (uint i = 0; i < steps; i++) { const float ypos_view = start + i * distance; const float ypos_region = UI_view2d_view_to_region_y(v2d, ypos_view + display_offset); char text[32]; to_string(to_string_data, ypos_view, distance, sizeof(text), text); - const float text_width = BLF_width(font_id, text, strlen(text)); - if (ypos_region - text_width / 2.0f >= ymin && ypos_region + text_width / 2.0f <= ymax) { - BLF_draw_default_ascii(xpos, ypos_region - text_width / 2.0f, 0.0f, text, sizeof(text)); + if (ypos_region - y_offset >= ymin && ypos_region + y_offset <= ymax) { + BLF_draw_default(xpos, ypos_region - y_offset, 0.0f, text, sizeof(text)); } } + BLF_disable(font_id, BLF_SHADOW); + BLF_batch_draw_end(); - BLF_disable(font_id, BLF_ROTATION); GPU_matrix_pop_projection(); } @@ -413,11 +432,15 @@ static void view_to_string__frame_number( } static void view_to_string__time( - void *user_data, float v2d_pos, float UNUSED(v2d_step), uint max_len, char *r_str) + void *user_data, float v2d_pos, float v2d_step, uint max_len, char *r_str) { const Scene *scene = (const Scene *)user_data; - const int brevity_level = 0; + int brevity_level = 0; + if (U.timecode_style == USER_TIMECODE_MINIMAL && v2d_step >= FPS) { + brevity_level = 1; + } + BLI_timecode_string_from_time( r_str, max_len, brevity_level, v2d_pos / (float)FPS, FPS, U.timecode_style); } @@ -460,10 +483,11 @@ float UI_view2d_grid_resolution_y__values(const struct View2D *v2d) /* Line Drawing API **************************************************/ -void UI_view2d_draw_lines_x__discrete_values(const View2D *v2d) +void UI_view2d_draw_lines_x__discrete_values(const View2D *v2d, bool display_minor_lines) { const uint major_line_distance = view2d_major_step_x__discrete(v2d); - view2d_draw_lines(v2d, major_line_distance, major_line_distance > 1, 'v'); + view2d_draw_lines( + v2d, major_line_distance, display_minor_lines && (major_line_distance > 1), 'v'); } void UI_view2d_draw_lines_x__values(const View2D *v2d) @@ -478,21 +502,25 @@ void UI_view2d_draw_lines_y__values(const View2D *v2d) view2d_draw_lines(v2d, major_line_distance, true, 'h'); } -void UI_view2d_draw_lines_x__discrete_time(const View2D *v2d, const Scene *scene) +void UI_view2d_draw_lines_x__discrete_time(const View2D *v2d, + const Scene *scene, + bool display_minor_lines) { const float major_line_distance = view2d_major_step_x__time(v2d, scene); - view2d_draw_lines(v2d, major_line_distance, major_line_distance > 1, 'v'); + view2d_draw_lines( + v2d, major_line_distance, display_minor_lines && (major_line_distance > 1), 'v'); } void UI_view2d_draw_lines_x__discrete_frames_or_seconds(const View2D *v2d, const Scene *scene, - bool display_seconds) + bool display_seconds, + bool display_minor_lines) { if (display_seconds) { - UI_view2d_draw_lines_x__discrete_time(v2d, scene); + UI_view2d_draw_lines_x__discrete_time(v2d, scene, display_minor_lines); } else { - UI_view2d_draw_lines_x__discrete_values(v2d); + UI_view2d_draw_lines_x__discrete_values(v2d, display_minor_lines); } } @@ -501,7 +529,7 @@ void UI_view2d_draw_lines_x__frames_or_seconds(const View2D *v2d, bool display_seconds) { if (display_seconds) { - UI_view2d_draw_lines_x__discrete_time(v2d, scene); + UI_view2d_draw_lines_x__discrete_time(v2d, scene, true); } else { UI_view2d_draw_lines_x__values(v2d); diff --git a/source/blender/editors/interface/view2d_edge_pan.c b/source/blender/editors/interface/view2d_edge_pan.c index ca32a754f1d..8d8b9a4fe48 100644 --- a/source/blender/editors/interface/view2d_edge_pan.c +++ b/source/blender/editors/interface/view2d_edge_pan.c @@ -71,7 +71,8 @@ void UI_view2d_edge_pan_init(bContext *C, float outside_pad, float speed_ramp, float max_speed, - float delay) + float delay, + float zoom_influence) { if (!UI_view2d_edge_pan_poll(C)) { return; @@ -89,6 +90,9 @@ void UI_view2d_edge_pan_init(bContext *C, vpd->speed_ramp = speed_ramp; vpd->max_speed = max_speed; vpd->delay = delay; + vpd->zoom_influence = zoom_influence; + + vpd->enabled = false; /* Calculate translation factor, based on size of view. */ const float winx = (float)(BLI_rcti_size_x(&vpd->region->winrct) + 1); @@ -104,6 +108,7 @@ void UI_view2d_edge_pan_reset(View2DEdgePanData *vpd) vpd->edge_pan_start_time_x = 0.0; vpd->edge_pan_start_time_y = 0.0; vpd->edge_pan_last_time = PIL_check_seconds_timer(); + vpd->initial_rect = vpd->region->v2d.cur; } /** @@ -160,7 +165,7 @@ static float edge_pan_speed(View2DEdgePanData *vpd, distance = min - event_loc; } else { - BLI_assert(!"Calculating speed outside of pan zones"); + BLI_assert_msg(0, "Calculating speed outside of pan zones"); return 0.0f; } float distance_factor = distance / (vpd->speed_ramp * U.widget_unit); @@ -168,9 +173,17 @@ static float edge_pan_speed(View2DEdgePanData *vpd, /* Apply a fade in to the speed based on a start time delay. */ const double start_time = x_dir ? vpd->edge_pan_start_time_x : vpd->edge_pan_start_time_y; - const float delay_factor = smootherstep(vpd->delay, (float)(current_time - start_time)); + const float delay_factor = vpd->delay > 0.01f ? + smootherstep(vpd->delay, (float)(current_time - start_time)) : + 1.0f; + + /* Zoom factor increases speed when zooming in and decreases speed when zooming out. */ + const float zoomx = (float)(BLI_rcti_size_x(®ion->winrct) + 1) / + BLI_rctf_size_x(®ion->v2d.cur); + const float zoom_factor = 1.0f + CLAMPIS(vpd->zoom_influence, 0.0f, 1.0f) * (zoomx - 1.0f); - return distance_factor * delay_factor * vpd->max_speed * U.widget_unit * (float)U.dpi_fac; + return distance_factor * delay_factor * zoom_factor * vpd->max_speed * U.widget_unit * + (float)U.dpi_fac; } static void edge_pan_apply_delta(bContext *C, View2DEdgePanData *vpd, float dx, float dy) @@ -206,7 +219,7 @@ static void edge_pan_apply_delta(bContext *C, View2DEdgePanData *vpd, float dx, UI_view2d_sync(vpd->screen, vpd->area, v2d, V2D_LOCK_COPY); } -void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, int x, int y) +void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, const int xy[2]) { ARegion *region = vpd->region; @@ -216,20 +229,27 @@ void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, int x, int y) BLI_rcti_pad(&inside_rect, -vpd->inside_pad * U.widget_unit, -vpd->inside_pad * U.widget_unit); BLI_rcti_pad(&outside_rect, vpd->outside_pad * U.widget_unit, vpd->outside_pad * U.widget_unit); + /* Check if we can actually start the edge pan (e.g. adding nodes outside the view will start + * disabled). */ + if (BLI_rcti_isect_pt_v(&inside_rect, xy)) { + /* We are inside once, can start. */ + vpd->enabled = true; + } + int pan_dir_x = 0; int pan_dir_y = 0; - if ((vpd->outside_pad == 0) || BLI_rcti_isect_pt(&outside_rect, x, y)) { + if (vpd->enabled && ((vpd->outside_pad == 0) || BLI_rcti_isect_pt_v(&outside_rect, xy))) { /* Find whether the mouse is beyond X and Y edges. */ - if (x > inside_rect.xmax) { + if (xy[0] > inside_rect.xmax) { pan_dir_x = 1; } - else if (x < inside_rect.xmin) { + else if (xy[0] < inside_rect.xmin) { pan_dir_x = -1; } - if (y > inside_rect.ymax) { + if (xy[1] > inside_rect.ymax) { pan_dir_y = 1; } - else if (y < inside_rect.ymin) { + else if (xy[1] < inside_rect.ymin) { pan_dir_y = -1; } } @@ -241,11 +261,11 @@ void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, int x, int y) const float dtime = (float)(current_time - vpd->edge_pan_last_time); float dx = 0.0f, dy = 0.0f; if (pan_dir_x != 0) { - const float speed = edge_pan_speed(vpd, x, true, current_time); + const float speed = edge_pan_speed(vpd, xy[0], true, current_time); dx = dtime * speed * (float)pan_dir_x; } if (pan_dir_y != 0) { - const float speed = edge_pan_speed(vpd, y, false, current_time); + const float speed = edge_pan_speed(vpd, xy[1], false, current_time); dy = dtime * speed * (float)pan_dir_y; } vpd->edge_pan_last_time = current_time; @@ -261,7 +281,28 @@ void UI_view2d_edge_pan_apply_event(bContext *C, View2DEdgePanData *vpd, const w return; } - UI_view2d_edge_pan_apply(C, vpd, event->x, event->y); + UI_view2d_edge_pan_apply(C, vpd, event->xy); +} + +void UI_view2d_edge_pan_cancel(bContext *C, View2DEdgePanData *vpd) +{ + View2D *v2d = vpd->v2d; + if (!v2d) { + return; + } + + v2d->cur = vpd->initial_rect; + + /* Inform v2d about changes after this operation. */ + UI_view2d_curRect_changed(C, v2d); + + /* Don't rebuild full tree in outliner, since we're just changing our view. */ + ED_region_tag_redraw_no_rebuild(vpd->region); + + /* Request updates to be done. */ + WM_event_add_mousemove(CTX_wm_window(C)); + + UI_view2d_sync(vpd->screen, vpd->area, v2d, V2D_LOCK_COPY); } void UI_view2d_edge_pan_operator_properties(wmOperatorType *ot) @@ -272,7 +313,8 @@ void UI_view2d_edge_pan_operator_properties(wmOperatorType *ot) /*outside_pad*/ 0.0f, /*speed_ramp*/ 1.0f, /*max_speed*/ 500.0f, - /*delay*/ 1.0f); + /*delay*/ 1.0f, + /*zoom_influence*/ 0.0f); } void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot, @@ -280,7 +322,8 @@ void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot, float outside_pad, float speed_ramp, float max_speed, - float delay) + float delay, + float zoom_influence) { RNA_def_float( ot->srna, @@ -329,6 +372,15 @@ void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot, "Delay in seconds before maximum speed is reached", 0.0f, 10.0f); + RNA_def_float(ot->srna, + "zoom_influence", + zoom_influence, + 0.0f, + 1.0f, + "Zoom Influence", + "Influence of the zoom factor on scroll speed", + 0.0f, + 1.0f); } void UI_view2d_edge_pan_operator_init(bContext *C, View2DEdgePanData *vpd, wmOperator *op) @@ -339,7 +391,8 @@ void UI_view2d_edge_pan_operator_init(bContext *C, View2DEdgePanData *vpd, wmOpe RNA_float_get(op->ptr, "outside_padding"), RNA_float_get(op->ptr, "speed_ramp"), RNA_float_get(op->ptr, "max_speed"), - RNA_float_get(op->ptr, "delay")); + RNA_float_get(op->ptr, "delay"), + RNA_float_get(op->ptr, "zoom_influence")); } /** \} */ diff --git a/source/blender/editors/interface/view2d_gizmo_navigate.c b/source/blender/editors/interface/view2d_gizmo_navigate.c index 30b4a7c097a..3ff5b471731 100644 --- a/source/blender/editors/interface/view2d_gizmo_navigate.c +++ b/source/blender/editors/interface/view2d_gizmo_navigate.c @@ -127,11 +127,24 @@ struct NavigateWidgetGroup { int region_size[2]; }; -static bool WIDGETGROUP_navigate_poll(const bContext *UNUSED(C), wmGizmoGroupType *UNUSED(gzgt)) +static bool WIDGETGROUP_navigate_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt)) { if ((U.uiflag & USER_SHOW_GIZMO_NAVIGATE) == 0) { return false; } + ScrArea *area = CTX_wm_area(C); + if (area == NULL) { + return false; + } + switch (area->spacetype) { + case SPACE_SEQ: { + const SpaceSeq *sseq = area->spacedata.first; + if (sseq->gizmo_flag & (SEQ_GIZMO_HIDE | SEQ_GIZMO_HIDE_NAVIGATE)) { + return false; + } + break; + } + } return true; } diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c index 69acfc657dc..0bca4e327cc 100644 --- a/source/blender/editors/interface/view2d_ops.c +++ b/source/blender/editors/interface/view2d_ops.c @@ -147,6 +147,8 @@ static void view_pan_init(bContext *C, wmOperator *op) const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1); vpd->facx = (BLI_rctf_size_x(&vpd->v2d->cur)) / winx; vpd->facy = (BLI_rctf_size_y(&vpd->v2d->cur)) / winy; + + vpd->v2d->flag |= V2D_IS_NAVIGATING; } /* apply transform to view (i.e. adjust 'cur' rect) */ @@ -190,6 +192,8 @@ static void view_pan_apply(bContext *C, wmOperator *op) /* Cleanup temp custom-data. */ static void view_pan_exit(wmOperator *op) { + v2dViewPanData *vpd = op->customdata; + vpd->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_SAFE_FREE(op->customdata); } @@ -220,13 +224,13 @@ static int view_pan_invoke(bContext *C, wmOperator *op, const wmEvent *event) View2D *v2d = vpd->v2d; /* set initial settings */ - vpd->startx = vpd->lastx = event->x; - vpd->starty = vpd->lasty = event->y; + vpd->startx = vpd->lastx = event->xy[0]; + vpd->starty = vpd->lasty = event->xy[1]; vpd->invoke_event = event->type; if (event->type == MOUSEPAN) { - RNA_int_set(op->ptr, "deltax", event->prevx - event->x); - RNA_int_set(op->ptr, "deltay", event->prevy - event->y); + RNA_int_set(op->ptr, "deltax", event->prev_xy[0] - event->xy[0]); + RNA_int_set(op->ptr, "deltay", event->prev_xy[1] - event->xy[1]); view_pan_apply(C, op); view_pan_exit(op); @@ -262,16 +266,16 @@ static int view_pan_modal(bContext *C, wmOperator *op, const wmEvent *event) switch (event->type) { case MOUSEMOVE: { /* calculate new delta transform, then store mouse-coordinates for next-time */ - RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x)); - RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y)); + RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->xy[0])); + RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->xy[1])); - vpd->lastx = event->x; - vpd->lasty = event->y; + vpd->lastx = event->xy[0]; + vpd->lasty = event->xy[1]; view_pan_apply(C, op); break; } - /* XXX - Mode switching isn't implemented. See comments in 36818. + /* XXX: Mode switching isn't implemented. See comments in 36818. * switch to zoom */ #if 0 case LEFTMOUSE: @@ -358,6 +362,7 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event View2DEdgePanData *vpd = op->customdata; if (event->val == KM_RELEASE || event->type == EVT_ESCKEY) { + vpd->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_SAFE_FREE(op->customdata); return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); } @@ -371,6 +376,8 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event static void view_edge_pan_cancel(bContext *UNUSED(C), wmOperator *op) { + v2dViewPanData *vpd = op->customdata; + vpd->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_SAFE_FREE(op->customdata); } @@ -680,6 +687,8 @@ static void view_zoomdrag_init(bContext *C, wmOperator *op) vzd->v2d = &vzd->region->v2d; /* False by default. Interactive callbacks (ie invoke()) can set it to true. */ vzd->zoom_to_mouse_pos = false; + + vzd->v2d->flag |= V2D_IS_NAVIGATING; } /* apply transform to view (i.e. adjust 'cur' rect) */ @@ -809,7 +818,8 @@ static void view_zoomstep_apply(bContext *C, wmOperator *op) static void view_zoomstep_exit(wmOperator *op) { UI_view2d_zoom_cache_reset(); - + v2dViewZoomData *vzd = op->customdata; + vzd->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_SAFE_FREE(op->customdata); } @@ -1041,6 +1051,7 @@ static void view_zoomdrag_exit(bContext *C, wmOperator *op) if (op->customdata) { v2dViewZoomData *vzd = op->customdata; + vzd->v2d->flag &= ~V2D_IS_NAVIGATING; if (vzd->timer) { WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer); @@ -1086,8 +1097,8 @@ static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *even } if (ELEM(event->type, MOUSEZOOM, MOUSEPAN)) { - vzd->lastx = event->prevx; - vzd->lasty = event->prevy; + vzd->lastx = event->prev_xy[0]; + vzd->lasty = event->prev_xy[1]; float facx, facy; float zoomfac = 0.01f; @@ -1145,8 +1156,8 @@ static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *even } /* set initial settings */ - vzd->lastx = event->x; - vzd->lasty = event->y; + vzd->lastx = event->xy[0]; + vzd->lasty = event->xy[1]; RNA_float_set(op->ptr, "deltax", 0); RNA_float_set(op->ptr, "deltay", 0); @@ -1205,12 +1216,12 @@ static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event /* x-axis transform */ dist = BLI_rcti_size_x(&v2d->mask) / 2.0f; len_old[0] = zoomfac * fabsf(vzd->lastx - vzd->region->winrct.xmin - dist); - len_new[0] = zoomfac * fabsf(event->x - vzd->region->winrct.xmin - dist); + len_new[0] = zoomfac * fabsf(event->xy[0] - vzd->region->winrct.xmin - dist); /* y-axis transform */ dist = BLI_rcti_size_y(&v2d->mask) / 2.0f; len_old[1] = zoomfac * fabsf(vzd->lasty - vzd->region->winrct.ymin - dist); - len_new[1] = zoomfac * fabsf(event->y - vzd->region->winrct.ymin - dist); + len_new[1] = zoomfac * fabsf(event->xy[1] - vzd->region->winrct.ymin - dist); /* Calculate distance */ if (v2d->keepzoom & V2D_KEEPASPECT) { @@ -1226,8 +1237,8 @@ static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event dy *= BLI_rctf_size_y(&v2d->cur); } else { /* USER_ZOOM_CONTINUE or USER_ZOOM_DOLLY */ - float facx = zoomfac * (event->x - vzd->lastx); - float facy = zoomfac * (event->y - vzd->lasty); + float facx = zoomfac * (event->xy[0] - vzd->lastx); + float facy = zoomfac * (event->xy[1] - vzd->lasty); /* Only respect user setting zoom axis if the view does not have any zoom restrictions * any will be scaled uniformly */ @@ -1273,8 +1284,8 @@ static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event * to starting point to determine rate of change. */ if (U.viewzoom != USER_ZOOM_CONTINUE) { /* XXX store this setting as RNA prop? */ - vzd->lastx = event->x; - vzd->lasty = event->y; + vzd->lastx = event->xy[0]; + vzd->lasty = event->xy[1]; } /* apply zooming */ @@ -1527,7 +1538,7 @@ struct SmoothView2DStore { /** * function to get a factor out of a rectangle * - * note: this doesn't always work as well as it might because the target size + * NOTE: this doesn't always work as well as it might because the target size * may not be reached because of clamping the desired rect, we _could_ * attempt to clamp the rect before working out the zoom factor but its * not really worthwhile for the few cases this happens. @@ -1745,7 +1756,7 @@ typedef struct v2dScrollerMove { * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, * as we only need focus bubble info. * - * \warning: The start of this struct must not change, + * \warning The start of this struct must not change, * so that it stays in sync with the 'real' version. * For now, we don't need to have a separate (internal) header for structs like this... */ @@ -1835,7 +1846,7 @@ static bool scroller_activate_poll(bContext *C) wmEvent *event = win->eventstate; /* check if mouse in scrollbars, if they're enabled */ - return (UI_view2d_mouse_in_scrollers(region, v2d, event->x, event->y) != 0); + return (UI_view2d_mouse_in_scrollers(region, v2d, event->xy) != 0); } /* initialize customdata for scroller manipulation operator */ @@ -1857,8 +1868,8 @@ static void scroller_activate_init(bContext *C, vsm->scroller = in_scroller; /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */ - vsm->lastx = event->x; - vsm->lasty = event->y; + vsm->lastx = event->xy[0]; + vsm->lasty = event->xy[1]; /* 'zone' depends on where mouse is relative to bubble * - zooming must be allowed on this axis, otherwise, default to pan */ @@ -1911,6 +1922,8 @@ static void scroller_activate_init(bContext *C, vsm->scrollbar_orig = ((scrollers.vert_max + scrollers.vert_min) / 2) + region->winrct.ymin; } + vsm->v2d->flag |= V2D_IS_NAVIGATING; + ED_region_tag_redraw_no_rebuild(region); } @@ -1921,6 +1934,7 @@ static void scroller_activate_exit(bContext *C, wmOperator *op) v2dScrollerMove *vsm = op->customdata; vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE); + vsm->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_freeN(op->customdata); op->customdata = NULL; @@ -2010,11 +2024,11 @@ static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *e switch (vsm->scroller) { case 'h': /* horizontal scroller - so only horizontal movement * ('cur' moves opposite to mouse) */ - vsm->delta = (float)(event->x - vsm->lastx); + vsm->delta = (float)(event->xy[0] - vsm->lastx); break; case 'v': /* vertical scroller - so only vertical movement * ('cur' moves opposite to mouse) */ - vsm->delta = (float)(event->y - vsm->lasty); + vsm->delta = (float)(event->xy[1] - vsm->lasty); break; } } @@ -2023,18 +2037,18 @@ static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *e switch (vsm->scroller) { case 'h': /* horizontal scroller - so only horizontal movement * ('cur' moves with mouse) */ - vsm->delta = (float)(vsm->lastx - event->x); + vsm->delta = (float)(vsm->lastx - event->xy[0]); break; case 'v': /* vertical scroller - so only vertical movement * ('cur' moves with to mouse) */ - vsm->delta = (float)(vsm->lasty - event->y); + vsm->delta = (float)(vsm->lasty - event->xy[1]); break; } } /* store previous coordinates */ - vsm->lastx = event->x; - vsm->lasty = event->y; + vsm->lastx = event->xy[0]; + vsm->lasty = event->xy[1]; scroller_activate_apply(C, op); break; @@ -2076,7 +2090,7 @@ static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent * View2D *v2d = ®ion->v2d; /* check if mouse in scrollbars, if they're enabled */ - const char in_scroller = UI_view2d_mouse_in_scrollers(region, v2d, event->x, event->y); + const char in_scroller = UI_view2d_mouse_in_scrollers(region, v2d, event->xy); /* if in a scroller, init customdata then set modal handler which will * catch mouse-down to start doing useful stuff */ @@ -2090,11 +2104,11 @@ static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent * switch (vsm->scroller) { case 'h': /* horizontal scroller - so only horizontal movement * ('cur' moves opposite to mouse) */ - vsm->delta = (float)(event->x - vsm->scrollbar_orig); + vsm->delta = (float)(event->xy[0] - vsm->scrollbar_orig); break; case 'v': /* vertical scroller - so only vertical movement * ('cur' moves opposite to mouse) */ - vsm->delta = (float)(event->y - vsm->scrollbar_orig); + vsm->delta = (float)(event->xy[1] - vsm->scrollbar_orig); break; } scroller_activate_apply(C, op); @@ -2132,7 +2146,7 @@ static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent * scroller_activate_exit(C, op); /* can't catch this event for ourselves, so let it go to someone else? */ - /* XXX note: if handlers use mask rect to clip input, input will fail for this case */ + /* XXX NOTE: if handlers use mask rect to clip input, input will fail for this case. */ return OPERATOR_PASS_THROUGH; } |