using NativeEngine; using Sandbox.Utility; namespace Sandbox; public static partial class Input { /// /// The current input context, pushed using Context.Push /// static Context CurrentContext { get; set; } // weak list of contexts static List> contexts = new List>(); /// /// Get all of the contexts /// internal static IEnumerable Contexts { get { // remove collected contexts.RemoveAll( x => x.TryGetTarget( out _ ) == false ); foreach ( var t in contexts ) { if ( t.TryGetTarget( out var x ) ) yield return x; } } } static Input() { // We should always have a context available // This will just get GC'd on client as we replace it with frame/tick context var d = Context.Create( "Default" ); d.Push(); } /// /// If the input is suppressed then everything will act like there is no input /// public static bool Suppressed { get; set; } /// /// Allows tracking states of button changes and input deltas in a custom period (such as a tick) rather /// than in a per frame manner. This allows frame and tick to have legit data. /// internal class Context { public string Name { get; private set; } // // These are accumulated values. We collect these during input, then on // Flip we put these into the actual values and reset. // internal ulong AccumActionsPressed { get; set; } internal ulong AccumActionsReleased { get; set; } internal Vector2 AccumMouseDelta { get; set; } internal Vector2 AccumMouseWheel { get; set; } internal HashSet AccumKeysPressed { get; set; } = new(); internal HashSet AccumKeysReleased { get; set; } = new(); // // Last tick // internal ulong SavedActions { get; set; } internal HashSet SavedKeys { get; set; } = new(); // // The input states for this context. These are accessed via // the Input class, when it's been pushed. // internal Vector2 MouseDelta { get; set; } internal Vector2 MouseWheel { get; set; } internal ulong ActionsCurrent { get; set; } internal ulong ActionsPrevious { get; set; } internal HashSet KeysCurrent { get; set; } = new(); internal HashSet KeysPrevious { get; set; } = new(); internal bool MouseCursorVisible { get; set; } public static Context Create( string name ) { var context = new Context( name ); contexts.Add( new( context ) ); return context; } Context( string name ) { this.Name = name; } /// /// Copy accumulated values. Flip previous actions to current actions etc. /// public void Flip() { // Accumulate and reset mouse input MouseDelta = AccumMouseDelta; AccumMouseDelta = default; MouseWheel = AccumMouseWheel; AccumMouseWheel = default; // previous actions are current ActionsPrevious = SavedActions; KeysPrevious = SavedKeys; // Current keys are all pressed KeysCurrent = AccumKeysPressed; SavedKeys = KeysCurrent; // Current actions are all pressed ActionsCurrent = AccumActionsPressed; SavedActions = ActionsCurrent; // Remove released actions AccumActionsPressed &= ~AccumActionsReleased; AccumActionsReleased = default; // Remove released keys AccumKeysPressed = AccumKeysPressed.Except( AccumKeysReleased ).ToHashSet(); AccumKeysReleased.Clear(); } /// /// Make this the current active context. You can optionally use the returned /// IDisposable to restore back to the previous context when you're done. /// public IDisposable Push() { var oc = CurrentContext; CurrentContext = this; return DisposeAction.Create( () => oc?.Push() ); } } }