diff options
author | Campbell Barton <ideasman42@gmail.com> | 2021-07-08 09:23:41 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2021-07-13 13:03:40 +0300 |
commit | 8839b4c32a44ba2c50f08ccd815d450ac71c454a (patch) | |
tree | b53e08ed505c9e58430579a1fb2a6ba43db71e3a /source | |
parent | 1b4d5c7a35597a70411515f721a405416244b540 (diff) |
UI: support persistent state during number/slider interaction
Support for begin/update/end callbacks allowing state to be cached
and reused while dragging a number button or slider.
This is done using `UI_block_interaction_set` to set callbacks.
- Dragging multiple buttons at once is supported,
passing multiple unique events into the update function.
- Update is only called once even when multiple buttons are edited.
- The update callback can detect the difference between click & drag
actions so situations to support skipping cache creation and
freeing for situations where it's not beneficial.
Reviewed by: Severin, HooglyBoogly
Ref D11861
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/editors/include/UI_interface.h | 49 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_handlers.c | 198 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_intern.h | 3 |
3 files changed, 250 insertions, 0 deletions
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 6901176a587..802c175492f 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -58,6 +58,7 @@ struct bNodeTree; struct bScreen; struct rctf; struct rcti; +struct uiBlockInteraction_Handle; struct uiButSearch; struct uiFontStyle; struct uiList; @@ -514,6 +515,54 @@ typedef int (*uiButPushedStateFunc)(struct uiBut *but, const void *arg); typedef void (*uiBlockHandleFunc)(struct bContext *C, void *arg, int event); +/* -------------------------------------------------------------------- */ +/** \name Custom Interaction + * + * Sometimes it's useful to create data that remains available + * while the user interacts with a button. + * + * A common case is dragging a number button or slider + * however this could be used in other cases too. + * \{ */ + +struct uiBlockInteraction_Params { + /** + * When true, this interaction is not modal + * (user clicking on a number button arrows or pasting a value for example). + */ + bool is_click; + /** + * Array of unique event ID's (values from #uiBut.retval). + * There may be more than one for multi-button editing (see #UI_BUT_DRAG_MULTI). + */ + int *unique_retval_ids; + uint unique_retval_ids_len; +}; + +/** Returns 'user_data', freed by #uiBlockInteractionEndFn. */ +typedef void *(*uiBlockInteractionBeginFn)(struct bContext *C, + const struct uiBlockInteraction_Params *params, + void *arg1); +typedef void (*uiBlockInteractionEndFn)(struct bContext *C, + const struct uiBlockInteraction_Params *params, + void *arg1, + void *user_data); +typedef void (*uiBlockInteractionUpdateFn)(struct bContext *C, + const struct uiBlockInteraction_Params *params, + void *arg1, + void *user_data); + +typedef struct uiBlockInteraction_CallbackData { + uiBlockInteractionBeginFn begin_fn; + uiBlockInteractionEndFn end_fn; + uiBlockInteractionUpdateFn update_fn; + void *arg1; +} uiBlockInteraction_CallbackData; + +void UI_block_interaction_set(uiBlock *block, uiBlockInteraction_CallbackData *callbacks); + +/** \} */ + /* Menu Callbacks */ typedef void (*uiMenuCreateFunc)(struct bContext *C, struct uiLayout *layout, void *arg1); diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 743f646d4df..5d3e9e42f29 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]; @@ -827,6 +861,27 @@ static void ui_apply_but_func(bContext *C, uiBut *but) search_but->arg = NULL; } + 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++; + } + } + } + if (but->context) { after->context = CTX_store_copy(but->context); } @@ -997,6 +1052,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]) { @@ -2283,6 +2350,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); + } } /** \} */ @@ -4852,6 +4924,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 +5436,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; @@ -8239,6 +8315,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 +8553,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); @@ -11314,3 +11415,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_intern.h b/source/blender/editors/interface/interface_intern.h index a05afb6e542..40a9c67c630 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -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 *); |