#define DVX_WIDGET_IMPL // 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 "dvxWidgetPlugin.h" static int32_t sVBoxTypeId = -1; static int32_t sHBoxTypeId = -1; static int32_t sFrameTypeId = -1; typedef enum { FrameInE, FrameOutE, FrameFlatE } FrameStyleE; typedef struct { const char *title; FrameStyleE style; uint32_t color; } FrameDataT; // ============================================================ // 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) { FrameDataT *fd = (FrameDataT *)w->data; 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 (fd->style == FrameFlatE) { // Flat: solid color rectangle outline uint32_t fc = fd->color ? fd->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 (fd->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 (fd->title && fd->title[0]) { int32_t titleW = textWidthAccel(font, fd->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, fd->title, colors); } else { drawTextAccel(d, ops, font, titleX, titleY, fd->title, fg, bg, true); } } } // ============================================================ // widgetFrameGetLayoutMetrics // ============================================================ void widgetFrameGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW) { FrameDataT *fd = (FrameDataT *)w->data; *pad = DEFAULT_PADDING; *gap = 0; *extraTop = font ? font->charHeight / 2 : 0; *borderW = (fd && fd->style == FrameFlatE) ? 1 : 2; } // ============================================================ // widgetFrameDestroy // ============================================================ void widgetFrameDestroy(WidgetT *w) { free(w->data); } // ============================================================ // DXE registration // ============================================================ static const WidgetClassT sClassVBox = { .version = WGT_CLASS_VERSION, .flags = WCLASS_BOX_CONTAINER, }; static const WidgetClassT sClassHBox = { .version = WGT_CLASS_VERSION, .flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER, }; static const WidgetClassT sClassFrame = { .version = WGT_CLASS_VERSION, .flags = WCLASS_BOX_CONTAINER | WCLASS_FOCUS_FORWARD, .handlers = { [WGT_METHOD_PAINT] = (void *)widgetFramePaint, [WGT_METHOD_DESTROY] = (void *)widgetFrameDestroy, [WGT_METHOD_GET_LAYOUT_METRICS] = (void *)widgetFrameGetLayoutMetrics, } }; // ============================================================ // Widget creation functions // ============================================================ WidgetT *wgtFrame(WidgetT *parent, const char *title) { WidgetT *w = widgetAlloc(parent, sFrameTypeId); if (w) { FrameDataT *fd = calloc(1, sizeof(FrameDataT)); fd->title = title; fd->style = FrameInE; fd->color = 0; w->data = fd; w->accelKey = accelParse(title); } return w; } WidgetT *wgtHBox(WidgetT *parent) { return widgetAlloc(parent, sHBoxTypeId); } WidgetT *wgtVBox(WidgetT *parent) { return widgetAlloc(parent, sVBoxTypeId); } // ============================================================ // DXE registration // ============================================================ static const struct { WidgetT *(*vBox)(WidgetT *parent); WidgetT *(*hBox)(WidgetT *parent); WidgetT *(*frame)(WidgetT *parent, const char *title); } sApi = { .vBox = wgtVBox, .hBox = wgtHBox, .frame = wgtFrame }; static const WgtIfaceT sIface = { .basName = "Frame", .props = NULL, .propCount = 0, .methods = NULL, .methodCount = 0, .events = NULL, .eventCount = 0 }; void wgtRegister(void) { sVBoxTypeId = wgtRegisterClass(&sClassVBox); sHBoxTypeId = wgtRegisterClass(&sClassHBox); sFrameTypeId = wgtRegisterClass(&sClassFrame); wgtRegisterApi("box", &sApi); wgtRegisterIface("box", &sIface); }