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