UI/Navigation/NavStack.cs

Generic navigation stack for UI routes. Maintains a list of enum-typed routes, supports Go, Push, Pop, PopTo, Reset, Clear, and exposes current route, depth, revision and change event.

Reflection
namespace Sunless.Libraries.UI;

using System;
using System.Collections.Generic;
using System.Linq;

public static class NavStack<TRoute> where TRoute : struct, Enum
{
	static readonly List<TRoute> _stack = new();
	static int _revision;

	public static event Action OnChanged;

	public static TRoute Current => _stack.Count > 0 ? _stack[^1] : default;
	public static int Revision => _revision;
	public static int Depth => _stack.Count;
	public static bool CanPop => _stack.Count > 1;
	public static IReadOnlyList<TRoute> Stack => _stack;

	public static void Go(TRoute route)
	{
		if (_stack.Count > 0 && EqualityComparer<TRoute>.Default.Equals(_stack[^1], route)) return;

		if (_stack.Count == 0) _stack.Add(route);
		else _stack[^1] = route;

		Bump();
	}

	public static void Push(TRoute route)
	{
		if (_stack.Count > 0 && EqualityComparer<TRoute>.Default.Equals(_stack[^1], route)) return;
		_stack.Add(route);
		Bump();
	}

	public static bool Pop()
	{
		if (_stack.Count <= 1) return false;
		_stack.RemoveAt(_stack.Count - 1);
		Bump();
		return true;
	}

	public static void PopTo(TRoute route)
	{
		var idx = _stack.LastIndexOf(route);
		if (idx < 0) return;
		while (_stack.Count - 1 > idx) _stack.RemoveAt(_stack.Count - 1);
		Bump();
	}

	public static void Reset(TRoute route)
	{
		_stack.Clear();
		_stack.Add(route);
		Bump();
	}

	public static void Clear()
	{
		_stack.Clear();
		Bump();
	}

	public static bool IsAt(TRoute route) =>
		_stack.Count > 0 && EqualityComparer<TRoute>.Default.Equals(_stack[^1], route);

	public static bool Contains(TRoute route) => _stack.Contains(route);

	static void Bump()
	{
		_revision++;
		try { OnChanged?.Invoke(); }
		catch (Exception e) { Log.Warning($"[NavStack<{typeof(TRoute).Name}>] OnChanged threw: {e.Message}"); }
	}
}