mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
258 lines
9.9 KiB
C#
258 lines
9.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
namespace Robust.Shared.Input
|
|
{
|
|
/// <summary>
|
|
/// Contains a set of created <see cref="InputCmdContext"/>s.
|
|
/// </summary>
|
|
public interface IInputContextContainer
|
|
{
|
|
/// <summary>
|
|
/// The current "active" context that should be used for filtering key binds.
|
|
/// </summary>
|
|
IInputCmdContext ActiveContext { get; }
|
|
|
|
/// <summary>
|
|
/// Puts context switches 'on hold'. When set to false, the last deferred context switch is executed, if any.
|
|
/// </summary>
|
|
bool DeferringEnabled { get; set; }
|
|
|
|
/// <summary>
|
|
/// This event is raised when ever the Active Context is changed.
|
|
/// </summary>
|
|
event EventHandler<ContextChangedEventArgs> ContextChanged;
|
|
|
|
/// <summary>
|
|
/// Adds a new unique context to the set.
|
|
/// </summary>
|
|
/// <param name="uniqueName">Unique name of the new context.</param>
|
|
/// <param name="parentName">Unique name of the parent context. Tee parent context must already exist in the set.</param>
|
|
/// <returns>Instance of the newly created context.</returns>
|
|
IInputCmdContext New(string uniqueName, string parentName);
|
|
|
|
/// <summary>
|
|
/// Adds a new unique context to the set.
|
|
/// </summary>
|
|
/// <param name="uniqueName">Unique name of the new context.</param>
|
|
/// <param name="parent">Context to set, as the parent context, of the newly created context.</param>
|
|
/// <returns>Instance of the newly created context.</returns>
|
|
IInputCmdContext New(string uniqueName, IInputCmdContext parent);
|
|
|
|
/// <summary>
|
|
/// Checks if a context with a unique name exists in the set.
|
|
/// </summary>
|
|
/// <param name="uniqueName">Unique Name to search for.</param>
|
|
/// <returns>If a context exists with the given unique name in the set.</returns>
|
|
bool Exists(string uniqueName);
|
|
|
|
/// <summary>
|
|
/// Returns the context with the given unique name from the set.
|
|
/// </summary>
|
|
/// <param name="uniqueName">Unique name of the context to search for.</param>
|
|
/// <returns>Context with the given unique name in the set.</returns>
|
|
IInputCmdContext GetContext(string uniqueName);
|
|
|
|
/// <summary>
|
|
/// Tries to find a context with a given unique name.
|
|
/// </summary>
|
|
/// <param name="uniqueName">Unique name of the context to search for.</param>
|
|
/// <param name="context">The context with the given unique name (if any).</param>
|
|
/// <returns>If a context with a given unique name exists in the set.</returns>
|
|
bool TryGetContext(string uniqueName, [NotNullWhen(true)] out IInputCmdContext? context);
|
|
|
|
/// <summary>
|
|
/// Removes the context with the given unique name.
|
|
/// </summary>
|
|
/// <param name="uniqueName">Unique name of context to remove.</param>
|
|
void Remove(string uniqueName);
|
|
|
|
/// <summary>
|
|
/// Sets the context with the given unique name as the Active context.
|
|
/// This *may* be deferred if during input handling, and if multiple context switches are deferred, only the last 'counts'.
|
|
/// </summary>
|
|
/// <param name="uniqueName">Unique name of the context to set as active.</param>
|
|
void SetActiveContext(string uniqueName);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
internal sealed class InputContextContainer : IInputContextContainer
|
|
{
|
|
/// <summary>
|
|
/// Default 'root' context unique name that always exists in the set.
|
|
/// </summary>
|
|
public const string DefaultContextName = "common";
|
|
|
|
/// <inheritdoc />
|
|
public event EventHandler<ContextChangedEventArgs>? ContextChanged;
|
|
|
|
private readonly Dictionary<string, InputCmdContext> _contexts = new();
|
|
// Random scribble here:
|
|
// InputManager r/n feeds the first active context using DefaultContextName
|
|
// This is why SetActiveContext needs to be careful to *not* defer the switch if there's no context,
|
|
// or else the client instantly crashes because there's no context on startup
|
|
private InputCmdContext _activeContext = default!;
|
|
/// <summary>
|
|
/// This is used to hold a pending context switch, because someone decided that:
|
|
/// + Reentrant input events were illegal
|
|
/// and
|
|
/// + Context switches ought to generate input events
|
|
/// This prevents the inevitable errors this caused from input events that switch contexts,
|
|
/// by deferring the context switches until we're definitely out of input event code.
|
|
/// </summary>
|
|
private InputCmdContext? _deferredContextSwitch = null;
|
|
|
|
/// <inheritdoc />
|
|
public IInputCmdContext ActiveContext => _activeContext;
|
|
|
|
private bool _deferringEnabled = false;
|
|
|
|
/// <inheritdoc />
|
|
public bool DeferringEnabled
|
|
{
|
|
get => _deferringEnabled;
|
|
set {
|
|
// Must be first because _setActiveContextImmediately triggers input events.
|
|
_deferringEnabled = value;
|
|
if (!value)
|
|
{
|
|
if (_deferredContextSwitch != null)
|
|
{
|
|
var icc = _deferredContextSwitch;
|
|
_deferredContextSwitch = null;
|
|
_setActiveContextImmediately( icc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of <see cref="InputCmdContext"/>.
|
|
/// </summary>
|
|
public InputContextContainer()
|
|
{
|
|
_contexts.Add(DefaultContextName, new InputCmdContext(DefaultContextName));
|
|
SetActiveContext(DefaultContextName);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IInputCmdContext New(string uniqueName, string parentName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(uniqueName))
|
|
throw new ArgumentException("String is null or whitespace.", nameof(uniqueName));
|
|
|
|
if (string.IsNullOrWhiteSpace(parentName))
|
|
throw new ArgumentException("String is null or whitespace.", nameof(parentName));
|
|
|
|
if (!_contexts.TryGetValue(parentName, out var parentContext))
|
|
throw new ArgumentException("Parent does not exist.", nameof(parentName));
|
|
|
|
if (_contexts.ContainsKey(uniqueName))
|
|
throw new ArgumentException($"Context with name {uniqueName} already exists.", nameof(uniqueName));
|
|
|
|
var newContext = new InputCmdContext(parentContext, uniqueName);
|
|
_contexts.Add(uniqueName, newContext);
|
|
return newContext;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IInputCmdContext New(string uniqueName, IInputCmdContext parent)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(uniqueName))
|
|
throw new ArgumentException("String is null or whitespace.", nameof(uniqueName));
|
|
|
|
if (_contexts.ContainsKey(uniqueName))
|
|
throw new ArgumentException($"Context with name {uniqueName} already exists.", nameof(uniqueName));
|
|
|
|
if (parent == null)
|
|
throw new ArgumentNullException(nameof(parent));
|
|
|
|
var newContext = new InputCmdContext(parent, uniqueName);
|
|
_contexts.Add(uniqueName, newContext);
|
|
return newContext;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool Exists(string uniqueName)
|
|
{
|
|
return _contexts.ContainsKey(uniqueName);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IInputCmdContext GetContext(string uniqueName)
|
|
{
|
|
return _contexts[uniqueName];
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool TryGetContext(string uniqueName, [NotNullWhen(true)] out IInputCmdContext? context)
|
|
{
|
|
if (_contexts.TryGetValue(uniqueName, out var ctext))
|
|
{
|
|
context = ctext;
|
|
return true;
|
|
}
|
|
|
|
context = default;
|
|
return false;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Remove(string uniqueName)
|
|
{
|
|
if (uniqueName == DefaultContextName)
|
|
throw new ArgumentException("The default context cannot be removed.", nameof(uniqueName));
|
|
|
|
_contexts.Remove(uniqueName);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SetActiveContext(string uniqueName)
|
|
{
|
|
if (!DeferringEnabled)
|
|
{
|
|
_setActiveContextImmediately(_contexts[uniqueName]);
|
|
}
|
|
else
|
|
{
|
|
_deferredContextSwitch = _contexts[uniqueName];
|
|
}
|
|
}
|
|
|
|
private void _setActiveContextImmediately(InputCmdContext icc)
|
|
{
|
|
var args = new ContextChangedEventArgs(_activeContext, icc);
|
|
_activeContext = icc;
|
|
ContextChanged?.Invoke(this, args);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event arguments for an input context change.
|
|
/// </summary>
|
|
public sealed class ContextChangedEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// The new context that became active.
|
|
/// </summary>
|
|
public IInputCmdContext? NewContext { get; }
|
|
|
|
/// <summary>
|
|
/// The old context that used to be active.
|
|
/// </summary>
|
|
public IInputCmdContext? OldContext { get; }
|
|
|
|
/// <summary>
|
|
/// Constructs a new instance of <see cref="ContextChangedEventArgs"/>/
|
|
/// </summary>
|
|
/// <param name="oldContext">The old context that used to be active.</param>
|
|
/// <param name="newContext">The new context that became active.</param>
|
|
public ContextChangedEventArgs(IInputCmdContext? oldContext, IInputCmdContext? newContext)
|
|
{
|
|
OldContext = oldContext;
|
|
NewContext = newContext;
|
|
}
|
|
}
|
|
}
|