#nullable enable
using System;
namespace Terminal.Gui;
///
/// Abstract base class for and .
///
/// The type of the event (e.g. or ).
/// The binding type (e.g. ).
public abstract class Bindings where TBinding : IInputBinding, new() where TEvent : notnull
{
///
/// The bindings.
///
protected readonly Dictionary _bindings;
private readonly Func _constructBinding;
///
/// Initializes a new instance.
///
///
///
protected Bindings (Func constructBinding, IEqualityComparer equalityComparer)
{
_constructBinding = constructBinding;
_bindings = new (equalityComparer);
}
/// Adds a bound to to the collection.
///
///
public void Add (TEvent eventArgs, TBinding binding)
{
if (TryGet (eventArgs, out TBinding _))
{
throw new InvalidOperationException (@$"A binding for {eventArgs} exists ({binding}).");
}
// IMPORTANT: Add a COPY of the mouseEventArgs. This is needed because ConfigurationManager.Apply uses DeepMemberWiseCopy
// IMPORTANT: update the memory referenced by the key, and Dictionary uses caching for performance, and thus
// IMPORTANT: Apply will update the Dictionary with the new mouseEventArgs, but the old mouseEventArgs will still be in the dictionary.
// IMPORTANT: See the ConfigurationManager.Illustrate_DeepMemberWiseCopy_Breaks_Dictionary test for details.
_bindings.Add (eventArgs, binding);
}
/// Gets the commands bound with the specified .
///
/// The args to check.
///
/// When this method returns, contains the commands bound with the specified mouse flags, if the mouse flags are
/// found; otherwise, null. This parameter is passed uninitialized.
///
/// if the mouse flags are bound; otherwise .
public bool TryGet (TEvent eventArgs, out TBinding? binding)
{
return _bindings.TryGetValue (eventArgs, out binding);
}
///
/// Adds a new mouse flag combination that will trigger the commands in .
///
/// If the key is already bound to a different array of s it will be rebound
/// .
///
///
///
/// Commands are only ever applied to the current (i.e. this feature cannot be used to switch
/// focus to another view and perform multiple commands there).
///
/// The mouse flags to check.
///
/// The command to invoked on the when is received. When
/// multiple commands are provided,they will be applied in sequence. The bound event
/// will be
/// consumed if any took effect.
///
public void Add (TEvent eventArgs, params Command [] commands)
{
if (commands.Length == 0)
{
throw new ArgumentException (@"At least one command must be specified", nameof (commands));
}
if (TryGet (eventArgs, out var binding))
{
throw new InvalidOperationException (@$"A binding for {eventArgs} exists ({binding}).");
}
Add (eventArgs, _constructBinding(commands,eventArgs));
}
///
/// Gets the bindings.
///
///
public IEnumerable> GetBindings ()
{
return _bindings;
}
/// Removes all objects from the collection.
public void Clear () { _bindings.Clear (); }
///
/// Removes all bindings that trigger the given command set. Views can have multiple different events bound to
/// the same command sets and this method will clear all of them.
///
///
public void Clear (params Command [] command)
{
KeyValuePair [] kvps = _bindings
.Where (kvp => kvp.Value.Commands.SequenceEqual (command))
.ToArray ();
foreach (KeyValuePair kvp in kvps)
{
Remove (kvp.Key);
}
}
/// Gets the for the specified .
///
///
public TBinding? Get (TEvent eventArgs)
{
if (TryGet (eventArgs, out var binding))
{
return binding;
}
throw new InvalidOperationException ($"{eventArgs} is not bound.");
}
/// Removes a from the collection.
///
public void Remove (TEvent mouseEventArgs)
{
if (!TryGet (mouseEventArgs, out var _))
{
return;
}
_bindings.Remove (mouseEventArgs);
}
}