1 /*
2  * Copyright (c) 2003-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/box.h>
28 #include <agar/gui/window.h>
29 #include <agar/gui/primitive.h>
30 #include <agar/gui/text.h>
31 #include <agar/gui/label.h>
32 #ifdef AG_DEBUG
33 #include <agar/gui/numerical.h>
34 #include <agar/gui/checkbox.h>
35 #endif
36 
37 AG_Box *
AG_BoxNew(void * parent,enum ag_box_type type,Uint flags)38 AG_BoxNew(void *parent, enum ag_box_type type, Uint flags)
39 {
40 	AG_Box *box;
41 
42 	box = Malloc(sizeof(AG_Box));
43 	AG_ObjectInit(box, &agBoxClass);
44 
45 	box->type = type;
46 	box->flags |= flags;
47 
48 	if (flags & AG_BOX_HFILL) { AG_ExpandHoriz(box); }
49 	if (flags & AG_BOX_VFILL) { AG_ExpandVert(box); }
50 
51 	AG_ObjectAttach(parent, box);
52 	return (box);
53 }
54 
55 /* Set the label text (format string). */
56 void
AG_BoxSetLabel(AG_Box * box,const char * fmt,...)57 AG_BoxSetLabel(AG_Box *box, const char *fmt, ...)
58 {
59 	char *s;
60 	va_list ap;
61 
62 	if (fmt != NULL) {
63 		va_start(ap, fmt);
64 		Vasprintf(&s, fmt, ap);
65 		va_end(ap);
66 		AG_BoxSetLabelS(box, s);
67 		free(s);
68 		box->flags |= AG_BOX_FRAME;
69 	} else {
70 		AG_BoxSetLabelS(box, NULL);
71 		box->flags &= ~(AG_BOX_FRAME);
72 	}
73 }
74 
75 /* Set the label text (format string). */
76 void
AG_BoxSetLabelS(AG_Box * box,const char * s)77 AG_BoxSetLabelS(AG_Box *box, const char *s)
78 {
79 	AG_ObjectLock(box);
80 	if (s != NULL) {
81 		if (box->lbl == NULL) {
82 			box->lbl = AG_LabelNewS(box, 0, s);
83 			AG_SetStyle(box->lbl, "font-size", "80%");
84 		} else {
85 			AG_LabelTextS(box->lbl, s);
86 		}
87 	} else {
88 		AG_ObjectDetach(box->lbl);
89 		AG_ObjectDestroy(box->lbl);
90 		box->lbl = NULL;
91 	}
92 	AG_Redraw(box);
93 	AG_ObjectUnlock(box);
94 }
95 
96 static void
Init(void * obj)97 Init(void *obj)
98 {
99 	AG_Box *box = obj;
100 
101 	box->flags = 0;
102 	box->type = AG_BOX_VERT;
103 	box->depth = -1;
104 	box->padding = 4;
105 	box->spacing = 2;
106 	box->lbl = NULL;
107 	box->hAlign = AG_BOX_LEFT;
108 	box->vAlign = AG_BOX_TOP;
109 }
110 
111 static void
Draw(void * obj)112 Draw(void *obj)
113 {
114 	AG_Box *box = obj;
115 	AG_Widget *chld;
116 
117 	if (box->flags & AG_BOX_FRAME) {
118 		AG_DrawBox(box,
119 		    AG_RECT(0, 0, WIDTH(box), HEIGHT(box)),
120 		    box->depth, WCOLOR(box,AG_COLOR));
121 	}
122 	OBJECT_FOREACH_CHILD(chld, box, ag_widget)
123 		AG_WidgetDraw(chld);
124 }
125 
126 static int
CountChildWidgets(AG_Box * box,int * totFixed)127 CountChildWidgets(AG_Box *box, int *totFixed)
128 {
129 	AG_Widget *chld;
130 	AG_SizeReq r;
131 	Uint count = 0;
132 	int fixed = 0;
133 
134 	OBJECT_FOREACH_CHILD(chld, box, ag_widget) {
135 		AG_WidgetSizeReq(chld, &r);
136 		switch (box->type) {
137 		case AG_BOX_HORIZ:
138 			if ((chld->flags & AG_WIDGET_HFILL) == 0)
139 				fixed += r.w;
140 			break;
141 		case AG_BOX_VERT:
142 			if ((chld->flags & AG_WIDGET_VFILL) == 0)
143 				fixed += r.h;
144 			break;
145 		}
146 		count++;
147 		fixed += box->spacing;
148 	}
149 	if (count > 0 && fixed >= box->spacing) {
150 		fixed -= box->spacing;
151 	}
152 	*totFixed = fixed;
153 	return (count);
154 }
155 
156 static void
SizeRequest(void * obj,AG_SizeReq * r)157 SizeRequest(void *obj, AG_SizeReq *r)
158 {
159 	AG_Box *box = obj;
160 	AG_Widget *chld;
161 	AG_SizeReq rChld;
162 	int wMax = 0, hMax = 0;
163 	int nWidgets, totArea;
164 
165 	nWidgets = CountChildWidgets(box, &totArea);
166 	r->w = box->padding*2;
167 	r->h = box->padding*2;
168 	OBJECT_FOREACH_CHILD(chld, box, ag_widget) {
169 		AG_WidgetSizeReq(chld, &rChld);
170 		if (rChld.w > wMax) { wMax = rChld.w; }
171 		if (rChld.h > hMax) { hMax = rChld.h; }
172 		switch (box->type) {
173 		case AG_BOX_HORIZ:
174 			r->h = MAX(r->h, hMax + box->padding*2);
175 			r->w += rChld.w + box->spacing;
176 			break;
177 		case AG_BOX_VERT:
178 			r->w = MAX(r->w, wMax + box->padding*2);
179 			r->h += rChld.h + box->spacing;
180 			break;
181 		}
182 	}
183 	if (nWidgets > 0) {
184 		switch (box->type) {
185 		case AG_BOX_HORIZ:
186 			if (r->w >= box->spacing)
187 				r->w -= box->spacing;
188 			break;
189 		case AG_BOX_VERT:
190 			if (r->h >= box->spacing)
191 				r->h -= box->spacing;
192 			break;
193 		}
194 	}
195 }
196 
197 static int
SizeAllocateHomogenous(AG_Box * box,const AG_SizeAlloc * a,int nWidgets)198 SizeAllocateHomogenous(AG_Box *box, const AG_SizeAlloc *a, int nWidgets)
199 {
200 	int wSize, totUsed = 0, avail;
201 	AG_Widget *chld, *chldLast = NULL;
202 	AG_SizeAlloc aChld;
203 
204 	avail = ((box->type == AG_BOX_HORIZ) ? a->w : a->h);
205 	avail -= box->padding*2;
206 	wSize = (avail - nWidgets - 1) / nWidgets;
207 
208 	aChld.x = box->padding;
209 	aChld.y = box->padding;
210 	OBJECT_FOREACH_CHILD(chld, box, ag_widget) {
211 		aChld.w = (box->type==AG_BOX_HORIZ) ?
212 		          wSize : (a->w - box->padding*2);
213 		aChld.h = (box->type==AG_BOX_VERT) ?
214 		          wSize : (a->h - box->padding*2);
215 
216 		AG_WidgetSizeAlloc(chld, &aChld);
217 		if (chld->flags & AG_WIDGET_UNDERSIZE)
218 			continue;
219 
220 		if (OBJECT(chld) ==
221 		    TAILQ_LAST(&OBJECT(box)->children,ag_objectq)) {
222 			chldLast = chld;
223 		} else {
224 			if (box->type == AG_BOX_HORIZ) {
225 				aChld.x += aChld.w + 1;
226 			} else {
227 				aChld.y += aChld.h + 1;
228 			}
229 		}
230 		totUsed += ((box->type == AG_BOX_HORIZ) ? aChld.w : aChld.h)+1;
231 	}
232 	/* Compensate for rounding error due to division. */
233 	if (chldLast != NULL && totUsed < avail) {
234 		switch (box->type) {
235 		case AG_BOX_VERT:
236 			aChld.h += avail - totUsed;
237 			AG_WidgetSizeAlloc(chldLast, &aChld);
238 			break;
239 		case AG_BOX_HORIZ:
240 			aChld.w += avail - totUsed;
241 			AG_WidgetSizeAlloc(chldLast, &aChld);
242 			break;
243 		}
244 	}
245 	return (0);
246 }
247 
248 static int
SizeAllocate(void * obj,const AG_SizeAlloc * a)249 SizeAllocate(void *obj, const AG_SizeAlloc *a)
250 {
251 	AG_Box *box = obj;
252 	AG_Widget *chld;
253 	AG_SizeReq rChld;
254 	AG_SizeAlloc aChld;
255 	int nWidgets, totFixed;
256 	int wAvail, hAvail;
257 	int x, y;
258 
259 	if ((nWidgets = CountChildWidgets(box, &totFixed)) == 0) {
260 		return (0);
261 	}
262 	if (box->flags & AG_BOX_HOMOGENOUS) {
263 		if (SizeAllocateHomogenous(box, a, nWidgets) == -1) {
264 			return (-1);
265 		}
266 		return (0);
267 	}
268 	wAvail = a->w - box->padding*2;
269 	hAvail = a->h - box->padding*2;
270 	x = box->padding;
271 	y = box->padding;
272 	if (totFixed < wAvail) {
273 		switch (box->hAlign) {
274 		case AG_BOX_CENTER:	x = wAvail/2 - totFixed/2;	break;
275 		case AG_BOX_RIGHT:	x = wAvail - totFixed;		break;
276 		}
277 		switch (box->vAlign) {
278 		case AG_BOX_CENTER:	y = hAvail/2 - totFixed/2;	break;
279 		case AG_BOX_BOTTOM:	y = hAvail - totFixed;		break;
280 		}
281 	}
282 	OBJECT_FOREACH_CHILD(chld, box, ag_widget) {
283 		AG_WidgetSizeReq(chld, &rChld);
284 		switch (box->type) {
285 		case AG_BOX_HORIZ:
286 			aChld.w = (chld->flags & AG_WIDGET_HFILL) ?
287 			          (wAvail - totFixed) : MIN(rChld.w, wAvail);
288 			aChld.h = (chld->flags & AG_WIDGET_VFILL) ?
289 				  hAvail : MIN(hAvail, rChld.h);
290 			aChld.x = x;
291 			if (aChld.x+aChld.w > a->w) {
292 				aChld.w = a->w - aChld.x;
293 			}
294 			switch (box->vAlign) {
295 			case AG_BOX_TOP:	aChld.y = y;			break;
296 			case AG_BOX_CENTER:	aChld.y = hAvail/2 - aChld.h/2;	break;
297 			case AG_BOX_BOTTOM:	aChld.y = hAvail - aChld.h;	break;
298 			}
299 			AG_WidgetSizeAlloc(chld, &aChld);
300 			x += aChld.w + box->spacing;
301 			break;
302 		case AG_BOX_VERT:
303 			aChld.w = (chld->flags & AG_WIDGET_HFILL) ?
304 			          wAvail : MIN(wAvail, rChld.w);
305 			aChld.h = (chld->flags & AG_WIDGET_VFILL) ?
306 			          (hAvail - totFixed) : MIN(rChld.h, hAvail);
307 			aChld.y = y;
308 			if (aChld.y+aChld.h > a->h) {
309 				aChld.h = a->h - aChld.y;
310 			}
311 			switch (box->hAlign) {
312 			case AG_BOX_TOP:	aChld.x = x;			break;
313 			case AG_BOX_CENTER:	aChld.x = wAvail/2 - aChld.w/2;	break;
314 			case AG_BOX_BOTTOM:	aChld.x = wAvail - aChld.w;	break;
315 			}
316 			AG_WidgetSizeAlloc(chld, &aChld);
317 			y += aChld.h + box->spacing;
318 			break;
319 		}
320 	}
321 	return (0);
322 }
323 
324 void
AG_BoxSetHomogenous(AG_Box * box,int enable)325 AG_BoxSetHomogenous(AG_Box *box, int enable)
326 {
327 	AG_ObjectLock(box);
328 	AG_SETFLAGS(box->flags, AG_BOX_HOMOGENOUS, enable);
329 	AG_ObjectUnlock(box);
330 	AG_Redraw(box);
331 }
332 
333 void
AG_BoxSetPadding(AG_Box * box,int padding)334 AG_BoxSetPadding(AG_Box *box, int padding)
335 {
336 	AG_ObjectLock(box);
337 	box->padding = padding;
338 	AG_ObjectUnlock(box);
339 	AG_Redraw(box);
340 }
341 
342 void
AG_BoxSetSpacing(AG_Box * box,int spacing)343 AG_BoxSetSpacing(AG_Box *box, int spacing)
344 {
345 	AG_ObjectLock(box);
346 	box->spacing = spacing;
347 	AG_ObjectUnlock(box);
348 	AG_Redraw(box);
349 }
350 
351 void
AG_BoxSetDepth(AG_Box * box,int depth)352 AG_BoxSetDepth(AG_Box *box, int depth)
353 {
354 	AG_ObjectLock(box);
355 	box->depth = depth;
356 	AG_ObjectUnlock(box);
357 	AG_Redraw(box);
358 }
359 
360 void
AG_BoxSetType(AG_Box * box,enum ag_box_type type)361 AG_BoxSetType(AG_Box *box, enum ag_box_type type)
362 {
363 	AG_SizeAlloc a;
364 
365 	AG_ObjectLock(box);
366 	box->type = type;
367 	a.x = WIDGET(box)->x;
368 	a.y = WIDGET(box)->y;
369 	a.w = WIDGET(box)->w;
370 	a.h = WIDGET(box)->h;
371 	SizeAllocate(box, &a);
372 	AG_ObjectUnlock(box);
373 	AG_Redraw(box);
374 }
375 
376 void
AG_BoxSetHorizAlign(AG_Box * box,enum ag_box_align align)377 AG_BoxSetHorizAlign(AG_Box *box, enum ag_box_align align)
378 {
379 	AG_ObjectLock(box);
380 	box->hAlign = align;
381 	AG_ObjectUnlock(box);
382 	AG_Redraw(box);
383 }
384 
385 void
AG_BoxSetVertAlign(AG_Box * box,enum ag_box_align align)386 AG_BoxSetVertAlign(AG_Box *box, enum ag_box_align align)
387 {
388 	AG_ObjectLock(box);
389 	box->vAlign = align;
390 	AG_ObjectUnlock(box);
391 	AG_Redraw(box);
392 }
393 
394 #ifdef AG_DEBUG
395 static void
UpdateWindow(AG_Event * event)396 UpdateWindow(AG_Event *event)
397 {
398 	AG_Window *win = AG_PTR(1);
399 	AG_WindowUpdate(win);
400 }
401 
402 static void *
Edit(void * obj)403 Edit(void *obj)
404 {
405 	AG_Box *ctr = AG_BoxNewVert(NULL, AG_BOX_EXPAND);
406 	AG_Box *box = obj;
407 	AG_Numerical *num;
408 
409 	num = AG_NumericalNewIntR(ctr, 0, "px", _("Padding: "),
410 	    &box->padding, 0, 255);
411 	AG_SetEvent(num, "numerical-changed",
412 	    UpdateWindow, "%p", AG_ParentWindow(box));
413 
414 	num = AG_NumericalNewIntR(ctr, 0, "px", _("Spacing: "),
415 	    &box->spacing, 0, 255);
416 	AG_SetEvent(num, "numerical-changed",
417 	    UpdateWindow, "%p", AG_ParentWindow(box));
418 
419 	AG_CheckboxNewFlag(ctr, 0, _("Homogenous"),
420 	    &box->flags, AG_BOX_HOMOGENOUS);
421 	AG_CheckboxNewFlag(ctr, 0, _("Visual frame"),
422 	    &box->flags, AG_BOX_FRAME);
423 
424 	return (ctr);
425 }
426 #endif /* AG_DEBUG */
427 
428 AG_WidgetClass agBoxClass = {
429 	{
430 		"Agar(Widget:Box)",
431 		sizeof(AG_Box),
432 		{ 0,0 },
433 		Init,
434 		NULL,		/* free */
435 		NULL,		/* destroy */
436 		NULL,		/* load */
437 		NULL,		/* save */
438 #ifdef AG_DEBUG
439 		Edit
440 #else
441 		NULL		/* edit */
442 #endif
443 	},
444 	Draw,
445 	SizeRequest,
446 	SizeAllocate
447 };
448