1 /*
2 * Copyright (c) 2002-2012 Hypertriton, Inc. <http://hypertriton.com/>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23 * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include <agar/core/core.h>
27 #include <agar/gui/scrollbar.h>
28 #include <agar/gui/window.h>
29 #include <agar/gui/primitive.h>
30 #include <agar/gui/text.h>
31 #include <agar/gui/gui_math.h>
32
33 #define SBPOS(sb,x,y) (((sb)->type == AG_SCROLLBAR_HORIZ) ? (x) : (y))
34 #define SBLEN(sb) (((sb)->type == AG_SCROLLBAR_HORIZ) ? WIDTH(sb) : HEIGHT(sb))
35
36 AG_Scrollbar *
AG_ScrollbarNew(void * parent,enum ag_scrollbar_type type,Uint flags)37 AG_ScrollbarNew(void *parent, enum ag_scrollbar_type type, Uint flags)
38 {
39 AG_Scrollbar *sb;
40
41 sb = Malloc(sizeof(AG_Scrollbar));
42 AG_ObjectInit(sb, &agScrollbarClass);
43 sb->type = type;
44 sb->flags |= flags;
45
46 if (flags & AG_SCROLLBAR_HFILL) { AG_ExpandHoriz(sb); }
47 if (flags & AG_SCROLLBAR_VFILL) { AG_ExpandVert(sb); }
48 if (flags & AG_SCROLLBAR_NOAUTOHIDE) { sb->flags &= ~(AG_SCROLLBAR_AUTOHIDE); }
49
50 AG_ObjectAttach(parent, sb);
51 return (sb);
52 }
53
54 /* Configure an initial length for the size requisition. */
55 void
AG_ScrollbarSizeHint(AG_Scrollbar * sb,int len)56 AG_ScrollbarSizeHint(AG_Scrollbar *sb, int len)
57 {
58 AG_ObjectLock(sb);
59 sb->lenPre = len;
60 AG_ObjectUnlock(sb);
61 }
62
63 /* Set an alternate handler for UP/LEFT button click. */
64 void
AG_ScrollbarSetIncFn(AG_Scrollbar * sb,AG_EventFn fn,const char * fmt,...)65 AG_ScrollbarSetIncFn(AG_Scrollbar *sb, AG_EventFn fn, const char *fmt, ...)
66 {
67 AG_ObjectLock(sb);
68 sb->buttonIncFn = AG_SetEvent(sb, NULL, fn, NULL);
69 AG_EVENT_GET_ARGS(sb->buttonIncFn, fmt);
70 AG_ObjectUnlock(sb);
71 }
72
73 /* Set an alternate handler for DOWN/RIGHT button click. */
74 void
AG_ScrollbarSetDecFn(AG_Scrollbar * sb,AG_EventFn fn,const char * fmt,...)75 AG_ScrollbarSetDecFn(AG_Scrollbar *sb, AG_EventFn fn, const char *fmt, ...)
76 {
77 AG_ObjectLock(sb);
78 sb->buttonDecFn = AG_SetEvent(sb, NULL, fn, NULL);
79 AG_EVENT_GET_ARGS(sb->buttonDecFn, fmt);
80 AG_ObjectUnlock(sb);
81 }
82
83 /*
84 * Return the the current position and size (in pixels) of the scrollbar
85 * control. Returns 0 on success, or -1 if the values are outside the range.
86 */
87 #undef GET_EXTENT_PX
88 #define GET_EXTENT_PX(TYPE) { \
89 if (vis > 0) { \
90 if ((max - min) > 0) { \
91 extentPx = sb->length - \
92 (int)(vis * sb->length / (max - min)); \
93 } else { \
94 extentPx = 0; \
95 } \
96 } else { \
97 extentPx = (sb->wBar == -1) ? 0 : (sb->length - sb->wBar); \
98 } \
99 }
100 #define GET_PX_COORDS(TYPE) { \
101 TYPE min = *(TYPE *)pMin; \
102 TYPE max = *(TYPE *)pMax; \
103 TYPE vis = *(TYPE *)pVis; \
104 int extentPx, divPx; \
105 \
106 if (min >= (max - vis)) { \
107 goto fail; \
108 } \
109 GET_EXTENT_PX(TYPE); \
110 divPx = (int)(max - vis - min); \
111 if (divPx < 1) { goto fail; } \
112 *x = (int)(((*(TYPE *)pVal - min) * extentPx) / divPx); \
113 *len = (vis > 0) ? (vis * sb->length / (max - min)) : \
114 (sb->wBar == -1) ? sb->length : sb->wBar; \
115 }
116 static __inline__ int
GetPxCoords(AG_Scrollbar * sb,int * x,int * len)117 GetPxCoords(AG_Scrollbar *sb, int *x, int *len)
118 {
119 AG_Variable *bMin, *bMax, *bVis, *bVal;
120 void *pMin, *pMax, *pVal, *pVis;
121
122 *x = 0;
123 *len = 0;
124
125 bVal = AG_GetVariable(sb, "value", &pVal);
126 bMin = AG_GetVariable(sb, "min", &pMin);
127 bMax = AG_GetVariable(sb, "max", &pMax);
128 bVis = AG_GetVariable(sb, "visible", &pVis);
129
130 switch (AG_VARIABLE_TYPE(bVal)) {
131 case AG_VARIABLE_INT: GET_PX_COORDS(int); break;
132 case AG_VARIABLE_UINT: GET_PX_COORDS(Uint); break;
133 case AG_VARIABLE_FLOAT: GET_PX_COORDS(float); break;
134 case AG_VARIABLE_DOUBLE: GET_PX_COORDS(double); break;
135 #ifdef HAVE_LONG_DOUBLE
136 case AG_VARIABLE_LONG_DOUBLE: GET_PX_COORDS(long double); break;
137 #endif
138 case AG_VARIABLE_UINT8: GET_PX_COORDS(Uint8); break;
139 case AG_VARIABLE_SINT8: GET_PX_COORDS(Sint8); break;
140 case AG_VARIABLE_UINT16: GET_PX_COORDS(Uint16); break;
141 case AG_VARIABLE_SINT16: GET_PX_COORDS(Sint16); break;
142 case AG_VARIABLE_UINT32: GET_PX_COORDS(Uint32); break;
143 case AG_VARIABLE_SINT32: GET_PX_COORDS(Sint32); break;
144 #ifdef HAVE_64BIT
145 case AG_VARIABLE_UINT64: GET_PX_COORDS(Uint64); break;
146 case AG_VARIABLE_SINT64: GET_PX_COORDS(Sint64); break;
147 #endif
148 default: break;
149 }
150
151 AG_UnlockVariable(bVis);
152 AG_UnlockVariable(bMax);
153 AG_UnlockVariable(bMin);
154 AG_UnlockVariable(bVal);
155 return (0);
156 fail:
157 AG_UnlockVariable(bVis);
158 AG_UnlockVariable(bMax);
159 AG_UnlockVariable(bMin);
160 AG_UnlockVariable(bVal);
161 return (-1);
162 }
163 #undef GET_PX_COORDS
164
165 /*
166 * Map specified pixel coordinates to a value.
167 */
168 #define MAP_PX_COORDS(TYPE) { \
169 TYPE min = *(TYPE *)pMin; \
170 TYPE max = *(TYPE *)pMax; \
171 TYPE vis = *(TYPE *)pVis; \
172 int extentPx; \
173 \
174 if (*(TYPE *)pMax > *(TYPE *)pVis) { \
175 GET_EXTENT_PX(TYPE); \
176 if (x <= 0) { \
177 *(TYPE *)pVal = min; \
178 } else if (x >= (int)extentPx) { \
179 *(TYPE *)pVal = MAX(min, (max - vis)); \
180 } else { \
181 *(TYPE *)pVal = min + x*(max-vis-min)/extentPx; \
182 if (*(TYPE *)pVal < min) { *(TYPE *)pVal = min; } \
183 if (*(TYPE *)pVal > max) { *(TYPE *)pVal = max; } \
184 } \
185 } \
186 }
187 static __inline__ void
SeekToPxCoords(AG_Scrollbar * sb,int x)188 SeekToPxCoords(AG_Scrollbar *sb, int x)
189 {
190 AG_Variable *bMin, *bMax, *bVis, *bVal;
191 void *pMin, *pMax, *pVal, *pVis;
192
193 bVal = AG_GetVariable(sb, "value", &pVal);
194 bMin = AG_GetVariable(sb, "min", &pMin);
195 bMax = AG_GetVariable(sb, "max", &pMax);
196 bVis = AG_GetVariable(sb, "visible", &pVis);
197
198 switch (AG_VARIABLE_TYPE(bVal)) {
199 case AG_VARIABLE_INT: MAP_PX_COORDS(int); break;
200 case AG_VARIABLE_UINT: MAP_PX_COORDS(Uint); break;
201 case AG_VARIABLE_FLOAT: MAP_PX_COORDS(float); break;
202 case AG_VARIABLE_DOUBLE: MAP_PX_COORDS(double); break;
203 #ifdef HAVE_LONG_DOUBLE
204 case AG_VARIABLE_LONG_DOUBLE: MAP_PX_COORDS(long double); break;
205 #endif
206 case AG_VARIABLE_UINT8: MAP_PX_COORDS(Uint8); break;
207 case AG_VARIABLE_SINT8: MAP_PX_COORDS(Sint8); break;
208 case AG_VARIABLE_UINT16: MAP_PX_COORDS(Uint16); break;
209 case AG_VARIABLE_SINT16: MAP_PX_COORDS(Sint16); break;
210 case AG_VARIABLE_UINT32: MAP_PX_COORDS(Uint32); break;
211 case AG_VARIABLE_SINT32: MAP_PX_COORDS(Sint32); break;
212 #ifdef HAVE_64BIT
213 case AG_VARIABLE_UINT64: MAP_PX_COORDS(Uint64); break;
214 case AG_VARIABLE_SINT64: MAP_PX_COORDS(Sint64); break;
215 #endif
216 default: break;
217 }
218
219 AG_PostEvent(NULL, sb, "scrollbar-changed", NULL);
220 AG_UnlockVariable(bVis);
221 AG_UnlockVariable(bMax);
222 AG_UnlockVariable(bMin);
223 AG_UnlockVariable(bVal);
224 AG_Redraw(sb);
225 }
226 #undef MAP_PX_COORDS
227
228 /*
229 * Type-independent increment/decrement operation.
230 */
231 #undef INCREMENT
232 #define INCREMENT(TYPE) { \
233 if (*(TYPE *)pMax > *(TYPE *)pVis) { \
234 if ((*(TYPE *)pVal + *(TYPE *)pInc) > \
235 (*(TYPE *)pMax - (*(TYPE *)pVis))) { \
236 *(TYPE *)pVal = (*(TYPE *)pMax) - (*(TYPE *)pVis); \
237 rv = 1; \
238 } else { \
239 *(TYPE *)pVal += *(TYPE *)pInc; \
240 } \
241 } \
242 }
243 #undef DECREMENT
244 #define DECREMENT(TYPE) { \
245 if (*(TYPE *)pMax > *(TYPE *)pVis) { \
246 if (*(TYPE *)pVal < *(TYPE *)pMin + *(TYPE *)pInc) { \
247 *(TYPE *)pVal = *(TYPE *)pMin; \
248 rv = 1; \
249 } else { \
250 *(TYPE *)pVal -= *(TYPE *)pInc; \
251 } \
252 } \
253 }
254 static int
Increment(AG_Scrollbar * sb)255 Increment(AG_Scrollbar *sb)
256 {
257 AG_Variable *bVal, *bMin, *bMax, *bInc, *bVis;
258 void *pVal, *pMin, *pMax, *pInc, *pVis;
259 int rv = 0;
260
261 bVal = AG_GetVariable(sb, "value", &pVal);
262 bMin = AG_GetVariable(sb, "min", &pMin);
263 bMax = AG_GetVariable(sb, "max", &pMax);
264 bInc = AG_GetVariable(sb, "inc", &pInc);
265 bVis = AG_GetVariable(sb, "visible", &pVis);
266
267 switch (AG_VARIABLE_TYPE(bVal)) {
268 case AG_VARIABLE_INT: INCREMENT(int); break;
269 case AG_VARIABLE_UINT: INCREMENT(Uint); break;
270 case AG_VARIABLE_FLOAT: INCREMENT(float); break;
271 case AG_VARIABLE_DOUBLE: INCREMENT(double); break;
272 #ifdef HAVE_LONG_DOUBLE
273 case AG_VARIABLE_LONG_DOUBLE: INCREMENT(long double); break;
274 #endif
275 case AG_VARIABLE_UINT8: INCREMENT(Uint8); break;
276 case AG_VARIABLE_SINT8: INCREMENT(Sint8); break;
277 case AG_VARIABLE_UINT16: INCREMENT(Uint16); break;
278 case AG_VARIABLE_SINT16: INCREMENT(Sint16); break;
279 case AG_VARIABLE_UINT32: INCREMENT(Uint32); break;
280 case AG_VARIABLE_SINT32: INCREMENT(Sint32); break;
281 #ifdef HAVE_64BIT
282 case AG_VARIABLE_UINT64: INCREMENT(Uint64); break;
283 case AG_VARIABLE_SINT64: INCREMENT(Sint64); break;
284 #endif
285 default: break;
286 }
287
288 AG_PostEvent(NULL, sb, "scrollbar-changed", NULL);
289 AG_UnlockVariable(bVal);
290 AG_UnlockVariable(bMin);
291 AG_UnlockVariable(bMax);
292 AG_UnlockVariable(bInc);
293 AG_UnlockVariable(bVis);
294 AG_Redraw(sb);
295 return (rv);
296 }
297 static int
Decrement(AG_Scrollbar * sb)298 Decrement(AG_Scrollbar *sb)
299 {
300 AG_Variable *bVal, *bMin, *bMax, *bInc, *bVis;
301 void *pVal, *pMin, *pMax, *pInc, *pVis;
302 int rv = 0;
303
304 bVal = AG_GetVariable(sb, "value", &pVal);
305 bMin = AG_GetVariable(sb, "min", &pMin);
306 bMax = AG_GetVariable(sb, "max", &pMax);
307 bInc = AG_GetVariable(sb, "inc", &pInc);
308 bVis = AG_GetVariable(sb, "visible", &pVis);
309
310 switch (AG_VARIABLE_TYPE(bVal)) {
311 case AG_VARIABLE_INT: DECREMENT(int); break;
312 case AG_VARIABLE_UINT: DECREMENT(Uint); break;
313 case AG_VARIABLE_FLOAT: DECREMENT(float); break;
314 case AG_VARIABLE_DOUBLE: DECREMENT(double); break;
315 #ifdef HAVE_LONG_DOUBLE
316 case AG_VARIABLE_LONG_DOUBLE: DECREMENT(long double); break;
317 #endif
318 case AG_VARIABLE_UINT8: DECREMENT(Uint8); break;
319 case AG_VARIABLE_SINT8: DECREMENT(Sint8); break;
320 case AG_VARIABLE_UINT16: DECREMENT(Uint16); break;
321 case AG_VARIABLE_SINT16: DECREMENT(Sint16); break;
322 case AG_VARIABLE_UINT32: DECREMENT(Uint32); break;
323 case AG_VARIABLE_SINT32: DECREMENT(Sint32); break;
324 #ifdef HAVE_64BIT
325 case AG_VARIABLE_UINT64: DECREMENT(Uint64); break;
326 case AG_VARIABLE_SINT64: DECREMENT(Sint64); break;
327 #endif
328 default: break;
329 }
330
331 AG_PostEvent(NULL, sb, "scrollbar-changed", NULL);
332 AG_UnlockVariable(bVal);
333 AG_UnlockVariable(bMin);
334 AG_UnlockVariable(bMax);
335 AG_UnlockVariable(bInc);
336 AG_UnlockVariable(bVis);
337 AG_Redraw(sb);
338 return (rv);
339 }
340 #undef INCREMENT
341 #undef DECREMENT
342
343 static void
MouseButtonUp(AG_Event * event)344 MouseButtonUp(AG_Event *event)
345 {
346 AG_Scrollbar *sb = AG_SELF();
347
348 AG_DelTimer(sb, &sb->moveTo);
349
350 if (sb->curBtn == AG_SCROLLBAR_BUTTON_DEC && sb->buttonDecFn != NULL) {
351 AG_PostEventByPtr(NULL, sb, sb->buttonDecFn, "%i", 0);
352 }
353 if (sb->curBtn == AG_SCROLLBAR_BUTTON_INC && sb->buttonIncFn != NULL) {
354 AG_PostEventByPtr(NULL, sb, sb->buttonIncFn, "%i", 0);
355 }
356
357 if (sb->curBtn != AG_SCROLLBAR_BUTTON_NONE) {
358 sb->curBtn = AG_SCROLLBAR_BUTTON_NONE;
359 sb->xOffs = 0;
360 }
361 AG_PostEvent(NULL, sb, "scrollbar-drag-end", NULL);
362 AG_Redraw(sb);
363 }
364
365 /* Timer for scrolling controlled by buttons (mouse spin setting). */
366 static Uint32
MoveButtonsTimeout(AG_Timer * to,AG_Event * event)367 MoveButtonsTimeout(AG_Timer *to, AG_Event *event)
368 {
369 AG_Scrollbar *sb = AG_SELF();
370 int dir = AG_INT(1);
371 int rv;
372
373 if (dir == -1) {
374 rv = Decrement(sb);
375 } else {
376 rv = Increment(sb);
377 }
378 if (sb->xSeek != -1) {
379 int pos, len;
380
381 if (GetPxCoords(sb, &pos, &len) == -1 ||
382 ((dir == -1 && sb->xSeek >= pos) ||
383 (dir == +1 && sb->xSeek <= pos+len))) {
384 sb->curBtn = AG_SCROLLBAR_BUTTON_NONE;
385 sb->xSeek = -1;
386 return (0);
387 }
388 }
389 return (rv != 1) ? agMouseScrollIval : 0;
390 }
391
392 /* Timer for scrolling controlled by keyboard (keyrepeat setting). */
393 static Uint32
MoveKbdTimeout(AG_Timer * to,AG_Event * event)394 MoveKbdTimeout(AG_Timer *to, AG_Event *event)
395 {
396 AG_Scrollbar *sb = AG_SELF();
397 int dir = AG_INT(1);
398 int rv;
399
400 if (dir == -1) {
401 rv = Decrement(sb);
402 } else {
403 rv = Increment(sb);
404 }
405 return (rv != 1) ? agKbdRepeat : 0;
406 }
407
408 static void
MouseButtonDown(AG_Event * event)409 MouseButtonDown(AG_Event *event)
410 {
411 AG_Scrollbar *sb = AG_SELF();
412 int button = AG_INT(1);
413 int x = SBPOS(sb, AG_INT(2), AG_INT(3)) - sb->width;
414 int totsize = SBLEN(sb);
415
416 if (button != AG_MOUSE_LEFT) {
417 return;
418 }
419 if (!AG_WidgetIsFocused(sb)) {
420 AG_WidgetFocus(sb);
421 }
422 if (x < 0) { /* Decrement */
423 sb->curBtn = AG_SCROLLBAR_BUTTON_DEC;
424 if (sb->buttonDecFn != NULL) {
425 AG_PostEventByPtr(NULL, sb, sb->buttonDecFn, "%i", 1);
426 } else {
427 if (Decrement(sb) != 1) {
428 sb->xSeek = -1;
429 AG_AddTimer(sb, &sb->moveTo, agMouseScrollDelay,
430 MoveButtonsTimeout, "%i", -1);
431 }
432 }
433 } else if (x > totsize - sb->width*2) { /* Increment */
434 sb->curBtn = AG_SCROLLBAR_BUTTON_INC;
435 if (sb->buttonIncFn != NULL) {
436 AG_PostEventByPtr(NULL, sb, sb->buttonIncFn, "%i", 1);
437 } else {
438 if (Increment(sb) != 1) {
439 sb->xSeek = -1;
440 AG_AddTimer(sb, &sb->moveTo, agMouseScrollDelay,
441 MoveButtonsTimeout, "%i", +1);
442 }
443 }
444 } else {
445 int pos, len;
446
447 if (GetPxCoords(sb, &pos, &len) == -1) { /* No range */
448 sb->curBtn = AG_SCROLLBAR_BUTTON_SCROLL;
449 sb->xOffs = x;
450 } else if (x >= pos && x <= pos+len) {
451 sb->curBtn = AG_SCROLLBAR_BUTTON_SCROLL;
452 sb->xOffs = (x - pos);
453 } else {
454 if (x < pos) {
455 sb->curBtn = AG_SCROLLBAR_BUTTON_DEC;
456 if (Decrement(sb) != 1) {
457 sb->xSeek = x;
458 AG_AddTimer(sb, &sb->moveTo, agMouseScrollDelay,
459 MoveButtonsTimeout, "%i,", -1);
460 }
461 } else {
462 sb->curBtn = AG_SCROLLBAR_BUTTON_INC;
463 if (Increment(sb) != 1) {
464 sb->xSeek = x;
465 AG_AddTimer(sb, &sb->moveTo, agMouseScrollDelay,
466 MoveButtonsTimeout, "%i", +1);
467 }
468 }
469 }
470 }
471 AG_PostEvent(NULL, sb, "scrollbar-drag-begin", NULL);
472 AG_Redraw(sb);
473 }
474
475 static void
MouseMotion(AG_Event * event)476 MouseMotion(AG_Event *event)
477 {
478 AG_Scrollbar *sb = AG_SELF();
479 int mx = AG_INT(1);
480 int my = AG_INT(2);
481 int x = SBPOS(sb,mx,my) - sb->width;
482 enum ag_scrollbar_button mouseOverBtn;
483
484 if (sb->curBtn == AG_SCROLLBAR_BUTTON_SCROLL) {
485 SeekToPxCoords(sb, x - sb->xOffs);
486 } else if (AG_WidgetRelativeArea(sb, mx,my)) {
487 if (x < 0) {
488 mouseOverBtn = AG_SCROLLBAR_BUTTON_DEC;
489 } else if (x > SBLEN(sb) - sb->width*2) {
490 mouseOverBtn = AG_SCROLLBAR_BUTTON_INC;
491 } else {
492 int pos, len;
493
494 if (GetPxCoords(sb, &pos, &len) == -1 || /* No range */
495 (x >= pos && x <= pos+len)) {
496 mouseOverBtn = AG_SCROLLBAR_BUTTON_SCROLL;
497 } else {
498 mouseOverBtn = AG_SCROLLBAR_BUTTON_NONE;
499 if (sb->xSeek != -1)
500 sb->xSeek = x;
501 }
502 }
503 if (mouseOverBtn != sb->mouseOverBtn) {
504 sb->mouseOverBtn = mouseOverBtn;
505 AG_Redraw(sb);
506 }
507 } else {
508 if (sb->mouseOverBtn != AG_SCROLLBAR_BUTTON_NONE) {
509 sb->mouseOverBtn = AG_SCROLLBAR_BUTTON_NONE;
510 AG_Redraw(sb);
511 }
512 }
513
514 }
515
516 static void
KeyDown(AG_Event * event)517 KeyDown(AG_Event *event)
518 {
519 AG_Scrollbar *sb = AG_SELF();
520 int keysym = AG_INT(1);
521
522 switch (keysym) {
523 case AG_KEY_UP:
524 case AG_KEY_LEFT:
525 if (Decrement(sb) != 1) {
526 AG_AddTimer(sb, &sb->moveTo, agKbdDelay,
527 MoveKbdTimeout, "%i", -1);
528 }
529 break;
530 case AG_KEY_DOWN:
531 case AG_KEY_RIGHT:
532 if (Increment(sb) != 1) {
533 AG_AddTimer(sb, &sb->moveTo, agKbdDelay,
534 MoveKbdTimeout, "%i", +1);
535 }
536 break;
537 }
538 }
539
540 static void
KeyUp(AG_Event * event)541 KeyUp(AG_Event *event)
542 {
543 AG_Scrollbar *sb = AG_SELF();
544 int keysym = AG_INT(1);
545
546 switch (keysym) {
547 case AG_KEY_UP:
548 case AG_KEY_LEFT:
549 case AG_KEY_DOWN:
550 case AG_KEY_RIGHT:
551 AG_DelTimer(sb, &sb->moveTo);
552 break;
553 }
554 }
555
556 /* Timer for AUTOHIDE visibility test. */
557 static Uint32
AutoHideTimeout(AG_Timer * to,AG_Event * event)558 AutoHideTimeout(AG_Timer *to, AG_Event *event)
559 {
560 AG_Scrollbar *sb = AG_SELF();
561 int rv, x, len;
562
563 if (WIDGET(sb)->window != NULL &&
564 WIDGET(sb)->window->visible == 0)
565 return (to->ival);
566
567 rv = GetPxCoords(sb, &x, &len);
568 if (rv == -1 || len == sb->length) {
569 if (AG_WidgetVisible(sb))
570 AG_WidgetHide(sb);
571 } else {
572 if (!AG_WidgetVisible(sb)) {
573 AG_WidgetShow(sb);
574 AG_Redraw(sb);
575 }
576 }
577 return (to->ival);
578 }
579
580 static void
OnFocusLoss(AG_Event * event)581 OnFocusLoss(AG_Event *event)
582 {
583 AG_Scrollbar *sb = AG_SELF();
584
585 AG_DelTimer(sb, &sb->moveTo);
586 }
587
588 #undef SET_DEF
589 #define SET_DEF(fn,dmin,dmax,dinc) { \
590 if (!AG_Defined(sb, "min")) { fn(sb, "min", dmin); } \
591 if (!AG_Defined(sb, "max")) { fn(sb, "max", dmax); } \
592 if (!AG_Defined(sb, "inc")) { fn(sb, "inc", dinc); } \
593 if (!AG_Defined(sb, "visible")) { fn(sb, "visible", 0); } \
594 }
595 static void
OnShow(AG_Event * event)596 OnShow(AG_Event *event)
597 {
598 AG_Scrollbar *sb = AG_SELF();
599 AG_Variable *V;
600
601 if ((V = AG_GetVariable(sb, "value", NULL)) == NULL) {
602 V = AG_SetInt(sb, "value", 0);
603 AG_LockVariable(V);
604 }
605 switch (AG_VARIABLE_TYPE(V)) {
606 case AG_VARIABLE_FLOAT: SET_DEF(AG_SetFloat, 0.0f, 1.0f, 0.1f); break;
607 case AG_VARIABLE_DOUBLE: SET_DEF(AG_SetDouble, 0.0, 1.0, 0.1); break;
608 #ifdef HAVE_LONG_DOUBLE
609 case AG_VARIABLE_LONG_DOUBLE: SET_DEF(AG_SetLongDouble, 0.0l, 1.0l, 0.1l); break;
610 #endif
611 case AG_VARIABLE_INT: SET_DEF(AG_SetInt, 0, AG_INT_MAX-1, 1); break;
612 case AG_VARIABLE_UINT: SET_DEF(AG_SetUint, 0U, AG_UINT_MAX-1, 1U); break;
613 case AG_VARIABLE_UINT8: SET_DEF(AG_SetUint8, 0U, 0xffU, 1U); break;
614 case AG_VARIABLE_SINT8: SET_DEF(AG_SetSint8, 0, 0x7f, 1); break;
615 case AG_VARIABLE_UINT16: SET_DEF(AG_SetUint16, 0U, 0xffffU, 1U); break;
616 case AG_VARIABLE_SINT16: SET_DEF(AG_SetSint16, 0, 0x7fff, 1); break;
617 case AG_VARIABLE_UINT32: SET_DEF(AG_SetUint32, 0UL, 0xffffffffUL, 1UL); break;
618 case AG_VARIABLE_SINT32: SET_DEF(AG_SetSint32, 0L, 0x7fffffffL, 1L); break;
619 #ifdef HAVE_64BIT
620 case AG_VARIABLE_UINT64: SET_DEF(AG_SetUint64, 0ULL, 0xffffffffffffffffULL, 1ULL); break;
621 case AG_VARIABLE_SINT64: SET_DEF(AG_SetSint64, 0LL, 0x7fffffffffffffffLL, 1LL); break;
622 #endif
623 default: break;
624 }
625 AG_UnlockVariable(V);
626
627 if ((sb->flags & AG_SCROLLBAR_EXCL) == 0) {
628 /* Trigger redraw upon external changes to the bindings. */
629 AG_RedrawOnChange(sb, 500, "value");
630 AG_RedrawOnChange(sb, 500, "min");
631 AG_RedrawOnChange(sb, 500, "max");
632 AG_RedrawOnChange(sb, 500, "visible");
633 }
634 if (sb->flags & AG_SCROLLBAR_AUTOHIDE)
635 AG_AddTimer(sb, &sb->autoHideTo, 1000, AutoHideTimeout, NULL);
636 }
637 #undef SET_DEF
638
639 static void
OnHide(AG_Event * event)640 OnHide(AG_Event *event)
641 {
642 AG_Scrollbar *sb = AG_SELF();
643
644 AG_DelTimer(sb, &sb->moveTo);
645 }
646
647 static void
OnDetach(AG_Event * event)648 OnDetach(AG_Event *event)
649 {
650 AG_Scrollbar *sb = AG_SELF();
651
652 if (sb->flags & AG_SCROLLBAR_AUTOHIDE)
653 AG_DelTimer(sb, &sb->autoHideTo);
654 }
655
656 static void
Init(void * obj)657 Init(void *obj)
658 {
659 AG_Scrollbar *sb = obj;
660
661 WIDGET(sb)->flags |= AG_WIDGET_UNFOCUSED_BUTTONUP|
662 AG_WIDGET_UNFOCUSED_MOTION|
663 AG_WIDGET_FOCUSABLE;
664
665 sb->type = AG_SCROLLBAR_HORIZ;
666 sb->curBtn = AG_SCROLLBAR_BUTTON_NONE;
667 sb->mouseOverBtn = AG_SCROLLBAR_BUTTON_NONE;
668 sb->flags = AG_SCROLLBAR_AUTOHIDE;
669 sb->buttonIncFn = NULL;
670 sb->buttonDecFn = NULL;
671 sb->xOffs = 0;
672 sb->xSeek = -1;
673 sb->length = 0;
674 sb->lenPre = 32;
675
676 sb->wBar = agTextFontHeight/2;
677 sb->wBarMin = 16;
678 sb->width = agTextFontHeight;
679 sb->hArrow = sb->width*5/9;
680
681 AG_InitTimer(&sb->moveTo, "move", 0);
682 AG_InitTimer(&sb->autoHideTo, "autoHide", 0);
683
684 AG_AddEvent(sb, "widget-shown", OnShow, NULL);
685 AG_AddEvent(sb, "widget-hidden", OnHide, NULL);
686 AG_AddEvent(sb, "detached", OnDetach, NULL);
687 AG_SetEvent(sb, "widget-lostfocus", OnFocusLoss, NULL);
688 AG_SetEvent(sb, "mouse-button-down", MouseButtonDown, NULL);
689 AG_SetEvent(sb, "mouse-button-up", MouseButtonUp, NULL);
690 AG_SetEvent(sb, "mouse-motion", MouseMotion, NULL);
691 AG_SetEvent(sb, "key-down", KeyDown, NULL);
692 AG_SetEvent(sb, "key-up", KeyUp, NULL);
693
694 #ifdef AG_DEBUG
695 AG_BindInt(sb, "width", &sb->width);
696 AG_BindInt(sb, "length", &sb->length);
697 AG_BindInt(sb, "wBar", &sb->wBar);
698 AG_BindInt(sb, "xOffs", &sb->xOffs);
699 #endif /* AG_DEBUG */
700 }
701
702 static void
SizeRequest(void * obj,AG_SizeReq * r)703 SizeRequest(void *obj, AG_SizeReq *r)
704 {
705 AG_Scrollbar *sb = obj;
706
707 switch (sb->type) {
708 case AG_SCROLLBAR_HORIZ:
709 r->w = sb->width*2 + sb->lenPre;
710 r->h = sb->width;
711 break;
712 case AG_SCROLLBAR_VERT:
713 r->w = sb->width;
714 r->h = sb->width*2 + sb->lenPre;
715 break;
716 }
717 }
718
719 static int
SizeAllocate(void * obj,const AG_SizeAlloc * a)720 SizeAllocate(void *obj, const AG_SizeAlloc *a)
721 {
722 AG_Scrollbar *sb = obj;
723
724 if (a->w < 4 || a->h < 4) {
725 return (-1);
726 }
727 sb->length = ((sb->type==AG_SCROLLBAR_VERT) ? a->h:a->w) - sb->width*2;
728 if (sb->length < 0) { sb->length = 0; }
729 return (0);
730 }
731
732 static void
DrawText(AG_Scrollbar * sb)733 DrawText(AG_Scrollbar *sb)
734 {
735 AG_Driver *drv = WIDGET(sb)->drv;
736 AG_Surface *txt;
737 char label[32];
738 AG_Rect r;
739
740 AG_PushTextState();
741 AG_TextColor(WCOLOR(sb,TEXT_COLOR));
742
743 Snprintf(label, sizeof(label),
744 (sb->type == AG_SCROLLBAR_HORIZ) ?
745 "%d|%d|%d|%d" : "%d\n:%d\n%d\nv%d\n",
746 AG_GetInt(sb,"min"),
747 AG_GetInt(sb,"value"),
748 AG_GetInt(sb,"max"),
749 AG_GetInt(sb,"visible"));
750
751 txt = AG_TextRender(label); /* XXX inefficient */
752 r = AG_RECT(
753 WIDTH(sb)/2 - txt->w/2,
754 HEIGHT(sb)/2 - txt->h/2,
755 txt->w,
756 txt->h);
757 AG_WidgetBlit(sb, txt, r.x, r.y);
758 AG_SurfaceFree(txt);
759
760 AG_RectTranslate(&r, WIDGET(sb)->rView.x1, WIDGET(sb)->rView.y1);
761
762 if (AGDRIVER_CLASS(drv)->updateRegion != NULL) {
763 AGDRIVER_CLASS(drv)->updateRegion(drv, r);
764 }
765 AG_PopTextState();
766 }
767
768 static void
DrawVertUndersize(AG_Scrollbar * sb)769 DrawVertUndersize(AG_Scrollbar *sb)
770 {
771 int w = WIDTH(sb)/2;
772 int size = MIN(HEIGHT(sb)/4, WIDTH(sb));
773
774 AG_DrawBox(sb, AG_RECT(0,0,WIDTH(sb),HEIGHT(sb)), 1, WCOLOR(sb,0));
775 AG_DrawArrowUp(sb,
776 w,
777 size,
778 size,
779 WCOLOR(sb,SHAPE_COLOR), WCOLOR(sb,SHAPE_COLOR));
780 AG_DrawArrowDown(sb,
781 w,
782 HEIGHT(sb)/2 + size,
783 size,
784 WCOLOR(sb,SHAPE_COLOR), WCOLOR(sb,SHAPE_COLOR));
785 }
786 static void
DrawVert(AG_Scrollbar * sb,int y,int h)787 DrawVert(AG_Scrollbar *sb, int y, int h)
788 {
789 int mid = WIDTH(sb)/2;
790 int b2 = sb->width*2;
791 int hArrow = MIN(WIDTH(sb), sb->hArrow);
792 int y2;
793
794 if (HEIGHT(sb) < b2) {
795 DrawVertUndersize(sb);
796 return;
797 }
798
799 /* Background */
800 AG_DrawBox(sb,
801 AG_RECT(0, 0, WIDTH(sb), HEIGHT(sb)), -1,
802 WCOLOR(sb,0));
803
804 /* Upper button. */
805 AG_DrawBox(sb,
806 AG_RECT(0, 0, WIDTH(sb), sb->width),
807 (sb->curBtn == AG_SCROLLBAR_BUTTON_DEC) ? -1 : 1,
808 (sb->mouseOverBtn == AG_SCROLLBAR_BUTTON_DEC) ?
809 WCOLOR_HOV(sb,0) : WCOLOR(sb,0));
810 AG_DrawArrowUp(sb,
811 mid,
812 sb->width/2,
813 hArrow,
814 WCOLOR(sb,SHAPE_COLOR), WCOLOR(sb,SHAPE_COLOR));
815
816 /* Lower button. */
817 y2 = HEIGHT(sb) - sb->width;
818 AG_DrawBox(sb,
819 AG_RECT(0, y2, WIDTH(sb), sb->width),
820 (sb->curBtn == AG_SCROLLBAR_BUTTON_INC) ? -1 : 1,
821 (sb->mouseOverBtn == AG_SCROLLBAR_BUTTON_INC) ?
822 WCOLOR_HOV(sb,0) : WCOLOR(sb,0));
823 AG_DrawArrowDown(sb,
824 mid,
825 y2 + sb->width/2,
826 hArrow,
827 WCOLOR(sb,SHAPE_COLOR), WCOLOR(sb,SHAPE_COLOR));
828
829 /* Scrollbar. */
830 if (h > 0) {
831 AG_DrawBox(sb,
832 AG_RECT(0,
833 sb->width + y,
834 WIDTH(sb),
835 MIN(h, HEIGHT(sb)-b2)),
836 (sb->curBtn == AG_SCROLLBAR_BUTTON_SCROLL) ? -1 : 1,
837 (sb->mouseOverBtn == AG_SCROLLBAR_BUTTON_SCROLL) ?
838 WCOLOR_HOV(sb,0) : WCOLOR(sb,0));
839 } else {
840 AG_DrawBox(sb,
841 AG_RECT(0,
842 sb->width,
843 WIDTH(sb),
844 HEIGHT(sb)-b2),
845 (sb->curBtn == AG_SCROLLBAR_BUTTON_SCROLL) ? -1 : 1,
846 (sb->mouseOverBtn == AG_SCROLLBAR_BUTTON_SCROLL) ?
847 WCOLOR_HOV(sb,0) : WCOLOR(sb,0));
848 }
849 }
850 static void
DrawHorizUndersize(AG_Scrollbar * sb)851 DrawHorizUndersize(AG_Scrollbar *sb)
852 {
853 int h = HEIGHT(sb)/2;
854 int size = MIN(WIDTH(sb)/4, HEIGHT(sb));
855
856 AG_DrawBox(sb, AG_RECT(0,0,WIDTH(sb),HEIGHT(sb)), 1,
857 WCOLOR(sb,0));
858 AG_DrawArrowLeft(sb,
859 size,
860 h,
861 size,
862 WCOLOR(sb,SHAPE_COLOR), WCOLOR(sb,SHAPE_COLOR));
863 AG_DrawArrowRight(sb,
864 WIDTH(sb)/2 + size,
865 h,
866 size,
867 WCOLOR(sb,SHAPE_COLOR), WCOLOR(sb,SHAPE_COLOR));
868 }
869 static void
DrawHoriz(AG_Scrollbar * sb,int x,int w)870 DrawHoriz(AG_Scrollbar *sb, int x, int w)
871 {
872 int mid = HEIGHT(sb)/2;
873 int b2 = sb->width*2;
874 int hArrow = MIN(HEIGHT(sb), sb->hArrow);
875 int x2;
876
877 if (WIDTH(sb) < b2) {
878 DrawHorizUndersize(sb);
879 return;
880 }
881
882 /* Background */
883 AG_DrawBox(sb,
884 AG_RECT(0, 0, WIDTH(sb), HEIGHT(sb)), -1,
885 WCOLOR(sb,0));
886
887 /* Left button */
888 AG_DrawBox(sb,
889 AG_RECT(0, 0, sb->width, HEIGHT(sb)),
890 (sb->curBtn == AG_SCROLLBAR_BUTTON_DEC) ? -1 : 1,
891 WCOLOR(sb,0));
892 AG_DrawArrowLeft(sb,
893 sb->width/2, mid,
894 hArrow,
895 WCOLOR(sb,SHAPE_COLOR), WCOLOR(sb,SHAPE_COLOR));
896
897 /* Right button */
898 x2 = WIDTH(sb) - sb->width;
899 AG_DrawBox(sb,
900 AG_RECT(x2, 0, sb->width, HEIGHT(sb)),
901 (sb->curBtn == AG_SCROLLBAR_BUTTON_INC) ? -1 : 1,
902 WCOLOR(sb,0));
903 AG_DrawArrowRight(sb, (x2 + sb->width/2), mid, hArrow,
904 WCOLOR(sb,SHAPE_COLOR), WCOLOR(sb,SHAPE_COLOR));
905
906 /* Scrollbar */
907 if (w > 0) {
908 AG_DrawBox(sb,
909 AG_RECT(sb->width + x,
910 0,
911 MIN(w, WIDTH(sb)-b2),
912 HEIGHT(sb)),
913 (sb->curBtn == AG_SCROLLBAR_BUTTON_SCROLL) ? -1 : 1,
914 WCOLOR(sb,0));
915 } else {
916 AG_DrawBox(sb,
917 AG_RECT(sb->width,
918 0,
919 WIDTH(sb)-b2,
920 HEIGHT(sb)),
921 (sb->curBtn == AG_SCROLLBAR_BUTTON_SCROLL) ? -1 : 1,
922 WCOLOR(sb,0));
923 }
924 }
925
926 static void
Draw(void * obj)927 Draw(void *obj)
928 {
929 AG_Scrollbar *sb = obj;
930 int x, len;
931
932 if (GetPxCoords(sb, &x, &len) == -1) { /* No range */
933 x = 0;
934 len = sb->length;
935 }
936 if (sb->flags & AG_SCROLLBAR_AUTOHIDE && len == sb->length)
937 return;
938
939 switch (sb->type) {
940 case AG_SCROLLBAR_VERT:
941 DrawVert(sb, x, len);
942 break;
943 case AG_SCROLLBAR_HORIZ:
944 DrawHoriz(sb, x, len);
945 break;
946 }
947 if (sb->flags & AG_SCROLLBAR_TEXT)
948 DrawText(sb);
949 }
950
951 /* Return 1 if it is useful to display the scrollbar given the current range. */
952 int
AG_ScrollbarVisible(AG_Scrollbar * sb)953 AG_ScrollbarVisible(AG_Scrollbar *sb)
954 {
955 int rv, x, len;
956
957 AG_ObjectLock(sb);
958 if (!AG_Defined(sb, "value") ||
959 !AG_Defined(sb, "min") ||
960 !AG_Defined(sb, "max") ||
961 !AG_Defined(sb, "visible")) {
962 AG_ObjectUnlock(sb);
963 return (1);
964 }
965 rv = (GetPxCoords(sb, &x, &len) == -1) ? 0 : 1;
966 AG_ObjectUnlock(sb);
967 return (rv);
968 }
969
970 #ifdef AG_LEGACY
971 int
AG_ScrollbarControlLength(AG_Scrollbar * sb)972 AG_ScrollbarControlLength(AG_Scrollbar *sb)
973 {
974 int rv;
975
976 if (sb->wBar == -1) {
977 rv = (sb->type == AG_SCROLLBAR_VERT) ? AGWIDGET(sb)->h :
978 AGWIDGET(sb)->w;
979 rv -= sb->width*2;
980 if (rv < sb->wBarMin) { rv = sb->wBarMin; }
981 } else {
982 rv = MAX(sb->wBar, sb->wBarMin);
983 }
984 return (rv);
985 }
986
987 void
AG_ScrollbarSetIntIncrement(AG_Scrollbar * sb,int inc)988 AG_ScrollbarSetIntIncrement(AG_Scrollbar *sb, int inc)
989 {
990 AG_ObjectLock(sb);
991 AG_SetInt(sb, "inc", inc);
992 AG_ObjectUnlock(sb);
993 }
994 void
AG_ScrollbarSetRealIncrement(AG_Scrollbar * sb,double inc)995 AG_ScrollbarSetRealIncrement(AG_Scrollbar *sb, double inc)
996 {
997 AG_ObjectLock(sb);
998 AG_SetDouble(sb, "inc", inc);
999 AG_ObjectUnlock(sb);
1000 }
1001 #endif /* AG_LEGACY */
1002
1003 AG_WidgetClass agScrollbarClass = {
1004 {
1005 "Agar(Widget:Scrollbar)",
1006 sizeof(AG_Scrollbar),
1007 { 0,0 },
1008 Init,
1009 NULL, /* free */
1010 NULL, /* destroy */
1011 NULL, /* load */
1012 NULL, /* save */
1013 NULL /* edit */
1014 },
1015 Draw,
1016 SizeRequest,
1017 SizeAllocate
1018 };
1019