Editor/XGUIHierarchyWidget.cs
using Editor;
using Sandbox;
using Sandbox.UI;
using System;
using System.Collections.Generic;
using XGUI.XGUIEditor;
class XGUIHierarchyWidget : Widget
{
public XGUIDesigner OwnerDesigner { get; private set; }
private TreeView _hierarchyTree;
public XGUIHierarchyWidget( Widget parent, XGUIDesigner ownerDesigner ) : base( parent )
{
OwnerDesigner = ownerDesigner;
this.Layout = Layout.Column();
BuildUI();
}
private void BuildUI()
{
_hierarchyTree = new TreeView( this );
_hierarchyTree.ItemSelected += OnHierarchyNodeSelected;
this.Layout.Add( _hierarchyTree );
}
public void UpdateHierarchy( List<MarkupNode> rootNodes )
{
_hierarchyTree.Clear();
// Rebuild the hierarchy tree
foreach ( var rootNode in rootNodes )
{
BuildTreeForMarkupNodeRecursive( rootNode, null, _hierarchyTree );
}
}
private void BuildTreeForMarkupNodeRecursive( MarkupNode node, TreeNode parentTreeNode, TreeView treeView )
{
string displayName = $"{node.TagName}";
if ( node.Attributes.TryGetValue( "class", out var cls ) && !string.IsNullOrWhiteSpace( cls ) ) displayName += $" .{cls.Split( ' ' )[0]}";
if ( node.Attributes.TryGetValue( "id", out var id ) && !string.IsNullOrWhiteSpace( id ) ) displayName += $" #{id}";
// Create a tree node for this markup node
var treeNode = new MarkupTreeNode() { Name = displayName, /*ToolTip = $"Pos: {node.SourceStart}-{node.SourceEnd}",*/ Value = node };
// If there's a parent node, add as child; otherwise add to the root
if ( parentTreeNode != null )
{
parentTreeNode.AddItem( treeNode );
}
else
{
treeView.AddItem( treeNode );
}
// Recurse for children
if ( node.Children != null )
{
foreach ( var child in node.Children )
{
BuildTreeForMarkupNodeRecursive( child, treeNode, treeView );
}
}
}
private void OnHierarchyNodeSelected( object item )
{
// Update the hierarchy tree if needed
_hierarchyTree.UpdateIfDirty();
if ( item is MarkupNode node && node.Type == NodeType.Element )
{
// Check if this is the window-content node (root node in hierarchy)
bool isWindowContentNode = node.TagName.Equals( "div", StringComparison.OrdinalIgnoreCase ) &&
node.Attributes.TryGetValue( "class", out var cls ) &&
cls.Contains( "window-content" );
if ( isWindowContentNode )
{
// Select the parent root node (which contains window properties)
// This is the <root> node that wraps everything
var rootNode = OwnerDesigner.GetOrCreateWindowNode();
// Use the window itself as the panel
OwnerDesigner.SelectAndInspect( rootNode, OwnerDesigner.Window );
Log.Info( "Selected window root node" );
return;
}
// Normal element selection
Panel correspondingPanel = OwnerDesigner.LookupPanelByNode( node );
OwnerDesigner.SelectAndInspect( node, correspondingPanel );
}
// for code blocks, select the text in the code view
else if ( item is MarkupNode codeBlock && codeBlock.Type == NodeType.RazorBlock )
{
// Select the text in the code view
OwnerDesigner.SelectAndInspect( codeBlock, null );
}
else if ( item is MarkupNode textNode && textNode.Type == NodeType.Text )
{
// Do nothing for text nodes, or handle as needed
Log.Info( "Text node selected" );
// OwnerDesigner.SelectAndInspect( textNode, null ); // Optional: Handle text node selection if needed
}
else
{
OwnerDesigner.SelectAndInspect( null, null ); // Clear selection if text node or something else selected
}
}
}
// we can use custom draw
class MarkupTreeNode : TreeNode<MarkupNode>
{
public override void OnPaint( VirtualWidget item )
{
PaintSelection( item );
var hoveredInGame = false;//Value.PseudoClass.HasFlag( Sandbox.UI.PseudoClass.Hover );
var a = 1.0f;//Value.IsVisible ? 1.0f : 0.5f;
var r = item.Rect;
void Write( Color color, string text, ref Rect r )
{
Paint.SetPen( color.WithAlphaMultiplied( a ) );
var size = Paint.DrawText( r, text, TextFlag.LeftCenter );
r.Left += size.Width;
}
{
// Paint.SetPen( Theme.Yellow.WithAlpha( alpha ) );
// Paint.DrawIcon( r, info.Icon ?? "window", 18, TextFlag.LeftCenter );
//r.Left += Theme.RowHeight;
}
var brackets = Theme.Yellow.WithAlpha( 0.7f );
var element = Color.White.WithAlpha( 0.9f );
var keyword = Color.White.WithAlpha( 0.7f );
var code = Theme.Pink.WithAlpha( 0.7f );
if ( hoveredInGame )
{
element = Theme.Green.WithAlpha( 0.9f );
keyword = Theme.Green.WithAlpha( 0.6f );
}
Paint.SetDefaultFont();
if ( Value.Parent == null )
{
Paint.SetDefaultFont( 8, 500 );
Write( element, "Window Content", ref r );
Paint.SetDefaultFont();
}
else if ( Value.Type == NodeType.Element )
{
{
Write( brackets, $"<", ref r );
Paint.SetDefaultFont( 8, 500 );
Write( element, $"{Value.TagName}", ref r );
Paint.SetDefaultFont();
}
if ( Value.Attributes.ContainsKey( "id" ) )
{
Write( keyword, $" id=\"", ref r );
Paint.SetDefaultFont( 8, 500 );
Write( Theme.Blue, Value?.Attributes["id"], ref r );
Paint.SetDefaultFont();
Write( keyword, $"\"", ref r );
}
if ( Value.Attributes.ContainsKey( "class" ) )
{
Write( keyword, $" class=\"", ref r );
Write( Theme.Blue, Value?.Attributes["class"], ref r );
Write( keyword, $"\"", ref r );
}
foreach ( var attribute in Value.Attributes )
{
if ( attribute.Key == "id" || attribute.Key == "class" )
continue;
Write( keyword, $" {attribute.Key}=\"", ref r );
Write( Theme.Blue, attribute.Value, ref r );
Write( keyword, $"\"", ref r );
}
Write( brackets, $">", ref r );
}
else if ( Value.Type == NodeType.Text )
{
Write( brackets, $"\"{Value.TextContent}\"", ref r );
}
else if ( Value.Type == NodeType.RazorBlock )
{
// only show the first 32 characters of the Razor block
var trimmedCode = Value.TextContent.Trim();
Paint.SetDefaultFont( 8, 500 );
Write( keyword, "Code Block ", ref r );
Paint.SetDefaultFont();
if ( trimmedCode.Length > 64 )
trimmedCode = trimmedCode.Substring( 0, 64 ) + "...";
Write( code, $"{trimmedCode}", ref r );
}
else
{
Write( element, Value.ToString(), ref r );
}
}
}