Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/interface/interface_handlers.c
| Context not available. | |||||
| #include "BLI_math.h" | #include "BLI_math.h" | ||||
| #include "BLI_blenlib.h" | #include "BLI_blenlib.h" | ||||
| #include "BLI_linklist.h" | |||||
| #include "BLI_utildefines.h" | #include "BLI_utildefines.h" | ||||
| #include "BLI_string_cursor_utf8.h" | #include "BLI_string_cursor_utf8.h" | ||||
| Context not available. | |||||
| /* support dragging toggle buttons */ | /* support dragging toggle buttons */ | ||||
| #define USE_DRAG_TOGGLE | #define USE_DRAG_TOGGLE | ||||
| /* support dragging multiple number buttons at once */ | |||||
| #define USE_DRAG_MULTINUM | |||||
| /* so we can avoid very small mouse-moves from jumping away from keyboard navigation [#34936] */ | /* so we can avoid very small mouse-moves from jumping away from keyboard navigation [#34936] */ | ||||
| #define USE_KEYNAV_LIMIT | #define USE_KEYNAV_LIMIT | ||||
| Context not available. | |||||
| BUTTON_STATE_EXIT | BUTTON_STATE_EXIT | ||||
| } uiHandleButtonState; | } uiHandleButtonState; | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| /* how far to drag before we check for gesture direction (in pixels), | |||||
| * note: half the height of a button is about right... */ | |||||
| #define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 2) | |||||
| /* how far to drag horizontally before we stop checkign which buttons the gesture spans (in pixels), | |||||
| * locking down the buttons so we can drag freely without worrying about vertical movement. */ | |||||
| #define DRAG_MULTINUM_THRESHOLD_DRAG_Y (UI_UNIT_Y / 2) | |||||
| /* how strict to be when detecting a vertical gesture, [0.5 == sloppy], [0.9 == strict], (unsigned dot-product) | |||||
| * note: we should be quite strict here, since doing a vertical gesture by accident should be avoided, | |||||
| * however with some care a user should be able to do a vertical movement without *missing*. */ | |||||
| #define DRAG_MULTINUM_THRESHOLD_VERTICAL (0.75f) | |||||
| /* a simple version of uiHandleButtonData when accessing multiple buttons */ | |||||
| typedef struct uiButMultiState { | |||||
| double origvalue; | |||||
| rctf rect; | |||||
| } uiButMultiState; | |||||
| typedef struct uiHandleButtonMulti { | |||||
| enum { | |||||
| BUTTON_MULTI_INIT_UNSET = 0, /* gesture direction unknown, wait until mouse has moved enough... */ | |||||
| BUTTON_MULTI_INIT_SETUP, /* vertical gesture detected, flag buttons interactively (UI_BUT_DRAG_MULTI) */ | |||||
| BUTTON_MULTI_INIT_ENABLE, /* flag buttons finished, apply horizontal motion to active and flagged */ | |||||
| BUTTON_MULTI_INIT_DISABLE, /* vertical gesture _not_ detected, take no further action */ | |||||
| } init; | |||||
| bool has_mbuts; | |||||
| LinkNode *mbuts; | |||||
| /* before activating, we need to check gesture direction | |||||
| * accumulate signed cursor movement here so we can tell if this is a vertical motion or not. */ | |||||
| float drag_dir[2]; | |||||
| /* values copied direct from event->x,y | |||||
| * used to detect buttons between the current and initial mouse position */ | |||||
| int drag_start[2]; | |||||
| /* store x location once BUTTON_MULTI_INIT_SETUP is set, | |||||
| * moving outside this sets BUTTON_MULTI_INIT_ENABLE */ | |||||
| int drag_lock_x; | |||||
| } uiHandleButtonMulti; | |||||
| #endif /* USE_DRAG_MULTINUM */ | |||||
| typedef struct uiHandleButtonData { | typedef struct uiHandleButtonData { | ||||
| wmWindowManager *wm; | wmWindowManager *wm; | ||||
| wmWindow *window; | wmWindow *window; | ||||
| Context not available. | |||||
| struct uiKeyNavLock searchbox_keynav_state; | struct uiKeyNavLock searchbox_keynav_state; | ||||
| #endif | #endif | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| /* Multi-buttons will be updated in unison with the active button. */ | |||||
| uiHandleButtonMulti multi_data; | |||||
| #endif | |||||
| /* post activate */ | /* post activate */ | ||||
| uiButtonActivateType posttype; | uiButtonActivateType posttype; | ||||
| uiBut *postbut; | uiBut *postbut; | ||||
| Context not available. | |||||
| static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type); | static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type); | ||||
| static void button_timers_tooltip_remove(bContext *C, uiBut *but); | static void button_timers_tooltip_remove(bContext *C, uiBut *but); | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| static void ui_multibut_restore(uiHandleButtonData *data, uiBlock *block); | |||||
| static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but); | |||||
| #endif | |||||
| /* buttons clipboard */ | /* buttons clipboard */ | ||||
| static ColorBand but_copypaste_coba = {0}; | static ColorBand but_copypaste_coba = {0}; | ||||
| static CurveMapping but_copypaste_curve = {0}; | static CurveMapping but_copypaste_curve = {0}; | ||||
| Context not available. | |||||
| data->applied = true; | data->applied = true; | ||||
| } | } | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| static void ui_apply_mbut_states(bContext *C, uiHandleButtonData *data, uiBlock *block) | |||||
| { | |||||
| ARegion *ar = data->region; | |||||
| const double value_delta = data->value - data->origvalue; | |||||
| uiBut *but; | |||||
| BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_ENABLE); | |||||
| for (but = block->buttons.first; but; but = but->next) { | |||||
| if (but->flag & UI_BUT_DRAG_MULTI) { | |||||
| /* mbut_states for delta */ | |||||
| uiButMultiState *mbut_state = ui_multibut_lookup(data, but); | |||||
| if (mbut_state) { | |||||
| void *active_back; | |||||
| ui_button_execute_begin(C, ar, but, &active_back); | |||||
| BLI_assert(active_back == NULL); | |||||
| /* no need to check 'data->state' here */ | |||||
| if (data->str) { | |||||
| /* entering text (set all) */ | |||||
| but->active->value = data->value; | |||||
| ui_set_but_string(C, but, data->str); | |||||
| } | |||||
| else { | |||||
| /* dragging (use delta) */ | |||||
| but->active->value = mbut_state->origvalue + value_delta; | |||||
| } | |||||
| ui_button_execute_end(C, ar, but, active_back); | |||||
| } | |||||
| else { | |||||
| /* highly unlikely */ | |||||
| printf("%s: cant find button\n", __func__); | |||||
| } | |||||
| /* end */ | |||||
| } | |||||
| } | |||||
| } | |||||
| #endif /* USE_DRAG_MULTINUM */ | |||||
| static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data) | static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data) | ||||
| { | { | ||||
| if (data->str) { | if (data->str) { | ||||
| Context not available. | |||||
| break; | break; | ||||
| } | } | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| if (data->multi_data.has_mbuts) { | |||||
| if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) { | |||||
| if (data->cancel) { | |||||
| ui_multibut_restore(data, block); | |||||
| } | |||||
| else { | |||||
| ui_apply_mbut_states(C, data, block); | |||||
| } | |||||
| } | |||||
| } | |||||
| #endif | |||||
| but->editstr = editstr; | but->editstr = editstr; | ||||
| but->editval = editval; | but->editval = editval; | ||||
| but->editvec = editvec; | but->editvec = editvec; | ||||
| Context not available. | |||||
| return changed; | return changed; | ||||
| } | } | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| /* small multi-but api */ | |||||
| static void ui_multibut_add(uiHandleButtonData *data, uiBut *but) | |||||
| { | |||||
| uiButMultiState *mbut_state; | |||||
| BLI_assert(but->flag & UI_BUT_DRAG_MULTI); | |||||
| BLI_assert(data->multi_data.has_mbuts); | |||||
| mbut_state = MEM_callocN(sizeof(*mbut_state), __func__); | |||||
| mbut_state->rect = but->rect; | |||||
| mbut_state->origvalue = ui_get_but_val(but); | |||||
| BLI_linklist_prepend(&data->multi_data.mbuts, mbut_state); | |||||
| } | |||||
| static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but) | |||||
| { | |||||
| LinkNode *l; | |||||
| const float xy[2] = {BLI_rctf_cent_x(&but->rect), BLI_rctf_cent_y(&but->rect)}; | |||||
| for (l = data->multi_data.mbuts; l; l = l->next) { | |||||
| uiButMultiState *mbut_state; | |||||
| mbut_state = l->link; | |||||
| if (BLI_rctf_isect_pt_v(&mbut_state->rect, xy)) { | |||||
| return mbut_state; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| static void ui_multibut_restore(uiHandleButtonData *data, uiBlock *block) | |||||
| { | |||||
| uiBut *but; | |||||
| for (but = block->buttons.first; but; but = but->next) { | |||||
| if (but->flag & UI_BUT_DRAG_MULTI) { | |||||
| uiButMultiState *mbut_state = ui_multibut_lookup(data, but); | |||||
| if (mbut_state) { | |||||
| ui_set_but_val(but, mbut_state->origvalue); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| static void ui_multibut_free(uiHandleButtonData *data) | |||||
| { | |||||
| BLI_linklist_freeN(data->multi_data.mbuts); | |||||
| data->multi_data.mbuts = NULL; | |||||
| } | |||||
| static bool ui_but_is_compatible(uiBut *but_a, uiBut *but_b) | |||||
| { | |||||
| if (but_a->type != but_b->type) | |||||
| return false; | |||||
| if (but_a->pointype != but_b->pointype) | |||||
| return false; | |||||
| if (but_a->rnaprop) { | |||||
| if (but_a->rnapoin.type != but_b->rnapoin.type) | |||||
| return false; | |||||
| if (but_a->rnapoin.data != but_b->rnapoin.data) | |||||
| return false; | |||||
| if (but_a->rnapoin.id.data != but_b->rnapoin.id.data) | |||||
| return false; | |||||
| if (RNA_property_type(but_a->rnaprop) != RNA_property_type(but_b->rnaprop)) | |||||
| return false; | |||||
| if (RNA_property_subtype(but_a->rnaprop) != RNA_property_subtype(but_b->rnaprop)) | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| static bool ui_tag_mbut_states(uiBut *but_active, uiHandleButtonData *data, const wmEvent *event) | |||||
| { | |||||
| uiBut *but; | |||||
| float seg[2][2]; | |||||
| bool changed = false; | |||||
| seg[0][0] = data->multi_data.drag_start[0]; | |||||
| seg[0][1] = data->multi_data.drag_start[1]; | |||||
| seg[1][0] = event->x; | |||||
| seg[1][1] = event->y; | |||||
| BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP); | |||||
| ui_window_to_block_fl(data->region, but_active->block, &seg[0][0], &seg[0][1]); | |||||
| ui_window_to_block_fl(data->region, but_active->block, &seg[1][0], &seg[1][1]); | |||||
| data->multi_data.has_mbuts = false; | |||||
| /* follow ui_but_find_mouse_over_ex logic */ | |||||
| for (but = but_active->block->buttons.first; but; but = but->next) { | |||||
| bool drag_prev = false; | |||||
| bool drag_curr = false; | |||||
| /* re-set each time */ | |||||
| if (but->flag & UI_BUT_DRAG_MULTI) { | |||||
| but->flag &= ~UI_BUT_DRAG_MULTI; | |||||
| drag_prev = true; | |||||
| } | |||||
| if (ui_is_but_interactive(but, false)) { | |||||
| /* drag checks */ | |||||
| if (but_active != but) { | |||||
| if (ui_but_is_compatible(but_active, but)) { | |||||
| /* finally check for overlap */ | |||||
| if (BLI_rctf_isect_segment(&but->rect, seg[0], seg[1])) { | |||||
| but->flag |= UI_BUT_DRAG_MULTI; | |||||
| data->multi_data.has_mbuts = true; | |||||
| drag_curr = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| /* -- */ | |||||
| } | |||||
| changed |= (drag_prev != drag_curr); | |||||
| } | |||||
| return changed; | |||||
| } | |||||
| static void ui_create_mbut_states(uiBut *but_active, uiHandleButtonData *data) | |||||
| { | |||||
| uiBut *but; | |||||
| BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP); | |||||
| for (but = but_active->block->buttons.first; but; but = but->next) { | |||||
| if (but->flag & UI_BUT_DRAG_MULTI) { | |||||
| ui_multibut_add(data, but); | |||||
| } | |||||
| } | |||||
| } | |||||
| #endif /* USE_DRAG_MULTINUM */ | |||||
| static int ui_do_but_NUM(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) | static int ui_do_but_NUM(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) | ||||
| { | { | ||||
| int mx, my; /* mouse location scaled to fit the UI */ | int mx, my; /* mouse location scaled to fit the UI */ | ||||
| Context not available. | |||||
| button_activate_state(C, but, BUTTON_STATE_EXIT); | button_activate_state(C, but, BUTTON_STATE_EXIT); | ||||
| retval = WM_UI_HANDLER_BREAK; | retval = WM_UI_HANDLER_BREAK; | ||||
| } | } | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| copy_v2_v2_int(data->multi_data.drag_start, &event->x); | |||||
| #endif | |||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| const enum eSnapType snap = ui_event_to_snap(event); | const enum eSnapType snap = ui_event_to_snap(event); | ||||
| float fac; | float fac; | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| data->multi_data.drag_dir[0] += fabsf(data->draglastx - mx); | |||||
| data->multi_data.drag_dir[1] += fabsf(data->draglasty - my); | |||||
| #endif | |||||
| fac = 1.0f; | fac = 1.0f; | ||||
| if (event->shift) fac /= 10.0f; | if (event->shift) fac /= 10.0f; | ||||
| if (event->alt) fac /= 20.0f; | if (event->alt) fac /= 20.0f; | ||||
| if (ui_numedit_but_NUM(but, data, (ui_is_a_warp_but(but) ? screen_mx : mx), snap, fac)) | if (ui_numedit_but_NUM(but, data, (ui_is_a_warp_but(but) ? screen_mx : mx), snap, fac)) | ||||
| ui_numedit_apply(C, block, but, data); | ui_numedit_apply(C, block, but, data); | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| else if (data->multi_data.has_mbuts) { | |||||
| if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) { | |||||
| ui_apply_mbut_states(C, data, block); | |||||
| } | |||||
| } | |||||
| #endif | |||||
| } | } | ||||
| retval = WM_UI_HANDLER_BREAK; | retval = WM_UI_HANDLER_BREAK; | ||||
| } | } | ||||
| Context not available. | |||||
| retval = WM_UI_HANDLER_BREAK; | retval = WM_UI_HANDLER_BREAK; | ||||
| } | } | ||||
| data->draglastx = mx; | |||||
| data->draglasty = my; | |||||
| return retval; | return retval; | ||||
| } | } | ||||
| Context not available. | |||||
| } | } | ||||
| } | } | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| if (data) { | |||||
| if (event->type == MOUSEMOVE) { | |||||
| if (ELEM3(but->type, NUM, NUMSLI, TEX)) { | |||||
| /* initialize! */ | |||||
| if (data->multi_data.init == BUTTON_MULTI_INIT_UNSET) { | |||||
| /* --> (BUTTON_MULTI_INIT_SETUP | BUTTON_MULTI_INIT_DISABLE) */ | |||||
| const float margin_y = DRAG_MULTINUM_THRESHOLD_DRAG_Y / sqrtf(block->aspect); | |||||
| /* check if we have a vertical gesture */ | |||||
| if (len_squared_v2(data->multi_data.drag_dir) > (margin_y * margin_y)) { | |||||
| const float dir_nor_y[2] = {0, 1}; | |||||
| float dir_nor_drag[2]; | |||||
| normalize_v2_v2(dir_nor_drag, data->multi_data.drag_dir); | |||||
| if (fabsf(dot_v2v2(dir_nor_drag, dir_nor_y)) > DRAG_MULTINUM_THRESHOLD_VERTICAL) { | |||||
| data->multi_data.init = BUTTON_MULTI_INIT_SETUP; | |||||
| data->multi_data.drag_lock_x = event->x; | |||||
| } | |||||
| else { | |||||
| data->multi_data.init = BUTTON_MULTI_INIT_DISABLE; | |||||
| } | |||||
| } | |||||
| } | |||||
| else if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) { | |||||
| /* --> (BUTTON_MULTI_INIT_ENABLE) */ | |||||
| const float margin_x = DRAG_MULTINUM_THRESHOLD_DRAG_X / sqrtf(block->aspect); | |||||
| /* check if we're dont setting buttons */ | |||||
| if ((data->str && ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) || | |||||
| (abs(data->multi_data.drag_lock_x - event->x) > margin_x)) | |||||
| { | |||||
| ui_create_mbut_states(but, data); | |||||
| data->multi_data.init = BUTTON_MULTI_INIT_ENABLE; | |||||
| } | |||||
| } | |||||
| if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) { | |||||
| if (ui_tag_mbut_states(but, data, event)) { | |||||
| ED_region_tag_redraw(data->region); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| #endif | |||||
| return retval; | return retval; | ||||
| } | } | ||||
| Context not available. | |||||
| if (!onfree) | if (!onfree) | ||||
| ui_apply_button(C, block, but, data, false); | ui_apply_button(C, block, but, data, false); | ||||
| #ifdef USE_DRAG_MULTINUM | |||||
| if (data->multi_data.has_mbuts) { | |||||
| for (bt = but->block->buttons.first; bt; bt = bt->next) { | |||||
| bt->flag &= ~UI_BUT_DRAG_MULTI; | |||||
| } | |||||
| ui_multibut_free(data); | |||||
| } | |||||
| #endif | |||||
| /* if this button is in a menu, this will set the button return | /* if this button is in a menu, this will set the button return | ||||
| * value to the button value and the menu return value to ok, the | * value to the button value and the menu return value to ok, the | ||||
| * menu return value will be picked up and the menu will close */ | * menu return value will be picked up and the menu will close */ | ||||
| Context not available. | |||||
| ui_do_button(C, but->block, but, &event); | ui_do_button(C, but->block, but, &event); | ||||
| } | } | ||||
| void ui_button_execute_do(struct bContext *C, struct ARegion *ar, uiBut *but) | void ui_button_execute_begin(struct bContext *UNUSED(C), struct ARegion *ar, uiBut *but, void **active_back) | ||||
| { | { | ||||
| /* note: ideally we would not have to change 'but->active' howevwer | /* note: ideally we would not have to change 'but->active' howevwer | ||||
| * some functions we call don't use data (as they should be doing) */ | * some functions we call don't use data (as they should be doing) */ | ||||
| void *active_back = but->active; | uiHandleButtonData *data; | ||||
| uiHandleButtonData *data = MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData_Fake"); | *active_back = but->active; | ||||
| data = MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData_Fake"); | |||||
| but->active = data; | but->active = data; | ||||
| data->region = ar; | data->region = ar; | ||||
| ui_apply_button(C, but->block, but, data, true); | } | ||||
| void ui_button_execute_end(struct bContext *C, struct ARegion *UNUSED(ar), uiBut *but, void *active_back) | |||||
| { | |||||
| ui_apply_button(C, but->block, but, but->active, true); | |||||
| /* use onfree event so undo is handled by caller and apply is already done above */ | /* use onfree event so undo is handled by caller and apply is already done above */ | ||||
| ui_apply_autokey(C, but); | ui_apply_autokey(C, but); | ||||
| button_activate_exit((bContext *)C, but, data, false, true); | button_activate_exit((bContext *)C, but, but->active, false, true); | ||||
| but->active = active_back; | but->active = active_back; | ||||
| } | } | ||||
| Context not available. | |||||