/* * Copyright (c) 2003-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * General-purpose container widget which aligns and packs its widgets * horizontally or vertically based on their size requisitions. * * Spacing and padding is added according to style attributes. Widgets * with the HFILL / VFILL bits set are expanded to fill remaining space. */ #include #ifdef AG_WIDGETS #include #include #include #include #include #include #include #include #include const char *agBoxHorizAlignNames[] = { N_("Left"), N_("Center"), N_("Right"), NULL }; const char *agBoxVertAlignNames[] = { N_("Top"), N_("Middle"), N_("Bottom"), NULL }; static Uint SizeRequestHoriz(AG_Box *_Nonnull, int *_Nonnull); static Uint SizeRequestVert(AG_Box *_Nonnull, int *_Nonnull); static void SizeAllocateHoriz(AG_Box *_Nonnull, const AG_SizeAlloc *_Nonnull, int); static void SizeAllocateVert(AG_Box *_Nonnull, const AG_SizeAlloc *_Nonnull, int); static void SizeAllocateHomogenousHoriz(AG_Box *_Nonnull, const AG_SizeAlloc *_Nonnull, int); static void SizeAllocateHomogenousVert(AG_Box *_Nonnull, const AG_SizeAlloc *_Nonnull, int); AG_Box * AG_BoxNew(void *parent, enum ag_box_type type, Uint flags) { AG_Box *box; box = Malloc(sizeof(AG_Box)); AG_ObjectInit(box, &agBoxClass); box->type = type; box->flags |= flags; if (flags & AG_BOX_HFILL) { WIDGET(box)->flags |= AG_WIDGET_HFILL; } if (flags & AG_BOX_VFILL) { WIDGET(box)->flags |= AG_WIDGET_VFILL; } if (flags & AG_BOX_NO_SPACING) { AG_SetSpacing(box, "0"); AG_SetPadding(box, "0"); } AG_ObjectAttach(parent, box); return (box); } AG_Box * AG_BoxNewHoriz(void *p, Uint flags) { return AG_BoxNew(p, AG_BOX_HORIZ, flags); } AG_Box * AG_BoxNewVert(void *p, Uint flags) { return AG_BoxNew(p, AG_BOX_VERT, flags); } /* Set the graphical style of background and borders. */ void AG_BoxSetStyle(AG_Box *box, enum ag_box_style which) { AG_OBJECT_ISA(box, "AG_Widget:AG_Box:*"); box->style = which; AG_Redraw(box); } /* Set the label text (format string). */ void AG_BoxSetLabel(AG_Box *box, const char *fmt, ...) { char *s; va_list ap; if (fmt != NULL) { va_start(ap, fmt); Vasprintf(&s, fmt, ap); va_end(ap); AG_BoxSetLabelS(box, s); free(s); } else { AG_BoxSetLabelS(box, NULL); } } /* Set the label text (format string). */ void AG_BoxSetLabelS(AG_Box *box, const char *s) { AG_OBJECT_ISA(box, "AG_Widget:AG_Box:*"); AG_ObjectLock(box); if (s != NULL) { if (box->lbl == NULL) { box->lbl = AG_LabelNewS(box, 0, s); AG_SetFontSize(box->lbl, "80%"); } else { AG_LabelTextS(box->lbl, s); } } else { AG_ObjectDetach(box->lbl); AG_ObjectDestroy(box->lbl); box->lbl = NULL; } AG_Redraw(box); AG_ObjectUnlock(box); } static void MouseButtonDown(void *obj, AG_MouseButton button, int x, int y) { AG_Box *box = obj; AG_Window *wParent = AG_ParentWindow(box); if (!AG_WindowIsFocused(wParent)) AG_WindowFocus(wParent); } static void Init(void *_Nonnull obj) { AG_Box *box = obj; box->flags = 0; box->style = AG_BOX_STYLE_NONE; box->type = AG_BOX_VERT; box->wPre = -1; box->hPre = -1; box->depth = -1; box->lbl = NULL; box->hAlign = AG_BOX_LEFT; box->vAlign = AG_BOX_TOP; } static void Draw(void *_Nonnull obj) { AG_Box *box = obj; const AG_Color *cBg = &WCOLOR(box, BG_COLOR); AG_Widget *chld; AG_Rect r = WIDGET(box)->r; if (box->style != AG_BOX_STYLE_NONE && cBg->a > 0) { static void (*pfBox[])(void *_Nonnull, const AG_Rect *_Nonnull, const AG_Color *_Nonnull) = { ag_draw_rect_noop, /* NONE */ ag_draw_box_raised, /* BOX */ ag_draw_box_sunk, /* WELL */ ag_draw_rect /* PLAIN */ }; #ifdef AG_DEBUG if (box->style >= AG_BOX_STYLE_LAST) AG_FatalError("style"); #endif pfBox[box->style](box, &r, cBg); } else if (box->flags & AG_BOX_SHADING) { /* Shading only */ if (box->depth < 0) { AG_DrawFrameSunk(box, &r); } else { AG_DrawFrameRaised(box, &r); } } OBJECT_FOREACH_CHILD(chld, box, ag_widget) AG_WidgetDraw(chld); } static void SizeRequest(void *_Nonnull obj, AG_SizeReq *_Nonnull r) { static Uint (*pfSizeRequest[])(AG_Box *_Nonnull, int *_Nonnull) = { SizeRequestHoriz, SizeRequestVert }; AG_Box *box = obj; AG_Widget *chld; AG_SizeReq req; int nWidgets, totReqd, wdMax=0; r->w = WIDGET(box)->paddingLeft + WIDGET(box)->paddingRight; r->h = WIDGET(box)->paddingTop + WIDGET(box)->paddingBottom; #ifdef AG_DEBUG if (box->type >= AG_BOX_TYPE_LAST) AG_FatalError("box->type"); #endif nWidgets = pfSizeRequest[box->type](box, &totReqd); switch (box->type) { case AG_BOX_HORIZ: OBJECT_FOREACH_CHILD(chld, box, ag_widget) { AG_WidgetSizeReq(chld, &req); req.h += (chld->marginTop + chld->marginBottom); if (req.h > wdMax) { wdMax = req.h; } r->h = MAX(r->h, wdMax + WIDGET(box)->paddingTop + WIDGET(box)->paddingBottom); r->w += req.w + WIDGET(box)->spacingHoriz; } if (nWidgets > 0) { if (r->w >= WIDGET(box)->spacingHoriz) r->w -= WIDGET(box)->spacingHoriz; } break; case AG_BOX_VERT: OBJECT_FOREACH_CHILD(chld, box, ag_widget) { AG_WidgetSizeReq(chld, &req); req.w += (chld->marginLeft + chld->marginRight); if (req.w > wdMax) { wdMax = req.w; } r->w = MAX(r->w, wdMax + WIDGET(box)->paddingLeft + WIDGET(box)->paddingRight); r->h += req.h + WIDGET(box)->spacingVert; } if (nWidgets > 0) { if (r->h >= WIDGET(box)->spacingVert) r->h -= WIDGET(box)->spacingVert; } break; case AG_BOX_TYPE_LAST: default: break; } if (box->wPre != -1) { r->w = box->wPre; } if (box->hPre != -1) { r->h = box->hPre; } } static Uint SizeRequestHoriz(AG_Box *_Nonnull box, int *_Nonnull totReqd) { AG_SizeReq req; AG_Widget *chld; const int spacing = WIDGET(box)->spacingHoriz; Uint count = 0; int wReqd = 0; OBJECT_FOREACH_CHILD(chld, box, ag_widget) { AG_WidgetSizeReq(chld, &req); if ((chld->flags & AG_WIDGET_HFILL) == 0) { wReqd += (req.w + chld->marginLeft + chld->marginRight); } count++; wReqd += spacing; } if (wReqd >= spacing) { wReqd -= spacing; } *totReqd = wReqd; return (count); } static Uint SizeRequestVert(AG_Box *_Nonnull box, int *_Nonnull totReqd) { AG_SizeReq req; AG_Widget *chld; const int spacing = WIDGET(box)->spacingVert; Uint count = 0; int hReqd = 0; OBJECT_FOREACH_CHILD(chld, box, ag_widget) { AG_WidgetSizeReq(chld, &req); if ((chld->flags & AG_WIDGET_VFILL) == 0) { hReqd += (req.h + chld->marginTop + chld->marginBottom); } count++; hReqd += spacing; } if (hReqd >= spacing) { hReqd -= spacing; } *totReqd = hReqd; return (count); } static int SizeAllocate(void *_Nonnull obj, const AG_SizeAlloc *_Nonnull a) { static Uint (*pfSizeRequest[])(AG_Box *_Nonnull, int *_Nonnull) = { SizeRequestHoriz, SizeRequestVert }; static void (*pfSizeAllocate[])(AG_Box *_Nonnull, const AG_SizeAlloc *_Nonnull, int) = { SizeAllocateHoriz, SizeAllocateVert, SizeAllocateHomogenousHoriz, SizeAllocateHomogenousVert }; AG_Box *box = obj; int nWidgets, totReqd; #ifdef AG_DEBUG if (box->type >= AG_BOX_TYPE_LAST) AG_FatalError("box->type"); #endif nWidgets = pfSizeRequest[box->type](box, &totReqd); if (nWidgets < 1) { return (0); } if (box->flags & AG_BOX_HOMOGENOUS) { pfSizeAllocate[box->type + 2](box, a, nWidgets); } else { pfSizeAllocate[box->type](box, a, totReqd); } return (0); } static void SizeAllocateHoriz(AG_Box *_Nonnull box, const AG_SizeAlloc *_Nonnull a, int wReqd) { AG_Widget *chld; const int wAvail = a->w - (WIDGET(box)->paddingLeft + WIDGET(box)->paddingRight); const int hAvail = a->h - (WIDGET(box)->paddingTop + WIDGET(box)->paddingBottom); const int spacing = WIDGET(box)->spacingHoriz; const enum ag_box_align vAlign = box->vAlign; int x = WIDGET(box)->paddingLeft; int y = WIDGET(box)->paddingTop; if (wReqd < wAvail) { switch (box->hAlign) { case AG_BOX_CENTER: x = (wAvail >> 1) - (wReqd >> 1); break; case AG_BOX_RIGHT: x = wAvail - wReqd; break; default: break; } if (x < 0) { x = 0; } switch (vAlign) { case AG_BOX_CENTER: y = (hAvail >> 1) - (wReqd >> 1); break; case AG_BOX_BOTTOM: y = hAvail - wReqd; break; default: break; } if (y < 0) { y = 0; } } OBJECT_FOREACH_CHILD(chld, box, ag_widget) { const Uint flags = chld->flags; AG_SizeReq req; AG_SizeAlloc alloc; AG_WidgetSizeReq(chld, &req); alloc.x = x + chld->marginLeft; alloc.w = (flags & AG_WIDGET_HFILL) ? (wAvail - wReqd) : MIN(wAvail, req.w); alloc.h = (flags & AG_WIDGET_VFILL) ? (hAvail - chld->marginTop - chld->marginBottom) : MIN(hAvail, req.h); if (x + alloc.w > a->w) alloc.w = a->w - x; switch (vAlign) { case AG_BOX_TOP: alloc.y = y + chld->marginTop; break; case AG_BOX_CENTER: alloc.y = (hAvail >> 1) - (alloc.h >> 1); if (alloc.y < 0) { alloc.y = 0; } break; case AG_BOX_BOTTOM: alloc.y = hAvail - alloc.h - chld->marginBottom; if (alloc.y < 0) { alloc.y = 0; } break; } AG_WidgetSizeAlloc(chld, &alloc); AG_SETFLAGS(chld->flags, AG_WIDGET_VISIBLE, (alloc.w > 0)); x += alloc.w + spacing + chld->marginLeft + chld->marginRight; } } static void SizeAllocateVert(AG_Box *_Nonnull box, const AG_SizeAlloc *_Nonnull a, int hReqd) { AG_Widget *chld; const int wAvail = a->w - (WIDGET(box)->paddingLeft + WIDGET(box)->paddingRight); const int hAvail = a->h - (WIDGET(box)->paddingTop + WIDGET(box)->paddingBottom); const int spacing = WIDGET(box)->spacingVert; const enum ag_box_align hAlign = box->hAlign; int x = WIDGET(box)->paddingLeft; int y = WIDGET(box)->paddingTop; if (hReqd < hAvail) { switch (hAlign) { case AG_BOX_CENTER: x = (wAvail >> 1) - (hReqd >> 1); break; case AG_BOX_RIGHT: x = wAvail - hReqd; break; default: break; } if (x < 0) { x = 0; } switch (box->vAlign) { case AG_BOX_CENTER: y = (hAvail >> 1) - (hReqd >> 1); break; case AG_BOX_BOTTOM: y = hAvail - hReqd; break; default: break; } if (y < 0) { y = 0; } } OBJECT_FOREACH_CHILD(chld, box, ag_widget) { AG_SizeReq req; AG_SizeAlloc alloc; const Uint flags = chld->flags; AG_WidgetSizeReq(chld, &req); alloc.y = y + chld->marginTop; alloc.w = (flags & AG_WIDGET_HFILL) ? (wAvail - chld->marginLeft - chld->marginRight) : MIN(wAvail, req.w); alloc.h = (flags & AG_WIDGET_VFILL) ? (hAvail - hReqd) : MIN(hAvail, req.h); if (y + alloc.h > a->h) alloc.h = a->h - y; switch (hAlign) { case AG_BOX_LEFT: alloc.x = x + chld->marginLeft; break; case AG_BOX_CENTER: alloc.x = (wAvail >> 1) - (alloc.w >> 1); if (alloc.x < 0) { alloc.x = 0; } break; case AG_BOX_RIGHT: alloc.x = wAvail - alloc.w - chld->marginRight; if (alloc.x < 0) { alloc.x = 0; } break; } AG_WidgetSizeAlloc(chld, &alloc); AG_SETFLAGS(chld->flags, AG_WIDGET_VISIBLE, (alloc.h > 0)); y += alloc.h + spacing + chld->marginTop + chld->marginBottom; } } static void SizeAllocateHomogenousHoriz(AG_Box *_Nonnull box, const AG_SizeAlloc *_Nonnull a, int nWidgets) { AG_SizeAlloc alloc; AG_Widget *chld, *chldLast=NULL; const int paddingTopBot = WIDGET(box)->paddingTop + WIDGET(box)->paddingBottom; const int wAvail = a->w - (WIDGET(box)->paddingLeft + WIDGET(box)->paddingRight); const int spacing = WIDGET(box)->spacingHoriz; int wUsed = 0; alloc.x = WIDGET(box)->paddingLeft; alloc.y = WIDGET(box)->paddingTop; alloc.w = (wAvail - nWidgets - 1) / nWidgets; alloc.h = (a->h - paddingTopBot); OBJECT_FOREACH_CHILD(chld, box, ag_widget) { AG_WidgetSizeAlloc(chld, &alloc); if (chld->flags & AG_WIDGET_UNDERSIZE) { continue; } if (OBJECT(chld) == TAILQ_LAST(&OBJECT(box)->children, ag_objectq)) { chldLast = chld; } else { alloc.x += alloc.w + spacing; } wUsed += (alloc.w + spacing); } if (chldLast != NULL && wUsed < wAvail) { /* Roundoff compensation */ alloc.w += wAvail - wUsed; AG_WidgetSizeAlloc(chldLast, &alloc); } } static void SizeAllocateHomogenousVert(AG_Box *_Nonnull box, const AG_SizeAlloc *_Nonnull a, int nWidgets) { AG_SizeAlloc alloc; AG_Widget *chld, *chldLast=NULL; const int paddingLeftRight = WIDGET(box)->paddingLeft + WIDGET(box)->paddingRight; const int hAvail = a->h - (WIDGET(box)->paddingTop + WIDGET(box)->paddingBottom); const int spacing = WIDGET(box)->spacingVert; int hUsed = 0; alloc.x = WIDGET(box)->paddingLeft; alloc.y = WIDGET(box)->paddingTop; alloc.w = (a->w - paddingLeftRight); alloc.h = (hAvail - nWidgets - 1) / nWidgets; OBJECT_FOREACH_CHILD(chld, box, ag_widget) { AG_WidgetSizeAlloc(chld, &alloc); if (chld->flags & AG_WIDGET_UNDERSIZE) { continue; } if (OBJECT(chld) == TAILQ_LAST(&OBJECT(box)->children, ag_objectq)) { chldLast = chld; } else { alloc.y += alloc.h + spacing; } hUsed += (alloc.h + spacing); } if (chldLast != NULL && hUsed < hAvail) { /* Roundoff compensation */ alloc.h += hAvail - hUsed; AG_WidgetSizeAlloc(chldLast, &alloc); } } /* Set a specific size requisition in pixels (-1 = auto). */ void AG_BoxSizeHint(AG_Box *box, int w, int h) { AG_OBJECT_ISA(box, "AG_Widget:AG_Box:*"); box->wPre = w; box->hPre = h; } /* Enable/Disable HOMOGENOUS (divide space equally) mode. */ void AG_BoxSetHomogenous(AG_Box *box, int enable) { AG_OBJECT_ISA(box, "AG_Widget:AG_Box:*"); AG_ObjectLock(box); AG_SETFLAGS(box->flags, AG_BOX_HOMOGENOUS, enable); AG_ObjectUnlock(box); AG_Redraw(box); } /* Set depth of shading in pixels. */ void AG_BoxSetDepth(AG_Box *box, int depth) { AG_OBJECT_ISA(box, "AG_Widget:AG_Box:*"); box->depth = depth; AG_Redraw(box); } /* Set packing alignment (HORIZ or VERT). */ void AG_BoxSetType(AG_Box *box, enum ag_box_type type) { AG_SizeAlloc a; #ifdef AG_DEBUG if (type >= AG_BOX_TYPE_LAST) AG_FatalError("Box type"); #endif AG_OBJECT_ISA(box, "AG_Widget:AG_Box:*"); AG_ObjectLock(box); box->type = type; a.x = WIDGET(box)->x; a.y = WIDGET(box)->y; a.w = WIDGET(box)->w; a.h = WIDGET(box)->h; SizeAllocate(box, &a); AG_ObjectUnlock(box); AG_Redraw(box); } /* Set horizontal alignment (LEFT, CENTER, RIGHT). */ void AG_BoxSetHorizAlign(AG_Box *box, enum ag_box_align align) { AG_OBJECT_ISA(box, "AG_Widget:AG_Box:*"); box->hAlign = align; AG_Redraw(box); } /* Set vertical alignment (TOP, MIDDLE, BOTTOM). */ void AG_BoxSetVertAlign(AG_Box *box, enum ag_box_align align) { AG_OBJECT_ISA(box, "AG_Widget:AG_Box:*"); box->vAlign = align; AG_Redraw(box); } static void UpdateWindowOfTgt(AG_Event *_Nonnull event) { AG_Box *box = AG_BOX_PTR(1); AG_Window *win = AG_ParentWindow(box); AG_Redraw(box); AG_WindowUpdate(win); } static void *_Nullable Edit(void *_Nonnull obj) { AG_Box *tgt = obj, *box, *hBox; AG_Numerical *num; AG_Checkbox *cb; AG_Radio *rad; const char *typeNames[] = { N_("Vertical"), N_("Horizontal"), NULL }; const char *styleNames[] = { N_("None"), N_("Box"), N_("Well"), N_("Plain"), NULL }; box = AG_BoxNewVert(NULL, AG_BOX_EXPAND); AG_LabelNewS(box, 0, _("Disposition:")); rad = AG_RadioNewUint(box, 0, typeNames, (Uint *)&tgt->type); AG_SetEvent(rad, "radio-changed", UpdateWindowOfTgt,"%p",tgt); AG_LabelNewS(box, 0, _("Style:")); rad = AG_RadioNewUint(box, 0, styleNames, (Uint *)&tgt->style); AG_SetEvent(rad, "radio-changed", UpdateWindowOfTgt,"%p",tgt); AG_LabelNewS(box, 0, _("Alignment:")); hBox = AG_BoxNewHoriz(box, 0); { rad = AG_RadioNewUint(hBox, 0, agBoxHorizAlignNames, (Uint *)&tgt->hAlign); AG_SetEvent(rad, "radio-changed", UpdateWindowOfTgt,"%p",tgt); rad = AG_RadioNewUint(hBox, 0, agBoxVertAlignNames, (Uint *)&tgt->vAlign); AG_SetEvent(rad, "radio-changed", UpdateWindowOfTgt,"%p",tgt); } AG_SpacerNewHoriz(box); num = AG_NumericalNewIntR(box, 0, NULL, _("Depth: "), &tgt->depth, -127, 127); AG_SetEvent(num, "numerical-changed", UpdateWindowOfTgt,"%p",tgt); AG_SpacerNewHoriz(box); cb = AG_CheckboxNewFlag(box, 0, _("Homogenous"), &tgt->flags, AG_BOX_HOMOGENOUS); AG_SetEvent(cb, "checkbox-changed", UpdateWindowOfTgt,"%p",tgt); cb = AG_CheckboxNewFlag(box, 0, _("Shading"), &tgt->flags, AG_BOX_SHADING); AG_SetEvent(cb, "checkbox-changed", UpdateWindowOfTgt,"%p",tgt); return (box); } #ifdef AG_LEGACY AG_HBox * AG_HBoxNew(void *p, Uint flags) { return AG_BoxNew(p, AG_BOX_HORIZ, flags); } AG_VBox * AG_VBoxNew(void *p, Uint flags) { return AG_BoxNew(p, AG_BOX_VERT, flags); } #endif /* AG_LEGACY */ AG_WidgetClass agBoxClass = { { "Agar(Widget:Box)", sizeof(AG_Box), { 1,0, AGC_BOX, 0xE010 }, Init, NULL, /* reset */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ Edit }, Draw, SizeRequest, SizeAllocate, MouseButtonDown, NULL, /* mouse_button_up */ NULL, /* mouse_motion */ NULL, /* key_down */ NULL, /* key_up */ NULL, /* touch */ NULL, /* ctrl */ NULL /* joy */ }; #endif /* AG_WIDGETS */