diff options
Diffstat (limited to 'source/blender/editors/interface')
27 files changed, 2697 insertions, 1152 deletions
diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 5011a50ed73..39dd6143eb9 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -66,6 +66,8 @@ set(SRC interface_region_tooltip.c interface_regions.c interface_style.c + interface_template_asset_view.cc + interface_template_list.cc interface_template_search_menu.c interface_template_search_operator.c interface_templates.c @@ -102,7 +104,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 6f341edf11b..ddde4f5a9dc 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -131,12 +131,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 +144,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 +168,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 +262,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 +497,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 +608,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; @@ -862,7 +883,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... */ } /** @@ -1382,11 +1403,11 @@ static bool ui_but_event_property_operator_string(const bContext *C, else { /* special exceptions for common nested data in editors... */ if (RNA_struct_is_a(ptr->type, &RNA_DopeSheet)) { - /* dopesheet filtering options... */ + /* Dope-sheet 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... */ + /* File-browser options. */ data_path = BLI_sprintfN("space_data.params.%s", RNA_property_identifier(prop)); } } @@ -1945,8 +1966,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; @@ -2432,7 +2453,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)); @@ -3140,7 +3161,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 { @@ -3223,7 +3244,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 @@ -3440,6 +3461,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) { @@ -3542,7 +3572,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; } @@ -4106,7 +4136,6 @@ 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_POPOVER)) { but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); @@ -4247,7 +4276,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); @@ -4550,7 +4579,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). */ } } @@ -6757,7 +6786,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)) { diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c index 775e3923edc..3049e2bd7b8 100644 --- a/source/blender/editors/interface/interface_context_menu.c +++ b/source/blender/editors/interface/interface_context_menu.c @@ -417,7 +417,7 @@ 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. */ + /* 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; @@ -425,7 +425,7 @@ static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um) member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); } 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); @@ -494,7 +494,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 +560,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 */ @@ -1226,6 +1226,20 @@ 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. */ + ARegion *region = 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->x, event->y) != + 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)); diff --git a/source/blender/editors/interface/interface_draw.c b/source/blender/editors/interface/interface_draw.c index 05b6fcdded1..655fdda3069 100644 --- a/source/blender/editors/interface/interface_draw.c +++ b/source/blender/editors/interface/interface_draw.c @@ -229,7 +229,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 +592,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); @@ -2223,9 +2223,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) { @@ -2350,7 +2349,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_handlers.c b/source/blender/editors/interface/interface_handlers.c index 45609d96840..4f8bb6342f7 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) /** @@ -430,6 +459,8 @@ typedef struct uiHandleButtonData { uiSelectContextStore select_others; #endif + struct uiBlockInteraction_Handle *custom_interaction_handle; + /* Text field undo. */ struct uiUndoStack_Text *undo_stack_text; @@ -471,6 +502,9 @@ typedef struct uiAfterFunc { void *search_arg; uiFreeArgFunc search_arg_free_fn; + uiBlockInteraction_CallbackData custom_interaction_callbacks; + uiBlockInteraction_Handle *custom_interaction_handle; + bContextStore *context; char undostr[BKE_UNDO_STR_MAX]; @@ -733,23 +767,34 @@ 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, + int 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; +void ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext) +{ + ui_handle_afterfunc_add_operator_ex(ot, NULL, opcontext, NULL); } static void popup_check(bContext *C, wmOperator *op) @@ -769,72 +814,95 @@ 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); } + + but->optype = NULL; + but->opcontext = 0; + but->opptr = NULL; } /* typically call ui_apply_but_undo(), ui_apply_but_autokey() */ @@ -997,6 +1065,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 +1156,42 @@ static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleBu data->applied = true; } +/** + * \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) { @@ -1350,7 +1466,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 +1618,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 +1664,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[0], xy_input[1], true, NULL, NULL); if (but) { if (but->flag & UI_BUT_DRAG_LOCK) { @@ -1619,7 +1735,7 @@ static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void 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); + region, drag_info->xy_init[0], drag_info->xy_init[1], true, NULL, NULL); if (but) { ui_apply_but_undo(but); @@ -1686,7 +1802,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. */ @@ -1987,7 +2103,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; @@ -2181,9 +2297,11 @@ 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_LISTROW: + ui_apply_but_LISTROW(C, block, but, data); + break; case UI_BTYPE_DATASETROW: ui_apply_but_ROW(C, block, but, data); break; @@ -2283,6 +2401,11 @@ static void ui_apply_but( uiButCurveProfile *but_profile = (uiButCurveProfile *)but; but_profile->edit_profile = editprofile; } + + if (data->custom_interaction_handle != NULL) { + ui_block_interaction_update( + C, &block->custom_interaction_callbacks, data->custom_interaction_handle); + } } /** \} */ @@ -2297,7 +2420,7 @@ static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleB ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */ LISTBASE_FOREACH (wmDrag *, wmd, drags) { - /* TODO asset dropping. */ + /* 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)) { @@ -2428,7 +2551,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'); } @@ -3268,7 +3391,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? */ @@ -3284,7 +3407,7 @@ static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but)) 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); @@ -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 @@ -4210,7 +4338,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->x, event->y, true, NULL, NULL); if (labelbut && labelbut->type == UI_BTYPE_TEXT && !(labelbut->flag & UI_BUT_DISABLED)) { /* exit listrow */ @@ -4284,7 +4412,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; } @@ -4334,7 +4462,7 @@ static bool ui_do_but_ANY_drag_toggle( } } 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); @@ -4677,7 +4805,7 @@ static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, con { 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)) { @@ -4700,10 +4828,19 @@ static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, con 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 +4989,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; @@ -5362,6 +5501,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; @@ -5763,7 +5904,7 @@ 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. @@ -5836,7 +5977,7 @@ 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); @@ -6021,7 +6162,7 @@ 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)) { @@ -6435,7 +6576,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; } @@ -6951,8 +7092,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 +7151,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 */ @@ -7219,8 +7360,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 +7422,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 */ @@ -7773,7 +7914,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * if ((event->type == RIGHTMOUSE) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) && (event->val == KM_PRESS)) { /* 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; } } @@ -7853,6 +7994,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: @@ -8239,6 +8381,16 @@ static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState s 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,6 +8619,21 @@ static void button_activate_exit( ED_region_tag_redraw_no_rebuild(data->region); ED_region_tag_refresh_ui(data->region); + 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 */ if (but->active) { MEM_freeN(but->active); @@ -8614,9 +8781,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 +8889,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); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -8805,7 +8992,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; @@ -9174,7 +9361,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 +9373,149 @@ 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->prevclickx : &event->x; + uiBut *listrow = ui_list_row_find_mouse_over(region, mouse_xy[0], mouse_xy[1]); + 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->prevclickx : &event->x; + const uiBut *hovered_but = ui_but_find_mouse_over_ex( + region, mouse_xy[0], mouse_xy[1], 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; @@ -9219,22 +9549,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 +9608,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; @@ -10255,7 +10577,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; } @@ -11314,3 +11636,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..43ac646f053 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -1180,7 +1180,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); @@ -1500,7 +1500,7 @@ static void icon_draw_rect(float x, /* 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 +1519,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); @@ -2201,7 +2201,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 +2294,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 +2302,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: diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index b9a44b5bce9..a07f924e65b 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -262,7 +262,7 @@ struct uiBut { ListBase extra_op_icons; /** #uiButExtraOpIcon */ - /* Draggable data, type is WM_DRAG_... */ + /* Drag-able data, type is WM_DRAG_... */ char dragtype; short dragflag; void *dragpoin; @@ -511,6 +511,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 *); @@ -594,11 +597,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, @@ -617,6 +628,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); @@ -928,9 +942,7 @@ 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, int 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); @@ -943,6 +955,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); @@ -1042,8 +1055,18 @@ 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) @@ -1125,19 +1148,32 @@ bool ui_but_contains_point_px_icon(const uiBut *but, bool ui_but_contains_point_px(const uiBut *but, const struct ARegion *region, int x, int y) 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, + int x, + int y) ATTR_WARN_UNUSED_RESULT; +uiBut *ui_list_row_find_from_index(const struct ARegion *region, + const int index, + uiBut *listbox) ATTR_WARN_UNUSED_RESULT; + +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 bool labeledit, + const uiButFindPollFn find_poll, + const void *find_custom_data) 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, + int x, + int y) ATTR_WARN_UNUSED_RESULT; bool ui_but_contains_password(const uiBut *but) ATTR_WARN_UNUSED_RESULT; @@ -1149,6 +1185,7 @@ 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; @@ -1176,7 +1213,7 @@ 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); @@ -1204,6 +1241,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. */ diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index a17a527c868..8b9539f1d33 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -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; @@ -643,7 +643,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 +662,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)) { @@ -1411,7 +1411,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. */ @@ -1818,7 +1818,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 +1984,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 +2062,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 +2353,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 +2831,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; } @@ -3147,7 +3147,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; } @@ -4639,7 +4639,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; diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index ce5c17a0718..376a41ff9bb 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 * \{ */ @@ -1215,7 +1241,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,7 +1366,7 @@ 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 */ + * but since this is a developer 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; @@ -1379,11 +1405,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; @@ -1836,6 +1858,64 @@ static void UI_OT_drop_color(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \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 Operator & Keymap Registration * \{ */ @@ -1860,6 +1940,8 @@ 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); + /* 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..97d01ac3763 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) { @@ -1892,7 +1892,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); } @@ -2563,7 +2563,7 @@ PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wm /** \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; @@ -2653,7 +2653,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..8534c95b6fd 100644 --- a/source/blender/editors/interface/interface_query.c +++ b/source/blender/editors/interface/interface_query.c @@ -78,7 +78,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; } @@ -266,11 +266,29 @@ 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 bool labeledit, + const uiButFindPollFn find_poll, + const void *find_custom_data) { uiBut *butover = NULL; @@ -282,6 +300,9 @@ uiBut *ui_but_find_mouse_over_ex(const ARegion *region, 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 +331,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->x, event->y, event->ctrl != 0, NULL, NULL); } uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px) @@ -351,7 +372,7 @@ 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, int x, int y) { if (!ui_region_contains_point_px(region, x, y)) { return NULL; @@ -369,11 +390,77 @@ 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->x, event->y); } +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 x, const int y) +{ + return ui_but_find_mouse_over_ex(region, x, y, 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); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -485,6 +572,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) && @@ -588,10 +686,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; } } 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_popup.c b/source/blender/editors/interface/interface_region_menu_popup.c index 6e60ca79aaf..d3c1a97e957 100644 --- a/source/blender/editors/interface/interface_region_menu_popup.c +++ b/source/blender/editors/interface/interface_region_menu_popup.c @@ -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); diff --git a/source/blender/editors/interface/interface_region_popover.c b/source/blender/editors/interface/interface_region_popover.c index a9f72233cb1..b8c4d8ddb09 100644 --- a/source/blender/editors/interface/interface_region_popover.c +++ b/source/blender/editors/interface/interface_region_popover.c @@ -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 60e51244384..55a162c883a 100644 --- a/source/blender/editors/interface/interface_region_popup.c +++ b/source/blender/editors/interface/interface_region_popup.c @@ -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; diff --git a/source/blender/editors/interface/interface_region_search.c b/source/blender/editors/interface/interface_region_search.c index c35dbc5d7a6..c863b1f8bdf 100644 --- a/source/blender/editors/interface/interface_region_search.c +++ b/source/blender/editors/interface/interface_region_search.c @@ -599,8 +599,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 */ @@ -684,13 +688,13 @@ 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); } } @@ -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); } } diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c index bf3425dd3eb..10bc3760b42 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 = 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..5a05813f947 --- /dev/null +++ b/source/blender/editors/interface/interface_template_asset_view.cc @@ -0,0 +1,272 @@ +/* + * 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; + bScreen *screen; +}; + +static void asset_view_item_but_drag_set(uiBut *but, + AssetViewListData *list_data, + AssetHandle *asset_handle) +{ + ID *id = asset_handle->file_data->id; + if (id != nullptr) { + UI_but_drag_set_id(but, id); + return; + } + + const blender::StringRef asset_list_path = ED_assetlist_library_path(&list_data->asset_library); + char blend_path[FILE_MAX_LIBEXTRA]; + + char path[FILE_MAX_LIBEXTRA]; + BLI_join_dirfile(path, sizeof(path), asset_list_path.data(), asset_handle->file_data->relpath); + if (BLO_library_path_explode(path, blend_path, nullptr, nullptr)) { + ImBuf *imbuf = ED_assetlist_asset_image_get(asset_handle); + UI_but_drag_set_asset(but, + asset_handle->file_data->name, + BLI_strdup(blend_path), + asset_handle->file_data->blentype, + FILE_ASSET_IMPORT_APPEND, + asset_handle->file_data->preview_icon_id, + 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); + /* 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; + uiBut *but = uiDefIconTextBut(block, + UI_BTYPE_PREVIEW_TILE, + 0, + asset_handle->file_data->preview_icon_id, + asset_handle->file_data->name, + 0, + 0, + size_x, + size_y, + nullptr, + 0, + 0, + 0, + 0, + ""); + ui_def_but_icon(but, + asset_handle->file_data->preview_icon_id, + /* 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, 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, + 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_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, [&](FileDirEntry &file) { + PointerRNA itemptr, fileptr; + RNA_property_collection_add(&assets_dataptr, assets_prop, &itemptr); + + RNA_pointer_create(nullptr, &RNA_FileSelectEntry, &file, &fileptr); + RNA_pointer_set(&itemptr, "file_data", fileptr); + + /* Copy name from file to asset-handle name ID-property. */ + char name[MAX_NAME]; + PropertyRNA *file_name_prop = RNA_struct_name_property(fileptr.type); + RNA_property_string_get(&fileptr, file_name_prop, name); + PropertyRNA *asset_name_prop = RNA_struct_name_property(&RNA_AssetHandle); + RNA_property_string_set(&itemptr, asset_name_prop, name); + + 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 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 = ED_asset_library_reference_from_enum_value( + RNA_property_enum_get(asset_library_dataptr, asset_library_prop)); + + uiLayout *row = uiLayoutRow(col, true); + uiItemFullR(row, asset_library_dataptr, asset_library_prop, RNA_NO_INDEX, 0, 0, "", 0); + if (asset_library.type != ASSET_LIBRARY_LOCAL) { + uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_list_refresh"); + } + + ED_assetlist_storage_fetch(&asset_library, filter_settings, C); + ED_assetlist_ensure_previews_job(&asset_library, C); + const int tot_items = ED_assetlist_size(&asset_library); + + asset_view_template_refresh_asset_collection(asset_library, *assets_dataptr, assets_propname); + + AssetViewListData *list_data = (AssetViewListData *)MEM_mallocN(sizeof(*list_data), + "AssetViewListData"); + list_data->asset_library = asset_library; + list_data->screen = CTX_wm_screen(C); + + /* 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, + UI_TEMPLATE_LIST_NO_GRIP, + 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_list.cc b/source/blender/editors/interface/interface_template_list.cc new file mode 100644 index 00000000000..eaab33e32c9 --- /dev/null +++ b/source/blender/editors/interface/interface_template_list.cc @@ -0,0 +1,1310 @@ +/* + * 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_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 = 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); + + /* 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; + + 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; + } + + if (glob) { + 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) { + WM_operator_properties_alloc(&dyn_data->custom_activate_opptr, 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) { + WM_operator_properties_alloc(&dyn_data->custom_drag_opptr, 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..3105891142f 100644 --- a/source/blender/editors/interface/interface_template_search_menu.c +++ b/source/blender/editors/interface/interface_template_search_menu.c @@ -873,7 +873,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); @@ -1037,7 +1037,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 +1058,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; } diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 5232d4310a3..766840909cc 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -877,7 +877,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. */ @@ -2399,8 +2399,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 +2556,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; } @@ -4015,23 +4015,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 +5645,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, MEM_freeN); - } - - 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 * \{ */ diff --git a/source/blender/editors/interface/interface_utils.c b/source/blender/editors/interface/interface_utils.c index 6ad1de68a1f..93a790b53d0 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" @@ -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_widgets.c b/source/blender/editors/interface/interface_widgets.c index 1ab12a2c8ea..0decaa5e45d 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -106,6 +106,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, @@ -238,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]; @@ -400,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}; @@ -524,7 +525,7 @@ void UI_draw_anti_tria( 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); @@ -769,7 +770,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]; @@ -1378,8 +1379,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); @@ -1421,6 +1420,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 */ @@ -1642,7 +1642,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( @@ -2007,14 +2007,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); @@ -2030,8 +2031,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) { @@ -2056,14 +2060,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 } } @@ -2071,7 +2089,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; } @@ -2096,6 +2114,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; @@ -2109,16 +2129,24 @@ 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 @@ -2494,7 +2522,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); @@ -3089,7 +3117,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]); @@ -3693,10 +3721,6 @@ 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( @@ -3996,6 +4020,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), @@ -4307,7 +4339,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); @@ -4461,6 +4493,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; @@ -4756,6 +4795,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; @@ -4909,13 +4952,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); + } } } @@ -5352,7 +5397,7 @@ void ui_draw_menu_item(const uiFontStyle *fstyle, } } else { - BLI_assert(!"Unknwon menu item separator type"); + BLI_assert_msg(0, "Unknwon menu item separator type"); } if (fstyle->kerning == 1) { @@ -5437,17 +5482,20 @@ 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); /* draw icon in rect above the space reserved for the label */ rect->ymin += text_size; @@ -5459,8 +5507,6 @@ void ui_draw_preview_item( 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) { @@ -5479,11 +5525,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/view2d.c b/source/blender/editors/interface/view2d.c index 5eb20ae601b..e9804840801 100644 --- a/source/blender/editors/interface/view2d.c +++ b/source/blender/editors/interface/view2d.c @@ -347,7 +347,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 +717,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, @@ -1059,7 +1059,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 +1158,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; @@ -1844,7 +1844,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); diff --git a/source/blender/editors/interface/view2d_draw.c b/source/blender/editors/interface/view2d_draw.c index 5801b7cdbdb..f7ef8c06389 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 { diff --git a/source/blender/editors/interface/view2d_edge_pan.c b/source/blender/editors/interface/view2d_edge_pan.c index ca32a754f1d..1d300c7b275 100644 --- a/source/blender/editors/interface/view2d_edge_pan.c +++ b/source/blender/editors/interface/view2d_edge_pan.c @@ -160,7 +160,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); diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c index 69acfc657dc..1fd1b6c984d 100644 --- a/source/blender/editors/interface/view2d_ops.c +++ b/source/blender/editors/interface/view2d_ops.c @@ -271,7 +271,7 @@ static int view_pan_modal(bContext *C, wmOperator *op, const wmEvent *event) 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: @@ -1527,7 +1527,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 +1745,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... */ @@ -2132,7 +2132,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; } |