358 lines
11 KiB
C
358 lines
11 KiB
C
// widgetTreeView.c — TreeView and TreeItem widgets
|
|
|
|
#include "widgetInternal.h"
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
static int32_t calcTreeItemsHeight(WidgetT *parent, const BitmapFontT *font);
|
|
static int32_t calcTreeItemsMaxWidth(WidgetT *parent, const BitmapFontT *font, int32_t depth);
|
|
static void layoutTreeItems(WidgetT *parent, const BitmapFontT *font, int32_t x, int32_t *y, int32_t width, int32_t depth);
|
|
static void paintTreeItems(WidgetT *parent, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t baseX, int32_t *itemY, int32_t depth, int32_t clipTop, int32_t clipBottom);
|
|
static WidgetT *treeItemAtY(WidgetT *parent, int32_t targetY, int32_t *curY, const BitmapFontT *font);
|
|
|
|
|
|
// ============================================================
|
|
// calcTreeItemsHeight
|
|
// ============================================================
|
|
|
|
static int32_t calcTreeItemsHeight(WidgetT *parent, const BitmapFontT *font) {
|
|
int32_t totalH = 0;
|
|
|
|
for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) {
|
|
if (c->type != WidgetTreeItemE || !c->visible) {
|
|
continue;
|
|
}
|
|
|
|
totalH += font->charHeight;
|
|
|
|
if (c->as.treeItem.expanded && c->firstChild) {
|
|
totalH += calcTreeItemsHeight(c, font);
|
|
}
|
|
}
|
|
|
|
return totalH;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// calcTreeItemsMaxWidth
|
|
// ============================================================
|
|
|
|
static int32_t calcTreeItemsMaxWidth(WidgetT *parent, const BitmapFontT *font, int32_t depth) {
|
|
int32_t maxW = 0;
|
|
|
|
for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) {
|
|
if (c->type != WidgetTreeItemE || !c->visible) {
|
|
continue;
|
|
}
|
|
|
|
int32_t indent = depth * TREE_INDENT + TREE_EXPAND_SIZE + TREE_ICON_GAP;
|
|
int32_t textW = (int32_t)strlen(c->as.treeItem.text) * font->charWidth;
|
|
int32_t itemW = indent + textW;
|
|
|
|
if (itemW > maxW) {
|
|
maxW = itemW;
|
|
}
|
|
|
|
if (c->as.treeItem.expanded && c->firstChild) {
|
|
int32_t childW = calcTreeItemsMaxWidth(c, font, depth + 1);
|
|
|
|
if (childW > maxW) {
|
|
maxW = childW;
|
|
}
|
|
}
|
|
}
|
|
|
|
return maxW;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// layoutTreeItems
|
|
// ============================================================
|
|
|
|
static void layoutTreeItems(WidgetT *parent, const BitmapFontT *font, int32_t x, int32_t *y, int32_t width, int32_t depth) {
|
|
for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) {
|
|
if (c->type != WidgetTreeItemE || !c->visible) {
|
|
continue;
|
|
}
|
|
|
|
c->x = x;
|
|
c->y = *y;
|
|
c->w = width;
|
|
c->h = font->charHeight;
|
|
*y += font->charHeight;
|
|
|
|
if (c->as.treeItem.expanded && c->firstChild) {
|
|
layoutTreeItems(c, font, x, y, width, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// paintTreeItems
|
|
// ============================================================
|
|
|
|
static void paintTreeItems(WidgetT *parent, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t baseX, int32_t *itemY, int32_t depth, int32_t clipTop, int32_t clipBottom) {
|
|
for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) {
|
|
if (c->type != WidgetTreeItemE || !c->visible) {
|
|
continue;
|
|
}
|
|
|
|
int32_t ix = baseX + depth * TREE_INDENT;
|
|
int32_t iy = *itemY;
|
|
*itemY += font->charHeight;
|
|
|
|
// Skip items outside visible area
|
|
if (iy + font->charHeight <= clipTop || iy >= clipBottom) {
|
|
if (c->as.treeItem.expanded && c->firstChild) {
|
|
paintTreeItems(c, d, ops, font, colors, baseX, itemY, depth + 1, clipTop, clipBottom);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// Draw expand/collapse icon if item has children
|
|
bool hasChildren = false;
|
|
|
|
for (WidgetT *gc = c->firstChild; gc; gc = gc->nextSibling) {
|
|
if (gc->type == WidgetTreeItemE) {
|
|
hasChildren = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int32_t textX = ix;
|
|
|
|
if (hasChildren) {
|
|
int32_t iconX = ix;
|
|
int32_t iconY = iy + (font->charHeight - TREE_EXPAND_SIZE) / 2;
|
|
|
|
// Draw box
|
|
rectFill(d, ops, iconX + 1, iconY + 1, TREE_EXPAND_SIZE - 2, TREE_EXPAND_SIZE - 2, colors->contentBg);
|
|
drawHLine(d, ops, iconX, iconY, TREE_EXPAND_SIZE, colors->windowShadow);
|
|
drawHLine(d, ops, iconX, iconY + TREE_EXPAND_SIZE - 1, TREE_EXPAND_SIZE, colors->windowShadow);
|
|
drawVLine(d, ops, iconX, iconY, TREE_EXPAND_SIZE, colors->windowShadow);
|
|
drawVLine(d, ops, iconX + TREE_EXPAND_SIZE - 1, iconY, TREE_EXPAND_SIZE, colors->windowShadow);
|
|
|
|
// Draw + or -
|
|
int32_t mid = TREE_EXPAND_SIZE / 2;
|
|
drawHLine(d, ops, iconX + 2, iconY + mid, TREE_EXPAND_SIZE - 4, colors->contentFg);
|
|
|
|
if (!c->as.treeItem.expanded) {
|
|
drawVLine(d, ops, iconX + mid, iconY + 2, TREE_EXPAND_SIZE - 4, colors->contentFg);
|
|
}
|
|
|
|
textX += TREE_EXPAND_SIZE + TREE_ICON_GAP;
|
|
} else {
|
|
textX += TREE_EXPAND_SIZE + TREE_ICON_GAP;
|
|
}
|
|
|
|
// Draw text
|
|
uint32_t fg = c->fgColor ? c->fgColor : colors->contentFg;
|
|
uint32_t bg = c->bgColor ? c->bgColor : colors->contentBg;
|
|
drawText(d, ops, font, textX, iy, c->as.treeItem.text, fg, bg, false);
|
|
|
|
// Recurse into expanded children
|
|
if (c->as.treeItem.expanded && c->firstChild) {
|
|
paintTreeItems(c, d, ops, font, colors, baseX, itemY, depth + 1, clipTop, clipBottom);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// treeItemAtY
|
|
// ============================================================
|
|
//
|
|
// Find the tree item at a given Y coordinate.
|
|
|
|
static WidgetT *treeItemAtY(WidgetT *parent, int32_t targetY, int32_t *curY, const BitmapFontT *font) {
|
|
for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) {
|
|
if (c->type != WidgetTreeItemE || !c->visible) {
|
|
continue;
|
|
}
|
|
|
|
if (targetY >= *curY && targetY < *curY + font->charHeight) {
|
|
return c;
|
|
}
|
|
|
|
*curY += font->charHeight;
|
|
|
|
if (c->as.treeItem.expanded && c->firstChild) {
|
|
WidgetT *found = treeItemAtY(c, targetY, curY, font);
|
|
|
|
if (found) {
|
|
return found;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtTreeItem
|
|
// ============================================================
|
|
|
|
WidgetT *wgtTreeItem(WidgetT *parent, const char *text) {
|
|
WidgetT *w = widgetAlloc(parent, WidgetTreeItemE);
|
|
|
|
if (w) {
|
|
w->as.treeItem.text = text;
|
|
w->as.treeItem.expanded = false;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtTreeItemIsExpanded
|
|
// ============================================================
|
|
|
|
bool wgtTreeItemIsExpanded(const WidgetT *w) {
|
|
if (!w || w->type != WidgetTreeItemE) {
|
|
return false;
|
|
}
|
|
|
|
return w->as.treeItem.expanded;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtTreeItemSetExpanded
|
|
// ============================================================
|
|
|
|
void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) {
|
|
if (!w || w->type != WidgetTreeItemE) {
|
|
return;
|
|
}
|
|
|
|
w->as.treeItem.expanded = expanded;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// wgtTreeView
|
|
// ============================================================
|
|
|
|
WidgetT *wgtTreeView(WidgetT *parent) {
|
|
WidgetT *w = widgetAlloc(parent, WidgetTreeViewE);
|
|
|
|
if (w) {
|
|
w->weight = 100;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetTreeViewCalcMinSize
|
|
// ============================================================
|
|
|
|
void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|
int32_t totalH = calcTreeItemsHeight(w, font);
|
|
int32_t maxW = calcTreeItemsMaxWidth(w, font, 0);
|
|
|
|
w->calcMinW = maxW + TREE_BORDER * 2;
|
|
w->calcMinH = totalH + TREE_BORDER * 2;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetTreeViewLayout
|
|
// ============================================================
|
|
|
|
void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font) {
|
|
int32_t innerX = w->x + TREE_BORDER;
|
|
int32_t innerY = w->y + TREE_BORDER;
|
|
int32_t innerW = w->w - TREE_BORDER * 2;
|
|
|
|
layoutTreeItems(w, font, innerX, &innerY, innerW, 0);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetTreeViewOnMouse
|
|
// ============================================================
|
|
|
|
void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|
AppContextT *ctx = (AppContextT *)root->userData;
|
|
const BitmapFontT *font = &ctx->font;
|
|
int32_t curY = hit->y + TREE_BORDER;
|
|
|
|
WidgetT *item = treeItemAtY(hit, vy, &curY, font);
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
// Check if click is on expand/collapse icon
|
|
bool hasChildren = false;
|
|
|
|
for (WidgetT *gc = item->firstChild; gc; gc = gc->nextSibling) {
|
|
if (gc->type == WidgetTreeItemE) {
|
|
hasChildren = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hasChildren) {
|
|
// Calculate indent depth
|
|
int32_t depth = 0;
|
|
WidgetT *p = item->parent;
|
|
|
|
while (p && p->type == WidgetTreeItemE) {
|
|
depth++;
|
|
p = p->parent;
|
|
}
|
|
|
|
int32_t iconX = hit->x + TREE_BORDER + depth * TREE_INDENT;
|
|
|
|
if (vx >= iconX && vx < iconX + TREE_EXPAND_SIZE) {
|
|
item->as.treeItem.expanded = !item->as.treeItem.expanded;
|
|
|
|
if (item->onChange) {
|
|
item->onChange(item);
|
|
}
|
|
} else {
|
|
if (item->onClick) {
|
|
item->onClick(item);
|
|
}
|
|
}
|
|
} else {
|
|
if (item->onClick) {
|
|
item->onClick(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetTreeViewPaint
|
|
// ============================================================
|
|
|
|
void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
|
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
|
|
|
// Sunken border
|
|
BevelStyleT bevel;
|
|
bevel.highlight = colors->windowShadow;
|
|
bevel.shadow = colors->windowHighlight;
|
|
bevel.face = bg;
|
|
bevel.width = 2;
|
|
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
|
|
|
|
// Paint tree items
|
|
int32_t itemY = w->y + TREE_BORDER;
|
|
|
|
paintTreeItems(w, d, ops, font, colors,
|
|
w->x + TREE_BORDER, &itemY, 0,
|
|
w->y + TREE_BORDER, w->y + w->h - TREE_BORDER);
|
|
}
|