95e68ee89f02026a514ae2ca6093bc922bdbfc6b
[blender.git] / source / blender / editors / interface / view2d.c
1 /**
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version. 
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19  *
20  * The Original Code is Copyright (C) 2008 Blender Foundation.
21  * All rights reserved.
22  * 
23  * Contributor(s): Blender Foundation, Joshua Leung
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 #include <string.h>
29 #include <math.h>
30
31 #include "MEM_guardedalloc.h"
32
33 #include "DNA_scene_types.h"
34 #include "DNA_screen_types.h"
35 #include "DNA_space_types.h"
36 #include "DNA_view2d_types.h"
37
38 #include "BLI_blenlib.h"
39
40 #include "BKE_global.h"
41 #include "BKE_utildefines.h"
42
43 #include "WM_api.h"
44
45 #include "BIF_gl.h"
46 #include "BIF_glutil.h"
47
48 #include "ED_screen.h"
49
50 #include "UI_resources.h"
51 #include "UI_text.h"
52 #include "UI_view2d.h"
53
54 #include "UI_interface.h"
55 #include "interface.h"
56
57 /* *********************************************************************** */
58 /* Refresh and Validation */
59
60 /* Adjust mask size in response to view size changes 
61  *      - This should only be called in region init() callbacks, which are
62  *        called when the region is resized or area changes...
63  */
64 // XXX pre2.5 -> this used to be called  calc_scrollrcts()
65 void UI_view2d_size_update(View2D *v2d, int winx, int winy)
66 {
67         v2d->winx= winx;
68         v2d->winy= winy;
69         
70         /* mask - view frame */
71         v2d->mask.xmin= v2d->mask.ymin= 0;
72         v2d->mask.xmax= winx - 1;       /* -1 yes! masks are pixels */
73         v2d->mask.ymax= winy - 1;
74         
75         /* scrollbars shrink mask area, but should be based off regionsize 
76          *      - they can only be on one edge of the region they define
77          */
78         if (v2d->scroll) {
79                 /* vertical scrollbar */
80                 if (v2d->scroll & V2D_SCROLL_LEFT) {
81                         /* on left-hand edge of region */
82                         v2d->vert= v2d->mask;
83                         v2d->vert.xmax= V2D_SCROLL_WIDTH;
84                         v2d->mask.xmin= v2d->vert.xmax + 1;
85                 }
86                 else if (v2d->scroll & V2D_SCROLL_RIGHT) {
87                         /* on right-hand edge of region */
88                         v2d->vert= v2d->mask;
89                         v2d->vert.xmin= v2d->vert.xmax - V2D_SCROLL_WIDTH;
90                         v2d->mask.xmax= v2d->vert.xmin - 1;
91                 }
92                 
93                 /* horizontal scrollbar */
94                 if (v2d->scroll & (V2D_SCROLL_BOTTOM|V2D_SCROLL_BOTTOM_O)) {
95                         /* on bottom edge of region (V2D_SCROLL_BOTTOM_O is outliner, the ohter is for standard) */
96                         v2d->hor= v2d->mask;
97                         v2d->hor.ymax= V2D_SCROLL_HEIGHT;
98                         v2d->mask.ymin= v2d->hor.ymax + 1;
99                 }
100                 else if (v2d->scroll & V2D_SCROLL_TOP) {
101                         /* on upper edge of region */
102                         v2d->hor= v2d->mask;
103                         v2d->hor.ymin= v2d->hor.ymax - V2D_SCROLL_HEIGHT;
104                         v2d->mask.ymax= v2d->hor.ymin - 1;
105                 }
106         }
107         
108         /* cope with unitialized veriables for simple cases, like header or outliner */
109         if(v2d->tot.xmin==v2d->tot.xmax || v2d->cur.xmin==v2d->cur.xmax) {
110                 if(v2d->keepzoom) {
111                         BLI_init_rctf(&v2d->tot, v2d->mask.xmin, v2d->mask.xmax, v2d->mask.ymin, v2d->mask.ymax);
112                         BLI_init_rctf(&v2d->cur, v2d->mask.xmin, v2d->mask.xmax, v2d->mask.ymin, v2d->mask.ymax);
113                         
114                         v2d->min[0]= v2d->max[0]= winx;
115                         v2d->min[1]= v2d->max[1]= winy;
116                         v2d->minzoom= 1.0f;
117                         v2d->maxzoom= 1.0f;
118                 }
119         }
120         
121         /* make sure that 'cur' rect is in a valid state as a result of these changes */
122         UI_view2d_curRect_validate(v2d);
123 }
124
125 /* Ensure View2D rects remain in a viable configuration 
126  *      - cur is not allowed to be: larger than max, smaller than min, or outside of tot
127  */
128 // XXX pre2.5 -> this used to be called  test_view2d()
129 void UI_view2d_curRect_validate(View2D *v2d)
130 {
131         float totwidth, totheight, curwidth, curheight, width, height;
132         float winx, winy;
133         rctf *cur, *tot;
134         
135         /* use mask as size of region that View2D resides in, as it takes into account scrollbars already  */
136         winx= (float)(v2d->mask.xmax - v2d->mask.xmin + 1);
137         winy= (float)(v2d->mask.ymax - v2d->mask.ymin + 1);
138         
139         /* get pointers to rcts for less typing */
140         cur= &v2d->cur;
141         tot= &v2d->tot;
142         
143         /* we must satisfy the following constraints (in decreasing order of importance):
144          *      - cur must not fall outside of tot
145          *      - axis locks (zoom and offset) must be maintained
146          *      - zoom must not be excessive (check either sizes or zoom values)
147          *      - aspect ratio should be respected (NOTE: this is quite closely realted to zoom too)
148          */
149         
150         /* Step 1: if keepzoom, adjust the sizes of the rects only
151          *      - firstly, we calculate the sizes of the rects
152          *      - curwidth and curheight are saved as reference... modify width and height values here
153          */
154         totwidth= tot->xmax - tot->xmin;
155         totheight= tot->ymax - tot->ymin;
156         curwidth= width= cur->xmax - cur->xmin;
157         curheight= height= cur->ymax - cur->ymin;
158         
159         /* if zoom is locked, size on the appropriate axis is reset to mask size */
160         if (v2d->keepzoom & V2D_LOCKZOOM_X)
161                 width= winx;
162         if (v2d->keepzoom & V2D_LOCKZOOM_Y)
163                 height= winy;
164                 
165         /* keepzoom (V2D_KEEPZOOM set), indicates that zoom level on each axis must not exceed limits 
166          * NOTE: in general, it is not expected that the lock-zoom will be used in conjunction with this
167          */
168         if (v2d->keepzoom & V2D_KEEPZOOM) {
169                 float zoom, fac;
170                 
171                 /* check if excessive zoom on x-axis */
172                 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
173                         zoom= winx / width;
174                         if ((zoom < v2d->minzoom) || (zoom > v2d->maxzoom)) {
175                                 fac= (zoom < v2d->minzoom) ? (zoom / v2d->minzoom) : (zoom / v2d->maxzoom);
176                                 width *= fac;
177                         }
178                 }
179                 
180                 /* check if excessive zoom on y-axis */
181                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
182                         zoom= winy / height;
183                         if ((zoom < v2d->minzoom) || (zoom > v2d->maxzoom)) {
184                                 fac= (zoom < v2d->minzoom) ? (zoom / v2d->minzoom) : (zoom / v2d->maxzoom);
185                                 height *= fac;
186                         }
187                 }
188         }
189         else {
190                 /* make sure sizes don't exceed that of the min/max sizes (even though we're not doing zoom clamping) */
191                 CLAMP(width, v2d->min[0], v2d->max[0]);
192                 CLAMP(height, v2d->min[1], v2d->max[1]);
193         }
194         
195         /* check if we should restore aspect ratio (if view size changed) */
196         if (v2d->keepaspect) {
197                 short do_x=0, do_y=0, do_cur, do_win;
198                 float curRatio, winRatio;
199                 
200                 /* when a window edge changes, the aspect ratio can't be used to
201                  * find which is the best new 'cur' rect. thats why it stores 'old' 
202                  */
203                 if (winx != v2d->oldwinx) do_x= 1;
204                 if (winy != v2d->oldwiny) do_y= 1;
205                 
206                 curRatio= height / width;
207                 winRatio= winy / winx;
208                 
209                 /* both sizes change (area/region maximised)  */
210                 if (do_x == do_y) {
211                         if (do_x && do_y) {
212                                 /* here is 1,1 case, so all others must be 0,0 */
213                                 if (ABS(winx - v2d->oldwinx) > ABS(winy - v2d->oldwiny)) do_y= 0;
214                                 else do_x= 0;
215                         }
216                         else if (winRatio > 1.0f) do_x= 0; 
217                         else do_x= 1;
218                 }
219                 do_cur= do_x;
220                 do_win= do_y;
221                 
222                 if (do_cur) {
223                         if ((v2d->keeptot == 2) && (winx != v2d->oldwinx)) {
224                                 /* special exception for Outliner (and later channel-lists):
225                                  *      - The view may be moved left to avoid contents being pushed out of view when view shrinks. 
226                                  *      - The keeptot code will make sure cur->xmin will not be less than tot->xmin (which cannot be allowed)
227                                  *      - width is not adjusted for changed ratios here...
228                                  */
229                                 if (winx < v2d->oldwinx) {
230                                         float temp = v2d->oldwinx - winx;
231                                         
232                                         cur->xmin -= temp;
233                                         cur->xmax -= temp;
234                                         
235                                         /* width does not get modified, as keepaspect here is just set to make 
236                                          * sure visible area adjusts to changing view shape! 
237                                          */
238                                 }
239                         }
240                         else {
241                                 /* portrait window: correct for x */
242                                 width= height / winRatio;
243                         }
244                 }
245                 else {
246                         /* landscape window: correct for y */
247                         height = width * winRatio;
248                 }
249                 
250                 /* store region size for next time */
251                 v2d->oldwinx= winx; 
252                 v2d->oldwiny= winy;
253         }
254         
255         /* Step 2: apply new sizes of cur rect to cur rect */
256         if ((width != curwidth) || (height != curheight)) {
257                 float temp, dh;
258                 
259                 /* resize around 'center' of frame */
260                 if (width != curwidth) {
261                         temp= (cur->xmax + cur->xmin) * 0.5f;
262                         dh= width * 0.5f;
263                         
264                         cur->xmin = temp - dh;
265                         cur->xmax = temp + dh;
266                 }
267                 if (height != curheight) {
268                         temp= (cur->ymax + cur->ymin) * 0.5f;
269                         dh= height * 0.5f;
270                         
271                         cur->ymin = temp - dh;
272                         cur->ymax = temp + dh;
273                 }
274         }
275         
276         /* Step 3: adjust so that it doesn't fall outside of bounds of tot */
277         if (v2d->keeptot) {
278                 float temp, diff;
279                 
280                 /* recalculate extents of cur */
281                 curwidth= cur->xmax - cur->xmin;
282                 curheight= cur->ymax - cur->ymin;
283                 
284                 /* width */
285                 if ( (curwidth > totwidth) && !(v2d->keepzoom & (V2D_KEEPZOOM|V2D_LOCKZOOM_X)) ) {
286                         /* if zoom doesn't have to be maintained, just clamp edges */
287                         if (cur->xmin < tot->xmin) cur->xmin= tot->xmin;
288                         if (cur->xmax > tot->xmax) cur->xmax= tot->xmax;
289                 }
290                 else if (v2d->keeptot == 2) {
291                         /* This is an exception for the outliner (and later channel-lists) 
292                          *      - must clamp within tot rect (absolutely no excuses)
293                          *      --> therefore, cur->xmin must not be less than tot->xmin
294                          */
295                         if (cur->xmin < tot->xmin) {
296                                 /* move cur across so that it sits at minimum of tot */
297                                 temp= tot->xmin - cur->xmin;
298                                 
299                                 cur->xmin += temp;
300                                 cur->xmax += temp;
301                         }
302                         else if (cur->xmax > tot->xmax) {
303                                 /* - only offset by difference of cur-xmax and tot-xmax if that would not move 
304                                  *      cur-xmin to lie past tot-xmin
305                                  * - otherwise, simply shift to tot-xmin???
306                                  */
307                                 temp= cur->xmax - tot->xmax;
308                                 
309                                 if ((cur->xmin - temp) < tot->xmin) {
310                                         /* only offset by difference from cur-min and tot-min */
311                                         temp= cur->xmin - tot->xmin;
312                                         
313                                         cur->xmin -= temp;
314                                         cur->xmax -= temp;
315                                 }
316                                 else {
317                                         cur->xmin -= temp;
318                                         cur->xmax -= temp;
319                                 }
320                         }
321                 }
322                 else {
323                         /* This here occurs when:
324                          *      - width too big, but maintaining zoom (i.e. widths cannot be changed)
325                          *      - width is OK, but need to check if outside of boundaries
326                          * 
327                          * So, resolution is to just shift view by the gap between the extremities.
328                          * We favour moving the 'minimum' across, as that's origin for most things
329                          * (XXX - in the past, max was favoured... if there are bugs, swap!)
330                          */
331                         if ((cur->ymin < tot->ymin) && (cur->ymax > tot->ymax)) {
332                                 /* outside boundaries on both sides, so take middle-point of tot, and place in balanced way */
333                                 temp= (tot->ymax + tot->ymin) * 0.5f;
334                                 diff= curheight * 0.5f;
335                                 
336                                 cur->ymin= temp - diff;
337                                 cur->ymax= temp + diff;
338                         }
339                         else if (cur->xmin > tot->xmin) {
340                                 /* there's still space remaining, so shift left */
341                                 temp= cur->xmin - tot->xmin;
342                                 
343                                 cur->xmin -= temp;
344                                 cur->xmax -= temp;
345                         }
346                         else if (cur->xmax < tot->xmax) {
347                                 /* there's still space remaining, so shift right */
348                                 temp= tot->xmax - cur->xmax;
349                                 
350                                 cur->xmin += temp;
351                                 cur->xmax += temp;
352                         }
353                 }
354                 
355                 /* height */
356                 if ( (curheight > totheight) && !(v2d->keepzoom & (V2D_KEEPZOOM|V2D_LOCKZOOM_Y)) ) {
357                         /* if zoom doesn't have to be maintained, just clamp edges */
358                         if (cur->ymin < tot->ymin) cur->ymin= tot->ymin;
359                         if (cur->ymax > tot->ymax) cur->ymax= tot->ymax;
360                 }
361                 else {
362                         /* This here occurs when:
363                          *      - height too big, but maintaining zoom (i.e. heights cannot be changed)
364                          *      - height is OK, but need to check if outside of boundaries
365                          * 
366                          * So, resolution is to just shift view by the gap between the extremities.
367                          * We favour moving the 'minimum' across, as that's origin for most things
368                          */
369                         if ((cur->ymin < tot->ymin) && (cur->ymax > tot->ymax)) {
370                                 /* outside boundaries on both sides, so take middle-point of tot, and place in balanced way */
371                                 temp= (tot->ymax + tot->ymin) * 0.5f;
372                                 diff= curheight * 0.5f;
373                                 
374                                 cur->ymin= temp - diff;
375                                 cur->ymax= temp + diff;
376                         }
377                         else if (cur->ymin < tot->ymin) {
378                                 /* there's still space remaining, so shift up */
379                                 temp= tot->ymin - cur->ymin;
380                                 
381                                 cur->ymin += temp;
382                                 cur->ymax += temp;
383                         }
384                         else if (cur->ymax > tot->ymax) {
385                                 /* there's still space remaining, so shift down */
386                                 temp= cur->ymax - tot->ymax;
387                                 
388                                 cur->ymin -= temp;
389                                 cur->ymax -= temp;
390                         }
391                 }
392         }
393         
394 }
395
396 /* ------------------ */
397
398 /* Restore 'cur' rect to standard orientation (i.e. optimal maximum view of tot) */
399 void UI_view2d_curRect_reset (View2D *v2d)
400 {
401         float width, height;
402         
403         /* assume width and height of 'cur' rect by default, should be same size as mask */
404         width= (float)(v2d->mask.xmax - v2d->mask.xmin + 1);
405         height= (float)(v2d->mask.ymax - v2d->mask.ymin + 1);
406         
407         /* handle width - posx and negx flags are mutually exclusive, so watch out */
408         if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
409                 /* width is in negative-x half */
410                 v2d->cur.xmin= (float)-width;
411                 v2d->cur.xmax= 0.0f;
412         }
413         else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
414                 /* width is in positive-x half */
415                 v2d->cur.xmin= 0.0f;
416                 v2d->cur.xmax= (float)width;
417         }
418         else {
419                 /* width is centered around x==0 */
420                 const float dx= (float)width / 2.0f;
421                 
422                 v2d->cur.xmin= -dx;
423                 v2d->cur.xmax= dx;
424         }
425         
426         /* handle height - posx and negx flags are mutually exclusive, so watch out */
427         if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
428                 /* height is in negative-y half */
429                 v2d->cur.ymin= (float)-height;
430                 v2d->cur.ymax= 0.0f;
431         }
432         else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
433                 /* height is in positive-y half */
434                 v2d->cur.ymin= 0.0f;
435                 v2d->cur.ymax= (float)height;
436         }
437         else {
438                 /* height is centered around y==0 */
439                 const float dy= (float)height / 2.0f;
440                 
441                 v2d->cur.ymin= -dy;
442                 v2d->cur.ymax= dy;
443         }
444 }
445
446 /* ------------------ */
447
448 /* Change the size of the maximum viewable area (i.e. 'tot' rect) */
449 void UI_view2d_totRect_set (View2D *v2d, int width, int height)
450 {
451         /* don't do anything if either value is 0 */
452         if (ELEM3(0, v2d, width, height))
453                 return;
454         
455         /* handle width - posx and negx flags are mutually exclusive, so watch out */
456         if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) {
457                 /* width is in negative-x half */
458                 v2d->tot.xmin= (float)-width;
459                 v2d->tot.xmax= 0.0f;
460         }
461         else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) {
462                 /* width is in positive-x half */
463                 v2d->tot.xmin= 0.0f;
464                 v2d->tot.xmax= (float)width;
465         }
466         else {
467                 /* width is centered around x==0 */
468                 const float dx= (float)width / 2.0f;
469                 
470                 v2d->tot.xmin= -dx;
471                 v2d->tot.xmax= dx;
472         }
473         
474         /* handle height - posx and negx flags are mutually exclusive, so watch out */
475         if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) {
476                 /* height is in negative-y half */
477                 v2d->tot.ymin= (float)-height;
478                 v2d->tot.ymax= 0.0f;
479         }
480         else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) {
481                 /* height is in positive-y half */
482                 v2d->tot.ymin= 0.0f;
483                 v2d->tot.ymax= (float)height;
484         }
485         else {
486                 /* height is centered around y==0 */
487                 const float dy= (float)height / 2.0f;
488                 
489                 v2d->tot.ymin= -dy;
490                 v2d->tot.ymax= dy;
491         }
492         
493         /* make sure that 'cur' rect is in a valid state as a result of these changes */
494         UI_view2d_curRect_validate(v2d);
495 }
496
497 /* *********************************************************************** */
498 /* View Matrix Setup */
499
500 /* mapping function to ensure 'cur' draws extended over the area where sliders are */
501 static void view2d_map_cur_using_mask(View2D *v2d, rctf *curmasked)
502 {
503         *curmasked= v2d->cur;
504         
505         if ((v2d->scroll)) {
506                 float dx= (v2d->cur.xmax-v2d->cur.xmin)/((float)(v2d->mask.xmax-v2d->mask.xmin+1));
507                 float dy= (v2d->cur.ymax-v2d->cur.ymin)/((float)(v2d->mask.ymax-v2d->mask.ymin+1));
508                 
509                 if (v2d->mask.xmin != 0)
510                         curmasked->xmin -= dx*(float)v2d->mask.xmin;
511                 if (v2d->mask.xmax+1 != v2d->winx)
512                         curmasked->xmax += dx*(float)(v2d->winx - v2d->mask.xmax-1);
513                 
514                 if (v2d->mask.ymin != 0)
515                         curmasked->ymin -= dy*(float)v2d->mask.ymin;
516                 if (v2d->mask.ymax+1 != v2d->winy)
517                         curmasked->ymax += dy*(float)(v2d->winy - v2d->mask.ymax-1);
518                 
519         }
520 }
521
522 /* Set view matrices to use 'cur' rect as viewing frame for View2D drawing 
523 *       - this assumes viewport/scissor been set for the region, taking scrollbars into account
524 */
525
526 void UI_view2d_view_ortho(const bContext *C, View2D *v2d)
527 {
528         rctf curmasked;
529         
530         /* set the matrix - pixel offsets (-0.375) for 1:1 correspondance are not applied, 
531          * as they were causing some unwanted offsets when drawing 
532          */
533         view2d_map_cur_using_mask(v2d, &curmasked);
534         wmOrtho2(C->window, curmasked.xmin, curmasked.xmax, curmasked.ymin, curmasked.ymax);
535         
536         /* XXX is this necessary? */
537         wmLoadIdentity(C->window);
538 }
539
540 /* Set view matrices to only use one axis of 'cur' only
541  *      - this assumes viewport/scissor been set for the region, taking scrollbars into account
542  *
543  *      - xaxis         = if non-zero, only use cur x-axis, otherwise use cur-yaxis (mostly this will be used for x)
544  */
545 void UI_view2d_view_orthoSpecial(const bContext *C, View2D *v2d, short xaxis)
546 {
547         ARegion *ar= C->region;
548         rctf curmasked;
549         
550         /* set the matrix - pixel offsets (-0.375) for 1:1 correspondance are not applied, 
551          * as they were causing some unwanted offsets when drawing 
552          */
553         view2d_map_cur_using_mask(v2d, &curmasked);
554         if (xaxis)
555                 wmOrtho2(C->window, curmasked.xmin, curmasked.xmax, 0, ar->winy);
556         else
557                 wmOrtho2(C->window, 0, ar->winx, curmasked.ymin, curmasked.ymax);
558                 
559         /* XXX is this necessary? */
560         wmLoadIdentity(C->window);
561
562
563
564 /* Restore view matrices after drawing */
565 void UI_view2d_view_restore(const bContext *C)
566 {
567         ED_region_pixelspace(C, C->region);
568 }
569
570 /* allowing horizontal pan */
571 void UI_view2d_header_default(View2D *v2d)
572 {
573         v2d->keepaspect= 1;
574         v2d->keepzoom = (V2D_LOCKZOOM_X|V2D_LOCKZOOM_Y|V2D_KEEPZOOM);
575         v2d->keepofs = V2D_LOCKOFS_Y;
576         v2d->keeptot = 2; // this keeps the view in place when region size changes...
577         v2d->align = V2D_ALIGN_NO_NEG_X;
578         
579 }
580
581 /* *********************************************************************** */
582 /* Gridlines */
583
584 /* minimum pixels per gridstep */
585 #define MINGRIDSTEP     35
586
587 /* View2DGrid is typedef'd in UI_view2d.h */
588 struct View2DGrid {
589         float dx, dy;                   /* stepsize (in pixels) between gridlines */
590         float startx, starty;   /* initial coordinates to start drawing grid from */
591         int powerx, powery;             /* step as power of 10 */
592 };
593
594 /* --------------- */
595
596 /* try to write step as a power of 10 */
597 static void step_to_grid(float *step, int *power, int unit)
598 {
599         const float loga= log10(*step);
600         float rem;
601         
602         *power= (int)(loga);
603         
604         rem= loga - (*power);
605         rem= pow(10.0, rem);
606         
607         if (loga < 0.0) {
608                 if (rem < 0.2) rem= 0.2;
609                 else if(rem < 0.5) rem= 0.5;
610                 else rem= 1.0;
611                 
612                 *step= rem * pow(10.0, (float)(*power));
613                 
614                 /* for frames, we want 1.0 frame intervals only */
615                 if (unit == V2D_UNIT_FRAMES) {
616                         rem = 1.0;
617                         *step = 1.0;
618                 }
619                 
620                 /* prevents printing 1.0 2.0 3.0 etc */
621                 if (rem == 1.0) (*power)++;     
622         }
623         else {
624                 if (rem < 2.0) rem= 2.0;
625                 else if(rem < 5.0) rem= 5.0;
626                 else rem= 10.0;
627                 
628                 *step= rem * pow(10.0, (float)(*power));
629                 
630                 (*power)++;
631                 /* prevents printing 1.0, 2.0, 3.0, etc. */
632                 if (rem == 10.0) (*power)++;    
633         }
634 }
635
636 /* Intialise settings necessary for drawing gridlines in a 2d-view 
637  *      - Currently, will return pointer to View2DGrid struct that needs to 
638  *        be freed with UI_view2d_grid_free()
639  *      - Is used for scrollbar drawing too (for units drawing)
640  *      - Units + clamping args will be checked, to make sure they are valid values that can be used
641  *        so it is very possible that we won't return grid at all!
642  *      
643  *      - xunits,yunits = V2D_UNIT_*  grid steps in seconds or frames 
644  *      - xclamp,yclamp = V2D_CLAMP_* only show whole-number intervals
645  *      - winx                  = width of region we're drawing to
646  *      - winy                  = height of region we're drawing into
647  */
648 View2DGrid *UI_view2d_grid_calc(const bContext *C, View2D *v2d, short xunits, short xclamp, short yunits, short yclamp, int winx, int winy)
649 {
650         View2DGrid *grid;
651         float space, pixels, seconddiv;
652         int secondgrid;
653         
654         /* check that there are at least some workable args */
655         if (ELEM(V2D_ARG_DUMMY, xunits, xclamp) && ELEM(V2D_ARG_DUMMY, yunits, yclamp))
656                 return NULL;
657         
658         /* grid here is allocated... */
659         grid= MEM_callocN(sizeof(View2DGrid), "View2DGrid");
660         
661         /* rule: gridstep is minimal GRIDSTEP pixels */
662         if (xunits == V2D_UNIT_SECONDS) {
663                 secondgrid= 1;
664                 seconddiv= 0.01f * FPS;
665         }
666         else {
667                 secondgrid= 0;
668                 seconddiv= 1.0f;
669         }
670         
671         /* calculate x-axis grid scale (only if both args are valid) */
672         if (ELEM(V2D_ARG_DUMMY, xunits, xclamp) == 0) {
673                 space= v2d->cur.xmax - v2d->cur.xmin;
674                 pixels= v2d->mask.xmax - v2d->mask.xmin;
675                 
676                 grid->dx= (MINGRIDSTEP * space) / (seconddiv * pixels);
677                 step_to_grid(&grid->dx, &grid->powerx, xunits);
678                 grid->dx *= seconddiv;
679                 
680                 if (xclamp == V2D_GRID_CLAMP) {
681                         if (grid->dx < 0.1f) grid->dx= 0.1f;
682                         grid->powerx-= 2;
683                         if (grid->powerx < -2) grid->powerx= -2;
684                 }
685         }
686         
687         /* calculate y-axis grid scale (only if both args are valid) */
688         if (ELEM(V2D_ARG_DUMMY, yunits, yclamp) == 0) {
689                 space= v2d->cur.ymax - v2d->cur.ymin;
690                 pixels= winy;
691                 
692                 grid->dy= MINGRIDSTEP * space / pixels;
693                 step_to_grid(&grid->dy, &grid->powery, yunits);
694                 
695                 if (yclamp == V2D_GRID_CLAMP) {
696                         if (grid->dy < 1.0f) grid->dy= 1.0f;
697                         if (grid->powery < 1) grid->powery= 1;
698                 }
699         }
700         
701         /* calculate start position */
702         if (ELEM(V2D_ARG_DUMMY, xunits, xclamp) == 0) {
703                 grid->startx= seconddiv*(v2d->cur.xmin/seconddiv - fmod(v2d->cur.xmin/seconddiv, grid->dx/seconddiv));
704                 if (v2d->cur.xmin < 0.0f) grid->startx-= grid->dx;
705         }
706         if (ELEM(V2D_ARG_DUMMY, yunits, yclamp) == 0) {
707                 grid->starty= (v2d->cur.ymin - fmod(v2d->cur.ymin, grid->dy));
708                 if (v2d->cur.ymin < 0.0f) grid->starty-= grid->dy;
709         }
710         
711         return grid;
712 }
713
714 /* Draw gridlines in the given 2d-region */
715 void UI_view2d_grid_draw(const bContext *C, View2D *v2d, View2DGrid *grid, int flag)
716 {
717         float vec1[2], vec2[2];
718         int a, step;
719         
720         /* check for grid first, as it may not exist */
721         if (grid == NULL)
722                 return;
723         
724         /* vertical lines */
725         if (flag & V2D_VERTICAL_LINES) {
726                 /* initialise initial settings */
727                 vec1[0]= vec2[0]= grid->startx;
728                 vec1[1]= grid->starty;
729                 vec2[1]= v2d->cur.ymax;
730                 
731                 /* minor gridlines */
732                 step= (v2d->mask.xmax - v2d->mask.xmin + 1) / MINGRIDSTEP;
733                 UI_ThemeColor(TH_GRID);
734                 
735                 for (a=0; a<step; a++) {
736                         glBegin(GL_LINE_STRIP);
737                                 glVertex2fv(vec1); 
738                                 glVertex2fv(vec2);
739                         glEnd();
740                         
741                         vec2[0]= vec1[0]+= grid->dx;
742                 }
743                 
744                 /* major gridlines */
745                 vec2[0]= vec1[0]-= 0.5f*grid->dx;
746                 UI_ThemeColorShade(TH_GRID, 16);
747                 
748                 step++;
749                 for (a=0; a<=step; a++) {
750                         glBegin(GL_LINE_STRIP);
751                                 glVertex2fv(vec1); 
752                                 glVertex2fv(vec2);
753                         glEnd();
754                         
755                         vec2[0]= vec1[0]-= grid->dx;
756                 }
757         }
758         
759         /* horizontal lines */
760         if (flag & V2D_HORIZONTAL_LINES) {
761                 /* only major gridlines */
762                 vec1[1]= vec2[1]= grid->starty;
763                 vec1[0]= grid->startx;
764                 vec2[0]= v2d->cur.xmax;
765                 
766                 step= (v2d->mask.ymax - v2d->mask.ymin + 1) / MINGRIDSTEP;
767                 
768                 UI_ThemeColor(TH_GRID);
769                 for (a=0; a<=step; a++) {
770                         glBegin(GL_LINE_STRIP);
771                                 glVertex2fv(vec1); 
772                                 glVertex2fv(vec2);
773                         glEnd();
774                         
775                         vec2[1]= vec1[1]+= grid->dy;
776                 }
777                 
778                 /* fine grid lines */
779                 vec2[1]= vec1[1]-= 0.5f*grid->dy;
780                 step++;
781                 
782                 if (flag & V2D_HORIZONTAL_FINELINES) { 
783                         UI_ThemeColorShade(TH_GRID, 16);
784                         for (a=0; a<step; a++) {
785                                 glBegin(GL_LINE_STRIP);
786                                         glVertex2fv(vec1); 
787                                         glVertex2fv(vec2);
788                                 glEnd();
789                                 
790                                 vec2[1]= vec1[1]-= grid->dy;
791                         }
792                 }
793         }
794         
795         /* Axes are drawn as darker lines */
796         UI_ThemeColorShade(TH_GRID, -50);
797         
798         /* horizontal axis */
799         if (flag & V2D_HORIZONTAL_AXIS) {
800                 vec1[0]= v2d->cur.xmin;
801                 vec2[0]= v2d->cur.xmax;
802                 vec1[1]= vec2[1]= 0.0f;
803                 
804                 glBegin(GL_LINE_STRIP);
805                         glVertex2fv(vec1);
806                         glVertex2fv(vec2);
807                 glEnd();
808         }
809         
810         /* vertical axis */
811         if (flag & V2D_VERTICAL_AXIS) {
812                 vec1[1]= v2d->cur.ymin;
813                 vec2[1]= v2d->cur.ymax;
814                 vec1[0]= vec2[0]= 0.0f;
815                 
816                 glBegin(GL_LINE_STRIP);
817                         glVertex2fv(vec1); 
818                         glVertex2fv(vec2);
819                 glEnd();
820         }
821 }
822
823 /* free temporary memory used for drawing grid */
824 void UI_view2d_grid_free(View2DGrid *grid)
825 {
826         /* only free if there's a grid */
827         if (grid)
828                 MEM_freeN(grid);
829 }
830
831 /* *********************************************************************** */
832 /* Scrollbars */
833
834 /* View2DScrollers is typedef'd in UI_view2d.h 
835  * WARNING: the start of this struct must not change, as view2d_ops.c uses this too. 
836  *                 For now, we don't need to have a separate (internal) header for structs like this...
837  */
838 struct View2DScrollers {        
839                 /* focus bubbles */
840         int vert_min, vert_max; /* vertical scrollbar */
841         int hor_min, hor_max;   /* horizontal scrollbar */
842         
843                 /* scales */
844         View2DGrid *grid;               /* grid for coordinate drawing */
845         short xunits, xclamp;   /* units and clamping options for x-axis */
846         short yunits, yclamp;   /* units and clamping options for y-axis */
847 };
848
849 /* Calculate relevant scroller properties */
850 View2DScrollers *UI_view2d_scrollers_calc(const bContext *C, View2D *v2d, short xunits, short xclamp, short yunits, short yclamp)
851 {
852         View2DScrollers *scrollers;
853         rcti vert, hor;
854         float fac, totsize, scrollsize;
855         
856         vert= v2d->vert;
857         hor= v2d->hor;
858         
859         /* scrollers is allocated here... */
860         scrollers= MEM_callocN(sizeof(View2DScrollers), "View2DScrollers");
861         
862         /* scroller 'buttons':
863          *      - These should always remain within the visible region of the scrollbar
864          *      - They represent the region of 'tot' that is visible in 'cur'
865          */
866         
867         /* horizontal scrollers */
868         if (v2d->scroll & (V2D_SCROLL_HORIZONTAL|V2D_SCROLL_HORIZONTAL_O)) {
869                 /* scroller 'button' extents */
870                 totsize= v2d->tot.xmax - v2d->tot.xmin;
871                 scrollsize= hor.xmax - hor.xmin;
872                 
873                 fac= (v2d->cur.xmin- v2d->tot.xmin) / totsize;
874                 scrollers->hor_min= hor.xmin + (fac * scrollsize);
875                 
876                 fac= (v2d->cur.xmax - v2d->tot.xmin) / totsize;
877                 scrollers->hor_max= hor.xmin + (fac * scrollsize);
878                 
879                 if (scrollers->hor_min > scrollers->hor_max) 
880                         scrollers->hor_min= scrollers->hor_max;
881         }
882         
883         /* vertical scrollers */
884         if (v2d->scroll & V2D_SCROLL_VERTICAL) {
885                 /* scroller 'button' extents */
886                 totsize= v2d->tot.ymax - v2d->tot.ymin;
887                 scrollsize= vert.ymax - vert.ymin;
888                 
889                 fac= (v2d->cur.ymin- v2d->tot.ymin) / totsize;
890                 scrollers->vert_min= vert.ymin + (fac * scrollsize);
891                 
892                 fac= (v2d->cur.ymax - v2d->tot.ymin) / totsize;
893                 scrollers->vert_max= vert.ymin + (fac * scrollsize);
894                 
895                 if (scrollers->vert_min > scrollers->vert_max) 
896                         scrollers->vert_min= scrollers->vert_max;
897         }
898         
899         /* grid markings on scrollbars */
900         if (v2d->scroll & (V2D_SCROLL_SCALE_HORIZONTAL|V2D_SCROLL_SCALE_VERTICAL)) {
901                 /* store clamping */
902                 scrollers->xclamp= xclamp;
903                 scrollers->xunits= xunits;
904                 scrollers->yclamp= yclamp;
905                 scrollers->yunits= yunits;
906                 
907                 scrollers->grid= UI_view2d_grid_calc(C, v2d, xunits, xclamp, yunits, yclamp, (hor.xmax - hor.xmin), (vert.ymax - vert.ymin));
908         }
909         
910         /* return scrollers */
911         return scrollers;
912 }
913
914 /* XXX */
915 extern void ui_rasterpos_safe(float x, float y, float aspect);
916
917 /* Print scale marking along a time scrollbar */
918 static void scroll_printstr(View2DScrollers *scrollers, float x, float y, float val, int power, short unit, char dir)
919 {
920         int len;
921         char str[32];
922         
923         /* adjust the scale unit to work ok */
924         if (dir == 'v') {
925                 /* here we bump up the power by factor of 10, as 
926                  * rotation values (hence 'degrees') are divided by 10 to 
927                  * be able to show the curves at the same time
928                  */
929                 if ELEM(unit, V2D_UNIT_DEGREES, V2D_UNIT_TIME) {
930                         power += 1;
931                         val *= 10;
932                 }
933         }
934         
935         /* get string to print */
936         if (unit == V2D_UNIT_SECONDS) {
937                 /* SMPTE timecode style:
938                  *      - In general, minutes and seconds should be shown, as most clips will be
939                  *        within this length. Hours will only be included if relevant.
940                  *      - Only show frames when zoomed in enough for them to be relevant 
941                  *        (using separator convention of ';' for frames, ala QuickTime).
942                  *        When showing frames, use slightly different display to avoid confusion with mm:ss format
943                  */
944                 int hours=0, minutes=0, seconds=0, frames=0;
945                 char neg[2]= "";
946                 
947                 /* get values */
948                 if (val < 0) {
949                         /* correction for negative values */
950                         sprintf(neg, "-");
951                         val = -val;
952                 }
953                 if (val >= 3600) {
954                         /* hours */
955                         /* XXX should we only display a single digit for hours since clips are 
956                          *         VERY UNLIKELY to be more than 1-2 hours max? However, that would 
957                          *         go against conventions...
958                          */
959                         hours= (int)val / 3600;
960                         val= fmod(val, 3600);
961                 }
962                 if (val >= 60) {
963                         /* minutes */
964                         minutes= (int)val / 60;
965                         val= fmod(val, 60);
966                 }
967                 if (power <= 0) {
968                         /* seconds + frames
969                          *      Frames are derived from 'fraction' of second. We need to perform some additional rounding
970                          *      to cope with 'half' frames, etc., which should be fine in most cases
971                          */
972                         seconds= (int)val;
973                         frames= (int)floor( ((val - seconds) * FPS) + 0.5f );
974                 }
975                 else {
976                         /* seconds (with pixel offset) */
977                         seconds= (int)floor(val + 0.375f);
978                 }
979                 
980                 /* print timecode to temp string buffer */
981                 if (power <= 0) {
982                         /* include "frames" in display */
983                         if (hours) sprintf(str, "%s%02d:%02d:%02d;%02d", neg, hours, minutes, seconds, frames);
984                         else if (minutes) sprintf(str, "%s%02d:%02d;%02d", neg, minutes, seconds, frames);
985                         else sprintf(str, "%s%d;%02d", neg, seconds, frames);
986                 }
987                 else {
988                         /* don't include 'frames' in display */
989                         if (hours) sprintf(str, "%s%02d:%02d:%02d", neg, hours, minutes, seconds);
990                         else sprintf(str, "%s%02d:%02d", neg, minutes, seconds);
991                 }
992         }
993         else {
994                 /* round to whole numbers if power is >= 1 (i.e. scale is coarse) */
995                 if (power <= 0) sprintf(str, "%.*f", 1-power, val);
996                 else sprintf(str, "%d", (int)floor(val + 0.375f));
997         }
998         
999         /* get length of string, and adjust printing location to fit it into the horizontal scrollbar */
1000         len= strlen(str);
1001         if (dir == 'h') {
1002                 /* seconds/timecode display has slightly longer strings... */
1003                 if (unit == V2D_UNIT_SECONDS)
1004                         x-= 3*len;
1005                 else
1006                         x-= 4*len;
1007         }
1008         
1009         /* Add degree sympbol to end of string for vertical scrollbar? */
1010         if ((dir == 'v') && (unit == V2D_UNIT_DEGREES)) {
1011                 str[len]= 186;
1012                 str[len+1]= 0;
1013         }
1014         
1015         /* draw it */
1016         ui_rasterpos_safe(x, y, 1.0);
1017         UI_DrawString(G.fonts, str, 0); // XXX check this again when new text-drawing api is done
1018 }
1019
1020 /* local define for radius of scroller 'button' caps */
1021 #define V2D_SCROLLCAP_RAD       5
1022
1023 /* Draw scrollbars in the given 2d-region */
1024 void UI_view2d_scrollers_draw(const bContext *C, View2D *v2d, View2DScrollers *vs)
1025 {
1026         const int darker= -50, dark= -10, light= 20, lighter= 50;
1027         rcti vert, hor;
1028         
1029         /* make copies of rects for less typing */
1030         vert= v2d->vert;
1031         hor= v2d->hor;
1032         
1033         /* horizontal scrollbar */
1034         if (v2d->scroll & (V2D_SCROLL_HORIZONTAL|V2D_SCROLL_HORIZONTAL_O)) {
1035                 /* scroller backdrop */
1036                 UI_ThemeColorShade(TH_SHADE1, light);
1037                 glRecti(hor.xmin,  hor.ymin,  hor.xmax,  hor.ymax);
1038                 
1039                 /* scroller 'button' 
1040                  *      - if view is zoomable in x, draw handles too 
1041                  *      - handles are drawn darker
1042                  */
1043                 if (v2d->keepzoom & V2D_LOCKZOOM_X) {
1044                         /* draw base bar as rounded shape */
1045                         UI_ThemeColorShade(TH_SHADE1, dark);
1046                         
1047                         uiSetRoundBox(15);
1048                         gl_round_box_shade(GL_POLYGON, 
1049                                         vs->hor_min, hor.ymin+2, 
1050                                         vs->hor_max, hor.ymax-2, 
1051                                         V2D_SCROLLCAP_RAD, 0.05f, -0.05f);
1052                 }
1053                 else {
1054                         /* base bar drawn as shaded rect */
1055                         UI_ThemeColorShade(TH_SHADE1, dark);
1056                         uiSetRoundBox(0);
1057                         gl_round_box_shade(GL_POLYGON, 
1058                                         vs->hor_min, hor.ymin+2, 
1059                                         vs->hor_max, hor.ymax-2, 
1060                                         V2D_SCROLLCAP_RAD, 0.05f, -0.05f);
1061                         
1062                         /* handles draw darker */
1063                         // XXX handles are drawn with the two last args set to same values, otherwise, max appears darker than min
1064                         UI_ThemeColorShade(TH_SHADE1, darker);
1065                         
1066                         /* 'minimum' handle */
1067                         uiSetRoundBox(9);
1068                         gl_round_box_shade(GL_POLYGON, 
1069                                         vs->hor_min-V2D_SCROLLER_HANDLE_SIZE, hor.ymin+2, 
1070                                         vs->hor_min+V2D_SCROLLER_HANDLE_SIZE, hor.ymax-2, 
1071                                         V2D_SCROLLCAP_RAD, 0.07f, -0.07f);
1072                                         //V2D_SCROLLCAP_RAD, 0, 0);
1073                         
1074                         /* maximum handle */
1075                         uiSetRoundBox(6);
1076                         gl_round_box_shade(GL_POLYGON, 
1077                                         vs->hor_max-V2D_SCROLLER_HANDLE_SIZE, hor.ymin+2, 
1078                                         vs->hor_max+V2D_SCROLLER_HANDLE_SIZE, hor.ymax-2, 
1079                                         V2D_SCROLLCAP_RAD, 0.07f, -0.07f);
1080                                         //V2D_SCROLLCAP_RAD, 0, 0);
1081                 }
1082                 
1083                 /* scale indicators */
1084                 // XXX will need to update the font drawing when the new stuff comes in
1085                 if ((v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) && (vs->grid)) {
1086                         View2DGrid *grid= vs->grid;
1087                         float fac, dfac, fac2, val;
1088                         
1089                         /* the numbers: convert grid->startx and -dx to scroll coordinates 
1090                          *      - fac is x-coordinate to draw to
1091                          *      - dfac is gap between scale markings
1092                          */
1093                         fac= (grid->startx - v2d->cur.xmin) / (v2d->cur.xmax - v2d->cur.xmin);
1094                         fac= hor.xmin + fac*(hor.xmax - hor.xmin);
1095                         
1096                         dfac= (grid->dx) / (v2d->cur.xmax - v2d->cur.xmin);
1097                         dfac= dfac * (hor.xmax - hor.xmin);
1098                         
1099                         /* set starting value, and text color */
1100                         UI_ThemeColor(TH_TEXT);
1101                         val= grid->startx;
1102                         
1103                         /* if we're clamping to whole numbers only, make sure entries won't be repeated */
1104                         if (vs->xclamp == V2D_GRID_CLAMP) {
1105                                 while (grid->dx < 0.9999f) {
1106                                         grid->dx *= 2.0f;
1107                                         dfac *= 2.0f;
1108                                 }
1109                         }
1110                         if (vs->xunits == V2D_UNIT_FRAMES)
1111                                 grid->powerx= 1;
1112                         
1113                         /* draw numbers in the appropriate range */
1114                         if (dfac != 0.0f) {
1115                                 for (; fac < hor.xmax; fac+=dfac, val+=grid->dx) {
1116                                         switch (vs->xunits) {
1117                                                 case V2D_UNIT_FRAMES:           /* frames (as whole numbers)*/
1118                                                         scroll_printstr(vs, fac, 3.0+(float)(hor.ymin), val, grid->powerx, V2D_UNIT_FRAMES, 'h');
1119                                                         break;
1120                                                 
1121                                                 case V2D_UNIT_SECONDS:          /* seconds */
1122                                                         fac2= val/FPS;
1123                                                         scroll_printstr(vs, fac, 3.0+(float)(hor.ymin), fac2, grid->powerx, V2D_UNIT_SECONDS, 'h');
1124                                                         break;
1125                                                         
1126                                                 case V2D_UNIT_SECONDSSEQ:       /* seconds with special calculations (only used for sequencer only) */
1127                                                 {
1128                                                         float time;
1129                                                         
1130                                                         fac2= val/FPS;
1131                                                         time= floor(fac2);
1132                                                         fac2= fac2-time;
1133                                                         
1134                                                         scroll_printstr(vs, fac, 3.0+(float)(hor.ymin), time+FPS*fac2/100.0, grid->powerx, V2D_UNIT_SECONDSSEQ, 'h');
1135                                                 }
1136                                                         break;
1137                                                         
1138                                                 case V2D_UNIT_DEGREES:          /* IPO-Editor for rotation IPO-Drivers */
1139                                                         /* HACK: although we're drawing horizontal, we make this draw as 'vertical', just to get degree signs */
1140                                                         scroll_printstr(vs, fac, 3.0+(float)(hor.ymin), val, grid->powerx, V2D_UNIT_DEGREES, 'v');
1141                                                         break;
1142                                         }
1143                                 }
1144                         }
1145                 }
1146                 
1147                 /* decoration outer bevel line */
1148                 UI_ThemeColorShade(TH_SHADE1, lighter);
1149                 if (v2d->scroll & (V2D_SCROLL_BOTTOM|V2D_SCROLL_BOTTOM_O))
1150                         sdrawline(hor.xmin, hor.ymax, hor.xmax, hor.ymax);
1151                 else if (v2d->scroll & V2D_SCROLL_TOP)
1152                         sdrawline(hor.xmin, hor.ymin, hor.xmax, hor.ymin);
1153         }
1154         
1155         /* vertical scrollbar */
1156         if (v2d->scroll & V2D_SCROLL_VERTICAL) {
1157                 /* scroller backdrop  */
1158                 UI_ThemeColorShade(TH_SHADE1, light);
1159                 glRecti(vert.xmin,  vert.ymin,  vert.xmax,  vert.ymax);
1160                 
1161                 /* scroller 'button' 
1162                  *      - if view is zoomable in y, draw handles too 
1163                  *      - handles are drawn darker
1164                  */
1165                 // XXX gl_round_box_shade is currently hardcoded to do horizontal buts only... need a vertical region
1166                 if (v2d->keepzoom & V2D_LOCKZOOM_Y) {
1167                         /* draw base bar as rounded shape */
1168                         UI_ThemeColorShade(TH_SHADE1, dark);
1169                         
1170                         uiSetRoundBox(15);
1171                         gl_round_box_shade(GL_POLYGON, 
1172                                         vert.xmin+2, vs->vert_min, 
1173                                         vert.xmax-2, vs->vert_max, 
1174                                         V2D_SCROLLCAP_RAD, 0.05f, -0.05f);
1175                 }
1176                 else {
1177                         /* for now, draw base bar as unshaded rect */
1178                         // FIXME: this should be shaded horizontally too
1179                         UI_ThemeColorShade(TH_SHADE1, dark);
1180                         glRecti(vert.xmin+2, vs->vert_min,  
1181                                         vert.xmax-2, vs->vert_max);
1182                         
1183                         /* handles draw darker */
1184                         // XXX handles are drawn with the two last args set to same values, otherwise, max appears darker than min
1185                         UI_ThemeColorShade(TH_SHADE1, darker);
1186                         
1187                         /* 'minimum' handle */
1188                         uiSetRoundBox(12);
1189                         gl_round_box_shade(GL_POLYGON, 
1190                                         vert.xmin+2, vs->vert_min-V2D_SCROLLER_HANDLE_SIZE, 
1191                                         vert.xmax-2, vs->vert_min+V2D_SCROLLER_HANDLE_SIZE, 
1192                                         V2D_SCROLLCAP_RAD, 0, 0);
1193                         
1194                         /* maximum handle */
1195                         uiSetRoundBox(3);
1196                         gl_round_box_shade(GL_POLYGON, 
1197                                         vert.xmin+2, vs->vert_max-V2D_SCROLLER_HANDLE_SIZE, 
1198                                         vert.xmax-2, vs->vert_max+V2D_SCROLLER_HANDLE_SIZE, 
1199                                         V2D_SCROLLCAP_RAD, 0, 0);
1200                 }
1201                 
1202                 /* scale indiators */
1203                 // XXX will need to update the font drawing when the new stuff comes in
1204                 if ((v2d->scroll & V2D_SCROLL_SCALE_VERTICAL) && (vs->grid)) {
1205                         View2DGrid *grid= vs->grid;
1206                         float fac, dfac, val;
1207                         
1208                         /* the numbers: convert grid->starty and dy to scroll coordinates 
1209                          *      - fac is y-coordinate to draw to
1210                          *      - dfac is gap between scale markings
1211                          *      - these involve a correction for horizontal scrollbar
1212                          *        NOTE: it's assumed that that scrollbar is there if this is involved!
1213                          */
1214                         fac= (grid->starty- v2d->cur.ymin) / (v2d->cur.ymax - v2d->cur.ymin);
1215                         fac= (vert.ymin + V2D_SCROLL_HEIGHT) + fac*(vert.ymax - vert.ymin - V2D_SCROLL_HEIGHT);
1216                         
1217                         dfac= (grid->dy) / (v2d->cur.ymax - v2d->cur.ymin);
1218                         dfac= dfac * (vert.ymax - vert.ymin - V2D_SCROLL_HEIGHT);
1219                         
1220                         /* set starting value, and text color */
1221                         UI_ThemeColor(TH_TEXT);
1222                         val= grid->starty;
1223                         
1224                         /* if vertical clamping (to whole numbers) is used (i.e. in Sequencer), apply correction */
1225                         // XXX only relevant to Sequencer, so need to review this when we port that code
1226                         if (vs->yclamp == V2D_GRID_CLAMP)
1227                                 fac += 0.5f * dfac;
1228                                 
1229                         /* draw vertical steps */
1230                         for (; fac < vert.ymax; fac+= dfac, val += grid->dy) {
1231                                 scroll_printstr(vs, (float)(vert.xmax)-14.0, fac, val, grid->powery, vs->yunits, 'v');
1232                         }                       
1233                 }       
1234                 
1235                 /* decoration outer bevel line */
1236                 UI_ThemeColorShade(TH_SHADE1, lighter);
1237                 if (v2d->scroll & V2D_SCROLL_RIGHT)
1238                         sdrawline(vert.xmin, vert.ymin, vert.xmin, vert.ymax);
1239                 else if (v2d->scroll & V2D_SCROLL_LEFT)
1240                         sdrawline(vert.xmax, vert.ymin, vert.xmax, vert.ymax);
1241         }
1242 }
1243
1244 /* free temporary memory used for drawing scrollers */
1245 void UI_view2d_scrollers_free(View2DScrollers *scrollers)
1246 {
1247         /* need to free grid as well... */
1248         if (scrollers->grid) MEM_freeN(scrollers->grid);
1249         MEM_freeN(scrollers);
1250 }
1251
1252 /* *********************************************************************** */
1253 /* Coordinate Conversions */
1254
1255 /* Convert from screen/region space to 2d-View space 
1256  *      
1257  *      - x,y                   = coordinates to convert
1258  *      - viewx,viewy           = resultant coordinates
1259  */
1260 void UI_view2d_region_to_view(View2D *v2d, int x, int y, float *viewx, float *viewy)
1261 {
1262         float div, ofs;
1263
1264         if (viewx) {
1265                 div= v2d->mask.xmax - v2d->mask.xmin;
1266                 ofs= v2d->mask.xmin;
1267                 
1268                 *viewx= v2d->cur.xmin + (v2d->cur.xmax-v2d->cur.xmin) * ((float)x - ofs) / div;
1269         }
1270
1271         if (viewy) {
1272                 div= v2d->mask.ymax - v2d->mask.ymin;
1273                 ofs= v2d->mask.ymin;
1274                 
1275                 *viewy= v2d->cur.ymin + (v2d->cur.ymax - v2d->cur.ymin) * ((float)y - ofs) / div;
1276         }
1277 }
1278
1279 /* Convert from 2d-View space to screen/region space
1280  *      - Coordinates are clamped to lie within bounds of region
1281  *
1282  *      - x,y                           = coordinates to convert
1283  *      - regionx,regiony       = resultant coordinates 
1284  */
1285 void UI_view2d_view_to_region(View2D *v2d, float x, float y, short *regionx, short *regiony)
1286 {
1287         /* set initial value in case coordinate lies outside of bounds */
1288         if (regionx)
1289                 *regionx= V2D_IS_CLIPPED;
1290         if (regiony)
1291                 *regiony= V2D_IS_CLIPPED;
1292         
1293         /* express given coordinates as proportional values */
1294         x= (x - v2d->cur.xmin) / (v2d->cur.xmax - v2d->cur.xmin);
1295         y= (y - v2d->cur.ymin) / (v2d->cur.ymax - v2d->cur.ymin);
1296         
1297         /* check if values are within bounds */
1298         if ((x>=0.0f) && (x<=1.0f) && (y>=0.0f) && (y<=1.0f)) {
1299                 if (regionx)
1300                         *regionx= v2d->mask.xmin + x*(v2d->mask.xmax-v2d->mask.xmin);
1301                 if (regiony)
1302                         *regiony= v2d->mask.ymin + y*(v2d->mask.ymax-v2d->mask.ymin);
1303         }
1304 }
1305
1306 /* Convert from 2d-view space to screen/region space
1307  *      - Coordinates are NOT clamped to lie within bounds of region
1308  *
1309  *      - x,y                           = coordinates to convert
1310  *      - regionx,regiony       = resultant coordinates 
1311  */
1312 void UI_view2d_to_region_no_clip(View2D *v2d, float x, float y, short *regionx, short *regiony)
1313 {
1314         /* step 1: express given coordinates as proportional values */
1315         x= (x - v2d->cur.xmin) / (v2d->cur.xmax - v2d->cur.xmin);
1316         y= (y - v2d->cur.ymin) / (v2d->cur.ymax - v2d->cur.ymin);
1317         
1318         /* step 2: convert proportional distances to screen coordinates  */
1319         x= v2d->mask.xmin + x*(v2d->mask.xmax - v2d->mask.xmin);
1320         y= v2d->mask.ymin + y*(v2d->mask.ymax - v2d->mask.ymin);
1321         
1322         /* although we don't clamp to lie within region bounds, we must avoid exceeding size of shorts */
1323         if (regionx) {
1324                 if (x < -32760) *regionx= -32760;
1325                 else if(x > 32760) *regionx= 32760;
1326                 else *regionx= x;
1327         }
1328         if (regiony) {
1329                 if (y < -32760) *regiony= -32760;
1330                 else if(y > 32760) *regiony= 32760;
1331                 else *regiony= y;
1332         }
1333 }
1334
1335 /* *********************************************************************** */
1336 /* Utilities */
1337
1338 /* View2D data by default resides in region, so get from region stored in context */
1339 View2D *UI_view2d_fromcontext(const bContext *C)
1340 {
1341         if (C->area == NULL) return NULL;
1342         if (C->region == NULL) return NULL;
1343         return &(C->region->v2d);
1344 }
1345
1346 /* same as above, but it returns regionwindow. Utility for pulldowns or buttons */
1347 View2D *UI_view2d_fromcontext_rwin(const bContext *C)
1348 {
1349         if (C->area == NULL) return NULL;
1350         if (C->region == NULL) return NULL;
1351         if (C->region->regiontype!=RGN_TYPE_WINDOW) {
1352                 ARegion *ar= C->area->regionbase.first;
1353                 for(; ar; ar= ar->next)
1354                         if(ar->regiontype==RGN_TYPE_WINDOW)
1355                                 return &(ar->v2d);
1356                 return NULL;
1357         }
1358         return &(C->region->v2d);
1359 }
1360
1361
1362 /* Calculate the scale per-axis of the drawing-area
1363  *      - Is used to inverse correct drawing of icons, etc. that need to follow view 
1364  *        but not be affected by scale
1365  *
1366  *      - x,y   = scale on each axis
1367  */
1368 void UI_view2d_getscale(View2D *v2d, float *x, float *y) 
1369 {
1370         if (x) *x = (v2d->mask.xmax - v2d->mask.xmin) / (v2d->cur.xmax - v2d->cur.xmin);
1371         if (y) *y = (v2d->mask.ymax - v2d->mask.ymin) / (v2d->cur.ymax - v2d->cur.ymin);
1372 }