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