DVX_GUI/dvx/widgets/widgetCore.c

258 lines
6.6 KiB
C

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