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