UI: ignore events in empty region overlap areas
[blender.git] / source / blender / editors / interface / view2d.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup edinterface
22  */
23
24 #include <float.h>
25 #include <limits.h>
26 #include <math.h>
27 #include <string.h>
28
29 #include "MEM_guardedalloc.h"
30
31 #include "DNA_scene_types.h"
32 #include "DNA_userdef_types.h"
33
34 #include "BLI_utildefines.h"
35 #include "BLI_link_utils.h"
36 #include "BLI_rect.h"
37 #include "BLI_math.h"
38 #include "BLI_memarena.h"
39 #include "BLI_timecode.h"
40
41 #include "BKE_context.h"
42 #include "BKE_screen.h"
43 #include "BKE_global.h"
44
45 #include "GPU_immediate.h"
46 #include "GPU_matrix.h"
47 #include "GPU_state.h"
48
49 #include "WM_api.h"
50
51 #include "BLF_api.h"
52
53 #include "ED_screen.h"
54
55 #include "UI_interface.h"
56 #include "UI_view2d.h"
57
58 #include "interface_intern.h"
59
60 static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize, bool mask_scrollers);
61
62 /* *********************************************************************** */
63
64 BLI_INLINE int clamp_float_to_int(const float f)
65 {
66   const float min = INT_MIN;
67   const float max = INT_MAX;
68
69   if (UNLIKELY(f < min)) {
70     return min;
71   }
72   else if (UNLIKELY(f > max)) {
73     return (int)max;
74   }
75   else {
76     return (int)f;
77   }
78 }
79
80 /**
81  * use instead of #BLI_rcti_rctf_copy so we have consistent behavior
82  * with users of #clamp_float_to_int.
83  */
84 BLI_INLINE void clamp_rctf_to_rcti(rcti *dst, const rctf *src)
85 {
86   dst->xmin = clamp_float_to_int(src->xmin);
87   dst->xmax = clamp_float_to_int(src->xmax);
88   dst->ymin = clamp_float_to_int(src->ymin);
89   dst->ymax = clamp_float_to_int(src->ymax);
90 }
91
92 /* XXX still unresolved: scrolls hide/unhide vs region mask handling */
93 /* XXX there's V2D_SCROLL_HORIZONTAL_HIDE and V2D_SCROLL_HORIZONTAL_FULLR ... */
94
95 /**
96  * helper to allow scrollbars to dynamically hide
97  * - returns a copy of the scrollbar settings with the flags to display
98  *   horizontal/vertical scrollbars removed
99  * - input scroll value is the v2d->scroll var
100  * - hide flags are set per region at drawtime
101  */
102 static int view2d_scroll_mapped(int scroll)
103 {
104   if (scroll & V2D_SCROLL_HORIZONTAL_FULLR) {
105     scroll &= ~(V2D_SCROLL_HORIZONTAL);
106   }
107   if (scroll & V2D_SCROLL_VERTICAL_FULLR) {
108     scroll &= ~(V2D_SCROLL_VERTICAL);
109   }
110   return scroll;
111 }
112
113 void UI_view2d_mask_from_win(const View2D *v2d, rcti *r_mask)
114 {
115   r_mask->xmin = 0;
116   r_mask->ymin = 0;
117   r_mask->xmax = v2d->winx - 1; /* -1 yes! masks are pixels */
118   r_mask->ymax = v2d->winy - 1;
119 }
120
121 /**
122  * Called each time #View2D.cur changes, to dynamically update masks.
123  *
124  * \param mask_scroll: Optionally clamp scrollbars by this region.
125  */
126 static void view2d_masks(View2D *v2d, bool check_scrollers, const rcti *mask_scroll)
127 {
128   int scroll;
129
130   /* mask - view frame */
131   UI_view2d_mask_from_win(v2d, &v2d->mask);
132   if (mask_scroll == NULL) {
133     mask_scroll = &v2d->mask;
134   }
135
136   if (check_scrollers) {
137     /* check size if hiding flag is set: */
138     if (v2d->scroll & V2D_SCROLL_HORIZONTAL_HIDE) {
139       if (!(v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL)) {
140         if (BLI_rctf_size_x(&v2d->tot) > BLI_rctf_size_x(&v2d->cur)) {
141           v2d->scroll &= ~V2D_SCROLL_HORIZONTAL_FULLR;
142         }
143         else {
144           v2d->scroll |= V2D_SCROLL_HORIZONTAL_FULLR;
145         }
146       }
147     }
148     if (v2d->scroll & V2D_SCROLL_VERTICAL_HIDE) {
149       if (!(v2d->scroll & V2D_SCROLL_SCALE_VERTICAL)) {
150         if (BLI_rctf_size_y(&v2d->tot) + 0.01f > BLI_rctf_size_y(&v2d->cur)) {
151           v2d->scroll &= ~V2D_SCROLL_VERTICAL_FULLR;
152         }
153         else {
154           v2d->scroll |= V2D_SCROLL_VERTICAL_FULLR;
155         }
156       }
157     }
158   }
159
160   scroll = view2d_scroll_mapped(v2d->scroll);
161
162   /* scrollers are based off regionsize
163    * - they can only be on one to two edges of the region they define
164    * - if they overlap, they must not occupy the corners (which are reserved for other widgets)
165    */
166   if (scroll) {
167     const int scroll_width = (v2d->scroll & V2D_SCROLL_SCALE_VERTICAL) ? V2D_SCROLL_WIDTH_TEXT :
168                                                                          V2D_SCROLL_WIDTH;
169     const int scroll_height = (v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) ?
170                                   V2D_SCROLL_HEIGHT_TEXT :
171                                   V2D_SCROLL_HEIGHT;
172
173     /* vertical scroller */
174     if (scroll & V2D_SCROLL_LEFT) {
175       /* on left-hand edge of region */
176       v2d->vert = *mask_scroll;
177       v2d->vert.xmax = scroll_width;
178     }
179     else if (scroll & V2D_SCROLL_RIGHT) {
180       /* on right-hand edge of region */
181       v2d->vert = *mask_scroll;
182       v2d->vert.xmax++; /* one pixel extra... was leaving a minor gap... */
183       v2d->vert.xmin = v2d->vert.xmax - scroll_width;
184     }
185
186     /* horizontal scroller */
187     if (scroll & (V2D_SCROLL_BOTTOM)) {
188       /* on bottom edge of region */
189       v2d->hor = *mask_scroll;
190       v2d->hor.ymax = scroll_height;
191     }
192     else if (scroll & V2D_SCROLL_TOP) {
193       /* on upper edge of region */
194       v2d->hor = *mask_scroll;
195       v2d->hor.ymin = v2d->hor.ymax - scroll_height;
196     }
197
198     /* adjust vertical scroller if there's a horizontal scroller, to leave corner free */
199     if (scroll & V2D_SCROLL_VERTICAL) {
200       if (scroll & (V2D_SCROLL_BOTTOM)) {
201         /* on bottom edge of region */
202         v2d->vert.ymin = v2d->hor.ymax;
203       }
204       else if (scroll & V2D_SCROLL_TOP) {
205         /* on upper edge of region */
206         v2d->vert.ymax = v2d->hor.ymin;
207       }
208     }
209   }
210 }
211
212 /* Refresh and Validation */
213
214 /**
215  * Initialize all relevant View2D data (including view rects if first time)
216  * and/or refresh mask sizes after view resize.
217  *
218  * - For some of these presets, it is expected that the region will have defined some
219  *   additional settings necessary for the customization of the 2D viewport to its requirements
220  * - This function should only be called from region init() callbacks, where it is expected that
221  *   this is called before #UI_view2d_size_update(),
222  *   as this one checks that the rects are properly initialized.
223  */
224 void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy)
225 {
226   bool tot_changed = false, do_init;
227   uiStyle *style = UI_style_get();
228
229   do_init = (v2d->flag & V2D_IS_INITIALISED) == 0;
230
231   /* see eView2D_CommonViewTypes in UI_view2d.h for available view presets */
232   switch (type) {
233     /* 'standard view' - optimum setup for 'standard' view behavior,
234      * that should be used new views as basis for their
235      * own unique View2D settings, which should be used instead of this in most cases...
236      */
237     case V2D_COMMONVIEW_STANDARD: {
238       /* for now, aspect ratio should be maintained,
239        * and zoom is clamped within sane default limits */
240       v2d->keepzoom = (V2D_KEEPASPECT | V2D_LIMITZOOM);
241       v2d->minzoom = 0.01f;
242       v2d->maxzoom = 1000.0f;
243
244       /* View2D tot rect and cur should be same size,
245        * and aligned using 'standard' OpenGL coordinates for now:
246        * - region can resize 'tot' later to fit other data
247        * - keeptot is only within bounds, as strict locking is not that critical
248        * - view is aligned for (0,0) -> (winx-1, winy-1) setup
249        */
250       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y);
251       v2d->keeptot = V2D_KEEPTOT_BOUNDS;
252
253       if (do_init) {
254         v2d->tot.xmin = v2d->tot.ymin = 0.0f;
255         v2d->tot.xmax = (float)(winx - 1);
256         v2d->tot.ymax = (float)(winy - 1);
257
258         v2d->cur = v2d->tot;
259       }
260       /* scrollers - should we have these by default? */
261       /* XXX for now, we don't override this, or set it either! */
262       break;
263     }
264     /* 'list/channel view' - zoom, aspect ratio, and alignment restrictions are set here */
265     case V2D_COMMONVIEW_LIST: {
266       /* zoom + aspect ratio are locked */
267       v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT);
268       v2d->minzoom = v2d->maxzoom = 1.0f;
269
270       /* tot rect has strictly regulated placement, and must only occur in +/- quadrant */
271       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y);
272       v2d->keeptot = V2D_KEEPTOT_STRICT;
273       tot_changed = do_init;
274
275       /* scroller settings are currently not set here... that is left for regions... */
276       break;
277     }
278     /* 'stack view' - practically the same as list/channel view,
279      * except is located in the pos y half instead.
280      * Zoom, aspect ratio, and alignment restrictions are set here. */
281     case V2D_COMMONVIEW_STACK: {
282       /* zoom + aspect ratio are locked */
283       v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT);
284       v2d->minzoom = v2d->maxzoom = 1.0f;
285
286       /* tot rect has strictly regulated placement, and must only occur in +/+ quadrant */
287       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y);
288       v2d->keeptot = V2D_KEEPTOT_STRICT;
289       tot_changed = do_init;
290
291       /* scroller settings are currently not set here... that is left for regions... */
292       break;
293     }
294     /* 'header' regions - zoom, aspect ratio,
295      * alignment, and panning restrictions are set here */
296     case V2D_COMMONVIEW_HEADER: {
297       /* zoom + aspect ratio are locked */
298       v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT);
299       v2d->minzoom = v2d->maxzoom = 1.0f;
300
301       if (do_init) {
302         v2d->tot.xmin = 0.0f;
303         v2d->tot.xmax = winx;
304         v2d->tot.ymin = 0.0f;
305         v2d->tot.ymax = winy;
306         v2d->cur = v2d->tot;
307
308         v2d->min[0] = v2d->max[0] = (float)(winx - 1);
309         v2d->min[1] = v2d->max[1] = (float)(winy - 1);
310       }
311       /* tot rect has strictly regulated placement, and must only occur in +/+ quadrant */
312       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y);
313       v2d->keeptot = V2D_KEEPTOT_STRICT;
314       tot_changed = do_init;
315
316       /* panning in y-axis is prohibited */
317       v2d->keepofs = V2D_LOCKOFS_Y;
318
319       /* absolutely no scrollers allowed */
320       v2d->scroll = 0;
321       break;
322     }
323     /* panels view, with horizontal/vertical align */
324     case V2D_COMMONVIEW_PANELS_UI: {
325
326       /* for now, aspect ratio should be maintained,
327        * and zoom is clamped within sane default limits */
328       v2d->keepzoom = (V2D_KEEPASPECT | V2D_LIMITZOOM | V2D_KEEPZOOM);
329       v2d->minzoom = 0.5f;
330       v2d->maxzoom = 2.0f;
331
332       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y);
333       v2d->keeptot = V2D_KEEPTOT_BOUNDS;
334
335       /* note, scroll is being flipped in ED_region_panels() drawing */
336       v2d->scroll |= (V2D_SCROLL_HORIZONTAL_HIDE | V2D_SCROLL_VERTICAL_HIDE);
337
338       /* initialize without scroll bars (interferes with zoom level see: T47047) */
339       if (do_init) {
340         v2d->scroll |= (V2D_SCROLL_VERTICAL_FULLR | V2D_SCROLL_HORIZONTAL_FULLR);
341       }
342
343       if (do_init) {
344         float panelzoom = (style) ? style->panelzoom : 1.0f;
345
346         v2d->tot.xmin = 0.0f;
347         v2d->tot.xmax = winx;
348
349         v2d->tot.ymax = 0.0f;
350         v2d->tot.ymin = -winy;
351
352         v2d->cur.xmin = 0.0f;
353         v2d->cur.xmax = (winx)*panelzoom;
354
355         v2d->cur.ymax = 0.0f;
356         v2d->cur.ymin = (-winy) * panelzoom;
357       }
358       break;
359     }
360     /* other view types are completely defined using their own settings already */
361     default:
362       /* we don't do anything here,
363        * as settings should be fine, but just make sure that rect */
364       break;
365   }
366
367   /* set initialized flag so that View2D doesn't get reinitialised next time again */
368   v2d->flag |= V2D_IS_INITIALISED;
369
370   /* store view size */
371   v2d->winx = winx;
372   v2d->winy = winy;
373
374   /* set masks (always do), but leave scroller scheck to totrect_set */
375   view2d_masks(v2d, 0, NULL);
376
377   if (do_init) {
378     /* Visible by default. */
379     v2d->alpha_hor = v2d->alpha_vert = 255;
380   }
381
382   /* set 'tot' rect before setting cur? */
383   /* XXX confusing stuff here still -
384    * I made this function not check scroller hide - that happens in totrect_set */
385   if (tot_changed) {
386     UI_view2d_totRect_set_resize(v2d, winx, winy, !do_init);
387   }
388   else {
389     ui_view2d_curRect_validate_resize(v2d, !do_init, 0);
390   }
391 }
392
393 /**
394  * Ensure View2D rects remain in a viable configuration
395  * 'cur' is not allowed to be: larger than max, smaller than min, or outside of 'tot'
396  */
397 // XXX pre2.5 -> this used to be called  test_view2d()
398 static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize, bool mask_scrollers)
399 {
400   float totwidth, totheight, curwidth, curheight, width, height;
401   float winx, winy;
402   rctf *cur, *tot;
403
404   /* use mask as size of region that View2D resides in, as it takes into account
405    * scrollbars already - keep in sync with zoomx/zoomy in view_zoomstep_apply_ex! */
406   winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1);
407   winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1);
408
409   /* get pointers to rcts for less typing */
410   cur = &v2d->cur;
411   tot = &v2d->tot;
412
413   /* we must satisfy the following constraints (in decreasing order of importance):
414    * - alignment restrictions are respected
415    * - cur must not fall outside of tot
416    * - axis locks (zoom and offset) must be maintained
417    * - zoom must not be excessive (check either sizes or zoom values)
418    * - aspect ratio should be respected (NOTE: this is quite closely related to zoom too)
419    */
420
421   /* Step 1: if keepzoom, adjust the sizes of the rects only
422    * - firstly, we calculate the sizes of the rects
423    * - curwidth and curheight are saved as reference... modify width and height values here
424    */
425   totwidth = BLI_rctf_size_x(tot);
426   totheight = BLI_rctf_size_y(tot);
427   /* keep in sync with zoomx/zoomy in view_zoomstep_apply_ex! */
428   curwidth = width = BLI_rctf_size_x(cur);
429   curheight = height = BLI_rctf_size_y(cur);
430
431   /* if zoom is locked, size on the appropriate axis is reset to mask size */
432   if (v2d->keepzoom & V2D_LOCKZOOM_X) {
433     width = winx;
434   }
435   if (v2d->keepzoom & V2D_LOCKZOOM_Y) {
436     height = winy;
437   }
438
439   /* values used to divide, so make it safe
440    * NOTE: width and height must use FLT_MIN instead of 1, otherwise it is impossible to
441    *       get enough resolution in Graph Editor for editing some curves
442    */
443   if (width < FLT_MIN) {
444     width = 1;
445   }
446   if (height < FLT_MIN) {
447     height = 1;
448   }
449   if (winx < 1) {
450     winx = 1;
451   }
452   if (winy < 1) {
453     winy = 1;
454   }
455
456   /* V2D_LIMITZOOM indicates that zoom level should be preserved when the window size changes */
457   if (resize && (v2d->keepzoom & V2D_KEEPZOOM)) {
458     float zoom, oldzoom;
459
460     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
461       zoom = winx / width;
462       oldzoom = v2d->oldwinx / curwidth;
463
464       if (oldzoom != zoom) {
465         width *= zoom / oldzoom;
466       }
467     }
468
469     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
470       zoom = winy / height;
471       oldzoom = v2d->oldwiny / curheight;
472
473       if (oldzoom != zoom) {
474         height *= zoom / oldzoom;
475       }
476     }
477   }
478   /* keepzoom (V2D_LIMITZOOM set), indicates that zoom level on each axis must not exceed limits
479    * NOTE: in general, it is not expected that the lock-zoom will be used in conjunction with this
480    */
481   else if (v2d->keepzoom & V2D_LIMITZOOM) {
482
483     /* check if excessive zoom on x-axis */
484     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
485       const float zoom = winx / width;
486       if (zoom < v2d->minzoom) {
487         width = winx / v2d->minzoom;
488       }
489       else if (zoom > v2d->maxzoom) {
490         width = winx / v2d->maxzoom;
491       }
492     }
493
494     /* check if excessive zoom on y-axis */
495     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
496       const float zoom = winy / height;
497       if (zoom < v2d->minzoom) {
498         height = winy / v2d->minzoom;
499       }
500       else if (zoom > v2d->maxzoom) {
501         height = winy / v2d->maxzoom;
502       }
503     }
504   }
505   else {
506     /* make sure sizes don't exceed that of the min/max sizes
507      * (even though we're not doing zoom clamping) */
508     CLAMP(width, v2d->min[0], v2d->max[0]);
509     CLAMP(height, v2d->min[1], v2d->max[1]);
510   }
511
512   /* check if we should restore aspect ratio (if view size changed) */
513   if (v2d->keepzoom & V2D_KEEPASPECT) {
514     bool do_x = false, do_y = false, do_cur /* , do_win */ /* UNUSED */;
515     float curRatio, winRatio;
516
517     /* when a window edge changes, the aspect ratio can't be used to
518      * find which is the best new 'cur' rect. that's why it stores 'old'
519      */
520     if (winx != v2d->oldwinx) {
521       do_x = true;
522     }
523     if (winy != v2d->oldwiny) {
524       do_y = true;
525     }
526
527     curRatio = height / width;
528     winRatio = winy / winx;
529
530     /* both sizes change (area/region maximized)  */
531     if (do_x == do_y) {
532       if (do_x && do_y) {
533         /* here is 1,1 case, so all others must be 0,0 */
534         if (fabsf(winx - v2d->oldwinx) > fabsf(winy - v2d->oldwiny)) {
535           do_y = false;
536         }
537         else {
538           do_x = false;
539         }
540       }
541       else if (winRatio > curRatio) {
542         do_x = false;
543       }
544       else {
545         do_x = true;
546       }
547     }
548     do_cur = do_x;
549     /* do_win = do_y; */ /* UNUSED */
550
551     if (do_cur) {
552       if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winx != v2d->oldwinx)) {
553         /* Special exception for Outliner (and later channel-lists):
554          * - The view may be moved left to avoid contents
555          *   being pushed out of view when view shrinks.
556          * - The keeptot code will make sure cur->xmin will not be less than tot->xmin
557          *   (which cannot be allowed).
558          * - width is not adjusted for changed ratios here.
559          */
560         if (winx < v2d->oldwinx) {
561           float temp = v2d->oldwinx - winx;
562
563           cur->xmin -= temp;
564           cur->xmax -= temp;
565
566           /* width does not get modified, as keepaspect here is just set to make
567            * sure visible area adjusts to changing view shape!
568            */
569         }
570       }
571       else {
572         /* portrait window: correct for x */
573         width = height / winRatio;
574       }
575     }
576     else {
577       if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winy != v2d->oldwiny)) {
578         /* special exception for Outliner (and later channel-lists):
579          * - Currently, no actions need to be taken here...
580          */
581
582         if (winy < v2d->oldwiny) {
583           float temp = v2d->oldwiny - winy;
584
585           if (v2d->align & V2D_ALIGN_NO_NEG_Y) {
586             cur->ymin -= temp;
587             cur->ymax -= temp;
588           }
589           else { /* Assume V2D_ALIGN_NO_POS_Y or combination */
590             cur->ymin += temp;
591             cur->ymax += temp;
592           }
593         }
594       }
595       else {
596         /* landscape window: correct for y */
597         height = width * winRatio;
598       }
599     }
600
601     /* store region size for next time */
602     v2d->oldwinx = (short)winx;
603     v2d->oldwiny = (short)winy;
604   }
605
606   /* Step 2: apply new sizes to cur rect,
607    * but need to take into account alignment settings here... */
608   if ((width != curwidth) || (height != curheight)) {
609     float temp, dh;
610
611     /* resize from centerpoint, unless otherwise specified */
612     if (width != curwidth) {
613       if (v2d->keepofs & V2D_LOCKOFS_X) {
614         cur->xmax += width - BLI_rctf_size_x(cur);
615       }
616       else if (v2d->keepofs & V2D_KEEPOFS_X) {
617         if (v2d->align & V2D_ALIGN_NO_POS_X) {
618           cur->xmin -= width - BLI_rctf_size_x(cur);
619         }
620         else {
621           cur->xmax += width - BLI_rctf_size_x(cur);
622         }
623       }
624       else {
625         temp = BLI_rctf_cent_x(cur);
626         dh = width * 0.5f;
627
628         cur->xmin = temp - dh;
629         cur->xmax = temp + dh;
630       }
631     }
632     if (height != curheight) {
633       if (v2d->keepofs & V2D_LOCKOFS_Y) {
634         cur->ymax += height - BLI_rctf_size_y(cur);
635       }
636       else if (v2d->keepofs & V2D_KEEPOFS_Y) {
637         if (v2d->align & V2D_ALIGN_NO_POS_Y) {
638           cur->ymin -= height - BLI_rctf_size_y(cur);
639         }
640         else {
641           cur->ymax += height - BLI_rctf_size_y(cur);
642         }
643       }
644       else {
645         temp = BLI_rctf_cent_y(cur);
646         dh = height * 0.5f;
647
648         cur->ymin = temp - dh;
649         cur->ymax = temp + dh;
650       }
651     }
652   }
653
654   /* Step 3: adjust so that it doesn't fall outside of bounds of 'tot' */
655   if (v2d->keeptot) {
656     float temp, diff;
657
658     /* recalculate extents of cur */
659     curwidth = BLI_rctf_size_x(cur);
660     curheight = BLI_rctf_size_y(cur);
661
662     /* width */
663     if ((curwidth > totwidth) &&
664         !(v2d->keepzoom & (V2D_KEEPZOOM | V2D_LOCKZOOM_X | V2D_LIMITZOOM))) {
665       /* if zoom doesn't have to be maintained, just clamp edges */
666       if (cur->xmin < tot->xmin) {
667         cur->xmin = tot->xmin;
668       }
669       if (cur->xmax > tot->xmax) {
670         cur->xmax = tot->xmax;
671       }
672     }
673     else if (v2d->keeptot == V2D_KEEPTOT_STRICT) {
674       /* This is an exception for the outliner (and later channel-lists, headers)
675        * - must clamp within tot rect (absolutely no excuses)
676        * --> therefore, cur->xmin must not be less than tot->xmin
677        */
678       if (cur->xmin < tot->xmin) {
679         /* move cur across so that it sits at minimum of tot */
680         temp = tot->xmin - cur->xmin;
681
682         cur->xmin += temp;
683         cur->xmax += temp;
684       }
685       else if (cur->xmax > tot->xmax) {
686         /* - only offset by difference of cur-xmax and tot-xmax if that would not move
687          *   cur-xmin to lie past tot-xmin
688          * - otherwise, simply shift to tot-xmin???
689          */
690         temp = cur->xmax - tot->xmax;
691
692         if ((cur->xmin - temp) < tot->xmin) {
693           /* only offset by difference from cur-min and tot-min */
694           temp = cur->xmin - tot->xmin;
695
696           cur->xmin -= temp;
697           cur->xmax -= temp;
698         }
699         else {
700           cur->xmin -= temp;
701           cur->xmax -= temp;
702         }
703       }
704     }
705     else {
706       /* This here occurs when:
707        * - width too big, but maintaining zoom (i.e. widths cannot be changed)
708        * - width is OK, but need to check if outside of boundaries
709        *
710        * So, resolution is to just shift view by the gap between the extremities.
711        * We favour moving the 'minimum' across, as that's origin for most things
712        * (XXX - in the past, max was favored... if there are bugs, swap!)
713        */
714       if ((cur->xmin < tot->xmin) && (cur->xmax > tot->xmax)) {
715         /* outside boundaries on both sides,
716          * so take middle-point of tot, and place in balanced way */
717         temp = BLI_rctf_cent_x(tot);
718         diff = curwidth * 0.5f;
719
720         cur->xmin = temp - diff;
721         cur->xmax = temp + diff;
722       }
723       else if (cur->xmin < tot->xmin) {
724         /* move cur across so that it sits at minimum of tot */
725         temp = tot->xmin - cur->xmin;
726
727         cur->xmin += temp;
728         cur->xmax += temp;
729       }
730       else if (cur->xmax > tot->xmax) {
731         /* - only offset by difference of cur-xmax and tot-xmax if that would not move
732          *   cur-xmin to lie past tot-xmin
733          * - otherwise, simply shift to tot-xmin???
734          */
735         temp = cur->xmax - tot->xmax;
736
737         if ((cur->xmin - temp) < tot->xmin) {
738           /* only offset by difference from cur-min and tot-min */
739           temp = cur->xmin - tot->xmin;
740
741           cur->xmin -= temp;
742           cur->xmax -= temp;
743         }
744         else {
745           cur->xmin -= temp;
746           cur->xmax -= temp;
747         }
748       }
749     }
750
751     /* height */
752     if ((curheight > totheight) &&
753         !(v2d->keepzoom & (V2D_KEEPZOOM | V2D_LOCKZOOM_Y | V2D_LIMITZOOM))) {
754       /* if zoom doesn't have to be maintained, just clamp edges */
755       if (cur->ymin < tot->ymin) {
756         cur->ymin = tot->ymin;
757       }
758       if (cur->ymax > tot->ymax) {
759         cur->ymax = tot->ymax;
760       }
761     }
762     else {
763       /* This here occurs when:
764        * - height too big, but maintaining zoom (i.e. heights cannot be changed)
765        * - height is OK, but need to check if outside of boundaries
766        *
767        * So, resolution is to just shift view by the gap between the extremities.
768        * We favour moving the 'minimum' across, as that's origin for most things
769        */
770       if ((cur->ymin < tot->ymin) && (cur->ymax > tot->ymax)) {
771         /* outside boundaries on both sides,
772          * so take middle-point of tot, and place in balanced way */
773         temp = BLI_rctf_cent_y(tot);
774         diff = curheight * 0.5f;
775
776         cur->ymin = temp - diff;
777         cur->ymax = temp + diff;
778       }
779       else if (cur->ymin < tot->ymin) {
780         /* there's still space remaining, so shift up */
781         temp = tot->ymin - cur->ymin;
782
783         cur->ymin += temp;
784         cur->ymax += temp;
785       }
786       else if (cur->ymax > tot->ymax) {
787         /* there's still space remaining, so shift down */
788         temp = cur->ymax - tot->ymax;
789
790         cur->ymin -= temp;
791         cur->ymax -= temp;
792       }
793     }
794   }
795
796   /* Step 4: Make sure alignment restrictions are respected */
797   if (v2d->align) {
798     /* If alignment flags are set (but keeptot is not), they must still be respected, as although
799      * they don't specify any particular bounds to stay within, they do define ranges which are
800      * invalid.
801      *
802      * Here, we only check to make sure that on each axis, the 'cur' rect doesn't stray into these
803      * invalid zones, otherwise we offset.
804      */
805
806     /* handle width - posx and negx flags are mutually exclusive, so watch out */
807     if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
808       /* width is in negative-x half */
809       if (v2d->cur.xmax > 0) {
810         v2d->cur.xmin -= v2d->cur.xmax;
811         v2d->cur.xmax = 0.0f;
812       }
813     }
814     else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
815       /* width is in positive-x half */
816       if (v2d->cur.xmin < 0) {
817         v2d->cur.xmax -= v2d->cur.xmin;
818         v2d->cur.xmin = 0.0f;
819       }
820     }
821
822     /* handle height - posx and negx flags are mutually exclusive, so watch out */
823     if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
824       /* height is in negative-y half */
825       if (v2d->cur.ymax > 0) {
826         v2d->cur.ymin -= v2d->cur.ymax;
827         v2d->cur.ymax = 0.0f;
828       }
829     }
830     else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
831       /* height is in positive-y half */
832       if (v2d->cur.ymin < 0) {
833         v2d->cur.ymax -= v2d->cur.ymin;
834         v2d->cur.ymin = 0.0f;
835       }
836     }
837   }
838
839   /* set masks */
840   view2d_masks(v2d, mask_scrollers, NULL);
841 }
842
843 void UI_view2d_curRect_validate(View2D *v2d)
844 {
845   ui_view2d_curRect_validate_resize(v2d, 0, 1);
846 }
847
848 /* ------------------ */
849
850 /* Called by menus to activate it, or by view2d operators
851  * to make sure 'related' views stay in synchrony */
852 void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag)
853 {
854   ScrArea *sa;
855   ARegion *ar;
856
857   /* don't continue if no view syncing to be done */
858   if ((v2dcur->flag & (V2D_VIEWSYNC_SCREEN_TIME | V2D_VIEWSYNC_AREA_VERTICAL)) == 0) {
859     return;
860   }
861
862   /* check if doing within area syncing (i.e. channels/vertical) */
863   if ((v2dcur->flag & V2D_VIEWSYNC_AREA_VERTICAL) && (area)) {
864     for (ar = area->regionbase.first; ar; ar = ar->next) {
865       /* don't operate on self */
866       if (v2dcur != &ar->v2d) {
867         /* only if view has vertical locks enabled */
868         if (ar->v2d.flag & V2D_VIEWSYNC_AREA_VERTICAL) {
869           if (flag == V2D_LOCK_COPY) {
870             /* other views with locks on must copy active */
871             ar->v2d.cur.ymin = v2dcur->cur.ymin;
872             ar->v2d.cur.ymax = v2dcur->cur.ymax;
873           }
874           else { /* V2D_LOCK_SET */
875                  /* active must copy others */
876             v2dcur->cur.ymin = ar->v2d.cur.ymin;
877             v2dcur->cur.ymax = ar->v2d.cur.ymax;
878           }
879
880           /* region possibly changed, so refresh */
881           ED_region_tag_redraw_no_rebuild(ar);
882         }
883       }
884     }
885   }
886
887   /* check if doing whole screen syncing (i.e. time/horizontal) */
888   if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) {
889     for (sa = screen->areabase.first; sa; sa = sa->next) {
890       for (ar = sa->regionbase.first; ar; ar = ar->next) {
891         /* don't operate on self */
892         if (v2dcur != &ar->v2d) {
893           /* only if view has horizontal locks enabled */
894           if (ar->v2d.flag & V2D_VIEWSYNC_SCREEN_TIME) {
895             if (flag == V2D_LOCK_COPY) {
896               /* other views with locks on must copy active */
897               ar->v2d.cur.xmin = v2dcur->cur.xmin;
898               ar->v2d.cur.xmax = v2dcur->cur.xmax;
899             }
900             else { /* V2D_LOCK_SET */
901                    /* active must copy others */
902               v2dcur->cur.xmin = ar->v2d.cur.xmin;
903               v2dcur->cur.xmax = ar->v2d.cur.xmax;
904             }
905
906             /* region possibly changed, so refresh */
907             ED_region_tag_redraw_no_rebuild(ar);
908           }
909         }
910       }
911     }
912   }
913 }
914
915 /**
916  * Restore 'cur' rect to standard orientation (i.e. optimal maximum view of tot).
917  * This does not take into account if zooming the view on an axis
918  * will improve the view (if allowed).
919  */
920 void UI_view2d_curRect_reset(View2D *v2d)
921 {
922   float width, height;
923
924   /* assume width and height of 'cur' rect by default, should be same size as mask */
925   width = (float)(BLI_rcti_size_x(&v2d->mask) + 1);
926   height = (float)(BLI_rcti_size_y(&v2d->mask) + 1);
927
928   /* handle width - posx and negx flags are mutually exclusive, so watch out */
929   if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
930     /* width is in negative-x half */
931     v2d->cur.xmin = -width;
932     v2d->cur.xmax = 0.0f;
933   }
934   else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
935     /* width is in positive-x half */
936     v2d->cur.xmin = 0.0f;
937     v2d->cur.xmax = width;
938   }
939   else {
940     /* width is centered around (x == 0) */
941     const float dx = width / 2.0f;
942
943     v2d->cur.xmin = -dx;
944     v2d->cur.xmax = dx;
945   }
946
947   /* handle height - posx and negx flags are mutually exclusive, so watch out */
948   if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
949     /* height is in negative-y half */
950     v2d->cur.ymin = -height;
951     v2d->cur.ymax = 0.0f;
952   }
953   else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
954     /* height is in positive-y half */
955     v2d->cur.ymin = 0.0f;
956     v2d->cur.ymax = height;
957   }
958   else {
959     /* height is centered around (y == 0) */
960     const float dy = height / 2.0f;
961
962     v2d->cur.ymin = -dy;
963     v2d->cur.ymax = dy;
964   }
965 }
966
967 /* ------------------ */
968
969 /* Change the size of the maximum viewable area (i.e. 'tot' rect) */
970 void UI_view2d_totRect_set_resize(View2D *v2d, int width, int height, bool resize)
971 {
972   //  int scroll = view2d_scroll_mapped(v2d->scroll);
973
974   /* don't do anything if either value is 0 */
975   width = abs(width);
976   height = abs(height);
977
978   /* hrumf! */
979   /* XXX: there are work arounds for this in the panel and file browse code. */
980   /* round to int, because this is called with width + V2D_SCROLL_WIDTH */
981   //  if (scroll & V2D_SCROLL_HORIZONTAL) {
982   //      width -= (int)V2D_SCROLL_WIDTH;
983   //  }
984   //  if (scroll & V2D_SCROLL_VERTICAL) {
985   //      height -= (int)V2D_SCROLL_HEIGHT;
986   //  }
987
988   if (ELEM(0, width, height)) {
989     if (G.debug & G_DEBUG) {
990       printf("Error: View2D totRect set exiting: v2d=%p width=%d height=%d\n",
991              (void *)v2d,
992              width,
993              height);  // XXX temp debug info
994     }
995     return;
996   }
997
998   /* handle width - posx and negx flags are mutually exclusive, so watch out */
999   if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
1000     /* width is in negative-x half */
1001     v2d->tot.xmin = (float)-width;
1002     v2d->tot.xmax = 0.0f;
1003   }
1004   else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
1005     /* width is in positive-x half */
1006     v2d->tot.xmin = 0.0f;
1007     v2d->tot.xmax = (float)width;
1008   }
1009   else {
1010     /* width is centered around (x == 0) */
1011     const float dx = (float)width / 2.0f;
1012
1013     v2d->tot.xmin = -dx;
1014     v2d->tot.xmax = dx;
1015   }
1016
1017   /* handle height - posx and negx flags are mutually exclusive, so watch out */
1018   if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
1019     /* height is in negative-y half */
1020     v2d->tot.ymin = (float)-height;
1021     v2d->tot.ymax = 0.0f;
1022   }
1023   else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
1024     /* height is in positive-y half */
1025     v2d->tot.ymin = 0.0f;
1026     v2d->tot.ymax = (float)height;
1027   }
1028   else {
1029     /* height is centered around (y == 0) */
1030     const float dy = (float)height / 2.0f;
1031
1032     v2d->tot.ymin = -dy;
1033     v2d->tot.ymax = dy;
1034   }
1035
1036   /* make sure that 'cur' rect is in a valid state as a result of these changes */
1037   ui_view2d_curRect_validate_resize(v2d, resize, 1);
1038 }
1039
1040 void UI_view2d_totRect_set(View2D *v2d, int width, int height)
1041 {
1042   int scroll = view2d_scroll_mapped(v2d->scroll);
1043
1044   UI_view2d_totRect_set_resize(v2d, width, height, 0);
1045
1046   /* solve bad recursion... if scroller state changed,
1047    * mask is different, so you get different rects */
1048   if (scroll != view2d_scroll_mapped(v2d->scroll)) {
1049     UI_view2d_totRect_set_resize(v2d, width, height, 0);
1050   }
1051 }
1052
1053 bool UI_view2d_tab_set(View2D *v2d, int tab)
1054 {
1055   float default_offset[2] = {0.0f, 0.0f};
1056   float *offset, *new_offset;
1057   bool changed = false;
1058
1059   /* if tab changed, change offset */
1060   if (tab != v2d->tab_cur && v2d->tab_offset) {
1061     if (tab < v2d->tab_num) {
1062       offset = &v2d->tab_offset[tab * 2];
1063     }
1064     else {
1065       offset = default_offset;
1066     }
1067
1068     v2d->cur.xmax += offset[0] - v2d->cur.xmin;
1069     v2d->cur.xmin = offset[0];
1070
1071     v2d->cur.ymin += offset[1] - v2d->cur.ymax;
1072     v2d->cur.ymax = offset[1];
1073
1074     /* validation should happen in subsequent totRect_set */
1075
1076     changed = true;
1077   }
1078
1079   /* resize array if needed */
1080   if (tab >= v2d->tab_num) {
1081     new_offset = MEM_callocN(sizeof(float) * (tab + 1) * 2, "view2d tab offset");
1082
1083     if (v2d->tab_offset) {
1084       memcpy(new_offset, v2d->tab_offset, sizeof(float) * v2d->tab_num * 2);
1085       MEM_freeN(v2d->tab_offset);
1086     }
1087
1088     v2d->tab_offset = new_offset;
1089     v2d->tab_num = tab + 1;
1090   }
1091
1092   /* set current tab and offset */
1093   v2d->tab_cur = tab;
1094   v2d->tab_offset[2 * tab + 0] = v2d->cur.xmin;
1095   v2d->tab_offset[2 * tab + 1] = v2d->cur.ymax;
1096
1097   return changed;
1098 }
1099
1100 void UI_view2d_zoom_cache_reset(void)
1101 {
1102   /* TODO(sergey): This way we avoid threading conflict with sequencer rendering
1103    * text strip. But ideally we want to make glyph cache to be fully safe
1104    * for threading.
1105    */
1106   if (G.is_rendering) {
1107     return;
1108   }
1109   /* While scaling we can accumulate fonts at many sizes (~20 or so).
1110    * Not an issue with embedded font, but can use over 500Mb with i18n ones! See [#38244]. */
1111
1112   /* note: only some views draw text, we could check for this case to avoid clearning cache */
1113   BLF_cache_clear();
1114 }
1115
1116 /* *********************************************************************** */
1117 /* View Matrix Setup */
1118
1119 /* mapping function to ensure 'cur' draws extended over the area where sliders are */
1120 static void view2d_map_cur_using_mask(View2D *v2d, rctf *r_curmasked)
1121 {
1122   *r_curmasked = v2d->cur;
1123
1124   if (view2d_scroll_mapped(v2d->scroll)) {
1125     float sizex = BLI_rcti_size_x(&v2d->mask);
1126     float sizey = BLI_rcti_size_y(&v2d->mask);
1127
1128     /* prevent tiny or narrow regions to get
1129      * invalid coordinates - mask can get negative even... */
1130     if (sizex > 0.0f && sizey > 0.0f) {
1131       float dx = BLI_rctf_size_x(&v2d->cur) / (sizex + 1);
1132       float dy = BLI_rctf_size_y(&v2d->cur) / (sizey + 1);
1133
1134       if (v2d->mask.xmin != 0) {
1135         r_curmasked->xmin -= dx * (float)v2d->mask.xmin;
1136       }
1137       if (v2d->mask.xmax + 1 != v2d->winx) {
1138         r_curmasked->xmax += dx * (float)(v2d->winx - v2d->mask.xmax - 1);
1139       }
1140
1141       if (v2d->mask.ymin != 0) {
1142         r_curmasked->ymin -= dy * (float)v2d->mask.ymin;
1143       }
1144       if (v2d->mask.ymax + 1 != v2d->winy) {
1145         r_curmasked->ymax += dy * (float)(v2d->winy - v2d->mask.ymax - 1);
1146       }
1147     }
1148   }
1149 }
1150
1151 /* Set view matrices to use 'cur' rect as viewing frame for View2D drawing */
1152 void UI_view2d_view_ortho(View2D *v2d)
1153 {
1154   rctf curmasked;
1155   const int sizex = BLI_rcti_size_x(&v2d->mask);
1156   const int sizey = BLI_rcti_size_y(&v2d->mask);
1157   const float eps = 0.001f;
1158   float xofs = 0.0f, yofs = 0.0f;
1159
1160   /* Pixel offsets (-GLA_PIXEL_OFS) are needed to get 1:1
1161    * correspondence with pixels for smooth UI drawing,
1162    * but only applied where requested.
1163    */
1164   /* XXX brecht: instead of zero at least use a tiny offset, otherwise
1165    * pixel rounding is effectively random due to float inaccuracy */
1166   if (sizex > 0) {
1167     xofs = eps * BLI_rctf_size_x(&v2d->cur) / sizex;
1168   }
1169   if (sizey > 0) {
1170     yofs = eps * BLI_rctf_size_y(&v2d->cur) / sizey;
1171   }
1172
1173   /* apply mask-based adjustments to cur rect (due to scrollers),
1174    * to eliminate scaling artifacts */
1175   view2d_map_cur_using_mask(v2d, &curmasked);
1176
1177   BLI_rctf_translate(&curmasked, -xofs, -yofs);
1178
1179   /* XXX ton: this flag set by outliner, for icons */
1180   if (v2d->flag & V2D_PIXELOFS_X) {
1181     curmasked.xmin = floorf(curmasked.xmin) - (eps + xofs);
1182     curmasked.xmax = floorf(curmasked.xmax) - (eps + xofs);
1183   }
1184   if (v2d->flag & V2D_PIXELOFS_Y) {
1185     curmasked.ymin = floorf(curmasked.ymin) - (eps + yofs);
1186     curmasked.ymax = floorf(curmasked.ymax) - (eps + yofs);
1187   }
1188
1189   /* set matrix on all appropriate axes */
1190   wmOrtho2(curmasked.xmin, curmasked.xmax, curmasked.ymin, curmasked.ymax);
1191 }
1192
1193 /**
1194  * Set view matrices to only use one axis of 'cur' only
1195  *
1196  * \param xaxis: if non-zero, only use cur x-axis,
1197  * otherwise use cur-yaxis (mostly this will be used for x).
1198  */
1199 void UI_view2d_view_orthoSpecial(ARegion *ar, View2D *v2d, const bool xaxis)
1200 {
1201   rctf curmasked;
1202   float xofs, yofs;
1203
1204   /* Pixel offsets (-GLA_PIXEL_OFS) are needed to get 1:1
1205    * correspondence with pixels for smooth UI drawing,
1206    * but only applied where requested.
1207    */
1208   /* XXX temp (ton) */
1209   xofs = 0.0f;  // (v2d->flag & V2D_PIXELOFS_X) ? GLA_PIXEL_OFS : 0.0f;
1210   yofs = 0.0f;  // (v2d->flag & V2D_PIXELOFS_Y) ? GLA_PIXEL_OFS : 0.0f;
1211
1212   /* apply mask-based adjustments to cur rect (due to scrollers),
1213    * to eliminate scaling artifacts */
1214   view2d_map_cur_using_mask(v2d, &curmasked);
1215
1216   /* only set matrix with 'cur' coordinates on relevant axes */
1217   if (xaxis) {
1218     wmOrtho2(curmasked.xmin - xofs, curmasked.xmax - xofs, -yofs, ar->winy - yofs);
1219   }
1220   else {
1221     wmOrtho2(-xofs, ar->winx - xofs, curmasked.ymin - yofs, curmasked.ymax - yofs);
1222   }
1223 }
1224
1225 /* Restore view matrices after drawing */
1226 void UI_view2d_view_restore(const bContext *C)
1227 {
1228   ARegion *ar = CTX_wm_region(C);
1229   int width = BLI_rcti_size_x(&ar->winrct) + 1;
1230   int height = BLI_rcti_size_y(&ar->winrct) + 1;
1231
1232   wmOrtho2(0.0f, (float)width, 0.0f, (float)height);
1233   GPU_matrix_identity_set();
1234
1235   //  ED_region_pixelspace(CTX_wm_region(C));
1236 }
1237
1238 /* *********************************************************************** */
1239 /* Gridlines */
1240
1241 /* View2DGrid is typedef'd in UI_view2d.h */
1242 struct View2DGrid {
1243   float dx, dy;         /* stepsize (in pixels) between gridlines */
1244   float startx, starty; /* initial coordinates to start drawing grid from */
1245   int powerx, powery;   /* step as power of 10 */
1246 };
1247
1248 /* --------------- */
1249
1250 /* try to write step as a power of 10 */
1251 static void step_to_grid(float *step, const int unit, int *r_power)
1252 {
1253   const float loga = (float)log10(*step);
1254   float rem;
1255
1256   int power = (int)(loga);
1257
1258   rem = loga - power;
1259   rem = (float)pow(10.0, rem);
1260
1261   if (loga < 0.0f) {
1262     if (rem < 0.2f) {
1263       rem = 0.2f;
1264     }
1265     else if (rem < 0.5f) {
1266       rem = 0.5f;
1267     }
1268     else {
1269       rem = 1.0f;
1270     }
1271
1272     *step = rem * (float)pow(10.0, power);
1273
1274     /* for frames, we want 1.0 frame intervals only */
1275     if (unit == V2D_UNIT_FRAMES) {
1276       rem = 1.0f;
1277       /* use 2 since there are grid lines drawn in between,
1278        * this way to get 1 line per frame */
1279       *step = 2.0f;
1280     }
1281
1282     /* prevents printing 1.0 2.0 3.0 etc */
1283     if (rem == 1.0f) {
1284       power++;
1285     }
1286   }
1287   else {
1288     if (rem < 2.0f) {
1289       rem = 2.0f;
1290     }
1291     else if (rem < 5.0f) {
1292       rem = 5.0f;
1293     }
1294     else {
1295       rem = 10.0f;
1296     }
1297
1298     *step = rem * (float)pow(10.0, power);
1299
1300     power++;
1301     /* prevents printing 1.0, 2.0, 3.0, etc. */
1302     if (rem == 10.0f) {
1303       power++;
1304     }
1305   }
1306
1307   *r_power = power;
1308 }
1309
1310 /**
1311  * Initialize settings necessary for drawing gridlines in a 2d-view
1312  *
1313  * - Currently, will return pointer to View2DGrid struct that needs to
1314  *   be freed with UI_view2d_grid_free()
1315  * - Is used for scrollbar drawing too (for units drawing)
1316  * - Units + clamping args will be checked, to make sure they are valid values that can be used
1317  *   so it is very possible that we won't return grid at all!
1318  *
1319  * - xunits,yunits = V2D_UNIT_*  grid steps in seconds or frames
1320  * - xclamp,yclamp = V2D_CLAMP_* only show whole-number intervals
1321  * - winx          = width of region we're drawing to, note: not used but keeping for completeness.
1322  * - winy          = height of region we're drawing into
1323  */
1324 View2DGrid *UI_view2d_grid_calc(Scene *scene,
1325                                 View2D *v2d,
1326                                 short xunits,
1327                                 short xclamp,
1328                                 short yunits,
1329                                 short yclamp,
1330                                 int UNUSED(winx),
1331                                 int winy)
1332 {
1333
1334   View2DGrid *grid;
1335   float space, seconddiv;
1336
1337   /* check that there are at least some workable args */
1338   if (ELEM(V2D_ARG_DUMMY, xunits, xclamp) && ELEM(V2D_ARG_DUMMY, yunits, yclamp)) {
1339     return NULL;
1340   }
1341
1342   /* grid here is allocated... */
1343   grid = MEM_callocN(sizeof(View2DGrid), "View2DGrid");
1344
1345   /* rule: gridstep is minimal GRIDSTEP pixels */
1346   if (xunits == V2D_UNIT_SECONDS) {
1347     seconddiv = (float)(0.01 * FPS);
1348   }
1349   else {
1350     seconddiv = 1.0f;
1351   }
1352
1353   /* calculate x-axis grid scale (only if both args are valid) */
1354   if (ELEM(V2D_ARG_DUMMY, xunits, xclamp) == 0) {
1355     space = BLI_rctf_size_x(&v2d->cur);
1356
1357     if (space != 0.0f) {
1358       const float pixels = (float)BLI_rcti_size_x(&v2d->mask);
1359       if (pixels != 0.0f) {
1360         grid->dx = (U.v2d_min_gridsize * UI_DPI_FAC * space) / (seconddiv * pixels);
1361         step_to_grid(&grid->dx, xunits, &grid->powerx);
1362         grid->dx *= seconddiv;
1363       }
1364     }
1365
1366     if (xclamp == V2D_GRID_CLAMP) {
1367       CLAMP_MIN(grid->dx, 0.1f);
1368       CLAMP_MIN(grid->powerx, 0);
1369       grid->powerx -= 2;
1370     }
1371   }
1372
1373   /* calculate y-axis grid scale (only if both args are valid) */
1374   if (ELEM(V2D_ARG_DUMMY, yunits, yclamp) == 0) {
1375     space = BLI_rctf_size_y(&v2d->cur);
1376     if (space != 0.0f) {
1377       const float pixels = (float)winy;
1378       if (pixels != 0.0f) {
1379         grid->dy = U.v2d_min_gridsize * UI_DPI_FAC * space / pixels;
1380         step_to_grid(&grid->dy, yunits, &grid->powery);
1381       }
1382     }
1383
1384     if (yclamp == V2D_GRID_CLAMP) {
1385       CLAMP_MIN(grid->dy, 1.0f);
1386       CLAMP_MIN(grid->powery, 1);
1387     }
1388   }
1389
1390   /* calculate start position */
1391   if (ELEM(V2D_ARG_DUMMY, xunits, xclamp) == 0) {
1392     grid->startx = seconddiv * (v2d->cur.xmin / seconddiv -
1393                                 (float)fmod(v2d->cur.xmin / seconddiv, grid->dx / seconddiv));
1394     if (v2d->cur.xmin < 0.0f) {
1395       grid->startx -= grid->dx;
1396     }
1397   }
1398   else {
1399     grid->startx = v2d->cur.xmin;
1400   }
1401
1402   if (ELEM(V2D_ARG_DUMMY, yunits, yclamp) == 0) {
1403     grid->starty = (v2d->cur.ymin - (float)fmod(v2d->cur.ymin, grid->dy));
1404     if (v2d->cur.ymin < 0.0f) {
1405       grid->starty -= grid->dy;
1406     }
1407   }
1408   else {
1409     grid->starty = v2d->cur.ymin;
1410   }
1411
1412   return grid;
1413 }
1414
1415 /* Draw gridlines in the given 2d-region */
1416 void UI_view2d_grid_draw(View2D *v2d, View2DGrid *grid, int flag)
1417 {
1418   float vec1[2], vec2[2];
1419   int a, step;
1420   int vertical_minor_step = (BLI_rcti_size_x(&v2d->mask) + 1) / (U.v2d_min_gridsize * UI_DPI_FAC);
1421   int horizontal_major_step = (BLI_rcti_size_y(&v2d->mask) + 1) /
1422                               (U.v2d_min_gridsize * UI_DPI_FAC);
1423   uchar grid_line_color[3];
1424
1425   /* check for grid first, as it may not exist */
1426   if (grid == NULL) {
1427     return;
1428   }
1429
1430   /* Count the needed vertices for the gridlines */
1431   unsigned vertex_count = 0;
1432   if (flag & V2D_VERTICAL_LINES) {
1433     /* vertical lines */
1434     vertex_count += 2 * vertical_minor_step;       /* minor gridlines */
1435     vertex_count += 2 * (vertical_minor_step + 2); /* major gridlines */
1436   }
1437   if (flag & V2D_HORIZONTAL_LINES) {
1438     /* horizontal lines */
1439     vertex_count += 2 * (horizontal_major_step + 1); /* major gridlines */
1440
1441     /* fine lines */
1442     if (flag & V2D_HORIZONTAL_FINELINES) {
1443       vertex_count += 2 * (horizontal_major_step + 1);
1444     }
1445   }
1446   /* axes */
1447   if (flag & V2D_HORIZONTAL_AXIS) {
1448     vertex_count += 2;
1449   }
1450   if (flag & V2D_VERTICAL_AXIS) {
1451     vertex_count += 2;
1452   }
1453
1454   /* If there is nothing to render, exit early */
1455   if (vertex_count == 0) {
1456     return;
1457   }
1458
1459   GPUVertFormat *format = immVertexFormat();
1460   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
1461   uint color = GPU_vertformat_attr_add(
1462       format, "color", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
1463
1464   immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
1465   immBegin(GPU_PRIM_LINES, vertex_count);
1466
1467   /* vertical lines */
1468   if (flag & V2D_VERTICAL_LINES) {
1469     /* initialize initial settings */
1470     vec1[0] = vec2[0] = grid->startx;
1471     vec1[1] = grid->starty;
1472     vec2[1] = v2d->cur.ymax;
1473
1474     /* minor gridlines */
1475     step = vertical_minor_step;
1476     if (step != 0) {
1477       UI_GetThemeColor3ubv(TH_GRID, grid_line_color);
1478
1479       for (a = 0; a < step; a++) {
1480         immAttrSkip(color);
1481         immVertex2fv(pos, vec1);
1482         immAttr3ubv(color, grid_line_color);
1483         immVertex2fv(pos, vec2);
1484
1485         vec2[0] = vec1[0] += grid->dx;
1486       }
1487     }
1488
1489     /* major gridlines */
1490     vec2[0] = vec1[0] -= 0.5f * grid->dx;
1491
1492     UI_GetThemeColorShade3ubv(TH_GRID, 16, grid_line_color);
1493
1494     step++;
1495     for (a = 0; a <= step; a++) {
1496       immAttrSkip(color);
1497       immVertex2fv(pos, vec1);
1498       immAttr3ubv(color, grid_line_color);
1499       immVertex2fv(pos, vec2);
1500
1501       vec2[0] = vec1[0] -= grid->dx;
1502     }
1503   }
1504
1505   /* horizontal lines */
1506   if (flag & V2D_HORIZONTAL_LINES) {
1507     /* only major gridlines */
1508     vec1[1] = vec2[1] = grid->starty;
1509     vec1[0] = grid->startx;
1510     vec2[0] = v2d->cur.xmax;
1511
1512     step = horizontal_major_step;
1513
1514     UI_GetThemeColor3ubv(TH_GRID, grid_line_color);
1515
1516     for (a = 0; a <= step; a++) {
1517       immAttrSkip(color);
1518       immVertex2fv(pos, vec1);
1519       immAttr3ubv(color, grid_line_color);
1520       immVertex2fv(pos, vec2);
1521
1522       vec2[1] = vec1[1] += grid->dy;
1523     }
1524
1525     /* fine grid lines */
1526     vec2[1] = vec1[1] -= 0.5f * grid->dy;
1527     step++;
1528
1529     if (flag & V2D_HORIZONTAL_FINELINES) {
1530       UI_GetThemeColorShade3ubv(TH_GRID, 16, grid_line_color);
1531       for (a = 0; a < step; a++) {
1532         immAttrSkip(color);
1533         immVertex2fv(pos, vec1);
1534         immAttr3ubv(color, grid_line_color);
1535         immVertex2fv(pos, vec2);
1536
1537         vec2[1] = vec1[1] -= grid->dy;
1538       }
1539     }
1540   }
1541
1542   /* Axes are drawn as darker lines */
1543   UI_GetThemeColorShade3ubv(TH_GRID, -50, grid_line_color);
1544
1545   /* horizontal axis */
1546   if (flag & V2D_HORIZONTAL_AXIS) {
1547     vec1[0] = v2d->cur.xmin;
1548     vec2[0] = v2d->cur.xmax;
1549     vec1[1] = vec2[1] = 0.0f;
1550
1551     immAttrSkip(color);
1552     immVertex2fv(pos, vec1);
1553     immAttr3ubv(color, grid_line_color);
1554     immVertex2fv(pos, vec2);
1555   }
1556
1557   /* vertical axis */
1558   if (flag & V2D_VERTICAL_AXIS) {
1559     vec1[1] = v2d->cur.ymin;
1560     vec2[1] = v2d->cur.ymax;
1561     vec1[0] = vec2[0] = 0.0f;
1562
1563     immAttrSkip(color);
1564     immVertex2fv(pos, vec1);
1565     immAttr3ubv(color, grid_line_color);
1566     immVertex2fv(pos, vec2);
1567   }
1568
1569   immEnd();
1570   immUnbindProgram();
1571 }
1572
1573 /* Draw a constant grid in given 2d-region */
1574 void UI_view2d_constant_grid_draw(View2D *v2d, float step)
1575 {
1576   float start_x, start_y;
1577   int count_x, count_y;
1578
1579   start_x = v2d->cur.xmin;
1580   if (start_x < 0.0) {
1581     start_x += -(float)fmod(v2d->cur.xmin, step);
1582   }
1583   else {
1584     start_x += (step - (float)fmod(v2d->cur.xmin, step));
1585   }
1586
1587   if (start_x > v2d->cur.xmax) {
1588     count_x = 0;
1589   }
1590   else {
1591     count_x = (v2d->cur.xmax - start_x) / step + 1;
1592   }
1593
1594   start_y = v2d->cur.ymin;
1595   if (start_y < 0.0) {
1596     start_y += -(float)fmod(v2d->cur.ymin, step);
1597   }
1598   else {
1599     start_y += (step - (float)fabs(fmod(v2d->cur.ymin, step)));
1600   }
1601
1602   if (start_y > v2d->cur.ymax) {
1603     count_y = 0;
1604   }
1605   else {
1606     count_y = (v2d->cur.ymax - start_y) / step + 1;
1607   }
1608
1609   if (count_x > 0 || count_y > 0) {
1610     GPUVertFormat *format = immVertexFormat();
1611     uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
1612     uint color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
1613     float theme_color[3];
1614
1615     UI_GetThemeColorShade3fv(TH_BACK, -10, theme_color);
1616
1617     immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
1618     immBegin(GPU_PRIM_LINES, count_x * 2 + count_y * 2 + 4);
1619
1620     immAttr3fv(color, theme_color);
1621     for (int i = 0; i < count_x; start_x += step, i++) {
1622       immVertex2f(pos, start_x, v2d->cur.ymin);
1623       immVertex2f(pos, start_x, v2d->cur.ymax);
1624     }
1625
1626     for (int i = 0; i < count_y; start_y += step, i++) {
1627       immVertex2f(pos, v2d->cur.xmin, start_y);
1628       immVertex2f(pos, v2d->cur.xmax, start_y);
1629     }
1630
1631     /* X and Y axis */
1632     UI_GetThemeColorShade3fv(TH_BACK, -18, theme_color);
1633
1634     immAttr3fv(color, theme_color);
1635     immVertex2f(pos, 0.0f, v2d->cur.ymin);
1636     immVertex2f(pos, 0.0f, v2d->cur.ymax);
1637     immVertex2f(pos, v2d->cur.xmin, 0.0f);
1638     immVertex2f(pos, v2d->cur.xmax, 0.0f);
1639
1640     immEnd();
1641     immUnbindProgram();
1642   }
1643 }
1644
1645 /* Draw a multi-level grid in given 2d-region */
1646 void UI_view2d_multi_grid_draw(View2D *v2d, int colorid, float step, int level_size, int totlevels)
1647 {
1648   /* Exit if there is nothing to draw */
1649   if (totlevels == 0) {
1650     return;
1651   }
1652
1653   int offset = -10;
1654   float lstep = step;
1655   uchar grid_line_color[3];
1656
1657   /* Make an estimate of at least how many vertices will be needed */
1658   unsigned vertex_count = 4;
1659   vertex_count += 2 * ((int)((v2d->cur.xmax - v2d->cur.xmin) / lstep) + 1);
1660   vertex_count += 2 * ((int)((v2d->cur.ymax - v2d->cur.ymin) / lstep) + 1);
1661
1662   GPUVertFormat *format = immVertexFormat();
1663   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
1664   uint color = GPU_vertformat_attr_add(
1665       format, "color", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
1666
1667   GPU_line_width(1.0f);
1668
1669   immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
1670   immBeginAtMost(GPU_PRIM_LINES, vertex_count);
1671
1672   for (int level = 0; level < totlevels; ++level) {
1673     UI_GetThemeColorShade3ubv(colorid, offset, grid_line_color);
1674
1675     int i = (int)(v2d->cur.xmin / lstep);
1676     if (v2d->cur.xmin > 0.0f) {
1677       i++;
1678     }
1679     float start = i * lstep;
1680
1681     for (; start < v2d->cur.xmax; start += lstep, ++i) {
1682       if (i == 0 || (level < totlevels - 1 && i % level_size == 0)) {
1683         continue;
1684       }
1685
1686       immAttrSkip(color);
1687       immVertex2f(pos, start, v2d->cur.ymin);
1688       immAttr3ubv(color, grid_line_color);
1689       immVertex2f(pos, start, v2d->cur.ymax);
1690     }
1691
1692     i = (int)(v2d->cur.ymin / lstep);
1693     if (v2d->cur.ymin > 0.0f) {
1694       i++;
1695     }
1696     start = i * lstep;
1697
1698     for (; start < v2d->cur.ymax; start += lstep, ++i) {
1699       if (i == 0 || (level < totlevels - 1 && i % level_size == 0)) {
1700         continue;
1701       }
1702
1703       immAttrSkip(color);
1704       immVertex2f(pos, v2d->cur.xmin, start);
1705       immAttr3ubv(color, grid_line_color);
1706       immVertex2f(pos, v2d->cur.xmax, start);
1707     }
1708
1709     lstep *= level_size;
1710     offset -= 6;
1711   }
1712
1713   /* X and Y axis */
1714   UI_GetThemeColorShade3ubv(colorid, -18 + ((totlevels - 1) * -6), grid_line_color);
1715
1716   immAttrSkip(color);
1717   immVertex2f(pos, 0.0f, v2d->cur.ymin);
1718   immAttr3ubv(color, grid_line_color);
1719   immVertex2f(pos, 0.0f, v2d->cur.ymax);
1720
1721   immAttrSkip(color);
1722   immVertex2f(pos, v2d->cur.xmin, 0.0f);
1723   immAttr3ubv(color, grid_line_color);
1724   immVertex2f(pos, v2d->cur.xmax, 0.0f);
1725
1726   immEnd();
1727   immUnbindProgram();
1728 }
1729
1730 static void get_scale_indicator_text(
1731     const Scene *scene, float value, int brevity_level, short unit, uint max_length, char *r_str)
1732 {
1733   if (unit == V2D_UNIT_SECONDS) {
1734     BLI_timecode_string_from_time(
1735         r_str, max_length, brevity_level, value / (float)FPS, FPS, U.timecode_style);
1736   }
1737   else {
1738     BLI_timecode_string_from_time_seconds(r_str, max_length, brevity_level, value);
1739   }
1740 }
1741
1742 void UI_view2d_grid_draw_numbers_horizontal(const Scene *scene,
1743                                             const View2D *v2d,
1744                                             const View2DGrid *grid,
1745                                             const rcti *rect,
1746                                             int unit,
1747                                             bool whole_numbers_only)
1748 {
1749   BLI_assert(grid);
1750   float xstep = grid->dx * UI_view2d_scale_get_x(v2d);
1751   if (xstep <= 0.0f) {
1752     return;
1753   }
1754
1755   float initial_xpos = UI_view2d_view_to_region_x(v2d, grid->startx);
1756   float ypos = (float)rect->ymin + 2 * UI_DPI_FAC;
1757   float initial_value = grid->startx;
1758   float value_step = grid->dx;
1759   int brevity_level = grid->powerx;
1760
1761   /* Make sure that the value_step is >= 1 when only whole numbers are displayed.
1762    * Otherwise the same number could be displayed more than once. */
1763   if (whole_numbers_only) {
1764     while (value_step < 0.9999f) {
1765       xstep *= 2.0f;
1766       value_step *= 2.0f;
1767     }
1768   }
1769
1770   /* Skip first few steps if they don't intersect
1771    * the rectangle that will contain the numbers. */
1772   while (initial_xpos < rect->xmin) {
1773     initial_xpos += xstep;
1774     initial_value += value_step;
1775   }
1776
1777   if (unit == V2D_UNIT_FRAMES) {
1778     brevity_level = 1;
1779   }
1780
1781   const int font_id = BLF_default();
1782   UI_FontThemeColor(font_id, TH_TEXT);
1783
1784   BLF_batch_draw_begin();
1785
1786   for (float xpos = initial_xpos, value = initial_value; xpos < rect->xmax;
1787        xpos += xstep, value += value_step) {
1788     char text[32];
1789     get_scale_indicator_text(scene, value, brevity_level, unit, sizeof(text), text);
1790     float text_width = BLF_width(font_id, text, strlen(text));
1791     BLF_draw_default_ascii(xpos - text_width / 2.0f, ypos, 0.0f, text, sizeof(text));
1792   }
1793
1794   BLF_batch_draw_end();
1795 }
1796
1797 void UI_view2d_grid_draw_numbers_vertical(const Scene *scene,
1798                                           const View2D *v2d,
1799                                           const View2DGrid *grid,
1800                                           const rcti *rect,
1801                                           int unit,
1802                                           float text_offset)
1803 {
1804   BLI_assert(grid);
1805   float ystep = grid->dy * UI_view2d_scale_get_y(v2d);
1806   if (ystep <= 0.0f) {
1807     return;
1808   }
1809
1810   const int font_id = BLF_default();
1811   UI_FontThemeColor(font_id, TH_TEXT);
1812
1813   BLF_enable(font_id, BLF_ROTATION);
1814   BLF_rotation(font_id, M_PI_2);
1815
1816   float initial_value = grid->starty;
1817   float value_step = grid->dy;
1818   float xpos = rect->xmax - 2.0f * UI_DPI_FAC;
1819   float initial_ypos = UI_view2d_view_to_region_y(v2d, grid->starty);
1820
1821   /* Currently only used by the sequencer to display
1822    * channel numbers in the center. */
1823   initial_ypos += text_offset * ystep;
1824
1825   /* Skip first few steps if they don't intersect
1826    * the rectangle that will contain the numbers. */
1827   while (initial_ypos < rect->ymin) {
1828     initial_ypos += ystep;
1829     initial_value += value_step;
1830   }
1831
1832   for (float ypos = initial_ypos, value = initial_value; ypos < rect->ymax;
1833        ypos += ystep, value += value_step) {
1834     char text[32];
1835     get_scale_indicator_text(scene, value, grid->powery, unit, sizeof(text), text);
1836     float text_width = BLF_width(font_id, text, sizeof(text));
1837     BLF_draw_default_ascii(xpos, ypos - text_width / 2.0f, 0.0f, text, sizeof(text));
1838   }
1839
1840   BLF_disable(font_id, BLF_ROTATION);
1841 }
1842
1843 /* the price we pay for not exposting structs :( */
1844 void UI_view2d_grid_size(View2DGrid *grid, float *r_dx, float *r_dy)
1845 {
1846   *r_dx = grid->dx;
1847   *r_dy = grid->dy;
1848 }
1849
1850 /* free temporary memory used for drawing grid */
1851 void UI_view2d_grid_free(View2DGrid *grid)
1852 {
1853   /* only free if there's a grid */
1854   if (grid) {
1855     MEM_freeN(grid);
1856   }
1857 }
1858
1859 /* *********************************************************************** */
1860 /* Scrollers */
1861
1862 /**
1863  * View2DScrollers is typedef'd in UI_view2d.h
1864  *
1865  * \warning The start of this struct must not change, as view2d_ops.c uses this too.
1866  * For now, we don't need to have a separate (internal) header for structs like this...
1867  */
1868 struct View2DScrollers {
1869   /* focus bubbles */
1870   int vert_min, vert_max; /* vertical scrollbar */
1871   int hor_min, hor_max;   /* horizontal scrollbar */
1872
1873   rcti hor, vert;        /* exact size of slider backdrop */
1874   int horfull, vertfull; /* set if sliders are full, we don't draw them */
1875
1876   /* scales */
1877   View2DGrid *grid;     /* grid for coordinate drawing */
1878   short xunits, xclamp; /* units and clamping options for x-axis */
1879   short yunits, yclamp; /* units and clamping options for y-axis */
1880 };
1881
1882 /* Calculate relevant scroller properties */
1883 View2DScrollers *UI_view2d_scrollers_calc(const bContext *C,
1884                                           View2D *v2d,
1885                                           const rcti *mask_custom,
1886                                           short xunits,
1887                                           short xclamp,
1888                                           short yunits,
1889                                           short yclamp)
1890 {
1891   View2DScrollers *scrollers;
1892   rcti vert, hor;
1893   float fac1, fac2, totsize, scrollsize;
1894   int scroll = view2d_scroll_mapped(v2d->scroll);
1895   int smaller;
1896
1897   /* scrollers is allocated here... */
1898   scrollers = MEM_callocN(sizeof(View2DScrollers), "View2DScrollers");
1899
1900   /* Always update before drawing (for dynamically sized scrollers). */
1901   view2d_masks(v2d, false, mask_custom);
1902
1903   vert = v2d->vert;
1904   hor = v2d->hor;
1905
1906   /* slider rects need to be smaller than region and not interfere with splitter areas */
1907   hor.xmin += UI_HEADER_OFFSET;
1908   hor.xmax -= UI_HEADER_OFFSET;
1909   vert.ymin += UI_HEADER_OFFSET;
1910   vert.ymax -= UI_HEADER_OFFSET;
1911
1912   /* width of sliders */
1913   smaller = (int)(0.1f * U.widget_unit);
1914   if (scroll & V2D_SCROLL_BOTTOM) {
1915     hor.ymin += smaller;
1916   }
1917   else {
1918     hor.ymax -= smaller;
1919   }
1920
1921   if (scroll & V2D_SCROLL_LEFT) {
1922     vert.xmin += smaller;
1923   }
1924   else {
1925     vert.xmax -= smaller;
1926   }
1927
1928   CLAMP(vert.ymin, vert.ymin, vert.ymax - V2D_SCROLLER_HANDLE_SIZE);
1929   CLAMP(hor.xmin, hor.xmin, hor.xmax - V2D_SCROLLER_HANDLE_SIZE);
1930
1931   /* store in scrollers, used for drawing */
1932   scrollers->vert = vert;
1933   scrollers->hor = hor;
1934
1935   /* scroller 'buttons':
1936    * - These should always remain within the visible region of the scrollbar
1937    * - They represent the region of 'tot' that is visible in 'cur'
1938    */
1939
1940   /* horizontal scrollers */
1941   if (scroll & V2D_SCROLL_HORIZONTAL) {
1942     /* scroller 'button' extents */
1943     totsize = BLI_rctf_size_x(&v2d->tot);
1944     scrollsize = (float)BLI_rcti_size_x(&hor);
1945     if (totsize == 0.0f) {
1946       totsize = 1.0f; /* avoid divide by zero */
1947     }
1948
1949     fac1 = (v2d->cur.xmin - v2d->tot.xmin) / totsize;
1950     if (fac1 <= 0.0f) {
1951       scrollers->hor_min = hor.xmin;
1952     }
1953     else {
1954       scrollers->hor_min = (int)(hor.xmin + (fac1 * scrollsize));
1955     }
1956
1957     fac2 = (v2d->cur.xmax - v2d->tot.xmin) / totsize;
1958     if (fac2 >= 1.0f) {
1959       scrollers->hor_max = hor.xmax;
1960     }
1961     else {
1962       scrollers->hor_max = (int)(hor.xmin + (fac2 * scrollsize));
1963     }
1964
1965     /* prevent inverted sliders */
1966     if (scrollers->hor_min > scrollers->hor_max) {
1967       scrollers->hor_min = scrollers->hor_max;
1968     }
1969     /* prevent sliders from being too small, and disappearing */
1970     if ((scrollers->hor_max - scrollers->hor_min) < V2D_SCROLLER_HANDLE_SIZE) {
1971       scrollers->hor_max = scrollers->hor_min + V2D_SCROLLER_HANDLE_SIZE;
1972
1973       CLAMP(scrollers->hor_max, hor.xmin + V2D_SCROLLER_HANDLE_SIZE, hor.xmax);
1974       CLAMP(scrollers->hor_min, hor.xmin, hor.xmax - V2D_SCROLLER_HANDLE_SIZE);
1975     }
1976   }
1977
1978   /* vertical scrollers */
1979   if (scroll & V2D_SCROLL_VERTICAL) {
1980     /* scroller 'button' extents */
1981     totsize = BLI_rctf_size_y(&v2d->tot);
1982     scrollsize = (float)BLI_rcti_size_y(&vert);
1983     if (totsize == 0.0f) {
1984       totsize = 1.0f; /* avoid divide by zero */
1985     }
1986
1987     fac1 = (v2d->cur.ymin - v2d->tot.ymin) / totsize;
1988     if (fac1 <= 0.0f) {
1989       scrollers->vert_min = vert.ymin;
1990     }
1991     else {
1992       scrollers->vert_min = (int)(vert.ymin + (fac1 * scrollsize));
1993     }
1994
1995     fac2 = (v2d->cur.ymax - v2d->tot.ymin) / totsize;
1996     if (fac2 >= 1.0f) {
1997       scrollers->vert_max = vert.ymax;
1998     }
1999     else {
2000       scrollers->vert_max = (int)(vert.ymin + (fac2 * scrollsize));
2001     }
2002
2003     /* prevent inverted sliders */
2004     if (scrollers->vert_min > scrollers->vert_max) {
2005       scrollers->vert_min = scrollers->vert_max;
2006     }
2007     /* prevent sliders from being too small, and disappearing */
2008     if ((scrollers->vert_max - scrollers->vert_min) < V2D_SCROLLER_HANDLE_SIZE) {
2009
2010       scrollers->vert_max = scrollers->vert_min + V2D_SCROLLER_HANDLE_SIZE;
2011
2012       CLAMP(scrollers->vert_max, vert.ymin + V2D_SCROLLER_HANDLE_SIZE, vert.ymax);
2013       CLAMP(scrollers->vert_min, vert.ymin, vert.ymax - V2D_SCROLLER_HANDLE_SIZE);
2014     }
2015   }
2016
2017   /* grid markings on scrollbars */
2018   if (scroll & (V2D_SCROLL_SCALE_HORIZONTAL | V2D_SCROLL_SCALE_VERTICAL)) {
2019     /* store clamping */
2020     scrollers->xclamp = xclamp;
2021     scrollers->xunits = xunits;
2022     scrollers->yclamp = yclamp;
2023     scrollers->yunits = yunits;
2024
2025     scrollers->grid = UI_view2d_grid_calc(CTX_data_scene(C),
2026                                           v2d,
2027                                           xunits,
2028                                           xclamp,
2029                                           yunits,
2030                                           yclamp,
2031                                           BLI_rcti_size_x(&hor),
2032                                           BLI_rcti_size_y(&vert));
2033   }
2034
2035   /* return scrollers */
2036   return scrollers;
2037 }
2038
2039 /* Draw scrollbars in the given 2d-region */
2040 void UI_view2d_scrollers_draw(const bContext *C, View2D *v2d, View2DScrollers *vs)
2041 {
2042   bTheme *btheme = UI_GetTheme();
2043   rcti vert, hor;
2044   const int scroll = view2d_scroll_mapped(v2d->scroll);
2045   const char emboss_alpha = btheme->tui.widget_emboss[3];
2046   uchar scrollers_back_color[4];
2047
2048   /* Color for scrollbar backs */
2049   UI_GetThemeColor4ubv(TH_BACK, scrollers_back_color);
2050
2051   /* make copies of rects for less typing */
2052   vert = vs->vert;
2053   hor = vs->hor;
2054
2055   /* horizontal scrollbar */
2056   if (scroll & V2D_SCROLL_HORIZONTAL) {
2057     uiWidgetColors wcol = btheme->tui.wcol_scroll;
2058     const float alpha_fac = v2d->alpha_hor / 255.0f;
2059     rcti slider;
2060     int state;
2061
2062     slider.xmin = vs->hor_min;
2063     slider.xmax = vs->hor_max;
2064     slider.ymin = hor.ymin;
2065     slider.ymax = hor.ymax;
2066
2067     state = (v2d->scroll_ui & V2D_SCROLL_H_ACTIVE) ? UI_SCROLL_PRESSED : 0;
2068
2069     wcol.inner[3] *= alpha_fac;
2070     wcol.item[3] *= alpha_fac;
2071     wcol.outline[3] *= alpha_fac;
2072     btheme->tui.widget_emboss[3] *= alpha_fac; /* will be reset later */
2073
2074     /* show zoom handles if:
2075      * - zooming on x-axis is allowed (no scroll otherwise)
2076      * - slider bubble is large enough (no overdraw confusion)
2077      * - scale is shown on the scroller
2078      *   (workaround to make sure that button windows don't show these,
2079      *   and only the time-grids with their zoomability can do so)
2080      */
2081     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0 && (v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) &&
2082         (BLI_rcti_size_x(&slider) > V2D_SCROLLER_HANDLE_SIZE)) {
2083       state |= UI_SCROLL_ARROWS;
2084     }
2085
2086     UI_draw_widget_scroll(&wcol, &hor, &slider, state);
2087
2088     {
2089       if (scroll & V2D_SCROLL_SCALE_HORIZONTAL) {
2090         UI_view2d_grid_draw_numbers_horizontal(
2091             CTX_data_scene(C), v2d, vs->grid, &vs->hor, vs->xunits, vs->xclamp == V2D_GRID_CLAMP);
2092       }
2093     }
2094   }
2095
2096   /* vertical scrollbar */
2097   if (scroll & V2D_SCROLL_VERTICAL) {
2098     uiWidgetColors wcol = btheme->tui.wcol_scroll;
2099     rcti slider;
2100     const float alpha_fac = v2d->alpha_vert / 255.0f;
2101     int state;
2102
2103     slider.xmin = vert.xmin;
2104     slider.xmax = vert.xmax;
2105     slider.ymin = vs->vert_min;
2106     slider.ymax = vs->vert_max;
2107
2108     state = (v2d->scroll_ui & V2D_SCROLL_V_ACTIVE) ? UI_SCROLL_PRESSED : 0;
2109
2110     wcol.inner[3] *= alpha_fac;
2111     wcol.item[3] *= alpha_fac;
2112     wcol.outline[3] *= alpha_fac;
2113     btheme->tui.widget_emboss[3] *= alpha_fac; /* will be reset later */
2114
2115     /* show zoom handles if:
2116      * - zooming on y-axis is allowed (no scroll otherwise)
2117      * - slider bubble is large enough (no overdraw confusion)
2118      * - scale is shown on the scroller
2119      *   (workaround to make sure that button windows don't show these,
2120      *   and only the time-grids with their zoomability can do so)
2121      */
2122     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0 && (v2d->scroll & V2D_SCROLL_SCALE_VERTICAL) &&
2123         (BLI_rcti_size_y(&slider) > V2D_SCROLLER_HANDLE_SIZE)) {
2124       state |= UI_SCROLL_ARROWS;
2125     }
2126
2127     UI_draw_widget_scroll(&wcol, &vert, &slider, state);
2128
2129     {
2130       if (scroll & V2D_SCROLL_SCALE_VERTICAL) {
2131         float text_offset = 0.0f;
2132         if (vs->yclamp & V2D_GRID_CLAMP) {
2133           text_offset = 0.5f;
2134         }
2135         UI_view2d_grid_draw_numbers_vertical(
2136             CTX_data_scene(C), v2d, vs->grid, &vs->vert, vs->yunits, text_offset);
2137       }
2138     }
2139   }
2140
2141   /* Was changed above, so reset. */
2142   btheme->tui.widget_emboss[3] = emboss_alpha;
2143 }
2144
2145 /* free temporary memory used for drawing scrollers */
2146 void UI_view2d_scrollers_free(View2DScrollers *scrollers)
2147 {
2148   /* need to free grid as well... */
2149   if (scrollers->grid) {
2150     MEM_freeN(scrollers->grid);
2151   }
2152   MEM_freeN(scrollers);
2153 }
2154
2155 /* *********************************************************************** */
2156 /* List View Utilities */
2157
2158 /** Get the view-coordinates of the nominated cell
2159  *
2160  * \param columnwidth, rowheight: size of each 'cell'
2161  * \param startx, starty: coordinates (in 'tot' rect space) that the list starts from.
2162  * This should be (0,0) for most views. However, for those where the starting row was offsetted
2163  * (like for Animation Editor channel lists, to make the first entry more visible), these will be
2164  * the min-coordinates of the first item.
2165  * \param column, row: The 2d-coordinates
2166  * (in 2D-view / 'tot' rect space) the cell exists at
2167  * \param rect:  coordinates of the cell
2168  * (passed as single var instead of 4 separate, as it's more useful this way)
2169  */
2170 void UI_view2d_listview_cell_to_view(View2D *v2d,
2171                                      float columnwidth,
2172                                      float rowheight,
2173                                      float startx,
2174                                      float starty,
2175                                      int column,
2176                                      int row,
2177                                      rctf *rect)
2178 {
2179   /* sanity checks */
2180   if (ELEM(NULL, v2d, rect)) {
2181     return;
2182   }
2183
2184   if ((columnwidth <= 0) && (rowheight <= 0)) {
2185     rect->xmin = rect->xmax = 0.0f;
2186     rect->ymin = rect->ymax = 0.0f;
2187     return;
2188   }
2189
2190   /* x-coordinates */
2191   rect->xmin = startx + (float)(columnwidth * column);
2192   rect->xmax = startx + (float)(columnwidth * (column + 1));
2193
2194   if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
2195     /* simply negate the values for the coordinates if in negative half */
2196     rect->xmin = -rect->xmin;
2197     rect->xmax = -rect->xmax;
2198   }
2199
2200   /* y-coordinates */
2201   rect->ymin = starty + (float)(rowheight * row);
2202   rect->ymax = starty + (float)(rowheight * (row + 1));
2203
2204   if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
2205     /* simply negate the values for the coordinates if in negative half */
2206     rect->ymin = -rect->ymin;
2207     rect->ymax = -rect->ymax;
2208   }
2209 }
2210
2211 /**
2212  * Get the 'cell' (row, column) that the given 2D-view coordinates
2213  * (i.e. in 'tot' rect space) lie in.
2214  *
2215  * \param columnwidth, rowheight: size of each 'cell'
2216  * \param startx, starty: coordinates (in 'tot' rect space) that the list starts from.
2217  * This should be (0,0) for most views. However, for those where the starting row was offsetted
2218  * (like for Animation Editor channel lists, to make the first entry more visible), these will be
2219  * the min-coordinates of the first item.
2220  * \param viewx, viewy: 2D-coordinates (in 2D-view / 'tot' rect space) to get the cell for
2221  * \param r_column, r_row: the 'coordinates' of the relevant 'cell'
2222  */
2223 void UI_view2d_listview_view_to_cell(View2D *v2d,
2224                                      float columnwidth,
2225                                      float rowheight,
2226                                      float startx,
2227                                      float starty,
2228                                      float viewx,
2229                                      float viewy,
2230                                      int *r_column,
2231                                      int *r_row)
2232 {
2233   /* adjust view coordinates to be all positive ints, corrected for the start offset */
2234   const int x = (int)(floorf(fabsf(viewx) + 0.5f) - startx);
2235   const int y = (int)(floorf(fabsf(viewy) + 0.5f) - starty);
2236
2237   /* sizes must not be negative */
2238   if ((v2d == NULL) || ((columnwidth <= 0) && (rowheight <= 0))) {
2239     if (r_column) {
2240       *r_column = 0;
2241     }
2242     if (r_row) {
2243       *r_row = 0;
2244     }
2245
2246     return;
2247   }
2248
2249   /* get column */
2250   if ((r_column) && (columnwidth > 0)) {
2251     *r_column = x / columnwidth;
2252   }
2253   else if (r_column) {
2254     *r_column = 0;
2255   }
2256
2257   /* get row */
2258   if ((r_row) && (rowheight > 0)) {
2259     *r_row = y / rowheight;
2260   }
2261   else if (r_row) {
2262     *r_row = 0;
2263   }
2264 }
2265
2266 /**
2267  * Get the 'extreme' (min/max) column and row indices which are visible within the 'cur' rect
2268  *
2269  * \param columnwidth, rowheight: Size of each 'cell'
2270  * \param startx, starty: Coordinates that the list starts from,
2271  * which should be (0,0) for most views.
2272  * \param column_min, column_max, row_min, row_max: The starting and ending column/row indices
2273  */
2274 void UI_view2d_listview_visible_cells(View2D *v2d,
2275                                       float columnwidth,
2276                                       float rowheight,
2277                                       float startx,
2278                                       float starty,
2279                                       int *column_min,
2280                                       int *column_max,
2281                                       int *row_min,
2282                                       int *row_max)
2283 {
2284   /* using 'cur' rect coordinates, call the cell-getting function to get the cells for this */
2285   if (v2d) {
2286     /* min */
2287     UI_view2d_listview_view_to_cell(v2d,
2288                                     columnwidth,
2289                                     rowheight,
2290                                     startx,
2291                                     starty,
2292                                     v2d->cur.xmin,
2293                                     v2d->cur.ymin,
2294                                     column_min,
2295                                     row_min);
2296
2297     /* max*/
2298     UI_view2d_listview_view_to_cell(v2d,
2299                                     columnwidth,
2300                                     rowheight,
2301                                     startx,
2302                                     starty,
2303                                     v2d->cur.xmax,
2304                                     v2d->cur.ymax,
2305                                     column_max,
2306                                     row_max);
2307   }
2308 }
2309
2310 /* *********************************************************************** */
2311 /* Coordinate Conversions */
2312
2313 float UI_view2d_region_to_view_x(const struct View2D *v2d, float x)
2314 {
2315   return (v2d->cur.xmin +
2316           (BLI_rctf_size_x(&v2d->cur) * (x - v2d->mask.xmin) / BLI_rcti_size_x(&v2d->mask)));
2317 }
2318 float UI_view2d_region_to_view_y(const struct View2D *v2d, float y)
2319 {
2320   return (v2d->cur.ymin +
2321           (BLI_rctf_size_y(&v2d->cur) * (y - v2d->mask.ymin) / BLI_rcti_size_y(&v2d->mask)));
2322 }
2323
2324 /**
2325  * Convert from screen/region space to 2d-View space
2326  *
2327  * \param x, y: coordinates to convert
2328  * \param r_view_x, r_view_y: resultant coordinates
2329  */
2330 void UI_view2d_region_to_view(
2331     const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y)
2332 {
2333   *r_view_x = UI_view2d_region_to_view_x(v2d, x);
2334   *r_view_y = UI_view2d_region_to_view_y(v2d, y);
2335 }
2336
2337 void UI_view2d_region_to_view_rctf(const View2D *v2d, const rctf *rect_src, rctf *rect_dst)
2338 {
2339   const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)};
2340   const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)};
2341
2342   rect_dst->xmin = (v2d->cur.xmin +
2343                     (cur_size[0] * (rect_src->xmin - v2d->mask.xmin) / mask_size[0]));
2344   rect_dst->xmax = (v2d->cur.xmin +
2345                     (cur_size[0] * (rect_src->xmax - v2d->mask.xmin) / mask_size[0]));
2346   rect_dst->ymin = (v2d->cur.ymin +
2347                     (cur_size[1] * (rect_src->ymin - v2d->mask.ymin) / mask_size[1]));
2348   rect_dst->ymax = (v2d->cur.ymin +
2349                     (cur_size[1] * (rect_src->ymax - v2d->mask.ymin) / mask_size[1]));
2350 }
2351
2352 float UI_view2d_view_to_region_x(const View2D *v2d, float x)
2353 {
2354   return (v2d->mask.xmin +
2355           (((x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur)) * BLI_rcti_size_x(&v2d->mask)));
2356 }
2357 float UI_view2d_view_to_region_y(const View2D *v2d, float y)
2358 {
2359   return (v2d->mask.ymin +
2360           (((y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur)) * BLI_rcti_size_y(&v2d->mask)));
2361 }
2362
2363 /**
2364  * Convert from 2d-View space to screen/region space
2365  * \note Coordinates are clamped to lie within bounds of region
2366  *
2367  * \param x, y: Coordinates to convert.
2368  * \param r_region_x, r_region_y: Resultant coordinates.
2369  */
2370 bool UI_view2d_view_to_region_clip(
2371     const View2D *v2d, float x, float y, int *r_region_x, int *r_region_y)
2372 {
2373   /* express given coordinates as proportional values */
2374   x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
2375   y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
2376
2377   /* check if values are within bounds */
2378   if ((x >= 0.0f) && (x <= 1.0f) && (y >= 0.0f) && (y <= 1.0f)) {
2379     *r_region_x = (int)(v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask)));
2380     *r_region_y = (int)(v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask)));
2381
2382     return true;
2383   }
2384   else {
2385     /* set initial value in case coordinate lies outside of bounds */
2386     *r_region_x = *r_region_y = V2D_IS_CLIPPED;
2387
2388     return false;
2389   }
2390 }
2391
2392 /**
2393  * Convert from 2d-view space to screen/region space
2394  *
2395  * \note Coordinates are NOT clamped to lie within bounds of region.
2396  *
2397  * \param x, y: Coordinates to convert.
2398  * \param r_region_x, r_region_y: Resultant coordinates.
2399  */
2400 void UI_view2d_view_to_region(View2D *v2d, float x, float y, int *r_region_x, int *r_region_y)
2401 {
2402   /* step 1: express given coordinates as proportional values */
2403   x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
2404   y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
2405
2406   /* step 2: convert proportional distances to screen coordinates  */
2407   x = v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask));
2408   y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask));
2409
2410   /* although we don't clamp to lie within region bounds, we must avoid exceeding size of ints */
2411   *r_region_x = clamp_float_to_int(x);
2412   *r_region_y = clamp_float_to_int(y);
2413 }
2414
2415 void UI_view2d_view_to_region_fl(
2416     View2D *v2d, float x, float y, float *r_region_x, float *r_region_y)
2417 {
2418   /* express given coordinates as proportional values */
2419   x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
2420   y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
2421
2422   /* convert proportional distances to screen coordinates */
2423   *r_region_x = v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask));
2424   *r_region_y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask));
2425 }
2426
2427 void UI_view2d_view_to_region_rcti(View2D *v2d, const rctf *rect_src, rcti *rect_dst)
2428 {
2429   const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)};
2430   const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)};
2431   rctf rect_tmp;
2432
2433   /* step 1: express given coordinates as proportional values */
2434   rect_tmp.xmin = (rect_src->xmin - v2d->cur.xmin) / cur_size[0];
2435   rect_tmp.xmax = (rect_src->xmax - v2d->cur.xmin) / cur_size[0];
2436   rect_tmp.ymin = (rect_src->ymin - v2d->cur.ymin) / cur_size[1];
2437   rect_tmp.ymax = (rect_src->ymax - v2d->cur.ymin) / cur_size[1];
2438
2439   /* step 2: convert proportional distances to screen coordinates  */
2440   rect_tmp.xmin = v2d->mask.xmin + (rect_tmp.xmin * mask_size[0]);
2441   rect_tmp.xmax = v2d->mask.xmin + (rect_tmp.xmax * mask_size[0]);
2442   rect_tmp.ymin = v2d->mask.ymin + (rect_tmp.ymin * mask_size[1]);
2443   rect_tmp.ymax = v2d->mask.ymin + (rect_tmp.ymax * mask_size[1]);
2444
2445   clamp_rctf_to_rcti(rect_dst, &rect_tmp);
2446 }
2447
2448 void UI_view2d_view_to_region_m4(View2D *v2d, float matrix[4][4])
2449 {
2450   rctf mask;
2451   unit_m4(matrix);
2452   BLI_rctf_rcti_copy(&mask, &v2d->mask);
2453   BLI_rctf_transform_calc_m4_pivot_min(&v2d->cur, &mask, matrix);
2454 }
2455
2456 bool UI_view2d_view_to_region_rcti_clip(View2D *v2d, const rctf *rect_src, rcti *rect_dst)
2457 {
2458   const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)};
2459   const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)};
2460   rctf rect_tmp;
2461
2462   BLI_assert(rect_src->xmin <= rect_src->xmax && rect_src->ymin <= rect_src->ymax);
2463
2464   /* step 1: express given coordinates as proportional values */
2465   rect_tmp.xmin = (rect_src->xmin - v2d->cur.xmin) / cur_size[0];
2466   rect_tmp.xmax = (rect_src->xmax - v2d->cur.xmin) / cur_size[0];
2467   rect_tmp.ymin = (rect_src->ymin - v2d->cur.ymin) / cur_size[1];
2468   rect_tmp.ymax = (rect_src->ymax - v2d->cur.ymin) / cur_size[1];
2469
2470   if (((rect_tmp.xmax < 0.0f) || (rect_tmp.xmin > 1.0f) || (rect_tmp.ymax < 0.0f) ||
2471        (rect_tmp.ymin > 1.0f)) == 0) {
2472     /* step 2: convert proportional distances to screen coordinates  */
2473     rect_tmp.xmin = v2d->mask.xmin + (rect_tmp.xmin * mask_size[0]);
2474     rect_tmp.xmax = v2d->mask.ymin + (rect_tmp.xmax * mask_size[0]);
2475     rect_tmp.ymin = v2d->mask.ymin + (rect_tmp.ymin * mask_size[1]);
2476     rect_tmp.ymax = v2d->mask.ymin + (rect_tmp.ymax * mask_size[1]);
2477
2478     clamp_rctf_to_rcti(rect_dst, &rect_tmp);
2479
2480     return true;
2481   }
2482   else {
2483     rect_dst->xmin = rect_dst->xmax = rect_dst->ymin = rect_dst->ymax = V2D_IS_CLIPPED;
2484
2485     return false;
2486   }
2487 }
2488
2489 /* *********************************************************************** */
2490 /* Utilities */
2491
2492 /* View2D data by default resides in region, so get from region stored in context */
2493 View2D *UI_view2d_fromcontext(const bContext *C)
2494 {
2495   ScrArea *area = CTX_wm_area(C);
2496   ARegion *region = CTX_wm_region(C);
2497
2498   if (area == NULL) {
2499     return NULL;
2500   }
2501   if (region == NULL) {
2502     return NULL;
2503   }
2504   return &(region->v2d);
2505 }
2506
2507 /* same as above, but it returns regionwindow. Utility for pulldowns or buttons */
2508 View2D *UI_view2d_fromcontext_rwin(const bContext *C)
2509 {
2510   ScrArea *sa = CTX_wm_area(C);
2511   ARegion *region = CTX_wm_region(C);
2512
2513   if (sa == NULL) {
2514     return NULL;
2515   }
2516   if (region == NULL) {
2517     return NULL;
2518   }
2519   if (region->regiontype != RGN_TYPE_WINDOW) {
2520     ARegion *ar = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW);
2521     return ar ? &(ar->v2d) : NULL;
2522   }
2523   return &(region->v2d);
2524 }
2525
2526 /**
2527  * Calculate the scale per-axis of the drawing-area
2528  *
2529  * Is used to inverse correct drawing of icons, etc. that need to follow view
2530  * but not be affected by scale
2531  *
2532  * \param r_x, r_y: scale on each axis
2533  */
2534 void UI_view2d_scale_get(View2D *v2d, float *r_x, float *r_y)
2535 {
2536   if (r_x) {
2537     *r_x = UI_view2d_scale_get_x(v2d);
2538   }
2539   if (r_y) {
2540     *r_y = UI_view2d_scale_get_y(v2d);
2541   }
2542 }
2543 float UI_view2d_scale_get_x(const View2D *v2d)
2544 {
2545   return BLI_rcti_size_x(&v2d->mask) / BLI_rctf_size_x(&v2d->cur);
2546 }
2547 float UI_view2d_scale_get_y(const View2D *v2d)
2548 {
2549   return BLI_rcti_size_y(&v2d->mask) / BLI_rctf_size_y(&v2d->cur);
2550 }
2551 /**
2552  * Same as ``UI_view2d_scale_get() - 1.0f / x, y``
2553  */
2554 void UI_view2d_scale_get_inverse(View2D *v2d, float *r_x, float *r_y)
2555 {
2556   if (r_x) {
2557     *r_x = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask);
2558   }
2559   if (r_y) {
2560     *r_y = BLI_rctf_size_y(&v2d->cur) / BLI_rcti_size_y(&v2d->mask);
2561   }
2562 }
2563
2564 /**
2565  * Simple functions for consistent center offset access.
2566  * Used by node editor to shift view center for each individual node tree.
2567  */
2568 void UI_view2d_center_get(struct View2D *v2d, float *r_x, float *r_y)
2569 {
2570   /* get center */
2571   if (r_x) {
2572     *r_x = BLI_rctf_cent_x(&v2d->cur);
2573   }
2574   if (r_y) {
2575     *r_y = BLI_rctf_cent_y(&v2d->cur);
2576   }
2577 }
2578 void UI_view2d_center_set(struct View2D *v2d, float x, float y)
2579 {
2580   BLI_rctf_recenter(&v2d->cur, x, y);
2581
2582   /* make sure that 'cur' rect is in a valid state as a result of these changes */
2583   UI_view2d_curRect_validate(v2d);
2584 }
2585
2586 /**
2587  * Simple pan function
2588  *  (0.0, 0.0) bottom left
2589  *  (0.5, 0.5) center
2590  *  (1.0, 1.0) top right.
2591  */
2592 void UI_view2d_offset(struct View2D *v2d, float xfac, float yfac)
2593 {
2594   if (xfac != -1.0f) {
2595     const float xsize = BLI_rctf_size_x(&v2d->cur);
2596     const float xmin = v2d->tot.xmin;
2597     const float xmax = v2d->tot.xmax - xsize;
2598
2599     v2d->cur.xmin = (xmin * (1.0f - xfac)) + (xmax * xfac);
2600     v2d->cur.xmax = v2d->cur.xmin + xsize;
2601   }
2602
2603   if (yfac != -1.0f) {
2604     const float ysize = BLI_rctf_size_y(&v2d->cur);
2605     const float ymin = v2d->tot.ymin;
2606     const float ymax = v2d->tot.ymax - ysize;
2607
2608     v2d->cur.ymin = (ymin * (1.0f - yfac)) + (ymax * yfac);
2609     v2d->cur.ymax = v2d->cur.ymin + ysize;
2610   }
2611
2612   UI_view2d_curRect_validate(v2d);
2613 }
2614
2615 /**
2616  * Check if mouse is within scrollers
2617  *
2618  * \param x, y: Mouse coordinates in screen (not region) space.
2619  * \param r_scroll: Mapped view2d scroll flag.
2620  *
2621  * \return appropriate code for match.
2622  * - 'h' = in horizontal scroller.
2623  * - 'v' = in vertical scroller.
2624  * - 0 = not in scroller.
2625  */
2626 char UI_view2d_mouse_in_scrollers_ex(
2627     const ARegion *ar, const View2D *v2d, int x, int y, int *r_scroll)
2628 {
2629   const int scroll = view2d_scroll_mapped(v2d->scroll);
2630   *r_scroll = scroll;
2631
2632   /* clamp x,y to region-coordinates first */
2633   const int co[2] = {
2634       x - ar->winrct.xmin,
2635       y - ar->winrct.ymin,
2636   };
2637
2638   /* check if within scrollbars */
2639   if (scroll & V2D_SCROLL_HORIZONTAL) {
2640     if (IN_2D_HORIZ_SCROLL(v2d, co)) {
2641       return 'h';
2642     }
2643   }
2644   if (scroll & V2D_SCROLL_VERTICAL) {
2645     if (IN_2D_VERT_SCROLL(v2d, co)) {
2646       return 'v';
2647     }
2648   }
2649
2650   /* not found */
2651   return 0;
2652 }
2653
2654 char UI_view2d_rect_in_scrollers_ex(const ARegion *ar,
2655                                     const View2D *v2d,
2656                                     const rcti *rect,
2657                                     int *r_scroll)
2658 {
2659   const int scroll = view2d_scroll_mapped(v2d->scroll);
2660   *r_scroll = scroll;
2661
2662   /* clamp x,y to region-coordinates first */
2663   rcti rect_region = *rect;
2664   BLI_rcti_translate(&rect_region, -ar->winrct.xmin, ar->winrct.ymin);
2665
2666   /* check if within scrollbars */
2667   if (scroll & V2D_SCROLL_HORIZONTAL) {
2668     if (IN_2D_HORIZ_SCROLL_RECT(v2d, &rect_region)) {
2669       return 'h';
2670     }
2671   }
2672   if (scroll & V2D_SCROLL_VERTICAL) {
2673     if (IN_2D_VERT_SCROLL_RECT(v2d, &rect_region)) {
2674       return 'v';
2675     }
2676   }
2677
2678   /* not found */
2679   return 0;
2680 }
2681
2682 char UI_view2d_mouse_in_scrollers(const ARegion *ar, const View2D *v2d, int x, int y)
2683 {
2684   int scroll_dummy = 0;
2685   return UI_view2d_mouse_in_scrollers_ex(ar, v2d, x, y, &scroll_dummy);
2686 }
2687
2688 char UI_view2d_rect_in_scrollers(const ARegion *ar, const View2D *v2d, const rcti *rect)
2689 {
2690   int scroll_dummy = 0;
2691   return UI_view2d_rect_in_scrollers_ex(ar, v2d, rect, &scroll_dummy);
2692 }
2693
2694 /* ******************* view2d text drawing cache ******************** */
2695
2696 typedef struct View2DString {
2697   struct View2DString *next;
2698   union {
2699     uchar ub[4];
2700     int pack;
2701   } col;
2702   rcti rect;
2703   int mval[2];
2704
2705   /* str is allocated past the end */
2706   char str[0];
2707 } View2DString;
2708
2709 /* assumes caches are used correctly, so for time being no local storage in v2d */
2710 static MemArena *g_v2d_strings_arena = NULL;
2711 static View2DString *g_v2d_strings = NULL;
2712
2713 void UI_view2d_text_cache_add(
2714     View2D *v2d, float x, float y, const char *str, size_t str_len, const char col[4])
2715 {
2716   int mval[2];
2717
2718   BLI_assert(str_len == strlen(str));
2719
2720   if (UI_view2d_view_to_region_clip(v2d, x, y, &mval[0], &mval[1])) {
2721     int alloc_len = str_len + 1;
2722     View2DString *v2s;
2723
2724     if (g_v2d_strings_arena == NULL) {
2725       g_v2d_strings_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 14), __func__);
2726     }
2727
2728     v2s = BLI_memarena_alloc(g_v2d_strings_arena, sizeof(View2DString) + alloc_len);
2729
2730     BLI_LINKS_PREPEND(g_v2d_strings, v2s);
2731
2732     v2s->col.pack = *((const int *)col);
2733
2734     memset(&v2s->rect, 0, sizeof(v2s->rect));
2735
2736     v2s->mval[0] = mval[0];
2737     v2s->mval[1] = mval[1];
2738
2739     memcpy(v2s->str, str, alloc_len);
2740   }
2741 }
2742
2743 /* no clip (yet) */
2744 void UI_view2d_text_cache_add_rectf(
2745     View2D *v2d, const rctf *rect_view, const char *str, size_t str_len, const char col[4])
2746 {
2747   rcti rect;
2748
2749   BLI_assert(str_len == strlen(str));
2750
2751   if (UI_view2d_view_to_region_rcti_clip(v2d, rect_view, &rect)) {
2752     int alloc_len = str_len + 1;
2753     View2DString *v2s;
2754
2755     if (g_v2d_strings_arena == NULL) {
2756       g_v2d_strings_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 14), __func__);
2757     }
2758
2759     v2s = BLI_memarena_alloc(g_v2d_strings_arena, sizeof(View2DString) + alloc_len);
2760
2761     BLI_LINKS_PREPEND(g_v2d_strings, v2s);
2762
2763     v2s->col.pack = *((const int *)col);
2764
2765     v2s->rect = rect;
2766
2767     v2s->mval[0] = v2s->rect.xmin;
2768     v2s->mval[1] = v2s->rect.ymin;
2769
2770     memcpy(v2s->str, str, alloc_len);
2771   }
2772 }
2773
2774 void UI_view2d_text_cache_draw(ARegion *ar)
2775 {
2776   View2DString *v2s;
2777   int col_pack_prev = 0;
2778
2779   /* investigate using BLF_ascender() */
2780   const int font_id = BLF_default();
2781
2782   BLF_set_default();
2783   const float default_height = g_v2d_strings ? BLF_height(font_id, "28", 3) : 0.0f;
2784
2785   wmOrtho2_region_pixelspace(ar);
2786
2787   for (v2s = g_v2d_strings; v2s; v2s = v2s->next) {
2788     int xofs = 0, yofs;
2789
2790     yofs = ceil(0.5f * (BLI_rcti_size_y(&v2s->rect) - default_height));
2791     if (yofs < 1) {
2792       yofs = 1;
2793     }
2794
2795     if (col_pack_prev != v2s->col.pack) {
2796       BLF_color3ubv(font_id, v2s->col.ub);
2797       col_pack_prev = v2s->col.pack;
2798     }
2799
2800     if (v2s->rect.xmin >= v2s->rect.xmax) {
2801       BLF_draw_default((float)(v2s->mval[0] + xofs),
2802                        (float)(v2s->mval[1] + yofs),
2803                        0.0,
2804                        v2s->str,
2805                        BLF_DRAW_STR_DUMMY_MAX);
2806     }
2807     else {
2808       BLF_enable(font_id, BLF_CLIPPING);
2809       BLF_clipping(
2810           font_id, v2s->rect.xmin - 4, v2s->rect.ymin - 4, v2s->rect.xmax + 4, v2s->rect.ymax + 4);
2811       BLF_draw_default(
2812           v2s->rect.xmin + xofs, v2s->rect.ymin + yofs, 0.0f, v2s->str, BLF_DRAW_STR_DUMMY_MAX);
2813       BLF_disable(font_id, BLF_CLIPPING);
2814     }
2815   }
2816   g_v2d_strings = NULL;
2817
2818   if (g_v2d_strings_arena) {
2819     BLI_memarena_free(g_v2d_strings_arena);
2820     g_v2d_strings_arena = NULL;
2821   }
2822 }
2823
2824 /* ******************************************************** */