258 lines
6.6 KiB
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;
|
|
}
|
|
}
|