UI/Window/Window.cs
using Sandbox.UI;

namespace HC3.UI;

public abstract class Window : Panel
{
	/// <summary>
	/// Window title, shown in titlebar
	/// </summary>
	public virtual string Title { get; set; } = "New Window";

	/// <summary>
	/// Unique key for this window. If a new window is opened with the same key, we'll focus this one instead.
	/// </summary>
	public virtual object Key { get; }

	/// <summary>
	/// Window icon, shown in titlebar
	/// </summary>
	public virtual string Icon { get; } = null;

	/// <summary>
	/// Can this window be shrunk?
	/// </summary>
	public virtual bool CanShrink { get; } = true;

	/// <summary>
	/// Normalised position of this window
	/// </summary>
	public Vector2 Position;

	/// <summary>
	/// Position of this window within Screen bounds
	/// </summary>
	public Vector2 ScreenPosition
	{
		get => (Position * Screen.Size);//.Clamp( Vector2.Zero, Screen.Size );
		set
		{
			var p = value / Screen.Size;
			if ( p.AlmostEqual( Position ) ) return;
			Position = p;
		}
	}

	public enum ScreenAlignment
	{
		TopLeft,
		TopCenter,
		TopRight,
		CenterLeft,
		Center,
		CenterRight,
		BottomLeft,
		BottomCenter,
		BottomRight
	}
	public ScreenAlignment Alignment { get; private set; } = ScreenAlignment.TopLeft;
	bool _alignmentDirty = false;

	private List<Window> _subwindows = new List<Window>();

	public Window()
	{
		Key = this;
	}

	/// <summary>
	/// Whether this window should persist when another of the same type opens
	/// </summary>
	public bool Pinned { get; set; } = false;

	/// <summary>
	/// Whether this window is pinnable at all
	/// </summary>
	public virtual bool IsPinnable => false;

	public void SetTitle( string title )
	{
		Title = title;
	}

	public void ConstrainToScreen()
	{
		ScreenPosition = ScreenPosition.Clamp( Vector2.Zero, Screen.Size - Box.Rect.Size );
	}

	public void Realign()
	{
		var boxSize = Box.Rect.Size;
		var offset = Vector2.Zero;

		switch ( Alignment )
		{
			case ScreenAlignment.TopLeft:
				offset = Vector2.Zero;
				break;
			case ScreenAlignment.TopCenter:
				offset = new Vector2( -boxSize.x / 2, 0 );
				break;
			case ScreenAlignment.TopRight:
				offset = new Vector2( -boxSize.x, 0 );
				break;
			case ScreenAlignment.CenterLeft:
				offset = new Vector2( 0, -boxSize.y / 2 );
				break;
			case ScreenAlignment.Center:
				offset = -boxSize / 2;
				break;
			case ScreenAlignment.CenterRight:
				offset = new Vector2( -boxSize.x, -boxSize.y / 2 );
				break;
			case ScreenAlignment.BottomLeft:
				offset = new Vector2( 0, -boxSize.y );
				break;
			case ScreenAlignment.BottomCenter:
				offset = new Vector2( -boxSize.x / 2, -boxSize.y );
				break;
			case ScreenAlignment.BottomRight:
				offset = -boxSize;
				break;
		}

		ScreenPosition = ScreenPosition + offset;
		ConstrainToScreen();
	}

	public void SetPosition( Vector2 screenPosition, ScreenAlignment alignment )
	{
		ScreenPosition = screenPosition;

		Alignment = alignment;
		_alignmentDirty = true;
	}

	public void SetPosition( ScreenAlignment alignment )
	{
		Alignment = alignment;
		_alignmentDirty = true;

		ScreenPosition = Alignment switch
		{
			ScreenAlignment.BottomCenter => new Vector2( Screen.Size.x / 2, Screen.Size.y - 425f ),
			_ => Screen.Size / 2,
		};
	}

	public override void OnLayout( ref Rect layoutRect )
	{
		base.OnLayout( ref layoutRect );

		if ( _alignmentDirty )
		{
			//  bs: can't do this on creation as we've not sized it
			Realign();
			_alignmentDirty = false;
		}
	}

	/// <summary>
	/// Opens the window as a subwindow of this one, so it'll get closed when we do.
	/// </summary>
	public void OpenSubwindow( Window window )
	{
		_subwindows.Add( window );
		WindowManager.Instance.Open( window );
	}

	public override void OnDeleted()
	{
		base.OnDeleted();

		foreach ( Window window in _subwindows )
		{
			WindowManager.Instance.Close( window );
		}
	}

	/// <summary>
	/// Close this window
	/// </summary>
	public void Close()
	{
		WindowManager.Instance.Close( this );
	}

	/// <summary>
	/// Called when this window is closed
	/// </summary>
	public virtual void OnClose()
	{
		OnDeactivated();
	}

	/// <summary>
	/// Called when this window is activated
	/// </summary>
	public virtual void OnActivated()
	{
		if ( Key is IInspectable inspectable )
		{
			SelectionSystem.Instance.OnSelected( inspectable );
		}
	}

	/// <summary>
	/// Called when this window is deactivated
	/// </summary>
	public virtual void OnDeactivated()
	{
		if ( Key is IInspectable inspectable )
		{
			SelectionSystem.Instance.OnSelected( null );
		}
	}
}