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