440 lines
12 KiB
C
440 lines
12 KiB
C
// widgetOps.c — Paint dispatcher and public widget operations
|
|
|
|
#include "widgetInternal.h"
|
|
|
|
|
|
// ============================================================
|
|
// debugContainerBorder
|
|
// ============================================================
|
|
//
|
|
// Draw a 1px border in a garish neon color derived from the widget
|
|
// pointer so every container gets a distinct, ugly color.
|
|
|
|
static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops) {
|
|
static const uint8_t palette[][3] = {
|
|
{255, 0, 255}, // magenta
|
|
{ 0, 255, 0}, // lime
|
|
{255, 255, 0}, // yellow
|
|
{ 0, 255, 255}, // cyan
|
|
{255, 128, 0}, // orange
|
|
{128, 0, 255}, // purple
|
|
{255, 0, 128}, // hot pink
|
|
{ 0, 128, 255}, // sky blue
|
|
{128, 255, 0}, // chartreuse
|
|
{255, 64, 64}, // salmon
|
|
{ 64, 255, 128}, // mint
|
|
{255, 128, 255}, // orchid
|
|
};
|
|
|
|
uint32_t h = (uint32_t)(uintptr_t)w * 2654435761u;
|
|
int32_t idx = (int32_t)((h >> 16) % 12);
|
|
uint32_t color = packColor(d, palette[idx][0], palette[idx][1], palette[idx][2]);
|
|
|
|
drawHLine(d, ops, w->x, w->y, w->w, color);
|
|
drawHLine(d, ops, w->x, w->y + w->h - 1, w->w, color);
|
|
drawVLine(d, ops, w->x, w->y, w->h, color);
|
|
drawVLine(d, ops, w->x + w->w - 1, w->y, w->h, color);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetPaintOne
|
|
// ============================================================
|
|
//
|
|
// Paint a single widget and its children. Dispatches to per-widget
|
|
// paint functions defined in their respective files.
|
|
|
|
void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
|
if (!w->visible) {
|
|
return;
|
|
}
|
|
|
|
switch (w->type) {
|
|
case WidgetVBoxE:
|
|
case WidgetHBoxE:
|
|
// Containers are transparent — just paint children
|
|
break;
|
|
|
|
case WidgetFrameE:
|
|
widgetFramePaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetImageE:
|
|
widgetImagePaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetCanvasE:
|
|
widgetCanvasPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetLabelE:
|
|
widgetLabelPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetButtonE:
|
|
widgetButtonPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetCheckboxE:
|
|
widgetCheckboxPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetRadioE:
|
|
widgetRadioPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetTextInputE:
|
|
widgetTextInputPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetSpacerE:
|
|
// Invisible — draws nothing
|
|
break;
|
|
|
|
case WidgetSeparatorE:
|
|
widgetSeparatorPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetDropdownE:
|
|
widgetDropdownPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetComboBoxE:
|
|
widgetComboBoxPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetProgressBarE:
|
|
widgetProgressBarPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetSliderE:
|
|
widgetSliderPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetTabControlE:
|
|
widgetTabControlPaint(w, d, ops, font, colors);
|
|
if (sDebugLayout) {
|
|
debugContainerBorder(w, d, ops);
|
|
}
|
|
return; // handles its own children
|
|
|
|
case WidgetStatusBarE:
|
|
widgetStatusBarPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetToolbarE:
|
|
widgetToolbarPaint(w, d, ops, font, colors);
|
|
break;
|
|
|
|
case WidgetTreeViewE:
|
|
widgetTreeViewPaint(w, d, ops, font, colors);
|
|
if (sDebugLayout) {
|
|
debugContainerBorder(w, d, ops);
|
|
}
|
|
return; // handles its own children
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Paint children (TabControl and TreeView return early above)
|
|
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
|
widgetPaintOne(c, d, ops, font, colors);
|
|
}
|
|
|
|
// Debug: draw container borders on top of children
|
|
if (sDebugLayout && w->firstChild) {
|
|
debugContainerBorder(w, d, ops);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetPaintOverlays
|
|
// ============================================================
|
|
//
|
|
// Paints popup overlays (open dropdowns/comboboxes) on top of
|
|
// the widget tree. Called after the main paint pass.
|
|
|
|
void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
|
if (!sOpenPopup) {
|
|
return;
|
|
}
|
|
|
|
// Verify the popup belongs to this widget tree
|
|
WidgetT *check = sOpenPopup;
|
|
|
|
while (check->parent) {
|
|
check = check->parent;
|
|
}
|
|
|
|
if (check != root) {
|
|
return;
|
|
}
|
|
|
|
if (sOpenPopup->type == WidgetDropdownE) {
|
|
widgetDropdownPaintPopup(sOpenPopup, d, ops, font, colors);
|
|
} else if (sOpenPopup->type == WidgetComboBoxE) {
|
|
widgetComboBoxPaintPopup(sOpenPopup, d, ops, font, colors);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtDestroy
|
|
// ============================================================
|
|
|
|
void wgtDestroy(WidgetT *w) {
|
|
if (!w) {
|
|
return;
|
|
}
|
|
|
|
if (w->parent) {
|
|
widgetRemoveChild(w->parent, w);
|
|
}
|
|
|
|
widgetDestroyChildren(w);
|
|
|
|
if (w->type == WidgetTextInputE) {
|
|
free(w->as.textInput.buf);
|
|
} else if (w->type == WidgetTextAreaE) {
|
|
free(w->as.textArea.buf);
|
|
} else if (w->type == WidgetComboBoxE) {
|
|
free(w->as.comboBox.buf);
|
|
} else if (w->type == WidgetImageE) {
|
|
free(w->as.image.data);
|
|
} else if (w->type == WidgetCanvasE) {
|
|
free(w->as.canvas.data);
|
|
}
|
|
|
|
// Clear static references
|
|
if (sOpenPopup == w) {
|
|
sOpenPopup = NULL;
|
|
}
|
|
|
|
if (sDragSlider == w) {
|
|
sDragSlider = NULL;
|
|
}
|
|
|
|
if (sDrawingCanvas == w) {
|
|
sDrawingCanvas = NULL;
|
|
}
|
|
|
|
// If this is the root, clear the window's reference
|
|
if (w->window && w->window->widgetRoot == w) {
|
|
w->window->widgetRoot = NULL;
|
|
}
|
|
|
|
free(w);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtFind
|
|
// ============================================================
|
|
|
|
WidgetT *wgtFind(WidgetT *root, const char *name) {
|
|
if (!root || !name) {
|
|
return NULL;
|
|
}
|
|
|
|
if (root->name[0] && strcmp(root->name, name) == 0) {
|
|
return root;
|
|
}
|
|
|
|
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
|
|
WidgetT *found = wgtFind(c, name);
|
|
|
|
if (found) {
|
|
return found;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtGetText
|
|
// ============================================================
|
|
|
|
const char *wgtGetText(const WidgetT *w) {
|
|
if (!w) {
|
|
return "";
|
|
}
|
|
|
|
switch (w->type) {
|
|
case WidgetLabelE: return w->as.label.text;
|
|
case WidgetButtonE: return w->as.button.text;
|
|
case WidgetCheckboxE: return w->as.checkbox.text;
|
|
case WidgetRadioE: return w->as.radio.text;
|
|
case WidgetTextInputE: return w->as.textInput.buf ? w->as.textInput.buf : "";
|
|
case WidgetComboBoxE: return w->as.comboBox.buf ? w->as.comboBox.buf : "";
|
|
case WidgetTreeItemE: return w->as.treeItem.text ? w->as.treeItem.text : "";
|
|
case WidgetDropdownE:
|
|
if (w->as.dropdown.selectedIdx >= 0 && w->as.dropdown.selectedIdx < w->as.dropdown.itemCount) {
|
|
return w->as.dropdown.items[w->as.dropdown.selectedIdx];
|
|
}
|
|
return "";
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtInitWindow
|
|
// ============================================================
|
|
|
|
WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
|
|
WidgetT *root = widgetAlloc(NULL, WidgetVBoxE);
|
|
|
|
if (!root) {
|
|
return NULL;
|
|
}
|
|
|
|
root->window = win;
|
|
root->userData = ctx;
|
|
|
|
win->widgetRoot = root;
|
|
win->onPaint = widgetOnPaint;
|
|
win->onMouse = widgetOnMouse;
|
|
win->onKey = widgetOnKey;
|
|
win->onResize = widgetOnResize;
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtInvalidate
|
|
// ============================================================
|
|
|
|
void wgtInvalidate(WidgetT *w) {
|
|
if (!w || !w->window) {
|
|
return;
|
|
}
|
|
|
|
// Find the root
|
|
WidgetT *root = w;
|
|
|
|
while (root->parent) {
|
|
root = root->parent;
|
|
}
|
|
|
|
AppContextT *ctx = (AppContextT *)root->userData;
|
|
|
|
if (!ctx) {
|
|
return;
|
|
}
|
|
|
|
// Manage scrollbars (measures, adds/removes scrollbars, relayouts)
|
|
widgetManageScrollbars(w->window, ctx);
|
|
|
|
// Repaint
|
|
RectT fullRect = {0, 0, w->window->contentW, w->window->contentH};
|
|
widgetOnPaint(w->window, &fullRect);
|
|
w->window->contentDirty = true;
|
|
|
|
// Dirty the window on screen
|
|
dvxInvalidateWindow(ctx, w->window);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtPaint
|
|
// ============================================================
|
|
|
|
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
widgetPaintOne(root, d, ops, font, colors);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtSetDebugLayout
|
|
// ============================================================
|
|
|
|
void wgtSetDebugLayout(bool enabled) {
|
|
sDebugLayout = enabled;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtSetEnabled
|
|
// ============================================================
|
|
|
|
void wgtSetEnabled(WidgetT *w, bool enabled) {
|
|
if (w) {
|
|
w->enabled = enabled;
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtSetText
|
|
// ============================================================
|
|
|
|
void wgtSetText(WidgetT *w, const char *text) {
|
|
if (!w) {
|
|
return;
|
|
}
|
|
|
|
switch (w->type) {
|
|
case WidgetLabelE:
|
|
w->as.label.text = text;
|
|
break;
|
|
|
|
case WidgetButtonE:
|
|
w->as.button.text = text;
|
|
break;
|
|
|
|
case WidgetCheckboxE:
|
|
w->as.checkbox.text = text;
|
|
break;
|
|
|
|
case WidgetRadioE:
|
|
w->as.radio.text = text;
|
|
break;
|
|
|
|
case WidgetTextInputE:
|
|
if (w->as.textInput.buf) {
|
|
strncpy(w->as.textInput.buf, text, w->as.textInput.bufSize - 1);
|
|
w->as.textInput.buf[w->as.textInput.bufSize - 1] = '\0';
|
|
w->as.textInput.len = (int32_t)strlen(w->as.textInput.buf);
|
|
w->as.textInput.cursorPos = w->as.textInput.len;
|
|
w->as.textInput.scrollOff = 0;
|
|
}
|
|
break;
|
|
|
|
case WidgetComboBoxE:
|
|
if (w->as.comboBox.buf) {
|
|
strncpy(w->as.comboBox.buf, text, w->as.comboBox.bufSize - 1);
|
|
w->as.comboBox.buf[w->as.comboBox.bufSize - 1] = '\0';
|
|
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
|
|
w->as.comboBox.cursorPos = w->as.comboBox.len;
|
|
w->as.comboBox.scrollOff = 0;
|
|
}
|
|
break;
|
|
|
|
case WidgetTreeItemE:
|
|
w->as.treeItem.text = text;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtSetVisible
|
|
// ============================================================
|
|
|
|
void wgtSetVisible(WidgetT *w, bool visible) {
|
|
if (w) {
|
|
w->visible = visible;
|
|
}
|
|
}
|