Editor/Session/PanelEditorSession.cs

using Sandbox.Razor;
using Sandbox.UI;
using System.IO;
using System.Linq;

namespace Panelize;

public partial class PanelEditorSession
{
	[EditorEvent.Hotload]
	private static void OnHotload()
	{
		UpdatePanelTypes();
	}
	private static void UpdatePanelTypes()
	{
		panelTypes = EditorTypeLibrary.GetTypes<Panel>().ToList();
	}
	static List<TypeDescription> panelTypes;
	public static PanelEditorSession Current { get; internal set; }
	public string FilePath { get; set; }
	public Panel Panel { get; set; }
	public PanelEditor EditorWidget { get; set; }
	public IReadOnlyList<StyleSheet> PanelSheets => panelSheets.AsReadOnly();
	public bool HasChanges { get; set; }
	private Dictionary<Panel, PanelState> panelStates = new();
	private List<StyleSheet> panelSheets = new();
	private List<Panel> createdPanels = new();
	public PanelEditorSession(PanelEditor editor)
	{
		EditorWidget = editor;
		UpdateWindowTitle();

		if ( panelTypes == null )
			UpdatePanelTypes();
	}
	public Panel New()
	{
		Panel newPanel = new();
		newPanel.ElementName = "newpanel";
		StyleSheet sheet = new();

		newPanel.StyleSheet.Add( sheet );
		panelSheets.Add( sheet );

		Panel = newPanel;
		EditorWidget.RootPanel.DeleteChildren();
		EditorWidget.RootPanel.AddChild( newPanel );

		UpdateWindowTitle();
		return newPanel;
	}
	public Panel LoadPanel( string path )
	{
		if ( string.IsNullOrEmpty( path ) )
		{
			Log.Warning( $"Cant load panel, path is invalid!" );
			return null;
		}

		if ( panelTypes.Count == 0 )
		{
			Log.Warning( $"Cant load panel, no types found!" );
			return null;
		}

		TypeDescription panelType = null;
		foreach ( var type in panelTypes )
		{
			if ( path.ToLower() == GetFullPath( type ).ToLower() )
			{
				panelType = type;
				break;
			}
		}

		if ( panelType == null )
		{
			Log.Warning( "Cant load panel, not found in types!" );
			return null;
		}

		var referencePanel = panelType.Create<Panel>();
		// Render reference manually
		referencePanel.CallMethod( "InternalRenderTree" );
		void MovePanel(Panel reference, Panel parent)
		{
			parent.AddChild( reference );
			if(path.Contains(reference.SourceFile))
				createdPanels.Add(reference);
			if ( reference.HasChildren )
			{
				foreach ( var child in reference.Children )
				{
					MovePanel( child, reference );
				}
			}
		}

		Panel newPanel = new();
		newPanel.StyleSheet = referencePanel.StyleSheet;
		EditorWidget.RootPanel.DeleteChildren( true );
		EditorWidget.RootPanel.AddChild( newPanel );

		createdPanels = [newPanel];
		newPanel.ElementName = referencePanel.ElementName;
		foreach(var child in referencePanel.Children.ToArray() )
			MovePanel(child, newPanel);

		FilePath = path;
		Panel = newPanel;
		panelSheets = newPanel.StyleSheet.GetMember<List<StyleSheet>>( "List" ) ?? new();
		if ( !panelSheets.Any() )
			panelSheets.Add( new() );

		referencePanel.Delete( true );
		Log.Info( $"Load panel {newPanel} ({newPanel.ChildrenCount} children) ({createdPanels.Count} created)" );

		UpdateWindowTitle();
		return referencePanel;
	}
	/// <summary>
	/// Registers a panel as created by this editor.
	/// </summary>
	/// <param name="panel"></param>
	public void AddEditorPanel(Panel panel)
	{
		createdPanels.Add( panel );
	}
	public bool IsEditorPanel(Panel panel)
	{
		return createdPanels?.Contains( panel ) ?? false;
	}
	public List<StyleBlock> GetStyleBlocks(Panel panel)
	{
		List<StyleBlock> blocks = new();
		foreach(var sheet in panelSheets)
		{
			blocks.AddRange(sheet.Nodes.Where( b => b.TestBroadphase(panel) ));
		}

		return blocks;
	}
	public PanelState GetPanelState(Panel panel)
	{
		if ( panelStates.TryGetValue( panel, out var value ) )
			return value;

		return new();
	}
	public void SetPanelState( Panel panel, PanelState state )
	{
		if ( panelStates.ContainsKey( panel ) )
			panelStates[panel] = state;
		else
			panelStates.Add( panel, state );
	}
	private string GetFullPath( TypeDescription type )
	{
		return Editor.FileSystem.Mounted.GetFullPath( type.SourceFile )?.Replace( '\\', '/' ) ?? "";
	}
	public void MakeActive()
	{
		Current = this;
		UpdateWindowTitle();
		BringToFront();
	}
	public void BringToFront()
	{
		MainWindow.Instance.DockManager.RaiseDock( EditorWidget );
	}
	public void UpdateWindowTitle()
	{
		if(Panel.IsValid())
		{
			EditorWidget.WindowTitle = $"Preview - {Panel.ElementName}";
		}
		else if ( !string.IsNullOrEmpty(FilePath))
		{
			EditorWidget.WindowTitle = $"Preview - {FilePath}";
		}
		else
		{
			EditorWidget.WindowTitle = $"Preview - Unnamed";
		}
	}
	public void DockEditorTab()
	{
		if ( !EditorWidget.IsValid() )
			return;

		Widget sibling = MainWindow.Instance.Children.OfType<PanelEditor>().Where(e => e != EditorWidget).FirstOrDefault();
		MainWindow.Instance.DockManager.AddDock( sibling, EditorWidget, DockArea.Inside );
	}

	internal void OnEditorClosed()
	{
		MainWindow.Instance.RemoveEditor( EditorWidget );
	}

	public override int GetHashCode()
	{
		return HashCode.Combine( FilePath, Panel, createdPanels );
	}
}