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