// widgetCore.c — Core widget infrastructure (alloc, tree ops, helpers) #include "widgetInternal.h" // ============================================================ // Global state for drag and popup tracking // ============================================================ bool sDebugLayout = false; WidgetT *sOpenPopup = NULL; WidgetT *sDragSlider = NULL; WidgetT *sDrawingCanvas = NULL; int32_t sDragOffset = 0; // ============================================================ // widgetAddChild // ============================================================ void widgetAddChild(WidgetT *parent, WidgetT *child) { child->parent = parent; child->nextSibling = NULL; if (parent->lastChild) { parent->lastChild->nextSibling = child; parent->lastChild = child; } else { parent->firstChild = child; parent->lastChild = child; } } // ============================================================ // widgetAlloc // ============================================================ WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type) { WidgetT *w = (WidgetT *)malloc(sizeof(WidgetT)); if (!w) { return NULL; } memset(w, 0, sizeof(*w)); w->type = type; w->visible = true; w->enabled = true; if (parent) { w->window = parent->window; widgetAddChild(parent, w); } return w; } // ============================================================ // widgetCountVisibleChildren // ============================================================ int32_t widgetCountVisibleChildren(const WidgetT *w) { int32_t count = 0; for (const WidgetT *c = w->firstChild; c; c = c->nextSibling) { if (c->visible) { count++; } } return count; } // ============================================================ // widgetDestroyChildren // ============================================================ void widgetDestroyChildren(WidgetT *w) { WidgetT *child = w->firstChild; while (child) { WidgetT *next = child->nextSibling; widgetDestroyChildren(child); if (child->type == WidgetTextInputE) { free(child->as.textInput.buf); } else if (child->type == WidgetTextAreaE) { free(child->as.textArea.buf); } else if (child->type == WidgetComboBoxE) { free(child->as.comboBox.buf); } else if (child->type == WidgetImageE) { free(child->as.image.data); } else if (child->type == WidgetCanvasE) { free(child->as.canvas.data); } // Clear popup/drag references if they point to destroyed widgets if (sOpenPopup == child) { sOpenPopup = NULL; } if (sDragSlider == child) { sDragSlider = NULL; } if (sDrawingCanvas == child) { sDrawingCanvas = NULL; } free(child); child = next; } w->firstChild = NULL; w->lastChild = NULL; } // ============================================================ // widgetDropdownPopupRect // ============================================================ // // Calculate the rectangle for a dropdown/combobox popup list. void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) { int32_t itemCount = 0; if (w->type == WidgetDropdownE) { itemCount = w->as.dropdown.itemCount; } else if (w->type == WidgetComboBoxE) { itemCount = w->as.comboBox.itemCount; } int32_t visibleItems = itemCount; if (visibleItems > DROPDOWN_MAX_VISIBLE) { visibleItems = DROPDOWN_MAX_VISIBLE; } if (visibleItems < 1) { visibleItems = 1; } *popX = w->x; *popW = w->w; *popH = visibleItems * font->charHeight + 4; // 2px border each side // Try below first, then above if no room if (w->y + w->h + *popH <= contentH) { *popY = w->y + w->h; } else { *popY = w->y - *popH; if (*popY < 0) { *popY = 0; } } } // ============================================================ // widgetFrameBorderWidth // ============================================================ int32_t widgetFrameBorderWidth(const WidgetT *w) { if (w->type != WidgetFrameE) { return 0; } if (w->as.frame.style == FrameFlatE) { return FRAME_FLAT_BORDER; } return FRAME_BEVEL_BORDER; } // ============================================================ // widgetHitTest // ============================================================ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) { if (!w->visible) { return NULL; } if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) { return NULL; } // Check children — take the last match (topmost in Z-order) WidgetT *hit = NULL; for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { WidgetT *childHit = widgetHitTest(c, x, y); if (childHit) { hit = childHit; } } return hit ? hit : w; } // ============================================================ // widgetIsBoxContainer // ============================================================ // // Returns true for widget types that use the generic box layout. bool widgetIsBoxContainer(WidgetTypeE type) { return type == WidgetVBoxE || type == WidgetHBoxE || type == WidgetFrameE || type == WidgetRadioGroupE || type == WidgetTabPageE || type == WidgetStatusBarE || type == WidgetToolbarE; } // ============================================================ // widgetIsHorizContainer // ============================================================ // // Returns true for container types that lay out children horizontally. bool widgetIsHorizContainer(WidgetTypeE type) { return type == WidgetHBoxE || type == WidgetStatusBarE || type == WidgetToolbarE; } // ============================================================ // widgetRemoveChild // ============================================================ void widgetRemoveChild(WidgetT *parent, WidgetT *child) { WidgetT *prev = NULL; for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) { if (c == child) { if (prev) { prev->nextSibling = c->nextSibling; } else { parent->firstChild = c->nextSibling; } if (parent->lastChild == child) { parent->lastChild = prev; } child->nextSibling = NULL; child->parent = NULL; return; } prev = c; } }