/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2007 Blender Foundation. * All rights reserved. */ /** \file * \ingroup wm * * Default operator callbacks for use with gestures (border/circle/lasso/straightline). * Operators themselves are defined elsewhere. * * - Keymaps are in ``wm_operators.c``. * - Property definitions are in ``wm_operator_props.c``. */ #include "MEM_guardedalloc.h" #include "DNA_windowmanager_types.h" #include "BLI_math.h" #include "BLI_rect.h" #include "BKE_context.h" #include "WM_api.h" #include "WM_types.h" #include "wm.h" #include "wm_event_system.h" #include "wm_event_types.h" #include "ED_screen.h" #include "ED_select_utils.h" #include "UI_interface.h" #include "RNA_access.h" #include "RNA_define.h" /* -------------------------------------------------------------------- */ /** \name Internal Gesture Utilities * * Border gesture has two types: * -# #WM_GESTURE_CROSS_RECT: starts a cross, on mouse click it changes to border. * -# #WM_GESTURE_RECT: starts immediate as a border, on mouse click or release it ends. * * It stores 4 values (xmin, xmax, ymin, ymax) and event it ended with (event_type). * * \{ */ static void gesture_modal_end(bContext *C, wmOperator *op) { wmWindow *win = CTX_wm_window(C); wmGesture *gesture = op->customdata; WM_gesture_end(win, gesture); /* frees gesture itself, and unregisters from window */ op->customdata = NULL; ED_area_tag_redraw(CTX_wm_area(C)); if (RNA_struct_find_property(op->ptr, "cursor")) { WM_cursor_modal_restore(win); } } static void gesture_modal_state_to_operator(wmOperator *op, int modal_state) { PropertyRNA *prop; switch (modal_state) { case GESTURE_MODAL_SELECT: case GESTURE_MODAL_DESELECT: if ((prop = RNA_struct_find_property(op->ptr, "deselect"))) { RNA_property_boolean_set(op->ptr, prop, (modal_state == GESTURE_MODAL_DESELECT)); } if ((prop = RNA_struct_find_property(op->ptr, "mode"))) { RNA_property_enum_set( op->ptr, prop, (modal_state == GESTURE_MODAL_DESELECT) ? SEL_OP_SUB : SEL_OP_ADD); } break; case GESTURE_MODAL_IN: case GESTURE_MODAL_OUT: if ((prop = RNA_struct_find_property(op->ptr, "zoom_out"))) { RNA_property_boolean_set(op->ptr, prop, (modal_state == GESTURE_MODAL_OUT)); } break; } } static int UNUSED_FUNCTION(gesture_modal_state_from_operator)(wmOperator *op) { PropertyRNA *prop; if ((prop = RNA_struct_find_property(op->ptr, "deselect"))) { if (RNA_property_is_set(op->ptr, prop)) { return RNA_property_boolean_get(op->ptr, prop) ? GESTURE_MODAL_DESELECT : GESTURE_MODAL_SELECT; } } if ((prop = RNA_struct_find_property(op->ptr, "mode"))) { if (RNA_property_is_set(op->ptr, prop)) { return RNA_property_enum_get(op->ptr, prop) == SEL_OP_SUB ? GESTURE_MODAL_DESELECT : GESTURE_MODAL_SELECT; } } if ((prop = RNA_struct_find_property(op->ptr, "zoom_out"))) { if (RNA_property_is_set(op->ptr, prop)) { return RNA_property_boolean_get(op->ptr, prop) ? GESTURE_MODAL_OUT : GESTURE_MODAL_IN; } } return GESTURE_MODAL_NOP; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Border Gesture * * Border gesture has two types: * -# #WM_GESTURE_CROSS_RECT: starts a cross, on mouse click it changes to border. * -# #WM_GESTURE_RECT: starts immediate as a border, on mouse click or release it ends. * * It stores 4 values (xmin, xmax, ymin, ymax) and event it ended with (event_type). * * \{ */ static bool gesture_box_apply_rect(wmOperator *op) { wmGesture *gesture = op->customdata; rcti *rect = gesture->customdata; if (rect->xmin == rect->xmax || rect->ymin == rect->ymax) { return 0; } /* operator arguments and storage. */ RNA_int_set(op->ptr, "xmin", min_ii(rect->xmin, rect->xmax)); RNA_int_set(op->ptr, "ymin", min_ii(rect->ymin, rect->ymax)); RNA_int_set(op->ptr, "xmax", max_ii(rect->xmin, rect->xmax)); RNA_int_set(op->ptr, "ymax", max_ii(rect->ymin, rect->ymax)); return 1; } static bool gesture_box_apply(bContext *C, wmOperator *op) { wmGesture *gesture = op->customdata; int retval; if (!gesture_box_apply_rect(op)) { return 0; } if (gesture->wait_for_input) { gesture_modal_state_to_operator(op, gesture->modal_state); } retval = op->type->exec(C, op); OPERATOR_RETVAL_CHECK(retval); return 1; } int WM_gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); const ARegion *region = CTX_wm_region(C); const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input"); if (wait_for_input) { op->customdata = WM_gesture_new(win, region, event, WM_GESTURE_CROSS_RECT); } else { op->customdata = WM_gesture_new(win, region, event, WM_GESTURE_RECT); } { wmGesture *gesture = op->customdata; gesture->wait_for_input = wait_for_input; } /* add modal handler */ WM_event_add_modal_handler(C, op); wm_gesture_tag_redraw(win); return OPERATOR_RUNNING_MODAL; } int WM_gesture_box_modal(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); wmGesture *gesture = op->customdata; rcti *rect = gesture->customdata; if (event->type == EVT_MODAL_MAP) { switch (event->val) { case GESTURE_MODAL_MOVE: { gesture->move = !gesture->move; break; } case GESTURE_MODAL_BEGIN: { if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->is_active == false) { gesture->is_active = true; wm_gesture_tag_redraw(win); } break; } case GESTURE_MODAL_SELECT: case GESTURE_MODAL_DESELECT: case GESTURE_MODAL_IN: case GESTURE_MODAL_OUT: { if (gesture->wait_for_input) { gesture->modal_state = event->val; } if (gesture_box_apply(C, op)) { gesture_modal_end(C, op); return OPERATOR_FINISHED; } gesture_modal_end(C, op); return OPERATOR_CANCELLED; } case GESTURE_MODAL_CANCEL: { gesture_modal_end(C, op); return OPERATOR_CANCELLED; } } } else { switch (event->type) { case MOUSEMOVE: { if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->is_active == false) { rect->xmin = rect->xmax = event->x - gesture->winrct.xmin; rect->ymin = rect->ymax = event->y - gesture->winrct.ymin; } else if (gesture->move) { BLI_rcti_translate(rect, (event->x - gesture->winrct.xmin) - rect->xmax, (event->y - gesture->winrct.ymin) - rect->ymax); } else { rect->xmax = event->x - gesture->winrct.xmin; rect->ymax = event->y - gesture->winrct.ymin; } gesture_box_apply_rect(op); wm_gesture_tag_redraw(win); break; } #ifdef WITH_INPUT_NDOF case NDOF_MOTION: { return OPERATOR_PASS_THROUGH; } #endif #if 0 /* This allows view navigation, keep disabled as it's too unpredictable. */ default: return OPERATOR_PASS_THROUGH; #endif } } gesture->is_active_prev = gesture->is_active; return OPERATOR_RUNNING_MODAL; } void WM_gesture_box_cancel(bContext *C, wmOperator *op) { gesture_modal_end(C, op); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Circle Gesture * * Currently only used for selection or modal paint stuff, * calls #wmOperatorType.exec while hold mouse, exits on release * (with no difference between cancel and confirm). * * \{ */ static void gesture_circle_apply(bContext *C, wmOperator *op); int WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input"); op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_CIRCLE); wmGesture *gesture = op->customdata; rcti *rect = gesture->customdata; /* Default or previously stored value. */ rect->xmax = RNA_int_get(op->ptr, "radius"); gesture->wait_for_input = wait_for_input; /* Starting with the mode starts immediately, * like having 'wait_for_input' disabled (some tools use this). */ if (gesture->wait_for_input == false) { gesture->is_active = true; gesture_circle_apply(C, op); gesture->is_active_prev = true; } /* add modal handler */ WM_event_add_modal_handler(C, op); wm_gesture_tag_redraw(win); return OPERATOR_RUNNING_MODAL; } static void gesture_circle_apply(bContext *C, wmOperator *op) { wmGesture *gesture = op->customdata; rcti *rect = gesture->customdata; if (gesture->wait_for_input && (gesture->modal_state == GESTURE_MODAL_NOP)) { return; } /* operator arguments and storage. */ RNA_int_set(op->ptr, "x", rect->xmin); RNA_int_set(op->ptr, "y", rect->ymin); RNA_int_set(op->ptr, "radius", rect->xmax); /* When 'wait_for_input' is false, * use properties to get the selection state (typically tool settings). * This is done so executing as a mode can select & de-select, see: T58594. */ if (gesture->wait_for_input) { gesture_modal_state_to_operator(op, gesture->modal_state); } if (op->type->exec) { int retval; retval = op->type->exec(C, op); OPERATOR_RETVAL_CHECK(retval); } } int WM_gesture_circle_modal(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); wmGesture *gesture = op->customdata; rcti *rect = gesture->customdata; if (event->type == MOUSEMOVE) { rect->xmin = event->x - gesture->winrct.xmin; rect->ymin = event->y - gesture->winrct.ymin; wm_gesture_tag_redraw(win); if (gesture->is_active) { gesture_circle_apply(C, op); } } else if (event->type == EVT_MODAL_MAP) { bool is_circle_size = false; bool is_finished = false; float fac; switch (event->val) { case GESTURE_MODAL_CIRCLE_SIZE: fac = 0.3f * (event->y - event->prevy); if (fac > 0) { rect->xmax += ceil(fac); } else { rect->xmax += floor(fac); } if (rect->xmax < 1) { rect->xmax = 1; } is_circle_size = true; break; case GESTURE_MODAL_CIRCLE_ADD: rect->xmax += 2 + rect->xmax / 10; is_circle_size = true; break; case GESTURE_MODAL_CIRCLE_SUB: rect->xmax -= 2 + rect->xmax / 10; if (rect->xmax < 1) { rect->xmax = 1; } is_circle_size = true; break; case GESTURE_MODAL_SELECT: case GESTURE_MODAL_DESELECT: case GESTURE_MODAL_NOP: { if (gesture->wait_for_input) { gesture->modal_state = event->val; } if (event->val == GESTURE_MODAL_NOP) { /* Single action, click-drag & release to exit. */ if (gesture->wait_for_input == false) { is_finished = true; } } else { /* apply first click */ gesture->is_active = true; gesture_circle_apply(C, op); wm_gesture_tag_redraw(win); } break; } case GESTURE_MODAL_CANCEL: case GESTURE_MODAL_CONFIRM: is_finished = true; } if (is_finished) { gesture_modal_end(C, op); return OPERATOR_FINISHED; /* use finish or we don't get an undo */ } if (is_circle_size) { wm_gesture_tag_redraw(win); /* So next use remembers last seen size, even if we didn't apply it. */ RNA_int_set(op->ptr, "radius", rect->xmax); } } #ifdef WITH_INPUT_NDOF else if (event->type == NDOF_MOTION) { return OPERATOR_PASS_THROUGH; } #endif #if 0 /* Allow view navigation??? */ /* NOTE: this gives issues: * 1) other modal ops run on top (box select), * 2) middle-mouse is used now 3) tablet/trackpad? */ else { return OPERATOR_PASS_THROUGH; } #endif gesture->is_active_prev = gesture->is_active; return OPERATOR_RUNNING_MODAL; } void WM_gesture_circle_cancel(bContext *C, wmOperator *op) { gesture_modal_end(C, op); } #if 0 /* template to copy from */ void WM_OT_circle_gesture(wmOperatorType *ot) { ot->name = "Circle Gesture"; ot->idname = "WM_OT_circle_gesture"; ot->description = "Enter rotate mode with a circular gesture"; ot->invoke = WM_gesture_circle_invoke; ot->modal = WM_gesture_circle_modal; ot->poll = WM_operator_winactive; /* properties */ WM_operator_properties_gesture_circle(ot); } #endif /** \} */ /* -------------------------------------------------------------------- */ /** \name Tweak Gesture * \{ */ static void gesture_tweak_modal(bContext *C, const wmEvent *event) { wmWindow *window = CTX_wm_window(C); wmGesture *gesture = window->tweak; rcti *rect = gesture->customdata; bool gesture_end = false; switch (event->type) { case MOUSEMOVE: case INBETWEEN_MOUSEMOVE: { rect->xmax = event->x - gesture->winrct.xmin; rect->ymax = event->y - gesture->winrct.ymin; const int val = wm_gesture_evaluate(gesture, event); if (val != 0) { wmEvent tevent; wm_event_init_from_window(window, &tevent); /* We want to get coord from start of drag, * not from point where it becomes a tweak event, see T40549. */ tevent.x = rect->xmin + gesture->winrct.xmin; tevent.y = rect->ymin + gesture->winrct.ymin; if (gesture->event_type == LEFTMOUSE) { tevent.type = EVT_TWEAK_L; } else if (gesture->event_type == RIGHTMOUSE) { tevent.type = EVT_TWEAK_R; } else { tevent.type = EVT_TWEAK_M; } tevent.val = val; tevent.is_repeat = false; /* mouse coords! */ /* important we add immediately after this event, so future mouse releases * (which may be in the queue already), are handled in order, see T44740 */ wm_event_add_ex(window, &tevent, event); gesture_end = true; } break; } case LEFTMOUSE: case RIGHTMOUSE: case MIDDLEMOUSE: if (gesture->event_type == event->type) { gesture_end = true; /* when tweak fails we should give the other keymap entries a chance */ /* XXX, assigning to readonly, BAD JUJU! */ ((wmEvent *)event)->val = KM_RELEASE; } break; default: if (!ISTIMER(event->type) && event->type != EVENT_NONE) { gesture_end = true; } break; } if (gesture_end) { /* Frees gesture itself, and unregisters from window. */ WM_gesture_end(window, gesture); /* This isn't very nice but needed to redraw gizmos which are hidden while tweaking, * See #WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK for details. */ ARegion *region = CTX_wm_region(C); if ((region != NULL) && (region->gizmo_map != NULL)) { if (WM_gizmomap_tag_delay_refresh_for_tweak_check(region->gizmo_map)) { ED_region_tag_redraw(region); } } } } /* standard tweak, called after window handlers passed on event */ void wm_tweakevent_test(bContext *C, const wmEvent *event, int action) { wmWindow *win = CTX_wm_window(C); if (win->tweak == NULL) { const ARegion *region = CTX_wm_region(C); if (region) { if (event->val == KM_PRESS) { if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) { win->tweak = WM_gesture_new(win, region, event, WM_GESTURE_TWEAK); } } } } else { /* no tweaks if event was handled */ if ((action & WM_HANDLER_BREAK)) { WM_gesture_end(win, win->tweak); } else { gesture_tweak_modal(C, event); } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Lasso Gesture * \{ */ int WM_gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); PropertyRNA *prop; op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_LASSO); /* add modal handler */ WM_event_add_modal_handler(C, op); wm_gesture_tag_redraw(win); if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) { WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop)); } return OPERATOR_RUNNING_MODAL; } int WM_gesture_lines_invoke(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); PropertyRNA *prop; op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_LINES); /* add modal handler */ WM_event_add_modal_handler(C, op); wm_gesture_tag_redraw(win); if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) { WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop)); } return OPERATOR_RUNNING_MODAL; } static int gesture_lasso_apply(bContext *C, wmOperator *op) { int retval = OPERATOR_FINISHED; wmGesture *gesture = op->customdata; PointerRNA itemptr; float loc[2]; int i; const short *lasso = gesture->customdata; /* operator storage as path. */ RNA_collection_clear(op->ptr, "path"); for (i = 0; i < gesture->points; i++, lasso += 2) { loc[0] = lasso[0]; loc[1] = lasso[1]; RNA_collection_add(op->ptr, "path", &itemptr); RNA_float_set_array(&itemptr, "loc", loc); } gesture_modal_end(C, op); if (op->type->exec) { retval = op->type->exec(C, op); OPERATOR_RETVAL_CHECK(retval); } return retval; } int WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event) { wmGesture *gesture = op->customdata; if (event->type == EVT_MODAL_MAP) { switch (event->val) { case GESTURE_MODAL_MOVE: { gesture->move = !gesture->move; break; } } } else { switch (event->type) { case MOUSEMOVE: case INBETWEEN_MOUSEMOVE: { wm_gesture_tag_redraw(CTX_wm_window(C)); if (gesture->points == gesture->points_alloc) { gesture->points_alloc *= 2; gesture->customdata = MEM_reallocN(gesture->customdata, sizeof(short[2]) * gesture->points_alloc); } { short(*lasso)[2] = gesture->customdata; const int x = ((event->x - gesture->winrct.xmin) - lasso[gesture->points - 1][0]); const int y = ((event->y - gesture->winrct.ymin) - lasso[gesture->points - 1][1]); /* move the lasso */ if (gesture->move) { for (int i = 0; i < gesture->points; i++) { lasso[i][0] += x; lasso[i][1] += y; } } /* Make a simple distance check to get a smoother lasso * add only when at least 2 pixels between this and previous location. */ else if ((x * x + y * y) > pow2f(2.0f * UI_DPI_FAC)) { lasso[gesture->points][0] = event->x - gesture->winrct.xmin; lasso[gesture->points][1] = event->y - gesture->winrct.ymin; gesture->points++; } } break; } case LEFTMOUSE: case MIDDLEMOUSE: case RIGHTMOUSE: { if (event->val == KM_RELEASE) { /* key release */ return gesture_lasso_apply(C, op); } break; } case EVT_ESCKEY: { gesture_modal_end(C, op); return OPERATOR_CANCELLED; } } } gesture->is_active_prev = gesture->is_active; return OPERATOR_RUNNING_MODAL; } int WM_gesture_lines_modal(bContext *C, wmOperator *op, const wmEvent *event) { return WM_gesture_lasso_modal(C, op, event); } void WM_gesture_lasso_cancel(bContext *C, wmOperator *op) { gesture_modal_end(C, op); } void WM_gesture_lines_cancel(bContext *C, wmOperator *op) { gesture_modal_end(C, op); } /** * helper function, we may want to add options for conversion to view space * * caller must free. */ const int (*WM_gesture_lasso_path_to_array(bContext *UNUSED(C), wmOperator *op, int *r_mcoords_len))[2] { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "path"); int(*mcoords)[2] = NULL; BLI_assert(prop != NULL); if (prop) { const int len = RNA_property_collection_length(op->ptr, prop); if (len) { int i = 0; mcoords = MEM_mallocN(sizeof(int[2]) * len, __func__); RNA_PROP_BEGIN (op->ptr, itemptr, prop) { float loc[2]; RNA_float_get_array(&itemptr, "loc", loc); mcoords[i][0] = (int)loc[0]; mcoords[i][1] = (int)loc[1]; i++; } RNA_PROP_END; } *r_mcoords_len = len; } else { *r_mcoords_len = 0; } /* cast for 'const' */ return mcoords; } #if 0 /* template to copy from */ static int gesture_lasso_exec(bContext *C, wmOperator *op) { RNA_BEGIN (op->ptr, itemptr, "path") { float loc[2]; RNA_float_get_array(&itemptr, "loc", loc); printf("Location: %f %f\n", loc[0], loc[1]); } RNA_END; return OPERATOR_FINISHED; } void WM_OT_lasso_gesture(wmOperatorType *ot) { PropertyRNA *prop; ot->name = "Lasso Gesture"; ot->idname = "WM_OT_lasso_gesture"; ot->description = "Select objects within the lasso as you move the pointer"; ot->invoke = WM_gesture_lasso_invoke; ot->modal = WM_gesture_lasso_modal; ot->exec = gesture_lasso_exec; ot->poll = WM_operator_winactive; prop = RNA_def_property(ot->srna, "path", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_runtime(ot->srna, prop, &RNA_OperatorMousePath); } #endif /** \} */ /* -------------------------------------------------------------------- */ /** \name Straight Line Gesture * * Gesture defined by the start and end points of a line that is created between the position of * the initial event and the position of the current event. * * Straight Line Gesture has two modal callbacks depending on the tool that is being implemented: a * regular modal callback intended to update the data during the execution of the gesture and a * one-shot callback that only updates the data once when the gesture finishes. * * It stores 4 values: `xstart, ystart, xend, yend`. * \{ */ static bool gesture_straightline_apply(bContext *C, wmOperator *op) { wmGesture *gesture = op->customdata; rcti *rect = gesture->customdata; if (rect->xmin == rect->xmax && rect->ymin == rect->ymax) { return 0; } /* operator arguments and storage. */ RNA_int_set(op->ptr, "xstart", rect->xmin); RNA_int_set(op->ptr, "ystart", rect->ymin); RNA_int_set(op->ptr, "xend", rect->xmax); RNA_int_set(op->ptr, "yend", rect->ymax); RNA_boolean_set(op->ptr, "flip", gesture->use_flip); if (op->type->exec) { int retval = op->type->exec(C, op); OPERATOR_RETVAL_CHECK(retval); } return 1; } int WM_gesture_straightline_invoke(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); PropertyRNA *prop; op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_STRAIGHTLINE); if (ISTWEAK(event->type)) { wmGesture *gesture = op->customdata; gesture->is_active = true; } /* add modal handler */ WM_event_add_modal_handler(C, op); wm_gesture_tag_redraw(win); if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) { WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop)); } return OPERATOR_RUNNING_MODAL; } /** * This invoke callback starts the straightline gesture with a viewport preview to the right side * of the line. */ int WM_gesture_straightline_active_side_invoke(bContext *C, wmOperator *op, const wmEvent *event) { WM_gesture_straightline_invoke(C, op, event); wmGesture *gesture = op->customdata; gesture->draw_active_side = true; gesture->use_flip = false; return OPERATOR_RUNNING_MODAL; } #define STRAIGHTLINE_SNAP_DEG 15.0f static void wm_gesture_straightline_do_angle_snap(rcti *rect) { const float line_start[2] = {rect->xmin, rect->ymin}; const float line_end[2] = {rect->xmax, rect->ymax}; const float x_axis[2] = {1.0f, 0.0f}; float line_direction[2]; sub_v2_v2v2(line_direction, line_end, line_start); const float line_length = normalize_v2(line_direction); const float angle = angle_signed_v2v2(x_axis, line_direction); const float angle_deg = RAD2DEG(angle) + (STRAIGHTLINE_SNAP_DEG / 2.0f); const float angle_snapped_deg = -floorf(angle_deg / STRAIGHTLINE_SNAP_DEG) * STRAIGHTLINE_SNAP_DEG; const float angle_snapped = DEG2RAD(angle_snapped_deg); float line_snapped_end[2]; rotate_v2_v2fl(line_snapped_end, x_axis, angle_snapped); mul_v2_fl(line_snapped_end, line_length); add_v2_v2(line_snapped_end, line_start); rect->xmax = (int)line_snapped_end[0]; rect->ymax = (int)line_snapped_end[1]; } /** * This modal callback calls exec once per mouse move event while the gesture is active with the * updated line start and end values, so it can be used for tools that have a real time preview * (like a gradient updating in real time over the mesh). */ int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *event) { wmGesture *gesture = op->customdata; wmWindow *win = CTX_wm_window(C); rcti *rect = gesture->customdata; if (event->type == EVT_MODAL_MAP) { switch (event->val) { case GESTURE_MODAL_MOVE: { gesture->move = !gesture->move; break; } case GESTURE_MODAL_BEGIN: { if (gesture->is_active == false) { gesture->is_active = true; wm_gesture_tag_redraw(win); } break; } case GESTURE_MODAL_SNAP: { /* Toggle snapping on/off. */ gesture->use_snap = !gesture->use_snap; break; } case GESTURE_MODAL_FLIP: { /* Toggle snapping on/off. */ gesture->use_flip = !gesture->use_flip; break; } case GESTURE_MODAL_SELECT: { if (gesture_straightline_apply(C, op)) { gesture_modal_end(C, op); return OPERATOR_FINISHED; } gesture_modal_end(C, op); return OPERATOR_CANCELLED; } case GESTURE_MODAL_CANCEL: { gesture_modal_end(C, op); return OPERATOR_CANCELLED; } } } else { switch (event->type) { case MOUSEMOVE: { if (gesture->is_active == false) { rect->xmin = rect->xmax = event->x - gesture->winrct.xmin; rect->ymin = rect->ymax = event->y - gesture->winrct.ymin; } else if (gesture->move) { BLI_rcti_translate(rect, (event->x - gesture->winrct.xmin) - rect->xmax, (event->y - gesture->winrct.ymin) - rect->ymax); gesture_straightline_apply(C, op); } else { rect->xmax = event->x - gesture->winrct.xmin; rect->ymax = event->y - gesture->winrct.ymin; gesture_straightline_apply(C, op); } if (gesture->use_snap) { wm_gesture_straightline_do_angle_snap(rect); } wm_gesture_tag_redraw(win); break; } } } gesture->is_active_prev = gesture->is_active; return OPERATOR_RUNNING_MODAL; } /** * This modal one-shot callback only calls exec once after the gesture finishes without any updates * during the gesture execution. Should be used for operations that are intended to be applied once * without real time preview (like a trimming tool that only applies the bisect operation once * after finishing the gesture as the bisect operation is too heavy to be computed in real time for * a preview). */ int WM_gesture_straightline_oneshot_modal(bContext *C, wmOperator *op, const wmEvent *event) { wmGesture *gesture = op->customdata; wmWindow *win = CTX_wm_window(C); rcti *rect = gesture->customdata; if (event->type == EVT_MODAL_MAP) { switch (event->val) { case GESTURE_MODAL_MOVE: { gesture->move = !gesture->move; break; } case GESTURE_MODAL_BEGIN: { if (gesture->is_active == false) { gesture->is_active = true; wm_gesture_tag_redraw(win); } break; } case GESTURE_MODAL_SNAP: { /* Toggle snapping on/off. */ gesture->use_snap = !gesture->use_snap; break; } case GESTURE_MODAL_FLIP: { /* Toggle flip on/off. */ gesture->use_flip = !gesture->use_flip; break; } case GESTURE_MODAL_SELECT: case GESTURE_MODAL_DESELECT: case GESTURE_MODAL_IN: case GESTURE_MODAL_OUT: { if (gesture->wait_for_input) { gesture->modal_state = event->val; } if (gesture_straightline_apply(C, op)) { gesture_modal_end(C, op); return OPERATOR_FINISHED; } gesture_modal_end(C, op); return OPERATOR_CANCELLED; } case GESTURE_MODAL_CANCEL: { gesture_modal_end(C, op); return OPERATOR_CANCELLED; } } } else { switch (event->type) { case MOUSEMOVE: { if (gesture->is_active == false) { rect->xmin = rect->xmax = event->x - gesture->winrct.xmin; rect->ymin = rect->ymax = event->y - gesture->winrct.ymin; } else if (gesture->move) { BLI_rcti_translate(rect, (event->x - gesture->winrct.xmin) - rect->xmax, (event->y - gesture->winrct.ymin) - rect->ymax); } else { rect->xmax = event->x - gesture->winrct.xmin; rect->ymax = event->y - gesture->winrct.ymin; } if (gesture->use_snap) { wm_gesture_straightline_do_angle_snap(rect); } wm_gesture_tag_redraw(win); break; } } } gesture->is_active_prev = gesture->is_active; return OPERATOR_RUNNING_MODAL; } void WM_gesture_straightline_cancel(bContext *C, wmOperator *op) { gesture_modal_end(C, op); } #if 0 /* template to copy from */ void WM_OT_straightline_gesture(wmOperatorType *ot) { PropertyRNA *prop; ot->name = "Straight Line Gesture"; ot->idname = "WM_OT_straightline_gesture"; ot->description = "Draw a straight line as you move the pointer"; ot->invoke = WM_gesture_straightline_invoke; ot->modal = WM_gesture_straightline_modal; ot->exec = gesture_straightline_exec; ot->poll = WM_operator_winactive; WM_operator_properties_gesture_straightline(ot, 0); } #endif /** \} */