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