Bindings.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. #nullable enable
  2. using System;
  3. namespace Terminal.Gui;
  4. /// <summary>
  5. /// Abstract base class for <see cref="KeyBindings"/> and <see cref="MouseBindings"/>.
  6. /// </summary>
  7. /// <typeparam name="TEvent">The type of the event (e.g. <see cref="Key"/> or <see cref="MouseEventArgs"/>).</typeparam>
  8. /// <typeparam name="TBinding">The binding type (e.g. <see cref="KeyBinding"/>).</typeparam>
  9. public abstract class Bindings<TEvent, TBinding> where TBinding : IInputBinding, new() where TEvent : notnull
  10. {
  11. /// <summary>
  12. /// The bindings.
  13. /// </summary>
  14. protected readonly Dictionary<TEvent, TBinding> _bindings;
  15. private readonly Func<Command [], TEvent, TBinding> _constructBinding;
  16. /// <summary>
  17. /// Initializes a new instance.
  18. /// </summary>
  19. /// <param name="constructBinding"></param>
  20. /// <param name="equalityComparer"></param>
  21. protected Bindings (Func<Command [], TEvent, TBinding> constructBinding, IEqualityComparer<TEvent> equalityComparer)
  22. {
  23. _constructBinding = constructBinding;
  24. _bindings = new (equalityComparer);
  25. }
  26. /// <summary>Adds a <see cref="TEvent"/> bound to <see cref="TBinding"/> to the collection.</summary>
  27. /// <param name="eventArgs"></param>
  28. /// <param name="binding"></param>
  29. public void Add (TEvent eventArgs, TBinding binding)
  30. {
  31. if (TryGet (eventArgs, out TBinding _))
  32. {
  33. throw new InvalidOperationException (@$"A binding for {eventArgs} exists ({binding}).");
  34. }
  35. // IMPORTANT: Add a COPY of the mouseEventArgs. This is needed because ConfigurationManager.Apply uses DeepMemberWiseCopy
  36. // IMPORTANT: update the memory referenced by the key, and Dictionary uses caching for performance, and thus
  37. // IMPORTANT: Apply will update the Dictionary with the new mouseEventArgs, but the old mouseEventArgs will still be in the dictionary.
  38. // IMPORTANT: See the ConfigurationManager.Illustrate_DeepMemberWiseCopy_Breaks_Dictionary test for details.
  39. _bindings.Add (eventArgs, binding);
  40. }
  41. /// <summary>Gets the commands bound with the specified <see cref="TEvent"/>.</summary>
  42. /// <remarks></remarks>
  43. /// <param name="eventArgs">The args to check.</param>
  44. /// <param name="binding">
  45. /// When this method returns, contains the commands bound with the specified mouse flags, if the mouse flags are
  46. /// found; otherwise, null. This parameter is passed uninitialized.
  47. /// </param>
  48. /// <returns><see langword="true"/> if the mouse flags are bound; otherwise <see langword="false"/>.</returns>
  49. public bool TryGet (TEvent eventArgs, out TBinding? binding)
  50. {
  51. return _bindings.TryGetValue (eventArgs, out binding);
  52. }
  53. /// <summary>
  54. /// <para>Adds a new mouse flag combination that will trigger the commands in <paramref name="commands"/>.</para>
  55. /// <para>
  56. /// If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
  57. /// <paramref name="commands"/>.
  58. /// </para>
  59. /// </summary>
  60. /// <remarks>
  61. /// Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
  62. /// focus to another view and perform multiple commands there).
  63. /// </remarks>
  64. /// <param name="eventArgs">The mouse flags to check.</param>
  65. /// <param name="commands">
  66. /// The command to invoked on the <see cref="View"/> when <paramref name="eventArgs"/> is received. When
  67. /// multiple commands are provided,they will be applied in sequence. The bound <paramref name="eventArgs"/> event
  68. /// will be
  69. /// consumed if any took effect.
  70. /// </param>
  71. public void Add (TEvent eventArgs, params Command [] commands)
  72. {
  73. if (commands.Length == 0)
  74. {
  75. throw new ArgumentException (@"At least one command must be specified", nameof (commands));
  76. }
  77. if (TryGet (eventArgs, out var binding))
  78. {
  79. throw new InvalidOperationException (@$"A binding for {eventArgs} exists ({binding}).");
  80. }
  81. Add (eventArgs, _constructBinding(commands,eventArgs));
  82. }
  83. /// <summary>
  84. /// Gets the bindings.
  85. /// </summary>
  86. /// <returns></returns>
  87. public IEnumerable<KeyValuePair<TEvent, TBinding>> GetBindings ()
  88. {
  89. return _bindings;
  90. }
  91. /// <summary>Removes all <see cref="TEvent"/> objects from the collection.</summary>
  92. public void Clear () { _bindings.Clear (); }
  93. /// <summary>
  94. /// Removes all bindings that trigger the given command set. Views can have multiple different events bound to
  95. /// the same command sets and this method will clear all of them.
  96. /// </summary>
  97. /// <param name="command"></param>
  98. public void Clear (params Command [] command)
  99. {
  100. KeyValuePair<TEvent, TBinding> [] kvps = _bindings
  101. .Where (kvp => kvp.Value.Commands.SequenceEqual (command))
  102. .ToArray ();
  103. foreach (KeyValuePair<TEvent, TBinding> kvp in kvps)
  104. {
  105. Remove (kvp.Key);
  106. }
  107. }
  108. /// <summary>Gets the <see cref="TBinding"/> for the specified <see cref="TEvent"/>.</summary>
  109. /// <param name="eventArgs"></param>
  110. /// <returns></returns>
  111. public TBinding? Get (TEvent eventArgs)
  112. {
  113. if (TryGet (eventArgs, out var binding))
  114. {
  115. return binding;
  116. }
  117. throw new InvalidOperationException ($"{eventArgs} is not bound.");
  118. }
  119. /// <summary>Removes a <see cref="MouseBinding"/> from the collection.</summary>
  120. /// <param name="mouseEventArgs"></param>
  121. public void Remove (TEvent mouseEventArgs)
  122. {
  123. if (!TryGet (mouseEventArgs, out var _))
  124. {
  125. return;
  126. }
  127. _bindings.Remove (mouseEventArgs);
  128. }
  129. }