// widgetBox.c — VBox, HBox, and Frame container widgets // // VBox and HBox are the primary layout containers. They have no visual // representation of their own — they exist purely to arrange children // vertically or horizontally. The actual layout algorithm lives in // widgetLayout.c (widgetCalcMinSizeBox / widgetLayoutBox) which handles // weight-based space distribution, spacing, padding, and alignment. // // VBox and HBox are distinguished by a flag (WCLASS_HORIZ_CONTAINER) in // the class table rather than having separate code. This keeps the layout // engine unified — the same algorithm works in both orientations by // swapping which axis is "major" vs "minor". // // Frame is a labeled grouping box with a Motif-style beveled border. // It acts as a VBox for layout purposes (children stack vertically inside // the frame's padded interior). The title text sits centered vertically // on the top border line, with a small background-filled gap to visually // "break" the border behind the title — this is the classic Win3.1/Motif // group box appearance. #include "widgetInternal.h" // ============================================================ // widgetFramePaint // ============================================================ // Paint the frame border and optional title. The border is offset down by // half the font height so the title text can sit centered on the top edge. // This creates the illusion of the title "interrupting" the border — a // background-colored rectangle is drawn behind the title to erase the // border pixels, then the title is drawn on top. // // Three border styles are supported: // FrameFlatE — single-pixel solid color outline // FrameInE — Motif "groove" (inset bevel: shadow-then-highlight) // FrameOutE — Motif "ridge" (outset bevel: highlight-then-shadow) // The groove/ridge are each two nested 1px bevels with swapped colors. void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { int32_t fb = widgetFrameBorderWidth(w); int32_t boxY = w->y + font->charHeight / 2; int32_t boxH = w->h - font->charHeight / 2; uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg; uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg; if (w->as.frame.style == FrameFlatE) { // Flat: solid color rectangle outline uint32_t fc = w->as.frame.color ? w->as.frame.color : colors->windowShadow; drawHLine(d, ops, w->x, boxY, w->w, fc); drawHLine(d, ops, w->x, boxY + boxH - 1, w->w, fc); drawVLine(d, ops, w->x, boxY, boxH, fc); drawVLine(d, ops, w->x + w->w - 1, boxY, boxH, fc); } else { // Beveled groove/ridge: two nested 1px bevels BevelStyleT outer; BevelStyleT inner; if (w->as.frame.style == FrameInE) { outer.highlight = colors->windowShadow; outer.shadow = colors->windowHighlight; inner.highlight = colors->windowHighlight; inner.shadow = colors->windowShadow; } else { outer.highlight = colors->windowHighlight; outer.shadow = colors->windowShadow; inner.highlight = colors->windowShadow; inner.shadow = colors->windowHighlight; } outer.face = 0; outer.width = 1; inner.face = 0; inner.width = 1; drawBevel(d, ops, w->x, boxY, w->w, boxH, &outer); drawBevel(d, ops, w->x + 1, boxY + 1, w->w - 2, boxH - 2, &inner); } // Draw title centered vertically on the top border line if (w->as.frame.title && w->as.frame.title[0]) { int32_t titleW = textWidthAccel(font, w->as.frame.title); int32_t titleX = w->x + DEFAULT_PADDING + fb; int32_t titleY = boxY + (fb - font->charHeight) / 2; rectFill(d, ops, titleX - 2, titleY, titleW + 4, font->charHeight, bg); if (!w->enabled) { drawTextAccelEmbossed(d, ops, font, titleX, titleY, w->as.frame.title, colors); } else { drawTextAccel(d, ops, font, titleX, titleY, w->as.frame.title, fg, bg, true); } } } // ============================================================ // wgtFrame // ============================================================ // Create a Frame container. The title string supports accelerator keys // (prefixed with '&') which are parsed by accelParse. The title pointer // is stored directly (not copied) — the caller must ensure it remains valid. WidgetT *wgtFrame(WidgetT *parent, const char *title) { WidgetT *w = widgetAlloc(parent, WidgetFrameE); if (w) { w->as.frame.title = title; w->as.frame.style = FrameInE; w->as.frame.color = 0; w->accelKey = accelParse(title); } return w; } // ============================================================ // wgtHBox // ============================================================ WidgetT *wgtHBox(WidgetT *parent) { return widgetAlloc(parent, WidgetHBoxE); } // ============================================================ // wgtVBox // ============================================================ WidgetT *wgtVBox(WidgetT *parent) { return widgetAlloc(parent, WidgetVBoxE); }