{"TotalCount":11,"Files":[{"Ident":"subzerostudios.subhierarchy","Path":".obj/__compiler_extra.cs","FileName":"__compiler_extra.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":381047,"IsPrivate":false,"Code":"global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonTitle\u0022, \u0022SubHierarchy\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonIdent\u0022, \u0022subhierarchy\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022OrgIdent\u0022, \u0022subzerostudios\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022Ident\u0022, \u0022subzerostudios.subhierarchy\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineVersion\u0022, \u002229\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineMinorVersion\u0022, \u00221\u0022 )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \u0022.NETCoreApp,Version=v9.0\u0022, FrameworkDisplayName = \u0022.NET 9.0\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022CompileTime\u0022, \u00222026-09-14T18:56:09.5154493Z\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\u00220.0.118.0\u0022)]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\u00220.0.118.0\u0022)]"},{"Ident":"subzerostudios.subhierarchy","Path":"Editor/HierarchyColorPainter.cs","FileName":"HierarchyColorPainter.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":381047,"IsPrivate":false,"Code":"using System.Collections;\r\nusing System.Reflection;\r\n\r\nnamespace SubHierarchy;\r\n\r\n/// \u003Csummary\u003E\r\n/// Tints Hierarchy rows for GameObjects that have a \u003Csee cref=\u0022HierarchyColor\u0022/\u003E component.\r\n/// Hooks the built-in Hierarchy TreeView paint (GameObjectNode is internal).\r\n/// \u003C/summary\u003E\r\npublic static class HierarchyColorPainter\r\n{\r\n\tconst float TintAlpha = 0.38f;\r\n\tconst BindingFlags LayoutFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;\r\n\r\n\tstatic TreeView _hookedTree;\r\n\tstatic PropertyInfo _itemLayoutsProperty;\r\n\tstatic FieldInfo _itemLayoutsField;\r\n\tstatic bool _layoutsLookupDone;\r\n\r\n\t[Event( \u0022editor.created\u0022 )]\r\n\tstatic void OnEditorCreated( object _ ) =\u003E EnsureHooked();\r\n\r\n\t[EditorEvent.Frame]\r\n\tstatic void OnFrame() =\u003E EnsureHooked();\r\n\r\n\tpublic static void EnsureHooked()\r\n\t{\r\n\t\tvar tree = SceneTreeWidget.Current?.TreeView;\r\n\t\tif ( tree is null || !tree.IsValid() )\r\n\t\t{\r\n\t\t\t_hookedTree = null;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Always re-apply: Hierarchy BuildUI can replace TreeView or reset OnPaintOverride.\r\n\t\tif ( !ReferenceEquals( _hookedTree, tree ) )\r\n\t\t\t_hookedTree = tree;\r\n\r\n\t\ttree.OnPaintOverride = PaintHierarchyBackground;\r\n\t}\r\n\r\n\tstatic bool PaintHierarchyBackground()\r\n\t{\r\n\t\tvar tree = _hookedTree;\r\n\t\tif ( tree is null || !tree.IsValid() )\r\n\t\t\treturn false;\r\n\r\n\t\tPaint.ClearPen();\r\n\t\tPaint.SetBrush( Theme.ControlBackground );\r\n\t\tPaint.DrawRect( tree.LocalRect, Theme.ControlRadius );\r\n\r\n\t\tPaintColoredRows( tree );\r\n\t\treturn false;\r\n\t}\r\n\r\n\tstatic void PaintColoredRows( TreeView tree )\r\n\t{\r\n\t\tvar layouts = GetItemLayouts( tree );\r\n\t\tif ( layouts is not null )\r\n\t\t{\r\n\t\t\tforeach ( var entry in layouts )\r\n\t\t\t{\r\n\t\t\t\tif ( entry is not VirtualWidget item )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tif ( !TryResolveGameObject( item.Object, out var go ) )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tvar marker = go.Components.Get\u003CHierarchyColor\u003E( true );\r\n\t\t\t\tif ( marker is null || !marker.IsValid() )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\t// ItemLayouts rects are already in paint / scrolled local space.\r\n\t\t\t\tPaintRow( tree, item.Rect, marker.Tint );\r\n\t\t\t}\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Fallback: TryGetItemRect is content-space \u2014 convert using scrollbar value.\r\n\t\tPaintColoredRowsFallback( tree );\r\n\t}\r\n\r\n\tstatic void PaintColoredRowsFallback( TreeView tree )\r\n\t{\r\n\t\tvar scene = SceneEditorSession.Active?.Scene;\r\n\t\tif ( scene is null || !scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tvar scroll = tree.VerticalScrollbar?.Value ?? 0f;\r\n\t\tvar originY = tree.LocalRect.Top \u002B scroll - tree.Margin.Top;\r\n\r\n\t\tforeach ( var go in scene.GetAllObjects( true ) )\r\n\t\t{\r\n\t\t\tif ( !go.IsValid() || go is Scene )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tvar marker = go.Components.Get\u003CHierarchyColor\u003E( true );\r\n\t\t\tif ( marker is null || !marker.IsValid() )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tif ( !tree.TryGetItemRect( go, out var contentRect ) )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tvar rect = contentRect;\r\n\t\t\trect.Top -= originY;\r\n\t\t\tPaintRow( tree, rect, marker.Tint );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void PaintRow( TreeView tree, Rect rect, Color color )\r\n\t{\r\n\t\trect.Left = 0;\r\n\t\trect.Right = tree.Width;\r\n\r\n\t\tPaint.ClearPen();\r\n\t\tPaint.SetBrush( color.WithAlpha( TintAlpha ) );\r\n\t\tPaint.DrawRect( rect );\r\n\t}\r\n\r\n\tstatic bool TryResolveGameObject( object obj, out GameObject go )\r\n\t{\r\n\t\tgo = null;\r\n\r\n\t\tif ( obj is TreeNode node )\r\n\t\t\tgo = node.Value as GameObject;\r\n\t\telse\r\n\t\t\tgo = obj as GameObject;\r\n\r\n\t\treturn go.IsValid() \u0026\u0026 go is not Scene;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Visible row layouts live on BaseItemWidget as a protected member.\r\n\t/// Those rects match what Hierarchy paints; TryGetItemRect is content-space (scroll Y) and misaligns.\r\n\t/// \u003C/summary\u003E\r\n\tstatic IEnumerable GetItemLayouts( TreeView tree )\r\n\t{\r\n\t\tif ( !_layoutsLookupDone )\r\n\t\t{\r\n\t\t\t_layoutsLookupDone = true;\r\n\t\t\tvar type = typeof( BaseItemWidget );\r\n\t\t\t_itemLayoutsProperty = type.GetProperty( \u0022ItemLayouts\u0022, LayoutFlags );\r\n\t\t\t_itemLayoutsField = type.GetField( \u0022ItemLayouts\u0022, LayoutFlags );\r\n\t\t}\r\n\r\n\t\tobject value = null;\r\n\t\tif ( _itemLayoutsProperty is not null )\r\n\t\t\tvalue = _itemLayoutsProperty.GetValue( tree );\r\n\t\telse if ( _itemLayoutsField is not null )\r\n\t\t\tvalue = _itemLayoutsField.GetValue( tree );\r\n\r\n\t\treturn value as IEnumerable;\r\n\t}\r\n}\r\n"},{"Ident":"subzerostudios.subhierarchy","Path":"Editor/SubHierarchyDock.cs","FileName":"SubHierarchyDock.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":381047,"IsPrivate":false,"Code":"namespace SubHierarchy;\r\n\r\n/// \u003Csummary\u003E\r\n/// Separate dock with Hierarchy helpers: collapse/expand, hide/show, row colors.\r\n/// \u003C/summary\u003E\r\n[Dock( \u0022Editor\u0022, \u0022Sub Hierarchy\u0022, \u0022account_tree\u0022 )]\r\npublic sealed class SubHierarchyDock : Widget\r\n{\r\n\tpublic const string DockTitle = \u0022Sub Hierarchy\u0022;\r\n\tconst string CustomColorsCookie = \u0022SubHierarchy.CustomColors\u0022;\r\n\tconst int MaxCustomColors = 24;\r\n\tconst float ColorMatchEpsilon = 0.02f;\r\n\r\n\tstatic readonly Color[] Presets =\r\n\t[\r\n\t\tnew Color( 0.85f, 0.25f, 0.25f ), // red\r\n\t\tnew Color( 0.90f, 0.50f, 0.15f ), // orange\r\n\t\tnew Color( 0.90f, 0.80f, 0.20f ), // yellow\r\n\t\tnew Color( 0.55f, 0.85f, 0.20f ), // lime\r\n\t\tnew Color( 0.30f, 0.75f, 0.35f ), // green\r\n\t\tnew Color( 0.15f, 0.70f, 0.65f ), // teal\r\n\t\tnew Color( 0.20f, 0.75f, 0.90f ), // cyan\r\n\t\tnew Color( 0.25f, 0.55f, 0.95f ), // blue\r\n\t\tnew Color( 0.40f, 0.35f, 0.90f ), // indigo\r\n\t\tnew Color( 0.65f, 0.35f, 0.90f ), // purple\r\n\t\tnew Color( 0.90f, 0.35f, 0.70f ), // magenta\r\n\t\tnew Color( 0.90f, 0.45f, 0.55f ), // pink\r\n\t\tnew Color( 0.70f, 0.50f, 0.30f ), // brown\r\n\t];\r\n\r\n\tColor _draftColor = new Color( 0.25f, 0.55f, 0.95f );\r\n\tColorSwatchButton _swatch;\r\n\tbool _colorChildrenRecursively;\r\n\tLayout _content;\r\n\tColorSwatchPalette _palette;\r\n\tList\u003CColor\u003E _customColors = new();\r\n\r\n\tpublic SubHierarchyDock( Widget parent ) : base( parent )\r\n\t{\r\n\t\tLayout = Layout.Column();\r\n\r\n\t\tvar scroller = Layout.Add( new ScrollArea( this ), 1 );\r\n\t\tscroller.Canvas = new Widget( scroller )\r\n\t\t{\r\n\t\t\tHorizontalSizeMode = SizeMode.Flexible,\r\n\t\t\tVerticalSizeMode = SizeMode.CanGrow\r\n\t\t};\r\n\t\tscroller.Canvas.Layout = Layout.Column();\r\n\t\tscroller.Canvas.Layout.Margin = 8;\r\n\t\tscroller.Canvas.Layout.Spacing = 8;\r\n\t\t_content = scroller.Canvas.Layout;\r\n\r\n\t\tLoadCustomColors();\r\n\t\tBuildUi();\r\n\t\tHierarchyColorPainter.EnsureHooked();\r\n\t}\r\n\r\n\tpublic static void Open()\r\n\t{\r\n\t\tEditorWindow.DockManager.SetDockState( DockTitle, true );\r\n\t\tEditorWindow.DockManager.RaiseDock( DockTitle );\r\n\t}\r\n\r\n\tvoid BuildUi()\r\n\t{\r\n\t\t_content.Clear( true );\r\n\r\n\t\tAddSectionLabel( \u0022Tree\u0022 );\r\n\t\tAddButtonRow(\r\n\t\t\t( \u0022Collapse All\u0022, \u0022unfold_less\u0022, HierarchyTreeActions.CollapseAll ),\r\n\t\t\t( \u0022Expand All\u0022, \u0022unfold_more\u0022, HierarchyTreeActions.ExpandAll )\r\n\t\t);\r\n\r\n\t\tAddSectionLabel( \u0022Visibility\u0022 );\r\n\t\tAddButtonRow(\r\n\t\t\t( \u0022Hide All\u0022, \u0022visibility_off\u0022, HierarchyTreeActions.HideAll ),\r\n\t\t\t( \u0022Show All\u0022, \u0022visibility\u0022, HierarchyTreeActions.ShowAll )\r\n\t\t);\r\n\t\tAddButtonRow(\r\n\t\t\t( \u0022Isolate Selection\u0022, \u0022filter_center_focus\u0022, HierarchyTreeActions.IsolateSelection )\r\n\t\t);\r\n\r\n\t\tAddSectionLabel( \u0022Hierarchy Color\u0022 );\r\n\t\tAddColorControls();\r\n\t\tRebuildColorSwatches();\r\n\r\n\t\tAddSectionLabel( \u0022Reorganize\u0022 );\r\n\t\tAddButtonRow(\r\n\t\t\t( \u0022Sort Alphabetically\u0022, \u0022sort_by_alpha\u0022, HierarchyTreeActions.ReorganizeAlphabetically ),\r\n\t\t\t( \u0022Sort by Color\u0022, \u0022palette\u0022, HierarchyTreeActions.ReorganizeByColor )\r\n\t\t);\r\n\r\n\t\t_content.AddStretchCell( 1 );\r\n\t}\r\n\r\n\tvoid AddSectionLabel( string text )\r\n\t{\r\n\t\tvar label = _content.Add( new Label( text ) );\r\n\t\tlabel.Color = Theme.TextControl.WithAlpha( 0.85f );\r\n\t}\r\n\r\n\tvoid AddButtonRow( params (string Title, string Icon, Action Action)[] buttons )\r\n\t{\r\n\t\tvar row = _content.AddRow();\r\n\t\trow.Spacing = 6;\r\n\r\n\t\tforeach ( var button in buttons )\r\n\t\t{\r\n\t\t\tvar btn = row.Add( new Button( button.Title, button.Icon ), 1 );\r\n\t\t\tbtn.Clicked = button.Action;\r\n\t\t}\r\n\t}\r\n\r\n\tvoid AddColorControls()\r\n\t{\r\n\t\tvar row = _content.AddRow();\r\n\t\trow.Spacing = 6;\r\n\t\trow.Alignment = TextFlag.LeftCenter;\r\n\r\n\t\t_swatch = row.Add( new ColorSwatchButton( () =\u003E _draftColor, OnCustomColorPicked )\r\n\t\t{\r\n\t\t\tRememberOnClose = true,\r\n\t\t\tOnRemember = RememberCustomColor\r\n\t\t} );\r\n\t\t_swatch.ToolTip = \u0022Pick a color and apply it to the selection\u0022;\r\n\t\t_swatch.FixedWidth = Theme.RowHeight * 2;\r\n\t\t_swatch.FixedHeight = Theme.RowHeight;\r\n\r\n\t\tvar clear = row.Add( new Button( \u0022Clear\u0022, \u0022format_color_reset\u0022 ), 1 );\r\n\t\tclear.ToolTip = \u0022Remove Hierarchy Color from selection\u0022;\r\n\t\tclear.Clicked = () =\u003E HierarchyTreeActions.ClearColorFromSelection( _colorChildrenRecursively );\r\n\r\n\t\tvar clearAll = row.Add( new Button( \u0022Clear All\u0022, \u0022delete_sweep\u0022 ), 1 );\r\n\t\tclearAll.ToolTip = \u0022Remove Hierarchy Color from every object in the scene\u0022;\r\n\t\tclearAll.Clicked = HierarchyTreeActions.ClearAllColors;\r\n\r\n\t\tvar recursiveRow = _content.AddRow();\r\n\t\trecursiveRow.Spacing = 6;\r\n\t\trecursiveRow.Alignment = TextFlag.LeftCenter;\r\n\r\n\t\tvar recursive = recursiveRow.Add( new Checkbox( \u0022Color children recursively\u0022 ) );\r\n\t\trecursive.State = _colorChildrenRecursively ? CheckState.On : CheckState.Off;\r\n\t\trecursive.ToolTip = \u0022When applying or clearing, include all descendants of the selection\u0022;\r\n\t\trecursive.Clicked = () =\u003E _colorChildrenRecursively = recursive.State == CheckState.On;\r\n\r\n\t\t_palette = _content.Add( new ColorSwatchPalette( OnCustomColorPicked, OnColorSwatchRemoved ) );\r\n\t\t_palette.HorizontalSizeMode = SizeMode.Flexible;\r\n\t}\r\n\r\n\tvoid RebuildColorSwatches()\r\n\t{\r\n\t\tif ( _palette is null || !_palette.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tvar entries = new List\u003C(Color Color, string ToolTip, bool Removable)\u003E();\r\n\t\tforeach ( var preset in Presets )\r\n\t\t\tentries.Add( (preset, \u0022Left-click: apply\u0022, false) );\r\n\r\n\t\tforeach ( var custom in _customColors )\r\n\t\t\tentries.Add( (custom, \u0022Left-click: apply. Right-click: remove from palette and all objects.\u0022, true) );\r\n\r\n\t\t_palette.SetColors( entries );\r\n\t}\r\n\r\n\tvoid OnCustomColorPicked( Color color )\r\n\t{\r\n\t\t_draftColor = color.WithAlpha( 1f );\r\n\t\t_swatch?.Update();\r\n\t\tHierarchyTreeActions.ApplyColorToSelection( _draftColor, _colorChildrenRecursively );\r\n\t}\r\n\r\n\tvoid OnColorSwatchRemoved( Color color )\r\n\t{\r\n\t\tcolor = color.WithAlpha( 1f );\r\n\r\n\t\t// Built-in presets stay in the palette permanently.\r\n\t\tif ( Presets.Any( p =\u003E ColorsMatch( p, color ) ) )\r\n\t\t\treturn;\r\n\r\n\t\tvar removedCustom = _customColors.RemoveAll( c =\u003E ColorsMatch( c, color ) ) \u003E 0;\r\n\t\tif ( !removedCustom )\r\n\t\t\treturn;\r\n\r\n\t\tHierarchyTreeActions.ClearColorsMatching( color, ColorMatchEpsilon );\r\n\t\tSaveCustomColors();\r\n\r\n\t\tif ( ColorsMatch( _draftColor, color ) )\r\n\t\t\t_draftColor = Presets[0];\r\n\r\n\t\t_swatch?.Update();\r\n\t\tRebuildColorSwatches();\r\n\t}\r\n\r\n\tvoid RememberCustomColor( Color color )\r\n\t{\r\n\t\tcolor = color.WithAlpha( 1f );\r\n\r\n\t\tif ( Presets.Any( p =\u003E ColorsMatch( p, color ) ) )\r\n\t\t\treturn;\r\n\r\n\t\tif ( _customColors.Any( c =\u003E ColorsMatch( c, color ) ) )\r\n\t\t\treturn;\r\n\r\n\t\t_customColors.Add( color );\r\n\t\twhile ( _customColors.Count \u003E MaxCustomColors )\r\n\t\t\t_customColors.RemoveAt( 0 );\r\n\r\n\t\tSaveCustomColors();\r\n\t\tRebuildColorSwatches();\r\n\t}\r\n\r\n\tvoid LoadCustomColors()\r\n\t{\r\n\t\t_customColors = EditorCookie.Get( CustomColorsCookie, new List\u003CColor\u003E() ) ?? new List\u003CColor\u003E();\r\n\t}\r\n\r\n\tvoid SaveCustomColors()\r\n\t{\r\n\t\tEditorCookie.Set( CustomColorsCookie, _customColors );\r\n\t}\r\n\r\n\tstatic bool ColorsMatch( Color a, Color b )\r\n\t{\r\n\t\treturn MathF.Abs( a.r - b.r ) \u003C ColorMatchEpsilon\r\n\t\t\t\u0026\u0026 MathF.Abs( a.g - b.g ) \u003C ColorMatchEpsilon\r\n\t\t\t\u0026\u0026 MathF.Abs( a.b - b.b ) \u003C ColorMatchEpsilon;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Wraps color swatches to the available width, with at most 10 per row.\r\n\t/// \u003C/summary\u003E\r\n\tsealed class ColorSwatchPalette : Widget\r\n\t{\r\n\t\tconst int MaxPerRow = 10;\r\n\t\tconst float Spacing = 4f;\r\n\r\n\t\treadonly Action\u003CColor\u003E _onPick;\r\n\t\treadonly Action\u003CColor\u003E _onRemove;\r\n\t\treadonly List\u003C(Color Color, string ToolTip, bool Removable)\u003E _entries = new();\r\n\t\tint _builtPerRow = -1;\r\n\r\n\t\tpublic ColorSwatchPalette( Action\u003CColor\u003E onPick, Action\u003CColor\u003E onRemove ) : base( null )\r\n\t\t{\r\n\t\t\t_onPick = onPick;\r\n\t\t\t_onRemove = onRemove;\r\n\t\t\tHorizontalSizeMode = SizeMode.Flexible;\r\n\t\t\tVerticalSizeMode = SizeMode.CanGrow;\r\n\t\t\tLayout = Layout.Column();\r\n\t\t\tLayout.Spacing = Spacing;\r\n\t\t}\r\n\r\n\t\tpublic void SetColors( IEnumerable\u003C(Color Color, string ToolTip, bool Removable)\u003E entries )\r\n\t\t{\r\n\t\t\t_entries.Clear();\r\n\t\t\t_entries.AddRange( entries );\r\n\t\t\t_builtPerRow = -1;\r\n\t\t\tRebuild();\r\n\t\t}\r\n\r\n\t\tprotected override void DoLayout()\r\n\t\t{\r\n\t\t\tbase.DoLayout();\r\n\r\n\t\t\tvar perRow = CalcPerRow();\r\n\t\t\tif ( perRow != _builtPerRow )\r\n\t\t\t\tRebuild();\r\n\t\t}\r\n\r\n\t\tint CalcPerRow()\r\n\t\t{\r\n\t\t\tvar cell = Theme.RowHeight \u002B Spacing;\r\n\t\t\tif ( cell \u003C= 0f || Width \u003C= 1f )\r\n\t\t\t\treturn MaxPerRow;\r\n\r\n\t\t\tvar fit = Math.Max( 1, (int)( (Width \u002B Spacing) / cell ) );\r\n\t\t\treturn Math.Min( MaxPerRow, fit );\r\n\t\t}\r\n\r\n\t\tvoid Rebuild()\r\n\t\t{\r\n\t\t\tvar perRow = CalcPerRow();\r\n\t\t\t_builtPerRow = perRow;\r\n\r\n\t\t\tLayout.Clear( true );\r\n\t\t\tLayout.Spacing = Spacing;\r\n\r\n\t\t\tLayout row = null;\r\n\t\t\tvar countInRow = 0;\r\n\r\n\t\t\tforeach ( var entry in _entries )\r\n\t\t\t{\r\n\t\t\t\tif ( row is null || countInRow \u003E= perRow )\r\n\t\t\t\t{\r\n\t\t\t\t\trow = Layout.AddRow();\r\n\t\t\t\t\trow.Spacing = Spacing;\r\n\t\t\t\t\trow.Alignment = TextFlag.LeftCenter;\r\n\t\t\t\t\tcountInRow = 0;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar color = entry.Color;\r\n\t\t\t\tvar removable = entry.Removable;\r\n\t\t\t\tvar swatch = row.Add( new ColorSwatchButton(\r\n\t\t\t\t\t() =\u003E color,\r\n\t\t\t\t\tc =\u003E _onPick( c ),\r\n\t\t\t\t\tremovable ? () =\u003E _onRemove( color ) : null ) );\r\n\t\t\t\tswatch.FixedWidth = Theme.RowHeight;\r\n\t\t\t\tswatch.FixedHeight = Theme.RowHeight;\r\n\t\t\t\tswatch.ToolTip = entry.ToolTip;\r\n\t\t\t\tswatch.OpenPickerOnClick = false;\r\n\t\t\t\tcountInRow\u002B\u002B;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ESimple color swatch; left-click opens the engine color picker (optional).\u003C/summary\u003E\r\n\tsealed class ColorSwatchButton : Widget\r\n\t{\r\n\t\treadonly Func\u003CColor\u003E _getColor;\r\n\t\treadonly Action\u003CColor\u003E _setColor;\r\n\t\treadonly Action _onRemove;\r\n\r\n\t\tpublic bool OpenPickerOnClick { get; set; } = true;\r\n\t\tpublic bool RememberOnClose { get; set; }\r\n\t\tpublic Action\u003CColor\u003E OnRemember { get; set; }\r\n\r\n\t\tpublic ColorSwatchButton( Func\u003CColor\u003E getColor, Action\u003CColor\u003E setColor, Action onRemove = null ) : base( null )\r\n\t\t{\r\n\t\t\t_getColor = getColor;\r\n\t\t\t_setColor = setColor;\r\n\t\t\t_onRemove = onRemove;\r\n\t\t\tCursor = CursorShape.Finger;\r\n\t\t\tMouseLeftPress = OnClicked;\r\n\t\t\tMouseRightPress = OnRightClicked;\r\n\t\t}\r\n\r\n\t\tvoid OnClicked()\r\n\t\t{\r\n\t\t\tif ( !OpenPickerOnClick )\r\n\t\t\t{\r\n\t\t\t\t_setColor( _getColor() );\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tvar popup = new ColorPopup( null );\r\n\t\t\tpopup.FixedWidth = 233;\r\n\t\t\tpopup.Layout = Layout.Column();\r\n\t\t\tpopup.Position = Editor.Application.CursorPosition;\r\n\r\n\t\t\tvar colorPicker = popup.Layout.Add( new ColorPicker( popup, _getColor() ), 1 );\r\n\t\t\tcolorPicker.OnChildValuesChanged \u002B= _ =\u003E\r\n\t\t\t{\r\n\t\t\t\t_setColor( colorPicker.Value );\r\n\t\t\t\tUpdate();\r\n\t\t\t};\r\n\r\n\t\t\tif ( RememberOnClose )\r\n\t\t\t{\r\n\t\t\t\tpopup.Closed = () =\u003E OnRemember?.Invoke( colorPicker.Value );\r\n\t\t\t}\r\n\r\n\t\t\tpopup.Show();\r\n\t\t\tpopup.ConstrainToScreen();\r\n\t\t}\r\n\r\n\t\tvoid OnRightClicked()\r\n\t\t{\r\n\t\t\t_onRemove?.Invoke();\r\n\t\t}\r\n\r\n\t\tprotected override void OnPaint()\r\n\t\t{\r\n\t\t\tvar color = _getColor();\r\n\t\t\tconst float radius = 3f;\r\n\t\t\tconst float border = 1.25f;\r\n\r\n\t\t\tPaint.Antialiasing = true;\r\n\r\n\t\t\t// Solid white rounded plate first so color can\u0027t bleed past the corners.\r\n\t\t\tPaint.ClearPen();\r\n\t\t\tPaint.SetBrush( Color.White );\r\n\t\t\tPaint.DrawRect( LocalRect, radius );\r\n\r\n\t\t\tvar inner = LocalRect.Shrink( border );\r\n\t\t\tvar innerRadius = Math.Max( radius - border * 0.5f, 0f );\r\n\t\t\tPaint.SetBrush( color );\r\n\t\t\tPaint.DrawRect( inner, innerRadius );\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EPopup that notifies when closed so custom colors can be saved once.\u003C/summary\u003E\r\n\tsealed class ColorPopup : PopupWidget\r\n\t{\r\n\t\tpublic Action Closed { get; set; }\r\n\r\n\t\tpublic ColorPopup( Widget parent ) : base( parent )\r\n\t\t{\r\n\t\t}\r\n\r\n\t\tprotected override void OnClosed()\r\n\t\t{\r\n\t\t\tClosed?.Invoke();\r\n\t\t\tbase.OnClosed();\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/// \u003Csummary\u003EMenu \u002B View registration for Sub Hierarchy.\u003C/summary\u003E\r\npublic static class SubHierarchyMenus\r\n{\r\n\t[Menu( \u0022Editor\u0022, \u0022Sub Hierarchy/Open\u0022, \u0022account_tree\u0022 )]\r\n\tpublic static void Open() =\u003E SubHierarchyDock.Open();\r\n\r\n\t[Event( \u0022tools.editorwindow.postcreateview\u0022 )]\r\n\tstatic void AddViewMenu( Menu menu )\r\n\t{\r\n\t\tmenu.AddSeparator();\r\n\t\tmenu.AddOption( \u0022Sub Hierarchy\u0022, \u0022account_tree\u0022, () =\u003E SubHierarchyDock.Open() );\r\n\t}\r\n\r\n\t[Event( \u0022editor.created\u0022 )]\r\n\tstatic void OnEditorCreated( object _ ) =\u003E HierarchyColorPainter.EnsureHooked();\r\n}\r\n"},{"Ident":"subzerostudios.subhierarchy","Path":"Code/Assembly.cs","FileName":"Assembly.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":381047,"IsPrivate":false,"Code":"global using Sandbox;\r\nglobal using System;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;\r\n"},{"Ident":"subzerostudios.subhierarchy","Path":"UnitTests/UnitTest.cs","FileName":"UnitTest.cs","PackageType":"library","CodeKind":"UnitTest","AssetVersionId":381047,"IsPrivate":false,"Code":"global using Microsoft.VisualStudio.TestTools.UnitTesting;\r\n\r\n[TestClass]\r\npublic class TestInit\r\n{\r\n\tpublic static Sandbox.TestAppSystem AppSystem;\r\n\r\n\t[AssemblyInitialize]\r\n\tpublic static void AssemblyInitialize( TestContext context )\r\n\t{\r\n\t\tAppSystem = new Sandbox.TestAppSystem();\r\n\t\tAppSystem.Init();\r\n\t}\r\n\r\n\t[AssemblyCleanup]\r\n\tpublic static void AssemblyCleanup()\r\n\t{\r\n\t\tAppSystem.Shutdown();\r\n\t}\r\n}\r\n"},{"Ident":"subzerostudios.subhierarchy","Path":"Assembly.cs","FileName":"Assembly.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":381047,"IsPrivate":false,"Code":"global using Sandbox;\r\nglobal using System;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;\r\n"},{"Ident":"subzerostudios.subhierarchy","Path":"UnitTests/LibraryTest.cs","FileName":"LibraryTest.cs","PackageType":"library","CodeKind":"UnitTest","AssetVersionId":381047,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\n[TestClass]\r\npublic partial class LibraryTests\r\n{\r\n\t[TestMethod]\r\n\tpublic void SceneTest()\r\n\t{\r\n\t\tvar scene = new Scene();\r\n\t\tusing ( scene.Push() )\r\n\t\t{\r\n\t\t\tvar go = new GameObject();\r\n\r\n\t\t\tAssert.AreEqual( 1, scene.Directory.GameObjectCount );\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"},{"Ident":"subzerostudios.subhierarchy","Path":"Editor/HierarchyTreeActions.cs","FileName":"HierarchyTreeActions.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":381047,"IsPrivate":false,"Code":"namespace SubHierarchy;\r\n\r\n/// \u003Csummary\u003E\r\n/// Actions that drive the built-in Hierarchy / scene visibility.\r\n/// \u003C/summary\u003E\r\npublic static class HierarchyTreeActions\r\n{\r\n\tconst string HiddenTag = \u0022hidden\u0022;\r\n\r\n\tpublic static TreeView GetHierarchyTree()\r\n\t{\r\n\t\treturn SceneTreeWidget.Current?.TreeView;\r\n\t}\r\n\r\n\tpublic static Scene GetActiveScene()\r\n\t{\r\n\t\treturn SceneEditorSession.Active?.Scene;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ECollapse every open group; keep the scene root expanded.\u003C/summary\u003E\r\n\tpublic static void CollapseAll()\r\n\t{\r\n\t\tvar tree = GetHierarchyTree();\r\n\t\tvar scene = GetActiveScene();\r\n\t\tif ( tree is null || scene is null || !scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\ttree.Close( scene, recursive: true );\r\n\t\ttree.Open( scene );\r\n\t\ttree.Update();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EExpand every group under the scene root.\u003C/summary\u003E\r\n\tpublic static void ExpandAll()\r\n\t{\r\n\t\tvar tree = GetHierarchyTree();\r\n\t\tvar scene = GetActiveScene();\r\n\t\tif ( tree is null || scene is null || !scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\ttree.Open( scene, recursive: true );\r\n\t\ttree.Update();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EHide every GameObject in the scene (same \u003Cc\u003Ehidden\u003C/c\u003E tag as H / U).\u003C/summary\u003E\r\n\tpublic static void HideAll()\r\n\t{\r\n\t\tusing var scope = SceneEditorSession.Scope();\r\n\t\tvar session = SceneEditorSession.Active;\r\n\t\tif ( session?.Scene is not { } scene || !scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tvar gos = scene.GetAllObjects( true )\r\n\t\t\t.Where( go =\u003E go.IsValid() \u0026\u0026 go is not Scene \u0026\u0026 !go.Tags.Has( HiddenTag ) )\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( gos.Length == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tusing ( session.UndoScope( \u0022Hide All Object(s)\u0022 )\r\n\t\t\t.WithGameObjectChanges( gos, GameObjectUndoFlags.All )\r\n\t\t\t.Push() )\r\n\t\t{\r\n\t\t\tforeach ( var go in gos )\r\n\t\t\t\tgo.Tags.Add( HiddenTag );\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EShow every GameObject that has the \u003Cc\u003Ehidden\u003C/c\u003E tag.\u003C/summary\u003E\r\n\tpublic static void ShowAll()\r\n\t{\r\n\t\tusing var scope = SceneEditorSession.Scope();\r\n\t\tvar session = SceneEditorSession.Active;\r\n\t\tif ( session?.Scene is not { } scene || !scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tvar gos = scene.GetAllObjects( true )\r\n\t\t\t.Where( go =\u003E go.IsValid() \u0026\u0026 go.Tags.Has( HiddenTag ) )\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( gos.Length == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tusing ( session.UndoScope( \u0022Unhide All Object(s)\u0022 )\r\n\t\t\t.WithGameObjectChanges( gos, GameObjectUndoFlags.All )\r\n\t\t\t.Push() )\r\n\t\t{\r\n\t\t\tforeach ( var go in gos )\r\n\t\t\t\tgo.Tags.Remove( HiddenTag );\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EHide everything except the selection, its ancestors, and its descendants.\u003C/summary\u003E\r\n\tpublic static void IsolateSelection()\r\n\t{\r\n\t\tusing var scope = SceneEditorSession.Scope();\r\n\t\tvar session = SceneEditorSession.Active;\r\n\t\tif ( session?.Scene is not { } scene || !scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tvar selected = EditorScene.Selection\r\n\t\t\t.OfType\u003CGameObject\u003E()\r\n\t\t\t.Where( go =\u003E go.IsValid() \u0026\u0026 go is not Scene )\r\n\t\t\t.ToArray();\r\n\t\tif ( selected.Length == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tvar visible = new HashSet\u003CGameObject\u003E();\r\n\t\tforeach ( var s in selected )\r\n\t\t{\r\n\t\t\t// Descendants first \u2014 CollectSelfAndDescendants skips if already present.\r\n\t\t\tCollectSelfAndDescendants( s, visible );\r\n\r\n\t\t\tfor ( var go = s.Parent; go is not null and not Scene; go = go.Parent )\r\n\t\t\t\tvisible.Add( go );\r\n\t\t}\r\n\r\n\t\tvar changedObjects = scene.GetAllObjects( true )\r\n\t\t\t.Where( go =\u003E go.IsValid() \u0026\u0026 go is not Scene )\r\n\t\t\t.Where( go =\u003E go.Tags.Has( HiddenTag ) == visible.Contains( go ) )\r\n\t\t\t.ToArray();\r\n\t\tif ( changedObjects.Length == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tusing ( session.UndoScope( \u0022Isolate Selection\u0022 )\r\n\t\t\t.WithGameObjectChanges( changedObjects, GameObjectUndoFlags.All )\r\n\t\t\t.Push() )\r\n\t\t{\r\n\t\t\tforeach ( var go in changedObjects )\r\n\t\t\t{\r\n\t\t\t\tif ( visible.Contains( go ) ) go.Tags.Remove( HiddenTag );\r\n\t\t\t\telse go.Tags.Add( HiddenTag );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EApply a HierarchyColor to the current selection.\u003C/summary\u003E\r\n\tpublic static void ApplyColorToSelection( Color color, bool includeChildren = false )\r\n\t{\r\n\t\tusing var scope = SceneEditorSession.Scope();\r\n\t\tvar session = SceneEditorSession.Active;\r\n\t\tif ( session is null )\r\n\t\t\treturn;\r\n\r\n\t\tvar gos = CollectSelection( includeChildren );\r\n\t\tif ( gos.Length == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tusing ( session.UndoScope( \u0022Set Hierarchy Color\u0022 )\r\n\t\t\t.WithGameObjectChanges( gos, GameObjectUndoFlags.All )\r\n\t\t\t.Push() )\r\n\t\t{\r\n\t\t\tforeach ( var go in gos )\r\n\t\t\t{\r\n\t\t\t\tvar marker = go.Components.GetOrCreate\u003CHierarchyColor\u003E();\r\n\t\t\t\tmarker.Tint = color;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tGetHierarchyTree()?.Update();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ERemove HierarchyColor from the current selection.\u003C/summary\u003E\r\n\tpublic static void ClearColorFromSelection( bool includeChildren = false )\r\n\t{\r\n\t\tusing var scope = SceneEditorSession.Scope();\r\n\t\tvar session = SceneEditorSession.Active;\r\n\t\tif ( session is null )\r\n\t\t\treturn;\r\n\r\n\t\tvar gos = CollectSelection( includeChildren )\r\n\t\t\t.Where( go =\u003E go.Components.Get\u003CHierarchyColor\u003E( true ) is not null )\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( gos.Length == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tusing ( session.UndoScope( \u0022Clear Hierarchy Color\u0022 )\r\n\t\t\t.WithGameObjectChanges( gos, GameObjectUndoFlags.All )\r\n\t\t\t.Push() )\r\n\t\t{\r\n\t\t\tforeach ( var go in gos )\r\n\t\t\t{\r\n\t\t\t\tvar marker = go.Components.Get\u003CHierarchyColor\u003E( true );\r\n\t\t\t\tmarker?.Destroy();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tGetHierarchyTree()?.Update();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ERemove HierarchyColor from every object in the active scene.\u003C/summary\u003E\r\n\tpublic static void ClearAllColors()\r\n\t{\r\n\t\tusing var scope = SceneEditorSession.Scope();\r\n\t\tvar session = SceneEditorSession.Active;\r\n\t\tif ( session?.Scene is not { } scene || !scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tvar gos = scene.GetAllObjects( true )\r\n\t\t\t.Where( go =\u003E go.IsValid() \u0026\u0026 go is not Scene \u0026\u0026 go.Components.Get\u003CHierarchyColor\u003E( true ) is not null )\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( gos.Length == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tusing ( session.UndoScope( \u0022Clear All Hierarchy Colors\u0022 )\r\n\t\t\t.WithGameObjectChanges( gos, GameObjectUndoFlags.All )\r\n\t\t\t.Push() )\r\n\t\t{\r\n\t\t\tforeach ( var go in gos )\r\n\t\t\t{\r\n\t\t\t\tvar marker = go.Components.Get\u003CHierarchyColor\u003E( true );\r\n\t\t\t\tmarker?.Destroy();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tGetHierarchyTree()?.Update();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ERemove HierarchyColor from every object whose tint matches \u003Cparamref name=\u0022color\u0022/\u003E.\u003C/summary\u003E\r\n\tpublic static void ClearColorsMatching( Color color, float epsilon = 0.02f )\r\n\t{\r\n\t\tusing var scope = SceneEditorSession.Scope();\r\n\t\tvar session = SceneEditorSession.Active;\r\n\t\tif ( session?.Scene is not { } scene || !scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tvar gos = scene.GetAllObjects( true )\r\n\t\t\t.Where( go =\u003E go.IsValid() \u0026\u0026 go is not Scene )\r\n\t\t\t.Where( go =\u003E\r\n\t\t\t{\r\n\t\t\t\tvar marker = go.Components.Get\u003CHierarchyColor\u003E( true );\r\n\t\t\t\treturn marker is not null \u0026\u0026 marker.IsValid() \u0026\u0026 ColorsMatch( marker.Tint, color, epsilon );\r\n\t\t\t} )\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( gos.Length == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tusing ( session.UndoScope( \u0022Clear Matching Hierarchy Colors\u0022 )\r\n\t\t\t.WithGameObjectChanges( gos, GameObjectUndoFlags.All )\r\n\t\t\t.Push() )\r\n\t\t{\r\n\t\t\tforeach ( var go in gos )\r\n\t\t\t{\r\n\t\t\t\tvar marker = go.Components.Get\u003CHierarchyColor\u003E( true );\r\n\t\t\t\tmarker?.Destroy();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tGetHierarchyTree()?.Update();\r\n\t}\r\n\r\n\tstatic bool ColorsMatch( Color a, Color b, float epsilon )\r\n\t{\r\n\t\treturn MathF.Abs( a.r - b.r ) \u003C epsilon\r\n\t\t\t\u0026\u0026 MathF.Abs( a.g - b.g ) \u003C epsilon\r\n\t\t\t\u0026\u0026 MathF.Abs( a.b - b.b ) \u003C epsilon;\r\n\t}\r\n\r\n\tstatic GameObject[] CollectSelection( bool includeChildren )\r\n\t{\r\n\t\tvar selected = EditorScene.Selection\r\n\t\t\t.OfType\u003CGameObject\u003E()\r\n\t\t\t.Where( go =\u003E go.IsValid() \u0026\u0026 go is not Scene )\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( !includeChildren || selected.Length == 0 )\r\n\t\t\treturn selected;\r\n\r\n\t\tvar set = new HashSet\u003CGameObject\u003E();\r\n\t\tforeach ( var root in selected )\r\n\t\t\tCollectSelfAndDescendants( root, set );\r\n\r\n\t\treturn set.ToArray();\r\n\t}\r\n\r\n\tstatic void CollectSelfAndDescendants( GameObject go, HashSet\u003CGameObject\u003E set )\r\n\t{\r\n\t\tif ( !go.IsValid() || go is Scene || !set.Add( go ) )\r\n\t\t\treturn;\r\n\r\n\t\tforeach ( var child in go.Children )\r\n\t\t\tCollectSelfAndDescendants( child, set );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ESort every parent\u0027s children A\u2013Z by name (recursive).\u003C/summary\u003E\r\n\tpublic static void ReorganizeAlphabetically()\r\n\t{\r\n\t\tReorganizeHierarchy(\r\n\t\t\t\u0022Reorganize Hierarchy Alphabetically\u0022,\r\n\t\t\tchildren =\u003E children\r\n\t\t\t\t.OrderBy( go =\u003E go.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase )\r\n\t\t\t\t.ThenBy( go =\u003E go.Id )\r\n\t\t\t\t.ToList() );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ESort every parent\u0027s children by HierarchyColor hue, then name (recursive).\u003C/summary\u003E\r\n\tpublic static void ReorganizeByColor()\r\n\t{\r\n\t\tReorganizeHierarchy(\r\n\t\t\t\u0022Reorganize Hierarchy By Color\u0022,\r\n\t\t\tchildren =\u003E children\r\n\t\t\t\t.OrderBy( GetColorSortKey )\r\n\t\t\t\t.ThenBy( go =\u003E go.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase )\r\n\t\t\t\t.ThenBy( go =\u003E go.Id )\r\n\t\t\t\t.ToList() );\r\n\t}\r\n\r\n\tstatic void ReorganizeHierarchy( string undoName, Func\u003CList\u003CGameObject\u003E, List\u003CGameObject\u003E\u003E sorter )\r\n\t{\r\n\t\tusing var scope = SceneEditorSession.Scope();\r\n\t\tvar session = SceneEditorSession.Active;\r\n\t\tif ( session?.Scene is not { } scene || !scene.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tvar affected = scene.GetAllObjects( true )\r\n\t\t\t.Where( go =\u003E go.IsValid() \u0026\u0026 go is not Scene )\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( affected.Length == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tusing ( session.UndoScope( undoName )\r\n\t\t\t.WithGameObjectChanges( affected, GameObjectUndoFlags.All )\r\n\t\t\t.Push() )\r\n\t\t{\r\n\t\t\tSortChildrenRecursive( scene, sorter );\r\n\t\t}\r\n\r\n\t\tGetHierarchyTree()?.Update();\r\n\t}\r\n\r\n\tstatic void SortChildrenRecursive( GameObject parent, Func\u003CList\u003CGameObject\u003E, List\u003CGameObject\u003E\u003E sorter )\r\n\t{\r\n\t\tif ( !parent.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tSortChildrenOf( parent, sorter );\r\n\r\n\t\tforeach ( var child in parent.Children.ToArray() )\r\n\t\t\tSortChildrenRecursive( child, sorter );\r\n\t}\r\n\r\n\tstatic void SortChildrenOf( GameObject parent, Func\u003CList\u003CGameObject\u003E, List\u003CGameObject\u003E\u003E sorter )\r\n\t{\r\n\t\tvar children = parent.Children\r\n\t\t\t.Where( go =\u003E go.IsValid() \u0026\u0026 ShouldReorder( go ) )\r\n\t\t\t.ToList();\r\n\r\n\t\tif ( children.Count \u003C 2 )\r\n\t\t\treturn;\r\n\r\n\t\tvar sorted = sorter( children );\r\n\t\tvar alreadyOrdered = true;\r\n\t\tfor ( var i = 0; i \u003C sorted.Count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tif ( !ReferenceEquals( children[i], sorted[i] ) )\r\n\t\t\t{\r\n\t\t\t\talreadyOrdered = false;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( alreadyOrdered )\r\n\t\t\treturn;\r\n\r\n\t\tfor ( var i = 0; i \u003C sorted.Count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar desired = sorted[i];\r\n\t\t\tvar live = parent.Children.Where( ShouldReorder ).ToList();\r\n\t\t\tif ( i \u003E= live.Count )\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tif ( ReferenceEquals( live[i], desired ) )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tif ( i == 0 )\r\n\t\t\t\tlive[0].AddSibling( desired, before: true );\r\n\t\t\telse\r\n\t\t\t\tsorted[i - 1].AddSibling( desired, before: false );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic bool ShouldReorder( GameObject go )\r\n\t{\r\n\t\tif ( !go.IsValid() || go is Scene )\r\n\t\t\treturn false;\r\n\r\n\t\t// Leave engine-generated hierarchy nodes alone.\r\n\t\tif ( go.Flags.Contains( GameObjectFlags.Bone ) )\r\n\t\t\treturn false;\r\n\t\tif ( go.Flags.Contains( GameObjectFlags.Attachment ) )\r\n\t\t\treturn false;\r\n\t\tif ( go.Flags.Contains( GameObjectFlags.PhysicsBone ) )\r\n\t\t\treturn false;\r\n\t\tif ( go.Flags.Contains( GameObjectFlags.ProceduralBone ) )\r\n\t\t\treturn false;\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EColored objects by hue; uncolored last.\u003C/summary\u003E\r\n\tstatic float GetColorSortKey( GameObject go )\r\n\t{\r\n\t\tvar marker = go.Components.Get\u003CHierarchyColor\u003E( true );\r\n\t\tif ( marker is null || !marker.IsValid() )\r\n\t\t\treturn float.MaxValue;\r\n\r\n\t\tvar hsv = marker.Tint.ToHsv();\r\n\t\treturn hsv.Hue;\r\n\t}\r\n}\r\n"},{"Ident":"subzerostudios.subhierarchy","Path":"HierarchyColor.cs","FileName":"HierarchyColor.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":381047,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\nnamespace SubHierarchy;\r\n\r\n/// \u003Csummary\u003E\r\n/// Marks a GameObject with a Hierarchy row tint color (editor organization).\r\n/// \u003C/summary\u003E\r\n[Title( \u0022Hierarchy Color\u0022 )]\r\n[Category( \u0022Editor\u0022 )]\r\n[Icon( \u0022palette\u0022 )]\r\npublic sealed class HierarchyColor : Component\r\n{\r\n\t/// \u003Csummary\u003EBackground tint drawn behind the object name in the Hierarchy.\u003C/summary\u003E\r\n\t[Property]\r\n\tpublic Color Tint { get; set; } = new Color( 0.2f, 0.55f, 0.95f, 1f );\r\n}\r\n"},{"Ident":"subzerostudios.subhierarchy","Path":"Code/HierarchyColor.cs","FileName":"HierarchyColor.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":381047,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\nnamespace SubHierarchy;\r\n\r\n/// \u003Csummary\u003E\r\n/// Marks a GameObject with a Hierarchy row tint color (editor organization).\r\n/// \u003C/summary\u003E\r\n[Title( \u0022Hierarchy Color\u0022 )]\r\n[Category( \u0022Editor\u0022 )]\r\n[Icon( \u0022palette\u0022 )]\r\npublic sealed class HierarchyColor : Component\r\n{\r\n\t/// \u003Csummary\u003EBackground tint drawn behind the object name in the Hierarchy.\u003C/summary\u003E\r\n\t[Property]\r\n\tpublic Color Tint { get; set; } = new Color( 0.2f, 0.55f, 0.95f, 1f );\r\n}\r\n"},{"Ident":"subzerostudios.subhierarchy","Path":"Editor/Assembly.cs","FileName":"Assembly.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":381047,"IsPrivate":false,"Code":"global using Sandbox;\r\nglobal using Editor;\r\nglobal using System;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;\r\nglobal using SubHierarchy;\r\n"}]}