Fix T68722: Improve Smooth algorithm for Thickness and Strength
[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_array.h"
35 #include "BLI_utildefines.h"
36 #include "BLI_link_utils.h"
37 #include "BLI_rect.h"
38 #include "BLI_math.h"
39 #include "BLI_memarena.h"
40 #include "BLI_timecode.h"
41 #include "BLI_string.h"
42
43 #include "BKE_context.h"
44 #include "BKE_screen.h"
45 #include "BKE_global.h"
46
47 #include "GPU_immediate.h"
48 #include "GPU_matrix.h"
49 #include "GPU_state.h"
50
51 #include "WM_api.h"
52
53 #include "BLF_api.h"
54
55 #include "ED_screen.h"
56
57 #include "UI_interface.h"
58 #include "UI_view2d.h"
59
60 #include "interface_intern.h"
61
62 static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize, bool mask_scrollers);
63
64 /* *********************************************************************** */
65
66 BLI_INLINE int clamp_float_to_int(const float f)
67 {
68   const float min = INT_MIN;
69   const float max = INT_MAX;
70
71   if (UNLIKELY(f < min)) {
72     return min;
73   }
74   else if (UNLIKELY(f > max)) {
75     return (int)max;
76   }
77   else {
78     return (int)f;
79   }
80 }
81
82 /**
83  * use instead of #BLI_rcti_rctf_copy so we have consistent behavior
84  * with users of #clamp_float_to_int.
85  */
86 BLI_INLINE void clamp_rctf_to_rcti(rcti *dst, const rctf *src)
87 {
88   dst->xmin = clamp_float_to_int(src->xmin);
89   dst->xmax = clamp_float_to_int(src->xmax);
90   dst->ymin = clamp_float_to_int(src->ymin);
91   dst->ymax = clamp_float_to_int(src->ymax);
92 }
93
94 /* XXX still unresolved: scrolls hide/unhide vs region mask handling */
95 /* XXX there's V2D_SCROLL_HORIZONTAL_HIDE and V2D_SCROLL_HORIZONTAL_FULLR ... */
96
97 /**
98  * helper to allow scrollbars to dynamically hide
99  * - returns a copy of the scrollbar settings with the flags to display
100  *   horizontal/vertical scrollbars removed
101  * - input scroll value is the v2d->scroll var
102  * - hide flags are set per region at drawtime
103  */
104 static int view2d_scroll_mapped(int scroll)
105 {
106   if (scroll & V2D_SCROLL_HORIZONTAL_FULLR) {
107     scroll &= ~(V2D_SCROLL_HORIZONTAL);
108   }
109   if (scroll & V2D_SCROLL_VERTICAL_FULLR) {
110     scroll &= ~(V2D_SCROLL_VERTICAL);
111   }
112   return scroll;
113 }
114
115 void UI_view2d_mask_from_win(const View2D *v2d, rcti *r_mask)
116 {
117   r_mask->xmin = 0;
118   r_mask->ymin = 0;
119   r_mask->xmax = v2d->winx - 1; /* -1 yes! masks are pixels */
120   r_mask->ymax = v2d->winy - 1;
121 }
122
123 /**
124  * Called each time #View2D.cur changes, to dynamically update masks.
125  *
126  * \param mask_scroll: Optionally clamp scrollbars by this region.
127  */
128 static void view2d_masks(View2D *v2d, bool check_scrollers, const rcti *mask_scroll)
129 {
130   int scroll;
131
132   /* mask - view frame */
133   UI_view2d_mask_from_win(v2d, &v2d->mask);
134   if (mask_scroll == NULL) {
135     mask_scroll = &v2d->mask;
136   }
137
138   if (check_scrollers) {
139     /* check size if hiding flag is set: */
140     if (v2d->scroll & V2D_SCROLL_HORIZONTAL_HIDE) {
141       if (!(v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES)) {
142         if (BLI_rctf_size_x(&v2d->tot) > BLI_rctf_size_x(&v2d->cur)) {
143           v2d->scroll &= ~V2D_SCROLL_HORIZONTAL_FULLR;
144         }
145         else {
146           v2d->scroll |= V2D_SCROLL_HORIZONTAL_FULLR;
147         }
148       }
149     }
150     if (v2d->scroll & V2D_SCROLL_VERTICAL_HIDE) {
151       if (!(v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES)) {
152         if (BLI_rctf_size_y(&v2d->tot) + 0.01f > BLI_rctf_size_y(&v2d->cur)) {
153           v2d->scroll &= ~V2D_SCROLL_VERTICAL_FULLR;
154         }
155         else {
156           v2d->scroll |= V2D_SCROLL_VERTICAL_FULLR;
157         }
158       }
159     }
160   }
161
162   scroll = view2d_scroll_mapped(v2d->scroll);
163
164   /* scrollers are based off regionsize
165    * - they can only be on one to two edges of the region they define
166    * - if they overlap, they must not occupy the corners (which are reserved for other widgets)
167    */
168   if (scroll) {
169     const int scroll_width = (v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) ?
170                                  V2D_SCROLL_HANDLE_WIDTH :
171                                  V2D_SCROLL_WIDTH;
172     const int scroll_height = (v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) ?
173                                   V2D_SCROLL_HANDLE_HEIGHT :
174                                   V2D_SCROLL_HEIGHT;
175
176     /* vertical scroller */
177     if (scroll & V2D_SCROLL_LEFT) {
178       /* on left-hand edge of region */
179       v2d->vert = *mask_scroll;
180       v2d->vert.xmax = scroll_width;
181     }
182     else if (scroll & V2D_SCROLL_RIGHT) {
183       /* on right-hand edge of region */
184       v2d->vert = *mask_scroll;
185       v2d->vert.xmax++; /* one pixel extra... was leaving a minor gap... */
186       v2d->vert.xmin = v2d->vert.xmax - scroll_width;
187     }
188
189     /* Currently, all regions that have vertical scale handles,
190      * also have the scrubbing area at the top.
191      * So the scrollbar has to move down a bit. */
192     if (scroll & V2D_SCROLL_VERTICAL_HANDLES) {
193       v2d->vert.ymax -= UI_TIME_SCRUB_MARGIN_Y;
194     }
195
196     /* horizontal scroller */
197     if (scroll & (V2D_SCROLL_BOTTOM)) {
198       /* on bottom edge of region */
199       v2d->hor = *mask_scroll;
200       v2d->hor.ymax = scroll_height;
201     }
202     else if (scroll & V2D_SCROLL_TOP) {
203       /* on upper edge of region */
204       v2d->hor = *mask_scroll;
205       v2d->hor.ymin = v2d->hor.ymax - scroll_height;
206     }
207
208     /* adjust vertical scroller if there's a horizontal scroller, to leave corner free */
209     if (scroll & V2D_SCROLL_VERTICAL) {
210       if (scroll & (V2D_SCROLL_BOTTOM)) {
211         /* on bottom edge of region */
212         v2d->vert.ymin = v2d->hor.ymax;
213       }
214       else if (scroll & V2D_SCROLL_TOP) {
215         /* on upper edge of region */
216         v2d->vert.ymax = v2d->hor.ymin;
217       }
218     }
219   }
220 }
221
222 /* Refresh and Validation */
223
224 /**
225  * Initialize all relevant View2D data (including view rects if first time)
226  * and/or refresh mask sizes after view resize.
227  *
228  * - For some of these presets, it is expected that the region will have defined some
229  *   additional settings necessary for the customization of the 2D viewport to its requirements
230  * - This function should only be called from region init() callbacks, where it is expected that
231  *   this is called before #UI_view2d_size_update(),
232  *   as this one checks that the rects are properly initialized.
233  */
234 void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy)
235 {
236   bool tot_changed = false, do_init;
237   uiStyle *style = UI_style_get();
238
239   do_init = (v2d->flag & V2D_IS_INITIALISED) == 0;
240
241   /* see eView2D_CommonViewTypes in UI_view2d.h for available view presets */
242   switch (type) {
243     /* 'standard view' - optimum setup for 'standard' view behavior,
244      * that should be used new views as basis for their
245      * own unique View2D settings, which should be used instead of this in most cases...
246      */
247     case V2D_COMMONVIEW_STANDARD: {
248       /* for now, aspect ratio should be maintained,
249        * and zoom is clamped within sane default limits */
250       v2d->keepzoom = (V2D_KEEPASPECT | V2D_LIMITZOOM);
251       v2d->minzoom = 0.01f;
252       v2d->maxzoom = 1000.0f;
253
254       /* View2D tot rect and cur should be same size,
255        * and aligned using 'standard' OpenGL coordinates for now:
256        * - region can resize 'tot' later to fit other data
257        * - keeptot is only within bounds, as strict locking is not that critical
258        * - view is aligned for (0,0) -> (winx-1, winy-1) setup
259        */
260       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y);
261       v2d->keeptot = V2D_KEEPTOT_BOUNDS;
262
263       if (do_init) {
264         v2d->tot.xmin = v2d->tot.ymin = 0.0f;
265         v2d->tot.xmax = (float)(winx - 1);
266         v2d->tot.ymax = (float)(winy - 1);
267
268         v2d->cur = v2d->tot;
269       }
270       /* scrollers - should we have these by default? */
271       /* XXX for now, we don't override this, or set it either! */
272       break;
273     }
274     /* 'list/channel view' - zoom, aspect ratio, and alignment restrictions are set here */
275     case V2D_COMMONVIEW_LIST: {
276       /* zoom + aspect ratio are locked */
277       v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT);
278       v2d->minzoom = v2d->maxzoom = 1.0f;
279
280       /* tot rect has strictly regulated placement, and must only occur in +/- quadrant */
281       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y);
282       v2d->keeptot = V2D_KEEPTOT_STRICT;
283       tot_changed = do_init;
284
285       /* scroller settings are currently not set here... that is left for regions... */
286       break;
287     }
288     /* 'stack view' - practically the same as list/channel view,
289      * except is located in the pos y half instead.
290      * Zoom, aspect ratio, and alignment restrictions are set here. */
291     case V2D_COMMONVIEW_STACK: {
292       /* zoom + aspect ratio are locked */
293       v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT);
294       v2d->minzoom = v2d->maxzoom = 1.0f;
295
296       /* tot rect has strictly regulated placement, and must only occur in +/+ quadrant */
297       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y);
298       v2d->keeptot = V2D_KEEPTOT_STRICT;
299       tot_changed = do_init;
300
301       /* scroller settings are currently not set here... that is left for regions... */
302       break;
303     }
304     /* 'header' regions - zoom, aspect ratio,
305      * alignment, and panning restrictions are set here */
306     case V2D_COMMONVIEW_HEADER: {
307       /* zoom + aspect ratio are locked */
308       v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT);
309       v2d->minzoom = v2d->maxzoom = 1.0f;
310
311       if (do_init) {
312         v2d->tot.xmin = 0.0f;
313         v2d->tot.xmax = winx;
314         v2d->tot.ymin = 0.0f;
315         v2d->tot.ymax = winy;
316         v2d->cur = v2d->tot;
317
318         v2d->min[0] = v2d->max[0] = (float)(winx - 1);
319         v2d->min[1] = v2d->max[1] = (float)(winy - 1);
320       }
321       /* tot rect has strictly regulated placement, and must only occur in +/+ quadrant */
322       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y);
323       v2d->keeptot = V2D_KEEPTOT_STRICT;
324       tot_changed = do_init;
325
326       /* panning in y-axis is prohibited */
327       v2d->keepofs = V2D_LOCKOFS_Y;
328
329       /* absolutely no scrollers allowed */
330       v2d->scroll = 0;
331       break;
332     }
333     /* panels view, with horizontal/vertical align */
334     case V2D_COMMONVIEW_PANELS_UI: {
335
336       /* for now, aspect ratio should be maintained,
337        * and zoom is clamped within sane default limits */
338       v2d->keepzoom = (V2D_KEEPASPECT | V2D_LIMITZOOM | V2D_KEEPZOOM);
339       v2d->minzoom = 0.5f;
340       v2d->maxzoom = 2.0f;
341
342       v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y);
343       v2d->keeptot = V2D_KEEPTOT_BOUNDS;
344
345       /* note, scroll is being flipped in ED_region_panels() drawing */
346       v2d->scroll |= (V2D_SCROLL_HORIZONTAL_HIDE | V2D_SCROLL_VERTICAL_HIDE);
347
348       /* initialize without scroll bars (interferes with zoom level see: T47047) */
349       if (do_init) {
350         v2d->scroll |= (V2D_SCROLL_VERTICAL_FULLR | V2D_SCROLL_HORIZONTAL_FULLR);
351       }
352
353       if (do_init) {
354         float panelzoom = (style) ? style->panelzoom : 1.0f;
355
356         v2d->tot.xmin = 0.0f;
357         v2d->tot.xmax = winx;
358
359         v2d->tot.ymax = 0.0f;
360         v2d->tot.ymin = -winy;
361
362         v2d->cur.xmin = 0.0f;
363         v2d->cur.xmax = (winx)*panelzoom;
364
365         v2d->cur.ymax = 0.0f;
366         v2d->cur.ymin = (-winy) * panelzoom;
367       }
368       break;
369     }
370     /* other view types are completely defined using their own settings already */
371     default:
372       /* we don't do anything here,
373        * as settings should be fine, but just make sure that rect */
374       break;
375   }
376
377   /* set initialized flag so that View2D doesn't get reinitialised next time again */
378   v2d->flag |= V2D_IS_INITIALISED;
379
380   /* store view size */
381   v2d->winx = winx;
382   v2d->winy = winy;
383
384   /* set masks (always do), but leave scroller scheck to totrect_set */
385   view2d_masks(v2d, 0, NULL);
386
387   if (do_init) {
388     /* Visible by default. */
389     v2d->alpha_hor = v2d->alpha_vert = 255;
390   }
391
392   /* set 'tot' rect before setting cur? */
393   /* XXX confusing stuff here still -
394    * I made this function not check scroller hide - that happens in totrect_set */
395   if (tot_changed) {
396     UI_view2d_totRect_set_resize(v2d, winx, winy, !do_init);
397   }
398   else {
399     ui_view2d_curRect_validate_resize(v2d, !do_init, 0);
400   }
401 }
402
403 /**
404  * Ensure View2D rects remain in a viable configuration
405  * 'cur' is not allowed to be: larger than max, smaller than min, or outside of 'tot'
406  */
407 // XXX pre2.5 -> this used to be called  test_view2d()
408 static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize, bool mask_scrollers)
409 {
410   float totwidth, totheight, curwidth, curheight, width, height;
411   float winx, winy;
412   rctf *cur, *tot;
413
414   /* use mask as size of region that View2D resides in, as it takes into account
415    * scrollbars already - keep in sync with zoomx/zoomy in view_zoomstep_apply_ex! */
416   winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1);
417   winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1);
418
419   /* get pointers to rcts for less typing */
420   cur = &v2d->cur;
421   tot = &v2d->tot;
422
423   /* we must satisfy the following constraints (in decreasing order of importance):
424    * - alignment restrictions are respected
425    * - cur must not fall outside of tot
426    * - axis locks (zoom and offset) must be maintained
427    * - zoom must not be excessive (check either sizes or zoom values)
428    * - aspect ratio should be respected (NOTE: this is quite closely related to zoom too)
429    */
430
431   /* Step 1: if keepzoom, adjust the sizes of the rects only
432    * - firstly, we calculate the sizes of the rects
433    * - curwidth and curheight are saved as reference... modify width and height values here
434    */
435   totwidth = BLI_rctf_size_x(tot);
436   totheight = BLI_rctf_size_y(tot);
437   /* keep in sync with zoomx/zoomy in view_zoomstep_apply_ex! */
438   curwidth = width = BLI_rctf_size_x(cur);
439   curheight = height = BLI_rctf_size_y(cur);
440
441   /* if zoom is locked, size on the appropriate axis is reset to mask size */
442   if (v2d->keepzoom & V2D_LOCKZOOM_X) {
443     width = winx;
444   }
445   if (v2d->keepzoom & V2D_LOCKZOOM_Y) {
446     height = winy;
447   }
448
449   /* values used to divide, so make it safe
450    * NOTE: width and height must use FLT_MIN instead of 1, otherwise it is impossible to
451    *       get enough resolution in Graph Editor for editing some curves
452    */
453   if (width < FLT_MIN) {
454     width = 1;
455   }
456   if (height < FLT_MIN) {
457     height = 1;
458   }
459   if (winx < 1) {
460     winx = 1;
461   }
462   if (winy < 1) {
463     winy = 1;
464   }
465
466   /* V2D_LIMITZOOM indicates that zoom level should be preserved when the window size changes */
467   if (resize && (v2d->keepzoom & V2D_KEEPZOOM)) {
468     float zoom, oldzoom;
469
470     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
471       zoom = winx / width;
472       oldzoom = v2d->oldwinx / curwidth;
473
474       if (oldzoom != zoom) {
475         width *= zoom / oldzoom;
476       }
477     }
478
479     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
480       zoom = winy / height;
481       oldzoom = v2d->oldwiny / curheight;
482
483       if (oldzoom != zoom) {
484         height *= zoom / oldzoom;
485       }
486     }
487   }
488   /* keepzoom (V2D_LIMITZOOM set), indicates that zoom level on each axis must not exceed limits
489    * NOTE: in general, it is not expected that the lock-zoom will be used in conjunction with this
490    */
491   else if (v2d->keepzoom & V2D_LIMITZOOM) {
492
493     /* check if excessive zoom on x-axis */
494     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) {
495       const float zoom = winx / width;
496       if (zoom < v2d->minzoom) {
497         width = winx / v2d->minzoom;
498       }
499       else if (zoom > v2d->maxzoom) {
500         width = winx / v2d->maxzoom;
501       }
502     }
503
504     /* check if excessive zoom on y-axis */
505     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) {
506       const float zoom = winy / height;
507       if (zoom < v2d->minzoom) {
508         height = winy / v2d->minzoom;
509       }
510       else if (zoom > v2d->maxzoom) {
511         height = winy / v2d->maxzoom;
512       }
513     }
514   }
515   else {
516     /* make sure sizes don't exceed that of the min/max sizes
517      * (even though we're not doing zoom clamping) */
518     CLAMP(width, v2d->min[0], v2d->max[0]);
519     CLAMP(height, v2d->min[1], v2d->max[1]);
520   }
521
522   /* check if we should restore aspect ratio (if view size changed) */
523   if (v2d->keepzoom & V2D_KEEPASPECT) {
524     bool do_x = false, do_y = false, do_cur /* , do_win */ /* UNUSED */;
525     float curRatio, winRatio;
526
527     /* when a window edge changes, the aspect ratio can't be used to
528      * find which is the best new 'cur' rect. that's why it stores 'old'
529      */
530     if (winx != v2d->oldwinx) {
531       do_x = true;
532     }
533     if (winy != v2d->oldwiny) {
534       do_y = true;
535     }
536
537     curRatio = height / width;
538     winRatio = winy / winx;
539
540     /* both sizes change (area/region maximized)  */
541     if (do_x == do_y) {
542       if (do_x && do_y) {
543         /* here is 1,1 case, so all others must be 0,0 */
544         if (fabsf(winx - v2d->oldwinx) > fabsf(winy - v2d->oldwiny)) {
545           do_y = false;
546         }
547         else {
548           do_x = false;
549         }
550       }
551       else if (winRatio > curRatio) {
552         do_x = false;
553       }
554       else {
555         do_x = true;
556       }
557     }
558     do_cur = do_x;
559     /* do_win = do_y; */ /* UNUSED */
560
561     if (do_cur) {
562       if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winx != v2d->oldwinx)) {
563         /* Special exception for Outliner (and later channel-lists):
564          * - The view may be moved left to avoid contents
565          *   being pushed out of view when view shrinks.
566          * - The keeptot code will make sure cur->xmin will not be less than tot->xmin
567          *   (which cannot be allowed).
568          * - width is not adjusted for changed ratios here.
569          */
570         if (winx < v2d->oldwinx) {
571           float temp = v2d->oldwinx - winx;
572
573           cur->xmin -= temp;
574           cur->xmax -= temp;
575
576           /* width does not get modified, as keepaspect here is just set to make
577            * sure visible area adjusts to changing view shape!
578            */
579         }
580       }
581       else {
582         /* portrait window: correct for x */
583         width = height / winRatio;
584       }
585     }
586     else {
587       if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winy != v2d->oldwiny)) {
588         /* special exception for Outliner (and later channel-lists):
589          * - Currently, no actions need to be taken here...
590          */
591
592         if (winy < v2d->oldwiny) {
593           float temp = v2d->oldwiny - winy;
594
595           if (v2d->align & V2D_ALIGN_NO_NEG_Y) {
596             cur->ymin -= temp;
597             cur->ymax -= temp;
598           }
599           else { /* Assume V2D_ALIGN_NO_POS_Y or combination */
600             cur->ymin += temp;
601             cur->ymax += temp;
602           }
603         }
604       }
605       else {
606         /* landscape window: correct for y */
607         height = width * winRatio;
608       }
609     }
610
611     /* store region size for next time */
612     v2d->oldwinx = (short)winx;
613     v2d->oldwiny = (short)winy;
614   }
615
616   /* Step 2: apply new sizes to cur rect,
617    * but need to take into account alignment settings here... */
618   if ((width != curwidth) || (height != curheight)) {
619     float temp, dh;
620
621     /* resize from centerpoint, unless otherwise specified */
622     if (width != curwidth) {
623       if (v2d->keepofs & V2D_LOCKOFS_X) {
624         cur->xmax += width - BLI_rctf_size_x(cur);
625       }
626       else if (v2d->keepofs & V2D_KEEPOFS_X) {
627         if (v2d->align & V2D_ALIGN_NO_POS_X) {
628           cur->xmin -= width - BLI_rctf_size_x(cur);
629         }
630         else {
631           cur->xmax += width - BLI_rctf_size_x(cur);
632         }
633       }
634       else {
635         temp = BLI_rctf_cent_x(cur);
636         dh = width * 0.5f;
637
638         cur->xmin = temp - dh;
639         cur->xmax = temp + dh;
640       }
641     }
642     if (height != curheight) {
643       if (v2d->keepofs & V2D_LOCKOFS_Y) {
644         cur->ymax += height - BLI_rctf_size_y(cur);
645       }
646       else if (v2d->keepofs & V2D_KEEPOFS_Y) {
647         if (v2d->align & V2D_ALIGN_NO_POS_Y) {
648           cur->ymin -= height - BLI_rctf_size_y(cur);
649         }
650         else {
651           cur->ymax += height - BLI_rctf_size_y(cur);
652         }
653       }
654       else {
655         temp = BLI_rctf_cent_y(cur);
656         dh = height * 0.5f;
657
658         cur->ymin = temp - dh;
659         cur->ymax = temp + dh;
660       }
661     }
662   }
663
664   /* Step 3: adjust so that it doesn't fall outside of bounds of 'tot' */
665   if (v2d->keeptot) {
666     float temp, diff;
667
668     /* recalculate extents of cur */
669     curwidth = BLI_rctf_size_x(cur);
670     curheight = BLI_rctf_size_y(cur);
671
672     /* width */
673     if ((curwidth > totwidth) &&
674         !(v2d->keepzoom & (V2D_KEEPZOOM | V2D_LOCKZOOM_X | V2D_LIMITZOOM))) {
675       /* if zoom doesn't have to be maintained, just clamp edges */
676       if (cur->xmin < tot->xmin) {
677         cur->xmin = tot->xmin;
678       }
679       if (cur->xmax > tot->xmax) {
680         cur->xmax = tot->xmax;
681       }
682     }
683     else if (v2d->keeptot == V2D_KEEPTOT_STRICT) {
684       /* This is an exception for the outliner (and later channel-lists, headers)
685        * - must clamp within tot rect (absolutely no excuses)
686        * --> therefore, cur->xmin must not be less than tot->xmin
687        */
688       if (cur->xmin < tot->xmin) {
689         /* move cur across so that it sits at minimum of tot */
690         temp = tot->xmin - cur->xmin;
691
692         cur->xmin += temp;
693         cur->xmax += temp;
694       }
695       else if (cur->xmax > tot->xmax) {
696         /* - only offset by difference of cur-xmax and tot-xmax if that would not move
697          *   cur-xmin to lie past tot-xmin
698          * - otherwise, simply shift to tot-xmin???
699          */
700         temp = cur->xmax - tot->xmax;
701
702         if ((cur->xmin - temp) < tot->xmin) {
703           /* only offset by difference from cur-min and tot-min */
704           temp = cur->xmin - tot->xmin;
705
706           cur->xmin -= temp;
707           cur->xmax -= temp;
708         }
709         else {
710           cur->xmin -= temp;
711           cur->xmax -= temp;
712         }
713       }
714     }
715     else {
716       /* This here occurs when:
717        * - width too big, but maintaining zoom (i.e. widths cannot be changed)
718        * - width is OK, but need to check if outside of boundaries
719        *
720        * So, resolution is to just shift view by the gap between the extremities.
721        * We favour moving the 'minimum' across, as that's origin for most things
722        * (XXX - in the past, max was favored... if there are bugs, swap!)
723        */
724       if ((cur->xmin < tot->xmin) && (cur->xmax > tot->xmax)) {
725         /* outside boundaries on both sides,
726          * so take middle-point of tot, and place in balanced way */
727         temp = BLI_rctf_cent_x(tot);
728         diff = curwidth * 0.5f;
729
730         cur->xmin = temp - diff;
731         cur->xmax = temp + diff;
732       }
733       else if (cur->xmin < tot->xmin) {
734         /* move cur across so that it sits at minimum of tot */
735         temp = tot->xmin - cur->xmin;
736
737         cur->xmin += temp;
738         cur->xmax += temp;
739       }
740       else if (cur->xmax > tot->xmax) {
741         /* - only offset by difference of cur-xmax and tot-xmax if that would not move
742          *   cur-xmin to lie past tot-xmin
743          * - otherwise, simply shift to tot-xmin???
744          */
745         temp = cur->xmax - tot->xmax;
746
747         if ((cur->xmin - temp) < tot->xmin) {
748           /* only offset by difference from cur-min and tot-min */
749           temp = cur->xmin - tot->xmin;
750
751           cur->xmin -= temp;
752           cur->xmax -= temp;
753         }
754         else {
755           cur->xmin -= temp;
756           cur->xmax -= temp;
757         }
758       }
759     }
760
761     /* height */
762     if ((curheight > totheight) &&
763         !(v2d->keepzoom & (V2D_KEEPZOOM | V2D_LOCKZOOM_Y | V2D_LIMITZOOM))) {
764       /* if zoom doesn't have to be maintained, just clamp edges */
765       if (cur->ymin < tot->ymin) {
766         cur->ymin = tot->ymin;
767       }
768       if (cur->ymax > tot->ymax) {
769         cur->ymax = tot->ymax;
770       }
771     }
772     else {
773       /* This here occurs when:
774        * - height too big, but maintaining zoom (i.e. heights cannot be changed)
775        * - height is OK, but need to check if outside of boundaries
776        *
777        * So, resolution is to just shift view by the gap between the extremities.
778        * We favour moving the 'minimum' across, as that's origin for most things
779        */
780       if ((cur->ymin < tot->ymin) && (cur->ymax > tot->ymax)) {
781         /* outside boundaries on both sides,
782          * so take middle-point of tot, and place in balanced way */
783         temp = BLI_rctf_cent_y(tot);
784         diff = curheight * 0.5f;
785
786         cur->ymin = temp - diff;
787         cur->ymax = temp + diff;
788       }
789       else if (cur->ymin < tot->ymin) {
790         /* there's still space remaining, so shift up */
791         temp = tot->ymin - cur->ymin;
792
793         cur->ymin += temp;
794         cur->ymax += temp;
795       }
796       else if (cur->ymax > tot->ymax) {
797         /* there's still space remaining, so shift down */
798         temp = cur->ymax - tot->ymax;
799
800         cur->ymin -= temp;
801         cur->ymax -= temp;
802       }
803     }
804   }
805
806   /* Step 4: Make sure alignment restrictions are respected */
807   if (v2d->align) {
808     /* If alignment flags are set (but keeptot is not), they must still be respected, as although
809      * they don't specify any particular bounds to stay within, they do define ranges which are
810      * invalid.
811      *
812      * Here, we only check to make sure that on each axis, the 'cur' rect doesn't stray into these
813      * invalid zones, otherwise we offset.
814      */
815
816     /* handle width - posx and negx flags are mutually exclusive, so watch out */
817     if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
818       /* width is in negative-x half */
819       if (v2d->cur.xmax > 0) {
820         v2d->cur.xmin -= v2d->cur.xmax;
821         v2d->cur.xmax = 0.0f;
822       }
823     }
824     else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
825       /* width is in positive-x half */
826       if (v2d->cur.xmin < 0) {
827         v2d->cur.xmax -= v2d->cur.xmin;
828         v2d->cur.xmin = 0.0f;
829       }
830     }
831
832     /* handle height - posx and negx flags are mutually exclusive, so watch out */
833     if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
834       /* height is in negative-y half */
835       if (v2d->cur.ymax > 0) {
836         v2d->cur.ymin -= v2d->cur.ymax;
837         v2d->cur.ymax = 0.0f;
838       }
839     }
840     else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
841       /* height is in positive-y half */
842       if (v2d->cur.ymin < 0) {
843         v2d->cur.ymax -= v2d->cur.ymin;
844         v2d->cur.ymin = 0.0f;
845       }
846     }
847   }
848
849   /* set masks */
850   view2d_masks(v2d, mask_scrollers, NULL);
851 }
852
853 void UI_view2d_curRect_validate(View2D *v2d)
854 {
855   ui_view2d_curRect_validate_resize(v2d, 0, 1);
856 }
857
858 /* ------------------ */
859
860 /* Called by menus to activate it, or by view2d operators
861  * to make sure 'related' views stay in synchrony */
862 void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag)
863 {
864   ScrArea *sa;
865   ARegion *ar;
866
867   /* don't continue if no view syncing to be done */
868   if ((v2dcur->flag & (V2D_VIEWSYNC_SCREEN_TIME | V2D_VIEWSYNC_AREA_VERTICAL)) == 0) {
869     return;
870   }
871
872   /* check if doing within area syncing (i.e. channels/vertical) */
873   if ((v2dcur->flag & V2D_VIEWSYNC_AREA_VERTICAL) && (area)) {
874     for (ar = area->regionbase.first; ar; ar = ar->next) {
875       /* don't operate on self */
876       if (v2dcur != &ar->v2d) {
877         /* only if view has vertical locks enabled */
878         if (ar->v2d.flag & V2D_VIEWSYNC_AREA_VERTICAL) {
879           if (flag == V2D_LOCK_COPY) {
880             /* other views with locks on must copy active */
881             ar->v2d.cur.ymin = v2dcur->cur.ymin;
882             ar->v2d.cur.ymax = v2dcur->cur.ymax;
883           }
884           else { /* V2D_LOCK_SET */
885                  /* active must copy others */
886             v2dcur->cur.ymin = ar->v2d.cur.ymin;
887             v2dcur->cur.ymax = ar->v2d.cur.ymax;
888           }
889
890           /* region possibly changed, so refresh */
891           ED_region_tag_redraw_no_rebuild(ar);
892         }
893       }
894     }
895   }
896
897   /* check if doing whole screen syncing (i.e. time/horizontal) */
898   if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) {
899     for (sa = screen->areabase.first; sa; sa = sa->next) {
900       for (ar = sa->regionbase.first; ar; ar = ar->next) {
901         /* don't operate on self */
902         if (v2dcur != &ar->v2d) {
903           /* only if view has horizontal locks enabled */
904           if (ar->v2d.flag & V2D_VIEWSYNC_SCREEN_TIME) {
905             if (flag == V2D_LOCK_COPY) {
906               /* other views with locks on must copy active */
907               ar->v2d.cur.xmin = v2dcur->cur.xmin;
908               ar->v2d.cur.xmax = v2dcur->cur.xmax;
909             }
910             else { /* V2D_LOCK_SET */
911                    /* active must copy others */
912               v2dcur->cur.xmin = ar->v2d.cur.xmin;
913               v2dcur->cur.xmax = ar->v2d.cur.xmax;
914             }
915
916             /* region possibly changed, so refresh */
917             ED_region_tag_redraw_no_rebuild(ar);
918           }
919         }
920       }
921     }
922   }
923 }
924
925 /**
926  * Restore 'cur' rect to standard orientation (i.e. optimal maximum view of tot).
927  * This does not take into account if zooming the view on an axis
928  * will improve the view (if allowed).
929  */
930 void UI_view2d_curRect_reset(View2D *v2d)
931 {
932   float width, height;
933
934   /* assume width and height of 'cur' rect by default, should be same size as mask */
935   width = (float)(BLI_rcti_size_x(&v2d->mask) + 1);
936   height = (float)(BLI_rcti_size_y(&v2d->mask) + 1);
937
938   /* handle width - posx and negx flags are mutually exclusive, so watch out */
939   if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
940     /* width is in negative-x half */
941     v2d->cur.xmin = -width;
942     v2d->cur.xmax = 0.0f;
943   }
944   else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
945     /* width is in positive-x half */
946     v2d->cur.xmin = 0.0f;
947     v2d->cur.xmax = width;
948   }
949   else {
950     /* width is centered around (x == 0) */
951     const float dx = width / 2.0f;
952
953     v2d->cur.xmin = -dx;
954     v2d->cur.xmax = dx;
955   }
956
957   /* handle height - posx and negx flags are mutually exclusive, so watch out */
958   if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
959     /* height is in negative-y half */
960     v2d->cur.ymin = -height;
961     v2d->cur.ymax = 0.0f;
962   }
963   else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
964     /* height is in positive-y half */
965     v2d->cur.ymin = 0.0f;
966     v2d->cur.ymax = height;
967   }
968   else {
969     /* height is centered around (y == 0) */
970     const float dy = height / 2.0f;
971
972     v2d->cur.ymin = -dy;
973     v2d->cur.ymax = dy;
974   }
975 }
976
977 /* ------------------ */
978
979 /* Change the size of the maximum viewable area (i.e. 'tot' rect) */
980 void UI_view2d_totRect_set_resize(View2D *v2d, int width, int height, bool resize)
981 {
982   //  int scroll = view2d_scroll_mapped(v2d->scroll);
983
984   /* don't do anything if either value is 0 */
985   width = abs(width);
986   height = abs(height);
987
988   /* hrumf! */
989   /* XXX: there are work arounds for this in the panel and file browse code. */
990   /* round to int, because this is called with width + V2D_SCROLL_WIDTH */
991   //  if (scroll & V2D_SCROLL_HORIZONTAL) {
992   //      width -= (int)V2D_SCROLL_WIDTH;
993   //  }
994   //  if (scroll & V2D_SCROLL_VERTICAL) {
995   //      height -= (int)V2D_SCROLL_HEIGHT;
996   //  }
997
998   if (ELEM(0, width, height)) {
999     if (G.debug & G_DEBUG) {
1000       printf("Error: View2D totRect set exiting: v2d=%p width=%d height=%d\n",
1001              (void *)v2d,
1002              width,
1003              height);  // XXX temp debug info
1004     }
1005     return;
1006   }
1007
1008   /* handle width - posx and negx flags are mutually exclusive, so watch out */
1009   if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
1010     /* width is in negative-x half */
1011     v2d->tot.xmin = (float)-width;
1012     v2d->tot.xmax = 0.0f;
1013   }
1014   else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
1015     /* width is in positive-x half */
1016     v2d->tot.xmin = 0.0f;
1017     v2d->tot.xmax = (float)width;
1018   }
1019   else {
1020     /* width is centered around (x == 0) */
1021     const float dx = (float)width / 2.0f;
1022
1023     v2d->tot.xmin = -dx;
1024     v2d->tot.xmax = dx;
1025   }
1026
1027   /* handle height - posx and negx flags are mutually exclusive, so watch out */
1028   if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
1029     /* height is in negative-y half */
1030     v2d->tot.ymin = (float)-height;
1031     v2d->tot.ymax = 0.0f;
1032   }
1033   else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
1034     /* height is in positive-y half */
1035     v2d->tot.ymin = 0.0f;
1036     v2d->tot.ymax = (float)height;
1037   }
1038   else {
1039     /* height is centered around (y == 0) */
1040     const float dy = (float)height / 2.0f;
1041
1042     v2d->tot.ymin = -dy;
1043     v2d->tot.ymax = dy;
1044   }
1045
1046   /* make sure that 'cur' rect is in a valid state as a result of these changes */
1047   ui_view2d_curRect_validate_resize(v2d, resize, 1);
1048 }
1049
1050 void UI_view2d_totRect_set(View2D *v2d, int width, int height)
1051 {
1052   int scroll = view2d_scroll_mapped(v2d->scroll);
1053
1054   UI_view2d_totRect_set_resize(v2d, width, height, 0);
1055
1056   /* solve bad recursion... if scroller state changed,
1057    * mask is different, so you get different rects */
1058   if (scroll != view2d_scroll_mapped(v2d->scroll)) {
1059     UI_view2d_totRect_set_resize(v2d, width, height, 0);
1060   }
1061 }
1062
1063 bool UI_view2d_tab_set(View2D *v2d, int tab)
1064 {
1065   float default_offset[2] = {0.0f, 0.0f};
1066   float *offset, *new_offset;
1067   bool changed = false;
1068
1069   /* if tab changed, change offset */
1070   if (tab != v2d->tab_cur && v2d->tab_offset) {
1071     if (tab < v2d->tab_num) {
1072       offset = &v2d->tab_offset[tab * 2];
1073     }
1074     else {
1075       offset = default_offset;
1076     }
1077
1078     v2d->cur.xmax += offset[0] - v2d->cur.xmin;
1079     v2d->cur.xmin = offset[0];
1080
1081     v2d->cur.ymin += offset[1] - v2d->cur.ymax;
1082     v2d->cur.ymax = offset[1];
1083
1084     /* validation should happen in subsequent totRect_set */
1085
1086     changed = true;
1087   }
1088
1089   /* resize array if needed */
1090   if (tab >= v2d->tab_num) {
1091     new_offset = MEM_callocN(sizeof(float) * (tab + 1) * 2, "view2d tab offset");
1092
1093     if (v2d->tab_offset) {
1094       memcpy(new_offset, v2d->tab_offset, sizeof(float) * v2d->tab_num * 2);
1095       MEM_freeN(v2d->tab_offset);
1096     }
1097
1098     v2d->tab_offset = new_offset;
1099     v2d->tab_num = tab + 1;
1100   }
1101
1102   /* set current tab and offset */
1103   v2d->tab_cur = tab;
1104   v2d->tab_offset[2 * tab + 0] = v2d->cur.xmin;
1105   v2d->tab_offset[2 * tab + 1] = v2d->cur.ymax;
1106
1107   return changed;
1108 }
1109
1110 void UI_view2d_zoom_cache_reset(void)
1111 {
1112   /* TODO(sergey): This way we avoid threading conflict with sequencer rendering
1113    * text strip. But ideally we want to make glyph cache to be fully safe
1114    * for threading.
1115    */
1116   if (G.is_rendering) {
1117     return;
1118   }
1119   /* While scaling we can accumulate fonts at many sizes (~20 or so).
1120    * Not an issue with embedded font, but can use over 500Mb with i18n ones! See [#38244]. */
1121
1122   /* note: only some views draw text, we could check for this case to avoid clearning cache */
1123   BLF_cache_clear();
1124 }
1125
1126 /* *********************************************************************** */
1127 /* View Matrix Setup */
1128
1129 /* mapping function to ensure 'cur' draws extended over the area where sliders are */
1130 static void view2d_map_cur_using_mask(const View2D *v2d, rctf *r_curmasked)
1131 {
1132   *r_curmasked = v2d->cur;
1133
1134   if (view2d_scroll_mapped(v2d->scroll)) {
1135     float sizex = BLI_rcti_size_x(&v2d->mask);
1136     float sizey = BLI_rcti_size_y(&v2d->mask);
1137
1138     /* prevent tiny or narrow regions to get
1139      * invalid coordinates - mask can get negative even... */
1140     if (sizex > 0.0f && sizey > 0.0f) {
1141       float dx = BLI_rctf_size_x(&v2d->cur) / (sizex + 1);
1142       float dy = BLI_rctf_size_y(&v2d->cur) / (sizey + 1);
1143
1144       if (v2d->mask.xmin != 0) {
1145         r_curmasked->xmin -= dx * (float)v2d->mask.xmin;
1146       }
1147       if (v2d->mask.xmax + 1 != v2d->winx) {
1148         r_curmasked->xmax += dx * (float)(v2d->winx - v2d->mask.xmax - 1);
1149       }
1150
1151       if (v2d->mask.ymin != 0) {
1152         r_curmasked->ymin -= dy * (float)v2d->mask.ymin;
1153       }
1154       if (v2d->mask.ymax + 1 != v2d->winy) {
1155         r_curmasked->ymax += dy * (float)(v2d->winy - v2d->mask.ymax - 1);
1156       }
1157     }
1158   }
1159 }
1160
1161 /* Set view matrices to use 'cur' rect as viewing frame for View2D drawing */
1162 void UI_view2d_view_ortho(const View2D *v2d)
1163 {
1164   rctf curmasked;
1165   const int sizex = BLI_rcti_size_x(&v2d->mask);
1166   const int sizey = BLI_rcti_size_y(&v2d->mask);
1167   const float eps = 0.001f;
1168   float xofs = 0.0f, yofs = 0.0f;
1169
1170   /* Pixel offsets (-GLA_PIXEL_OFS) are needed to get 1:1
1171    * correspondence with pixels for smooth UI drawing,
1172    * but only applied where requested.
1173    */
1174   /* XXX brecht: instead of zero at least use a tiny offset, otherwise
1175    * pixel rounding is effectively random due to float inaccuracy */
1176   if (sizex > 0) {
1177     xofs = eps * BLI_rctf_size_x(&v2d->cur) / sizex;
1178   }
1179   if (sizey > 0) {
1180     yofs = eps * BLI_rctf_size_y(&v2d->cur) / sizey;
1181   }
1182
1183   /* apply mask-based adjustments to cur rect (due to scrollers),
1184    * to eliminate scaling artifacts */
1185   view2d_map_cur_using_mask(v2d, &curmasked);
1186
1187   BLI_rctf_translate(&curmasked, -xofs, -yofs);
1188
1189   /* XXX ton: this flag set by outliner, for icons */
1190   if (v2d->flag & V2D_PIXELOFS_X) {
1191     curmasked.xmin = floorf(curmasked.xmin) - (eps + xofs);
1192     curmasked.xmax = floorf(curmasked.xmax) - (eps + xofs);
1193   }
1194   if (v2d->flag & V2D_PIXELOFS_Y) {
1195     curmasked.ymin = floorf(curmasked.ymin) - (eps + yofs);
1196     curmasked.ymax = floorf(curmasked.ymax) - (eps + yofs);
1197   }
1198
1199   /* set matrix on all appropriate axes */
1200   wmOrtho2(curmasked.xmin, curmasked.xmax, curmasked.ymin, curmasked.ymax);
1201 }
1202
1203 /**
1204  * Set view matrices to only use one axis of 'cur' only
1205  *
1206  * \param xaxis: if non-zero, only use cur x-axis,
1207  * otherwise use cur-yaxis (mostly this will be used for x).
1208  */
1209 void UI_view2d_view_orthoSpecial(ARegion *ar, View2D *v2d, const bool xaxis)
1210 {
1211   rctf curmasked;
1212   float xofs, yofs;
1213
1214   /* Pixel offsets (-GLA_PIXEL_OFS) are needed to get 1:1
1215    * correspondence with pixels for smooth UI drawing,
1216    * but only applied where requested.
1217    */
1218   /* XXX temp (ton) */
1219   xofs = 0.0f;  // (v2d->flag & V2D_PIXELOFS_X) ? GLA_PIXEL_OFS : 0.0f;
1220   yofs = 0.0f;  // (v2d->flag & V2D_PIXELOFS_Y) ? GLA_PIXEL_OFS : 0.0f;
1221
1222   /* apply mask-based adjustments to cur rect (due to scrollers),
1223    * to eliminate scaling artifacts */
1224   view2d_map_cur_using_mask(v2d, &curmasked);
1225
1226   /* only set matrix with 'cur' coordinates on relevant axes */
1227   if (xaxis) {
1228     wmOrtho2(curmasked.xmin - xofs, curmasked.xmax - xofs, -yofs, ar->winy - yofs);
1229   }
1230   else {
1231     wmOrtho2(-xofs, ar->winx - xofs, curmasked.ymin - yofs, curmasked.ymax - yofs);
1232   }
1233 }
1234
1235 /* Restore view matrices after drawing */
1236 void UI_view2d_view_restore(const bContext *C)
1237 {
1238   ARegion *ar = CTX_wm_region(C);
1239   int width = BLI_rcti_size_x(&ar->winrct) + 1;
1240   int height = BLI_rcti_size_y(&ar->winrct) + 1;
1241
1242   wmOrtho2(0.0f, (float)width, 0.0f, (float)height);
1243   GPU_matrix_identity_set();
1244
1245   //  ED_region_pixelspace(CTX_wm_region(C));
1246 }
1247
1248 /* *********************************************************************** */
1249 /* Gridlines */
1250
1251 /* Draw a constant grid in given 2d-region */
1252 void UI_view2d_constant_grid_draw(View2D *v2d, float step)
1253 {
1254   float start_x, start_y;
1255   int count_x, count_y;
1256
1257   start_x = v2d->cur.xmin;
1258   if (start_x < 0.0) {
1259     start_x += -(float)fmod(v2d->cur.xmin, step);
1260   }
1261   else {
1262     start_x += (step - (float)fmod(v2d->cur.xmin, step));
1263   }
1264
1265   if (start_x > v2d->cur.xmax) {
1266     count_x = 0;
1267   }
1268   else {
1269     count_x = (v2d->cur.xmax - start_x) / step + 1;
1270   }
1271
1272   start_y = v2d->cur.ymin;
1273   if (start_y < 0.0) {
1274     start_y += -(float)fmod(v2d->cur.ymin, step);
1275   }
1276   else {
1277     start_y += (step - (float)fabs(fmod(v2d->cur.ymin, step)));
1278   }
1279
1280   if (start_y > v2d->cur.ymax) {
1281     count_y = 0;
1282   }
1283   else {
1284     count_y = (v2d->cur.ymax - start_y) / step + 1;
1285   }
1286
1287   if (count_x > 0 || count_y > 0) {
1288     GPUVertFormat *format = immVertexFormat();
1289     uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
1290     uint color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
1291     float theme_color[3];
1292
1293     UI_GetThemeColorShade3fv(TH_BACK, -10, theme_color);
1294
1295     immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
1296     immBegin(GPU_PRIM_LINES, count_x * 2 + count_y * 2 + 4);
1297
1298     immAttr3fv(color, theme_color);
1299     for (int i = 0; i < count_x; start_x += step, i++) {
1300       immVertex2f(pos, start_x, v2d->cur.ymin);
1301       immVertex2f(pos, start_x, v2d->cur.ymax);
1302     }
1303
1304     for (int i = 0; i < count_y; start_y += step, i++) {
1305       immVertex2f(pos, v2d->cur.xmin, start_y);
1306       immVertex2f(pos, v2d->cur.xmax, start_y);
1307     }
1308
1309     /* X and Y axis */
1310     UI_GetThemeColorShade3fv(TH_BACK, -18, theme_color);
1311
1312     immAttr3fv(color, theme_color);
1313     immVertex2f(pos, 0.0f, v2d->cur.ymin);
1314     immVertex2f(pos, 0.0f, v2d->cur.ymax);
1315     immVertex2f(pos, v2d->cur.xmin, 0.0f);
1316     immVertex2f(pos, v2d->cur.xmax, 0.0f);
1317
1318     immEnd();
1319     immUnbindProgram();
1320   }
1321 }
1322
1323 /* Draw a multi-level grid in given 2d-region */
1324 void UI_view2d_multi_grid_draw(View2D *v2d, int colorid, float step, int level_size, int totlevels)
1325 {
1326   /* Exit if there is nothing to draw */
1327   if (totlevels == 0) {
1328     return;
1329   }
1330
1331   int offset = -10;
1332   float lstep = step;
1333   uchar grid_line_color[3];
1334
1335   /* Make an estimate of at least how many vertices will be needed */
1336   unsigned vertex_count = 4;
1337   vertex_count += 2 * ((int)((v2d->cur.xmax - v2d->cur.xmin) / lstep) + 1);
1338   vertex_count += 2 * ((int)((v2d->cur.ymax - v2d->cur.ymin) / lstep) + 1);
1339
1340   GPUVertFormat *format = immVertexFormat();
1341   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
1342   uint color = GPU_vertformat_attr_add(
1343       format, "color", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
1344
1345   GPU_line_width(1.0f);
1346
1347   immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
1348   immBeginAtMost(GPU_PRIM_LINES, vertex_count);
1349
1350   for (int level = 0; level < totlevels; ++level) {
1351     UI_GetThemeColorShade3ubv(colorid, offset, grid_line_color);
1352
1353     int i = (int)(v2d->cur.xmin / lstep);
1354     if (v2d->cur.xmin > 0.0f) {
1355       i++;
1356     }
1357     float start = i * lstep;
1358
1359     for (; start < v2d->cur.xmax; start += lstep, ++i) {
1360       if (i == 0 || (level < totlevels - 1 && i % level_size == 0)) {
1361         continue;
1362       }
1363
1364       immAttrSkip(color);
1365       immVertex2f(pos, start, v2d->cur.ymin);
1366       immAttr3ubv(color, grid_line_color);
1367       immVertex2f(pos, start, v2d->cur.ymax);
1368     }
1369
1370     i = (int)(v2d->cur.ymin / lstep);
1371     if (v2d->cur.ymin > 0.0f) {
1372       i++;
1373     }
1374     start = i * lstep;
1375
1376     for (; start < v2d->cur.ymax; start += lstep, ++i) {
1377       if (i == 0 || (level < totlevels - 1 && i % level_size == 0)) {
1378         continue;
1379       }
1380
1381       immAttrSkip(color);
1382       immVertex2f(pos, v2d->cur.xmin, start);
1383       immAttr3ubv(color, grid_line_color);
1384       immVertex2f(pos, v2d->cur.xmax, start);
1385     }
1386
1387     lstep *= level_size;
1388     offset -= 6;
1389   }
1390
1391   /* X and Y axis */
1392   UI_GetThemeColorShade3ubv(colorid, -18 + ((totlevels - 1) * -6), grid_line_color);
1393
1394   immAttrSkip(color);
1395   immVertex2f(pos, 0.0f, v2d->cur.ymin);
1396   immAttr3ubv(color, grid_line_color);
1397   immVertex2f(pos, 0.0f, v2d->cur.ymax);
1398
1399   immAttrSkip(color);
1400   immVertex2f(pos, v2d->cur.xmin, 0.0f);
1401   immAttr3ubv(color, grid_line_color);
1402   immVertex2f(pos, v2d->cur.xmax, 0.0f);
1403
1404   immEnd();
1405   immUnbindProgram();
1406 }
1407
1408 /* *********************************************************************** */
1409 /* Scrollers */
1410
1411 /**
1412  * View2DScrollers is typedef'd in UI_view2d.h
1413  *
1414  * \warning The start of this struct must not change, as view2d_ops.c uses this too.
1415  * For now, we don't need to have a separate (internal) header for structs like this...
1416  */
1417 struct View2DScrollers {
1418   /* focus bubbles */
1419   int vert_min, vert_max; /* vertical scrollbar */
1420   int hor_min, hor_max;   /* horizontal scrollbar */
1421
1422   rcti hor, vert;        /* exact size of slider backdrop */
1423   int horfull, vertfull; /* set if sliders are full, we don't draw them */
1424 };
1425
1426 /* Calculate relevant scroller properties */
1427 View2DScrollers *UI_view2d_scrollers_calc(View2D *v2d, const rcti *mask_custom)
1428 {
1429   View2DScrollers *scrollers;
1430   rcti vert, hor;
1431   float fac1, fac2, totsize, scrollsize;
1432   int scroll = view2d_scroll_mapped(v2d->scroll);
1433   int smaller;
1434
1435   /* scrollers is allocated here... */
1436   scrollers = MEM_callocN(sizeof(View2DScrollers), "View2DScrollers");
1437
1438   /* Always update before drawing (for dynamically sized scrollers). */
1439   view2d_masks(v2d, false, mask_custom);
1440
1441   vert = v2d->vert;
1442   hor = v2d->hor;
1443
1444   /* slider rects need to be smaller than region and not interfere with splitter areas */
1445   hor.xmin += UI_HEADER_OFFSET;
1446   hor.xmax -= UI_HEADER_OFFSET;
1447   vert.ymin += UI_HEADER_OFFSET;
1448   vert.ymax -= UI_HEADER_OFFSET;
1449
1450   /* width of sliders */
1451   smaller = (int)(0.1f * U.widget_unit);
1452   if (scroll & V2D_SCROLL_BOTTOM) {
1453     hor.ymin += smaller;
1454   }
1455   else {
1456     hor.ymax -= smaller;
1457   }
1458
1459   if (scroll & V2D_SCROLL_LEFT) {
1460     vert.xmin += smaller;
1461   }
1462   else {
1463     vert.xmax -= smaller;
1464   }
1465
1466   CLAMP(vert.ymin, vert.ymin, vert.ymax - V2D_SCROLL_HANDLE_SIZE_HOTSPOT);
1467   CLAMP(hor.xmin, hor.xmin, hor.xmax - V2D_SCROLL_HANDLE_SIZE_HOTSPOT);
1468
1469   /* store in scrollers, used for drawing */
1470   scrollers->vert = vert;
1471   scrollers->hor = hor;
1472
1473   /* scroller 'buttons':
1474    * - These should always remain within the visible region of the scrollbar
1475    * - They represent the region of 'tot' that is visible in 'cur'
1476    */
1477
1478   /* horizontal scrollers */
1479   if (scroll & V2D_SCROLL_HORIZONTAL) {
1480     /* scroller 'button' extents */
1481     totsize = BLI_rctf_size_x(&v2d->tot);
1482     scrollsize = (float)BLI_rcti_size_x(&hor);
1483     if (totsize == 0.0f) {
1484       totsize = 1.0f; /* avoid divide by zero */
1485     }
1486
1487     fac1 = (v2d->cur.xmin - v2d->tot.xmin) / totsize;
1488     if (fac1 <= 0.0f) {
1489       scrollers->hor_min = hor.xmin;
1490     }
1491     else {
1492       scrollers->hor_min = (int)(hor.xmin + (fac1 * scrollsize));
1493     }
1494
1495     fac2 = (v2d->cur.xmax - v2d->tot.xmin) / totsize;
1496     if (fac2 >= 1.0f) {
1497       scrollers->hor_max = hor.xmax;
1498     }
1499     else {
1500       scrollers->hor_max = (int)(hor.xmin + (fac2 * scrollsize));
1501     }
1502
1503     /* prevent inverted sliders */
1504     if (scrollers->hor_min > scrollers->hor_max) {
1505       scrollers->hor_min = scrollers->hor_max;
1506     }
1507     /* prevent sliders from being too small to grab */
1508     if ((scrollers->hor_max - scrollers->hor_min) < V2D_SCROLL_THUMB_SIZE_MIN) {
1509       scrollers->hor_max = scrollers->hor_min + V2D_SCROLL_THUMB_SIZE_MIN;
1510
1511       CLAMP(scrollers->hor_max, hor.xmin + V2D_SCROLL_THUMB_SIZE_MIN, hor.xmax);
1512       CLAMP(scrollers->hor_min, hor.xmin, hor.xmax - V2D_SCROLL_THUMB_SIZE_MIN);
1513     }
1514   }
1515
1516   /* vertical scrollers */
1517   if (scroll & V2D_SCROLL_VERTICAL) {
1518     /* scroller 'button' extents */
1519     totsize = BLI_rctf_size_y(&v2d->tot);
1520     scrollsize = (float)BLI_rcti_size_y(&vert);
1521     if (totsize == 0.0f) {
1522       totsize = 1.0f; /* avoid divide by zero */
1523     }
1524
1525     fac1 = (v2d->cur.ymin - v2d->tot.ymin) / totsize;
1526     if (fac1 <= 0.0f) {
1527       scrollers->vert_min = vert.ymin;
1528     }
1529     else {
1530       scrollers->vert_min = (int)(vert.ymin + (fac1 * scrollsize));
1531     }
1532
1533     fac2 = (v2d->cur.ymax - v2d->tot.ymin) / totsize;
1534     if (fac2 >= 1.0f) {
1535       scrollers->vert_max = vert.ymax;
1536     }
1537     else {
1538       scrollers->vert_max = (int)(vert.ymin + (fac2 * scrollsize));
1539     }
1540
1541     /* prevent inverted sliders */
1542     if (scrollers->vert_min > scrollers->vert_max) {
1543       scrollers->vert_min = scrollers->vert_max;
1544     }
1545     /* prevent sliders from being too small to grab */
1546     if ((scrollers->vert_max - scrollers->vert_min) < V2D_SCROLL_THUMB_SIZE_MIN) {
1547       scrollers->vert_max = scrollers->vert_min + V2D_SCROLL_THUMB_SIZE_MIN;
1548
1549       CLAMP(scrollers->vert_max, vert.ymin + V2D_SCROLL_THUMB_SIZE_MIN, vert.ymax);
1550       CLAMP(scrollers->vert_min, vert.ymin, vert.ymax - V2D_SCROLL_THUMB_SIZE_MIN);
1551     }
1552   }
1553
1554   return scrollers;
1555 }
1556
1557 /* Draw scrollbars in the given 2d-region */
1558 void UI_view2d_scrollers_draw(View2D *v2d, View2DScrollers *vs)
1559 {
1560   bTheme *btheme = UI_GetTheme();
1561   rcti vert, hor;
1562   const int scroll = view2d_scroll_mapped(v2d->scroll);
1563   const char emboss_alpha = btheme->tui.widget_emboss[3];
1564   uchar scrollers_back_color[4];
1565
1566   /* Color for scrollbar backs */
1567   UI_GetThemeColor4ubv(TH_BACK, scrollers_back_color);
1568
1569   /* make copies of rects for less typing */
1570   vert = vs->vert;
1571   hor = vs->hor;
1572
1573   /* horizontal scrollbar */
1574   if (scroll & V2D_SCROLL_HORIZONTAL) {
1575     uiWidgetColors wcol = btheme->tui.wcol_scroll;
1576     const float alpha_fac = v2d->alpha_hor / 255.0f;
1577     rcti slider;
1578     int state;
1579
1580     slider.xmin = vs->hor_min;
1581     slider.xmax = vs->hor_max;
1582     slider.ymin = hor.ymin;
1583     slider.ymax = hor.ymax;
1584
1585     state = (v2d->scroll_ui & V2D_SCROLL_H_ACTIVE) ? UI_SCROLL_PRESSED : 0;
1586
1587     wcol.inner[3] *= alpha_fac;
1588     wcol.item[3] *= alpha_fac;
1589     wcol.outline[3] *= alpha_fac;
1590     btheme->tui.widget_emboss[3] *= alpha_fac; /* will be reset later */
1591
1592     /* show zoom handles if:
1593      * - zooming on x-axis is allowed (no scroll otherwise)
1594      * - slider bubble is large enough (no overdraw confusion)
1595      * - scale is shown on the scroller
1596      *   (workaround to make sure that button windows don't show these,
1597      *   and only the time-grids with their zoomability can do so)
1598      */
1599     if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0 && (v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) &&
1600         (BLI_rcti_size_x(&slider) > V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) {
1601       state |= UI_SCROLL_ARROWS;
1602     }
1603
1604     UI_draw_widget_scroll(&wcol, &hor, &slider, state);
1605   }
1606
1607   /* vertical scrollbar */
1608   if (scroll & V2D_SCROLL_VERTICAL) {
1609     uiWidgetColors wcol = btheme->tui.wcol_scroll;
1610     rcti slider;
1611     const float alpha_fac = v2d->alpha_vert / 255.0f;
1612     int state;
1613
1614     slider.xmin = vert.xmin;
1615     slider.xmax = vert.xmax;
1616     slider.ymin = vs->vert_min;
1617     slider.ymax = vs->vert_max;
1618
1619     state = (v2d->scroll_ui & V2D_SCROLL_V_ACTIVE) ? UI_SCROLL_PRESSED : 0;
1620
1621     wcol.inner[3] *= alpha_fac;
1622     wcol.item[3] *= alpha_fac;
1623     wcol.outline[3] *= alpha_fac;
1624     btheme->tui.widget_emboss[3] *= alpha_fac; /* will be reset later */
1625
1626     /* show zoom handles if:
1627      * - zooming on y-axis is allowed (no scroll otherwise)
1628      * - slider bubble is large enough (no overdraw confusion)
1629      * - scale is shown on the scroller
1630      *   (workaround to make sure that button windows don't show these,
1631      *   and only the time-grids with their zoomability can do so)
1632      */
1633     if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0 && (v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) &&
1634         (BLI_rcti_size_y(&slider) > V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) {
1635       state |= UI_SCROLL_ARROWS;
1636     }
1637
1638     UI_draw_widget_scroll(&wcol, &vert, &slider, state);
1639   }
1640
1641   /* Was changed above, so reset. */
1642   btheme->tui.widget_emboss[3] = emboss_alpha;
1643 }
1644
1645 /* free temporary memory used for drawing scrollers */
1646 void UI_view2d_scrollers_free(View2DScrollers *scrollers)
1647 {
1648   MEM_freeN(scrollers);
1649 }
1650
1651 /* *********************************************************************** */
1652 /* List View Utilities */
1653
1654 /**
1655  * Get the 'cell' (row, column) that the given 2D-view coordinates
1656  * (i.e. in 'tot' rect space) lie in.
1657  *
1658  * \param columnwidth, rowheight: size of each 'cell'
1659  * \param startx, starty: coordinates (in 'tot' rect space) that the list starts from.
1660  * This should be (0,0) for most views. However, for those where the starting row was offsetted
1661  * (like for Animation Editor channel lists, to make the first entry more visible), these will be
1662  * the min-coordinates of the first item.
1663  * \param viewx, viewy: 2D-coordinates (in 2D-view / 'tot' rect space) to get the cell for
1664  * \param r_column, r_row: the 'coordinates' of the relevant 'cell'
1665  */
1666 void UI_view2d_listview_view_to_cell(float columnwidth,
1667                                      float rowheight,
1668                                      float startx,
1669                                      float starty,
1670                                      float viewx,
1671                                      float viewy,
1672                                      int *r_column,
1673                                      int *r_row)
1674 {
1675   if (r_column) {
1676     if (columnwidth > 0) {
1677       /* Columns go from left to right (x increases). */
1678       *r_column = floorf((viewx - startx) / columnwidth);
1679     }
1680     else {
1681       *r_column = 0;
1682     }
1683   }
1684
1685   if (r_row) {
1686     if (rowheight > 0) {
1687       /* Rows got from top to bottom (y decreases). */
1688       *r_row = floorf((starty - viewy) / rowheight);
1689     }
1690     else {
1691       *r_row = 0;
1692     }
1693   }
1694 }
1695
1696 /* *********************************************************************** */
1697 /* Coordinate Conversions */
1698
1699 float UI_view2d_region_to_view_x(const struct View2D *v2d, float x)
1700 {
1701   return (v2d->cur.xmin +
1702           (BLI_rctf_size_x(&v2d->cur) * (x - v2d->mask.xmin) / BLI_rcti_size_x(&v2d->mask)));
1703 }
1704 float UI_view2d_region_to_view_y(const struct View2D *v2d, float y)
1705 {
1706   return (v2d->cur.ymin +
1707           (BLI_rctf_size_y(&v2d->cur) * (y - v2d->mask.ymin) / BLI_rcti_size_y(&v2d->mask)));
1708 }
1709
1710 /**
1711  * Convert from screen/region space to 2d-View space
1712  *
1713  * \param x, y: coordinates to convert
1714  * \param r_view_x, r_view_y: resultant coordinates
1715  */
1716 void UI_view2d_region_to_view(
1717     const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y)
1718 {
1719   *r_view_x = UI_view2d_region_to_view_x(v2d, x);
1720   *r_view_y = UI_view2d_region_to_view_y(v2d, y);
1721 }
1722
1723 void UI_view2d_region_to_view_rctf(const View2D *v2d, const rctf *rect_src, rctf *rect_dst)
1724 {
1725   const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)};
1726   const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)};
1727
1728   rect_dst->xmin = (v2d->cur.xmin +
1729                     (cur_size[0] * (rect_src->xmin - v2d->mask.xmin) / mask_size[0]));
1730   rect_dst->xmax = (v2d->cur.xmin +
1731                     (cur_size[0] * (rect_src->xmax - v2d->mask.xmin) / mask_size[0]));
1732   rect_dst->ymin = (v2d->cur.ymin +
1733                     (cur_size[1] * (rect_src->ymin - v2d->mask.ymin) / mask_size[1]));
1734   rect_dst->ymax = (v2d->cur.ymin +
1735                     (cur_size[1] * (rect_src->ymax - v2d->mask.ymin) / mask_size[1]));
1736 }
1737
1738 float UI_view2d_view_to_region_x(const View2D *v2d, float x)
1739 {
1740   return (v2d->mask.xmin +
1741           (((x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur)) * BLI_rcti_size_x(&v2d->mask)));
1742 }
1743 float UI_view2d_view_to_region_y(const View2D *v2d, float y)
1744 {
1745   return (v2d->mask.ymin +
1746           (((y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur)) * BLI_rcti_size_y(&v2d->mask)));
1747 }
1748
1749 /**
1750  * Convert from 2d-View space to screen/region space
1751  * \note Coordinates are clamped to lie within bounds of region
1752  *
1753  * \param x, y: Coordinates to convert.
1754  * \param r_region_x, r_region_y: Resultant coordinates.
1755  */
1756 bool UI_view2d_view_to_region_clip(
1757     const View2D *v2d, float x, float y, int *r_region_x, int *r_region_y)
1758 {
1759   /* express given coordinates as proportional values */
1760   x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
1761   y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
1762
1763   /* check if values are within bounds */
1764   if ((x >= 0.0f) && (x <= 1.0f) && (y >= 0.0f) && (y <= 1.0f)) {
1765     *r_region_x = (int)(v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask)));
1766     *r_region_y = (int)(v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask)));
1767
1768     return true;
1769   }
1770   else {
1771     /* set initial value in case coordinate lies outside of bounds */
1772     *r_region_x = *r_region_y = V2D_IS_CLIPPED;
1773
1774     return false;
1775   }
1776 }
1777
1778 /**
1779  * Convert from 2d-view space to screen/region space
1780  *
1781  * \note Coordinates are NOT clamped to lie within bounds of region.
1782  *
1783  * \param x, y: Coordinates to convert.
1784  * \param r_region_x, r_region_y: Resultant coordinates.
1785  */
1786 void UI_view2d_view_to_region(View2D *v2d, float x, float y, int *r_region_x, int *r_region_y)
1787 {
1788   /* step 1: express given coordinates as proportional values */
1789   x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
1790   y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
1791
1792   /* step 2: convert proportional distances to screen coordinates  */
1793   x = v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask));
1794   y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask));
1795
1796   /* although we don't clamp to lie within region bounds, we must avoid exceeding size of ints */
1797   *r_region_x = clamp_float_to_int(x);
1798   *r_region_y = clamp_float_to_int(y);
1799 }
1800
1801 void UI_view2d_view_to_region_fl(
1802     View2D *v2d, float x, float y, float *r_region_x, float *r_region_y)
1803 {
1804   /* express given coordinates as proportional values */
1805   x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
1806   y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur);
1807
1808   /* convert proportional distances to screen coordinates */
1809   *r_region_x = v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask));
1810   *r_region_y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask));
1811 }
1812
1813 void UI_view2d_view_to_region_rcti(View2D *v2d, const rctf *rect_src, rcti *rect_dst)
1814 {
1815   const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)};
1816   const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)};
1817   rctf rect_tmp;
1818
1819   /* step 1: express given coordinates as proportional values */
1820   rect_tmp.xmin = (rect_src->xmin - v2d->cur.xmin) / cur_size[0];
1821   rect_tmp.xmax = (rect_src->xmax - v2d->cur.xmin) / cur_size[0];
1822   rect_tmp.ymin = (rect_src->ymin - v2d->cur.ymin) / cur_size[1];
1823   rect_tmp.ymax = (rect_src->ymax - v2d->cur.ymin) / cur_size[1];
1824
1825   /* step 2: convert proportional distances to screen coordinates  */
1826   rect_tmp.xmin = v2d->mask.xmin + (rect_tmp.xmin * mask_size[0]);
1827   rect_tmp.xmax = v2d->mask.xmin + (rect_tmp.xmax * mask_size[0]);
1828   rect_tmp.ymin = v2d->mask.ymin + (rect_tmp.ymin * mask_size[1]);
1829   rect_tmp.ymax = v2d->mask.ymin + (rect_tmp.ymax * mask_size[1]);
1830
1831   clamp_rctf_to_rcti(rect_dst, &rect_tmp);
1832 }
1833
1834 void UI_view2d_view_to_region_m4(View2D *v2d, float matrix[4][4])
1835 {
1836   rctf mask;
1837   unit_m4(matrix);
1838   BLI_rctf_rcti_copy(&mask, &v2d->mask);
1839   BLI_rctf_transform_calc_m4_pivot_min(&v2d->cur, &mask, matrix);
1840 }
1841
1842 bool UI_view2d_view_to_region_rcti_clip(View2D *v2d, const rctf *rect_src, rcti *rect_dst)
1843 {
1844   const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)};
1845   const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)};
1846   rctf rect_tmp;
1847
1848   BLI_assert(rect_src->xmin <= rect_src->xmax && rect_src->ymin <= rect_src->ymax);
1849
1850   /* step 1: express given coordinates as proportional values */
1851   rect_tmp.xmin = (rect_src->xmin - v2d->cur.xmin) / cur_size[0];
1852   rect_tmp.xmax = (rect_src->xmax - v2d->cur.xmin) / cur_size[0];
1853   rect_tmp.ymin = (rect_src->ymin - v2d->cur.ymin) / cur_size[1];
1854   rect_tmp.ymax = (rect_src->ymax - v2d->cur.ymin) / cur_size[1];
1855
1856   if (((rect_tmp.xmax < 0.0f) || (rect_tmp.xmin > 1.0f) || (rect_tmp.ymax < 0.0f) ||
1857        (rect_tmp.ymin > 1.0f)) == 0) {
1858     /* step 2: convert proportional distances to screen coordinates  */
1859     rect_tmp.xmin = v2d->mask.xmin + (rect_tmp.xmin * mask_size[0]);
1860     rect_tmp.xmax = v2d->mask.ymin + (rect_tmp.xmax * mask_size[0]);
1861     rect_tmp.ymin = v2d->mask.ymin + (rect_tmp.ymin * mask_size[1]);
1862     rect_tmp.ymax = v2d->mask.ymin + (rect_tmp.ymax * mask_size[1]);
1863
1864     clamp_rctf_to_rcti(rect_dst, &rect_tmp);
1865
1866     return true;
1867   }
1868   else {
1869     rect_dst->xmin = rect_dst->xmax = rect_dst->ymin = rect_dst->ymax = V2D_IS_CLIPPED;
1870
1871     return false;
1872   }
1873 }
1874
1875 /* *********************************************************************** */
1876 /* Utilities */
1877
1878 /* View2D data by default resides in region, so get from region stored in context */
1879 View2D *UI_view2d_fromcontext(const bContext *C)
1880 {
1881   ScrArea *area = CTX_wm_area(C);
1882   ARegion *region = CTX_wm_region(C);
1883
1884   if (area == NULL) {
1885     return NULL;
1886   }
1887   if (region == NULL) {
1888     return NULL;
1889   }
1890   return &(region->v2d);
1891 }
1892
1893 /* same as above, but it returns regionwindow. Utility for pulldowns or buttons */
1894 View2D *UI_view2d_fromcontext_rwin(const bContext *C)
1895 {
1896   ScrArea *sa = CTX_wm_area(C);
1897   ARegion *region = CTX_wm_region(C);
1898
1899   if (sa == NULL) {
1900     return NULL;
1901   }
1902   if (region == NULL) {
1903     return NULL;
1904   }
1905   if (region->regiontype != RGN_TYPE_WINDOW) {
1906     ARegion *ar = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW);
1907     return ar ? &(ar->v2d) : NULL;
1908   }
1909   return &(region->v2d);
1910 }
1911
1912 /**
1913  * Calculate the scale per-axis of the drawing-area
1914  *
1915  * Is used to inverse correct drawing of icons, etc. that need to follow view
1916  * but not be affected by scale
1917  *
1918  * \param r_x, r_y: scale on each axis
1919  */
1920 void UI_view2d_scale_get(View2D *v2d, float *r_x, float *r_y)
1921 {
1922   if (r_x) {
1923     *r_x = UI_view2d_scale_get_x(v2d);
1924   }
1925   if (r_y) {
1926     *r_y = UI_view2d_scale_get_y(v2d);
1927   }
1928 }
1929 float UI_view2d_scale_get_x(const View2D *v2d)
1930 {
1931   return BLI_rcti_size_x(&v2d->mask) / BLI_rctf_size_x(&v2d->cur);
1932 }
1933 float UI_view2d_scale_get_y(const View2D *v2d)
1934 {
1935   return BLI_rcti_size_y(&v2d->mask) / BLI_rctf_size_y(&v2d->cur);
1936 }
1937 /**
1938  * Same as ``UI_view2d_scale_get() - 1.0f / x, y``
1939  */
1940 void UI_view2d_scale_get_inverse(View2D *v2d, float *r_x, float *r_y)
1941 {
1942   if (r_x) {
1943     *r_x = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask);
1944   }
1945   if (r_y) {
1946     *r_y = BLI_rctf_size_y(&v2d->cur) / BLI_rcti_size_y(&v2d->mask);
1947   }
1948 }
1949
1950 /**
1951  * Simple functions for consistent center offset access.
1952  * Used by node editor to shift view center for each individual node tree.
1953  */
1954 void UI_view2d_center_get(struct View2D *v2d, float *r_x, float *r_y)
1955 {
1956   /* get center */
1957   if (r_x) {
1958     *r_x = BLI_rctf_cent_x(&v2d->cur);
1959   }
1960   if (r_y) {
1961     *r_y = BLI_rctf_cent_y(&v2d->cur);
1962   }
1963 }
1964 void UI_view2d_center_set(struct View2D *v2d, float x, float y)
1965 {
1966   BLI_rctf_recenter(&v2d->cur, x, y);
1967
1968   /* make sure that 'cur' rect is in a valid state as a result of these changes */
1969   UI_view2d_curRect_validate(v2d);
1970 }
1971
1972 /**
1973  * Simple pan function
1974  *  (0.0, 0.0) bottom left
1975  *  (0.5, 0.5) center
1976  *  (1.0, 1.0) top right.
1977  */
1978 void UI_view2d_offset(struct View2D *v2d, float xfac, float yfac)
1979 {
1980   if (xfac != -1.0f) {
1981     const float xsize = BLI_rctf_size_x(&v2d->cur);
1982     const float xmin = v2d->tot.xmin;
1983     const float xmax = v2d->tot.xmax - xsize;
1984
1985     v2d->cur.xmin = (xmin * (1.0f - xfac)) + (xmax * xfac);
1986     v2d->cur.xmax = v2d->cur.xmin + xsize;
1987   }
1988
1989   if (yfac != -1.0f) {
1990     const float ysize = BLI_rctf_size_y(&v2d->cur);
1991     const float ymin = v2d->tot.ymin;
1992     const float ymax = v2d->tot.ymax - ysize;
1993
1994     v2d->cur.ymin = (ymin * (1.0f - yfac)) + (ymax * yfac);
1995     v2d->cur.ymax = v2d->cur.ymin + ysize;
1996   }
1997
1998   UI_view2d_curRect_validate(v2d);
1999 }
2000
2001 /**
2002  * Check if mouse is within scrollers
2003  *
2004  * \param x, y: Mouse coordinates in screen (not region) space.
2005  * \param r_scroll: Mapped view2d scroll flag.
2006  *
2007  * \return appropriate code for match.
2008  * - 'h' = in horizontal scroller.
2009  * - 'v' = in vertical scroller.
2010  * - 0 = not in scroller.
2011  */
2012 char UI_view2d_mouse_in_scrollers_ex(
2013     const ARegion *ar, const View2D *v2d, int x, int y, int *r_scroll)
2014 {
2015   const int scroll = view2d_scroll_mapped(v2d->scroll);
2016   *r_scroll = scroll;
2017
2018   if (scroll) {
2019     /* Move to region-coordinates. */
2020     const int co[2] = {
2021         x - ar->winrct.xmin,
2022         y - ar->winrct.ymin,
2023     };
2024     if (scroll & V2D_SCROLL_HORIZONTAL) {
2025       if (IN_2D_HORIZ_SCROLL(v2d, co)) {
2026         return 'h';
2027       }
2028     }
2029     if (scroll & V2D_SCROLL_VERTICAL) {
2030       if (IN_2D_VERT_SCROLL(v2d, co)) {
2031         return 'v';
2032       }
2033     }
2034   }
2035
2036   return 0;
2037 }
2038
2039 char UI_view2d_rect_in_scrollers_ex(const ARegion *ar,
2040                                     const View2D *v2d,
2041                                     const rcti *rect,
2042                                     int *r_scroll)
2043 {
2044   const int scroll = view2d_scroll_mapped(v2d->scroll);
2045   *r_scroll = scroll;
2046
2047   if (scroll) {
2048     /* Move to region-coordinates. */
2049     rcti rect_region = *rect;
2050     BLI_rcti_translate(&rect_region, -ar->winrct.xmin, ar->winrct.ymin);
2051     if (scroll & V2D_SCROLL_HORIZONTAL) {
2052       if (IN_2D_HORIZ_SCROLL_RECT(v2d, &rect_region)) {
2053         return 'h';
2054       }
2055     }
2056     if (scroll & V2D_SCROLL_VERTICAL) {
2057       if (IN_2D_VERT_SCROLL_RECT(v2d, &rect_region)) {
2058         return 'v';
2059       }
2060     }
2061   }
2062
2063   return 0;
2064 }
2065
2066 char UI_view2d_mouse_in_scrollers(const ARegion *ar, const View2D *v2d, int x, int y)
2067 {
2068   int scroll_dummy = 0;
2069   return UI_view2d_mouse_in_scrollers_ex(ar, v2d, x, y, &scroll_dummy);
2070 }
2071
2072 char UI_view2d_rect_in_scrollers(const ARegion *ar, const View2D *v2d, const rcti *rect)
2073 {
2074   int scroll_dummy = 0;
2075   return UI_view2d_rect_in_scrollers_ex(ar, v2d, rect, &scroll_dummy);
2076 }
2077
2078 /* ******************* view2d text drawing cache ******************** */
2079
2080 typedef struct View2DString {
2081   struct View2DString *next;
2082   union {
2083     uchar ub[4];
2084     int pack;
2085   } col;
2086   rcti rect;
2087   int mval[2];
2088
2089   /* str is allocated past the end */
2090   char str[0];
2091 } View2DString;
2092
2093 /* assumes caches are used correctly, so for time being no local storage in v2d */
2094 static MemArena *g_v2d_strings_arena = NULL;
2095 static View2DString *g_v2d_strings = NULL;
2096
2097 void UI_view2d_text_cache_add(
2098     View2D *v2d, float x, float y, const char *str, size_t str_len, const char col[4])
2099 {
2100   int mval[2];
2101
2102   BLI_assert(str_len == strlen(str));
2103
2104   if (UI_view2d_view_to_region_clip(v2d, x, y, &mval[0], &mval[1])) {
2105     int alloc_len = str_len + 1;
2106     View2DString *v2s;
2107
2108     if (g_v2d_strings_arena == NULL) {
2109       g_v2d_strings_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 14), __func__);
2110     }
2111
2112     v2s = BLI_memarena_alloc(g_v2d_strings_arena, sizeof(View2DString) + alloc_len);
2113
2114     BLI_LINKS_PREPEND(g_v2d_strings, v2s);
2115
2116     v2s->col.pack = *((const int *)col);
2117
2118     memset(&v2s->rect, 0, sizeof(v2s->rect));
2119
2120     v2s->mval[0] = mval[0];
2121     v2s->mval[1] = mval[1];
2122
2123     memcpy(v2s->str, str, alloc_len);
2124   }
2125 }
2126
2127 /* no clip (yet) */
2128 void UI_view2d_text_cache_add_rectf(
2129     View2D *v2d, const rctf *rect_view, const char *str, size_t str_len, const char col[4])
2130 {
2131   rcti rect;
2132
2133   BLI_assert(str_len == strlen(str));
2134
2135   if (UI_view2d_view_to_region_rcti_clip(v2d, rect_view, &rect)) {
2136     int alloc_len = str_len + 1;
2137     View2DString *v2s;
2138
2139     if (g_v2d_strings_arena == NULL) {
2140       g_v2d_strings_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 14), __func__);
2141     }
2142
2143     v2s = BLI_memarena_alloc(g_v2d_strings_arena, sizeof(View2DString) + alloc_len);
2144
2145     BLI_LINKS_PREPEND(g_v2d_strings, v2s);
2146
2147     v2s->col.pack = *((const int *)col);
2148
2149     v2s->rect = rect;
2150
2151     v2s->mval[0] = v2s->rect.xmin;
2152     v2s->mval[1] = v2s->rect.ymin;
2153
2154     memcpy(v2s->str, str, alloc_len);
2155   }
2156 }
2157
2158 void UI_view2d_text_cache_draw(ARegion *ar)
2159 {
2160   View2DString *v2s;
2161   int col_pack_prev = 0;
2162
2163   /* investigate using BLF_ascender() */
2164   const int font_id = BLF_default();
2165
2166   BLF_set_default();
2167   const float default_height = g_v2d_strings ? BLF_height(font_id, "28", 3) : 0.0f;
2168
2169   wmOrtho2_region_pixelspace(ar);
2170
2171   for (v2s = g_v2d_strings; v2s; v2s = v2s->next) {
2172     int xofs = 0, yofs;
2173
2174     yofs = ceil(0.5f * (BLI_rcti_size_y(&v2s->rect) - default_height));
2175     if (yofs < 1) {
2176       yofs = 1;
2177     }
2178
2179     if (col_pack_prev != v2s->col.pack) {
2180       BLF_color3ubv(font_id, v2s->col.ub);
2181       col_pack_prev = v2s->col.pack;
2182     }
2183
2184     if (v2s->rect.xmin >= v2s->rect.xmax) {
2185       BLF_draw_default((float)(v2s->mval[0] + xofs),
2186                        (float)(v2s->mval[1] + yofs),
2187                        0.0,
2188                        v2s->str,
2189                        BLF_DRAW_STR_DUMMY_MAX);
2190     }
2191     else {
2192       BLF_enable(font_id, BLF_CLIPPING);
2193       BLF_clipping(
2194           font_id, v2s->rect.xmin - 4, v2s->rect.ymin - 4, v2s->rect.xmax + 4, v2s->rect.ymax + 4);
2195       BLF_draw_default(
2196           v2s->rect.xmin + xofs, v2s->rect.ymin + yofs, 0.0f, v2s->str, BLF_DRAW_STR_DUMMY_MAX);
2197       BLF_disable(font_id, BLF_CLIPPING);
2198     }
2199   }
2200   g_v2d_strings = NULL;
2201
2202   if (g_v2d_strings_arena) {
2203     BLI_memarena_free(g_v2d_strings_arena);
2204     g_v2d_strings_arena = NULL;
2205   }
2206 }
2207
2208 /* ******************************************************** */