Action?RenderNode(FolderTree node) { var r = node.Payload as AppRenderer; if (r != null) // leaf, i.e. actual script { r.DrawUI(); return(null); // do NOT render subnodes, none can exist; if having paylod, it must be a leaf because we always put the payload just to the leaves } else // just intermediate folder { ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4(0f, 1f, 1f, 1f)); bool opened = ImGui.TreeNodeEx($"{node.Name}##folder_{node.Name}", ImGuiTreeNodeFlags.FramePadding); ImGui.PopStyleColor(); if (opened) { return () => { ImGui.TreePop(); } } ; return(null); // do NOT render subnodes (not unfolded) } } void SortTree(FolderTree tree) { if (tree.Children == null) { return; } tree.Children.Sort((a, b) => a.Name.CompareTo(b.Name)); foreach (var subtree in tree.Children) { SortTree(subtree); } } AppRenderer GetOrCreateRenderer(string uniqueUiId, AppIdTuple id) { AppRenderer?r; if (!_nodeRenderers.TryGetValue(uniqueUiId, out r)) { r = new AppRenderer(_wnd, uniqueUiId, id, _ctrl); // will render the effective ones _nodeRenderers[uniqueUiId] = r; } return(r); } FolderTree BuildTree() { var root = new FolderTree(); foreach ((var id, var def) in _ctrl.GetAllAppDefs()) { // parse "Groups" attribute into individual group paths // use them to build a tree of script groups; the tree node payload = script renderer (null if just an intermediate node) var groups = def.Groups.Split(';'); if (groups.Length > 0) { foreach (var g in groups) { var path = $"{g.Trim()}/{id}"; var r = GetOrCreateRenderer(path, id); root.InsertNode(path, false, r, null); } } else // no groups defined => put to the root { var path = id.ToString(); var r = GetOrCreateRenderer(path, id); root.InsertNode(path, false, r, null); } } SortTree(root); return(root); } }