Editor/BoldHierarchyRow.cs
using Editor;
using Sandbox;
using System;
using System.Linq;
using static Editor.BaseItemWidget;

namespace HierarchyChanges;

// Adapted from Facepunch's installed GameObjectNode.OnPaint (2026-09-06).
// Source: sbox-public/game/addons/tools/Code/Scene/SceneTree/GameObjectNode.cs.
// Keep this compatibility copy aligned with upstream hierarchy indicators.
// Only the object name uses weight 700; following status text returns to normal.
internal static class BoldHierarchyRow
{
	internal static void PaintBold( VirtualWidget item, GameObject Value, TreeView TreeView )
	{
		if ( !Value.Scene.IsValid() )
			return;

		var isEven = item.Row % 2 == 0;
		var isHovered = item.Hovered;
		var selected = item.Selected || item.Pressed || item.Dragging;
		var isBone = Value.Flags.Contains( GameObjectFlags.Bone );
		var isProceduralBone = Value.Flags.Contains( GameObjectFlags.ProceduralBone );
		var isAttachment = Value.Flags.Contains( GameObjectFlags.Attachment );
		var isNetworked = Value.Scene.IsEditor ? Value.NetworkMode == NetworkMode.Object : Value.Network.Active;
		var isNetworkRoot = isNetworked && (Value.Scene.IsEditor ? Value.NetworkMode == NetworkMode.Object : Value.IsNetworkRoot);

		bool isErrored = Value.Flags.Contains( GameObjectFlags.Error );
		bool isLoading = Value.Flags.Contains( GameObjectFlags.Loading );
		bool isTemporary = Value.Flags.Contains( GameObjectFlags.NotSaved );
		bool isEditorOnly = Value.Flags.Contains( GameObjectFlags.EditorOnly );

		var fullSpanRect = item.Rect;
		fullSpanRect.Left = 0;
		fullSpanRect.Right = TreeView.Width;

		float opacity = 0.9f;

		if ( !Value.Active ) opacity *= 0.5f;

		Color pen = Theme.TextControl;
		string icon = "layers";
		Color iconColor = Theme.TextControl.WithAlpha( 0.6f );
		Color overlayIconColor = iconColor;

		string overlayIcon = null;

		if ( Value.IsPrefabInstance )
		{
			pen = Theme.Blue;
			overlayIconColor = Theme.Blue;

			if ( Value.IsPrefabInstanceRoot )
			{
				icon = "dataset";
				iconColor = Theme.Blue;

				if ( EditorUtility.Prefabs.IsInstanceModified( Value ) )
				{
					Paint.ClearPen();
					Paint.SetBrush( Theme.Blue.Darken( 0.25f ) );
					var modifiedRect = fullSpanRect;
					modifiedRect.Width = 2;
					Paint.DrawRect( modifiedRect );
				}
			}

			if ( EditorUtility.Prefabs.IsGameObjectAddedToInstance( Value ) )
			{
				overlayIcon = "add_circle";
			}
		}

		if ( isBone )
		{
			icon = "polyline";
			iconColor = Theme.Pink.WithAlpha( 0.8f );

			if ( isProceduralBone )
			{
				iconColor = Theme.Blue.WithAlpha( 0.8f );
			}
		}

		if ( isAttachment )
		{
			icon = "push_pin";
			iconColor = Theme.Pink.WithAlpha( 0.8f );

			if ( isProceduralBone )
			{
				iconColor = Theme.Blue.WithAlpha( 0.8f );
			}
		}

		if ( isTemporary )
		{
			iconColor = Color.White.WithAlpha( 0.5f );
			pen = iconColor.Lighten( 0.2f ).WithAlpha( 0.8f );
			icon = "no_sim";
		}

		if ( isNetworked )
		{
			icon = "rss_feed";
			iconColor = Theme.Blue.WithAlpha( 0.8f );

			if ( Value.Network.IsOwner )
			{
				iconColor = Theme.Green.WithAlpha( 0.8f );
			}

			if ( Value.IsProxy )
			{
				iconColor = Theme.TextControl.WithAlpha( 0.6f );
			}

			if ( !isNetworkRoot ) iconColor = iconColor.WithAlphaMultiplied( 0.4f );
		}

		if ( isErrored )
		{
			icon = "report";
			iconColor = Theme.Red.WithAlpha( 0.8f );
		}

		if ( isEditorOnly )
		{
			icon = "highlight_alt";
			iconColor = Theme.Yellow.WithAlpha( 0.8f );
			pen = Theme.Yellow.WithAlpha( 0.6f );
		}


		//
		// If there's a drag and drop happening, fade out nodes that aren't possible
		//
		if ( TreeView.IsBeingDroppedOn )
		{
			if ( TreeView.CurrentItemDragEvent.Data.Object is GameObject[] gos && gos.Any( go => Value.IsAncestor( go ) ) )
			{
				opacity *= 0.23f;
			}
			else if ( TreeView.CurrentItemDragEvent.Data.Object is GameObject go && Value.IsAncestor( go ) )
			{
				opacity *= 0.23f;
			}
		}

		if ( item.Dropping )
		{
			Paint.ClearPen();
			Paint.SetBrush( Theme.Blue );

			if ( TreeView.CurrentItemDragEvent.DropEdge.HasFlag( ItemEdge.Top ) )
			{
				var droprect = item.Rect;
				droprect.Top -= 1;
				droprect.Height = 2;
				Paint.DrawRect( droprect, 2 );
			}
			else if ( TreeView.CurrentItemDragEvent.DropEdge.HasFlag( ItemEdge.Bottom ) )
			{
				var droprect = item.Rect;
				droprect.Top = droprect.Bottom - 1;
				droprect.Height = 2;
				Paint.DrawRect( droprect, 2 );
			}
			else
			{
				Paint.SetBrushAndPen( Theme.Blue.WithAlpha( 0.2f ), Theme.Blue );
				Paint.PenSize = 2;
				Paint.DrawRect( item.Rect, 4 );
			}
		}

		if ( selected )
		{
			//item.PaintBackground( Color.Transparent, 3 );
			Paint.ClearPen();
			Paint.SetBrush( Theme.SelectedBackground.WithAlpha( opacity ) );
			Paint.DrawRect( fullSpanRect );
		}
		else if ( isHovered )
		{
			Paint.ClearPen();
			Paint.SetBrush( Theme.SelectedBackground.WithAlpha( 0.25f ) );
			Paint.DrawRect( fullSpanRect );
		}
		else if ( isEven )
		{
			Paint.ClearPen();
			Paint.SetBrush( Theme.SurfaceLightBackground.WithAlpha( 0.1f ) );
			Paint.DrawRect( fullSpanRect );
		}

		var name = Value.Name;
		if ( string.IsNullOrWhiteSpace( name ) ) name = "Untitled GameObject";

		var r = item.Rect;
		r.Left += 4;

		var iconSize = 16;

		Paint.Pen = iconColor.WithAlphaMultiplied( opacity );
		Paint.DrawIcon( r, icon, iconSize, TextFlag.LeftCenter );
		if ( !string.IsNullOrEmpty( overlayIcon ) )
		{
			var overlayIconRect = r;
			overlayIconRect.Left += 8;
			overlayIconRect.Top += 8;
			overlayIconRect.Width = 13;
			overlayIconRect.Height = 13;
			Paint.Pen = Theme.WidgetBackground;
			Paint.SetBrush( Theme.WidgetBackground );
			Paint.DrawRect( overlayIconRect, 12 );
			overlayIconRect.Left += 1;
			Paint.Pen = overlayIconColor;
			Paint.DrawIcon( overlayIconRect, overlayIcon, 13, TextFlag.Center );
		}
		r.Left += 22;

		Paint.Pen = pen.WithAlphaMultiplied( opacity );
		Paint.SetDefaultFont( weight: 700 );
		r.Left += Paint.DrawText( r, name, TextFlag.LeftCenter ).Width + 4;

		Paint.SetDefaultFont();

		if ( isLoading )
		{
			Paint.Pen = Theme.Blue;
			Paint.DrawIcon( r, "access_time_filled", iconSize, TextFlag.LeftCenter );
			r.Left += 22;
		}

		if ( isNetworkRoot && Value.Network.OwnerId != Guid.Empty )
		{
			var connection = Connection.Find( Value.Network.OwnerId );
			if ( connection is null )
			{
				Paint.Pen = Theme.Blue;
				Paint.DrawText( r, $"Unknown Owner - {Value.Network.OwnerId}", TextFlag.LeftCenter );
				r.Left += 22;
			}
			else
			{
				Paint.Pen = Theme.Blue;
				Paint.DrawText( r, $"{connection.DisplayName}", TextFlag.LeftCenter );
				r.Left += 22;
			}


		}

		if ( Value.Tags.Has( "hidden" ) )
		{
			var eyeRect = item.Rect;
			eyeRect.Right -= 4;
			eyeRect.Left = eyeRect.Right - 18;

			Paint.Pen = Theme.TextControl;
			Paint.DrawIcon( eyeRect, "visibility_off", 14, TextFlag.Center );
		}
	}

}