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