1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 *
16 * The Original Code is Copyright (C) 2007 Blender Foundation.
17 * All rights reserved.
18 */
19
20 /** \file
21 * \ingroup wm
22 *
23 * Default operator callbacks for use with gestures (border/circle/lasso/straightline).
24 * Operators themselves are defined elsewhere.
25 *
26 * - Keymaps are in ``wm_operators.c``.
27 * - Property definitions are in ``wm_operator_props.c``.
28 */
29
30 #include "MEM_guardedalloc.h"
31
32 #include "DNA_windowmanager_types.h"
33
34 #include "BLI_math.h"
35 #include "BLI_rect.h"
36
37 #include "BKE_context.h"
38
39 #include "WM_api.h"
40 #include "WM_types.h"
41
42 #include "wm.h"
43 #include "wm_event_system.h"
44 #include "wm_event_types.h"
45
46 #include "ED_screen.h"
47 #include "ED_select_utils.h"
48
49 #include "UI_interface.h"
50
51 #include "RNA_access.h"
52 #include "RNA_define.h"
53
54 /* -------------------------------------------------------------------- */
55 /** \name Internal Gesture Utilities
56 *
57 * Border gesture has two types:
58 * -# #WM_GESTURE_CROSS_RECT: starts a cross, on mouse click it changes to border.
59 * -# #WM_GESTURE_RECT: starts immediate as a border, on mouse click or release it ends.
60 *
61 * It stores 4 values (xmin, xmax, ymin, ymax) and event it ended with (event_type).
62 *
63 * \{ */
64
gesture_modal_end(bContext * C,wmOperator * op)65 static void gesture_modal_end(bContext *C, wmOperator *op)
66 {
67 wmWindow *win = CTX_wm_window(C);
68 wmGesture *gesture = op->customdata;
69
70 WM_gesture_end(win, gesture); /* frees gesture itself, and unregisters from window */
71 op->customdata = NULL;
72
73 ED_area_tag_redraw(CTX_wm_area(C));
74
75 if (RNA_struct_find_property(op->ptr, "cursor")) {
76 WM_cursor_modal_restore(win);
77 }
78 }
79
gesture_modal_state_to_operator(wmOperator * op,int modal_state)80 static void gesture_modal_state_to_operator(wmOperator *op, int modal_state)
81 {
82 PropertyRNA *prop;
83
84 switch (modal_state) {
85 case GESTURE_MODAL_SELECT:
86 case GESTURE_MODAL_DESELECT:
87 if ((prop = RNA_struct_find_property(op->ptr, "deselect"))) {
88 RNA_property_boolean_set(op->ptr, prop, (modal_state == GESTURE_MODAL_DESELECT));
89 }
90 if ((prop = RNA_struct_find_property(op->ptr, "mode"))) {
91 RNA_property_enum_set(
92 op->ptr, prop, (modal_state == GESTURE_MODAL_DESELECT) ? SEL_OP_SUB : SEL_OP_ADD);
93 }
94 break;
95 case GESTURE_MODAL_IN:
96 case GESTURE_MODAL_OUT:
97 if ((prop = RNA_struct_find_property(op->ptr, "zoom_out"))) {
98 RNA_property_boolean_set(op->ptr, prop, (modal_state == GESTURE_MODAL_OUT));
99 }
100 break;
101 }
102 }
103
UNUSED_FUNCTION(gesture_modal_state_from_operator)104 static int UNUSED_FUNCTION(gesture_modal_state_from_operator)(wmOperator *op)
105 {
106 PropertyRNA *prop;
107
108 if ((prop = RNA_struct_find_property(op->ptr, "deselect"))) {
109 if (RNA_property_is_set(op->ptr, prop)) {
110 return RNA_property_boolean_get(op->ptr, prop) ? GESTURE_MODAL_DESELECT :
111 GESTURE_MODAL_SELECT;
112 }
113 }
114 if ((prop = RNA_struct_find_property(op->ptr, "mode"))) {
115 if (RNA_property_is_set(op->ptr, prop)) {
116 return RNA_property_enum_get(op->ptr, prop) == SEL_OP_SUB ? GESTURE_MODAL_DESELECT :
117 GESTURE_MODAL_SELECT;
118 }
119 }
120 if ((prop = RNA_struct_find_property(op->ptr, "zoom_out"))) {
121 if (RNA_property_is_set(op->ptr, prop)) {
122 return RNA_property_boolean_get(op->ptr, prop) ? GESTURE_MODAL_OUT : GESTURE_MODAL_IN;
123 }
124 }
125 return GESTURE_MODAL_NOP;
126 }
127 /** \} */
128
129 /* -------------------------------------------------------------------- */
130 /** \name Border Gesture
131 *
132 * Border gesture has two types:
133 * -# #WM_GESTURE_CROSS_RECT: starts a cross, on mouse click it changes to border.
134 * -# #WM_GESTURE_RECT: starts immediate as a border, on mouse click or release it ends.
135 *
136 * It stores 4 values (xmin, xmax, ymin, ymax) and event it ended with (event_type).
137 *
138 * \{ */
139
gesture_box_apply_rect(wmOperator * op)140 static bool gesture_box_apply_rect(wmOperator *op)
141 {
142 wmGesture *gesture = op->customdata;
143 rcti *rect = gesture->customdata;
144
145 if (rect->xmin == rect->xmax || rect->ymin == rect->ymax) {
146 return 0;
147 }
148
149 /* operator arguments and storage. */
150 RNA_int_set(op->ptr, "xmin", min_ii(rect->xmin, rect->xmax));
151 RNA_int_set(op->ptr, "ymin", min_ii(rect->ymin, rect->ymax));
152 RNA_int_set(op->ptr, "xmax", max_ii(rect->xmin, rect->xmax));
153 RNA_int_set(op->ptr, "ymax", max_ii(rect->ymin, rect->ymax));
154
155 return 1;
156 }
157
gesture_box_apply(bContext * C,wmOperator * op)158 static bool gesture_box_apply(bContext *C, wmOperator *op)
159 {
160 wmGesture *gesture = op->customdata;
161
162 int retval;
163
164 if (!gesture_box_apply_rect(op)) {
165 return 0;
166 }
167
168 if (gesture->wait_for_input) {
169 gesture_modal_state_to_operator(op, gesture->modal_state);
170 }
171
172 retval = op->type->exec(C, op);
173 OPERATOR_RETVAL_CHECK(retval);
174
175 return 1;
176 }
177
WM_gesture_box_invoke(bContext * C,wmOperator * op,const wmEvent * event)178 int WM_gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event)
179 {
180 wmWindow *win = CTX_wm_window(C);
181 const ARegion *region = CTX_wm_region(C);
182 const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input");
183
184 if (wait_for_input) {
185 op->customdata = WM_gesture_new(win, region, event, WM_GESTURE_CROSS_RECT);
186 }
187 else {
188 op->customdata = WM_gesture_new(win, region, event, WM_GESTURE_RECT);
189 }
190
191 {
192 wmGesture *gesture = op->customdata;
193 gesture->wait_for_input = wait_for_input;
194 }
195
196 /* add modal handler */
197 WM_event_add_modal_handler(C, op);
198
199 wm_gesture_tag_redraw(win);
200
201 return OPERATOR_RUNNING_MODAL;
202 }
203
WM_gesture_box_modal(bContext * C,wmOperator * op,const wmEvent * event)204 int WM_gesture_box_modal(bContext *C, wmOperator *op, const wmEvent *event)
205 {
206 wmWindow *win = CTX_wm_window(C);
207 wmGesture *gesture = op->customdata;
208 rcti *rect = gesture->customdata;
209
210 if (event->type == EVT_MODAL_MAP) {
211 switch (event->val) {
212 case GESTURE_MODAL_MOVE: {
213 gesture->move = !gesture->move;
214 break;
215 }
216 case GESTURE_MODAL_BEGIN: {
217 if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->is_active == false) {
218 gesture->is_active = true;
219 wm_gesture_tag_redraw(win);
220 }
221 break;
222 }
223 case GESTURE_MODAL_SELECT:
224 case GESTURE_MODAL_DESELECT:
225 case GESTURE_MODAL_IN:
226 case GESTURE_MODAL_OUT: {
227 if (gesture->wait_for_input) {
228 gesture->modal_state = event->val;
229 }
230 if (gesture_box_apply(C, op)) {
231 gesture_modal_end(C, op);
232 return OPERATOR_FINISHED;
233 }
234 gesture_modal_end(C, op);
235 return OPERATOR_CANCELLED;
236 }
237 case GESTURE_MODAL_CANCEL: {
238 gesture_modal_end(C, op);
239 return OPERATOR_CANCELLED;
240 }
241 }
242 }
243 else {
244 switch (event->type) {
245 case MOUSEMOVE: {
246 if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->is_active == false) {
247 rect->xmin = rect->xmax = event->x - gesture->winrct.xmin;
248 rect->ymin = rect->ymax = event->y - gesture->winrct.ymin;
249 }
250 else if (gesture->move) {
251 BLI_rcti_translate(rect,
252 (event->x - gesture->winrct.xmin) - rect->xmax,
253 (event->y - gesture->winrct.ymin) - rect->ymax);
254 }
255 else {
256 rect->xmax = event->x - gesture->winrct.xmin;
257 rect->ymax = event->y - gesture->winrct.ymin;
258 }
259 gesture_box_apply_rect(op);
260
261 wm_gesture_tag_redraw(win);
262
263 break;
264 }
265 #ifdef WITH_INPUT_NDOF
266 case NDOF_MOTION: {
267 return OPERATOR_PASS_THROUGH;
268 }
269 #endif
270
271 #if 0 /* This allows view navigation, keep disabled as it's too unpredictable. */
272 default:
273 return OPERATOR_PASS_THROUGH;
274 #endif
275 }
276 }
277
278 gesture->is_active_prev = gesture->is_active;
279 return OPERATOR_RUNNING_MODAL;
280 }
281
WM_gesture_box_cancel(bContext * C,wmOperator * op)282 void WM_gesture_box_cancel(bContext *C, wmOperator *op)
283 {
284 gesture_modal_end(C, op);
285 }
286
287 /** \} */
288
289 /* -------------------------------------------------------------------- */
290 /** \name Circle Gesture
291 *
292 * Currently only used for selection or modal paint stuff,
293 * calls #wmOperatorType.exec while hold mouse, exits on release
294 * (with no difference between cancel and confirm).
295 *
296 * \{ */
297
298 static void gesture_circle_apply(bContext *C, wmOperator *op);
299
WM_gesture_circle_invoke(bContext * C,wmOperator * op,const wmEvent * event)300 int WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event)
301 {
302 wmWindow *win = CTX_wm_window(C);
303 const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input");
304
305 op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_CIRCLE);
306 wmGesture *gesture = op->customdata;
307 rcti *rect = gesture->customdata;
308
309 /* Default or previously stored value. */
310 rect->xmax = RNA_int_get(op->ptr, "radius");
311
312 gesture->wait_for_input = wait_for_input;
313
314 /* Starting with the mode starts immediately,
315 * like having 'wait_for_input' disabled (some tools use this). */
316 if (gesture->wait_for_input == false) {
317 gesture->is_active = true;
318 gesture_circle_apply(C, op);
319 }
320
321 /* add modal handler */
322 WM_event_add_modal_handler(C, op);
323
324 wm_gesture_tag_redraw(win);
325
326 return OPERATOR_RUNNING_MODAL;
327 }
328
gesture_circle_apply(bContext * C,wmOperator * op)329 static void gesture_circle_apply(bContext *C, wmOperator *op)
330 {
331 wmGesture *gesture = op->customdata;
332 rcti *rect = gesture->customdata;
333
334 if (gesture->wait_for_input && (gesture->modal_state == GESTURE_MODAL_NOP)) {
335 return;
336 }
337
338 /* operator arguments and storage. */
339 RNA_int_set(op->ptr, "x", rect->xmin);
340 RNA_int_set(op->ptr, "y", rect->ymin);
341 RNA_int_set(op->ptr, "radius", rect->xmax);
342
343 /* When 'wait_for_input' is false,
344 * use properties to get the selection state (typically tool settings).
345 * This is done so executing as a mode can select & de-select, see: T58594. */
346 if (gesture->wait_for_input) {
347 gesture_modal_state_to_operator(op, gesture->modal_state);
348 }
349
350 if (op->type->exec) {
351 int retval;
352 retval = op->type->exec(C, op);
353 OPERATOR_RETVAL_CHECK(retval);
354 }
355 }
356
WM_gesture_circle_modal(bContext * C,wmOperator * op,const wmEvent * event)357 int WM_gesture_circle_modal(bContext *C, wmOperator *op, const wmEvent *event)
358 {
359 wmWindow *win = CTX_wm_window(C);
360 wmGesture *gesture = op->customdata;
361 rcti *rect = gesture->customdata;
362
363 if (event->type == MOUSEMOVE) {
364
365 rect->xmin = event->x - gesture->winrct.xmin;
366 rect->ymin = event->y - gesture->winrct.ymin;
367
368 wm_gesture_tag_redraw(win);
369
370 if (gesture->is_active) {
371 gesture_circle_apply(C, op);
372 }
373 }
374 else if (event->type == EVT_MODAL_MAP) {
375 bool is_circle_size = false;
376 bool is_finished = false;
377 float fac;
378
379 switch (event->val) {
380 case GESTURE_MODAL_CIRCLE_SIZE:
381 fac = 0.3f * (event->y - event->prevy);
382 if (fac > 0) {
383 rect->xmax += ceil(fac);
384 }
385 else {
386 rect->xmax += floor(fac);
387 }
388 if (rect->xmax < 1) {
389 rect->xmax = 1;
390 }
391 is_circle_size = true;
392 break;
393 case GESTURE_MODAL_CIRCLE_ADD:
394 rect->xmax += 2 + rect->xmax / 10;
395 is_circle_size = true;
396 break;
397 case GESTURE_MODAL_CIRCLE_SUB:
398 rect->xmax -= 2 + rect->xmax / 10;
399 if (rect->xmax < 1) {
400 rect->xmax = 1;
401 }
402 is_circle_size = true;
403 break;
404 case GESTURE_MODAL_SELECT:
405 case GESTURE_MODAL_DESELECT:
406 case GESTURE_MODAL_NOP: {
407 if (gesture->wait_for_input) {
408 gesture->modal_state = event->val;
409 }
410 if (event->val == GESTURE_MODAL_NOP) {
411 /* Single action, click-drag & release to exit. */
412 if (gesture->wait_for_input == false) {
413 is_finished = true;
414 }
415 }
416 else {
417 /* apply first click */
418 gesture->is_active = true;
419 gesture_circle_apply(C, op);
420 wm_gesture_tag_redraw(win);
421 }
422 break;
423 }
424 case GESTURE_MODAL_CANCEL:
425 case GESTURE_MODAL_CONFIRM:
426 is_finished = true;
427 }
428
429 if (is_finished) {
430 gesture_modal_end(C, op);
431 return OPERATOR_FINISHED; /* use finish or we don't get an undo */
432 }
433
434 if (is_circle_size) {
435 wm_gesture_tag_redraw(win);
436
437 /* So next use remembers last seen size, even if we didn't apply it. */
438 RNA_int_set(op->ptr, "radius", rect->xmax);
439 }
440 }
441 #ifdef WITH_INPUT_NDOF
442 else if (event->type == NDOF_MOTION) {
443 return OPERATOR_PASS_THROUGH;
444 }
445 #endif
446
447 #if 0
448 /* Allow view navigation??? */
449 /* note, this gives issues:
450 * 1) other modal ops run on top (box select),
451 * 2) middle-mouse is used now 3) tablet/trackpad? */
452 else {
453 return OPERATOR_PASS_THROUGH;
454 }
455 #endif
456
457 gesture->is_active_prev = gesture->is_active;
458 return OPERATOR_RUNNING_MODAL;
459 }
460
WM_gesture_circle_cancel(bContext * C,wmOperator * op)461 void WM_gesture_circle_cancel(bContext *C, wmOperator *op)
462 {
463 gesture_modal_end(C, op);
464 }
465
466 #if 0
467 /* template to copy from */
468 void WM_OT_circle_gesture(wmOperatorType *ot)
469 {
470 ot->name = "Circle Gesture";
471 ot->idname = "WM_OT_circle_gesture";
472 ot->description = "Enter rotate mode with a circular gesture";
473
474 ot->invoke = WM_gesture_circle_invoke;
475 ot->modal = WM_gesture_circle_modal;
476 ot->poll = WM_operator_winactive;
477
478 /* properties */
479 WM_operator_properties_gesture_circle(ot);
480 }
481 #endif
482
483 /** \} */
484
485 /* -------------------------------------------------------------------- */
486 /** \name Tweak Gesture
487 * \{ */
488
gesture_tweak_modal(bContext * C,const wmEvent * event)489 static void gesture_tweak_modal(bContext *C, const wmEvent *event)
490 {
491 wmWindow *window = CTX_wm_window(C);
492 wmGesture *gesture = window->tweak;
493 rcti *rect = gesture->customdata;
494 bool gesture_end = false;
495
496 switch (event->type) {
497 case MOUSEMOVE:
498 case INBETWEEN_MOUSEMOVE: {
499
500 rect->xmax = event->x - gesture->winrct.xmin;
501 rect->ymax = event->y - gesture->winrct.ymin;
502
503 const int val = wm_gesture_evaluate(gesture, event);
504 if (val != 0) {
505 wmEvent tevent;
506
507 wm_event_init_from_window(window, &tevent);
508 /* We want to get coord from start of drag,
509 * not from point where it becomes a tweak event, see T40549. */
510 tevent.x = rect->xmin + gesture->winrct.xmin;
511 tevent.y = rect->ymin + gesture->winrct.ymin;
512 if (gesture->event_type == LEFTMOUSE) {
513 tevent.type = EVT_TWEAK_L;
514 }
515 else if (gesture->event_type == RIGHTMOUSE) {
516 tevent.type = EVT_TWEAK_R;
517 }
518 else {
519 tevent.type = EVT_TWEAK_M;
520 }
521 tevent.val = val;
522 tevent.is_repeat = false;
523 /* mouse coords! */
524
525 /* important we add immediately after this event, so future mouse releases
526 * (which may be in the queue already), are handled in order, see T44740 */
527 wm_event_add_ex(window, &tevent, event);
528
529 gesture_end = true;
530 }
531
532 break;
533 }
534
535 case LEFTMOUSE:
536 case RIGHTMOUSE:
537 case MIDDLEMOUSE:
538 if (gesture->event_type == event->type) {
539 gesture_end = true;
540
541 /* when tweak fails we should give the other keymap entries a chance */
542
543 /* XXX, assigning to readonly, BAD JUJU! */
544 ((wmEvent *)event)->val = KM_RELEASE;
545 }
546 break;
547 default:
548 if (!ISTIMER(event->type) && event->type != EVENT_NONE) {
549 gesture_end = true;
550 }
551 break;
552 }
553
554 if (gesture_end) {
555 /* Frees gesture itself, and unregisters from window. */
556 WM_gesture_end(window, gesture);
557
558 /* This isn't very nice but needed to redraw gizmos which are hidden while tweaking,
559 * See #WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK for details. */
560 ARegion *region = CTX_wm_region(C);
561 if ((region != NULL) && (region->gizmo_map != NULL)) {
562 if (WM_gizmomap_tag_delay_refresh_for_tweak_check(region->gizmo_map)) {
563 ED_region_tag_redraw(region);
564 }
565 }
566 }
567 }
568
569 /* standard tweak, called after window handlers passed on event */
wm_tweakevent_test(bContext * C,const wmEvent * event,int action)570 void wm_tweakevent_test(bContext *C, const wmEvent *event, int action)
571 {
572 wmWindow *win = CTX_wm_window(C);
573
574 if (win->tweak == NULL) {
575 const ARegion *region = CTX_wm_region(C);
576
577 if (region) {
578 if (event->val == KM_PRESS) {
579 if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) {
580 win->tweak = WM_gesture_new(win, region, event, WM_GESTURE_TWEAK);
581 }
582 }
583 }
584 }
585 else {
586 /* no tweaks if event was handled */
587 if ((action & WM_HANDLER_BREAK)) {
588 WM_gesture_end(win, win->tweak);
589 }
590 else {
591 gesture_tweak_modal(C, event);
592 }
593 }
594 }
595
596 /** \} */
597
598 /* -------------------------------------------------------------------- */
599 /** \name Lasso Gesture
600 * \{ */
601
WM_gesture_lasso_invoke(bContext * C,wmOperator * op,const wmEvent * event)602 int WM_gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event)
603 {
604 wmWindow *win = CTX_wm_window(C);
605 PropertyRNA *prop;
606
607 op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_LASSO);
608
609 /* add modal handler */
610 WM_event_add_modal_handler(C, op);
611
612 wm_gesture_tag_redraw(win);
613
614 if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) {
615 WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop));
616 }
617
618 return OPERATOR_RUNNING_MODAL;
619 }
620
WM_gesture_lines_invoke(bContext * C,wmOperator * op,const wmEvent * event)621 int WM_gesture_lines_invoke(bContext *C, wmOperator *op, const wmEvent *event)
622 {
623 wmWindow *win = CTX_wm_window(C);
624 PropertyRNA *prop;
625
626 op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_LINES);
627
628 /* add modal handler */
629 WM_event_add_modal_handler(C, op);
630
631 wm_gesture_tag_redraw(win);
632
633 if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) {
634 WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop));
635 }
636
637 return OPERATOR_RUNNING_MODAL;
638 }
639
gesture_lasso_apply(bContext * C,wmOperator * op)640 static int gesture_lasso_apply(bContext *C, wmOperator *op)
641 {
642 int retval = OPERATOR_FINISHED;
643 wmGesture *gesture = op->customdata;
644 PointerRNA itemptr;
645 float loc[2];
646 int i;
647 const short *lasso = gesture->customdata;
648
649 /* operator storage as path. */
650
651 RNA_collection_clear(op->ptr, "path");
652 for (i = 0; i < gesture->points; i++, lasso += 2) {
653 loc[0] = lasso[0];
654 loc[1] = lasso[1];
655 RNA_collection_add(op->ptr, "path", &itemptr);
656 RNA_float_set_array(&itemptr, "loc", loc);
657 }
658
659 gesture_modal_end(C, op);
660
661 if (op->type->exec) {
662 retval = op->type->exec(C, op);
663 OPERATOR_RETVAL_CHECK(retval);
664 }
665
666 return retval;
667 }
668
WM_gesture_lasso_modal(bContext * C,wmOperator * op,const wmEvent * event)669 int WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event)
670 {
671 wmGesture *gesture = op->customdata;
672
673 if (event->type == EVT_MODAL_MAP) {
674 switch (event->val) {
675 case GESTURE_MODAL_MOVE: {
676 gesture->move = !gesture->move;
677 break;
678 }
679 }
680 }
681 else {
682 switch (event->type) {
683 case MOUSEMOVE:
684 case INBETWEEN_MOUSEMOVE: {
685 wm_gesture_tag_redraw(CTX_wm_window(C));
686
687 if (gesture->points == gesture->points_alloc) {
688 gesture->points_alloc *= 2;
689 gesture->customdata = MEM_reallocN(gesture->customdata,
690 sizeof(short[2]) * gesture->points_alloc);
691 }
692
693 {
694 short(*lasso)[2] = gesture->customdata;
695
696 const int x = ((event->x - gesture->winrct.xmin) - lasso[gesture->points - 1][0]);
697 const int y = ((event->y - gesture->winrct.ymin) - lasso[gesture->points - 1][1]);
698
699 /* move the lasso */
700 if (gesture->move) {
701 for (int i = 0; i < gesture->points; i++) {
702 lasso[i][0] += x;
703 lasso[i][1] += y;
704 }
705 }
706 /* Make a simple distance check to get a smoother lasso
707 * add only when at least 2 pixels between this and previous location. */
708 else if ((x * x + y * y) > pow2f(2.0f * UI_DPI_FAC)) {
709 lasso[gesture->points][0] = event->x - gesture->winrct.xmin;
710 lasso[gesture->points][1] = event->y - gesture->winrct.ymin;
711 gesture->points++;
712 }
713 }
714 break;
715 }
716 case LEFTMOUSE:
717 case MIDDLEMOUSE:
718 case RIGHTMOUSE: {
719 if (event->val == KM_RELEASE) { /* key release */
720 return gesture_lasso_apply(C, op);
721 }
722 break;
723 }
724 case EVT_ESCKEY: {
725 gesture_modal_end(C, op);
726 return OPERATOR_CANCELLED;
727 }
728 }
729 }
730
731 gesture->is_active_prev = gesture->is_active;
732 return OPERATOR_RUNNING_MODAL;
733 }
734
WM_gesture_lines_modal(bContext * C,wmOperator * op,const wmEvent * event)735 int WM_gesture_lines_modal(bContext *C, wmOperator *op, const wmEvent *event)
736 {
737 return WM_gesture_lasso_modal(C, op, event);
738 }
739
WM_gesture_lasso_cancel(bContext * C,wmOperator * op)740 void WM_gesture_lasso_cancel(bContext *C, wmOperator *op)
741 {
742 gesture_modal_end(C, op);
743 }
744
WM_gesture_lines_cancel(bContext * C,wmOperator * op)745 void WM_gesture_lines_cancel(bContext *C, wmOperator *op)
746 {
747 gesture_modal_end(C, op);
748 }
749
750 /**
751 * helper function, we may want to add options for conversion to view space
752 *
753 * caller must free.
754 */
WM_gesture_lasso_path_to_array(bContext * UNUSED (C),wmOperator * op,int * r_mcoords_len)755 const int (*WM_gesture_lasso_path_to_array(bContext *UNUSED(C),
756 wmOperator *op,
757 int *r_mcoords_len))[2]
758 {
759 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "path");
760 int(*mcoords)[2] = NULL;
761 BLI_assert(prop != NULL);
762
763 if (prop) {
764 const int len = RNA_property_collection_length(op->ptr, prop);
765
766 if (len) {
767 int i = 0;
768 mcoords = MEM_mallocN(sizeof(int[2]) * len, __func__);
769
770 RNA_PROP_BEGIN (op->ptr, itemptr, prop) {
771 float loc[2];
772
773 RNA_float_get_array(&itemptr, "loc", loc);
774 mcoords[i][0] = (int)loc[0];
775 mcoords[i][1] = (int)loc[1];
776 i++;
777 }
778 RNA_PROP_END;
779 }
780 *r_mcoords_len = len;
781 }
782 else {
783 *r_mcoords_len = 0;
784 }
785
786 /* cast for 'const' */
787 return mcoords;
788 }
789
790 #if 0
791 /* template to copy from */
792
793 static int gesture_lasso_exec(bContext *C, wmOperator *op)
794 {
795 RNA_BEGIN (op->ptr, itemptr, "path") {
796 float loc[2];
797
798 RNA_float_get_array(&itemptr, "loc", loc);
799 printf("Location: %f %f\n", loc[0], loc[1]);
800 }
801 RNA_END;
802
803 return OPERATOR_FINISHED;
804 }
805
806 void WM_OT_lasso_gesture(wmOperatorType *ot)
807 {
808 PropertyRNA *prop;
809
810 ot->name = "Lasso Gesture";
811 ot->idname = "WM_OT_lasso_gesture";
812 ot->description = "Select objects within the lasso as you move the pointer";
813
814 ot->invoke = WM_gesture_lasso_invoke;
815 ot->modal = WM_gesture_lasso_modal;
816 ot->exec = gesture_lasso_exec;
817
818 ot->poll = WM_operator_winactive;
819
820 prop = RNA_def_property(ot->srna, "path", PROP_COLLECTION, PROP_NONE);
821 RNA_def_property_struct_runtime(prop, &RNA_OperatorMousePath);
822 }
823 #endif
824
825 /** \} */
826
827 /* -------------------------------------------------------------------- */
828 /** \name Straight Line Gesture
829 *
830 * Gesture defined by the start and end points of a line that is created between the position of
831 * the initial event and the position of the current event.
832 *
833 * Straight Line Gesture has two modal callbacks depending on the tool that is being implemented: a
834 * regular modal callback intended to update the data during the execution of the gesture and a
835 * one-shot callback that only updates the data once when the gesture finishes.
836 *
837 * It stores 4 values: `xstart, ystart, xend, yend`.
838 * \{ */
839
gesture_straightline_apply(bContext * C,wmOperator * op)840 static bool gesture_straightline_apply(bContext *C, wmOperator *op)
841 {
842 wmGesture *gesture = op->customdata;
843 rcti *rect = gesture->customdata;
844
845 if (rect->xmin == rect->xmax && rect->ymin == rect->ymax) {
846 return 0;
847 }
848
849 /* operator arguments and storage. */
850 RNA_int_set(op->ptr, "xstart", rect->xmin);
851 RNA_int_set(op->ptr, "ystart", rect->ymin);
852 RNA_int_set(op->ptr, "xend", rect->xmax);
853 RNA_int_set(op->ptr, "yend", rect->ymax);
854 RNA_boolean_set(op->ptr, "flip", gesture->use_flip);
855
856 if (op->type->exec) {
857 int retval = op->type->exec(C, op);
858 OPERATOR_RETVAL_CHECK(retval);
859 }
860
861 return 1;
862 }
863
WM_gesture_straightline_invoke(bContext * C,wmOperator * op,const wmEvent * event)864 int WM_gesture_straightline_invoke(bContext *C, wmOperator *op, const wmEvent *event)
865 {
866 wmWindow *win = CTX_wm_window(C);
867 PropertyRNA *prop;
868
869 op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_STRAIGHTLINE);
870
871 if (ISTWEAK(event->type)) {
872 wmGesture *gesture = op->customdata;
873 gesture->is_active = true;
874 }
875
876 /* add modal handler */
877 WM_event_add_modal_handler(C, op);
878
879 wm_gesture_tag_redraw(win);
880
881 if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) {
882 WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop));
883 }
884
885 return OPERATOR_RUNNING_MODAL;
886 }
887 /**
888 * This invoke callback starts the straightline gesture with a viewport preview to the right side
889 * of the line.
890 */
WM_gesture_straightline_active_side_invoke(bContext * C,wmOperator * op,const wmEvent * event)891 int WM_gesture_straightline_active_side_invoke(bContext *C, wmOperator *op, const wmEvent *event)
892 {
893 WM_gesture_straightline_invoke(C, op, event);
894 wmGesture *gesture = op->customdata;
895 gesture->draw_active_side = true;
896 gesture->use_flip = false;
897 return OPERATOR_RUNNING_MODAL;
898 }
899
900 #define STRAIGHTLINE_SNAP_DEG 15.0f
wm_gesture_straightline_do_angle_snap(rcti * rect)901 static void wm_gesture_straightline_do_angle_snap(rcti *rect)
902 {
903 const float line_start[2] = {rect->xmin, rect->ymin};
904 const float line_end[2] = {rect->xmax, rect->ymax};
905 const float x_axis[2] = {1.0f, 0.0f};
906
907 float line_direction[2];
908 sub_v2_v2v2(line_direction, line_end, line_start);
909 const float line_length = normalize_v2(line_direction);
910
911 const float angle = angle_signed_v2v2(x_axis, line_direction);
912 const float angle_deg = RAD2DEG(angle) + (STRAIGHTLINE_SNAP_DEG / 2.0f);
913 const float angle_snapped_deg = -floorf(angle_deg / STRAIGHTLINE_SNAP_DEG) *
914 STRAIGHTLINE_SNAP_DEG;
915 const float angle_snapped = DEG2RAD(angle_snapped_deg);
916
917 float line_snapped_end[2];
918 rotate_v2_v2fl(line_snapped_end, x_axis, angle_snapped);
919 mul_v2_fl(line_snapped_end, line_length);
920 add_v2_v2(line_snapped_end, line_start);
921
922 rect->xmax = (int)line_snapped_end[0];
923 rect->ymax = (int)line_snapped_end[1];
924 }
925
926 /**
927 * This modal callback calls exec once per mouse move event while the gesture is active with the
928 * updated line start and end values, so it can be used for tools that have a real time preview
929 * (like a gradient updating in real time over the mesh).
930 */
WM_gesture_straightline_modal(bContext * C,wmOperator * op,const wmEvent * event)931 int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *event)
932 {
933 wmGesture *gesture = op->customdata;
934 wmWindow *win = CTX_wm_window(C);
935 rcti *rect = gesture->customdata;
936
937 if (event->type == EVT_MODAL_MAP) {
938 switch (event->val) {
939 case GESTURE_MODAL_MOVE: {
940 gesture->move = !gesture->move;
941 break;
942 }
943 case GESTURE_MODAL_BEGIN: {
944 if (gesture->is_active == false) {
945 gesture->is_active = true;
946 wm_gesture_tag_redraw(win);
947 }
948 break;
949 }
950 case GESTURE_MODAL_SNAP: {
951 /* Toggle snapping on/off. */
952 gesture->use_snap = !gesture->use_snap;
953 break;
954 }
955 case GESTURE_MODAL_FLIP: {
956 /* Toggle snapping on/off. */
957 gesture->use_flip = !gesture->use_flip;
958 break;
959 }
960 case GESTURE_MODAL_SELECT: {
961 if (gesture_straightline_apply(C, op)) {
962 gesture_modal_end(C, op);
963 return OPERATOR_FINISHED;
964 }
965 gesture_modal_end(C, op);
966 return OPERATOR_CANCELLED;
967 }
968 case GESTURE_MODAL_CANCEL: {
969 gesture_modal_end(C, op);
970 return OPERATOR_CANCELLED;
971 }
972 }
973 }
974 else {
975 switch (event->type) {
976 case MOUSEMOVE: {
977 if (gesture->is_active == false) {
978 rect->xmin = rect->xmax = event->x - gesture->winrct.xmin;
979 rect->ymin = rect->ymax = event->y - gesture->winrct.ymin;
980 }
981 else if (gesture->move) {
982 BLI_rcti_translate(rect,
983 (event->x - gesture->winrct.xmin) - rect->xmax,
984 (event->y - gesture->winrct.ymin) - rect->ymax);
985 gesture_straightline_apply(C, op);
986 }
987 else {
988 rect->xmax = event->x - gesture->winrct.xmin;
989 rect->ymax = event->y - gesture->winrct.ymin;
990 gesture_straightline_apply(C, op);
991 }
992
993 if (gesture->use_snap) {
994 wm_gesture_straightline_do_angle_snap(rect);
995 }
996
997 wm_gesture_tag_redraw(win);
998
999 break;
1000 }
1001 }
1002 }
1003
1004 gesture->is_active_prev = gesture->is_active;
1005 return OPERATOR_RUNNING_MODAL;
1006 }
1007
1008 /**
1009 * This modal one-shot callback only calls exec once after the gesture finishes without any updates
1010 * during the gesture execution. Should be used for operations that are intended to be applied once
1011 * without real time preview (like a trimming tool that only applies the bisect operation once
1012 * after finishing the gesture as the bisect operation is too heavy to be computed in real time for
1013 * a preview).
1014 */
WM_gesture_straightline_oneshot_modal(bContext * C,wmOperator * op,const wmEvent * event)1015 int WM_gesture_straightline_oneshot_modal(bContext *C, wmOperator *op, const wmEvent *event)
1016 {
1017 wmGesture *gesture = op->customdata;
1018 wmWindow *win = CTX_wm_window(C);
1019 rcti *rect = gesture->customdata;
1020
1021 if (event->type == EVT_MODAL_MAP) {
1022 switch (event->val) {
1023 case GESTURE_MODAL_MOVE: {
1024 gesture->move = !gesture->move;
1025 break;
1026 }
1027 case GESTURE_MODAL_BEGIN: {
1028 if (gesture->is_active == false) {
1029 gesture->is_active = true;
1030 wm_gesture_tag_redraw(win);
1031 }
1032 break;
1033 }
1034 case GESTURE_MODAL_SNAP: {
1035 /* Toggle snapping on/off. */
1036 gesture->use_snap = !gesture->use_snap;
1037 break;
1038 }
1039 case GESTURE_MODAL_FLIP: {
1040 /* Toggle flip on/off. */
1041 gesture->use_flip = !gesture->use_flip;
1042 break;
1043 }
1044 case GESTURE_MODAL_SELECT:
1045 case GESTURE_MODAL_DESELECT:
1046 case GESTURE_MODAL_IN:
1047 case GESTURE_MODAL_OUT: {
1048 if (gesture->wait_for_input) {
1049 gesture->modal_state = event->val;
1050 }
1051 if (gesture_straightline_apply(C, op)) {
1052 gesture_modal_end(C, op);
1053 return OPERATOR_FINISHED;
1054 }
1055 gesture_modal_end(C, op);
1056 return OPERATOR_CANCELLED;
1057 }
1058 case GESTURE_MODAL_CANCEL: {
1059 gesture_modal_end(C, op);
1060 return OPERATOR_CANCELLED;
1061 }
1062 }
1063 }
1064 else {
1065 switch (event->type) {
1066 case MOUSEMOVE: {
1067 if (gesture->is_active == false) {
1068 rect->xmin = rect->xmax = event->x - gesture->winrct.xmin;
1069 rect->ymin = rect->ymax = event->y - gesture->winrct.ymin;
1070 }
1071 else if (gesture->move) {
1072 BLI_rcti_translate(rect,
1073 (event->x - gesture->winrct.xmin) - rect->xmax,
1074 (event->y - gesture->winrct.ymin) - rect->ymax);
1075 }
1076 else {
1077 rect->xmax = event->x - gesture->winrct.xmin;
1078 rect->ymax = event->y - gesture->winrct.ymin;
1079 }
1080
1081 if (gesture->use_snap) {
1082 wm_gesture_straightline_do_angle_snap(rect);
1083 }
1084
1085 wm_gesture_tag_redraw(win);
1086
1087 break;
1088 }
1089 }
1090 }
1091
1092 gesture->is_active_prev = gesture->is_active;
1093 return OPERATOR_RUNNING_MODAL;
1094 }
1095
WM_gesture_straightline_cancel(bContext * C,wmOperator * op)1096 void WM_gesture_straightline_cancel(bContext *C, wmOperator *op)
1097 {
1098 gesture_modal_end(C, op);
1099 }
1100
1101 #if 0
1102 /* template to copy from */
1103 void WM_OT_straightline_gesture(wmOperatorType *ot)
1104 {
1105 PropertyRNA *prop;
1106
1107 ot->name = "Straight Line Gesture";
1108 ot->idname = "WM_OT_straightline_gesture";
1109 ot->description = "Draw a straight line as you move the pointer";
1110
1111 ot->invoke = WM_gesture_straightline_invoke;
1112 ot->modal = WM_gesture_straightline_modal;
1113 ot->exec = gesture_straightline_exec;
1114
1115 ot->poll = WM_operator_winactive;
1116
1117 WM_operator_properties_gesture_straightline(ot, 0);
1118 }
1119 #endif
1120
1121 /** \} */
1122