WM: option to ignore cursor image/clip/view2d zoom
[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 zoom_to_pos, 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 (zoom_to_pos) {
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 (zoom_to_pos) {
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   const bool zoom_to_pos = U.uiflag & USER_ZOOM_TO_MOUSEPOS;
756   view_zoomstep_apply_ex(
757       C, vzd, zoom_to_pos, RNA_float_get(op->ptr, "zoomfacx"), RNA_float_get(op->ptr, "zoomfacy"));
758 }
759
760 /* --------------- Individual Operators ------------------- */
761
762 /* cleanup temp customdata  */
763 static void view_zoomstep_exit(wmOperator *op)
764 {
765   UI_view2d_zoom_cache_reset();
766
767   if (op->customdata) {
768     MEM_freeN(op->customdata);
769     op->customdata = NULL;
770   }
771 }
772
773 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
774 static int view_zoomin_exec(bContext *C, wmOperator *op)
775 {
776   bool do_zoom_xy[2];
777
778   /* check that there's an active region, as View2D data resides there */
779   if (!view_zoom_poll(C)) {
780     return OPERATOR_PASS_THROUGH;
781   }
782
783   view_zoom_axis_lock_defaults(C, do_zoom_xy);
784
785   /* set RNA-Props - zooming in by uniform factor */
786   RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? 0.0375f : 0.0f);
787   RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? 0.0375f : 0.0f);
788
789   /* apply movement, then we're done */
790   view_zoomstep_apply(C, op);
791
792   view_zoomstep_exit(op);
793
794   return OPERATOR_FINISHED;
795 }
796
797 static int view_zoomin_invoke(bContext *C, wmOperator *op, const wmEvent *event)
798 {
799   v2dViewZoomData *vzd;
800
801   if (!view_zoomdrag_init(C, op)) {
802     return OPERATOR_PASS_THROUGH;
803   }
804
805   vzd = op->customdata;
806
807   if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
808     ARegion *ar = CTX_wm_region(C);
809
810     /* store initial mouse position (in view space) */
811     UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d);
812   }
813
814   return view_zoomin_exec(C, op);
815 }
816
817 static void VIEW2D_OT_zoom_in(wmOperatorType *ot)
818 {
819   PropertyRNA *prop;
820
821   /* identifiers */
822   ot->name = "Zoom In";
823   ot->description = "Zoom in the view";
824   ot->idname = "VIEW2D_OT_zoom_in";
825
826   /* api callbacks */
827   ot->invoke = view_zoomin_invoke;
828   ot->exec = view_zoomin_exec;  // XXX, needs view_zoomdrag_init called first.
829   ot->poll = view_zoom_poll;
830
831   /* rna - must keep these in sync with the other operators */
832   prop = RNA_def_float(
833       ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
834   RNA_def_property_flag(prop, PROP_HIDDEN);
835   prop = RNA_def_float(
836       ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
837   RNA_def_property_flag(prop, PROP_HIDDEN);
838 }
839
840 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
841 static int view_zoomout_exec(bContext *C, wmOperator *op)
842 {
843   bool do_zoom_xy[2];
844
845   /* check that there's an active region, as View2D data resides there */
846   if (!view_zoom_poll(C)) {
847     return OPERATOR_PASS_THROUGH;
848   }
849
850   view_zoom_axis_lock_defaults(C, do_zoom_xy);
851
852   /* set RNA-Props - zooming in by uniform factor */
853   RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? -0.0375f : 0.0f);
854   RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? -0.0375f : 0.0f);
855
856   /* apply movement, then we're done */
857   view_zoomstep_apply(C, op);
858
859   view_zoomstep_exit(op);
860
861   return OPERATOR_FINISHED;
862 }
863
864 static int view_zoomout_invoke(bContext *C, wmOperator *op, const wmEvent *event)
865 {
866   v2dViewZoomData *vzd;
867
868   if (!view_zoomdrag_init(C, op)) {
869     return OPERATOR_PASS_THROUGH;
870   }
871
872   vzd = op->customdata;
873
874   if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
875     ARegion *ar = CTX_wm_region(C);
876
877     /* store initial mouse position (in view space) */
878     UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d);
879   }
880
881   return view_zoomout_exec(C, op);
882 }
883
884 static void VIEW2D_OT_zoom_out(wmOperatorType *ot)
885 {
886   PropertyRNA *prop;
887
888   /* identifiers */
889   ot->name = "Zoom Out";
890   ot->description = "Zoom out the view";
891   ot->idname = "VIEW2D_OT_zoom_out";
892
893   /* api callbacks */
894   ot->invoke = view_zoomout_invoke;
895   //  ot->exec = view_zoomout_exec; // XXX, needs view_zoomdrag_init called first.
896   ot->poll = view_zoom_poll;
897
898   /* rna - must keep these in sync with the other operators */
899   prop = RNA_def_float(
900       ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
901   RNA_def_property_flag(prop, PROP_HIDDEN);
902   prop = RNA_def_float(
903       ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
904   RNA_def_property_flag(prop, PROP_HIDDEN);
905 }
906
907 /* ********************************************************* */
908 /* DRAG-ZOOM OPERATOR                                    */
909
910 /**
911  * MMB Drag - allows non-uniform scaling by dragging mouse
912  *
913  * In order to make sure this works, each operator must define the following RNA-Operator Props:
914  * - `deltax, deltay` - amounts to add to each side of the 'cur' rect
915  */
916
917 /* apply transform to view (i.e. adjust 'cur' rect) */
918 static void view_zoomdrag_apply(bContext *C, wmOperator *op)
919 {
920   v2dViewZoomData *vzd = op->customdata;
921   View2D *v2d = vzd->v2d;
922   float dx, dy;
923   const int snap_test = ED_region_snap_size_test(vzd->ar);
924
925   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
926   const bool zoom_to_pos = use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS);
927
928   /* get amount to move view by */
929   dx = RNA_float_get(op->ptr, "deltax");
930   dy = RNA_float_get(op->ptr, "deltay");
931
932   if (U.uiflag & USER_ZOOM_INVERT) {
933     dx *= -1;
934     dy *= -1;
935   }
936
937   /* continuous zoom shouldn't move that fast... */
938   if (U.viewzoom == USER_ZOOM_CONT) {  // XXX store this setting as RNA prop?
939     double time = PIL_check_seconds_timer();
940     float time_step = (float)(time - vzd->timer_lastdraw);
941
942     dx *= time_step * 0.5f;
943     dy *= time_step * 0.5f;
944
945     vzd->timer_lastdraw = time;
946   }
947
948   /* only move view on an axis if change is allowed */
949   if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
950     if (v2d->keepofs & V2D_LOCKOFS_X) {
951       v2d->cur.xmax -= 2 * dx;
952     }
953     else {
954       if (zoom_to_pos) {
955         float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
956         float mval_faci = 1.0f - mval_fac;
957         float ofs = (mval_fac * dx) - (mval_faci * dx);
958
959         v2d->cur.xmin += ofs + dx;
960         v2d->cur.xmax += ofs - dx;
961       }
962       else {
963         v2d->cur.xmin += dx;
964         v2d->cur.xmax -= dx;
965       }
966     }
967   }
968   if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
969     if (v2d->keepofs & V2D_LOCKOFS_Y) {
970       v2d->cur.ymax -= 2 * dy;
971     }
972     else {
973       if (zoom_to_pos) {
974         float mval_fac = (vzd->my_2d - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
975         float mval_faci = 1.0f - mval_fac;
976         float ofs = (mval_fac * dy) - (mval_faci * dy);
977
978         v2d->cur.ymin += ofs + dy;
979         v2d->cur.ymax += ofs - dy;
980       }
981       else {
982         v2d->cur.ymin += dy;
983         v2d->cur.ymax -= dy;
984       }
985     }
986   }
987
988   /* validate that view is in valid configuration after this operation */
989   UI_view2d_curRect_validate(v2d);
990
991   if (ED_region_snap_size_apply(vzd->ar, snap_test)) {
992     ScrArea *sa = CTX_wm_area(C);
993     ED_area_tag_redraw(sa);
994     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
995   }
996
997   /* request updates to be done... */
998   ED_region_tag_redraw_no_rebuild(vzd->ar);
999   UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1000 }
1001
1002 /* cleanup temp customdata  */
1003 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
1004 {
1005   UI_view2d_zoom_cache_reset();
1006
1007   if (op->customdata) {
1008     v2dViewZoomData *vzd = op->customdata;
1009
1010     if (vzd->timer) {
1011       WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer);
1012     }
1013
1014     MEM_freeN(op->customdata);
1015     op->customdata = NULL;
1016   }
1017 }
1018
1019 static void view_zoomdrag_cancel(bContext *C, wmOperator *op)
1020 {
1021   view_zoomdrag_exit(C, op);
1022 }
1023
1024 /* for 'redo' only, with no user input */
1025 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
1026 {
1027   if (!view_zoomdrag_init(C, op)) {
1028     return OPERATOR_PASS_THROUGH;
1029   }
1030
1031   view_zoomdrag_apply(C, op);
1032   view_zoomdrag_exit(C, op);
1033   return OPERATOR_FINISHED;
1034 }
1035
1036 /* set up modal operator and relevant settings */
1037 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1038 {
1039   wmWindow *window = CTX_wm_window(C);
1040   v2dViewZoomData *vzd;
1041   View2D *v2d;
1042
1043   /* set up customdata */
1044   if (!view_zoomdrag_init(C, op)) {
1045     return OPERATOR_PASS_THROUGH;
1046   }
1047
1048   vzd = op->customdata;
1049   v2d = vzd->v2d;
1050
1051   if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
1052     float dx, dy, fac;
1053
1054     vzd->lastx = event->prevx;
1055     vzd->lasty = event->prevy;
1056
1057     /* As we have only 1D information (magnify value), feed both axes
1058      * with magnify information that is stored in x axis
1059      */
1060     fac = 0.01f * (event->prevx - event->x);
1061     dx = fac * BLI_rctf_size_x(&v2d->cur) / 10.0f;
1062     if (event->type == MOUSEPAN) {
1063       fac = 0.01f * (event->prevy - event->y);
1064     }
1065     dy = fac * BLI_rctf_size_y(&v2d->cur) / 10.0f;
1066
1067     /* support trackpad zoom to always zoom entirely - the v2d code uses portrait or
1068      * landscape exceptions */
1069     if (v2d->keepzoom & V2D_KEEPASPECT) {
1070       if (fabsf(dx) > fabsf(dy)) {
1071         dy = dx;
1072       }
1073       else {
1074         dx = dy;
1075       }
1076     }
1077     RNA_float_set(op->ptr, "deltax", dx);
1078     RNA_float_set(op->ptr, "deltay", dy);
1079
1080     view_zoomdrag_apply(C, op);
1081     view_zoomdrag_exit(C, op);
1082     return OPERATOR_FINISHED;
1083   }
1084
1085   /* set initial settings */
1086   vzd->lastx = event->x;
1087   vzd->lasty = event->y;
1088   RNA_float_set(op->ptr, "deltax", 0);
1089   RNA_float_set(op->ptr, "deltay", 0);
1090
1091   /* for modal exit test */
1092   vzd->invoke_event = event->type;
1093
1094   if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
1095     ARegion *ar = CTX_wm_region(C);
1096
1097     /* store initial mouse position (in view space) */
1098     UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d);
1099   }
1100
1101   if (v2d->keepofs & V2D_LOCKOFS_X) {
1102     WM_cursor_modal_set(window, BC_NS_SCROLLCURSOR);
1103   }
1104   else if (v2d->keepofs & V2D_LOCKOFS_Y) {
1105     WM_cursor_modal_set(window, BC_EW_SCROLLCURSOR);
1106   }
1107   else {
1108     WM_cursor_modal_set(window, BC_NSEW_SCROLLCURSOR);
1109   }
1110
1111   /* add temp handler */
1112   WM_event_add_modal_handler(C, op);
1113
1114   if (U.viewzoom == USER_ZOOM_CONT) {
1115     /* needs a timer to continue redrawing */
1116     vzd->timer = WM_event_add_timer(CTX_wm_manager(C), window, TIMER, 0.01f);
1117     vzd->timer_lastdraw = PIL_check_seconds_timer();
1118   }
1119
1120   return OPERATOR_RUNNING_MODAL;
1121 }
1122
1123 /* handle user input - calculations of mouse-movement need to be done here,
1124  * not in the apply callback! */
1125 static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event)
1126 {
1127   v2dViewZoomData *vzd = op->customdata;
1128   View2D *v2d = vzd->v2d;
1129
1130   /* execute the events */
1131   if (event->type == TIMER && event->customdata == vzd->timer) {
1132     view_zoomdrag_apply(C, op);
1133   }
1134   else if (event->type == MOUSEMOVE) {
1135     float dx, dy;
1136
1137     /* calculate new delta transform, based on zooming mode */
1138     if (U.viewzoom == USER_ZOOM_SCALE) {
1139       /* 'scale' zooming */
1140       float dist;
1141
1142       /* x-axis transform */
1143       dist = BLI_rcti_size_x(&v2d->mask) / 2.0f;
1144       dx = 1.0f - (fabsf(vzd->lastx - vzd->ar->winrct.xmin - dist) + 2.0f) /
1145                       (fabsf(event->mval[0] - dist) + 2.0f);
1146       dx *= 0.5f * BLI_rctf_size_x(&v2d->cur);
1147
1148       /* y-axis transform */
1149       dist = BLI_rcti_size_y(&v2d->mask) / 2.0f;
1150       dy = 1.0f - (fabsf(vzd->lasty - vzd->ar->winrct.ymin - dist) + 2.0f) /
1151                       (fabsf(event->mval[1] - dist) + 2.0f);
1152       dy *= 0.5f * BLI_rctf_size_y(&v2d->cur);
1153     }
1154     else {
1155       /* 'continuous' or 'dolly' */
1156       float fac, zoomfac = 0.01f;
1157
1158       /* some view2d's (graph) don't have min/max zoom, or extreme ones */
1159       if (v2d->maxzoom > 0.0f) {
1160         zoomfac = clamp_f(0.001f * v2d->maxzoom, 0.001f, 0.01f);
1161       }
1162
1163       /* x-axis transform */
1164       fac = zoomfac * (event->x - vzd->lastx);
1165       dx = fac * BLI_rctf_size_x(&v2d->cur);
1166
1167       /* y-axis transform */
1168       fac = zoomfac * (event->y - vzd->lasty);
1169       dy = fac * BLI_rctf_size_y(&v2d->cur);
1170     }
1171
1172     /* support zoom to always zoom entirely - the v2d code uses portrait or
1173      * landscape exceptions */
1174     if (v2d->keepzoom & V2D_KEEPASPECT) {
1175       if (fabsf(dx) > fabsf(dy)) {
1176         dy = dx;
1177       }
1178       else {
1179         dx = dy;
1180       }
1181     }
1182
1183     /* set transform amount, and add current deltas to stored total delta (for redo) */
1184     RNA_float_set(op->ptr, "deltax", dx);
1185     RNA_float_set(op->ptr, "deltay", dy);
1186
1187     vzd->dx += dx;
1188     vzd->dy += dy;
1189
1190     /* Store mouse coordinates for next time, if not doing continuous zoom:
1191      * - Continuous zoom only depends on distance of mouse
1192      *   to starting point to determine rate of change.
1193      */
1194     if (U.viewzoom != USER_ZOOM_CONT) {  // XXX store this setting as RNA prop?
1195       vzd->lastx = event->x;
1196       vzd->lasty = event->y;
1197     }
1198
1199     /* apply zooming */
1200     view_zoomdrag_apply(C, op);
1201   }
1202   else if (event->type == vzd->invoke_event || event->type == ESCKEY) {
1203     if (event->val == KM_RELEASE) {
1204
1205       /* for redo, store the overall deltas - need to respect zoom-locks here... */
1206       if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1207         RNA_float_set(op->ptr, "deltax", vzd->dx);
1208       }
1209       else {
1210         RNA_float_set(op->ptr, "deltax", 0);
1211       }
1212
1213       if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1214         RNA_float_set(op->ptr, "deltay", vzd->dy);
1215       }
1216       else {
1217         RNA_float_set(op->ptr, "deltay", 0);
1218       }
1219
1220       /* free customdata */
1221       view_zoomdrag_exit(C, op);
1222       WM_cursor_modal_restore(CTX_wm_window(C));
1223
1224       return OPERATOR_FINISHED;
1225     }
1226   }
1227
1228   return OPERATOR_RUNNING_MODAL;
1229 }
1230
1231 static void VIEW2D_OT_zoom(wmOperatorType *ot)
1232 {
1233   PropertyRNA *prop;
1234   /* identifiers */
1235   ot->name = "Zoom 2D View";
1236   ot->description = "Zoom in/out the view";
1237   ot->idname = "VIEW2D_OT_zoom";
1238
1239   /* api callbacks */
1240   ot->exec = view_zoomdrag_exec;
1241   ot->invoke = view_zoomdrag_invoke;
1242   ot->modal = view_zoomdrag_modal;
1243   ot->cancel = view_zoomdrag_cancel;
1244
1245   ot->poll = view_zoom_poll;
1246
1247   /* operator is repeatable */
1248   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
1249
1250   /* rna - must keep these in sync with the other operators */
1251   prop = RNA_def_float(ot->srna, "deltax", 0, -FLT_MAX, FLT_MAX, "Delta X", "", -FLT_MAX, FLT_MAX);
1252   RNA_def_property_flag(prop, PROP_HIDDEN);
1253   prop = RNA_def_float(ot->srna, "deltay", 0, -FLT_MAX, FLT_MAX, "Delta Y", "", -FLT_MAX, FLT_MAX);
1254   RNA_def_property_flag(prop, PROP_HIDDEN);
1255
1256   WM_operator_properties_use_cursor_init(ot);
1257 }
1258
1259 /* ********************************************************* */
1260 /* BORDER-ZOOM */
1261
1262 /**
1263  * The user defines a rect using standard box select tools, and we use this rect to
1264  * define the new zoom-level of the view in the following ways:
1265  *
1266  * -# LEFTMOUSE - zoom in to view
1267  * -# RIGHTMOUSE - zoom out of view
1268  *
1269  * Currently, these key mappings are hardcoded, but it shouldn't be too important to
1270  * have custom keymappings for this...
1271  */
1272
1273 static int view_borderzoom_exec(bContext *C, wmOperator *op)
1274 {
1275   ARegion *ar = CTX_wm_region(C);
1276   View2D *v2d = &ar->v2d;
1277   rctf rect;
1278   rctf cur_new = v2d->cur;
1279   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
1280
1281   /* convert coordinates of rect to 'tot' rect coordinates */
1282   WM_operator_properties_border_to_rctf(op, &rect);
1283   UI_view2d_region_to_view_rctf(v2d, &rect, &rect);
1284
1285   /* check if zooming in/out view */
1286   const bool zoom_in = !RNA_boolean_get(op->ptr, "zoom_out");
1287
1288   if (zoom_in) {
1289     /* zoom in:
1290      * - 'cur' rect will be defined by the coordinates of the border region
1291      * - just set the 'cur' rect to have the same coordinates as the border region
1292      *   if zoom is allowed to be changed
1293      */
1294     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1295       cur_new.xmin = rect.xmin;
1296       cur_new.xmax = rect.xmax;
1297     }
1298     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1299       cur_new.ymin = rect.ymin;
1300       cur_new.ymax = rect.ymax;
1301     }
1302   }
1303   else {
1304     /* zoom out:
1305      * - the current 'cur' rect coordinates are going to end up where the 'rect' ones are,
1306      *   but the 'cur' rect coordinates will need to be adjusted to take in more of the view
1307      * - calculate zoom factor, and adjust using center-point
1308      */
1309     float zoom, center, size;
1310
1311     /* TODO: is this zoom factor calculation valid?
1312      * It seems to produce same results every time... */
1313     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
1314       size = BLI_rctf_size_x(&cur_new);
1315       zoom = size / BLI_rctf_size_x(&rect);
1316       center = BLI_rctf_cent_x(&cur_new);
1317
1318       cur_new.xmin = center - (size * zoom);
1319       cur_new.xmax = center + (size * zoom);
1320     }
1321     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
1322       size = BLI_rctf_size_y(&cur_new);
1323       zoom = size / BLI_rctf_size_y(&rect);
1324       center = BLI_rctf_cent_y(&cur_new);
1325
1326       cur_new.ymin = center - (size * zoom);
1327       cur_new.ymax = center + (size * zoom);
1328     }
1329   }
1330
1331   UI_view2d_smooth_view(C, ar, &cur_new, smooth_viewtx);
1332
1333   return OPERATOR_FINISHED;
1334 }
1335
1336 static void VIEW2D_OT_zoom_border(wmOperatorType *ot)
1337 {
1338   /* identifiers */
1339   ot->name = "Zoom to Border";
1340   ot->description = "Zoom in the view to the nearest item contained in the border";
1341   ot->idname = "VIEW2D_OT_zoom_border";
1342
1343   /* api callbacks */
1344   ot->invoke = WM_gesture_box_invoke;
1345   ot->exec = view_borderzoom_exec;
1346   ot->modal = WM_gesture_box_modal;
1347   ot->cancel = WM_gesture_box_cancel;
1348
1349   ot->poll = view_zoom_poll;
1350
1351   /* rna */
1352   WM_operator_properties_gesture_box_zoom(ot);
1353 }
1354
1355 #ifdef WITH_INPUT_NDOF
1356 static int view2d_ndof_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1357 {
1358   if (event->type != NDOF_MOTION) {
1359     return OPERATOR_CANCELLED;
1360   }
1361   else {
1362     const wmNDOFMotionData *ndof = event->customdata;
1363
1364     /* tune these until it feels right */
1365     const float zoom_sensitivity = 0.5f;
1366     const float speed = 10.0f; /* match view3d ortho */
1367     const bool has_translate = (ndof->tvec[0] && ndof->tvec[1]) && view_pan_poll(C);
1368     const bool has_zoom = (ndof->tvec[2] != 0.0f) && view_zoom_poll(C);
1369
1370     if (has_translate) {
1371       if (view_pan_init(C, op)) {
1372         v2dViewPanData *vpd;
1373         float pan_vec[3];
1374
1375         WM_event_ndof_pan_get(ndof, pan_vec, false);
1376
1377         pan_vec[0] *= speed;
1378         pan_vec[1] *= speed;
1379
1380         vpd = op->customdata;
1381
1382         view_pan_apply_ex(C, vpd, pan_vec[0], pan_vec[1]);
1383
1384         view_pan_exit(op);
1385       }
1386     }
1387
1388     if (has_zoom) {
1389       if (view_zoomdrag_init(C, op)) {
1390         v2dViewZoomData *vzd;
1391         float zoom_factor = zoom_sensitivity * ndof->dt * -ndof->tvec[2];
1392
1393         bool do_zoom_xy[2];
1394
1395         if (U.ndof_flag & NDOF_ZOOM_INVERT) {
1396           zoom_factor = -zoom_factor;
1397         }
1398
1399         view_zoom_axis_lock_defaults(C, do_zoom_xy);
1400
1401         vzd = op->customdata;
1402
1403         view_zoomstep_apply_ex(
1404             C, vzd, false, do_zoom_xy[0] ? zoom_factor : 0.0f, do_zoom_xy[1] ? zoom_factor : 0.0f);
1405
1406         view_zoomstep_exit(op);
1407       }
1408     }
1409
1410     return OPERATOR_FINISHED;
1411   }
1412 }
1413
1414 static void VIEW2D_OT_ndof(wmOperatorType *ot)
1415 {
1416   /* identifiers */
1417   ot->name = "NDOF Pan/Zoom";
1418   ot->idname = "VIEW2D_OT_ndof";
1419   ot->description = "Use a 3D mouse device to pan/zoom the view";
1420
1421   /* api callbacks */
1422   ot->invoke = view2d_ndof_invoke;
1423   ot->poll = view2d_poll;
1424
1425   /* flags */
1426   ot->flag = OPTYPE_LOCK_BYPASS;
1427 }
1428 #endif /* WITH_INPUT_NDOF */
1429
1430 /* ********************************************************* */
1431 /* SMOOTH VIEW */
1432
1433 struct SmoothView2DStore {
1434   rctf orig_cur, new_cur;
1435
1436   double time_allowed;
1437 };
1438
1439 /**
1440  * function to get a factor out of a rectangle
1441  *
1442  * note: this doesn't always work as well as it might because the target size
1443  *       may not be reached because of clamping the desired rect, we _could_
1444  *       attempt to clamp the rect before working out the zoom factor but its
1445  *       not really worthwhile for the few cases this happens.
1446  */
1447 static float smooth_view_rect_to_fac(const rctf *rect_a, const rctf *rect_b)
1448 {
1449   const float size_a[2] = {BLI_rctf_size_x(rect_a), BLI_rctf_size_y(rect_a)};
1450   const float size_b[2] = {BLI_rctf_size_x(rect_b), BLI_rctf_size_y(rect_b)};
1451   const float cent_a[2] = {BLI_rctf_cent_x(rect_a), BLI_rctf_cent_y(rect_a)};
1452   const float cent_b[2] = {BLI_rctf_cent_x(rect_b), BLI_rctf_cent_y(rect_b)};
1453
1454   float fac_max = 0.0f;
1455   float tfac;
1456
1457   int i;
1458
1459   for (i = 0; i < 2; i++) {
1460     /* axis translation normalized to scale */
1461     tfac = fabsf(cent_a[i] - cent_b[i]) / min_ff(size_a[i], size_b[i]);
1462     fac_max = max_ff(fac_max, tfac);
1463     if (fac_max >= 1.0f) {
1464       break;
1465     }
1466
1467     /* axis scale difference, x2 so doubling or half gives 1.0f */
1468     tfac = (1.0f - (min_ff(size_a[i], size_b[i]) / max_ff(size_a[i], size_b[i]))) * 2.0f;
1469     fac_max = max_ff(fac_max, tfac);
1470     if (fac_max >= 1.0f) {
1471       break;
1472     }
1473   }
1474   return min_ff(fac_max, 1.0f);
1475 }
1476
1477 /* will start timer if appropriate */
1478 /* the arguments are the desired situation */
1479 void UI_view2d_smooth_view(bContext *C, ARegion *ar, const rctf *cur, const int smooth_viewtx)
1480 {
1481   wmWindowManager *wm = CTX_wm_manager(C);
1482   wmWindow *win = CTX_wm_window(C);
1483
1484   View2D *v2d = &ar->v2d;
1485   struct SmoothView2DStore sms = {{0}};
1486   bool ok = false;
1487   float fac = 1.0f;
1488
1489   /* initialize sms */
1490   sms.new_cur = v2d->cur;
1491
1492   /* store the options we want to end with */
1493   if (cur) {
1494     sms.new_cur = *cur;
1495   }
1496
1497   if (cur) {
1498     fac = smooth_view_rect_to_fac(&v2d->cur, cur);
1499   }
1500
1501   if (smooth_viewtx && fac > FLT_EPSILON) {
1502     bool changed = false;
1503
1504     if (BLI_rctf_compare(&sms.new_cur, &v2d->cur, FLT_EPSILON) == false) {
1505       changed = true;
1506     }
1507
1508     /* The new view is different from the old one
1509      * so animate the view */
1510     if (changed) {
1511       sms.orig_cur = v2d->cur;
1512
1513       sms.time_allowed = (double)smooth_viewtx / 1000.0;
1514
1515       /* scale the time allowed the change in view */
1516       sms.time_allowed *= (double)fac;
1517
1518       /* keep track of running timer! */
1519       if (v2d->sms == NULL) {
1520         v2d->sms = MEM_mallocN(sizeof(struct SmoothView2DStore), "smoothview v2d");
1521       }
1522       *v2d->sms = sms;
1523       if (v2d->smooth_timer) {
1524         WM_event_remove_timer(wm, win, v2d->smooth_timer);
1525       }
1526       /* TIMER1 is hardcoded in keymap */
1527       /* max 30 frs/sec */
1528       v2d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0);
1529
1530       ok = true;
1531     }
1532   }
1533
1534   /* if we get here nothing happens */
1535   if (ok == false) {
1536     v2d->cur = sms.new_cur;
1537
1538     UI_view2d_curRect_validate(v2d);
1539     ED_region_tag_redraw_no_rebuild(ar);
1540     UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1541   }
1542 }
1543
1544 /* only meant for timer usage */
1545 static int view2d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1546 {
1547   ARegion *ar = CTX_wm_region(C);
1548   View2D *v2d = &ar->v2d;
1549   struct SmoothView2DStore *sms = v2d->sms;
1550   float step;
1551
1552   /* escape if not our timer */
1553   if (v2d->smooth_timer == NULL || v2d->smooth_timer != event->customdata) {
1554     return OPERATOR_PASS_THROUGH;
1555   }
1556
1557   if (sms->time_allowed != 0.0) {
1558     step = (float)((v2d->smooth_timer->duration) / sms->time_allowed);
1559   }
1560   else {
1561     step = 1.0f;
1562   }
1563
1564   /* end timer */
1565   if (step >= 1.0f) {
1566     v2d->cur = sms->new_cur;
1567
1568     MEM_freeN(v2d->sms);
1569     v2d->sms = NULL;
1570
1571     WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), v2d->smooth_timer);
1572     v2d->smooth_timer = NULL;
1573
1574     /* Event handling won't know if a UI item has been moved under the pointer. */
1575     WM_event_add_mousemove(C);
1576   }
1577   else {
1578     /* ease in/out */
1579     step = (3.0f * step * step - 2.0f * step * step * step);
1580
1581     BLI_rctf_interp(&v2d->cur, &sms->orig_cur, &sms->new_cur, step);
1582   }
1583
1584   UI_view2d_curRect_validate(v2d);
1585   UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1586   ED_region_tag_redraw_no_rebuild(ar);
1587
1588   if (v2d->sms == NULL) {
1589     UI_view2d_zoom_cache_reset();
1590   }
1591
1592   return OPERATOR_FINISHED;
1593 }
1594
1595 static void VIEW2D_OT_smoothview(wmOperatorType *ot)
1596 {
1597   /* identifiers */
1598   ot->name = "Smooth View 2D";
1599   ot->idname = "VIEW2D_OT_smoothview";
1600
1601   /* api callbacks */
1602   ot->invoke = view2d_smoothview_invoke;
1603   ot->poll = view2d_poll;
1604
1605   /* flags */
1606   ot->flag = OPTYPE_INTERNAL;
1607
1608   /* rna */
1609   WM_operator_properties_gesture_box(ot);
1610 }
1611
1612 /* ********************************************************* */
1613 /* SCROLLERS */
1614
1615 /**
1616  * Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
1617  * -# 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable,
1618  *    enlarge 'cur' rect on the relevant side.
1619  * -# 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite).
1620  *
1621  * In order to make sure this works, each operator must define the following RNA-Operator Props:
1622  * - `deltax, deltay` - define how much to move view by (relative to zoom-correction factor)
1623  */
1624
1625 /* customdata for scroller-invoke data */
1626 typedef struct v2dScrollerMove {
1627   /** View2D data that this operation affects */
1628   View2D *v2d;
1629   /** region that the scroller is in */
1630   ARegion *ar;
1631
1632   /** scroller that mouse is in ('h' or 'v') */
1633   char scroller;
1634
1635   /* XXX find some way to provide visual feedback of this (active color?) */
1636   /** -1 is min zoomer, 0 is bar, 1 is max zoomer */
1637   short zone;
1638
1639   /** view adjustment factor, based on size of region */
1640   float fac;
1641   /** for pixel rounding (avoid visible UI jitter) */
1642   float fac_round;
1643   /** amount moved by mouse on axis of interest */
1644   float delta;
1645
1646   /** width of the scrollbar itself, used for page up/down clicks */
1647   float scrollbarwidth;
1648   /** initial location of scrollbar x/y, mouse relative */
1649   int scrollbar_orig;
1650
1651   /** previous mouse coordinates (in screen coordinates) for determining movement */
1652   int lastx, lasty;
1653 } v2dScrollerMove;
1654
1655 /**
1656  * #View2DScrollers is typedef'd in UI_view2d.h
1657  * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c,
1658  * as we only need focus bubble info.
1659  *
1660  * \warning: The start of this struct must not change,
1661  * so that it stays in sync with the 'real' version.
1662  * For now, we don't need to have a separate (internal) header for structs like this...
1663  */
1664 struct View2DScrollers {
1665   /* focus bubbles */
1666   int vert_min, vert_max; /* vertical scrollbar */
1667   int hor_min, hor_max;   /* horizontal scrollbar */
1668 };
1669
1670 /* quick enum for vsm->zone (scroller handles) */
1671 enum {
1672   SCROLLHANDLE_MIN = -1,
1673   SCROLLHANDLE_BAR,
1674   SCROLLHANDLE_MAX,
1675   SCROLLHANDLE_MIN_OUTSIDE,
1676   SCROLLHANDLE_MAX_OUTSIDE,
1677 } /*eV2DScrollerHandle_Zone*/;
1678
1679 /* ------------------------ */
1680
1681 /**
1682  * Check if mouse is within scroller handle.
1683  *
1684  * \param mouse: relevant mouse coordinate in region space.
1685  * \param sc_min, sc_max: extents of scroller 'groove' (potential available space for scroller).
1686  * \param sh_min, sh_max: positions of scrollbar handles.
1687  */
1688 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
1689 {
1690   bool in_min, in_max, in_bar, out_min, out_max, in_view = 1;
1691
1692   /* firstly, check if
1693    * - 'bubble' fills entire scroller
1694    * - 'bubble' completely out of view on either side
1695    */
1696   if ((sh_min <= sc_min) && (sh_max >= sc_max)) {
1697     in_view = 0;
1698   }
1699   if (sh_min == sh_max) {
1700     if (sh_min <= sc_min) {
1701       in_view = 0;
1702     }
1703     if (sh_max >= sc_max) {
1704       in_view = 0;
1705     }
1706   }
1707   else {
1708     if (sh_max <= sc_min) {
1709       in_view = 0;
1710     }
1711     if (sh_min >= sc_max) {
1712       in_view = 0;
1713     }
1714   }
1715
1716   if (in_view == 0) {
1717     return SCROLLHANDLE_BAR;
1718   }
1719
1720   /* check if mouse is in or past either handle */
1721   /* TODO: check if these extents are still valid or not */
1722   in_max = ((mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) &&
1723             (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)));
1724   in_min = ((mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) &&
1725             (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)));
1726   in_bar = ((mouse < (sh_max - V2D_SCROLLER_HANDLE_SIZE)) &&
1727             (mouse > (sh_min + V2D_SCROLLER_HANDLE_SIZE)));
1728   out_min = mouse < (sh_min - V2D_SCROLLER_HANDLE_SIZE);
1729   out_max = mouse > (sh_max + V2D_SCROLLER_HANDLE_SIZE);
1730
1731   if (in_bar) {
1732     return SCROLLHANDLE_BAR;
1733   }
1734   else if (in_max) {
1735     return SCROLLHANDLE_MAX;
1736   }
1737   else if (in_min) {
1738     return SCROLLHANDLE_MIN;
1739   }
1740   else if (out_min) {
1741     return SCROLLHANDLE_MIN_OUTSIDE;
1742   }
1743   else if (out_max) {
1744     return SCROLLHANDLE_MAX_OUTSIDE;
1745   }
1746
1747   /* unlikely to happen, though we just cover it in case */
1748   return SCROLLHANDLE_BAR;
1749 }
1750
1751 static bool scroller_activate_poll(bContext *C)
1752 {
1753   if (!view2d_poll(C)) {
1754     return false;
1755   }
1756
1757   wmWindow *win = CTX_wm_window(C);
1758   ARegion *ar = CTX_wm_region(C);
1759   View2D *v2d = &ar->v2d;
1760   wmEvent *event = win->eventstate;
1761
1762   /* check if mouse in scrollbars, if they're enabled */
1763   return (UI_view2d_mouse_in_scrollers(ar, v2d, event->x, event->y) != 0);
1764 }
1765
1766 /* initialize customdata for scroller manipulation operator */
1767 static void scroller_activate_init(bContext *C,
1768                                    wmOperator *op,
1769                                    const wmEvent *event,
1770                                    const char in_scroller)
1771 {
1772   v2dScrollerMove *vsm;
1773   View2DScrollers *scrollers;
1774   ARegion *ar = CTX_wm_region(C);
1775   View2D *v2d = &ar->v2d;
1776   rctf tot_cur_union;
1777   float mask_size;
1778
1779   /* set custom-data for operator */
1780   vsm = MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
1781   op->customdata = vsm;
1782
1783   /* set general data */
1784   vsm->v2d = v2d;
1785   vsm->ar = ar;
1786   vsm->scroller = in_scroller;
1787
1788   /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
1789   vsm->lastx = event->x;
1790   vsm->lasty = event->y;
1791   /* 'zone' depends on where mouse is relative to bubble
1792    * - zooming must be allowed on this axis, otherwise, default to pan
1793    */
1794   scrollers = UI_view2d_scrollers_calc(v2d, NULL);
1795
1796   /* use a union of 'cur' & 'tot' incase the current view is far outside 'tot'. In this cases
1797    * moving the scroll bars has far too little effect and the view can get stuck T31476. */
1798   tot_cur_union = v2d->tot;
1799   BLI_rctf_union(&tot_cur_union, &v2d->cur);
1800
1801   if (in_scroller == 'h') {
1802     /* horizontal scroller - calculate adjustment factor first */
1803     mask_size = (float)BLI_rcti_size_x(&v2d->hor);
1804     vsm->fac = BLI_rctf_size_x(&tot_cur_union) / mask_size;
1805
1806     /* pixel rounding */
1807     vsm->fac_round = (BLI_rctf_size_x(&v2d->cur)) / (float)(BLI_rcti_size_x(&ar->winrct) + 1);
1808
1809     /* get 'zone' (i.e. which part of scroller is activated) */
1810     vsm->zone = mouse_in_scroller_handle(
1811         event->mval[0], v2d->hor.xmin, v2d->hor.xmax, scrollers->hor_min, scrollers->hor_max);
1812
1813     if ((v2d->keepzoom & V2D_LOCKZOOM_X) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1814       /* default to scroll, as handles not usable */
1815       vsm->zone = SCROLLHANDLE_BAR;
1816     }
1817
1818     vsm->scrollbarwidth = scrollers->hor_max - scrollers->hor_min;
1819     vsm->scrollbar_orig = ((scrollers->hor_max + scrollers->hor_min) / 2) + ar->winrct.xmin;
1820   }
1821   else {
1822     /* vertical scroller - calculate adjustment factor first */
1823     mask_size = (float)BLI_rcti_size_y(&v2d->vert);
1824     vsm->fac = BLI_rctf_size_y(&tot_cur_union) / mask_size;
1825
1826     /* pixel rounding */
1827     vsm->fac_round = (BLI_rctf_size_y(&v2d->cur)) / (float)(BLI_rcti_size_y(&ar->winrct) + 1);
1828
1829     /* get 'zone' (i.e. which part of scroller is activated) */
1830     vsm->zone = mouse_in_scroller_handle(
1831         event->mval[1], v2d->vert.ymin, v2d->vert.ymax, scrollers->vert_min, scrollers->vert_max);
1832
1833     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1834       /* default to scroll, as handles not usable */
1835       vsm->zone = SCROLLHANDLE_BAR;
1836     }
1837
1838     vsm->scrollbarwidth = scrollers->vert_max - scrollers->vert_min;
1839     vsm->scrollbar_orig = ((scrollers->vert_max + scrollers->vert_min) / 2) + ar->winrct.ymin;
1840   }
1841
1842   UI_view2d_scrollers_free(scrollers);
1843   ED_region_tag_redraw_no_rebuild(ar);
1844 }
1845
1846 /* cleanup temp customdata  */
1847 static void scroller_activate_exit(bContext *C, wmOperator *op)
1848 {
1849   if (op->customdata) {
1850     v2dScrollerMove *vsm = op->customdata;
1851
1852     vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE);
1853
1854     MEM_freeN(op->customdata);
1855     op->customdata = NULL;
1856
1857     ED_region_tag_redraw_no_rebuild(CTX_wm_region(C));
1858   }
1859 }
1860
1861 static void scroller_activate_cancel(bContext *C, wmOperator *op)
1862 {
1863   scroller_activate_exit(C, op);
1864 }
1865
1866 /* apply transform to view (i.e. adjust 'cur' rect) */
1867 static void scroller_activate_apply(bContext *C, wmOperator *op)
1868 {
1869   v2dScrollerMove *vsm = op->customdata;
1870   View2D *v2d = vsm->v2d;
1871   float temp;
1872
1873   /* calculate amount to move view by */
1874   temp = vsm->fac * vsm->delta;
1875
1876   /* round to pixel */
1877   temp = roundf(temp / vsm->fac_round) * vsm->fac_round;
1878
1879   /* type of movement */
1880   switch (vsm->zone) {
1881     case SCROLLHANDLE_MIN:
1882       /* only expand view on axis if zoom is allowed */
1883       if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) {
1884         v2d->cur.xmin -= temp;
1885       }
1886       if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) {
1887         v2d->cur.ymin -= temp;
1888       }
1889       break;
1890
1891     case SCROLLHANDLE_MAX:
1892
1893       /* only expand view on axis if zoom is allowed */
1894       if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) {
1895         v2d->cur.xmax += temp;
1896       }
1897       if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) {
1898         v2d->cur.ymax += temp;
1899       }
1900       break;
1901
1902     case SCROLLHANDLE_MIN_OUTSIDE:
1903     case SCROLLHANDLE_MAX_OUTSIDE:
1904     case SCROLLHANDLE_BAR:
1905     default:
1906       /* only move view on an axis if panning is allowed */
1907       if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1908         v2d->cur.xmin += temp;
1909         v2d->cur.xmax += temp;
1910       }
1911       if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1912         v2d->cur.ymin += temp;
1913         v2d->cur.ymax += temp;
1914       }
1915       break;
1916   }
1917
1918   /* validate that view is in valid configuration after this operation */
1919   UI_view2d_curRect_validate(v2d);
1920
1921   /* request updates to be done... */
1922   ED_region_tag_redraw_no_rebuild(vsm->ar);
1923   UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1924 }
1925
1926 /**
1927  * Handle user input for scrollers - calculations of mouse-movement need to be done here,
1928  * not in the apply callback!
1929  */
1930 static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *event)
1931 {
1932   v2dScrollerMove *vsm = op->customdata;
1933
1934   /* execute the events */
1935   switch (event->type) {
1936     case MOUSEMOVE: {
1937       /* calculate new delta transform, then store mouse-coordinates for next-time */
1938       if (ELEM(vsm->zone, SCROLLHANDLE_BAR, SCROLLHANDLE_MAX)) {
1939         /* if using bar (i.e. 'panning') or 'max' zoom widget */
1940         switch (vsm->scroller) {
1941           case 'h': /* horizontal scroller - so only horizontal movement
1942                      * ('cur' moves opposite to mouse) */
1943             vsm->delta = (float)(event->x - vsm->lastx);
1944             break;
1945           case 'v': /* vertical scroller - so only vertical movement
1946                      * ('cur' moves opposite to mouse) */
1947             vsm->delta = (float)(event->y - vsm->lasty);
1948             break;
1949         }
1950       }
1951       else if (vsm->zone == SCROLLHANDLE_MIN) {
1952         /* using 'min' zoom widget */
1953         switch (vsm->scroller) {
1954           case 'h': /* horizontal scroller - so only horizontal movement
1955                      * ('cur' moves with mouse) */
1956             vsm->delta = (float)(vsm->lastx - event->x);
1957             break;
1958           case 'v': /* vertical scroller - so only vertical movement
1959                      * ('cur' moves with to mouse) */
1960             vsm->delta = (float)(vsm->lasty - event->y);
1961             break;
1962         }
1963       }
1964
1965       /* store previous coordinates */
1966       vsm->lastx = event->x;
1967       vsm->lasty = event->y;
1968
1969       scroller_activate_apply(C, op);
1970       break;
1971     }
1972     case LEFTMOUSE:
1973     case MIDDLEMOUSE:
1974       if (event->val == KM_RELEASE) {
1975         /* single-click was in empty space outside bubble, so scroll by 1 'page' */
1976         if (ELEM(vsm->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) {
1977           if (vsm->zone == SCROLLHANDLE_MIN_OUTSIDE) {
1978             vsm->delta = -vsm->scrollbarwidth * 0.8f;
1979           }
1980           else if (vsm->zone == SCROLLHANDLE_MAX_OUTSIDE) {
1981             vsm->delta = vsm->scrollbarwidth * 0.8f;
1982           }
1983
1984           scroller_activate_apply(C, op);
1985           scroller_activate_exit(C, op);
1986           return OPERATOR_FINISHED;
1987         }
1988
1989         /* otherwise, end the drag action  */
1990         if (vsm->lastx || vsm->lasty) {
1991           scroller_activate_exit(C, op);
1992           return OPERATOR_FINISHED;
1993         }
1994       }
1995       break;
1996   }
1997
1998   return OPERATOR_RUNNING_MODAL;
1999 }
2000
2001 /* a click (or click drag in progress)
2002  * should have occurred, so check if it happened in scrollbar */
2003 static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2004 {
2005   ARegion *ar = CTX_wm_region(C);
2006   View2D *v2d = &ar->v2d;
2007
2008   /* check if mouse in scrollbars, if they're enabled */
2009   const char in_scroller = UI_view2d_mouse_in_scrollers(ar, v2d, event->x, event->y);
2010
2011   /* if in a scroller, init customdata then set modal handler which will
2012    * catch mousedown to start doing useful stuff */
2013   if (in_scroller) {
2014     v2dScrollerMove *vsm;
2015
2016     /* initialize customdata */
2017     scroller_activate_init(C, op, event, in_scroller);
2018     vsm = (v2dScrollerMove *)op->customdata;
2019
2020     /* support for quick jump to location - gtk and qt do this on linux */
2021     if (event->type == MIDDLEMOUSE) {
2022       switch (vsm->scroller) {
2023         case 'h': /* horizontal scroller - so only horizontal movement
2024                    * ('cur' moves opposite to mouse) */
2025           vsm->delta = (float)(event->x - vsm->scrollbar_orig);
2026           break;
2027         case 'v': /* vertical scroller - so only vertical movement
2028                    * ('cur' moves opposite to mouse) */
2029           vsm->delta = (float)(event->y - vsm->scrollbar_orig);
2030           break;
2031       }
2032       scroller_activate_apply(C, op);
2033
2034       vsm->zone = SCROLLHANDLE_BAR;
2035     }
2036
2037     /* check if zoom zones are inappropriate (i.e. zoom widgets not shown), so cannot continue
2038      * NOTE: see view2d.c for latest conditions, and keep this in sync with that
2039      */
2040     if (ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
2041       if (((vsm->scroller == 'h') && (v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) == 0) ||
2042           ((vsm->scroller == 'v') && (v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) == 0)) {
2043         /* switch to bar (i.e. no scaling gets handled) */
2044         vsm->zone = SCROLLHANDLE_BAR;
2045       }
2046     }
2047
2048     /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */
2049     if (vsm->zone == SCROLLHANDLE_BAR) {
2050       if (((vsm->scroller == 'h') && (v2d->keepofs & V2D_LOCKOFS_X)) ||
2051           ((vsm->scroller == 'v') && (v2d->keepofs & V2D_LOCKOFS_Y))) {
2052         /* free customdata initialized */
2053         scroller_activate_exit(C, op);
2054
2055         /* can't catch this event for ourselves, so let it go to someone else? */
2056         return OPERATOR_PASS_THROUGH;
2057       }
2058     }
2059
2060     /* zone is also inappropriate if scroller is not visible... */
2061     if (((vsm->scroller == 'h') && (v2d->scroll & (V2D_SCROLL_HORIZONTAL_FULLR))) ||
2062         ((vsm->scroller == 'v') && (v2d->scroll & (V2D_SCROLL_VERTICAL_FULLR)))) {
2063       /* free customdata initialized */
2064       scroller_activate_exit(C, op);
2065
2066       /* can't catch this event for ourselves, so let it go to someone else? */
2067       /* XXX note: if handlers use mask rect to clip input, input will fail for this case */
2068       return OPERATOR_PASS_THROUGH;
2069     }
2070
2071     /* activate the scroller */
2072     if (vsm->scroller == 'h') {
2073       v2d->scroll_ui |= V2D_SCROLL_H_ACTIVE;
2074     }
2075     else {
2076       v2d->scroll_ui |= V2D_SCROLL_V_ACTIVE;
2077     }
2078
2079     /* still ok, so can add */
2080     WM_event_add_modal_handler(C, op);
2081     return OPERATOR_RUNNING_MODAL;
2082   }
2083   else {
2084     /* not in scroller, so nothing happened...
2085      * (pass through let's something else catch event) */
2086     return OPERATOR_PASS_THROUGH;
2087   }
2088 }
2089
2090 /* LMB-Drag in Scrollers - not repeatable operator! */
2091 static void VIEW2D_OT_scroller_activate(wmOperatorType *ot)
2092 {
2093   /* identifiers */
2094   ot->name = "Scroller Activate";
2095   ot->description = "Scroll view by mouse click and drag";
2096   ot->idname = "VIEW2D_OT_scroller_activate";
2097
2098   /* flags */
2099   ot->flag = OPTYPE_BLOCKING;
2100
2101   /* api callbacks */
2102   ot->invoke = scroller_activate_invoke;
2103   ot->modal = scroller_activate_modal;
2104   ot->cancel = scroller_activate_cancel;
2105
2106   ot->poll = scroller_activate_poll;
2107 }
2108
2109 /* ********************************************************* */
2110 /* RESET */
2111
2112 static int reset_exec(bContext *C, wmOperator *UNUSED(op))
2113 {
2114   uiStyle *style = UI_style_get();
2115   ARegion *ar = CTX_wm_region(C);
2116   View2D *v2d = &ar->v2d;
2117   int winx, winy;
2118   const int snap_test = ED_region_snap_size_test(ar);
2119
2120   /* zoom 1.0 */
2121   winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1);
2122   winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1);
2123
2124   v2d->cur.xmax = v2d->cur.xmin + winx;
2125   v2d->cur.ymax = v2d->cur.ymin + winy;
2126
2127   /* align */
2128   if (v2d->align) {
2129     /* posx and negx flags are mutually exclusive, so watch out */
2130     if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
2131       v2d->cur.xmax = 0.0f;
2132       v2d->cur.xmin = -winx * style->panelzoom;
2133     }
2134     else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
2135       v2d->cur.xmax = winx * style->panelzoom;
2136       v2d->cur.xmin = 0.0f;
2137     }
2138
2139     /* - posx and negx flags are mutually exclusive, so watch out */
2140     if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
2141       v2d->cur.ymax = 0.0f;
2142       v2d->cur.ymin = -winy * style->panelzoom;
2143     }
2144     else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
2145       v2d->cur.ymax = winy * style->panelzoom;
2146       v2d->cur.ymin = 0.0f;
2147     }
2148   }
2149
2150   /* validate that view is in valid configuration after this operation */
2151   UI_view2d_curRect_validate(v2d);
2152
2153   if (ED_region_snap_size_apply(ar, snap_test)) {
2154     ScrArea *sa = CTX_wm_area(C);
2155     ED_area_tag_redraw(sa);
2156     WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL);
2157   }
2158
2159   /* request updates to be done... */
2160   ED_region_tag_redraw(ar);
2161   UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
2162
2163   UI_view2d_zoom_cache_reset();
2164
2165   return OPERATOR_FINISHED;
2166 }
2167
2168 static void VIEW2D_OT_reset(wmOperatorType *ot)
2169 {
2170   /* identifiers */
2171   ot->name = "Reset View";
2172   ot->description = "Reset the view";
2173   ot->idname = "VIEW2D_OT_reset";
2174
2175   /* api callbacks */
2176   ot->exec = reset_exec;
2177   ot->poll = view2d_poll;
2178 }
2179
2180 /* ********************************************************* */
2181 /* Registration */
2182
2183 void ED_operatortypes_view2d(void)
2184 {
2185   WM_operatortype_append(VIEW2D_OT_pan);
2186
2187   WM_operatortype_append(VIEW2D_OT_scroll_left);
2188   WM_operatortype_append(VIEW2D_OT_scroll_right);
2189   WM_operatortype_append(VIEW2D_OT_scroll_up);
2190   WM_operatortype_append(VIEW2D_OT_scroll_down);
2191
2192   WM_operatortype_append(VIEW2D_OT_zoom_in);
2193   WM_operatortype_append(VIEW2D_OT_zoom_out);
2194
2195   WM_operatortype_append(VIEW2D_OT_zoom);
2196   WM_operatortype_append(VIEW2D_OT_zoom_border);
2197
2198 #ifdef WITH_INPUT_NDOF
2199   WM_operatortype_append(VIEW2D_OT_ndof);
2200 #endif
2201
2202   WM_operatortype_append(VIEW2D_OT_smoothview);
2203
2204   WM_operatortype_append(VIEW2D_OT_scroller_activate);
2205
2206   WM_operatortype_append(VIEW2D_OT_reset);
2207 }
2208
2209 void ED_keymap_view2d(wmKeyConfig *keyconf)
2210 {
2211   WM_keymap_ensure(keyconf, "View2D", 0, 0);
2212 }