diff options
author | Campbell Barton <ideasman42@gmail.com> | 2013-02-22 09:56:20 +0400 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2013-02-22 09:56:20 +0400 |
commit | a9e25ac43320a253f557d945a8e83245e414a2ea (patch) | |
tree | 646737f30921fba6dabb467435fda5295827b093 /source | |
parent | b00c3b801bf07a67b98b604ee834e2d93dff0f39 (diff) |
Toggle-Drag UI Feature
Dragging on toggle buttons can now be used to press multiple buttons at once, especially useful for layer and outliner buttons.
notes:
- automatically enabled for all toggle buttons
(may change this if it becomes a problem).
- only buttons of the same type are pressed
(helps avoid annoyances eg; dragging past layer buttons onto other 3d header buttons and pressing by accident).
- automatic axis locking - dragging will lock to X/Y depending on the initial drag direction,
makes swipe motions work better, especially with the outliner.
implementation details:
- may re-implement as a region handler (currently its a modal operator).
- checking buttons in-between cursor motion events could be more efficient (but currently works ok).
- button execution needs to be improved
(currently executing a button thats not under the mouse needed a workaround for passing uiHandleButtonData),
requires further changes to UI code, will do next.
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/editors/include/UI_interface.h | 3 | ||||
-rw-r--r-- | source/blender/editors/interface/interface.c | 18 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_handlers.c | 71 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_intern.h | 2 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_ops.c | 223 |
5 files changed, 308 insertions, 9 deletions
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 648f8bfd8dd..78689c078c6 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -437,6 +437,7 @@ void uiButSetDragValue(uiBut *but); void uiButSetDragImage(uiBut *but, const char *path, int icon, struct ImBuf *ima, float scale); int UI_but_active_drop_name(struct bContext *C); +struct uiBut *ui_but_find_mouse_over(struct ARegion *ar, int x, int y); void uiButSetFlag(uiBut *but, int flag); void uiButClearFlag(uiBut *but, int flag); @@ -447,6 +448,8 @@ void uiButClearDrawFlag(uiBut *but, int flag); /* special button case, only draw it when used actively, for outliner etc */ int uiButActiveOnly(const struct bContext *C, uiBlock *block, uiBut *but); +void uiButExecute(const struct bContext *C, uiBut *but); + /* Buttons * diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index d245349f2c4..e10d588c19f 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -713,6 +713,12 @@ int uiButActiveOnly(const bContext *C, uiBlock *block, uiBut *but) return 1; } +/* simulate button click */ +void uiButExecute(const bContext *C, uiBut *but) +{ + ui_button_execute_do((bContext *)C, CTX_wm_region(C), but); +} + /* use to check if we need to disable undo, but don't make any changes * returns FALSE if undo needs to be disabled. */ static int ui_but_is_rna_undo(uiBut *but) @@ -1394,6 +1400,18 @@ int ui_is_but_float(uiBut *but) return 0; } +int ui_is_but_bool(uiBut *but) +{ + if (ELEM5(but->type, TOG, TOGN, TOGR, ICONTOG, ICONTOGN)) + return 1; + + if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_BOOLEAN) + return 1; + + return 0; +} + + int ui_is_but_unit(uiBut *but) { UnitSettings *unit = but->block->unit; diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 017ab559911..532f9b50725 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -85,6 +85,7 @@ /* proto */ static void ui_add_smart_controller(bContext *C, uiBut *from, uiBut *to); static void ui_add_link(bContext *C, uiBut *from, uiBut *to); +static int ui_do_but_EXIT(bContext *C, uiBut *but, struct uiHandleButtonData *data, const wmEvent *event); /***************** structs and defines ****************/ @@ -761,14 +762,27 @@ static int ui_but_start_drag(bContext *C, uiBut *but, uiHandleButtonData *data, WM_gestures_remove(C); if (ABS(data->dragstartx - event->x) + ABS(data->dragstarty - event->y) > U.dragthreshold) { - wmDrag *drag; - + button_activate_state(C, but, BUTTON_STATE_EXIT); data->cancel = TRUE; - drag = WM_event_start_drag(C, but->icon, but->dragtype, but->dragpoin, ui_get_but_val(but)); - if (but->imb) - WM_event_drag_image(drag, but->imb, but->imb_scale, BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect)); + if (ui_is_but_bool(but)) { + const bool is_set = (ui_get_but_val(but) != 0.0); + PointerRNA ptr; + WM_operator_properties_create(&ptr, "UI_OT_drag_toggle"); + RNA_boolean_set(&ptr, "state", !is_set); + RNA_int_set(&ptr, "last_x", data->dragstartx); + RNA_int_set(&ptr, "last_y", data->dragstarty); + WM_operator_name_call(C, "UI_OT_drag_toggle", WM_OP_INVOKE_DEFAULT, &ptr); + WM_operator_properties_free(&ptr); + } + else { + wmDrag *drag; + + drag = WM_event_start_drag(C, but->icon, but->dragtype, but->dragpoin, ui_get_but_val(but)); + if (but->imb) + WM_event_drag_image(drag, but->imb, but->imb_scale, BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect)); + } return 1; } @@ -2472,13 +2486,24 @@ static int ui_do_but_SEARCH_UNLINK(bContext *C, uiBlock *block, uiBut *but, uiHa static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) { if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_is_but_bool(but)) { + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + return WM_UI_HANDLER_CONTINUE; + } + if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) { data->togdual = event->ctrl; data->togonly = !event->shift; button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; + return WM_UI_HANDLER_CONTINUE; } } + else if (data->state == BUTTON_STATE_WAIT_DRAG) { + /* note: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into its own function */ + return ui_do_but_EXIT(C, but, data, event); + } return WM_UI_HANDLER_CONTINUE; } @@ -2499,6 +2524,12 @@ static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, con return WM_UI_HANDLER_CONTINUE; } } + if (event->type == LEFTMOUSE && ui_is_but_bool(but)) { + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + return WM_UI_HANDLER_CONTINUE; + } if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) { int ret = WM_UI_HANDLER_BREAK; @@ -3215,6 +3246,12 @@ static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, co return WM_UI_HANDLER_BREAK; } } + if (event->type == LEFTMOUSE && ui_is_but_bool(but)) { + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + return WM_UI_HANDLER_BREAK; + } /* regular open menu */ if (ELEM3(event->type, LEFTMOUSE, PADENTER, RETKEY) && event->val == KM_PRESS) { @@ -5415,7 +5452,7 @@ static int ui_mouse_inside_button(ARegion *ar, uiBut *but, int x, int y) return 1; } -static uiBut *ui_but_find_mouse_over(ARegion *ar, int x, int y) +uiBut *ui_but_find_mouse_over(ARegion *ar, int x, int y) { uiBlock *block; uiBut *but, *butover = NULL; @@ -5784,8 +5821,10 @@ static void button_activate_exit(bContext *C, uiHandleButtonData *data, uiBut *b ED_region_tag_redraw(data->region); /* clean up button */ - MEM_freeN(but->active); - but->active = NULL; + if (but->active) { + MEM_freeN(but->active); + but->active = NULL; + } but->flag &= ~(UI_ACTIVE | UI_SELECT); but->flag |= UI_BUT_LAST_ACTIVE; if (!onfree) @@ -6034,6 +6073,20 @@ void ui_button_activate_do(bContext *C, ARegion *ar, uiBut *but) ui_do_button(C, but->block, but, &event); } +void ui_button_execute_do(struct bContext *C, struct ARegion *ar, uiBut *but) +{ + /* note: ideally we would not have to change 'but->active' howevwer + * some functions we call don't use data (as they should be doing) */ + void *active_back = but->active; + uiHandleButtonData *data = MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData_Fake"); + but->active = data; + data->region = ar; + ui_apply_button(C, but->block, but, data, true); + /* use onfree event so undo is handled by caller and apply is already done above */ + button_activate_exit((bContext *)C, data, but, false, true); + but->active = active_back; +} + static void ui_handle_button_activate(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type) { uiBut *oldbut; diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index b1e8b7b001e..f0e59f1f935 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -406,6 +406,7 @@ extern void ui_set_but_soft_range(uiBut *but, double value); extern void ui_check_but(uiBut *but); extern int ui_is_but_float(uiBut *but); +extern int ui_is_but_bool(uiBut *but); extern int ui_is_but_unit(uiBut *but); extern int ui_is_but_rna_valid(uiBut *but); extern int ui_is_but_utf8(uiBut *but); @@ -509,6 +510,7 @@ void ui_draw_but_TRACKPREVIEW(ARegion *ar, uiBut *but, struct uiWidgetColors *wc /* interface_handlers.c */ extern void ui_pan_to_scroll(const struct wmEvent *event, int *type, int *val); extern void ui_button_activate_do(struct bContext *C, struct ARegion *ar, uiBut *but); +extern void ui_button_execute_do(struct bContext *C, struct ARegion *ar, uiBut *but); extern void ui_button_active_free(const struct bContext *C, uiBut *but); extern int ui_button_is_active(struct ARegion *ar); extern int ui_button_open_menu_direction(uiBut *but); diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index e03a171da18..368ecdefe2c 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -1071,6 +1071,228 @@ static void UI_OT_reloadtranslation(wmOperatorType *ot) ot->exec = reloadtranslation_exec; } + +/* -------------------------------------------------------------------- */ +/* Toggle Drag Operator */ + +typedef struct DragOpInfo { + bool xy_lock[2]; + float but_cent_start[2]; + eButType but_type_start; +} DragOpInfo; + +typedef struct DragOpPlotData { + bContext *C; + ARegion *ar; + bool is_set; + eButType but_type_start; + bool do_draw; + const uiBut *but_prev; +} DragOpPlotData; + +static const uiBut *ui_but_set_xy(bContext *C, ARegion *ar, const bool is_set, const eButType but_type_start, + const int xy[2], const uiBut *but_prev) +{ + uiBut *but = ui_but_find_mouse_over(ar, xy[0], xy[1]); + + if (but_prev == but) { + return but_prev; + } + + if (but && ui_is_but_bool(but) && but->type == but_type_start) { + /* is it pressed? */ + bool is_set_but = (ui_get_but_val(but) != 0.0); + BLI_assert(ui_is_but_bool(but) == true); + if (is_set_but != is_set) { + uiButExecute(C, but); + return but; + } + } + + return but_prev; +} + +static int ui_but_set_cb(int x, int y, void *data_v) +{ + DragOpPlotData *data = data_v; + int xy[2] = {x, y}; + data->but_prev = ui_but_set_xy(data->C, data->ar, data->is_set, data->but_type_start, xy, data->but_prev); + return 1; /* keep going */ +} + +/* operates on buttons between 2 mouse-points */ +static bool ui_but_set_xy_xy(bContext *C, ARegion *ar, const bool is_set, const eButType but_type_start, + const int xy_src[2], const int xy_dst[2]) +{ + DragOpPlotData data; + data.C = C; + data.ar = ar; + data.is_set = is_set; + data.but_type_start = but_type_start; + data.do_draw = false; + data.but_prev = NULL; + + + /* prevent dragging too fast loosing buttons */ + plot_line_v2v2i(xy_src, xy_dst, ui_but_set_cb, &data); + + return data.do_draw; +} + +static void ui_drag_but_set(bContext *C, wmOperator *op, const int xy_input[2]) +{ + ARegion *ar = CTX_wm_region(C); + DragOpInfo *drag_info = op->customdata; + bool do_draw = false; + + const bool is_set = RNA_boolean_get(op->ptr, "state"); + const int xy_last[2] = {RNA_int_get(op->ptr, "last_x"), + RNA_int_get(op->ptr, "last_y")}; + + int xy[2]; + + /** + * Initialize Locking: + * + * Check if we need to initialize the lock axis by finding if the first + * button we mouse over is X or Y aligned, then lock the mouse to that axis after. + */ + if (drag_info->xy_lock[0] == false && drag_info->xy_lock[1] == false) { + ARegion *ar = CTX_wm_region(C); + + /* first store the buttons original coords */ + uiBut *but = ui_but_find_mouse_over(ar, xy_input[0], xy_input[1]); + if (but) { + const float but_cent_new[2] = {BLI_rctf_cent_x(&but->rect), + BLI_rctf_cent_y(&but->rect)}; + + /* check if this is a different button, chances are high the button wont move about :) */ + if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) { + if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) < + fabsf(drag_info->but_cent_start[1] - but_cent_new[1])) + { + drag_info->xy_lock[0] = true; + } + else { + drag_info->xy_lock[1] = true; + } + } + } + } + /* done with axis locking */ + + + xy[0] = (drag_info->xy_lock[0] == false) ? xy_input[0] : xy_last[0]; + xy[1] = (drag_info->xy_lock[1] == false) ? xy_input[1] : xy_last[1]; + + + /* touch all buttons between last mouse coord and this one */ + do_draw = ui_but_set_xy_xy(C, ar, is_set, drag_info->but_type_start, xy_last, xy); + + if (do_draw) { + ED_region_tag_redraw(ar); + } + + RNA_int_set(op->ptr, "last_x", xy[0]); + RNA_int_set(op->ptr, "last_y", xy[1]); +} + +static int ui_drag_toggle_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + int xy_last[2] = {RNA_int_get(op->ptr, "last_x"), + RNA_int_get(op->ptr, "last_y")}; + + float but_cent_start[2]; + eButType but_type_start; + DragOpInfo *drag_info; + + { + /* find the button where we started dragging */ + ARegion *ar = CTX_wm_region(C); + uiBut *but = ui_but_find_mouse_over(ar, xy_last[0], xy_last[1]); + if (but) { + but_cent_start[0] = BLI_rctf_cent_x(&but->rect); + but_cent_start[1] = BLI_rctf_cent_y(&but->rect); + but_type_start = but->type; + } + else { + return OPERATOR_CANCELLED; + } + } + + drag_info = op->customdata = MEM_callocN(sizeof(DragOpInfo), __func__); + copy_v2_v2(drag_info->but_cent_start, but_cent_start); + drag_info->but_type_start = but_type_start; + + /* set the initial button */ + ui_drag_but_set(C, op, xy_last); + ui_drag_but_set(C, op, &event->x); + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static int ui_drag_toggle_modal(bContext *C, wmOperator *op, wmEvent *event) +{ + bool done = false; + + switch (event->type) { + case LEFTMOUSE: + { + if (event->val != KM_PRESS) { + done = true; + } + break; + } + case MOUSEMOVE: + { + ARegion *ar = CTX_wm_region(C); + if (!BLI_rcti_isect_pt_v(&ar->winrct, &event->x)) { + done = true; + } + else { + ui_drag_but_set(C, op, &event->x); + } + break; + } + } + + if (done) { + MEM_freeN(op->customdata); + return OPERATOR_FINISHED; + } + else { + return OPERATOR_RUNNING_MODAL; + } +} + +static int ui_drag_toggle_cancel(bContext *UNUSED(C), wmOperator *op) +{ + MEM_freeN(op->customdata); + return OPERATOR_CANCELLED; +} + +static void UI_OT_drag_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Button Drag Toggle"; + ot->description = ""; + ot->idname = "UI_OT_drag_toggle"; + + /* api callbacks */ + ot->invoke = ui_drag_toggle_invoke; + ot->modal = ui_drag_toggle_modal; + ot->cancel = ui_drag_toggle_cancel; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + + /* properties */ + RNA_def_boolean(ot->srna, "state", true, "State", ""); + RNA_def_int(ot->srna, "last_x", 0, 0, INT_MAX, "X", "", 0, INT_MAX); + RNA_def_int(ot->srna, "last_y", 0, 0, INT_MAX, "Y", "", 0, INT_MAX); +} + /* ********************************************************* */ /* Registration */ @@ -1088,5 +1310,6 @@ void UI_buttons_operatortypes(void) WM_operatortype_append(UI_OT_edittranslation_init); #endif WM_operatortype_append(UI_OT_reloadtranslation); + WM_operatortype_append(UI_OT_drag_toggle); } |