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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 *****
30 #include "MEM_guardedalloc.h"
32 #include "DNA_scene_types.h"
33 #include "DNA_screen_types.h"
34 #include "DNA_space_types.h"
35 #include "DNA_userdef_types.h"
36 #include "DNA_view2d_types.h"
38 #include "BLI_blenlib.h"
40 #include "BKE_global.h"
41 #include "BKE_utildefines.h"
43 #include "RNA_access.h"
44 #include "RNA_define.h"
51 #include "UI_resources.h"
52 #include "UI_view2d.h"
54 /* ********************************************************* */
55 /* General Polling Funcs */
57 /* Check if mouse is within scrollbars
58 * - Returns appropriate code for match
59 * 'h' = in horizontal scrollbar
60 * 'v' = in vertical scrollbar
61 * 0 = not in scrollbar
63 * - x,y = mouse coordinates in screen (not region) space
65 static short mouse_in_v2d_scrollers (const bContext *C, View2D *v2d, int x, int y)
67 ARegion *ar= C->region;
70 /* clamp x,y to region-coordinates first */
71 co[0]= x - ar->winrct.xmin;
72 co[1]= y - ar->winrct.ymin;
74 /* check if within scrollbars */
75 if (v2d->scroll & V2D_SCROLL_HORIZONTAL) {
76 if (IN_2D_HORIZ_SCROLL(v2d, co)) return 'h';
78 if (v2d->scroll & V2D_SCROLL_VERTICAL) {
79 if (IN_2D_VERT_SCROLL(v2d, co)) return 'v';
87 /* ********************************************************* */
88 /* VIEW PANNING OPERATOR */
90 /* This group of operators come in several forms:
91 * 1) Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by
92 * 2) Scrollwheel 'steps' - rolling mousewheel by one step moves view by predefined amount
94 * In order to make sure this works, each operator must define the following RNA-Operator Props:
95 * deltax, deltay - define how much to move view by (relative to zoom-correction factor)
98 /* ------------------ Shared 'core' stuff ---------------------- */
100 /* temp customdata for operator */
101 typedef struct v2dViewPanData {
102 View2D *v2d; /* view2d we're operating in */
104 float facx, facy; /* amount to move view relative to zoom */
106 /* options for version 1 */
107 int startx, starty; /* mouse x/y values in window when operator was initiated */
108 int lastx, lasty; /* previous x/y values of mouse in window */
110 short in_scroller; /* for MMB in scrollers (old feature in past, but now not that useful) */
113 /* initialise panning customdata */
114 static int view_pan_init(bContext *C, wmOperator *op)
121 /* regions now have v2d-data by default, so check for region */
122 if (C->region == NULL)
126 /* set custom-data for operator */
127 vpd= MEM_callocN(sizeof(v2dViewPanData), "v2dViewPanData");
130 /* set pointers to owners */
131 vpd->v2d= v2d= &ar->v2d;
133 /* calculate translation factor - based on size of view */
134 winx= (float)(ar->winrct.xmax - ar->winrct.xmin);
135 winy= (float)(ar->winrct.ymax - ar->winrct.ymin);
136 vpd->facx= (v2d->cur.xmax - v2d->cur.xmin) / winx;
137 vpd->facy= (v2d->cur.ymax - v2d->cur.ymin) / winy;
142 /* apply transform to view (i.e. adjust 'cur' rect) */
143 static void view_pan_apply(bContext *C, wmOperator *op)
145 v2dViewPanData *vpd= op->customdata;
146 View2D *v2d= vpd->v2d;
149 /* calculate amount to move view by */
150 dx= vpd->facx * (float)RNA_int_get(op->ptr, "deltax");
151 dy= vpd->facy * (float)RNA_int_get(op->ptr, "deltay");
153 /* only move view on an axis if change is allowed */
154 if ((v2d->keepofs & V2D_LOCKOFS_X)==0) {
158 if ((v2d->keepofs & V2D_LOCKOFS_Y)==0) {
163 /* validate that view is in valid configuration after this operation */
164 UI_view2d_curRect_validate(v2d);
166 /* request updates to be done... */
167 WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
168 /* XXX: add WM_NOTE_TIME_CHANGED? */
171 /* cleanup temp customdata */
172 static void view_pan_exit(bContext *C, wmOperator *op)
174 if (op->customdata) {
175 MEM_freeN(op->customdata);
176 op->customdata= NULL;
180 /* ------------------ Modal Drag Version (1) ---------------------- */
182 /* for 'redo' only, with no user input */
183 static int view_pan_exec(bContext *C, wmOperator *op)
185 if (!view_pan_init(C, op))
186 return OPERATOR_CANCELLED;
188 view_pan_apply(C, op);
189 view_pan_exit(C, op);
190 return OPERATOR_FINISHED;
193 /* set up modal operator and relevant settings */
194 static int view_pan_invoke(bContext *C, wmOperator *op, wmEvent *event)
199 /* set up customdata */
200 if (!view_pan_init(C, op))
201 return OPERATOR_CANCELLED;
206 /* set initial settings */
207 vpd->startx= vpd->lastx= event->x;
208 vpd->starty= vpd->lasty= event->y;
209 RNA_int_set(op->ptr, "deltax", 0);
210 RNA_int_set(op->ptr, "deltay", 0);
212 #if 0 // XXX - enable this when cursors are working properly
213 if (v2d->keepofs & V2D_LOCKOFS_X)
214 WM_set_cursor(C, BC_NS_SCROLLCURSOR);
215 else if (v2d->keepofs & V2D_LOCKOFS_Y)
216 WM_set_cursor(C, BC_EW_SCROLLCURSOR);
218 WM_set_cursor(C, BC_NSEW_SCROLLCURSOR);
219 #endif // XXX - enable this when cursors are working properly
221 /* add temp handler */
222 WM_event_add_modal_handler(C, &C->window->handlers, op);
224 return OPERATOR_RUNNING_MODAL;
227 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
228 static int view_pan_modal(bContext *C, wmOperator *op, wmEvent *event)
230 v2dViewPanData *vpd= op->customdata;
232 /* execute the events */
233 switch (event->type) {
236 /* calculate new delta transform, then store mouse-coordinates for next-time */
237 RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x));
238 RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y));
240 vpd->lastx= event->x;
241 vpd->lasty= event->y;
243 view_pan_apply(C, op);
249 /* calculate overall delta mouse-movement for redo */
250 RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
251 RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
253 view_pan_exit(C, op);
254 //WM_set_cursor(C, CURSOR_STD); // XXX - enable this when cursors are working properly
256 return OPERATOR_FINISHED;
261 return OPERATOR_RUNNING_MODAL;
264 void ED_View2D_OT_view_pan(wmOperatorType *ot)
269 ot->name= "Pan View";
270 ot->idname= "ED_View2D_OT_view_pan";
273 ot->exec= view_pan_exec;
274 ot->invoke= view_pan_invoke;
275 ot->modal= view_pan_modal;
277 /* operator is repeatable */
278 ot->flag= OPTYPE_REGISTER;
280 /* rna - must keep these in sync with the other operators */
281 prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
282 prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
285 /* ------------------ Scrollwheel Versions (2) ---------------------- */
287 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
288 static int view_scrollright_exec(bContext *C, wmOperator *op)
290 /* initialise default settings (and validate if ok to run) */
291 if (!view_pan_init(C, op))
292 return OPERATOR_CANCELLED;
294 /* set RNA-Props - only movement in positive x-direction */
295 RNA_int_set(op->ptr, "deltax", 20);
296 RNA_int_set(op->ptr, "deltay", 0);
298 /* apply movement, then we're done */
299 view_pan_apply(C, op);
300 view_pan_exit(C, op);
302 return OPERATOR_FINISHED;
305 void ED_View2D_OT_view_scrollright(wmOperatorType *ot)
310 ot->name= "Scroll Right";
311 ot->idname= "ED_View2D_OT_view_rightscroll";
314 ot->exec= view_scrollright_exec;
316 /* operator is repeatable */
317 ot->flag= OPTYPE_REGISTER;
319 /* rna - must keep these in sync with the other operators */
320 prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
321 prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
326 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
327 static int view_scrollleft_exec(bContext *C, wmOperator *op)
329 /* initialise default settings (and validate if ok to run) */
330 if (!view_pan_init(C, op))
331 return OPERATOR_CANCELLED;
333 /* set RNA-Props - only movement in negative x-direction */
334 RNA_int_set(op->ptr, "deltax", -20);
335 RNA_int_set(op->ptr, "deltay", 0);
337 /* apply movement, then we're done */
338 view_pan_apply(C, op);
339 view_pan_exit(C, op);
341 return OPERATOR_FINISHED;
344 void ED_View2D_OT_view_scrollleft(wmOperatorType *ot)
349 ot->name= "Scroll Left";
350 ot->idname= "ED_View2D_OT_view_leftscroll";
353 ot->exec= view_scrollleft_exec;
355 /* operator is repeatable */
356 ot->flag= OPTYPE_REGISTER;
358 /* rna - must keep these in sync with the other operators */
359 prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
360 prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
363 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
364 static int view_scrolldown_exec(bContext *C, wmOperator *op)
366 /* initialise default settings (and validate if ok to run) */
367 if (!view_pan_init(C, op))
368 return OPERATOR_CANCELLED;
370 /* set RNA-Props - only movement in positive x-direction */
371 RNA_int_set(op->ptr, "deltax", 0);
372 RNA_int_set(op->ptr, "deltay", -20);
374 /* apply movement, then we're done */
375 view_pan_apply(C, op);
376 view_pan_exit(C, op);
378 return OPERATOR_FINISHED;
381 void ED_View2D_OT_view_scrolldown(wmOperatorType *ot)
386 ot->name= "Scroll Down";
387 ot->idname= "ED_View2D_OT_view_downscroll";
390 ot->exec= view_scrolldown_exec;
392 /* operator is repeatable */
393 ot->flag= OPTYPE_REGISTER;
395 /* rna - must keep these in sync with the other operators */
396 prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
397 prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
402 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
403 static int view_scrollup_exec(bContext *C, wmOperator *op)
405 /* initialise default settings (and validate if ok to run) */
406 if (!view_pan_init(C, op))
407 return OPERATOR_CANCELLED;
409 /* set RNA-Props - only movement in negative x-direction */
410 RNA_int_set(op->ptr, "deltax", 0);
411 RNA_int_set(op->ptr, "deltay", 20);
413 /* apply movement, then we're done */
414 view_pan_apply(C, op);
415 view_pan_exit(C, op);
417 return OPERATOR_FINISHED;
420 void ED_View2D_OT_view_scrollup(wmOperatorType *ot)
425 ot->name= "Scroll Up";
426 ot->idname= "ED_View2D_OT_view_upscroll";
429 ot->exec= view_scrollup_exec;
431 /* operator is repeatable */
432 ot->flag= OPTYPE_REGISTER;
434 /* rna - must keep these in sync with the other operators */
435 prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
436 prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
439 /* ********************************************************* */
440 /* SINGLE-STEP VIEW ZOOMING OPERATOR */
442 /* This group of operators come in several forms:
443 * 1) Scrollwheel 'steps' - rolling mousewheel by one step zooms view by predefined amount
444 * 2) Scrollwheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y) // XXX this could be implemented...
445 * 3) Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount
447 * In order to make sure this works, each operator must define the following RNA-Operator Props:
448 * zoomfacx, zoomfacy - These two zoom factors allow for non-uniform scaling.
449 * It is safe to scale by 0, as these factors are used to determine
450 * amount to enlarge 'cur' by
453 /* ------------------ 'Shared' stuff ------------------------ */
455 /* check if step-zoom can be applied */
456 static short view_zoomstep_ok(bContext *C)
460 /* check if there's a region in context to work with */
461 if (C->region == NULL)
463 v2d= &C->region->v2d;
465 /* check that 2d-view is zoomable */
466 if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
469 /* view is zoomable */
473 /* apply transform to view (i.e. adjust 'cur' rect) */
474 static void view_zoomstep_apply(bContext *C, wmOperator *op)
476 View2D *v2d= &C->region->v2d;
479 /* calculate amount to move view by */
480 dx= (v2d->cur.xmax - v2d->cur.xmin) * (float)RNA_float_get(op->ptr, "zoomfacx");
481 dy= (v2d->cur.ymax - v2d->cur.ymin) * (float)RNA_float_get(op->ptr, "zoomfacy");
483 /* only move view on an axis if change is allowed */
484 if ((v2d->keepzoom & V2D_LOCKOFS_X)==0) {
488 if ((v2d->keepzoom & V2D_LOCKOFS_Y)==0) {
493 /* validate that view is in valid configuration after this operation */
494 UI_view2d_curRect_validate(v2d);
496 /* request updates to be done... */
497 WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
498 /* XXX: add WM_NOTE_TIME_CHANGED? */
501 /* --------------- Individual Operators ------------------- */
503 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
504 static int view_zoomin_exec(bContext *C, wmOperator *op)
506 /* check that there's an active region, as View2D data resides there */
507 if (!view_zoomstep_ok(C))
508 return OPERATOR_CANCELLED;
510 /* set RNA-Props - zooming in by uniform factor */
511 RNA_float_set(op->ptr, "zoomfacx", 0.0375f);
512 RNA_float_set(op->ptr, "zoomfacy", 0.0375f);
514 /* apply movement, then we're done */
515 view_zoomstep_apply(C, op);
517 return OPERATOR_FINISHED;
520 void ED_View2D_OT_view_zoomin(wmOperatorType *ot)
526 ot->idname= "ED_View2D_OT_view_zoomin";
529 ot->exec= view_zoomin_exec;
531 /* operator is repeatable */
532 ot->flag= OPTYPE_REGISTER;
534 /* rna - must keep these in sync with the other operators */
535 prop= RNA_def_property(ot->srna, "zoomfacx", PROP_FLOAT, PROP_NONE);
536 prop= RNA_def_property(ot->srna, "zoomfacy", PROP_FLOAT, PROP_NONE);
541 /* this operator only needs this single callback, where it callsthe view_zoom_*() methods */
542 static int view_zoomout_exec(bContext *C, wmOperator *op)
544 /* check that there's an active region, as View2D data resides there */
545 if (!view_zoomstep_ok(C))
546 return OPERATOR_CANCELLED;
548 /* set RNA-Props - zooming in by uniform factor */
549 RNA_float_set(op->ptr, "zoomfacx", -0.0375f);
550 RNA_float_set(op->ptr, "zoomfacy", -0.0375f);
552 /* apply movement, then we're done */
553 view_zoomstep_apply(C, op);
555 return OPERATOR_FINISHED;
558 void ED_View2D_OT_view_zoomout(wmOperatorType *ot)
563 ot->name= "Zoom Out";
564 ot->idname= "ED_View2D_OT_view_zoomout";
567 ot->exec= view_zoomout_exec;
569 /* operator is repeatable */
570 ot->flag= OPTYPE_REGISTER;
572 /* rna - must keep these in sync with the other operators */
573 prop= RNA_def_property(ot->srna, "zoomfacx", PROP_FLOAT, PROP_NONE);
574 prop= RNA_def_property(ot->srna, "zoomfacy", PROP_FLOAT, PROP_NONE);
577 /* ********************************************************* */
578 /* DRAG-ZOOM OPERATOR */
580 /* This group of operators come in several forms:
581 * 1) MMB Drag - allows non-uniform scaling by dragging mouse
582 * - method of scaling depends upon U.viewzoom setting (Continue, Dolly, Scale)
583 * XXX should we store this info as RNA prop?
585 * In order to make sure this works, each operator must define the following RNA-Operator Props:
586 * deltax, deltay - amounts to add to each side of the 'cur' rect
589 /* ------------------ Shared 'core' stuff ---------------------- */
591 /* temp customdata for operator */
592 typedef struct v2dViewZoomData {
593 View2D *v2d; /* view2d we're operating in */
595 int lastx, lasty; /* previous x/y values of mouse in window */
596 float dx, dy; /* running tally of previous delta values (for obtaining final zoom) */
599 /* initialise panning customdata */
600 static int view_zoomdrag_init(bContext *C, wmOperator *op)
602 v2dViewZoomData *vzd;
605 /* regions now have v2d-data by default, so check for region */
606 if (C->region == NULL)
608 v2d= &C->region->v2d;
610 /* check that 2d-view is zoomable */
611 if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
614 /* set custom-data for operator */
615 vzd= MEM_callocN(sizeof(v2dViewZoomData), "v2dViewZoomData");
618 /* set pointers to owners */
624 /* apply transform to view (i.e. adjust 'cur' rect) */
625 static void view_zoomdrag_apply(bContext *C, wmOperator *op)
627 v2dViewZoomData *vzd= op->customdata;
628 View2D *v2d= vzd->v2d;
631 /* get amount to move view by */
632 dx= RNA_float_get(op->ptr, "deltax");
633 dy= RNA_float_get(op->ptr, "deltay");
635 /* only move view on an axis if change is allowed */
636 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
640 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
645 /* validate that view is in valid configuration after this operation */
646 UI_view2d_curRect_validate(v2d);
648 /* request updates to be done... */
649 WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
650 /* XXX: add WM_NOTE_TIME_CHANGED? */
653 /* cleanup temp customdata */
654 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
656 if (op->customdata) {
657 MEM_freeN(op->customdata);
658 op->customdata= NULL;
662 /* for 'redo' only, with no user input */
663 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
665 if (!view_zoomdrag_init(C, op))
666 return OPERATOR_CANCELLED;
668 view_zoomdrag_apply(C, op);
669 view_zoomdrag_exit(C, op);
670 return OPERATOR_FINISHED;
673 /* set up modal operator and relevant settings */
674 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, wmEvent *event)
676 v2dViewZoomData *vzd;
679 /* set up customdata */
680 if (!view_zoomdrag_init(C, op))
681 return OPERATOR_CANCELLED;
686 /* set initial settings */
687 vzd->lastx= event->x;
688 vzd->lasty= event->y;
689 RNA_float_set(op->ptr, "deltax", 0);
690 RNA_float_set(op->ptr, "deltay", 0);
692 #if 0 // XXX - enable this when cursors are working properly
693 if (v2d->keepofs & V2D_LOCKOFS_X)
694 WM_set_cursor(C, BC_NS_SCROLLCURSOR);
695 else if (v2d->keepofs & V2D_LOCKOFS_Y)
696 WM_set_cursor(C, BC_EW_SCROLLCURSOR);
698 WM_set_cursor(C, BC_NSEW_SCROLLCURSOR);
699 #endif // XXX - enable this when cursors are working properly
701 /* add temp handler */
702 WM_event_add_modal_handler(C, &C->window->handlers, op);
704 return OPERATOR_RUNNING_MODAL;
707 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
708 static int view_zoomdrag_modal(bContext *C, wmOperator *op, wmEvent *event)
710 v2dViewZoomData *vzd= op->customdata;
711 View2D *v2d= vzd->v2d;
713 /* execute the events */
714 switch (event->type) {
719 /* calculate new delta transform, based on zooming mode */
720 if (U.viewzoom == USER_ZOOM_SCALE) { // XXX store this setting as RNA prop?
721 /* 'scale' zooming */
724 /* x-axis transform */
725 dist = (v2d->mask.xmax - v2d->mask.xmin) / 2.0f;
726 dx= 1.0f - (fabs(vzd->lastx - dist) + 2.0f) / (fabs(event->x - dist) + 2.0f);
727 dx*= 0.5f * (v2d->cur.xmax - v2d->cur.xmin);
729 /* y-axis transform */
730 dist = (v2d->mask.ymax - v2d->mask.ymin) / 2.0f;
731 dy= 1.0f - (fabs(vzd->lasty - dist) + 2.0) / (fabs(event->y - dist) + 2.0f);
732 dy*= 0.5f * (v2d->cur.ymax - v2d->cur.ymin);
735 /* 'continuous' or 'dolly' */
738 /* x-axis transform */
739 fac= 0.01f * (event->x - vzd->lastx);
740 dx= fac * (v2d->cur.xmax - v2d->cur.xmin);
742 /* y-axis transform */
743 fac= 0.01f * (event->y - vzd->lasty);
744 dy= fac * (v2d->cur.ymax - v2d->cur.ymin);
746 /* continous zoom shouldn't move that fast... */
747 if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
753 /* set transform amount, and add current deltas to stored total delta (for redo) */
754 RNA_float_set(op->ptr, "deltax", dx);
755 RNA_float_set(op->ptr, "deltay", dy);
759 /* store mouse coordinates for next time, if not doing continuous zoom
760 * - continuous zoom only depends on distance of mouse to starting point to determine rate of change
762 if (U.viewzoom != USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
763 vzd->lastx= event->x;
764 vzd->lasty= event->y;
768 view_zoomdrag_apply(C, op);
774 /* for redo, store the overall deltas - need to respect zoom-locks here... */
775 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0)
776 RNA_float_set(op->ptr, "deltax", vzd->dx);
778 RNA_float_set(op->ptr, "deltax", 0);
780 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0)
781 RNA_float_set(op->ptr, "deltay", vzd->dy);
783 RNA_float_set(op->ptr, "deltay", 0);
785 /* free customdata */
786 view_zoomdrag_exit(C, op);
787 //WM_set_cursor(C, CURSOR_STD); // XXX - enable this when cursors are working properly
789 return OPERATOR_FINISHED;
794 return OPERATOR_RUNNING_MODAL;
797 void ED_View2D_OT_view_zoom(wmOperatorType *ot)
802 ot->name= "Zoom View";
803 ot->idname= "ED_View2D_OT_view_zoom";
806 ot->exec= view_zoomdrag_exec;
807 ot->invoke= view_zoomdrag_invoke;
808 ot->modal= view_zoomdrag_modal;
810 /* operator is repeatable */
811 ot->flag= OPTYPE_REGISTER;
813 /* rna - must keep these in sync with the other operators */
814 prop= RNA_def_property(ot->srna, "deltax", PROP_FLOAT, PROP_NONE);
815 prop= RNA_def_property(ot->srna, "deltay", PROP_FLOAT, PROP_NONE);
818 /* ********************************************************* */
821 /* Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
822 * 1) 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable,
823 * enlarge 'cur' rect on the relevant side
824 * 2) 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite)
826 * In order to make sure this works, each operator must define the following RNA-Operator Props:
827 * deltax, deltay - define how much to move view by (relative to zoom-correction factor)
830 /* customdata for scroller-invoke data */
831 typedef struct v2dScrollerMove {
832 View2D *v2d; /* View2D data that this operation affects */
834 short scroller; /* scroller that mouse is in ('h' or 'v') */
835 short zone; /* -1 is min zoomer, 0 is bar, 1 is max zoomer */ // XXX find some way to provide visual feedback of this (active colour?)
837 float fac; /* view adjustment factor, based on size of region */
838 float delta; /* amount moved by mouse on axis of interest */
840 int lastx, lasty; /* previous mouse coordinates (in screen coordinates) for determining movement */
844 /* View2DScrollers is typedef'd in UI_view2d.h
845 * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, as we only need focus bubble info
846 * WARNING: the start of this struct must not change, so that it stays in sync with the 'real' version
847 * For now, we don't need to have a separate (internal) header for structs like this...
849 struct View2DScrollers {
851 int vert_min, vert_max; /* vertical scrollbar */
852 int hor_min, hor_max; /* horizontal scrollbar */
855 /* quick enum for vsm->zone (scroller handles) */
857 SCROLLHANDLE_MIN= -1,
860 } eV2DScrollerHandle_Zone;
862 /* ------------------------ */
864 /* check if mouse is within scroller handle
865 * - mouse = relevant mouse coordinate in region space
866 * - sc_min, sc_max = extents of scroller
867 * - sh_min, sh_max = positions of scroller handles
869 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
871 short in_min, in_max, in_view=1;
874 * - 'bubble' fills entire scroller
875 * - 'bubble' completely out of view on either side
877 if ((sh_min <= sc_min) && (sh_max >= sc_max)) in_view= 0;
878 if (sh_min == sh_max) {
879 if (sh_min <= sc_min) in_view= 0;
880 if (sh_max >= sc_max) in_view= 0;
883 if (sh_max <= sc_min) in_view= 0;
884 if (sh_min >= sc_max) in_view= 0;
889 /* handles are only activated if the mouse is within the relative quater lengths of the scroller */
890 int qLen = (sc_max + sc_min) / 4;
892 if (mouse >= (sc_max - qLen))
893 return SCROLLHANDLE_MAX;
894 else if (mouse <= qLen)
895 return SCROLLHANDLE_MIN;
897 return SCROLLHANDLE_BAR;
900 /* check if mouse is in or past either handle */
901 in_max= ( (mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)) );
902 in_min= ( (mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) && (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)) );
904 /* check if overlap --> which means user clicked on bar, as bar is within handles region */
905 if (in_max && in_min)
906 return SCROLLHANDLE_BAR;
908 return SCROLLHANDLE_MAX;
910 return SCROLLHANDLE_MIN;
912 /* unlikely to happen, though we just cover it in case */
913 return SCROLLHANDLE_BAR;
916 /* initialise customdata for scroller manipulation operator */
917 static void scroller_activate_init(bContext *C, wmOperator *op, wmEvent *event, short in_scroller)
919 v2dScrollerMove *vsm;
920 View2DScrollers *scrollers;
921 ARegion *ar= C->region;
922 View2D *v2d= &ar->v2d;
926 /* set custom-data for operator */
927 vsm= MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
930 /* set general data */
932 vsm->scroller= in_scroller;
934 /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
935 vsm->lastx = event->x;
936 vsm->lasty = event->y;
937 x= event->x - ar->winrct.xmin;
938 y= event->y - ar->winrct.ymin;
940 /* 'zone' depends on where mouse is relative to bubble
941 * - zooming must be allowed on this axis, otherwise, default to pan
943 scrollers= UI_view2d_scrollers_calc(C, v2d, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY);
944 if (in_scroller == 'h') {
945 /* horizontal scroller - calculate adjustment factor first */
946 mask_size= (float)(v2d->hor.xmax - v2d->hor.xmin);
947 vsm->fac= (v2d->tot.xmax - v2d->tot.xmin) / mask_size;
949 /* get 'zone' (i.e. which part of scroller is activated) */
950 if (v2d->keepzoom & V2D_LOCKZOOM_X) {
951 /* default to scroll, as handles not usable */
952 vsm->zone= SCROLLHANDLE_BAR;
955 /* check which handle we're in */
956 vsm->zone= mouse_in_scroller_handle(x, v2d->hor.xmin, v2d->hor.xmax, scrollers->hor_min, scrollers->hor_max);
960 /* vertical scroller - calculate adjustment factor first */
961 mask_size= (float)(v2d->vert.ymax - v2d->vert.ymin);
962 vsm->fac= (v2d->tot.ymax - v2d->tot.ymin) / mask_size;
964 /* get 'zone' (i.e. which part of scroller is activated) */
965 if (v2d->keepzoom & V2D_LOCKZOOM_Y) {
966 /* default to scroll, as handles not usable */
967 vsm->zone= SCROLLHANDLE_BAR;
970 /* check which handle we're in */
971 vsm->zone= mouse_in_scroller_handle(y, v2d->vert.ymin, v2d->vert.ymax, scrollers->vert_min, scrollers->vert_max);
974 UI_view2d_scrollers_free(scrollers);
977 /* cleanup temp customdata */
978 static void scroller_activate_exit(bContext *C, wmOperator *op)
980 if (op->customdata) {
981 MEM_freeN(op->customdata);
982 op->customdata= NULL;
986 /* apply transform to view (i.e. adjust 'cur' rect) */
987 static void scroller_activate_apply(bContext *C, wmOperator *op)
989 v2dScrollerMove *vsm= op->customdata;
990 View2D *v2d= vsm->v2d;
993 /* calculate amount to move view by */
994 temp= vsm->fac * vsm->delta;
996 /* type of movement */
998 case SCROLLHANDLE_MIN:
999 /* only expand view on axis if zoom is allowed */
1000 if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1001 v2d->cur.xmin -= temp;
1002 if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1003 v2d->cur.ymin -= temp;
1006 case SCROLLHANDLE_MAX:
1007 /* only expand view on axis if zoom is allowed */
1008 if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1009 v2d->cur.xmax += temp;
1010 if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1011 v2d->cur.ymax += temp;
1014 default: /* SCROLLHANDLE_BAR */
1015 /* only move view on an axis if panning is allowed */
1016 if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1017 v2d->cur.xmin += temp;
1018 v2d->cur.xmax += temp;
1020 if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1021 v2d->cur.ymin += temp;
1022 v2d->cur.ymax += temp;
1027 /* validate that view is in valid configuration after this operation */
1028 UI_view2d_curRect_validate(v2d);
1030 /* request updates to be done... */
1031 WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
1032 /* XXX: add WM_NOTE_TIME_CHANGED? */
1035 /* handle user input for scrollers - calculations of mouse-movement need to be done here, not in the apply callback! */
1036 static int scroller_activate_modal(bContext *C, wmOperator *op, wmEvent *event)
1038 v2dScrollerMove *vsm= op->customdata;
1040 /* execute the events */
1041 switch (event->type) {
1044 /* calculate new delta transform, then store mouse-coordinates for next-time */
1045 if (vsm->zone != SCROLLHANDLE_MIN) {
1046 /* if using bar (i.e. 'panning') or 'max' zoom widget */
1047 switch (vsm->scroller) {
1048 case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */
1049 vsm->delta= (float)(event->x - vsm->lastx);
1051 case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */
1052 vsm->delta= (float)(event->y - vsm->lasty);
1057 /* using 'min' zoom widget */
1058 switch (vsm->scroller) {
1059 case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves with mouse) */
1060 vsm->delta= (float)(vsm->lastx - event->x);
1062 case 'v': /* vertical scroller - so only vertical movement ('cur' moves with to mouse) */
1063 vsm->delta= (float)(vsm->lasty - event->y);
1068 /* store previous coordinates */
1069 vsm->lastx= event->x;
1070 vsm->lasty= event->y;
1072 scroller_activate_apply(C, op);
1077 if (event->val==0) {
1078 scroller_activate_exit(C, op);
1079 return OPERATOR_FINISHED;
1084 return OPERATOR_RUNNING_MODAL;
1088 /* a click (or click drag in progress) should have occurred, so check if it happened in scrollbar */
1089 static int scroller_activate_invoke(bContext *C, wmOperator *op, wmEvent *event)
1092 short in_scroller= 0;
1094 /* firstly, check context to see if mouse is actually in region */
1095 // XXX isn't this the job of poll() callbacks which can't check events, but only context?
1096 if (C->region == NULL)
1097 return OPERATOR_CANCELLED;
1099 v2d= &C->region->v2d;
1101 /* check if mouse in scrollbars, if they're enabled */
1102 in_scroller= mouse_in_v2d_scrollers(C, v2d, event->x, event->y);
1104 /* if in a scroller, init customdata then set modal handler which will catch mousedown to start doing useful stuff */
1106 v2dScrollerMove *vsm;
1108 /* initialise customdata */
1109 scroller_activate_init(C, op, event, in_scroller);
1110 vsm= (v2dScrollerMove *)op->customdata;
1112 /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */
1113 if (vsm->zone == SCROLLHANDLE_BAR) {
1114 if ( ((vsm->scroller=='h') && (v2d->keepofs & V2D_LOCKOFS_X)) ||
1115 ((vsm->scroller=='v') && (v2d->keepofs & V2D_LOCKOFS_Y)) )
1117 /* free customdata initialised */
1118 scroller_activate_exit(C, op);
1120 /* can't catch this event for ourselves, so let it go to someone else? */
1121 return OPERATOR_PASS_THROUGH;
1125 /* still ok, so can add */
1126 WM_event_add_modal_handler(C, &C->window->handlers, op);
1127 return OPERATOR_RUNNING_MODAL;
1130 /* not in scroller, so nothing happened... (pass through let's something else catch event) */
1131 return OPERATOR_PASS_THROUGH;
1135 /* LMB-Drag in Scrollers - not repeatable operator! */
1136 void ED_View2D_OT_scroller_activate(wmOperatorType *ot)
1139 ot->name= "Scroller Activate";
1140 ot->idname= "ED_View2D_OT_scroller_activate";
1143 ot->invoke= scroller_activate_invoke;
1144 ot->modal= scroller_activate_modal;
1147 /* ********************************************************* */
1150 void ui_view2d_operatortypes(void)
1152 WM_operatortype_append(ED_View2D_OT_view_pan);
1154 WM_operatortype_append(ED_View2D_OT_view_scrollleft);
1155 WM_operatortype_append(ED_View2D_OT_view_scrollright);
1156 WM_operatortype_append(ED_View2D_OT_view_scrollup);
1157 WM_operatortype_append(ED_View2D_OT_view_scrolldown);
1159 WM_operatortype_append(ED_View2D_OT_view_zoomin);
1160 WM_operatortype_append(ED_View2D_OT_view_zoomout);
1162 WM_operatortype_append(ED_View2D_OT_view_zoom);
1164 WM_operatortype_append(ED_View2D_OT_scroller_activate);
1167 void UI_view2d_keymap(wmWindowManager *wm)
1169 ListBase *keymap= WM_keymap_listbase(wm, "View2D", 0, 0);
1172 WM_keymap_add_item(keymap, "ED_View2D_OT_view_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
1174 WM_keymap_add_item(keymap, "ED_View2D_OT_view_rightscroll", WHEELDOWNMOUSE, KM_ANY, KM_CTRL, 0);
1175 WM_keymap_add_item(keymap, "ED_View2D_OT_view_leftscroll", WHEELUPMOUSE, KM_ANY, KM_CTRL, 0);
1177 WM_keymap_add_item(keymap, "ED_View2D_OT_view_downscroll", WHEELDOWNMOUSE, KM_ANY, KM_SHIFT, 0);
1178 WM_keymap_add_item(keymap, "ED_View2D_OT_view_upscroll", WHEELUPMOUSE, KM_ANY, KM_SHIFT, 0);
1180 /* zoom - single step */
1181 WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomout", WHEELUPMOUSE, KM_ANY, 0, 0);
1182 WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomin", WHEELDOWNMOUSE, KM_ANY, 0, 0);
1183 WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomout", PADMINUS, KM_PRESS, 0, 0);
1184 WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomin", PADPLUSKEY, KM_PRESS, 0, 0);
1187 WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
1190 WM_keymap_add_item(keymap, "ED_View2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);