#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); } }