// 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; } }