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 
28 #include <agar/gui/button.h>
29 #include <agar/gui/primitive.h>
30 #include <agar/gui/window.h>
31 
32 #include <stdarg.h>
33 
34 static int  GetState(AG_Button *, AG_Variable *, void *);
35 static void SetState(AG_Button *, AG_Variable *, void *, int);
36 
37 AG_Button *
AG_ButtonNew(void * parent,Uint flags,const char * fmt,...)38 AG_ButtonNew(void *parent, Uint flags, const char *fmt, ...)
39 {
40 	AG_Button *bu;
41 	va_list ap;
42 	char *s;
43 
44 	if (fmt != NULL) {
45 		va_start(ap, fmt);
46 		Vasprintf(&s, fmt, ap);
47 		va_end(ap);
48 	} else {
49 		s = NULL;
50 	}
51 	bu = AG_ButtonNewS(parent, flags, s);
52 	Free(s);
53 	return (bu);
54 }
55 
56 AG_Button *
AG_ButtonNewS(void * parent,Uint flags,const char * label)57 AG_ButtonNewS(void *parent, Uint flags, const char *label)
58 {
59 	AG_Button *bu;
60 
61 	bu = Malloc(sizeof(AG_Button));
62 	AG_ObjectInit(bu, &agButtonClass);
63 
64 	if (label != NULL) {
65 		bu->lbl = AG_LabelNewS(bu, 0, label);
66 		AG_LabelJustify(bu->lbl, bu->justify);
67 		AG_LabelValign(bu->lbl, bu->valign);
68 	}
69 	bu->flags |= flags;
70 
71 	if (flags & AG_BUTTON_HFILL) { AG_ExpandHoriz(bu); }
72 	if (flags & AG_BUTTON_VFILL) { AG_ExpandVert(bu); }
73 
74 	AG_ObjectAttach(parent, bu);
75 	return (bu);
76 }
77 
78 AG_Button *
AG_ButtonNewFn(void * parent,Uint flags,const char * caption,AG_EventFn fn,const char * fmt,...)79 AG_ButtonNewFn(void *parent, Uint flags, const char *caption, AG_EventFn fn,
80     const char *fmt, ...)
81 {
82 	AG_Button *bu;
83 	AG_Event *ev;
84 
85 	if (!(flags & AG_BUTTON_NOEXCL)) { flags |= AG_BUTTON_EXCL;  }
86 	bu = AG_ButtonNewS(parent, flags, caption);
87 	ev = AG_SetEvent(bu, "button-pushed", fn, NULL);
88 	AG_EVENT_GET_ARGS(ev, fmt);
89 	return (bu);
90 }
91 
92 AG_Button *
AG_ButtonNewInt(void * parent,Uint flags,const char * caption,int * v)93 AG_ButtonNewInt(void *parent, Uint flags, const char *caption, int *v)
94 {
95 	AG_Button *bu = AG_ButtonNewS(parent, flags, caption);
96 	AG_BindInt(bu, "state", v);
97 	return (bu);
98 }
99 
100 AG_Button *
AG_ButtonNewUint8(void * parent,Uint flags,const char * caption,Uint8 * v)101 AG_ButtonNewUint8(void *parent, Uint flags, const char *caption, Uint8 *v)
102 {
103 	AG_Button *bu = AG_ButtonNewS(parent, flags, caption);
104 	AG_BindUint8(bu, "state", v);
105 	return (bu);
106 }
107 
108 AG_Button *
AG_ButtonNewUint16(void * parent,Uint flags,const char * caption,Uint16 * v)109 AG_ButtonNewUint16(void *parent, Uint flags, const char *caption, Uint16 *v)
110 {
111 	AG_Button *bu = AG_ButtonNewS(parent, flags, caption);
112 	AG_BindUint16(bu, "state", v);
113 	return (bu);
114 }
115 
116 AG_Button *
AG_ButtonNewUint32(void * parent,Uint flags,const char * caption,Uint32 * v)117 AG_ButtonNewUint32(void *parent, Uint flags, const char *caption, Uint32 *v)
118 {
119 	AG_Button *bu = AG_ButtonNewS(parent, flags, caption);
120 	AG_BindUint32(bu, "state", v);
121 	return (bu);
122 }
123 
124 AG_Button *
AG_ButtonNewFlag(void * parent,Uint flags,const char * caption,Uint * p,Uint bitmask)125 AG_ButtonNewFlag(void *parent, Uint flags, const char *caption,
126     Uint *p, Uint bitmask)
127 {
128 	AG_Button *bu = AG_ButtonNewS(parent, flags, caption);
129 	AG_BindFlag(bu, "state", p, bitmask);
130 	return (bu);
131 }
132 
133 AG_Button *
AG_ButtonNewFlag8(void * parent,Uint flags,const char * caption,Uint8 * p,Uint8 bitmask)134 AG_ButtonNewFlag8(void *parent, Uint flags, const char *caption,
135    Uint8 *p, Uint8 bitmask)
136 {
137 	AG_Button *bu = AG_ButtonNewS(parent, flags, caption);
138 	AG_BindFlag8(bu, "state", p, bitmask);
139 	return (bu);
140 }
141 
142 AG_Button *
AG_ButtonNewFlag16(void * parent,Uint flags,const char * caption,Uint16 * p,Uint16 bitmask)143 AG_ButtonNewFlag16(void *parent, Uint flags, const char *caption,
144     Uint16 *p, Uint16 bitmask)
145 {
146 	AG_Button *bu = AG_ButtonNewS(parent, flags, caption);
147 	AG_BindFlag16(bu, "state", p, bitmask);
148 	return (bu);
149 }
150 
151 AG_Button *
AG_ButtonNewFlag32(void * parent,Uint flags,const char * caption,Uint32 * p,Uint32 bitmask)152 AG_ButtonNewFlag32(void *parent, Uint flags, const char *caption,
153     Uint32 *p, Uint32 bitmask)
154 {
155 	AG_Button *bu = AG_ButtonNewS(parent, flags, caption);
156 	AG_BindFlag32(bu, "state", p, bitmask);
157 	return (bu);
158 }
159 
160 /* Delay/repeat timer callbacks for AG_BUTTON_REPEAT */
161 static Uint32
ExpireRepeat(AG_Timer * to,AG_Event * event)162 ExpireRepeat(AG_Timer *to, AG_Event *event)
163 {
164 	AG_Button *bu = AG_SELF();
165 
166 	AG_PostEvent(NULL, bu, "button-pushed", "%i", 1);
167 	return (to->ival);
168 }
169 static Uint32
ExpireDelay(AG_Timer * to,AG_Event * event)170 ExpireDelay(AG_Timer *to, AG_Event *event)
171 {
172 	AG_Button *bu = AG_SELF();
173 	int repeatIval = AG_INT(1);
174 
175 	AG_AddTimer(bu, &bu->repeatTo, repeatIval, ExpireRepeat, NULL);
176 	return (0);
177 }
178 
179 static void
MouseButtonUp(AG_Event * event)180 MouseButtonUp(AG_Event *event)
181 {
182 	AG_Button *bu = AG_SELF();
183 	int button = AG_INT(1);
184 	AG_Variable *binding;
185 	void *pState;
186 	int x = AG_INT(2);
187 	int y = AG_INT(3);
188 
189 	if (bu->flags & AG_BUTTON_REPEAT) {
190 		AG_DelTimer(bu, &bu->repeatTo);
191 		AG_DelTimer(bu, &bu->delayTo);
192 		return;
193 	}
194 
195 	if (AG_WidgetDisabled(bu) ||
196 	    x < 0 || y < 0 ||
197 	    x > WIDGET(bu)->w || y > WIDGET(bu)->h) {
198 		return;
199 	}
200 
201 	binding = AG_GetVariable(bu, "state", &pState);
202 	if (GetState(bu, binding, pState) && button == AG_MOUSE_LEFT &&
203 	    !(bu->flags & AG_BUTTON_STICKY)) {
204 	    	SetState(bu, binding, pState, 0);
205 		AG_PostEvent(NULL, bu, "button-pushed", "%i", 0);
206 	}
207 	AG_UnlockVariable(binding);
208 }
209 
210 static void
MouseButtonDown(AG_Event * event)211 MouseButtonDown(AG_Event *event)
212 {
213 	AG_Button *bu = AG_SELF();
214 	int button = AG_INT(1);
215 	AG_Variable *binding;
216 	void *pState;
217 	int newState;
218 
219 	if (AG_WidgetDisabled(bu))
220 		return;
221 
222 	if (!AG_WidgetIsFocused(bu))
223 		AG_WidgetFocus(bu);
224 
225 	if (button != AG_MOUSE_LEFT)
226 		return;
227 
228 	binding = AG_GetVariable(bu, "state", &pState);
229 	if (!(bu->flags & AG_BUTTON_STICKY)) {
230 		SetState(bu, binding, pState, 1);
231 	} else {
232 		newState = !GetState(bu, binding, pState);
233 		SetState(bu, binding, pState, newState);
234 		AG_PostEvent(NULL, bu, "button-pushed", "%i", newState);
235 	}
236 	AG_UnlockVariable(binding);
237 
238 	if (bu->flags & AG_BUTTON_REPEAT) {
239 		AG_DelTimer(bu, &bu->repeatTo);
240 		AG_PostEvent(NULL, bu, "button-pushed", "%i", 1);
241 		AG_AddTimer(bu, &bu->delayTo, agMouseSpinDelay,
242 		    ExpireDelay, "%i", agMouseSpinIval);
243 	}
244 }
245 
246 static void
MouseMotion(AG_Event * event)247 MouseMotion(AG_Event *event)
248 {
249 	AG_Button *bu = AG_SELF();
250 	AG_Variable *binding;
251 	int x = AG_INT(1);
252 	int y = AG_INT(2);
253 	void *pState;
254 
255 	if (AG_WidgetDisabled(bu))
256 		return;
257 
258 	binding = AG_GetVariable(bu, "state", &pState);
259 	if (!AG_WidgetRelativeArea(bu, x, y)) {
260 		if ((bu->flags & AG_BUTTON_STICKY) == 0 &&
261 		    GetState(bu, binding, pState) == 1) {
262 			SetState(bu, binding, pState, 0);
263 		}
264 	}
265 	AG_UnlockVariable(binding);
266 }
267 
268 static void
KeyUp(AG_Event * event)269 KeyUp(AG_Event *event)
270 {
271 	AG_Button *bu = AG_SELF();
272 	AG_Variable *binding;
273 	void *pState;
274 	int keysym = AG_INT(1);
275 
276 	if (AG_WidgetDisabled(bu)) {
277 		return;
278 	}
279 	if (bu->flags & AG_BUTTON_REPEAT) {
280 		AG_DelTimer(bu, &bu->delayTo);
281 		AG_DelTimer(bu, &bu->repeatTo);
282 	}
283 	if (keysym != AG_KEY_RETURN &&		/* TODO AG_Action */
284 	    keysym != AG_KEY_KP_ENTER &&
285 	    keysym != AG_KEY_SPACE) {
286 		return;
287 	}
288 	binding = AG_GetVariable(bu, "state", &pState);
289 	SetState(bu, binding, pState, 0);
290 	AG_UnlockVariable(binding);
291 
292 	if (bu->flags & AG_BUTTON_KEYDOWN) {
293 		bu->flags &= ~(AG_BUTTON_KEYDOWN);
294 		AG_PostEvent(NULL, bu, "button-pushed", "%i", 0);
295 	}
296 }
297 
298 static void
KeyDown(AG_Event * event)299 KeyDown(AG_Event *event)
300 {
301 	AG_Button *bu = AG_SELF();
302 	AG_Variable *binding;
303 	void *pState;
304 	int keysym = AG_INT(1);
305 
306 	if (AG_WidgetDisabled(bu))
307 		return;
308 	if (keysym != AG_KEY_RETURN &&		/* TODO AG_Action */
309 	    keysym != AG_KEY_KP_ENTER &&
310 	    keysym != AG_KEY_SPACE) {
311 		return;
312 	}
313 	binding = AG_GetVariable(bu, "state", &pState);
314 	SetState(bu, binding, pState, 1);
315 	AG_PostEvent(NULL, bu, "button-pushed", "%i", 1);
316 	bu->flags |= AG_BUTTON_KEYDOWN;
317 
318 	if (bu->flags & AG_BUTTON_REPEAT) {
319 		AG_DelTimer(bu, &bu->repeatTo);
320 		AG_AddTimer(bu, &bu->delayTo, agKbdDelay,
321 		    ExpireDelay, "%i", agKbdRepeat);
322 	}
323 	AG_UnlockVariable(binding);
324 }
325 
326 static void
OnShow(AG_Event * event)327 OnShow(AG_Event *event)
328 {
329 	AG_Button *bu = AG_SELF();
330 
331 	if ((bu->flags & AG_BUTTON_EXCL) == 0)
332 		AG_RedrawOnChange(bu, 100, "state");
333 }
334 
335 static void
Init(void * obj)336 Init(void *obj)
337 {
338 	AG_Button *bu = obj;
339 
340 	WIDGET(bu)->flags |= AG_WIDGET_FOCUSABLE|
341 	                     AG_WIDGET_UNFOCUSED_MOTION|
342 			     AG_WIDGET_UNFOCUSED_BUTTONUP|
343 			     AG_WIDGET_TABLE_EMBEDDABLE|
344 			     AG_WIDGET_USE_TEXT|
345 			     AG_WIDGET_USE_MOUSEOVER;
346 
347 	bu->flags = 0;
348 	bu->lbl = NULL;
349 	bu->surface = -1;
350 	bu->state = 0;
351 	bu->justify = AG_TEXT_CENTER;
352 	bu->valign = AG_TEXT_MIDDLE;
353 	bu->lPad = 4;
354 	bu->rPad = 4;
355 	bu->tPad = 3;
356 	bu->bPad = 3;
357 	AG_InitTimer(&bu->delayTo, "delay", 0);
358 	AG_InitTimer(&bu->repeatTo, "repeat", 0);
359 
360 	AG_AddEvent(bu, "widget-shown", OnShow, NULL);
361 	AG_SetEvent(bu, "mouse-button-up", MouseButtonUp, NULL);
362 	AG_SetEvent(bu, "mouse-button-down", MouseButtonDown, NULL);
363 	AG_SetEvent(bu, "mouse-motion", MouseMotion, NULL);
364 	AG_SetEvent(bu, "key-up", KeyUp, NULL);
365 	AG_SetEvent(bu, "key-down", KeyDown, NULL);
366 
367 	AG_BindInt(bu, "state", &bu->state);
368 }
369 
370 static void
SizeRequest(void * p,AG_SizeReq * r)371 SizeRequest(void *p, AG_SizeReq *r)
372 {
373 	AG_Button *bu = p;
374 	AG_SizeReq rLbl;
375 
376 	r->w = bu->lPad + bu->rPad;
377 	r->h = bu->tPad + bu->bPad;
378 
379 	if (bu->surface != -1) {
380 		r->w += WSURFACE(bu,bu->surface)->w;
381 		r->h += WSURFACE(bu,bu->surface)->h;
382 	} else {
383 		if (bu->lbl != NULL) {
384 			AG_WidgetSizeReq(bu->lbl, &rLbl);
385 			r->w += rLbl.w;
386 		}
387 		r->h += WIDGET(bu)->font->height;
388 	}
389 }
390 
391 static int
SizeAllocate(void * p,const AG_SizeAlloc * a)392 SizeAllocate(void *p, const AG_SizeAlloc *a)
393 {
394 	AG_Button *bu = p;
395 	AG_SizeAlloc aLbl;
396 
397 	if (a->w < 2 || a->h < 2) {
398 		return (-1);
399 	}
400 	if (bu->lbl != NULL) {
401 		aLbl.x = bu->lPad;
402 		aLbl.y = bu->tPad;
403 		aLbl.w = a->w - (bu->lPad+bu->rPad);
404 		aLbl.h = a->h - (bu->tPad+bu->bPad);
405 		AG_WidgetSizeAlloc(bu->lbl, &aLbl);
406 	}
407 	return (0);
408 }
409 
410 static int
GetState(AG_Button * bu,AG_Variable * binding,void * p)411 GetState(AG_Button *bu, AG_Variable *binding, void *p)
412 {
413 	int v;
414 
415 	switch (AG_VARIABLE_TYPE(binding)) {
416 	case AG_VARIABLE_INT:
417 		v = *(int *)p;
418 		break;
419 	case AG_VARIABLE_UINT8:
420 		v = (int)(*(Uint8 *)p);
421 		break;
422 	case AG_VARIABLE_UINT16:
423 		v = (int)(*(Uint16 *)p);
424 		break;
425 	case AG_VARIABLE_UINT32:
426 		v = (int)(*(Uint32 *)p);
427 		break;
428 	case AG_VARIABLE_P_FLAG:
429 		v = (*(int *)p & (int)binding->info.bitmask);
430 		break;
431 	case AG_VARIABLE_P_FLAG8:
432 		v = (int)(*(Uint8 *)p & (Uint8)binding->info.bitmask);
433 		break;
434 	case AG_VARIABLE_P_FLAG16:
435 		v = (int)(*(Uint16 *)p & (Uint16)binding->info.bitmask);
436 		break;
437 	case AG_VARIABLE_P_FLAG32:
438 		v = (int)(*(Uint32 *)p & (Uint32)binding->info.bitmask);
439 		break;
440 	default:
441 		v = 0;
442 		break;
443 	}
444 	if (bu->flags & AG_BUTTON_INVSTATE) {
445 		v = !v;
446 	}
447 	return (v);
448 }
449 
450 static void
SetState(AG_Button * bu,AG_Variable * binding,void * p,int v)451 SetState(AG_Button *bu, AG_Variable *binding, void *p, int v)
452 {
453 	switch (AG_VARIABLE_TYPE(binding)) {
454 	case AG_VARIABLE_INT:
455 		*(int *)p = v;
456 		break;
457 	case AG_VARIABLE_UINT8:
458 		*(Uint8 *)p = v;
459 		break;
460 	case AG_VARIABLE_UINT16:
461 		*(Uint16 *)p = v;
462 		break;
463 	case AG_VARIABLE_UINT32:
464 		*(Uint32 *)p = v;
465 		break;
466 	case AG_VARIABLE_P_FLAG:
467 		AG_SETFLAGS(*(int *)p, (int)binding->info.bitmask, v);
468 		break;
469 	case AG_VARIABLE_P_FLAG8:
470 		AG_SETFLAGS(*(Uint8 *)p, (Uint8)binding->info.bitmask, v);
471 		break;
472 	case AG_VARIABLE_P_FLAG16:
473 		AG_SETFLAGS(*(Uint16 *)p, (Uint16)binding->info.bitmask, v);
474 		break;
475 	case AG_VARIABLE_P_FLAG32:
476 		AG_SETFLAGS(*(Uint32 *)p, (Uint32)binding->info.bitmask, v);
477 		break;
478 	default:
479 		break;
480 	}
481 	AG_Redraw(bu);
482 }
483 
484 static void
Draw(void * p)485 Draw(void *p)
486 {
487 	AG_Button *bu = p;
488 	AG_Variable *binding;
489 	void *pState;
490 	int pressed;
491 
492 	binding = AG_GetVariable(bu, "state", &pState);
493 	pressed = GetState(bu, binding, pState);
494 	AG_UnlockVariable(binding);
495 
496 	if (AG_WidgetEnabled(bu)) {
497 		AG_DrawBox(bu,
498 		    AG_RECT(0, 0, WIDTH(bu), HEIGHT(bu)),
499 		    pressed ? -1 : 1,
500 		    WCOLOR(bu,0));
501 	} else {
502 		AG_DrawBoxDisabled(bu,
503 		    AG_RECT(0, 0, WIDTH(bu), HEIGHT(bu)),
504 		    pressed ? -1 : 1,
505 		    WCOLOR_DEF(bu,0),
506 		    WCOLOR_DIS(bu,0));
507 	}
508 
509 	if (bu->lbl != NULL) {
510 		AG_WidgetDraw(bu->lbl);
511 	} else if (bu->surface != -1) {
512 		int w = WSURFACE(bu,bu->surface)->w;
513 		int h = WSURFACE(bu,bu->surface)->h;
514 		int x = 0, y = 0;
515 
516 		switch (bu->justify) {
517 		case AG_TEXT_LEFT:	x = bu->lPad;			break;
518 		case AG_TEXT_CENTER:	x = WIDTH(bu)/2 - w/2;		break;
519 		case AG_TEXT_RIGHT:	x = WIDTH(bu) - w - bu->rPad;	break;
520 		}
521 		switch (bu->valign) {
522 		case AG_TEXT_TOP:	y = bu->tPad;			break;
523 		case AG_TEXT_MIDDLE:	y = HEIGHT(bu)/2 - h/2;		break;
524 		case AG_TEXT_BOTTOM:	y = HEIGHT(bu) - h - bu->bPad;	break;
525 		}
526 		if (pressed) {
527 			x++;
528 			y++;
529 		}
530 		AG_WidgetBlitSurface(bu, bu->surface, x, y);
531 	}
532 }
533 
534 void
AG_ButtonSetPadding(AG_Button * bu,int lPad,int rPad,int tPad,int bPad)535 AG_ButtonSetPadding(AG_Button *bu, int lPad, int rPad, int tPad, int bPad)
536 {
537 	AG_ObjectLock(bu);
538 	if (lPad != -1) { bu->lPad = lPad; }
539 	if (rPad != -1) { bu->rPad = rPad; }
540 	if (tPad != -1) { bu->tPad = tPad; }
541 	if (bPad != -1) { bu->bPad = bPad; }
542 	AG_ObjectUnlock(bu);
543 	AG_Redraw(bu);
544 }
545 
546 void
AG_ButtonSetFocusable(AG_Button * bu,int focusable)547 AG_ButtonSetFocusable(AG_Button *bu, int focusable)
548 {
549 	AG_ObjectLock(bu);
550 	if (focusable) {
551 		WIDGET(bu)->flags |= AG_WIDGET_FOCUSABLE;
552 		WIDGET(bu)->flags &= ~(AG_WIDGET_UNFOCUSED_BUTTONUP);
553 	} else {
554 		WIDGET(bu)->flags &= ~(AG_WIDGET_FOCUSABLE);
555 		WIDGET(bu)->flags |= AG_WIDGET_UNFOCUSED_BUTTONUP;
556 	}
557 	AG_ObjectUnlock(bu);
558 }
559 
560 void
AG_ButtonSetSticky(AG_Button * bu,int flag)561 AG_ButtonSetSticky(AG_Button *bu, int flag)
562 {
563 	AG_ObjectLock(bu);
564 	AG_SETFLAGS(bu->flags, AG_BUTTON_STICKY, flag);
565 	AG_ObjectUnlock(bu);
566 }
567 
568 void
AG_ButtonInvertState(AG_Button * bu,int flag)569 AG_ButtonInvertState(AG_Button *bu, int flag)
570 {
571 	AG_ObjectLock(bu);
572 	AG_SETFLAGS(bu->flags, AG_BUTTON_INVSTATE, flag);
573 	AG_ObjectUnlock(bu);
574 }
575 
576 void
AG_ButtonJustify(AG_Button * bu,enum ag_text_justify jus)577 AG_ButtonJustify(AG_Button *bu, enum ag_text_justify jus)
578 {
579 	AG_ObjectLock(bu);
580 	bu->justify = jus;
581 	if (bu->lbl != NULL) {
582 		AG_LabelJustify(bu->lbl, jus);
583 	}
584 	AG_ObjectUnlock(bu);
585 }
586 
587 void
AG_ButtonValign(AG_Button * bu,enum ag_text_valign va)588 AG_ButtonValign(AG_Button *bu, enum ag_text_valign va)
589 {
590 	AG_ObjectLock(bu);
591 	bu->valign = va;
592 	if (bu->lbl != NULL) {
593 		AG_LabelValign(bu->lbl, va);
594 	}
595 	AG_ObjectUnlock(bu);
596 }
597 
598 void
AG_ButtonSurface(AG_Button * bu,const AG_Surface * su)599 AG_ButtonSurface(AG_Button *bu, const AG_Surface *su)
600 {
601 	AG_Surface *suDup = (su != NULL) ? AG_SurfaceDup(su) : NULL;
602 
603 	AG_ObjectLock(bu);
604 	if (bu->lbl != NULL) {
605 		AG_ObjectDetach(bu->lbl);
606 		AG_ObjectDestroy(bu->lbl);
607 		bu->lbl = NULL;
608 	}
609 	if (bu->surface != -1) {
610 		AG_WidgetReplaceSurface(bu, bu->surface, suDup);
611 	} else {
612 		bu->surface = AG_WidgetMapSurface(bu, suDup);
613 	}
614 	AG_ObjectUnlock(bu);
615 	AG_Redraw(bu);
616 }
617 
618 void
AG_ButtonSurfaceNODUP(AG_Button * bu,AG_Surface * su)619 AG_ButtonSurfaceNODUP(AG_Button *bu, AG_Surface *su)
620 {
621 	AG_ObjectLock(bu);
622 	if (bu->lbl != NULL) {
623 		AG_ObjectDetach(bu->lbl);
624 		AG_ObjectDestroy(bu->lbl);
625 		bu->lbl = NULL;
626 	}
627 	if (bu->surface != -1) {
628 		AG_WidgetReplaceSurfaceNODUP(bu, bu->surface, su);
629 	} else {
630 		bu->surface = AG_WidgetMapSurfaceNODUP(bu, su);
631 	}
632 	AG_ObjectUnlock(bu);
633 	AG_Redraw(bu);
634 }
635 
636 void
AG_ButtonSetRepeatMode(AG_Button * bu,int repeat)637 AG_ButtonSetRepeatMode(AG_Button *bu, int repeat)
638 {
639 	AG_ObjectLock(bu);
640 	if (repeat) {
641 		bu->flags |= (AG_BUTTON_REPEAT);
642 	} else {
643 		AG_DelTimer(bu, &bu->repeatTo);
644 		AG_DelTimer(bu, &bu->delayTo);
645 		bu->flags &= ~(AG_BUTTON_REPEAT);
646 	}
647 	AG_ObjectUnlock(bu);
648 }
649 
650 /* Set the label text (C string). */
651 void
AG_ButtonTextS(AG_Button * bu,const char * label)652 AG_ButtonTextS(AG_Button *bu, const char *label)
653 {
654 	AG_ObjectLock(bu);
655 	if (bu->surface != -1) {
656 		AG_ButtonSurface(bu, NULL);
657 	}
658 	if (bu->lbl == NULL) {
659 		bu->lbl = AG_LabelNewS(bu, 0, label);
660 		AG_LabelJustify(bu->lbl, bu->justify);
661 		AG_LabelValign(bu->lbl, bu->valign);
662 	} else {
663 		AG_LabelTextS(bu->lbl, label);
664 	}
665 	AG_ObjectUnlock(bu);
666 	AG_Redraw(bu);
667 }
668 
669 /* Set the label text (format string). */
670 void
AG_ButtonText(AG_Button * bu,const char * fmt,...)671 AG_ButtonText(AG_Button *bu, const char *fmt, ...)
672 {
673 	char s[AG_LABEL_MAX];
674 	va_list ap;
675 
676 	va_start(ap, fmt);
677 	Vsnprintf(s, sizeof(s), fmt, ap);
678 	va_end(ap);
679 	AG_ButtonTextS(bu, s);
680 }
681 
682 AG_WidgetClass agButtonClass = {
683 	{
684 		"Agar(Widget:Button)",
685 		sizeof(AG_Button),
686 		{ 0,0 },
687 		Init,
688 		NULL,		/* free */
689 		NULL,		/* destroy */
690 		NULL,		/* load */
691 		NULL,		/* save */
692 		NULL		/* edit */
693 	},
694 	Draw,
695 	SizeRequest,
696 	SizeAllocate
697 };
698