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.
This commit is contained in:
2013-02-22 05:56:20 +00:00
parent b00c3b801b
commit a9e25ac433
6 changed files with 309 additions and 10 deletions

View File

@@ -1060,7 +1060,7 @@ class VIEW3D_PT_tools_weightpaint_options(Panel, View3DPaintPanel):
col.label("Show Zero Weights:")
rowsub = col.row()
rowsub.active = (not tool_settings.use_multipaint)
rowsub.prop(tool_settings, "vertex_group_user", text="Show Group Use", expand=True)
rowsub.prop(tool_settings, "vertex_group_user", expand=True)
self.unified_paint_settings(col, context)

View File

@@ -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
*

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}