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
This commit is contained in:
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@@ -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 *);
|
||||
|
||||
|
Reference in New Issue
Block a user