diff options
Diffstat (limited to 'source/blender/editors/interface/interface_handlers.c')
-rw-r--r-- | source/blender/editors/interface/interface_handlers.c | 573 |
1 files changed, 496 insertions, 77 deletions
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 743f646d4df..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: @@ -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) { @@ -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); @@ -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); + } } /** \} */ @@ -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 */ @@ -4704,6 +4832,15 @@ static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, con 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; @@ -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; } @@ -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); @@ -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); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -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; @@ -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; +} + +/** \} */ |