b400b9bf4ed9ee33ad92ff3c00723e6a918a4b51
[blender.git] / source / blender / editors / interface / view2d_ops.c
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) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup edinterface
22  */
23
24 #include <math.h>
25
26 #include "MEM_guardedalloc.h"
27
28 #include "DNA_userdef_types.h"
29 #include "DNA_windowmanager_types.h"
30
31 #include "BLI_blenlib.h"
32 #include "BLI_utildefines.h"
33 #include "BLI_math_base.h"
34
35 #include "BKE_context.h"
36
37 #include "RNA_access.h"
38 #include "RNA_define.h"
39
40 #include "WM_api.h"
41 #include "WM_types.h"
42
43 #include "ED_screen.h"
44
45 #include "UI_view2d.h"
46 #include "UI_interface.h"
47
48 #include "PIL_time.h" /* USER_ZOOM_CONT */
49
50 static bool view2d_poll(bContext *C)
51 {
52   ARegion *ar = CTX_wm_region(C);
53
54   return (ar != NULL) && (ar->v2d.flag & V2D_IS_INITIALISED);
55 }
56
57 /* ********************************************************* */
58 /* VIEW PANNING OPERATOR                                 */
59
60 /**
61  * This group of operators come in several forms:
62  * -# Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by
63  * -# Scrollwheel 'steps' - rolling mousewheel by one step moves view by predefined amount
64  *
65  * In order to make sure this works, each operator must define the following RNA-Operator Props:
66  * - `deltax, deltay` - define how much to move view by (relative to zoom-correction factor)
67  */
68
69 /* ------------------ Shared 'core' stuff ---------------------- */
70
71 /* temp customdata for operator */
72 typedef struct v2dViewPanData {
73   /** screen where view pan was initiated */
74   bScreen *sc;
75   /** area where view pan was initiated */
76   ScrArea *sa;
77   /** region where view pan was initiated */
78   ARegion *ar;
79   /** view2d we're operating in */
80   View2D *v2d;
81
82   /** amount to move view relative to zoom */
83   float facx, facy;
84
85   /* options for version 1 */
86   /** mouse x/y values in window when operator was initiated */
87   int startx, starty;
88   /** previous x/y values of mouse in window */
89   int lastx, lasty;
90   /** event starting pan, for modal exit */
91   int invoke_event;
92
93   /** for MMB in scrollers (old feature in past, but now not that useful) */
94   short in_scroller;
95 } v2dViewPanData;
96
97 /* initialize panning customdata */
98 static int view_pan_init(bContext *C, wmOperator *op)
99 {
100   ARegion *ar = CTX_wm_region(C);
101   v2dViewPanData *vpd;
102   View2D *v2d;
103   float winx, winy;
104
105   /* regions now have v2d-data by default, so check for region */
106   if (ar == NULL) {
107     return 0;
108   }
109
110   /* check if panning is allowed at all */
111   v2d = &ar->v2d;
112   if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y)) {
113     return 0;
114   }
115
116   /* set custom-data for operator */
117   vpd = MEM_callocN(sizeof(v2dViewPanData), "v2dViewPanData");
118   op->customdata = vpd;
119
120   /* set pointers to owners */
121   vpd->sc = CTX_wm_screen(C);
122   vpd->sa = CTX_wm_area(C);
123   vpd->v2d = v2d;
124   vpd->ar = ar;
125
126   /* calculate translation factor - based on size of view */
127   winx = (float)(BLI_rcti_size_x(&ar->winrct) + 1);
128   winy = (float)(BLI_rcti_size_y(&ar->winrct) + 1);
129   vpd->facx = (BLI_rctf_size_x(&v2d->cur)) / winx;
130   vpd->facy = (BLI_rctf_size_y(&v2d->cur)) / winy;
131
132   return 1;
133 }
134
135 #ifdef WITH_INPUT_NDOF
136 static bool view_pan_poll(bContext *C)
137 {
138   ARegion *ar = CTX_wm_region(C);
139   View2D *v2d;
140
141   /* check if there's a region in context to work with */
142   if (ar == NULL) {
143     return 0;
144   }
145   v2d = &ar->v2d;
146
147   /* check that 2d-view can pan */
148   if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y)) {
149     return 0;
150   }
151
152   /* view can pan */
153   return 1;
154 }
155 #endif
156
157 /* apply transform to view (i.e. adjust 'cur' rect) */
158 static void view_pan_apply_ex(bContext *C, v2dViewPanData *vpd, float dx, float dy)
159 {
160   View2D *v2d = vpd->v2d;
161
162   /* calculate amount to move view by */
163   dx *= vpd->facx;
164   dy *= vpd->facy;
165
166   /* only move view on an axis if change is allowed */
167   if ((v2d->keepofs & V2D_LOCKOFS_X) == 0) {
168     v2d->cur.xmin += dx;
169     v2d->cur.xmax += dx;
170   }
171   if ((v2d->keepofs & V2D_LOCKOFS_Y) == 0) {
172     v2d->cur.ymin += dy;
173     v2d->cur.ymax += dy;
174   }
175
176   /* validate that view is in valid configuration after this operation */
177   UI_view2d_curRect_validate(v2d);
178
179   /* don't rebuild full tree in outliner, since we're just changing our view */
180   ED_region_tag_redraw_no_rebuild(vpd->ar);
181
182   /* request updates to be done... */
183   WM_event_add_mousemove(C);
184
185   UI_view2d_sync(vpd->sc, vpd->sa, v2d, V2D_LOCK_COPY);
186 }
187
188 static void view_pan_apply(bContext *C, wmOperator *op)
189 {
190   v2dViewPanData *vpd = op->customdata;
191
192   view_pan_apply_ex(C, vpd, RNA_int_get(op->ptr, "deltax"), RNA_int_get(op->ptr, "deltay"));
193 }
194
195 /* cleanup temp customdata  */
196 static void view_pan_exit(wmOperator *op)
197 {
198   if (op->customdata) {
199     MEM_freeN(op->customdata);
200     op->customdata = NULL;
201   }
202 }
203
204 /* ------------------ Modal Drag Version (1) ---------------------- */
205
206 /* for 'redo' only, with no user input */
207 static int view_pan_exec(bContext *C, wmOperator *op)
208 {
209   if (!view_pan_init(C, op)) {
210     return OPERATOR_CANCELLED;
211   }
212
213   view_pan_apply(C, op);
214   view_pan_exit(op);
215   return OPERATOR_FINISHED;
216 }
217
218 /* set up modal operator and relevant settings */
219 static int view_pan_invoke(bContext *C, wmOperator *op, const wmEvent *event)
220 {
221   wmWindow *window = CTX_wm_window(C);
222   v2dViewPanData *vpd;
223   View2D *v2d;
224
225   /* set up customdata */
226   if (!view_pan_init(C, op)) {
227     return OPERATOR_PASS_THROUGH;
228   }
229
230   vpd = op->customdata;
231   v2d = vpd->v2d;
232
233   /* set initial settings */
234   vpd->startx = vpd->lastx = event->x;
235   vpd->starty = vpd->lasty = event->y;
236   vpd->invoke_event = event->type;
237
238   if (event->type == MOUSEPAN) {
239     RNA_int_set(op->ptr, "deltax", event->prevx - event->x);
240     RNA_int_set(op->ptr, "deltay", event->prevy - event->y);
241
242     view_pan_apply(C, op);
243     view_pan_exit(op);
244     return OPERATOR_FINISHED;
245   }
246
247   RNA_int_set(op->ptr, "deltax", 0);
248   RNA_int_set(op->ptr, "deltay", 0);
249
250   if (v2d->keepofs & V2D_LOCKOFS_X) {
251     WM_cursor_modal_set(window, BC_NS_SCROLLCURSOR);
252   }
253   else if (v2d->keepofs & V2D_LOCKOFS_Y) {
254     WM_cursor_modal_set(window, BC_EW_SCROLLCURSOR);
255   }
256   else {
257     WM_cursor_modal_set(window, BC_NSEW_SCROLLCURSOR);
258   }
259
260   /* add temp handler */
261   WM_event_add_modal_handler(C, op);
262
263   return OPERATOR_RUNNING_MODAL;
264 }
265
266 /* handle user input - calculations of mouse-movement
267  * need to be done here, not in the apply callback! */
268 static int view_pan_modal(bContext *C, wmOperator *op, const wmEvent *event)
269 {
270   v2dViewPanData *vpd = op->customdata;
271
272   /* execute the events */
273   switch (event->type) {
274     case MOUSEMOVE: {
275       /* calculate new delta transform, then store mouse-coordinates for next-time */
276       RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x));
277       RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y));
278
279       vpd->lastx = event->x;
280       vpd->lasty = event->y;
281
282       view_pan_apply(C, op);
283       break;
284     }
285     /* XXX - Mode switching isn't implemented. See comments in 36818.
286      * switch to zoom */
287 #if 0
288     case LEFTMOUSE:
289       if (event->val == KM_PRESS) {
290         /* calculate overall delta mouse-movement for redo */
291         RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
292         RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
293
294         view_pan_exit(op);
295         WM_cursor_modal_restore(CTX_wm_window(C));
296         WM_operator_name_call(C, "VIEW2D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
297         return OPERATOR_FINISHED;
298       }
299 #endif
300     default:
301       if (event->type == vpd->invoke_event || event->type == ESCKEY) {
302         if (event->val == KM_RELEASE) {
303           /* calculate overall delta mouse-movement for redo */
304           RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
305           RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
306
307           view_pan_exit(op);
308           WM_cursor_modal_restore(CTX_wm_window(C));
309
310           return OPERATOR_FINISHED;
311         }
312       }
313       break;
314   }
315
316   return OPERATOR_RUNNING_MODAL;
317 }
318
319 static void view_pan_cancel(bContext *UNUSED(C), wmOperator *op)
320 {
321   view_pan_exit(op);
322 }
323
324 static void VIEW2D_OT_pan(wmOperatorType *ot)
325 {
326   /* identifiers */
327   ot->name = "Pan View";
328   ot->description = "Pan the view";
329   ot->idname = "VIEW2D_OT_pan";
330
331   /* api callbacks */
332   ot->exec = view_pan_exec;
333   ot->invoke = view_pan_invoke;
334   ot->modal = view_pan_modal;
335   ot->cancel = view_pan_cancel;
336
337   /* operator is modal */
338   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
339
340   /* rna - must keep these in sync with the other operators */
341   RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
342   RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
343 }
344
345 /* ------------------ Scrollwheel Versions (2) ---------------------- */
346
347 /* this operator only needs this single callback, where it calls the view_pan_*() methods */
348 static int view_scrollright_exec(bContext *C, wmOperator *op)
349 {
350   v2dViewPanData *vpd;
351
352   /* initialize default settings (and validate if ok to run) */
353   if (!view_pan_init(C, op)) {
354     return OPERATOR_PASS_THROUGH;
355   }
356
357   /* also, check if can pan in horizontal axis */
358   vpd = op->customdata;
359   if (vpd->v2d->keepofs & V2D_LOCKOFS_X) {
360     view_pan_exit(op);
361     return OPERATOR_PASS_THROUGH;
362   }
363
364   /* set RNA-Props - only movement in positive x-direction */
365   RNA_int_set(op->ptr, "deltax", 40);
366   RNA_int_set(op->ptr, "deltay", 0);
367
368   /* apply movement, then we're done */
369   view_pan_apply(C, op);
370   view_pan_exit(op);
371
372   return OPERATOR_FINISHED;
373 }
374
375 static void VIEW2D_OT_scroll_right(wmOperatorType *ot)
376 {
377   /* identifiers */
378   ot->name = "Scroll Right";
379   ot->description = "Scroll the view right";
380   ot->idname = "VIEW2D_OT_scroll_right";
381
382   /* api callbacks */
383   ot->exec = view_scrollright_exec;
384
385   /* rna - must keep these in sync with the other operators */
386   RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
387   RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
388 }
389
390 /* this operator only needs this single callback, where it calls the view_pan_*() methods */
391 static int view_scrollleft_exec(bContext *C, wmOperator *op)
392 {
393   v2dViewPanData *vpd;
394
395   /* initialize default settings (and validate if ok to run) */
396   if (!view_pan_init(C, op)) {
397     return OPERATOR_PASS_THROUGH;
398   }
399
400   /* also, check if can pan in horizontal axis */
401   vpd = op->customdata;
402   if (vpd->v2d->keepofs & V2D_LOCKOFS_X) {
403     view_pan_exit(op);
404     return OPERATOR_PASS_THROUGH;
405   }
406
407   /* set RNA-Props - only movement in negative x-direction */
408   RNA_int_set(op->ptr, "deltax", -40);
409   RNA_int_set(op->ptr, "deltay", 0);
410
411   /* apply movement, then we're done */
412   view_pan_apply(C, op);
413   view_pan_exit(op);
414
415   return OPERATOR_FINISHED;
416 }
417
418 static void VIEW2D_OT_scroll_left(wmOperatorType *ot)
419 {
420   /* identifiers */
421   ot->name = "Scroll Left";
422   ot->description = "Scroll the view left";
423   ot->idname = "VIEW2D_OT_scroll_left";
424
425   /* api callbacks */
426   ot->exec = view_scrollleft_exec;
427
428   /* rna - must keep these in sync with the other operators */
429   RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
430   RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
431 }
432
433 /* this operator only needs this single callback, where it calls the view_pan_*() methods */
434 static int view_scrolldown_exec(bContext *C, wmOperator *op)
435 {
436   v2dViewPanData *vpd;
437
438   /* initialize default settings (and validate if ok to run) */
439   if (!view_pan_init(C, op)) {
440     return OPERATOR_PASS_THROUGH;
441   }
442
443   /* also, check if can pan in vertical axis */
444   vpd = op->customdata;
445   if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) {
446     view_pan_exit(op);
447     return OPERATOR_PASS_THROUGH;
448   }
449
450   /* set RNA-Props */
451   RNA_int_set(op->ptr, "deltax", 0);
452   RNA_int_set(op->ptr, "deltay", -40);
453
454   PropertyRNA *prop = RNA_struct_find_property(op->ptr, "page");
455   if (RNA_property_is_set(op->ptr, prop) && RNA_property_boolean_get(op->ptr, prop)) {
456     ARegion *ar = CTX_wm_region(C);
457     RNA_int_set(op->ptr, "deltay", ar->v2d.mask.ymin - ar->v2d.mask.ymax);
458   }
459
460   /* apply movement, then we're done */
461   view_pan_apply(C, op);
462   view_pan_exit(op);
463
464   return OPERATOR_FINISHED;
465 }
466
467 static void VIEW2D_OT_scroll_down(wmOperatorType *ot)
468 {
469   /* identifiers */
470   ot->name = "Scroll Down";
471   ot->description = "Scroll the view down";
472   ot->idname = "VIEW2D_OT_scroll_down";
473
474   /* api callbacks */
475   ot->exec = view_scrolldown_exec;
476
477   /* rna - must keep these in sync with the other operators */
478   RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
479   RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
480   RNA_def_boolean(ot->srna, "page", 0, "Page", "Scroll down one page");
481 }
482
483 /* this operator only needs this single callback, where it calls the view_pan_*() methods */
484 static int view_scrollup_exec(bContext *C, wmOperator *op)
485 {
486   v2dViewPanData *vpd;
487
488   /* initialize default settings (and validate if ok to run) */
489   if (!view_pan_init(C, op)) {
490     return OPERATOR_PASS_THROUGH;
491   }
492
493   /* also, check if can pan in vertical axis */
494   vpd = op->customdata;
495   if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) {
496     view_pan_exit(op);
497     return OPERATOR_PASS_THROUGH;
498   }
499
500   /* set RNA-Props */
501   RNA_int_set(op->ptr, "deltax", 0);
502   RNA_int_set(op->ptr, "deltay", 40);
503
504   PropertyRNA *prop = RNA_struct_find_property(op->ptr, "page");
505   if (RNA_property_is_set(op->ptr, prop) && RNA_property_boolean_get(op->ptr, prop)) {
506     ARegion *ar = CTX_wm_region(C);
507     RNA_int_set(op->ptr, "deltay", BLI_rcti_size_y(&ar->v2d.mask));
508   }
509
510   /* apply movement, then we're done */
511   view_pan_apply(C, op);
512   view_pan_exit(op);
513
514   return OPERATOR_FINISHED;
515 }
516
517 static void VIEW2D_OT_scroll_up(wmOperatorType *ot)
518 {
519   /* identifiers */
520   ot->name = "Scroll Up";
521   ot->description = "Scroll the view up";
522   ot->idname = "VIEW2D_OT_scroll_up";
523
524   /* api callbacks */
525   ot->exec = view_scrollup_exec;
526
527   /* rna - must keep these in sync with the other operators */
528   RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
529   RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
530   RNA_def_boolean(ot->srna, "page", 0, "Page", "Scroll up one page");
531 }
532
533 /* ********************************************************* */
534 /* SINGLE-STEP VIEW ZOOMING OPERATOR                         */
535
536 /**
537  * This group of operators come in several forms:
538  * -# Scrollwheel 'steps' - rolling mousewheel by one step zooms view by predefined amount.
539  * -# Scrollwheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y).
540  *    XXX this could be implemented...
541  * -# Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount.
542  *
543  * In order to make sure this works, each operator must define the following RNA-Operator Props:
544  *
545  * - zoomfacx, zoomfacy - These two zoom factors allow for non-uniform scaling.
546  *   It is safe to scale by 0, as these factors are used to determine.
547  *   amount to enlarge 'cur' by.
548  */
549
550 /* ------------------ 'Shared' stuff ------------------------ */
551
552 /* temp customdata for operator */
553 typedef struct v2dViewZoomData {
554   View2D *v2d; /* view2d we're operating in */
555   ARegion *ar;
556
557   /* needed for continuous zoom */
558   wmTimer *timer;
559   double timer_lastdraw;
560
561   int lastx, lasty;   /* previous x/y values of mouse in window */
562   int invoke_event;   /* event type that invoked, for modal exits */
563   float dx, dy;       /* running tally of previous delta values (for obtaining final zoom) */
564   float mx_2d, my_2d; /* initial mouse location in v2d coords */
565 } v2dViewZoomData;
566
567 /**
568  * Clamp by convention rather then locking flags,
569  * for ndof and +/- keys
570  */
571 static void view_zoom_axis_lock_defaults(bContext *C, bool r_do_zoom_xy[2])
572 {
573   ScrArea *sa = CTX_wm_area(C);
574
575   r_do_zoom_xy[0] = true;
576   r_do_zoom_xy[1] = true;
577
578   /* default not to zoom the sequencer vertically */
579   if (sa && sa->spacetype == SPACE_SEQ) {
580     ARegion *ar = CTX_wm_region(C);
581
582     if (ar && ar->regiontype == RGN_TYPE_WINDOW) {
583       r_do_zoom_xy[1] = false;
584     }
585   }
586 }
587
588 /* initialize panning customdata */
589 static int view_zoomdrag_init(bContext *C, wmOperator *op)
590 {
591   ARegion *ar = CTX_wm_region(C);
592   v2dViewZoomData *vzd;
593   View2D *v2d;
594
595   /* regions now have v2d-data by default, so check for region */
596   if (ar == NULL) {
597     return 0;
598   }
599   v2d = &ar->v2d;
600
601   /* check that 2d-view is zoomable */
602   if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y)) {
603     return 0;
604   }
605
606   /* set custom-data for operator */
607   vzd = MEM_callocN(sizeof(v2dViewZoomData), "v2dViewZoomData");
608   op->customdata = vzd;
609
610   /* set pointers to owners */
611   vzd->v2d = v2d;
612   vzd->ar = ar;
613
614   return 1;
615 }
616
617 /* check if step-zoom can be applied */
618 static bool view_zoom_poll(bContext *C)
619 {
620   ARegion *ar = CTX_wm_region(C);
621   View2D *v2d;
622
623   /* check if there's a region in context to work with */
624   if (ar == NULL) {
625     return false;
626   }
627
628   /* Do not show that in 3DView context. */
629   if (CTX_wm_region_view3d(C)) {
630     return false;
631   }
632
633   v2d = &ar->v2d;
634
635   /* check that 2d-view is zoomable */
636   if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y)) {
637     return false;
638   }
639
640   /* view is zoomable */
641   return true;
642 }
643
644 /* apply transform to view (i.e. adjust 'cur' rect) */
645 static void view_zoomstep_apply_ex(
646     bContext *C, v2dViewZoomData *vzd, const bool use_mousepos, const float facx, const float facy)
647 {
648   ARegion *ar = CTX_wm_region(C);
649   View2D *v2d = &ar->v2d;
650   const rctf cur_old = v2d->cur;
651   float dx, dy;
652   const int snap_test = ED_region_snap_size_test(ar);
653
654   /* calculate amount to move view by, ensuring symmetry so the
655    * old zoom level is restored after zooming back the same amount
656    */
657   if (facx >= 0.0f) {
658     dx = BLI_rctf_size_x(&v2d->cur) * facx;
659     dy = BLI_rctf_size_y(&v2d->cur) * facy;
660   }
661   else {
662     dx = (BLI_rctf_size_x(&v2d->cur) / (1.0f + 2.0f * facx)) * facx;
663     dy = (BLI_rctf_size_y(&v2d->cur) / (1.0f + 2.0f * facy)) * facy;
664   }
665
666   /* only resize view on an axis if change is allowed */
667   if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
668     if (v2d->keepofs & V2D_LOCKOFS_X) {
669       v2d->cur.xmax -= 2 * dx;
670     }
671     else if (v2d->keepofs & V2D_KEEPOFS_X) {
672       if (v2d->align & V2D_ALIGN_NO_POS_X) {
673         v2d->cur.xmin += 2 * dx;
674       }
675       else {
676         v2d->cur.xmax -= 2 * dx;
677       }
678     }
679     else {
680
681       v2d->cur.xmin += dx;
682       v2d->cur.xmax -= dx;
683
684       if (use_mousepos && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
685         /* get zoom fac the same way as in
686          * ui_view2d_curRect_validate_resize - better keep in sync! */
687         const float zoomx = (float)(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur);
688
689         /* only move view to mouse if zoom fac is inside minzoom/maxzoom */
690         if (((v2d->keepzoom & V2D_LIMITZOOM) == 0) ||
691             IN_RANGE_INCL(zoomx, v2d->minzoom, v2d->maxzoom)) {
692           float mval_fac = (vzd->mx_2d - cur_old.xmin) / BLI_rctf_size_x(&cur_old);
693           float mval_faci = 1.0f - mval_fac;
694           float ofs = (mval_fac * dx) - (mval_faci * dx);
695
696           v2d->cur.xmin += ofs;
697           v2d->cur.xmax += ofs;
698         }
699       }
700     }
701   }
702   if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
703     if (v2d->keepofs & V2D_LOCKOFS_Y) {
704       v2d->cur.ymax -= 2 * dy;
705     }
706     else if (v2d->keepofs & V2D_KEEPOFS_Y) {
707       if (v2d->align & V2D_ALIGN_NO_POS_Y) {
708         v2d->cur.ymin += 2 * dy;
709       }
710       else {
711         v2d->cur.ymax -= 2 * dy;
712       }
713     }
714     else {
715
716       v2d->cur.ymin += dy;
717       v2d->cur.ymax -= dy;
718
719       if (use_mousepos && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
720         /* get zoom fac the same way as in
721          * ui_view2d_curRect_validate_resize - better keep in sync! */
722         const float zoomy = (float)(BLI_rcti_size_y(&v2d->mask) + 1) / BLI_rctf_size_y(&v2d->cur);
723
724         /* only move view to mouse if zoom fac is inside minzoom/maxzoom */
725         if (((v2d->keepzoom & V2D_LIMITZOOM) == 0) ||
726             IN_RANGE_INCL(zoomy, v2d->minzoom, v2d->maxzoom)) {
727           float mval_fac = (vzd->my_2d - cur_old.ymin) / BLI_rctf_size_y(&cur_old);
728           float mval_faci = 1.0f - mval_fac;
729           float ofs = (mval_fac * dy) - (mval_faci * dy);
730
731           v2d->cur.ymin += ofs;
732           v2d->cur.ymax += ofs;
733         }
734       }
735     }
736   }
737
738   /* validate that view is in valid configuration after this operation */
739   UI_view2d_curRect_validate(v2d);
740
741   if (ED_region_snap_size_apply(ar, snap_test)) {
742     ScrArea *sa = CTX_wm_area(C);
743     ED_area_tag_redraw(sa);
744     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
745   }
746
747   /* request updates to be done... */
748   ED_region_tag_redraw_no_rebuild(vzd->ar);
749   UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
750 }
751
752 static void view_zoomstep_apply(bContext *C, wmOperator *op)
753 {
754   v2dViewZoomData *vzd = op->customdata;
755   view_zoomstep_apply_ex(
756       C, vzd, true, RNA_float_get(op->ptr, "zoomfacx"), RNA_float_get(op->ptr, "zoomfacy"));
757 }
758
759 /* --------------- Individual Operators ------------------- */
760
761 /* cleanup temp customdata  */
762 static void view_zoomstep_exit(wmOperator *op)
763 {
764   UI_view2d_zoom_cache_reset();
765
766   if (op->customdata) {
767     MEM_freeN(op->customdata);
768     op->customdata = NULL;
769   }
770 }
771
772 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
773 static int view_zoomin_exec(bContext *C, wmOperator *op)
774 {
775   bool do_zoom_xy[2];
776
777   /* check that there's an active region, as View2D data resides there */
778   if (!view_zoom_poll(C)) {
779     return OPERATOR_PASS_THROUGH;
780   }
781
782   view_zoom_axis_lock_defaults(C, do_zoom_xy);
783
784   /* set RNA-Props - zooming in by uniform factor */
785   RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? 0.0375f : 0.0f);
786   RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? 0.0375f : 0.0f);
787
788   /* apply movement, then we're done */
789   view_zoomstep_apply(C, op);
790
791   view_zoomstep_exit(op);
792
793   return OPERATOR_FINISHED;
794 }
795
796 static int view_zoomin_invoke(bContext *C, wmOperator *op, const wmEvent *event)
797 {
798   v2dViewZoomData *vzd;
799
800   if (!view_zoomdrag_init(C, op)) {
801     return OPERATOR_PASS_THROUGH;
802   }
803
804   vzd = op->customdata;
805
806   if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
807     ARegion *ar = CTX_wm_region(C);
808
809     /* store initial mouse position (in view space) */
810     UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d);
811   }
812
813   return view_zoomin_exec(C, op);
814 }
815
816 static void VIEW2D_OT_zoom_in(wmOperatorType *ot)
817 {
818   PropertyRNA *prop;
819
820   /* identifiers */
821   ot->name = "Zoom In";
822   ot->description = "Zoom in the view";
823   ot->idname = "VIEW2D_OT_zoom_in";
824
825   /* api callbacks */
826   ot->invoke = view_zoomin_invoke;
827   ot->exec = view_zoomin_exec;  // XXX, needs view_zoomdrag_init called first.
828   ot->poll = view_zoom_poll;
829
830   /* rna - must keep these in sync with the other operators */
831   prop = RNA_def_float(
832       ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
833   RNA_def_property_flag(prop, PROP_HIDDEN);
834   prop = RNA_def_float(
835       ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
836   RNA_def_property_flag(prop, PROP_HIDDEN);
837 }
838
839 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
840 static int view_zoomout_exec(bContext *C, wmOperator *op)
841 {
842   bool do_zoom_xy[2];
843
844   /* check that there's an active region, as View2D data resides there */
845   if (!view_zoom_poll(C)) {
846     return OPERATOR_PASS_THROUGH;
847   }
848
849   view_zoom_axis_lock_defaults(C, do_zoom_xy);
850
851   /* set RNA-Props - zooming in by uniform factor */
852   RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? -0.0375f : 0.0f);
853   RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? -0.0375f : 0.0f);
854
855   /* apply movement, then we're done */
856   view_zoomstep_apply(C, op);
857
858   view_zoomstep_exit(op);
859
860   return OPERATOR_FINISHED;
861 }
862
863 static int view_zoomout_invoke(bContext *C, wmOperator *op, const wmEvent *event)
864 {
865   v2dViewZoomData *vzd;
866
867   if (!view_zoomdrag_init(C, op)) {
868     return OPERATOR_PASS_THROUGH;
869   }
870
871   vzd = op->customdata;
872
873   if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
874     ARegion *ar = CTX_wm_region(C);
875
876     /* store initial mouse position (in view space) */
877     UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d);
878   }
879
880   return view_zoomout_exec(C, op);
881 }
882
883 static void VIEW2D_OT_zoom_out(wmOperatorType *ot)
884 {
885   PropertyRNA *prop;
886
887   /* identifiers */
888   ot->name = "Zoom Out";
889   ot->description = "Zoom out the view";
890   ot->idname = "VIEW2D_OT_zoom_out";
891
892   /* api callbacks */
893   ot->invoke = view_zoomout_invoke;
894   //  ot->exec = view_zoomout_exec; // XXX, needs view_zoomdrag_init called first.
895   ot->poll = view_zoom_poll;
896
897   /* rna - must keep these in sync with the other operators */
898   prop = RNA_def_float(
899       ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
900   RNA_def_property_flag(prop, PROP_HIDDEN);
901   prop = RNA_def_float(
902       ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
903   RNA_def_property_flag(prop, PROP_HIDDEN);
904 }
905
906 /* ********************************************************* */
907 /* DRAG-ZOOM OPERATOR                                    */
908
909 /**
910  * MMB Drag - allows non-uniform scaling by dragging mouse
911  *
912  * In order to make sure this works, each operator must define the following RNA-Operator Props:
913  * - `deltax, deltay` - amounts to add to each side of the 'cur' rect
914  */
915
916 /* apply transform to view (i.e. adjust 'cur' rect) */
917 static void view_zoomdrag_apply(bContext *C, wmOperator *op)
918 {
919   v2dViewZoomData *vzd = op->customdata;
920   View2D *v2d = vzd->v2d;
921   float dx, dy;
922   const int snap_test = ED_region_snap_size_test(vzd->ar);
923
924   /* get amount to move view by */
925   dx = RNA_float_get(op->ptr, "deltax");
926   dy = RNA_float_get(op->ptr, "deltay");
927
928   if (U.uiflag & USER_ZOOM_INVERT) {
929     dx *= -1;
930     dy *= -1;
931   }
932
933   /* continuous zoom shouldn't move that fast... */
934   if (U.viewzoom == USER_ZOOM_CONT) {  // XXX store this setting as RNA prop?
935     double time = PIL_check_seconds_timer();
936     float time_step = (float)(time - vzd->timer_lastdraw);
937
938     dx *= time_step * 0.5f;
939     dy *= time_step * 0.5f;
940
941     vzd->timer_lastdraw = time;
942   }
943
944   /* only move view on an axis if change is allowed */
945   if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
946     if (v2d->keepofs & V2D_LOCKOFS_X) {
947       v2d->cur.xmax -= 2 * dx;
948     }
949     else {
950       if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
951         float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
952         float mval_faci = 1.0f - mval_fac;
953         float ofs = (mval_fac * dx) - (mval_faci * dx);
954
955         v2d->cur.xmin += ofs + dx;
956         v2d->cur.xmax += ofs - dx;
957       }
958       else {
959         v2d->cur.xmin += dx;
960         v2d->cur.xmax -= dx;
961       }
962     }
963   }
964   if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
965     if (v2d->keepofs & V2D_LOCKOFS_Y) {
966       v2d->cur.ymax -= 2 * dy;
967     }
968     else {
969       if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
970         float mval_fac = (vzd->my_2d - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
971         float mval_faci = 1.0f - mval_fac;
972         float ofs = (mval_fac * dy) - (mval_faci * dy);
973
974         v2d->cur.ymin += ofs + dy;
975         v2d->cur.ymax += ofs - dy;
976       }
977       else {
978         v2d->cur.ymin += dy;
979         v2d->cur.ymax -= dy;
980       }
981     }
982   }
983
984   /* validate that view is in valid configuration after this operation */
985   UI_view2d_curRect_validate(v2d);
986
987   if (ED_region_snap_size_apply(vzd->ar, snap_test)) {
988     ScrArea *sa = CTX_wm_area(C);
989     ED_area_tag_redraw(sa);
990     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
991   }
992
993   /* request updates to be done... */
994   ED_region_tag_redraw_no_rebuild(vzd->ar);
995   UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
996 }
997
998 /* cleanup temp customdata  */
999 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
1000 {
1001   UI_view2d_zoom_cache_reset();
1002
1003   if (op->customdata) {
1004     v2dViewZoomData *vzd = op->customdata;
1005
1006     if (vzd->timer) {
1007       WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer);
1008     }
1009
1010     MEM_freeN(op->customdata);
1011     op->customdata = NULL;
1012   }
1013 }
1014
1015 static void view_zoomdrag_cancel(bContext *C, wmOperator *op)
1016 {
1017   view_zoomdrag_exit(C, op);
1018 }
1019
1020 /* for 'redo' only, with no user input */
1021 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
1022 {
1023   if (!view_zoomdrag_init(C, op)) {
1024     return OPERATOR_PASS_THROUGH;
1025   }
1026
1027   view_zoomdrag_apply(C, op);
1028   view_zoomdrag_exit(C, op);
1029   return OPERATOR_FINISHED;
1030 }
1031
1032 /* set up modal operator and relevant settings */
1033 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1034 {
1035   wmWindow *window = CTX_wm_window(C);
1036   v2dViewZoomData *vzd;
1037   View2D *v2d;
1038
1039   /* set up customdata */
1040   if (!view_zoomdrag_init(C, op)) {
1041     return OPERATOR_PASS_THROUGH;
1042   }
1043
1044   vzd = op->customdata;
1045   v2d = vzd->v2d;
1046
1047   if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
1048     float dx, dy, fac;
1049
1050     vzd->lastx = event->prevx;
1051     vzd->lasty = event->prevy;
1052
1053     /* As we have only 1D information (magnify value), feed both axes
1054      * with magnify information that is stored in x axis
1055      */
1056     fac = 0.01f * (event->prevx - event->x);
1057     dx = fac * BLI_rctf_size_x(&v2d->cur) / 10.0f;
1058     if (event->type == MOUSEPAN) {
1059       fac = 0.01f * (event->prevy - event->y);
1060     }
1061     dy = fac * BLI_rctf_size_y(&v2d->cur) / 10.0f;
1062
1063     /* support trackpad zoom to always zoom entirely - the v2d code uses portrait or
1064      * landscape exceptions */
1065     if (v2d->keepzoom & V2D_KEEPASPECT) {
1066       if (fabsf(dx) > fabsf(dy)) {
1067         dy = dx;
1068       }
1069       else {
1070         dx = dy;
1071       }
1072     }
1073     RNA_float_set(op->ptr, "deltax", dx);
1074     RNA_float_set(op->ptr, "deltay", dy);
1075
1076     view_zoomdrag_apply(C, op);
1077     view_zoomdrag_exit(C, op);
1078     return OPERATOR_FINISHED;
1079   }
1080
1081   /* set initial settings */
1082   vzd->lastx = event->x;
1083   vzd->lasty = event->y;
1084   RNA_float_set(op->ptr, "deltax", 0);
1085   RNA_float_set(op->ptr, "deltay", 0);
1086
1087   /* for modal exit test */
1088   vzd->invoke_event = event->type;
1089
1090   if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
1091     ARegion *ar = CTX_wm_region(C);
1092
1093     /* store initial mouse position (in view space) */
1094     UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d);
1095   }
1096
1097   if (v2d->keepofs & V2D_LOCKOFS_X) {
1098     WM_cursor_modal_set(window, BC_NS_SCROLLCURSOR);
1099   }
1100   else if (v2d->keepofs & V2D_LOCKOFS_Y) {
1101     WM_cursor_modal_set(window, BC_EW_SCROLLCURSOR);
1102   }
1103   else {
1104     WM_cursor_modal_set(window, BC_NSEW_SCROLLCURSOR);
1105   }
1106
1107   /* add temp handler */
1108   WM_event_add_modal_handler(C, op);
1109
1110   if (U.viewzoom == USER_ZOOM_CONT) {
1111     /* needs a timer to continue redrawing */
1112     vzd->timer = WM_event_add_timer(CTX_wm_manager(C), window, TIMER, 0.01f);
1113     vzd->timer_lastdraw = PIL_check_seconds_timer();
1114   }
1115
1116   return OPERATOR_RUNNING_MODAL;
1117 }
1118
1119 /* handle user input - calculations of mouse-movement need to be done here,
1120  * not in the apply callback! */
1121 static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event)
1122 {
1123   v2dViewZoomData *vzd = op->customdata;
1124   View2D *v2d = vzd->v2d;
1125
1126   /* execute the events */
1127   if (event->type == TIMER && event->customdata == vzd->timer) {
1128     view_zoomdrag_apply(C, op);
1129   }
1130   else if (event->type == MOUSEMOVE) {
1131     float dx, dy;
1132
1133     /* calculate new delta transform, based on zooming mode */
1134     if (U.viewzoom == USER_ZOOM_SCALE) {
1135       /* 'scale' zooming */
1136       float dist;
1137
1138       /* x-axis transform */
1139       dist = BLI_rcti_size_x(&v2d->mask) / 2.0f;
1140       dx = 1.0f - (fabsf(vzd->lastx - vzd->ar->winrct.xmin - dist) + 2.0f) /
1141                       (fabsf(event->mval[0] - dist) + 2.0f);
1142       dx *= 0.5f * BLI_rctf_size_x(&v2d->cur);
1143
1144       /* y-axis transform */
1145       dist = BLI_rcti_size_y(&v2d->mask) / 2.0f;
1146       dy = 1.0f - (fabsf(vzd->lasty - vzd->ar->winrct.ymin - dist) + 2.0f) /
1147                       (fabsf(event->mval[1] - dist) + 2.0f);
1148       dy *= 0.5f * BLI_rctf_size_y(&v2d->cur);
1149     }
1150     else {
1151       /* 'continuous' or 'dolly' */
1152       float fac, zoomfac = 0.01f;
1153
1154       /* some view2d's (graph) don't have min/max zoom, or extreme ones */
1155       if (v2d->maxzoom > 0.0f) {
1156         zoomfac = clamp_f(0.001f * v2d->maxzoom, 0.001f, 0.01f);
1157       }
1158
1159       /* x-axis transform */
1160       fac = zoomfac * (event->x - vzd->lastx);
1161       dx = fac * BLI_rctf_size_x(&v2d->cur);
1162
1163       /* y-axis transform */
1164       fac = zoomfac * (event->y - vzd->lasty);
1165       dy = fac * BLI_rctf_size_y(&v2d->cur);
1166     }
1167
1168     /* support zoom to always zoom entirely - the v2d code uses portrait or
1169      * landscape exceptions */
1170     if (v2d->keepzoom & V2D_KEEPASPECT) {
1171       if (fabsf(dx) > fabsf(dy)) {
1172         dy = dx;
1173       }
1174       else {
1175         dx = dy;
1176       }
1177     }
1178
1179     /* set transform amount, and add current deltas to stored total delta (for redo) */
1180     RNA_float_set(op->ptr, "deltax", dx);
1181     RNA_float_set(op->ptr, "deltay", dy);
1182
1183     vzd->dx += dx;
1184     vzd->dy += dy;
1185
1186     /* Store mouse coordinates for next time, if not doing continuous zoom:
1187      * - Continuous zoom only depends on distance of mouse
1188      *   to starting point to determine rate of change.
1189      */
1190     if (U.viewzoom != USER_ZOOM_CONT) {  // XXX store this setting as RNA prop?
1191       vzd->lastx = event->x;
1192       vzd->lasty = event->y;
1193     }
1194
1195     /* apply zooming */
1196     view_zoomdrag_apply(C, op);
1197   }
1198   else if (event->type == vzd->invoke_event || event->type == ESCKEY) {
1199     if (event->val == KM_RELEASE) {
1200
1201       /* for redo, store the overall deltas - need to respect zoom-locks here... */
1202       if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1203         RNA_float_set(op->ptr, "deltax", vzd->dx);
1204       }
1205       else {
1206         RNA_float_set(op->ptr, "deltax", 0);
1207       }
1208
1209       if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1210         RNA_float_set(op->ptr, "deltay", vzd->dy);
1211       }
1212       else {
1213         RNA_float_set(op->ptr, "deltay", 0);
1214       }
1215
1216       /* free customdata */
1217       view_zoomdrag_exit(C, op);
1218       WM_cursor_modal_restore(CTX_wm_window(C));
1219
1220       return OPERATOR_FINISHED;
1221     }
1222   }
1223
1224   return OPERATOR_RUNNING_MODAL;
1225 }
1226
1227 static void VIEW2D_OT_zoom(wmOperatorType *ot)
1228 {
1229   PropertyRNA *prop;
1230   /* identifiers */
1231   ot->name = "Zoom 2D View";
1232   ot->description = "Zoom in/out the view";
1233   ot->idname = "VIEW2D_OT_zoom";
1234
1235   /* api callbacks */
1236   ot->exec = view_zoomdrag_exec;
1237   ot->invoke = view_zoomdrag_invoke;
1238   ot->modal = view_zoomdrag_modal;
1239   ot->cancel = view_zoomdrag_cancel;
1240
1241   ot->poll = view_zoom_poll;
1242
1243   /* operator is repeatable */
1244   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
1245
1246   /* rna - must keep these in sync with the other operators */
1247   prop = RNA_def_float(ot->srna, "deltax", 0, -FLT_MAX, FLT_MAX, "Delta X", "", -FLT_MAX, FLT_MAX);
1248   RNA_def_property_flag(prop, PROP_HIDDEN);
1249   prop = RNA_def_float(ot->srna, "deltay", 0, -FLT_MAX, FLT_MAX, "Delta Y", "", -FLT_MAX, FLT_MAX);
1250   RNA_def_property_flag(prop, PROP_HIDDEN);
1251 }
1252
1253 /* ********************************************************* */
1254 /* BORDER-ZOOM */
1255
1256 /**
1257  * The user defines a rect using standard box select tools, and we use this rect to
1258  * define the new zoom-level of the view in the following ways:
1259  *
1260  * -# LEFTMOUSE - zoom in to view
1261  * -# RIGHTMOUSE - zoom out of view
1262  *
1263  * Currently, these key mappings are hardcoded, but it shouldn't be too important to
1264  * have custom keymappings for this...
1265  */
1266
1267 static int view_borderzoom_exec(bContext *C, wmOperator *op)
1268 {
1269   ARegion *ar = CTX_wm_region(C);
1270   View2D *v2d = &ar->v2d;
1271   rctf rect;
1272   rctf cur_new = v2d->cur;
1273   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
1274
1275   /* convert coordinates of rect to 'tot' rect coordinates */
1276   WM_operator_properties_border_to_rctf(op, &rect);
1277   UI_view2d_region_to_view_rctf(v2d, &rect, &rect);
1278
1279   /* check if zooming in/out view */
1280   const bool zoom_in = !RNA_boolean_get(op->ptr, "zoom_out");
1281
1282   if (zoom_in) {
1283     /* zoom in:
1284      * - 'cur' rect will be defined by the coordinates of the border region
1285      * - just set the 'cur' rect to have the same coordinates as the border region
1286      *   if zoom is allowed to be changed
1287      */
1288     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1289       cur_new.xmin = rect.xmin;
1290       cur_new.xmax = rect.xmax;
1291     }
1292     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1293       cur_new.ymin = rect.ymin;
1294       cur_new.ymax = rect.ymax;
1295     }
1296   }
1297   else {
1298     /* zoom out:
1299      * - the current 'cur' rect coordinates are going to end up where the 'rect' ones are,
1300      *   but the 'cur' rect coordinates will need to be adjusted to take in more of the view
1301      * - calculate zoom factor, and adjust using center-point
1302      */
1303     float zoom, center, size;
1304
1305     /* TODO: is this zoom factor calculation valid?
1306      * It seems to produce same results every time... */
1307     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1308       size = BLI_rctf_size_x(&cur_new);
1309       zoom = size / BLI_rctf_size_x(&rect);
1310       center = BLI_rctf_cent_x(&cur_new);
1311
1312       cur_new.xmin = center - (size * zoom);
1313       cur_new.xmax = center + (size * zoom);
1314     }
1315     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1316       size = BLI_rctf_size_y(&cur_new);
1317       zoom = size / BLI_rctf_size_y(&rect);
1318       center = BLI_rctf_cent_y(&cur_new);
1319
1320       cur_new.ymin = center - (size * zoom);
1321       cur_new.ymax = center + (size * zoom);
1322     }
1323   }
1324
1325   UI_view2d_smooth_view(C, ar, &cur_new, smooth_viewtx);
1326
1327   return OPERATOR_FINISHED;
1328 }
1329
1330 static void VIEW2D_OT_zoom_border(wmOperatorType *ot)
1331 {
1332   /* identifiers */
1333   ot->name = "Zoom to Border";
1334   ot->description = "Zoom in the view to the nearest item contained in the border";
1335   ot->idname = "VIEW2D_OT_zoom_border";
1336
1337   /* api callbacks */
1338   ot->invoke = WM_gesture_box_invoke;
1339   ot->exec = view_borderzoom_exec;
1340   ot->modal = WM_gesture_box_modal;
1341   ot->cancel = WM_gesture_box_cancel;
1342
1343   ot->poll = view_zoom_poll;
1344
1345   /* rna */
1346   WM_operator_properties_gesture_box_zoom(ot);
1347 }
1348
1349 #ifdef WITH_INPUT_NDOF
1350 static int view2d_ndof_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1351 {
1352   if (event->type != NDOF_MOTION) {
1353     return OPERATOR_CANCELLED;
1354   }
1355   else {
1356     const wmNDOFMotionData *ndof = event->customdata;
1357
1358     /* tune these until it feels right */
1359     const float zoom_sensitivity = 0.5f;
1360     const float speed = 10.0f; /* match view3d ortho */
1361     const bool has_translate = (ndof->tvec[0] && ndof->tvec[1]) && view_pan_poll(C);
1362     const bool has_zoom = (ndof->tvec[2] != 0.0f) && view_zoom_poll(C);
1363
1364     if (has_translate) {
1365       if (view_pan_init(C, op)) {
1366         v2dViewPanData *vpd;
1367         float pan_vec[3];
1368
1369         WM_event_ndof_pan_get(ndof, pan_vec, false);
1370
1371         pan_vec[0] *= speed;
1372         pan_vec[1] *= speed;
1373
1374         vpd = op->customdata;
1375
1376         view_pan_apply_ex(C, vpd, pan_vec[0], pan_vec[1]);
1377
1378         view_pan_exit(op);
1379       }
1380     }
1381
1382     if (has_zoom) {
1383       if (view_zoomdrag_init(C, op)) {
1384         v2dViewZoomData *vzd;
1385         float zoom_factor = zoom_sensitivity * ndof->dt * -ndof->tvec[2];
1386
1387         bool do_zoom_xy[2];
1388
1389         if (U.ndof_flag & NDOF_ZOOM_INVERT) {
1390           zoom_factor = -zoom_factor;
1391         }
1392
1393         view_zoom_axis_lock_defaults(C, do_zoom_xy);
1394
1395         vzd = op->customdata;
1396
1397         view_zoomstep_apply_ex(
1398             C, vzd, false, do_zoom_xy[0] ? zoom_factor : 0.0f, do_zoom_xy[1] ? zoom_factor : 0.0f);
1399
1400         view_zoomstep_exit(op);
1401       }
1402     }
1403
1404     return OPERATOR_FINISHED;
1405   }
1406 }
1407
1408 static void VIEW2D_OT_ndof(wmOperatorType *ot)
1409 {
1410   /* identifiers */
1411   ot->name = "NDOF Pan/Zoom";
1412   ot->idname = "VIEW2D_OT_ndof";
1413   ot->description = "Use a 3D mouse device to pan/zoom the view";
1414
1415   /* api callbacks */
1416   ot->invoke = view2d_ndof_invoke;
1417   ot->poll = view2d_poll;
1418
1419   /* flags */
1420   ot->flag = OPTYPE_LOCK_BYPASS;
1421 }
1422 #endif /* WITH_INPUT_NDOF */
1423
1424 /* ********************************************************* */
1425 /* SMOOTH VIEW */
1426
1427 struct SmoothView2DStore {
1428   rctf orig_cur, new_cur;
1429
1430   double time_allowed;
1431 };
1432
1433 /**
1434  * function to get a factor out of a rectangle
1435  *
1436  * note: this doesn't always work as well as it might because the target size
1437  *       may not be reached because of clamping the desired rect, we _could_
1438  *       attempt to clamp the rect before working out the zoom factor but its
1439  *       not really worthwhile for the few cases this happens.
1440  */
1441 static float smooth_view_rect_to_fac(const rctf *rect_a, const rctf *rect_b)
1442 {
1443   const float size_a[2] = {BLI_rctf_size_x(rect_a), BLI_rctf_size_y(rect_a)};
1444   const float size_b[2] = {BLI_rctf_size_x(rect_b), BLI_rctf_size_y(rect_b)};
1445   const float cent_a[2] = {BLI_rctf_cent_x(rect_a), BLI_rctf_cent_y(rect_a)};
1446   const float cent_b[2] = {BLI_rctf_cent_x(rect_b), BLI_rctf_cent_y(rect_b)};
1447
1448   float fac_max = 0.0f;
1449   float tfac;
1450
1451   int i;
1452
1453   for (i = 0; i < 2; i++) {
1454     /* axis translation normalized to scale */
1455     tfac = fabsf(cent_a[i] - cent_b[i]) / min_ff(size_a[i], size_b[i]);
1456     fac_max = max_ff(fac_max, tfac);
1457     if (fac_max >= 1.0f) {
1458       break;
1459     }
1460
1461     /* axis scale difference, x2 so doubling or half gives 1.0f */
1462     tfac = (1.0f - (min_ff(size_a[i], size_b[i]) / max_ff(size_a[i], size_b[i]))) * 2.0f;
1463     fac_max = max_ff(fac_max, tfac);
1464     if (fac_max >= 1.0f) {
1465       break;
1466     }
1467   }
1468   return min_ff(fac_max, 1.0f);
1469 }
1470
1471 /* will start timer if appropriate */
1472 /* the arguments are the desired situation */
1473 void UI_view2d_smooth_view(bContext *C, ARegion *ar, const rctf *cur, const int smooth_viewtx)
1474 {
1475   wmWindowManager *wm = CTX_wm_manager(C);
1476   wmWindow *win = CTX_wm_window(C);
1477
1478   View2D *v2d = &ar->v2d;
1479   struct SmoothView2DStore sms = {{0}};
1480   bool ok = false;
1481   float fac = 1.0f;
1482
1483   /* initialize sms */
1484   sms.new_cur = v2d->cur;
1485
1486   /* store the options we want to end with */
1487   if (cur) {
1488     sms.new_cur = *cur;
1489   }
1490
1491   if (cur) {
1492     fac = smooth_view_rect_to_fac(&v2d->cur, cur);
1493   }
1494
1495   if (smooth_viewtx && fac > FLT_EPSILON) {
1496     bool changed = false;
1497
1498     if (BLI_rctf_compare(&sms.new_cur, &v2d->cur, FLT_EPSILON) == false) {
1499       changed = true;
1500     }
1501
1502     /* The new view is different from the old one
1503      * so animate the view */
1504     if (changed) {
1505       sms.orig_cur = v2d->cur;
1506
1507       sms.time_allowed = (double)smooth_viewtx / 1000.0;
1508
1509       /* scale the time allowed the change in view */
1510       sms.time_allowed *= (double)fac;
1511
1512       /* keep track of running timer! */
1513       if (v2d->sms == NULL) {
1514         v2d->sms = MEM_mallocN(sizeof(struct SmoothView2DStore), "smoothview v2d");
1515       }
1516       *v2d->sms = sms;
1517       if (v2d->smooth_timer) {
1518         WM_event_remove_timer(wm, win, v2d->smooth_timer);
1519       }
1520       /* TIMER1 is hardcoded in keymap */
1521       /* max 30 frs/sec */
1522       v2d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0);
1523
1524       ok = true;
1525     }
1526   }
1527
1528   /* if we get here nothing happens */
1529   if (ok == false) {
1530     v2d->cur = sms.new_cur;
1531
1532     UI_view2d_curRect_validate(v2d);
1533     ED_region_tag_redraw_no_rebuild(ar);
1534     UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1535   }
1536 }
1537
1538 /* only meant for timer usage */
1539 static int view2d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1540 {
1541   ARegion *ar = CTX_wm_region(C);
1542   View2D *v2d = &ar->v2d;
1543   struct SmoothView2DStore *sms = v2d->sms;
1544   float step;
1545
1546   /* escape if not our timer */
1547   if (v2d->smooth_timer == NULL || v2d->smooth_timer != event->customdata) {
1548     return OPERATOR_PASS_THROUGH;
1549   }
1550
1551   if (sms->time_allowed != 0.0) {
1552     step = (float)((v2d->smooth_timer->duration) / sms->time_allowed);
1553   }
1554   else {
1555     step = 1.0f;
1556   }
1557
1558   /* end timer */
1559   if (step >= 1.0f) {
1560     v2d->cur = sms->new_cur;
1561
1562     MEM_freeN(v2d->sms);
1563     v2d->sms = NULL;
1564
1565     WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), v2d->smooth_timer);
1566     v2d->smooth_timer = NULL;
1567
1568     /* Event handling won't know if a UI item has been moved under the pointer. */
1569     WM_event_add_mousemove(C);
1570   }
1571   else {
1572     /* ease in/out */
1573     step = (3.0f * step * step - 2.0f * step * step * step);
1574
1575     BLI_rctf_interp(&v2d->cur, &sms->orig_cur, &sms->new_cur, step);
1576   }
1577
1578   UI_view2d_curRect_validate(v2d);
1579   UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1580   ED_region_tag_redraw_no_rebuild(ar);
1581
1582   if (v2d->sms == NULL) {
1583     UI_view2d_zoom_cache_reset();
1584   }
1585
1586   return OPERATOR_FINISHED;
1587 }
1588
1589 static void VIEW2D_OT_smoothview(wmOperatorType *ot)
1590 {
1591   /* identifiers */
1592   ot->name = "Smooth View 2D";
1593   ot->idname = "VIEW2D_OT_smoothview";
1594
1595   /* api callbacks */
1596   ot->invoke = view2d_smoothview_invoke;
1597   ot->poll = view2d_poll;
1598
1599   /* flags */
1600   ot->flag = OPTYPE_INTERNAL;
1601
1602   /* rna */
1603   WM_operator_properties_gesture_box(ot);
1604 }
1605
1606 /* ********************************************************* */
1607 /* SCROLLERS */
1608
1609 /**
1610  * Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
1611  * -# 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable,
1612  *    enlarge 'cur' rect on the relevant side.
1613  * -# 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite).
1614  *
1615  * In order to make sure this works, each operator must define the following RNA-Operator Props:
1616  * - `deltax, deltay` - define how much to move view by (relative to zoom-correction factor)
1617  */
1618
1619 /* customdata for scroller-invoke data */
1620 typedef struct v2dScrollerMove {
1621   /** View2D data that this operation affects */
1622   View2D *v2d;
1623   /** region that the scroller is in */
1624   ARegion *ar;
1625
1626   /** scroller that mouse is in ('h' or 'v') */
1627   char scroller;
1628
1629   /* XXX find some way to provide visual feedback of this (active color?) */
1630   /** -1 is min zoomer, 0 is bar, 1 is max zoomer */
1631   short zone;
1632
1633   /** view adjustment factor, based on size of region */
1634   float fac;
1635   /** for pixel rounding (avoid visible UI jitter) */
1636   float fac_round;
1637   /** amount moved by mouse on axis of interest */
1638   float delta;
1639
1640   /** width of the scrollbar itself, used for page up/down clicks */
1641   float scrollbarwidth;
1642   /** initial location of scrollbar x/y, mouse relative */
1643   int scrollbar_orig;
1644
1645   /** previous mouse coordinates (in screen coordinates) for determining movement */
1646   int lastx, lasty;
1647 } v2dScrollerMove;
1648
1649 /**
1650  * #View2DScrollers is typedef'd in UI_view2d.h
1651  * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c,
1652  * as we only need focus bubble info.
1653  *
1654  * \warning: The start of this struct must not change,
1655  * so that it stays in sync with the 'real' version.
1656  * For now, we don't need to have a separate (internal) header for structs like this...
1657  */
1658 struct View2DScrollers {
1659   /* focus bubbles */
1660   int vert_min, vert_max; /* vertical scrollbar */
1661   int hor_min, hor_max;   /* horizontal scrollbar */
1662 };
1663
1664 /* quick enum for vsm->zone (scroller handles) */
1665 enum {
1666   SCROLLHANDLE_MIN = -1,
1667   SCROLLHANDLE_BAR,
1668   SCROLLHANDLE_MAX,
1669   SCROLLHANDLE_MIN_OUTSIDE,
1670   SCROLLHANDLE_MAX_OUTSIDE,
1671 } /*eV2DScrollerHandle_Zone*/;
1672
1673 /* ------------------------ */
1674
1675 /**
1676  * Check if mouse is within scroller handle.
1677  *
1678  * \param mouse: relevant mouse coordinate in region space.
1679  * \param sc_min, sc_max: extents of scroller 'groove' (potential available space for scroller).
1680  * \param sh_min, sh_max: positions of scrollbar handles.
1681  */
1682 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
1683 {
1684   bool in_min, in_max, in_bar, out_min, out_max, in_view = 1;
1685
1686   /* firstly, check if
1687    * - 'bubble' fills entire scroller
1688    * - 'bubble' completely out of view on either side
1689    */
1690   if ((sh_min <= sc_min) && (sh_max >= sc_max)) {
1691     in_view = 0;
1692   }
1693   if (sh_min == sh_max) {
1694     if (sh_min <= sc_min) {
1695       in_view = 0;
1696     }
1697     if (sh_max >= sc_max) {
1698       in_view = 0;
1699     }
1700   }
1701   else {
1702     if (sh_max <= sc_min) {
1703       in_view = 0;
1704     }
1705     if (sh_min >= sc_max) {
1706       in_view = 0;
1707     }
1708   }
1709
1710   if (in_view == 0) {
1711     return SCROLLHANDLE_BAR;
1712   }
1713
1714   /* check if mouse is in or past either handle */
1715   /* TODO: check if these extents are still valid or not */
1716   in_max = ((mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) &&
1717             (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)));
1718   in_min = ((mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) &&
1719             (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)));
1720   in_bar = ((mouse < (sh_max - V2D_SCROLLER_HANDLE_SIZE)) &&
1721             (mouse > (sh_min + V2D_SCROLLER_HANDLE_SIZE)));
1722   out_min = mouse < (sh_min - V2D_SCROLLER_HANDLE_SIZE);
1723   out_max = mouse > (sh_max + V2D_SCROLLER_HANDLE_SIZE);
1724
1725   if (in_bar) {
1726     return SCROLLHANDLE_BAR;
1727   }
1728   else if (in_max) {
1729     return SCROLLHANDLE_MAX;
1730   }
1731   else if (in_min) {
1732     return SCROLLHANDLE_MIN;
1733   }
1734   else if (out_min) {
1735     return SCROLLHANDLE_MIN_OUTSIDE;
1736   }
1737   else if (out_max) {
1738     return SCROLLHANDLE_MAX_OUTSIDE;
1739   }
1740
1741   /* unlikely to happen, though we just cover it in case */
1742   return SCROLLHANDLE_BAR;
1743 }
1744
1745 static bool scroller_activate_poll(bContext *C)
1746 {
1747   if (!view2d_poll(C)) {
1748     return false;
1749   }
1750
1751   wmWindow *win = CTX_wm_window(C);
1752   ARegion *ar = CTX_wm_region(C);
1753   View2D *v2d = &ar->v2d;
1754   wmEvent *event = win->eventstate;
1755
1756   /* check if mouse in scrollbars, if they're enabled */
1757   return (UI_view2d_mouse_in_scrollers(ar, v2d, event->x, event->y) != 0);
1758 }
1759
1760 /* initialize customdata for scroller manipulation operator */
1761 static void scroller_activate_init(bContext *C,
1762                                    wmOperator *op,
1763                                    const wmEvent *event,
1764                                    const char in_scroller)
1765 {
1766   v2dScrollerMove *vsm;
1767   View2DScrollers *scrollers;
1768   ARegion *ar = CTX_wm_region(C);
1769   View2D *v2d = &ar->v2d;
1770   rctf tot_cur_union;
1771   float mask_size;
1772
1773   /* set custom-data for operator */
1774   vsm = MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
1775   op->customdata = vsm;
1776
1777   /* set general data */
1778   vsm->v2d = v2d;
1779   vsm->ar = ar;
1780   vsm->scroller = in_scroller;
1781
1782   /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
1783   vsm->lastx = event->x;
1784   vsm->lasty = event->y;
1785   /* 'zone' depends on where mouse is relative to bubble
1786    * - zooming must be allowed on this axis, otherwise, default to pan
1787    */
1788   scrollers = UI_view2d_scrollers_calc(v2d, NULL);
1789
1790   /* use a union of 'cur' & 'tot' incase the current view is far outside 'tot'. In this cases
1791    * moving the scroll bars has far too little effect and the view can get stuck T31476. */
1792   tot_cur_union = v2d->tot;
1793   BLI_rctf_union(&tot_cur_union, &v2d->cur);
1794
1795   if (in_scroller == 'h') {
1796     /* horizontal scroller - calculate adjustment factor first */
1797     mask_size = (float)BLI_rcti_size_x(&v2d->hor);
1798     vsm->fac = BLI_rctf_size_x(&tot_cur_union) / mask_size;
1799
1800     /* pixel rounding */
1801     vsm->fac_round = (BLI_rctf_size_x(&v2d->cur)) / (float)(BLI_rcti_size_x(&ar->winrct) + 1);
1802
1803     /* get 'zone' (i.e. which part of scroller is activated) */
1804     vsm->zone = mouse_in_scroller_handle(
1805         event->mval[0], v2d->hor.xmin, v2d->hor.xmax, scrollers->hor_min, scrollers->hor_max);
1806
1807     if ((v2d->keepzoom & V2D_LOCKZOOM_X) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1808       /* default to scroll, as handles not usable */
1809       vsm->zone = SCROLLHANDLE_BAR;
1810     }
1811
1812     vsm->scrollbarwidth = scrollers->hor_max - scrollers->hor_min;
1813     vsm->scrollbar_orig = ((scrollers->hor_max + scrollers->hor_min) / 2) + ar->winrct.xmin;
1814   }
1815   else {
1816     /* vertical scroller - calculate adjustment factor first */
1817     mask_size = (float)BLI_rcti_size_y(&v2d->vert);
1818     vsm->fac = BLI_rctf_size_y(&tot_cur_union) / mask_size;
1819
1820     /* pixel rounding */
1821     vsm->fac_round = (BLI_rctf_size_y(&v2d->cur)) / (float)(BLI_rcti_size_y(&ar->winrct) + 1);
1822
1823     /* get 'zone' (i.e. which part of scroller is activated) */
1824     vsm->zone = mouse_in_scroller_handle(
1825         event->mval[1], v2d->vert.ymin, v2d->vert.ymax, scrollers->vert_min, scrollers->vert_max);
1826
1827     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1828       /* default to scroll, as handles not usable */
1829       vsm->zone = SCROLLHANDLE_BAR;
1830     }
1831
1832     vsm->scrollbarwidth = scrollers->vert_max - scrollers->vert_min;
1833     vsm->scrollbar_orig = ((scrollers->vert_max + scrollers->vert_min) / 2) + ar->winrct.ymin;
1834   }
1835
1836   UI_view2d_scrollers_free(scrollers);
1837   ED_region_tag_redraw_no_rebuild(ar);
1838 }
1839
1840 /* cleanup temp customdata  */
1841 static void scroller_activate_exit(bContext *C, wmOperator *op)
1842 {
1843   if (op->customdata) {
1844     v2dScrollerMove *vsm = op->customdata;
1845
1846     vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE);
1847
1848     MEM_freeN(op->customdata);
1849     op->customdata = NULL;
1850
1851     ED_region_tag_redraw_no_rebuild(CTX_wm_region(C));
1852   }
1853 }
1854
1855 static void scroller_activate_cancel(bContext *C, wmOperator *op)
1856 {
1857   scroller_activate_exit(C, op);
1858 }
1859
1860 /* apply transform to view (i.e. adjust 'cur' rect) */
1861 static void scroller_activate_apply(bContext *C, wmOperator *op)
1862 {
1863   v2dScrollerMove *vsm = op->customdata;
1864   View2D *v2d = vsm->v2d;
1865   float temp;
1866
1867   /* calculate amount to move view by */
1868   temp = vsm->fac * vsm->delta;
1869
1870   /* round to pixel */
1871   temp = roundf(temp / vsm->fac_round) * vsm->fac_round;
1872
1873   /* type of movement */
1874   switch (vsm->zone) {
1875     case SCROLLHANDLE_MIN:
1876       /* only expand view on axis if zoom is allowed */
1877       if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) {
1878         v2d->cur.xmin -= temp;
1879       }
1880       if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) {
1881         v2d->cur.ymin -= temp;
1882       }
1883       break;
1884
1885     case SCROLLHANDLE_MAX:
1886
1887       /* only expand view on axis if zoom is allowed */
1888       if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) {
1889         v2d->cur.xmax += temp;
1890       }
1891       if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) {
1892         v2d->cur.ymax += temp;
1893       }
1894       break;
1895
1896     case SCROLLHANDLE_MIN_OUTSIDE:
1897     case SCROLLHANDLE_MAX_OUTSIDE:
1898     case SCROLLHANDLE_BAR:
1899     default:
1900       /* only move view on an axis if panning is allowed */
1901       if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1902         v2d->cur.xmin += temp;
1903         v2d->cur.xmax += temp;
1904       }
1905       if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1906         v2d->cur.ymin += temp;
1907         v2d->cur.ymax += temp;
1908       }
1909       break;
1910   }
1911
1912   /* validate that view is in valid configuration after this operation */
1913   UI_view2d_curRect_validate(v2d);
1914
1915   /* request updates to be done... */
1916   ED_region_tag_redraw_no_rebuild(vsm->ar);
1917   UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1918 }
1919
1920 /**
1921  * Handle user input for scrollers - calculations of mouse-movement need to be done here,
1922  * not in the apply callback!
1923  */
1924 static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *event)
1925 {
1926   v2dScrollerMove *vsm = op->customdata;
1927
1928   /* execute the events */
1929   switch (event->type) {
1930     case MOUSEMOVE: {
1931       /* calculate new delta transform, then store mouse-coordinates for next-time */
1932       if (ELEM(vsm->zone, SCROLLHANDLE_BAR, SCROLLHANDLE_MAX)) {
1933         /* if using bar (i.e. 'panning') or 'max' zoom widget */
1934         switch (vsm->scroller) {
1935           case 'h': /* horizontal scroller - so only horizontal movement
1936                      * ('cur' moves opposite to mouse) */
1937             vsm->delta = (float)(event->x - vsm->lastx);
1938             break;
1939           case 'v': /* vertical scroller - so only vertical movement
1940                      * ('cur' moves opposite to mouse) */
1941             vsm->delta = (float)(event->y - vsm->lasty);
1942             break;
1943         }
1944       }
1945       else if (vsm->zone == SCROLLHANDLE_MIN) {
1946         /* using 'min' zoom widget */
1947         switch (vsm->scroller) {
1948           case 'h': /* horizontal scroller - so only horizontal movement
1949                      * ('cur' moves with mouse) */
1950             vsm->delta = (float)(vsm->lastx - event->x);
1951             break;
1952           case 'v': /* vertical scroller - so only vertical movement
1953                      * ('cur' moves with to mouse) */
1954             vsm->delta = (float)(vsm->lasty - event->y);
1955             break;
1956         }
1957       }
1958
1959       /* store previous coordinates */
1960       vsm->lastx = event->x;
1961       vsm->lasty = event->y;
1962
1963       scroller_activate_apply(C, op);
1964       break;
1965     }
1966     case LEFTMOUSE:
1967     case MIDDLEMOUSE:
1968       if (event->val == KM_RELEASE) {
1969         /* single-click was in empty space outside bubble, so scroll by 1 'page' */
1970         if (ELEM(vsm->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) {
1971           if (vsm->zone == SCROLLHANDLE_MIN_OUTSIDE) {
1972             vsm->delta = -vsm->scrollbarwidth * 0.8f;
1973           }
1974           else if (vsm->zone == SCROLLHANDLE_MAX_OUTSIDE) {
1975             vsm->delta = vsm->scrollbarwidth * 0.8f;
1976           }
1977
1978           scroller_activate_apply(C, op);
1979           scroller_activate_exit(C, op);
1980           return OPERATOR_FINISHED;
1981         }
1982
1983         /* otherwise, end the drag action  */
1984         if (vsm->lastx || vsm->lasty) {
1985           scroller_activate_exit(C, op);
1986           return OPERATOR_FINISHED;
1987         }
1988       }
1989       break;
1990   }
1991
1992   return OPERATOR_RUNNING_MODAL;
1993 }
1994
1995 /* a click (or click drag in progress)
1996  * should have occurred, so check if it happened in scrollbar */
1997 static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1998 {
1999   ARegion *ar = CTX_wm_region(C);
2000   View2D *v2d = &ar->v2d;
2001
2002   /* check if mouse in scrollbars, if they're enabled */
2003   const char in_scroller = UI_view2d_mouse_in_scrollers(ar, v2d, event->x, event->y);
2004
2005   /* if in a scroller, init customdata then set modal handler which will
2006    * catch mousedown to start doing useful stuff */
2007   if (in_scroller) {
2008     v2dScrollerMove *vsm;
2009
2010     /* initialize customdata */
2011     scroller_activate_init(C, op, event, in_scroller);
2012     vsm = (v2dScrollerMove *)op->customdata;
2013
2014     /* support for quick jump to location - gtk and qt do this on linux */
2015     if (event->type == MIDDLEMOUSE) {
2016       switch (vsm->scroller) {
2017         case 'h': /* horizontal scroller - so only horizontal movement
2018                    * ('cur' moves opposite to mouse) */
2019           vsm->delta = (float)(event->x - vsm->scrollbar_orig);
2020           break;
2021         case 'v': /* vertical scroller - so only vertical movement
2022                    * ('cur' moves opposite to mouse) */
2023           vsm->delta = (float)(event->y - vsm->scrollbar_orig);
2024           break;
2025       }
2026       scroller_activate_apply(C, op);
2027
2028       vsm->zone = SCROLLHANDLE_BAR;
2029     }
2030
2031     /* check if zoom zones are inappropriate (i.e. zoom widgets not shown), so cannot continue
2032      * NOTE: see view2d.c for latest conditions, and keep this in sync with that
2033      */
2034     if (ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
2035       if (((vsm->scroller == 'h') && (v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) == 0) ||
2036           ((vsm->scroller == 'v') && (v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) == 0)) {
2037         /* switch to bar (i.e. no scaling gets handled) */
2038         vsm->zone = SCROLLHANDLE_BAR;
2039       }
2040     }
2041
2042     /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */
2043     if (vsm->zone == SCROLLHANDLE_BAR) {
2044       if (((vsm->scroller == 'h') && (v2d->keepofs & V2D_LOCKOFS_X)) ||
2045           ((vsm->scroller == 'v') && (v2d->keepofs & V2D_LOCKOFS_Y))) {
2046         /* free customdata initialized */
2047         scroller_activate_exit(C, op);
2048
2049         /* can't catch this event for ourselves, so let it go to someone else? */
2050         return OPERATOR_PASS_THROUGH;
2051       }
2052     }
2053
2054     /* zone is also inappropriate if scroller is not visible... */
2055     if (((vsm->scroller == 'h') && (v2d->scroll & (V2D_SCROLL_HORIZONTAL_FULLR))) ||
2056         ((vsm->scroller == 'v') && (v2d->scroll & (V2D_SCROLL_VERTICAL_FULLR)))) {
2057       /* free customdata initialized */
2058       scroller_activate_exit(C, op);
2059
2060       /* can't catch this event for ourselves, so let it go to someone else? */
2061       /* XXX note: if handlers use mask rect to clip input, input will fail for this case */
2062       return OPERATOR_PASS_THROUGH;
2063     }
2064
2065     /* activate the scroller */
2066     if (vsm->scroller == 'h') {
2067       v2d->scroll_ui |= V2D_SCROLL_H_ACTIVE;
2068     }
2069     else {
2070       v2d->scroll_ui |= V2D_SCROLL_V_ACTIVE;
2071     }
2072
2073     /* still ok, so can add */
2074     WM_event_add_modal_handler(C, op);
2075     return OPERATOR_RUNNING_MODAL;
2076   }
2077   else {
2078     /* not in scroller, so nothing happened...
2079      * (pass through let's something else catch event) */
2080     return OPERATOR_PASS_THROUGH;
2081   }
2082 }
2083
2084 /* LMB-Drag in Scrollers - not repeatable operator! */
2085 static void VIEW2D_OT_scroller_activate(wmOperatorType *ot)
2086 {
2087   /* identifiers */
2088   ot->name = "Scroller Activate";
2089   ot->description = "Scroll view by mouse click and drag";
2090   ot->idname = "VIEW2D_OT_scroller_activate";
2091
2092   /* flags */
2093   ot->flag = OPTYPE_BLOCKING;
2094
2095   /* api callbacks */
2096   ot->invoke = scroller_activate_invoke;
2097   ot->modal = scroller_activate_modal;
2098   ot->cancel = scroller_activate_cancel;
2099
2100   ot->poll = scroller_activate_poll;
2101 }
2102
2103 /* ********************************************************* */
2104 /* RESET */
2105
2106 static int reset_exec(bContext *C, wmOperator *UNUSED(op))
2107 {
2108   uiStyle *style = UI_style_get();
2109   ARegion *ar = CTX_wm_region(C);
2110   View2D *v2d = &ar->v2d;
2111   int winx, winy;
2112   const int snap_test = ED_region_snap_size_test(ar);
2113
2114   /* zoom 1.0 */
2115   winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1);
2116   winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1);
2117
2118   v2d->cur.xmax = v2d->cur.xmin + winx;
2119   v2d->cur.ymax = v2d->cur.ymin + winy;
2120
2121   /* align */
2122   if (v2d->align) {
2123     /* posx and negx flags are mutually exclusive, so watch out */
2124     if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
2125       v2d->cur.xmax = 0.0f;
2126       v2d->cur.xmin = -winx * style->panelzoom;
2127     }
2128     else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
2129       v2d->cur.xmax = winx * style->panelzoom;
2130       v2d->cur.xmin = 0.0f;
2131     }
2132
2133     /* - posx and negx flags are mutually exclusive, so watch out */
2134     if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
2135       v2d->cur.ymax = 0.0f;
2136       v2d->cur.ymin = -winy * style->panelzoom;
2137     }
2138     else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
2139       v2d->cur.ymax = winy * style->panelzoom;
2140       v2d->cur.ymin = 0.0f;
2141     }
2142   }
2143
2144   /* validate that view is in valid configuration after this operation */
2145   UI_view2d_curRect_validate(v2d);
2146
2147   if (ED_region_snap_size_apply(ar, snap_test)) {
2148     ScrArea *sa = CTX_wm_area(C);
2149     ED_area_tag_redraw(sa);
2150     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2151   }
2152
2153   /* request updates to be done... */
2154   ED_region_tag_redraw(ar);
2155   UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
2156
2157   UI_view2d_zoom_cache_reset();
2158
2159   return OPERATOR_FINISHED;
2160 }
2161
2162 static void VIEW2D_OT_reset(wmOperatorType *ot)
2163 {
2164   /* identifiers */
2165   ot->name = "Reset View";
2166   ot->description = "Reset the view";
2167   ot->idname = "VIEW2D_OT_reset";
2168
2169   /* api callbacks */
2170   ot->exec = reset_exec;
2171   ot->poll = view2d_poll;
2172 }
2173
2174 /* ********************************************************* */
2175 /* Registration */
2176
2177 void ED_operatortypes_view2d(void)
2178 {
2179   WM_operatortype_append(VIEW2D_OT_pan);
2180
2181   WM_operatortype_append(VIEW2D_OT_scroll_left);
2182   WM_operatortype_append(VIEW2D_OT_scroll_right);
2183   WM_operatortype_append(VIEW2D_OT_scroll_up);
2184   WM_operatortype_append(VIEW2D_OT_scroll_down);
2185
2186   WM_operatortype_append(VIEW2D_OT_zoom_in);
2187   WM_operatortype_append(VIEW2D_OT_zoom_out);
2188
2189   WM_operatortype_append(VIEW2D_OT_zoom);
2190   WM_operatortype_append(VIEW2D_OT_zoom_border);
2191
2192 #ifdef WITH_INPUT_NDOF
2193   WM_operatortype_append(VIEW2D_OT_ndof);
2194 #endif
2195
2196   WM_operatortype_append(VIEW2D_OT_smoothview);
2197
2198   WM_operatortype_append(VIEW2D_OT_scroller_activate);
2199
2200   WM_operatortype_append(VIEW2D_OT_reset);
2201 }
2202
2203 void ED_keymap_view2d(wmKeyConfig *keyconf)
2204 {
2205   WM_keymap_ensure(keyconf, "View2D", 0, 0);
2206 }