282 lines
9.3 KiB
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);
|
|
}
|