#define DVX_WIDGET_IMPL // widgetWrapBox.c -- Flow/wrap layout container // // Lays out children left-to-right, wrapping to the next row when // the available width is exceeded. Each row's height is the max // child height in that row. Supports spacing between items and // rows. Supports per-row alignment (center, right) for short rows. #include "dvxWidgetPlugin.h" #include #include // ============================================================ // Constants // ============================================================ #define DEFAULT_PAD 2 #define DEFAULT_GAP 4 // ============================================================ // Type ID and per-instance data // ============================================================ static int32_t sTypeId = -1; typedef struct { int32_t wrappedH; // actual height from last layout pass (-1 = not yet computed) } WrapBoxDataT; // ============================================================ // widgetWrapBoxCalcMinSize // ============================================================ void widgetWrapBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) { int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth); int32_t maxChildW = 0; int32_t maxChildH = 0; if (pad == 0) { pad = DEFAULT_PAD; } for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { if (!c->visible) { continue; } widgetCalcMinSizeTree(c, font); if (c->calcMinW > maxChildW) { maxChildW = c->calcMinW; } if (c->calcMinH > maxChildH) { maxChildH = c->calcMinH; } } w->calcMinW = maxChildW + pad * 2; WrapBoxDataT *d = (WrapBoxDataT *)w->data; if (d && d->wrappedH > 0) { w->calcMinH = d->wrappedH; } else { w->calcMinH = maxChildH + pad * 2; } } // ============================================================ // widgetWrapBoxLayout // ============================================================ // // Three-pass layout: // 1. Place children left-to-right with wrapping // 2. Apply per-row alignment offsets // 3. Recurse into child containers (so children see final positions) void widgetWrapBoxLayout(WidgetT *w, const BitmapFontT *font) { int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth); int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth); if (pad == 0) { pad = DEFAULT_PAD; } if (gap == 0) { gap = DEFAULT_GAP; } int32_t availW = w->w - pad * 2; int32_t baseX = w->x + pad; // Pass 1: position children left-aligned with wrapping int32_t curX = baseX; int32_t curY = w->y + pad; int32_t rowH = 0; for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { if (!c->visible) { continue; } int32_t childW = c->calcMinW; int32_t childH = c->calcMinH; if (curX > baseX && (curX - baseX) + childW > availW) { curX = baseX; curY += rowH + gap; rowH = 0; } c->x = curX; c->y = curY; c->w = childW; c->h = childH; if (childH > rowH) { rowH = childH; } curX += childW + gap; } // Pass 2: apply alignment offsets per row if (w->align == AlignCenterE || w->align == AlignEndE) { WidgetT *rowStart = NULL; int32_t rowY = -1; int32_t rowEndX = 0; for (WidgetT *c = w->firstChild; ; c = c ? c->nextSibling : NULL) { bool newRow = false; if (!c) { newRow = (rowStart != NULL); } else if (c->visible && c->y != rowY && rowStart != NULL) { newRow = true; } if (newRow) { int32_t rowW = rowEndX - baseX; int32_t slack = availW - rowW; if (slack > 0) { int32_t offset = (w->align == AlignCenterE) ? slack / 2 : slack; for (WidgetT *r = rowStart; r != c; r = r->nextSibling) { if (r->visible && r->y == rowY) { r->x += offset; } } } rowStart = NULL; } if (!c) { break; } if (!c->visible) { continue; } if (rowStart == NULL || c->y != rowY) { rowStart = c; rowY = c->y; } rowEndX = c->x + c->w; } } // Pass 3: recurse into child containers AFTER alignment // so children see their parent's final position. for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { if (c->visible) { widgetLayoutChildren(c, font); } } // Store actual wrapped height for subsequent measure passes. int32_t usedH = (curY + rowH + pad) - w->y; WrapBoxDataT *d = (WrapBoxDataT *)w->data; if (d) { d->wrappedH = usedH; } if (usedH > w->calcMinH) { w->calcMinH = usedH; } } // ============================================================ // widgetWrapBoxPaint // ============================================================ void widgetWrapBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { (void)w; (void)disp; (void)ops; (void)font; (void)colors; } // ============================================================ // DXE registration // ============================================================ // ============================================================ // BASIC-facing accessors // ============================================================ static int32_t wrapBoxGetAlign(const WidgetT *w) { return (int32_t)w->align; } static void wrapBoxSetAlign(WidgetT *w, int32_t align) { w->align = (WidgetAlignE)align; wgtInvalidate(w); } static const char *sAlignNames[] = { "Left", "Center", "Right", NULL }; static const WgtPropDescT sProps[] = { { "Alignment", WGT_IFACE_ENUM, (void *)wrapBoxGetAlign, (void *)wrapBoxSetAlign, sAlignNames } }; // ============================================================ // DXE registration // ============================================================ static void wrapBoxDestroy(WidgetT *w) { free(w->data); } static const WidgetClassT sClass = { .version = WGT_CLASS_VERSION, .flags = 0, .handlers = { [WGT_METHOD_PAINT] = (void *)widgetWrapBoxPaint, [WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetWrapBoxCalcMinSize, [WGT_METHOD_LAYOUT] = (void *)widgetWrapBoxLayout, [WGT_METHOD_DESTROY] = (void *)wrapBoxDestroy, } }; static WidgetT *wrapBoxCreate(WidgetT *parent) { if (!parent) { return NULL; } WidgetT *w = widgetAlloc(parent, sTypeId); if (w) { WrapBoxDataT *d = (WrapBoxDataT *)calloc(1, sizeof(WrapBoxDataT)); if (d) { d->wrappedH = -1; } w->data = d; } return w; } static const struct { WidgetT *(*create)(WidgetT *parent); } sApi = { .create = wrapBoxCreate }; static const WgtIfaceT sIface = { .basName = "WrapBox", .props = sProps, .propCount = 1, .methods = NULL, .methodCount = 0, .events = NULL, .eventCount = 0, .createSig = WGT_CREATE_PARENT, .isContainer = true, .defaultEvent = "Click", .namePrefix = "WrapBox" }; void wgtRegister(void) { sTypeId = wgtRegisterClass(&sClass); wgtRegisterApi("wrapbox", &sApi); wgtRegisterIface("wrapbox", &sIface); }