render method
Implementation
@override
void render(Rect area, Buffer buffer, RenderContext ctx) {
if (area.isEmpty) return;
final focused = ctx.isFocused(id);
final visible = _flatten();
if (visible.isEmpty) return;
state.activeIndex = state.activeIndex.clamp(0, visible.length - 1);
// Keep active in viewport.
if (state.activeIndex < state.scrollOffset) {
state.scrollOffset = state.activeIndex;
} else if (state.activeIndex >= state.scrollOffset + area.height) {
state.scrollOffset = state.activeIndex - area.height + 1;
}
state.scrollOffset = state.scrollOffset.clamp(0, visible.length);
final base = itemStyle ?? ctx.theme.text.body;
final activeS = activeStyle ??
Style(
fg: ctx.theme.colors.background,
bg: focused ? ctx.theme.colors.primary : ctx.theme.colors.foreground,
bold: true,
);
final icon = iconStyle ?? Style(fg: ctx.theme.colors.muted);
for (var row = 0; row < area.height; row++) {
final idx = state.scrollOffset + row;
if (idx >= visible.length) break;
final v = visible[idx];
final y = area.y + row;
final isActive = idx == state.activeIndex;
final iconChar = v.node.isLeaf
? leafIcon
: (state.expanded.contains(v.key) ? expandedIcon : collapsedIcon);
final prefix = ' ' * (v.depth * indent);
final line = '$prefix$iconChar ${v.node.label}';
// Background fill for active row.
if (isActive) {
for (var i = 0; i < area.width; i++) {
buffer.setChar(area.x + i, y, ' ', style: activeS);
}
}
// Icon
final iconX = area.x + prefix.length;
if (iconX < area.right) {
buffer.writeText(iconX, y, iconChar,
style: isActive ? activeS : icon, maxWidth: area.right - iconX);
}
// Label
final lblX = iconX + iconChar.length + 1;
if (lblX < area.right) {
buffer.writeText(lblX, y, v.node.label,
style: isActive ? activeS : base, maxWidth: area.right - lblX);
}
// Suppress unused
if (line.isEmpty) {}
}
}