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