#define DVX_WIDGET_IMPL // widgetProgressBar.c -- ProgressBar widget // // A non-interactive fill-bar widget for displaying bounded progress. // Supports both horizontal and vertical orientations via separate // constructors rather than a runtime flag, because orientation is // typically fixed at UI construction time and the two constructors // read more clearly at the call site. // // Rendering: sunken 2px bevel border (inverted highlight/shadow to // look recessed) with a solid fill rect proportional to value/maxValue. // Uses integer division for fill size -- no floating point, which would // be prohibitively expensive on a 486 without an FPU. // // The widget has no mouse or keyboard handlers -- it is purely display. // Value is controlled externally through the set/get API. #include "dvxWgtP.h" static int32_t sTypeId = -1; typedef struct { int32_t value; int32_t maxValue; bool vertical; } ProgressBarDataT; // ============================================================ // widgetProgressBarCalcMinSize // ============================================================ // Min size is based on font metrics to keep the bar proportional to // surrounding text. The cross-axis uses charHeight+4 (room for the // bar plus 2px bevel on each side). The main axis uses 12 char widths // so the bar is wide enough to show meaningful progress granularity. void widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font) { ProgressBarDataT *d = (ProgressBarDataT *)w->data; if (d->vertical) { w->calcMinW = font->charHeight + 4; w->calcMinH = font->charWidth * 12; } else { w->calcMinW = font->charWidth * 12; w->calcMinH = font->charHeight + 4; } } // ============================================================ // widgetProgressBarPaint // ============================================================ // Paint uses a two-pass approach: first the sunken bevel border, then // the fill rect. The fill color defaults to activeTitleBg (the window // title bar color) to provide strong visual contrast inside the // recessed trough -- matching classic Win3.1/Motif progress bar style. // When disabled, the fill becomes windowShadow to look grayed out. // // Vertical bars fill from bottom-up (natural "filling" metaphor), // which requires computing the Y offset as (innerH - fillH). void widgetProgressBarPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { (void)font; ProgressBarDataT *d = (ProgressBarDataT *)w->data; uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->activeTitleBg) : colors->windowShadow; uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg; // Sunken border -- highlight/shadow are swapped vs raised bevel // to create the "pressed in" trough appearance BevelStyleT bevel; bevel.highlight = colors->windowShadow; bevel.shadow = colors->windowHighlight; bevel.face = bg; bevel.width = 2; drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel); // Fill bar int32_t maxVal = d->maxValue; if (maxVal <= 0) { maxVal = 100; } int32_t innerW = w->w - 4; int32_t innerH = w->h - 4; if (d->vertical) { int32_t fillH = (innerH * d->value) / maxVal; if (fillH > innerH) { fillH = innerH; } if (fillH > 0) { rectFill(disp, ops, w->x + 2, w->y + 2 + innerH - fillH, innerW, fillH, fg); } } else { int32_t fillW = (innerW * d->value) / maxVal; if (fillW > innerW) { fillW = innerW; } if (fillW > 0) { rectFill(disp, ops, w->x + 2, w->y + 2, fillW, innerH, fg); } } } // ============================================================ // widgetProgressBarDestroy // ============================================================ void widgetProgressBarDestroy(WidgetT *w) { free(w->data); } // ============================================================ // DXE registration // ============================================================ static const WidgetClassT sClassProgressBar = { .version = WGT_CLASS_VERSION, .flags = 0, .handlers = { [WGT_METHOD_PAINT] = (void *)widgetProgressBarPaint, [WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetProgressBarCalcMinSize, [WGT_METHOD_DESTROY] = (void *)widgetProgressBarDestroy, } }; // ============================================================ // Widget creation functions // ============================================================ WidgetT *wgtProgressBar(WidgetT *parent) { WidgetT *w = widgetAlloc(parent, sTypeId); if (w) { ProgressBarDataT *d = calloc(1, sizeof(ProgressBarDataT)); d->value = 0; d->maxValue = 100; d->vertical = false; w->data = d; } return w; } WidgetT *wgtProgressBarV(WidgetT *parent) { WidgetT *w = widgetAlloc(parent, sTypeId); if (w) { ProgressBarDataT *d = calloc(1, sizeof(ProgressBarDataT)); d->value = 0; d->maxValue = 100; d->vertical = true; w->data = d; } return w; } int32_t wgtProgressBarGetValue(const WidgetT *w) { VALIDATE_WIDGET(w, sTypeId, 0); ProgressBarDataT *d = (ProgressBarDataT *)w->data; return d->value; } void wgtProgressBarSetValue(WidgetT *w, int32_t value) { VALIDATE_WIDGET_VOID(w, sTypeId); ProgressBarDataT *d = (ProgressBarDataT *)w->data; if (value < 0) { value = 0; } if (value > d->maxValue) { value = d->maxValue; } d->value = value; wgtInvalidatePaint(w); } // ============================================================ // DXE registration // ============================================================ static const struct { WidgetT *(*create)(WidgetT *parent); WidgetT *(*createV)(WidgetT *parent); void (*setValue)(WidgetT *w, int32_t value); int32_t (*getValue)(const WidgetT *w); } sApi = { .create = wgtProgressBar, .createV = wgtProgressBarV, .setValue = wgtProgressBarSetValue, .getValue = wgtProgressBarGetValue }; static const WgtPropDescT sProps[] = { { "Value", WGT_IFACE_INT, (void *)wgtProgressBarGetValue, (void *)wgtProgressBarSetValue, NULL } }; static const WgtIfaceT sIface = { .basName = "ProgressBar", .props = sProps, .propCount = 1, .methods = NULL, .methodCount = 0, .events = NULL, .eventCount = 0, .createSig = WGT_CREATE_PARENT }; void wgtRegister(void) { sTypeId = wgtRegisterClass(&sClassProgressBar); wgtRegisterApi("progressbar", &sApi); wgtRegisterIface("progressbar", &sIface); }