DVX_GUI/widgets/box/widgetBox.c

282 lines
9.3 KiB
C

#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 "dvxWgtP.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 + 2) : 0;
*borderW = (fd && fd->style == FrameFlatE) ? 1 : 2;
}
// ============================================================
// widgetFrameDestroy
// ============================================================
void widgetFrameDestroy(WidgetT *w) {
FrameDataT *fd = (FrameDataT *)w->data;
free((void *)fd->title);
free(w->data);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassVBox = {
.version = WGT_CLASS_VERSION,
.flags = 0,
.handlers = {
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
}
};
static const WidgetClassT sClassHBox = {
.version = WGT_CLASS_VERSION,
.flags = WCLASS_HORIZ_CONTAINER,
.handlers = {
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
}
};
static const WidgetClassT sClassFrame = {
.version = WGT_CLASS_VERSION,
.flags = WCLASS_FOCUS_FORWARD,
.handlers = {
[WGT_METHOD_PAINT] = (void *)widgetFramePaint,
[WGT_METHOD_DESTROY] = (void *)widgetFrameDestroy,
[WGT_METHOD_GET_LAYOUT_METRICS] = (void *)widgetFrameGetLayoutMetrics,
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
}
};
// ============================================================
// 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 ? strdup(title) : NULL;
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
};
// Separate API structs for each box type so the designer can
// create them independently. Each has create as the first slot.
static const struct { WidgetT *(*create)(WidgetT *); } sVBoxApi = { .create = wgtVBox };
static const struct { WidgetT *(*create)(WidgetT *); } sHBoxApi = { .create = wgtHBox };
static const struct { WidgetT *(*create)(WidgetT *, const char *); } sFrameApi = { .create = wgtFrame };
static const WgtIfaceT sIfaceVBox = {
.basName = "VBox",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT,
.isContainer = true,
.defaultEvent = "Click"
};
static const WgtIfaceT sIfaceHBox = {
.basName = "HBox",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT,
.isContainer = true,
.defaultEvent = "Click"
};
static const WgtIfaceT sIfaceFrame = {
.basName = "Frame",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_TEXT,
.isContainer = true,
.defaultEvent = "Click"
};
void wgtRegister(void) {
sVBoxTypeId = wgtRegisterClass(&sClassVBox);
sHBoxTypeId = wgtRegisterClass(&sClassHBox);
sFrameTypeId = wgtRegisterClass(&sClassFrame);
// Original combined API (for widgetBox.h compatibility)
wgtRegisterApi("box", &sApi);
// Per-type APIs and ifaces (for designer/toolbox)
wgtRegisterApi("vbox", &sVBoxApi);
wgtRegisterIface("vbox", &sIfaceVBox);
wgtRegisterApi("hbox", &sHBoxApi);
wgtRegisterIface("hbox", &sIfaceHBox);
wgtRegisterApi("frame", &sFrameApi);
wgtRegisterIface("frame", &sIfaceFrame);
}