Skip to main content

Tree

tree

See the TreeDemo.cs.

using Gridrand.RimGui.Manual;
using Gridrand.RimGui.Manual.Utility;
using System.Collections.Generic;

namespace Gridrand.RimGui.Extensions.Manual
{
/// <summary>
/// Demonstrates how to implement and display tree structures with different scrolling behaviors.
/// </summary>
class TreeDemo : ManualBase, IManual
{
readonly Tree dynamicScrollTree;
readonly Tree fixedScrollTree;

public TreeDemo(ManualBaseResource p) : base(p)
{
dynamicScrollTree = new();
fixedScrollTree = new();
}

public void Draw()
{
Gui.ToolTip("By holding down the Alt key while clicking triangle icon, you can open / close all child elements.");

DrawWithDynamicScroll(dynamicScrollTree);
DrawWithFixedScroll(fixedScrollTree);
}

void DrawWithDynamicScroll(Tree tree)
{
Gui.Heading("Scroll");

using var entries = tree.ExpansionControl.CollectExpandedNodes(tree.Roots);

// Render a scrollable tree.
if (Gui.NextHeight(200f).BeginScroll())
{
// Set SpacingY to 0 within this scope.
using var s = Style.SpacingYs.Begin(0f);

for (int i = 0; i < entries.Length; i++)
{
DrawNode(tree, entries[i]);
}
Gui.EndScroll();
}
}

void DrawWithFixedScroll(Tree tree)
{
Gui.HeadingTooltip(
"FixedScrollTree",
"It only processes the displayed nodes, making it fast even when there are many.");

using var entries = tree.ExpansionControl.CollectExpandedNodes(fixedScrollTree.Roots);

// Set SpacingY to 0 within this scope.
using (Style.SpacingYs.Begin(0f))
// Render a fixed-size scrollable tree.
using (Gui.NextHeight(200f).BeginFixedScroll(entries.Length))
{
var scrollRect = Ctx.GetWidgetRect();

while (Ctx.AdvanceFixedScrollItem())
{
var entry = entries[Ctx.GetFixedScrollIndex()];
DrawNode(tree, entry);
}

// Try deselect all
Gui.NextRect(scrollRect).DeselectionArea(fixedScrollTree.Selection);
}
}

void DrawNode(Tree tree, TreeNodeEntry<Node> entry)
{
using var s = Ctx.PushId(entry.Node.GetHashCode());
var nodeRect = Ctx.AllocateRect();

var (FoldoutRect, MainRect) = LayoutBuilder.BuildFoldoutNode(nodeRect, entry.Level);

// To process input first and then draw later
using (Ctx.BeginDeferredBuilding())
{
if (entry.Node.HasChild)
{
Gui.NextRect(FoldoutRect).TreeFoldout(tree.ExpansionControl, entry.Node);
}

// note:Unscaled because the scale has already been taken into account.
using var rects2 = Ctx.LayoutBuilder.FixedUnscaled(MainRect.height).Fit(1).BuildHorizontal(MainRect);
var isCheck = entry.Node.IsChecked;
Gui.NextRect(rects2.R0).CheckBox(ref isCheck);
Gui.NextRect(rects2.R1).Text(entry.Node.Name);
entry.Node.IsChecked = isCheck;
}

// When clicked, it will update the tree's selection state with this node.
Gui.NextRect(nodeRect).SelectableItem(tree.Selection, entry.Node);
}

/// <summary>
/// Represents a node in the tree structure.
/// </summary>
class Node
{
public bool IsChecked { get; set; }
public string Name { get; set; }
public List<Node> Children { get; private set; } = new();
public bool HasChild => 0 < Children.Count;

public Node AddChildren(params Node[] children)
{
Children.AddRange(children);
return this;
}
}

class Tree
{
// Fixed-size scrollable tree structure data.
public List<Node> Roots { get; } = new();
public ItemSelection<Node> Selection { get; } = new();
public TreeExpansionControl<Node> ExpansionControl { get; }

public Tree()
{
ExpansionControl = new TreeExpansionControl<Node>(n => n.Children);

BuildTree(Roots, Selection);

foreach (var n in Roots)
ExpansionControl.SetExpandedRecursively(n, true);
}

static void BuildTree(List<Node> roots, ItemSelection<Node> selection)
{
var n = 0;

roots.Add(new Node() { Name = ProcessName(ref n) }
.AddChildren(
new Node() { Name = ProcessName(ref n) }
.AddChildren(
new Node() { Name = ProcessName(ref n) },
new Node() { Name = ProcessName(ref n) }),
new Node() { Name = ProcessName(ref n) }
.AddChildren(new Node() { Name = ProcessName(ref n) })));


roots.Add(new Node() { Name = ProcessName(ref n) }
.AddChildren(
new Node() { Name = ProcessName(ref n) }
.AddChildren(
new Node() { Name = ProcessName(ref n) })));

roots.Add(new Node() { Name = ProcessName(ref n) });
roots.Add(new Node() { Name = ProcessName(ref n) });
roots.Add(new Node() { Name = ProcessName(ref n) });
roots.Add(new Node() { Name = ProcessName(ref n) });

selection.Select(roots[0]);
selection.Select(roots[1]);

}
static string ProcessName(ref int number)
{
var result = number;
number++;
return result.ToString();
}
}
}
}