4 * ***** BEGIN GPL LICENSE BLOCK *****
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * The Original Code is Copyright (C) 2008 Blender Foundation.
21 * All rights reserved.
23 * Contributor(s): Blender Foundation, Joshua Leung
25 * ***** END GPL LICENSE BLOCK *****
28 /** \file blender/editors/interface/view2d_ops.c
29 * \ingroup edinterface
35 #include "MEM_guardedalloc.h"
37 #include "DNA_userdef_types.h"
39 #include "BLI_blenlib.h"
40 #include "BLI_utildefines.h"
42 #include "BKE_context.h"
44 #include "RNA_access.h"
45 #include "RNA_define.h"
51 #include "ED_screen.h"
53 #include "UI_view2d.h"
55 #include "PIL_time.h" /* USER_ZOOM_CONT */
57 static int view2d_poll(bContext *C)
59 ARegion *ar= CTX_wm_region(C);
61 return (ar != NULL) && (ar->v2d.flag & V2D_IS_INITIALISED);
64 /* ********************************************************* */
65 /* VIEW PANNING OPERATOR */
67 /* This group of operators come in several forms:
68 * 1) Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by
69 * 2) Scrollwheel 'steps' - rolling mousewheel by one step moves view by predefined amount
71 * In order to make sure this works, each operator must define the following RNA-Operator Props:
72 * deltax, deltay - define how much to move view by (relative to zoom-correction factor)
75 /* ------------------ Shared 'core' stuff ---------------------- */
77 /* temp customdata for operator */
78 typedef struct v2dViewPanData {
79 bScreen *sc; /* screen where view pan was initiated */
80 ScrArea *sa; /* area where view pan was initiated */
81 ARegion *ar; /* region where view pan was initiated */
82 View2D *v2d; /* view2d we're operating in */
84 float facx, facy; /* amount to move view relative to zoom */
86 /* options for version 1 */
87 int startx, starty; /* mouse x/y values in window when operator was initiated */
88 int lastx, lasty; /* previous x/y values of mouse in window */
89 int invoke_event; /* event starting pan, for modal exit */
91 short in_scroller; /* for MMB in scrollers (old feature in past, but now not that useful) */
94 /* initialise panning customdata */
95 static int view_pan_init(bContext *C, wmOperator *op)
97 ARegion *ar= CTX_wm_region(C);
102 /* regions now have v2d-data by default, so check for region */
106 /* check if panning is allowed at all */
108 if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y))
111 /* set custom-data for operator */
112 vpd= MEM_callocN(sizeof(v2dViewPanData), "v2dViewPanData");
115 /* set pointers to owners */
116 vpd->sc= CTX_wm_screen(C);
117 vpd->sa= CTX_wm_area(C);
121 /* calculate translation factor - based on size of view */
122 winx= (float)(ar->winrct.xmax - ar->winrct.xmin + 1);
123 winy= (float)(ar->winrct.ymax - ar->winrct.ymin + 1);
124 vpd->facx= (v2d->cur.xmax - v2d->cur.xmin) / winx;
125 vpd->facy= (v2d->cur.ymax - v2d->cur.ymin) / winy;
130 /* apply transform to view (i.e. adjust 'cur' rect) */
131 static void view_pan_apply(wmOperator *op)
133 v2dViewPanData *vpd= op->customdata;
134 View2D *v2d= vpd->v2d;
137 /* calculate amount to move view by */
138 dx= vpd->facx * (float)RNA_int_get(op->ptr, "deltax");
139 dy= vpd->facy * (float)RNA_int_get(op->ptr, "deltay");
141 /* only move view on an axis if change is allowed */
142 if ((v2d->keepofs & V2D_LOCKOFS_X)==0) {
146 if ((v2d->keepofs & V2D_LOCKOFS_Y)==0) {
151 /* validate that view is in valid configuration after this operation */
152 UI_view2d_curRect_validate(v2d);
154 /* request updates to be done... */
155 ED_region_tag_redraw(vpd->ar);
157 UI_view2d_sync(vpd->sc, vpd->sa, v2d, V2D_LOCK_COPY);
160 if (vpd->sa->spacetype==SPACE_OUTLINER) {
161 /* don't rebuild full tree, since we're just changing our view */
162 SpaceOops *soops= vpd->sa->spacedata.first;
163 soops->storeflag |= SO_TREESTORE_REDRAW;
167 /* cleanup temp customdata */
168 static void view_pan_exit(wmOperator *op)
170 if (op->customdata) {
171 MEM_freeN(op->customdata);
172 op->customdata= NULL;
176 /* ------------------ Modal Drag Version (1) ---------------------- */
178 /* for 'redo' only, with no user input */
179 static int view_pan_exec(bContext *C, wmOperator *op)
181 if (!view_pan_init(C, op))
182 return OPERATOR_CANCELLED;
186 return OPERATOR_FINISHED;
189 /* set up modal operator and relevant settings */
190 static int view_pan_invoke(bContext *C, wmOperator *op, wmEvent *event)
192 wmWindow *window= CTX_wm_window(C);
196 /* set up customdata */
197 if (!view_pan_init(C, op))
198 return OPERATOR_PASS_THROUGH;
203 /* set initial settings */
204 vpd->startx= vpd->lastx= event->x;
205 vpd->starty= vpd->lasty= event->y;
206 vpd->invoke_event= event->type;
208 if (event->type == MOUSEPAN) {
209 RNA_int_set(op->ptr, "deltax", event->prevx - event->x);
210 RNA_int_set(op->ptr, "deltay", event->prevy - event->y);
214 return OPERATOR_FINISHED;
217 RNA_int_set(op->ptr, "deltax", 0);
218 RNA_int_set(op->ptr, "deltay", 0);
220 if (v2d->keepofs & V2D_LOCKOFS_X)
221 WM_cursor_modal(window, BC_NS_SCROLLCURSOR);
222 else if (v2d->keepofs & V2D_LOCKOFS_Y)
223 WM_cursor_modal(window, BC_EW_SCROLLCURSOR);
225 WM_cursor_modal(window, BC_NSEW_SCROLLCURSOR);
227 /* add temp handler */
228 WM_event_add_modal_handler(C, op);
230 return OPERATOR_RUNNING_MODAL;
233 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
234 static int view_pan_modal(bContext *C, wmOperator *op, wmEvent *event)
236 v2dViewPanData *vpd= op->customdata;
238 /* execute the events */
239 switch (event->type) {
242 /* calculate new delta transform, then store mouse-coordinates for next-time */
243 RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x));
244 RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y));
246 vpd->lastx= event->x;
247 vpd->lasty= event->y;
255 if (event->val==KM_PRESS) {
256 /* calculate overall delta mouse-movement for redo */
257 RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
258 RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
261 WM_cursor_restore(CTX_wm_window(C));
263 WM_operator_name_call(C, "VIEW2D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
264 return OPERATOR_FINISHED;
268 if (event->type == vpd->invoke_event || event->type==ESCKEY) {
269 if (event->val==KM_RELEASE) {
270 /* calculate overall delta mouse-movement for redo */
271 RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
272 RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
275 WM_cursor_restore(CTX_wm_window(C));
277 return OPERATOR_FINISHED;
283 return OPERATOR_RUNNING_MODAL;
286 static int view_pan_cancel(bContext *UNUSED(C), wmOperator *op)
289 return OPERATOR_CANCELLED;
292 static void VIEW2D_OT_pan(wmOperatorType *ot)
295 ot->name= "Pan View";
296 ot->description= "Pan the view";
297 ot->idname= "VIEW2D_OT_pan";
300 ot->exec= view_pan_exec;
301 ot->invoke= view_pan_invoke;
302 ot->modal= view_pan_modal;
303 ot->cancel= view_pan_cancel;
305 /* operator is modal */
306 ot->flag= OPTYPE_BLOCKING|OPTYPE_GRAB_POINTER;
308 /* rna - must keep these in sync with the other operators */
309 RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
310 RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
313 /* ------------------ Scrollwheel Versions (2) ---------------------- */
315 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
316 static int view_scrollright_exec(bContext *C, wmOperator *op)
320 /* initialise default settings (and validate if ok to run) */
321 if (!view_pan_init(C, op))
322 return OPERATOR_PASS_THROUGH;
324 /* also, check if can pan in horizontal axis */
326 if (vpd->v2d->keepofs & V2D_LOCKOFS_X) {
328 return OPERATOR_PASS_THROUGH;
331 /* set RNA-Props - only movement in positive x-direction */
332 RNA_int_set(op->ptr, "deltax", 20);
333 RNA_int_set(op->ptr, "deltay", 0);
335 /* apply movement, then we're done */
339 return OPERATOR_FINISHED;
342 static void VIEW2D_OT_scroll_right(wmOperatorType *ot)
345 ot->name= "Scroll Right";
346 ot->description= "Scroll the view right";
347 ot->idname= "VIEW2D_OT_scroll_right";
350 ot->exec= view_scrollright_exec;
352 /* rna - must keep these in sync with the other operators */
353 RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
354 RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
359 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
360 static int view_scrollleft_exec(bContext *C, wmOperator *op)
364 /* initialise default settings (and validate if ok to run) */
365 if (!view_pan_init(C, op))
366 return OPERATOR_PASS_THROUGH;
368 /* also, check if can pan in horizontal axis */
370 if (vpd->v2d->keepofs & V2D_LOCKOFS_X) {
372 return OPERATOR_PASS_THROUGH;
375 /* set RNA-Props - only movement in negative x-direction */
376 RNA_int_set(op->ptr, "deltax", -20);
377 RNA_int_set(op->ptr, "deltay", 0);
379 /* apply movement, then we're done */
383 return OPERATOR_FINISHED;
386 static void VIEW2D_OT_scroll_left(wmOperatorType *ot)
389 ot->name= "Scroll Left";
390 ot->description= "Scroll the view left";
391 ot->idname= "VIEW2D_OT_scroll_left";
394 ot->exec= view_scrollleft_exec;
396 /* rna - must keep these in sync with the other operators */
397 RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
398 RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
402 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
403 static int view_scrolldown_exec(bContext *C, wmOperator *op)
407 /* initialise default settings (and validate if ok to run) */
408 if (!view_pan_init(C, op))
409 return OPERATOR_PASS_THROUGH;
411 /* also, check if can pan in vertical axis */
413 if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) {
415 return OPERATOR_PASS_THROUGH;
419 RNA_int_set(op->ptr, "deltax", 0);
420 RNA_int_set(op->ptr, "deltay", -40);
422 if(RNA_boolean_get(op->ptr, "page")) {
423 ARegion *ar= CTX_wm_region(C);
424 RNA_int_set(op->ptr, "deltay", ar->v2d.mask.ymin - ar->v2d.mask.ymax);
427 /* apply movement, then we're done */
431 return OPERATOR_FINISHED;
434 static void VIEW2D_OT_scroll_down(wmOperatorType *ot)
437 ot->name= "Scroll Down";
438 ot->description= "Scroll the view down";
439 ot->idname= "VIEW2D_OT_scroll_down";
442 ot->exec= view_scrolldown_exec;
444 /* rna - must keep these in sync with the other operators */
445 RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
446 RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
447 RNA_def_boolean(ot->srna, "page", 0, "Page", "Scroll down one page.");
452 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
453 static int view_scrollup_exec(bContext *C, wmOperator *op)
457 /* initialise default settings (and validate if ok to run) */
458 if (!view_pan_init(C, op))
459 return OPERATOR_PASS_THROUGH;
461 /* also, check if can pan in vertical axis */
463 if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) {
465 return OPERATOR_PASS_THROUGH;
469 RNA_int_set(op->ptr, "deltax", 0);
470 RNA_int_set(op->ptr, "deltay", 40);
472 if(RNA_boolean_get(op->ptr, "page")) {
473 ARegion *ar= CTX_wm_region(C);
474 RNA_int_set(op->ptr, "deltay", ar->v2d.mask.ymax - ar->v2d.mask.ymin);
477 /* apply movement, then we're done */
481 return OPERATOR_FINISHED;
484 static void VIEW2D_OT_scroll_up(wmOperatorType *ot)
487 ot->name= "Scroll Up";
488 ot->description= "Scroll the view up";
489 ot->idname= "VIEW2D_OT_scroll_up";
492 ot->exec= view_scrollup_exec;
494 /* rna - must keep these in sync with the other operators */
495 RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX);
496 RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX);
497 RNA_def_boolean(ot->srna, "page", 0, "Page", "Scroll up one page.");
500 /* ********************************************************* */
501 /* SINGLE-STEP VIEW ZOOMING OPERATOR */
503 /* This group of operators come in several forms:
504 * 1) Scrollwheel 'steps' - rolling mousewheel by one step zooms view by predefined amount
505 * 2) Scrollwheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y) // XXX this could be implemented...
506 * 3) Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount
508 * In order to make sure this works, each operator must define the following RNA-Operator Props:
509 * zoomfacx, zoomfacy - These two zoom factors allow for non-uniform scaling.
510 * It is safe to scale by 0, as these factors are used to determine
511 * amount to enlarge 'cur' by
514 /* ------------------ 'Shared' stuff ------------------------ */
516 /* temp customdata for operator */
517 typedef struct v2dViewZoomData {
518 View2D *v2d; /* view2d we're operating in */
521 /* needed for continuous zoom */
523 double timer_lastdraw;
525 int lastx, lasty; /* previous x/y values of mouse in window */
526 int invoke_event; /* event type that invoked, for modal exits */
527 float dx, dy; /* running tally of previous delta values (for obtaining final zoom) */
528 float mx_2d, my_2d; /* initial mouse location in v2d coords */
532 /* initialise panning customdata */
533 static int view_zoomdrag_init(bContext *C, wmOperator *op)
535 ARegion *ar= CTX_wm_region(C);
536 v2dViewZoomData *vzd;
539 /* regions now have v2d-data by default, so check for region */
544 /* check that 2d-view is zoomable */
545 if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
548 /* set custom-data for operator */
549 vzd= MEM_callocN(sizeof(v2dViewZoomData), "v2dViewZoomData");
552 /* set pointers to owners */
559 /* check if step-zoom can be applied */
560 static int view_zoom_poll(bContext *C)
562 ARegion *ar= CTX_wm_region(C);
565 /* check if there's a region in context to work with */
570 /* check that 2d-view is zoomable */
571 if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
574 /* view is zoomable */
578 /* apply transform to view (i.e. adjust 'cur' rect) */
579 static void view_zoomstep_apply(bContext *C, wmOperator *op)
581 v2dViewZoomData *vzd= op->customdata;
582 ARegion *ar= CTX_wm_region(C);
583 View2D *v2d= &ar->v2d;
584 float dx, dy, facx, facy;
586 /* calculate amount to move view by, ensuring symmetry so the
587 * old zoom level is restored after zooming back the same amount
589 facx= RNA_float_get(op->ptr, "zoomfacx");
590 facy= RNA_float_get(op->ptr, "zoomfacy");
593 dx= (v2d->cur.xmax - v2d->cur.xmin) * facx;
594 dy= (v2d->cur.ymax - v2d->cur.ymin) * facy;
597 dx= ((v2d->cur.xmax - v2d->cur.xmin)/(1.0f + 2.0f*facx)) * facx;
598 dy= ((v2d->cur.ymax - v2d->cur.ymin)/(1.0f + 2.0f*facy)) * facy;
601 /* only resize view on an axis if change is allowed */
602 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
603 if (v2d->keepofs & V2D_LOCKOFS_X) {
604 v2d->cur.xmax -= 2*dx;
606 else if (v2d->keepofs & V2D_KEEPOFS_X) {
607 if (v2d->align & V2D_ALIGN_NO_POS_X)
608 v2d->cur.xmin += 2*dx;
610 v2d->cur.xmax -= 2*dx;
613 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
614 float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / (v2d->cur.xmax-v2d->cur.xmin);
615 float mval_faci = 1.0f - mval_fac;
616 float ofs= (mval_fac * dx) - (mval_faci * dx);
618 v2d->cur.xmin += ofs + dx;
619 v2d->cur.xmax += ofs - dx;
627 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
628 if (v2d->keepofs & V2D_LOCKOFS_Y) {
629 v2d->cur.ymax -= 2*dy;
631 else if (v2d->keepofs & V2D_KEEPOFS_Y) {
632 if (v2d->align & V2D_ALIGN_NO_POS_Y)
633 v2d->cur.ymin += 2*dy;
635 v2d->cur.ymax -= 2*dy;
638 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
639 float mval_fac = (vzd->my_2d - v2d->cur.ymin) / (v2d->cur.ymax-v2d->cur.ymin);
640 float mval_faci = 1.0f - mval_fac;
641 float ofs= (mval_fac * dy) - (mval_faci * dy);
643 v2d->cur.ymin += ofs + dy;
644 v2d->cur.ymax += ofs - dy;
653 /* validate that view is in valid configuration after this operation */
654 UI_view2d_curRect_validate(v2d);
656 /* request updates to be done... */
657 ED_region_tag_redraw(vzd->ar);
658 UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
661 /* --------------- Individual Operators ------------------- */
663 /* cleanup temp customdata */
664 static void view_zoomstep_exit(wmOperator *op)
666 if (op->customdata) {
667 MEM_freeN(op->customdata);
668 op->customdata= NULL;
672 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
673 static int view_zoomin_exec(bContext *C, wmOperator *op)
675 /* check that there's an active region, as View2D data resides there */
676 if (!view_zoom_poll(C))
677 return OPERATOR_PASS_THROUGH;
679 /* set RNA-Props - zooming in by uniform factor */
680 RNA_float_set(op->ptr, "zoomfacx", 0.0375f);
681 RNA_float_set(op->ptr, "zoomfacy", 0.0375f);
683 /* apply movement, then we're done */
684 view_zoomstep_apply(C, op);
686 view_zoomstep_exit(op);
688 return OPERATOR_FINISHED;
691 static int view_zoomin_invoke(bContext *C, wmOperator *op, wmEvent *event)
693 v2dViewZoomData *vzd;
695 if (!view_zoomdrag_init(C, op))
696 return OPERATOR_PASS_THROUGH;
700 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
701 ARegion *ar= CTX_wm_region(C);
703 /* store initial mouse position (in view space) */
704 UI_view2d_region_to_view(&ar->v2d,
705 event->mval[0], event->mval[1],
706 &vzd->mx_2d, &vzd->my_2d);
709 return view_zoomin_exec(C, op);
712 static void VIEW2D_OT_zoom_in(wmOperatorType *ot)
716 ot->description= "Zoom in the view";
717 ot->idname= "VIEW2D_OT_zoom_in";
720 ot->invoke= view_zoomin_invoke;
721 // ot->exec= view_zoomin_exec; // XXX, needs view_zoomdrag_init called first.
722 ot->poll= view_zoom_poll;
724 /* rna - must keep these in sync with the other operators */
725 RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
726 RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
729 /* this operator only needs this single callback, where it callsthe view_zoom_*() methods */
730 static int view_zoomout_exec(bContext *C, wmOperator *op)
732 /* check that there's an active region, as View2D data resides there */
733 if (!view_zoom_poll(C))
734 return OPERATOR_PASS_THROUGH;
736 /* set RNA-Props - zooming in by uniform factor */
737 RNA_float_set(op->ptr, "zoomfacx", -0.0375f);
738 RNA_float_set(op->ptr, "zoomfacy", -0.0375f);
740 /* apply movement, then we're done */
741 view_zoomstep_apply(C, op);
743 view_zoomstep_exit(op);
745 return OPERATOR_FINISHED;
748 static int view_zoomout_invoke(bContext *C, wmOperator *op, wmEvent *event)
750 v2dViewZoomData *vzd;
752 if (!view_zoomdrag_init(C, op))
753 return OPERATOR_PASS_THROUGH;
757 if(U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
758 ARegion *ar= CTX_wm_region(C);
760 /* store initial mouse position (in view space) */
761 UI_view2d_region_to_view(&ar->v2d,
762 event->mval[0], event->mval[1],
763 &vzd->mx_2d, &vzd->my_2d);
766 return view_zoomout_exec(C, op);
769 static void VIEW2D_OT_zoom_out(wmOperatorType *ot)
772 ot->name= "Zoom Out";
773 ot->description= "Zoom out the view";
774 ot->idname= "VIEW2D_OT_zoom_out";
777 ot->invoke= view_zoomout_invoke;
778 // ot->exec= view_zoomout_exec; // XXX, needs view_zoomdrag_init called first.
779 ot->poll= view_zoom_poll;
781 /* rna - must keep these in sync with the other operators */
782 RNA_def_float(ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX);
783 RNA_def_float(ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX);
786 /* ********************************************************* */
787 /* DRAG-ZOOM OPERATOR */
789 /* MMB Drag - allows non-uniform scaling by dragging mouse
791 * In order to make sure this works, each operator must define the following RNA-Operator Props:
792 * deltax, deltay - amounts to add to each side of the 'cur' rect
795 /* apply transform to view (i.e. adjust 'cur' rect) */
796 static void view_zoomdrag_apply(bContext *C, wmOperator *op)
798 v2dViewZoomData *vzd= op->customdata;
799 View2D *v2d= vzd->v2d;
802 /* get amount to move view by */
803 dx= RNA_float_get(op->ptr, "deltax");
804 dy= RNA_float_get(op->ptr, "deltay");
806 /* continous zoom shouldn't move that fast... */
807 if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
808 double time= PIL_check_seconds_timer();
809 float time_step= (float)(time - vzd->timer_lastdraw);
811 dx *= time_step * 0.5f;
812 dy *= time_step * 0.5f;
814 vzd->timer_lastdraw= time;
817 /* only move view on an axis if change is allowed */
818 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
819 if (v2d->keepofs & V2D_LOCKOFS_X) {
820 v2d->cur.xmax -= 2*dx;
823 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
824 float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / (v2d->cur.xmax-v2d->cur.xmin);
825 float mval_faci = 1.0f - mval_fac;
826 float ofs= (mval_fac * dx) - (mval_faci * dx);
828 v2d->cur.xmin += ofs + dx;
829 v2d->cur.xmax += ofs - dx;
837 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
838 if (v2d->keepofs & V2D_LOCKOFS_Y) {
839 v2d->cur.ymax -= 2*dy;
842 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
843 float mval_fac = (vzd->my_2d - v2d->cur.ymin) / (v2d->cur.ymax-v2d->cur.ymin);
844 float mval_faci = 1.0f - mval_fac;
845 float ofs= (mval_fac * dy) - (mval_faci * dy);
847 v2d->cur.ymin += ofs + dy;
848 v2d->cur.ymax += ofs - dy;
857 /* validate that view is in valid configuration after this operation */
858 UI_view2d_curRect_validate(v2d);
860 /* request updates to be done... */
861 ED_region_tag_redraw(vzd->ar);
862 UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
865 /* cleanup temp customdata */
866 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
868 if (op->customdata) {
869 v2dViewZoomData *vzd= op->customdata;
872 WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer);
874 MEM_freeN(op->customdata);
875 op->customdata= NULL;
879 static int view_zoomdrag_cancel(bContext *C, wmOperator *op)
881 view_zoomdrag_exit(C, op);
883 return OPERATOR_CANCELLED;
886 /* for 'redo' only, with no user input */
887 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
889 if (!view_zoomdrag_init(C, op))
890 return OPERATOR_PASS_THROUGH;
892 view_zoomdrag_apply(C, op);
893 view_zoomdrag_exit(C, op);
894 return OPERATOR_FINISHED;
897 /* set up modal operator and relevant settings */
898 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, wmEvent *event)
900 wmWindow *window= CTX_wm_window(C);
901 v2dViewZoomData *vzd;
904 /* set up customdata */
905 if (!view_zoomdrag_init(C, op))
906 return OPERATOR_PASS_THROUGH;
911 if (event->type == MOUSEZOOM) {
914 vzd->lastx= event->prevx;
915 vzd->lasty= event->prevy;
917 /* As we have only 1D information (magnify value), feed both axes
918 * with magnify information that is stored in x axis
920 fac= 0.01f * (event->x - event->prevx);
921 dx= fac * (v2d->cur.xmax - v2d->cur.xmin) / 10.0f;
922 dy= fac * (v2d->cur.ymax - v2d->cur.ymin) / 10.0f;
924 RNA_float_set(op->ptr, "deltax", dx);
925 RNA_float_set(op->ptr, "deltay", dy);
927 view_zoomdrag_apply(C, op);
928 view_zoomdrag_exit(C, op);
929 return OPERATOR_FINISHED;
932 /* set initial settings */
933 vzd->lastx= event->x;
934 vzd->lasty= event->y;
935 RNA_float_set(op->ptr, "deltax", 0);
936 RNA_float_set(op->ptr, "deltay", 0);
938 /* for modal exit test */
939 vzd->invoke_event= event->type;
941 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
942 ARegion *ar= CTX_wm_region(C);
944 /* store initial mouse position (in view space) */
945 UI_view2d_region_to_view(&ar->v2d,
946 event->mval[0], event->mval[1],
947 &vzd->mx_2d, &vzd->my_2d);
950 if (v2d->keepofs & V2D_LOCKOFS_X)
951 WM_cursor_modal(window, BC_NS_SCROLLCURSOR);
952 else if (v2d->keepofs & V2D_LOCKOFS_Y)
953 WM_cursor_modal(window, BC_EW_SCROLLCURSOR);
955 WM_cursor_modal(window, BC_NSEW_SCROLLCURSOR);
957 /* add temp handler */
958 WM_event_add_modal_handler(C, op);
960 if (U.viewzoom == USER_ZOOM_CONT) {
961 /* needs a timer to continue redrawing */
962 vzd->timer= WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
963 vzd->timer_lastdraw= PIL_check_seconds_timer();
966 return OPERATOR_RUNNING_MODAL;
969 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
970 static int view_zoomdrag_modal(bContext *C, wmOperator *op, wmEvent *event)
972 v2dViewZoomData *vzd= op->customdata;
973 View2D *v2d= vzd->v2d;
975 /* execute the events */
976 if (event->type == TIMER && event->customdata == vzd->timer) {
977 view_zoomdrag_apply(C, op);
979 else if(event->type == MOUSEMOVE) {
982 /* calculate new delta transform, based on zooming mode */
983 if (U.viewzoom == USER_ZOOM_SCALE) {
984 /* 'scale' zooming */
987 /* x-axis transform */
988 dist = (v2d->mask.xmax - v2d->mask.xmin) / 2.0f;
989 dx= 1.0f - ((float)fabs(vzd->lastx - dist) + 2.0f) / ((float)fabs(event->x - dist) + 2.0f);
990 dx*= 0.5f * (v2d->cur.xmax - v2d->cur.xmin);
992 /* y-axis transform */
993 dist = (v2d->mask.ymax - v2d->mask.ymin) / 2.0f;
994 dy= 1.0f - ((float)fabs(vzd->lasty - dist) + 2.0f) / ((float)fabs(event->y - dist) + 2.0f);
995 dy*= 0.5f * (v2d->cur.ymax - v2d->cur.ymin);
998 /* 'continuous' or 'dolly' */
1001 /* x-axis transform */
1002 fac= 0.01f * (event->x - vzd->lastx);
1003 dx= fac * (v2d->cur.xmax - v2d->cur.xmin);
1005 /* y-axis transform */
1006 fac= 0.01f * (event->y - vzd->lasty);
1007 dy= fac * (v2d->cur.ymax - v2d->cur.ymin);
1009 /* continous zoom shouldn't move that fast... */
1010 if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
1011 double time= PIL_check_seconds_timer();
1012 float time_step= (float)(time - vzd->timer_lastdraw);
1014 dx /= (0.1f / time_step);
1015 dy /= (0.1f / time_step);
1017 vzd->timer_lastdraw= time;
1022 /* set transform amount, and add current deltas to stored total delta (for redo) */
1023 RNA_float_set(op->ptr, "deltax", dx);
1024 RNA_float_set(op->ptr, "deltay", dy);
1028 /* store mouse coordinates for next time, if not doing continuous zoom
1029 * - continuous zoom only depends on distance of mouse to starting point to determine rate of change
1031 if (U.viewzoom != USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
1032 vzd->lastx= event->x;
1033 vzd->lasty= event->y;
1037 view_zoomdrag_apply(C, op);
1039 else if (event->type == vzd->invoke_event || event->type==ESCKEY) {
1040 if (event->val == KM_RELEASE) {
1042 /* for redo, store the overall deltas - need to respect zoom-locks here... */
1043 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0)
1044 RNA_float_set(op->ptr, "deltax", vzd->dx);
1046 RNA_float_set(op->ptr, "deltax", 0);
1048 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0)
1049 RNA_float_set(op->ptr, "deltay", vzd->dy);
1051 RNA_float_set(op->ptr, "deltay", 0);
1053 /* free customdata */
1054 view_zoomdrag_exit(C, op);
1055 WM_cursor_restore(CTX_wm_window(C));
1057 return OPERATOR_FINISHED;
1061 return OPERATOR_RUNNING_MODAL;
1064 static void VIEW2D_OT_zoom(wmOperatorType *ot)
1067 ot->name= "Zoom 2D View";
1068 ot->description= "Zoom in/out the view";
1069 ot->idname= "VIEW2D_OT_zoom";
1072 ot->exec= view_zoomdrag_exec;
1073 ot->invoke= view_zoomdrag_invoke;
1074 ot->modal= view_zoomdrag_modal;
1075 ot->cancel= view_zoomdrag_cancel;
1077 ot->poll= view_zoom_poll;
1079 /* operator is repeatable */
1080 // ot->flag= OPTYPE_BLOCKING;
1082 /* rna - must keep these in sync with the other operators */
1083 RNA_def_float(ot->srna, "deltax", 0, -FLT_MAX, FLT_MAX, "Delta X", "", -FLT_MAX, FLT_MAX);
1084 RNA_def_float(ot->srna, "deltay", 0, -FLT_MAX, FLT_MAX, "Delta Y", "", -FLT_MAX, FLT_MAX);
1087 /* ********************************************************* */
1090 /* The user defines a rect using standard borderselect tools, and we use this rect to
1091 * define the new zoom-level of the view in the following ways:
1092 * 1) LEFTMOUSE - zoom in to view
1093 * 2) RIGHTMOUSE - zoom out of view
1095 * Currently, these key mappings are hardcoded, but it shouldn't be too important to
1096 * have custom keymappings for this...
1099 static int view_borderzoom_exec(bContext *C, wmOperator *op)
1101 ARegion *ar= CTX_wm_region(C);
1102 View2D *v2d= &ar->v2d;
1106 /* convert coordinates of rect to 'tot' rect coordinates */
1107 UI_view2d_region_to_view(v2d, RNA_int_get(op->ptr, "xmin"), RNA_int_get(op->ptr, "ymin"), &rect.xmin, &rect.ymin);
1108 UI_view2d_region_to_view(v2d, RNA_int_get(op->ptr, "xmax"), RNA_int_get(op->ptr, "ymax"), &rect.xmax, &rect.ymax);
1110 /* check if zooming in/out view */
1111 gesture_mode= RNA_int_get(op->ptr, "gesture_mode");
1113 if (gesture_mode == GESTURE_MODAL_IN) {
1115 * - 'cur' rect will be defined by the coordinates of the border region
1116 * - just set the 'cur' rect to have the same coordinates as the border region
1117 * if zoom is allowed to be changed
1119 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
1120 v2d->cur.xmin= rect.xmin;
1121 v2d->cur.xmax= rect.xmax;
1123 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
1124 v2d->cur.ymin= rect.ymin;
1125 v2d->cur.ymax= rect.ymax;
1128 else /* if (gesture_mode == GESTURE_MODAL_OUT) */ {
1130 * - the current 'cur' rect coordinates are going to end upwhere the 'rect' ones are,
1131 * but the 'cur' rect coordinates will need to be adjusted to take in more of the view
1132 * - calculate zoom factor, and adjust using center-point
1134 float zoom, center, size;
1136 // TODO: is this zoom factor calculation valid? It seems to produce same results everytime...
1137 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
1138 size= (v2d->cur.xmax - v2d->cur.xmin);
1139 zoom= size / (rect.xmax - rect.xmin);
1140 center= (v2d->cur.xmax + v2d->cur.xmin) * 0.5f;
1142 v2d->cur.xmin= center - (size * zoom);
1143 v2d->cur.xmax= center + (size * zoom);
1145 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
1146 size= (v2d->cur.ymax - v2d->cur.ymin);
1147 zoom= size / (rect.ymax - rect.ymin);
1148 center= (v2d->cur.ymax + v2d->cur.ymin) * 0.5f;
1150 v2d->cur.ymin= center - (size * zoom);
1151 v2d->cur.ymax= center + (size * zoom);
1155 /* validate that view is in valid configuration after this operation */
1156 UI_view2d_curRect_validate(v2d);
1158 /* request updates to be done... */
1159 ED_region_tag_redraw(ar);
1160 UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1162 return OPERATOR_FINISHED;
1165 static void VIEW2D_OT_zoom_border(wmOperatorType *ot)
1168 ot->name= "Zoom to Border";
1169 ot->description= "Zoom in the view to the nearest item contained in the border";
1170 ot->idname= "VIEW2D_OT_zoom_border";
1173 ot->invoke= WM_border_select_invoke;
1174 ot->exec= view_borderzoom_exec;
1175 ot->modal= WM_border_select_modal;
1176 ot->cancel= WM_border_select_cancel;
1178 ot->poll= view_zoom_poll;
1181 RNA_def_int(ot->srna, "gesture_mode", 0, INT_MIN, INT_MAX, "Gesture Mode", "", INT_MIN, INT_MAX);
1182 RNA_def_int(ot->srna, "xmin", 0, INT_MIN, INT_MAX, "X Min", "", INT_MIN, INT_MAX);
1183 RNA_def_int(ot->srna, "xmax", 0, INT_MIN, INT_MAX, "X Max", "", INT_MIN, INT_MAX);
1184 RNA_def_int(ot->srna, "ymin", 0, INT_MIN, INT_MAX, "Y Min", "", INT_MIN, INT_MAX);
1185 RNA_def_int(ot->srna, "ymax", 0, INT_MIN, INT_MAX, "Y Max", "", INT_MIN, INT_MAX);
1188 /* ********************************************************* */
1191 /* Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
1192 * 1) 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable,
1193 * enlarge 'cur' rect on the relevant side
1194 * 2) 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite)
1196 * In order to make sure this works, each operator must define the following RNA-Operator Props:
1197 * deltax, deltay - define how much to move view by (relative to zoom-correction factor)
1200 /* customdata for scroller-invoke data */
1201 typedef struct v2dScrollerMove {
1202 View2D *v2d; /* View2D data that this operation affects */
1203 ARegion *ar; /* region that the scroller is in */
1205 short scroller; /* scroller that mouse is in ('h' or 'v') */
1206 short zone; /* -1 is min zoomer, 0 is bar, 1 is max zoomer */ // XXX find some way to provide visual feedback of this (active color?)
1208 float fac; /* view adjustment factor, based on size of region */
1209 float delta; /* amount moved by mouse on axis of interest */
1211 float scrollbarwidth; /* width of the scrollbar itself, used for page up/down clicks */
1213 int lastx, lasty; /* previous mouse coordinates (in screen coordinates) for determining movement */
1217 /* View2DScrollers is typedef'd in UI_view2d.h
1218 * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, as we only need focus bubble info
1219 * WARNING: the start of this struct must not change, so that it stays in sync with the 'real' version
1220 * For now, we don't need to have a separate (internal) header for structs like this...
1222 struct View2DScrollers {
1224 int vert_min, vert_max; /* vertical scrollbar */
1225 int hor_min, hor_max; /* horizontal scrollbar */
1228 /* quick enum for vsm->zone (scroller handles) */
1230 SCROLLHANDLE_MIN= -1,
1233 SCROLLHANDLE_MIN_OUTSIDE,
1234 SCROLLHANDLE_MAX_OUTSIDE
1235 } /*eV2DScrollerHandle_Zone*/;
1237 /* ------------------------ */
1239 /* check if mouse is within scroller handle
1240 * - mouse = relevant mouse coordinate in region space
1241 * - sc_min, sc_max = extents of scroller 'groove' (potential available space for scroller)
1242 * - sh_min, sh_max = positions of scrollbar handles
1244 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
1246 short in_min, in_max, in_bar, out_min, out_max, in_view=1;
1248 /* firstly, check if
1249 * - 'bubble' fills entire scroller
1250 * - 'bubble' completely out of view on either side
1252 if ((sh_min <= sc_min) && (sh_max >= sc_max)) in_view= 0;
1253 if (sh_min == sh_max) {
1254 if (sh_min <= sc_min) in_view= 0;
1255 if (sh_max >= sc_max) in_view= 0;
1258 if (sh_max <= sc_min) in_view= 0;
1259 if (sh_min >= sc_max) in_view= 0;
1264 return SCROLLHANDLE_BAR;
1267 /* check if mouse is in or past either handle */
1268 // TODO: check if these extents are still valid or not
1269 in_max= ( (mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)) );
1270 in_min= ( (mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) && (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)) );
1271 in_bar= ( (mouse < (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse > (sh_min + V2D_SCROLLER_HANDLE_SIZE)) );
1272 out_min= mouse < (sh_min - V2D_SCROLLER_HANDLE_SIZE);
1273 out_max= mouse > (sh_max + V2D_SCROLLER_HANDLE_SIZE);
1276 return SCROLLHANDLE_BAR;
1278 return SCROLLHANDLE_MAX;
1280 return SCROLLHANDLE_MIN;
1282 return SCROLLHANDLE_MIN_OUTSIDE;
1284 return SCROLLHANDLE_MAX_OUTSIDE;
1286 /* unlikely to happen, though we just cover it in case */
1287 return SCROLLHANDLE_BAR;
1290 /* initialise customdata for scroller manipulation operator */
1291 static void scroller_activate_init(bContext *C, wmOperator *op, wmEvent *event, short in_scroller)
1293 v2dScrollerMove *vsm;
1294 View2DScrollers *scrollers;
1295 ARegion *ar= CTX_wm_region(C);
1296 View2D *v2d= &ar->v2d;
1299 /* set custom-data for operator */
1300 vsm= MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
1301 op->customdata= vsm;
1303 /* set general data */
1306 vsm->scroller= in_scroller;
1308 /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
1309 vsm->lastx = event->x;
1310 vsm->lasty = event->y;
1312 /* 'zone' depends on where mouse is relative to bubble
1313 * - zooming must be allowed on this axis, otherwise, default to pan
1315 scrollers= UI_view2d_scrollers_calc(C, v2d, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY);
1316 if (in_scroller == 'h') {
1317 /* horizontal scroller - calculate adjustment factor first */
1318 mask_size= (float)(v2d->hor.xmax - v2d->hor.xmin);
1319 vsm->fac= (v2d->tot.xmax - v2d->tot.xmin) / mask_size;
1321 /* get 'zone' (i.e. which part of scroller is activated) */
1322 vsm->zone= mouse_in_scroller_handle(event->mval[0], v2d->hor.xmin, v2d->hor.xmax, scrollers->hor_min, scrollers->hor_max);
1324 if ((v2d->keepzoom & V2D_LOCKZOOM_X) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1325 /* default to scroll, as handles not usable */
1326 vsm->zone= SCROLLHANDLE_BAR;
1329 vsm->scrollbarwidth = scrollers->hor_max - scrollers->hor_min;
1332 /* vertical scroller - calculate adjustment factor first */
1333 mask_size= (float)(v2d->vert.ymax - v2d->vert.ymin);
1334 vsm->fac= (v2d->tot.ymax - v2d->tot.ymin) / mask_size;
1336 /* get 'zone' (i.e. which part of scroller is activated) */
1337 vsm->zone= mouse_in_scroller_handle(event->mval[1], v2d->vert.ymin, v2d->vert.ymax, scrollers->vert_min, scrollers->vert_max);
1339 if ((v2d->keepzoom & V2D_LOCKZOOM_Y) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1340 /* default to scroll, as handles not usable */
1341 vsm->zone= SCROLLHANDLE_BAR;
1344 vsm->scrollbarwidth = scrollers->vert_max - scrollers->vert_min;
1347 UI_view2d_scrollers_free(scrollers);
1348 ED_region_tag_redraw(ar);
1351 /* cleanup temp customdata */
1352 static void scroller_activate_exit(bContext *C, wmOperator *op)
1354 if (op->customdata) {
1355 v2dScrollerMove *vsm= op->customdata;
1357 vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE|V2D_SCROLL_V_ACTIVE);
1359 MEM_freeN(op->customdata);
1360 op->customdata= NULL;
1362 ED_region_tag_redraw(CTX_wm_region(C));
1366 static int scroller_activate_cancel(bContext *C, wmOperator *op)
1368 scroller_activate_exit(C, op);
1370 return OPERATOR_CANCELLED;
1373 /* apply transform to view (i.e. adjust 'cur' rect) */
1374 static void scroller_activate_apply(bContext *C, wmOperator *op)
1376 v2dScrollerMove *vsm= op->customdata;
1377 View2D *v2d= vsm->v2d;
1380 /* calculate amount to move view by */
1381 temp= vsm->fac * vsm->delta;
1383 /* type of movement */
1384 switch (vsm->zone) {
1385 case SCROLLHANDLE_MIN:
1386 /* only expand view on axis if zoom is allowed */
1387 if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1388 v2d->cur.xmin -= temp;
1389 if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1390 v2d->cur.ymin -= temp;
1393 case SCROLLHANDLE_MAX:
1395 /* only expand view on axis if zoom is allowed */
1396 if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1397 v2d->cur.xmax += temp;
1398 if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1399 v2d->cur.ymax += temp;
1402 case SCROLLHANDLE_MIN_OUTSIDE:
1403 case SCROLLHANDLE_MAX_OUTSIDE:
1404 case SCROLLHANDLE_BAR:
1406 /* only move view on an axis if panning is allowed */
1407 if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1408 v2d->cur.xmin += temp;
1409 v2d->cur.xmax += temp;
1411 if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1412 v2d->cur.ymin += temp;
1413 v2d->cur.ymax += temp;
1419 /* validate that view is in valid configuration after this operation */
1420 UI_view2d_curRect_validate(v2d);
1422 /* request updates to be done... */
1423 ED_region_tag_redraw(vsm->ar);
1424 UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1427 /* handle user input for scrollers - calculations of mouse-movement need to be done here, not in the apply callback! */
1428 static int scroller_activate_modal(bContext *C, wmOperator *op, wmEvent *event)
1430 v2dScrollerMove *vsm= op->customdata;
1432 /* execute the events */
1433 switch (event->type) {
1436 /* calculate new delta transform, then store mouse-coordinates for next-time */
1437 if (ELEM(vsm->zone, SCROLLHANDLE_BAR, SCROLLHANDLE_MAX)) {
1438 /* if using bar (i.e. 'panning') or 'max' zoom widget */
1439 switch (vsm->scroller) {
1440 case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */
1441 vsm->delta= (float)(event->x - vsm->lastx);
1443 case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */
1444 vsm->delta= (float)(event->y - vsm->lasty);
1448 else if (vsm->zone == SCROLLHANDLE_MIN) {
1449 /* using 'min' zoom widget */
1450 switch (vsm->scroller) {
1451 case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves with mouse) */
1452 vsm->delta= (float)(vsm->lastx - event->x);
1454 case 'v': /* vertical scroller - so only vertical movement ('cur' moves with to mouse) */
1455 vsm->delta= (float)(vsm->lasty - event->y);
1460 /* store previous coordinates */
1461 vsm->lastx= event->x;
1462 vsm->lasty= event->y;
1464 scroller_activate_apply(C, op);
1469 if (event->val==KM_RELEASE) {
1470 /* single-click was in empty space outside bubble, so scroll by 1 'page' */
1471 if (ELEM(vsm->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) {
1472 if (vsm->zone == SCROLLHANDLE_MIN_OUTSIDE)
1473 vsm->delta = -vsm->scrollbarwidth * 0.8f;
1474 else if (vsm->zone == SCROLLHANDLE_MAX_OUTSIDE)
1475 vsm->delta = vsm->scrollbarwidth * 0.8f;
1477 scroller_activate_apply(C, op);
1478 scroller_activate_exit(C, op);
1479 return OPERATOR_FINISHED;
1482 /* otherwise, end the drag action */
1483 if (vsm->lastx || vsm->lasty) {
1484 scroller_activate_exit(C, op);
1485 return OPERATOR_FINISHED;
1491 return OPERATOR_RUNNING_MODAL;
1495 /* a click (or click drag in progress) should have occurred, so check if it happened in scrollbar */
1496 static int scroller_activate_invoke(bContext *C, wmOperator *op, wmEvent *event)
1498 ARegion *ar= CTX_wm_region(C);
1499 View2D *v2d= &ar->v2d;
1500 short in_scroller= 0;
1502 /* check if mouse in scrollbars, if they're enabled */
1503 in_scroller= UI_view2d_mouse_in_scrollers(C, v2d, event->x, event->y);
1505 /* if in a scroller, init customdata then set modal handler which will catch mousedown to start doing useful stuff */
1507 v2dScrollerMove *vsm;
1509 /* initialise customdata */
1510 scroller_activate_init(C, op, event, in_scroller);
1511 vsm= (v2dScrollerMove *)op->customdata;
1513 /* check if zoom zones are inappropriate (i.e. zoom widgets not shown), so cannot continue
1514 * NOTE: see view2d.c for latest conditions, and keep this in sync with that
1516 if (ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) {
1517 if ( ((vsm->scroller=='h') && (v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL)==0) ||
1518 ((vsm->scroller=='v') && (v2d->scroll & V2D_SCROLL_SCALE_VERTICAL)==0) )
1520 /* switch to bar (i.e. no scaling gets handled) */
1521 vsm->zone= SCROLLHANDLE_BAR;
1525 /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */
1526 if (vsm->zone == SCROLLHANDLE_BAR) {
1527 if ( ((vsm->scroller=='h') && (v2d->keepofs & V2D_LOCKOFS_X)) ||
1528 ((vsm->scroller=='v') && (v2d->keepofs & V2D_LOCKOFS_Y)) )
1530 /* free customdata initialised */
1531 scroller_activate_exit(C, op);
1533 /* can't catch this event for ourselves, so let it go to someone else? */
1534 return OPERATOR_PASS_THROUGH;
1538 /* zone is also inappropriate if scroller is not visible... */
1539 if ( ((vsm->scroller=='h') && (v2d->scroll & (V2D_SCROLL_HORIZONTAL_HIDE|V2D_SCROLL_HORIZONTAL_FULLR))) ||
1540 ((vsm->scroller=='v') && (v2d->scroll & (V2D_SCROLL_VERTICAL_HIDE|V2D_SCROLL_VERTICAL_FULLR))) )
1542 /* free customdata initialised */
1543 scroller_activate_exit(C, op);
1545 /* can't catch this event for ourselves, so let it go to someone else? */
1546 /* XXX note: if handlers use mask rect to clip input, input will fail for this case */
1547 return OPERATOR_PASS_THROUGH;
1550 /* activate the scroller */
1551 if (vsm->scroller=='h')
1552 v2d->scroll_ui |= V2D_SCROLL_H_ACTIVE;
1554 v2d->scroll_ui |= V2D_SCROLL_V_ACTIVE;
1556 /* still ok, so can add */
1557 WM_event_add_modal_handler(C, op);
1558 return OPERATOR_RUNNING_MODAL;
1561 /* not in scroller, so nothing happened... (pass through let's something else catch event) */
1562 return OPERATOR_PASS_THROUGH;
1566 /* LMB-Drag in Scrollers - not repeatable operator! */
1567 static void VIEW2D_OT_scroller_activate(wmOperatorType *ot)
1570 ot->name= "Scroller Activate";
1571 ot->description= "Scroll view by mouse click and drag";
1572 ot->idname= "VIEW2D_OT_scroller_activate";
1575 ot->flag= OPTYPE_BLOCKING;
1578 ot->invoke= scroller_activate_invoke;
1579 ot->modal= scroller_activate_modal;
1580 ot->cancel= scroller_activate_cancel;
1582 ot->poll= view2d_poll;
1585 /* ********************************************************* */
1588 static int reset_exec(bContext *C, wmOperator *UNUSED(op))
1590 uiStyle *style= U.uistyles.first;
1591 ARegion *ar= CTX_wm_region(C);
1592 View2D *v2d= &ar->v2d;
1596 winx= (float)(v2d->mask.xmax - v2d->mask.xmin + 1);
1597 winy= (float)(v2d->mask.ymax - v2d->mask.ymin + 1);
1599 v2d->cur.xmax= v2d->cur.xmin + winx;
1600 v2d->cur.ymax= v2d->cur.ymin + winy;
1604 /* posx and negx flags are mutually exclusive, so watch out */
1605 if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
1606 v2d->cur.xmax= 0.0f;
1607 v2d->cur.xmin= -winx*style->panelzoom;
1609 else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
1610 v2d->cur.xmax= winx*style->panelzoom;
1611 v2d->cur.xmin= 0.0f;
1614 /* - posx and negx flags are mutually exclusive, so watch out */
1615 if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
1616 v2d->cur.ymax= 0.0f;
1617 v2d->cur.ymin= -winy*style->panelzoom;
1619 else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
1620 v2d->cur.ymax= winy*style->panelzoom;
1621 v2d->cur.ymin= 0.0f;
1625 /* validate that view is in valid configuration after this operation */
1626 UI_view2d_curRect_validate(v2d);
1628 /* request updates to be done... */
1629 ED_region_tag_redraw(ar);
1630 UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY);
1632 return OPERATOR_FINISHED;
1635 static void VIEW2D_OT_reset(wmOperatorType *ot)
1638 ot->name= "Reset View";
1639 ot->description= "Reset the view";
1640 ot->idname= "VIEW2D_OT_reset";
1643 ot->exec= reset_exec;
1644 ot->poll= view2d_poll;
1647 /* ********************************************************* */
1650 void UI_view2d_operatortypes(void)
1652 WM_operatortype_append(VIEW2D_OT_pan);
1654 WM_operatortype_append(VIEW2D_OT_scroll_left);
1655 WM_operatortype_append(VIEW2D_OT_scroll_right);
1656 WM_operatortype_append(VIEW2D_OT_scroll_up);
1657 WM_operatortype_append(VIEW2D_OT_scroll_down);
1659 WM_operatortype_append(VIEW2D_OT_zoom_in);
1660 WM_operatortype_append(VIEW2D_OT_zoom_out);
1662 WM_operatortype_append(VIEW2D_OT_zoom);
1663 WM_operatortype_append(VIEW2D_OT_zoom_border);
1665 WM_operatortype_append(VIEW2D_OT_scroller_activate);
1667 WM_operatortype_append(VIEW2D_OT_reset);
1670 void UI_view2d_keymap(wmKeyConfig *keyconf)
1672 wmKeyMap *keymap= WM_keymap_find(keyconf, "View2D", 0, 0);
1675 WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
1676 WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, KM_SHIFT, 0);
1678 WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MOUSEPAN, 0, 0, 0);
1680 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, KM_CTRL, 0);
1681 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, KM_CTRL, 0);
1683 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, KM_SHIFT, 0);
1684 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, KM_SHIFT, 0);
1686 /* zoom - single step */
1687 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", WHEELOUTMOUSE, KM_PRESS, 0, 0);
1688 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", WHEELINMOUSE, KM_PRESS, 0, 0);
1689 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0);
1690 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0);
1692 /* scroll up/down - no modifiers, only when zoom fails */
1693 /* these may fail if zoom is disallowed, in which case they should pass on event */
1694 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
1695 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0);
1696 /* these may be necessary if vertical scroll is disallowed */
1697 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
1698 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", WHEELUPMOUSE, KM_PRESS, 0, 0);
1700 /* alternatives for page up/down to scroll */
1701 #if 0 // XXX disabled, since this causes conflicts with hotkeys in animation editors
1702 /* scroll up/down may fall through to left/right */
1703 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", PAGEDOWNKEY, KM_PRESS, 0, 0);
1704 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", PAGEUPKEY, KM_PRESS, 0, 0);
1705 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", PAGEDOWNKEY, KM_PRESS, 0, 0);
1706 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", PAGEUPKEY, KM_PRESS, 0, 0);
1707 /* shift for moving view left/right with page up/down */
1708 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_right", PAGEDOWNKEY, KM_PRESS, KM_SHIFT, 0);
1709 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_left", PAGEUPKEY, KM_PRESS, KM_SHIFT, 0);
1713 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
1714 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEZOOM, 0, 0, 0);
1716 /* borderzoom - drag */
1717 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_border", BKEY, KM_PRESS, KM_SHIFT, 0);
1720 WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);
1722 /* Alternative keymap for buttons listview */
1723 keymap= WM_keymap_find(keyconf, "View2D Buttons List", 0, 0);
1724 WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
1725 WM_keymap_add_item(keymap, "VIEW2D_OT_pan", MOUSEPAN, 0, 0, 0);
1726 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", WHEELDOWNMOUSE, KM_PRESS, 0, 0);
1727 WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", WHEELUPMOUSE, KM_PRESS, 0, 0);
1729 RNA_boolean_set(WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_down", PAGEDOWNKEY, KM_PRESS, 0, 0)->ptr, "page", 1);
1730 RNA_boolean_set(WM_keymap_add_item(keymap, "VIEW2D_OT_scroll_up", PAGEUPKEY, KM_PRESS, 0, 0)->ptr, "page", 1);
1732 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
1733 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom", MOUSEZOOM, 0, 0, 0);
1734 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_out", PADMINUS, KM_PRESS, 0, 0);
1735 WM_keymap_add_item(keymap, "VIEW2D_OT_zoom_in", PADPLUSKEY, KM_PRESS, 0, 0);
1736 WM_keymap_add_item(keymap, "VIEW2D_OT_reset", HOMEKEY, KM_PRESS, 0, 0);
1737 WM_keymap_add_item(keymap, "VIEW2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);