瀏覽代碼

Incorporated tznind's stuff

Tig 7 月之前
父節點
當前提交
23344baca7

+ 150 - 0
Terminal.Gui/Input/Bindings.cs

@@ -0,0 +1,150 @@
+#nullable enable
+using System;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Abstract base class for <see cref="KeyBindings"/> and <see cref="MouseBindings"/>.
+/// </summary>
+/// <typeparam name="TEvent">The type of the event (e.g. <see cref="Key"/> or <see cref="MouseEventArgs"/>).</typeparam>
+/// <typeparam name="TBinding">The binding type (e.g. <see cref="KeyBinding"/>).</typeparam>
+public abstract class Bindings<TEvent, TBinding>  where TBinding : IInputBinding, new() where TEvent : notnull
+{
+    /// <summary>
+    ///     The bindings.
+    /// </summary>
+    protected readonly Dictionary<TEvent, TBinding> _bindings;
+
+    private readonly Func<Command [], TEvent, TBinding> _constructBinding;
+
+    /// <summary>
+    ///     Initializes a new instance.
+    /// </summary>
+    /// <param name="constructBinding"></param>
+    /// <param name="equalityComparer"></param>
+    protected Bindings (Func<Command [], TEvent, TBinding> constructBinding, IEqualityComparer<TEvent> equalityComparer)
+    {
+        _constructBinding = constructBinding;
+        _bindings = new (equalityComparer);
+    }
+
+    /// <summary>Adds a <see cref="TEvent"/> bound to <see cref="TBinding"/> to the collection.</summary>
+    /// <param name="eventArgs"></param>
+    /// <param name="binding"></param>
+    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);
+    }
+
+
+    /// <summary>Gets the commands bound with the specified <see cref="TEvent"/>.</summary>
+    /// <remarks></remarks>
+    /// <param name="eventArgs">The args to check.</param>
+    /// <param name="binding">
+    ///     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.
+    /// </param>
+    /// <returns><see langword="true"/> if the mouse flags are bound; otherwise <see langword="false"/>.</returns>
+    public bool TryGet (TEvent eventArgs, out TBinding? binding)
+    {
+         return _bindings.TryGetValue (eventArgs, out binding);
+    }
+
+
+    /// <summary>
+    ///     <para>Adds a new mouse flag combination that will trigger the commands in <paramref name="commands"/>.</para>
+    ///     <para>
+    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
+    ///         <paramref name="commands"/>.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
+    ///     focus to another view and perform multiple commands there).
+    /// </remarks>
+    /// <param name="eventArgs">The mouse flags to check.</param>
+    /// <param name="commands">
+    ///     The command to invoked on the <see cref="View"/> when <paramref name="eventArgs"/> is received. When
+    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="eventArgs"/> event
+    ///     will be
+    ///     consumed if any took effect.
+    /// </param>
+    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));
+    }
+
+    /// <summary>
+    ///     Gets the bindings.
+    /// </summary>
+    /// <returns></returns>
+    public IEnumerable<KeyValuePair<TEvent, TBinding>> GetBindings ()
+    {
+        return _bindings;
+    }
+
+    /// <summary>Removes all <see cref="TEvent"/> objects from the collection.</summary>
+    public void Clear () { _bindings.Clear (); }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    /// <param name="command"></param>
+    public void Clear (params Command [] command)
+    {
+        KeyValuePair<TEvent, TBinding> [] kvps = _bindings
+                                            .Where (kvp => kvp.Value.Commands.SequenceEqual (command))
+                                            .ToArray ();
+
+        foreach (KeyValuePair<TEvent, TBinding> kvp in kvps)
+        {
+            Remove (kvp.Key);
+        }
+    }
+
+    /// <summary>Gets the <see cref="TBinding"/> for the specified <see cref="TEvent"/>.</summary>
+    /// <param name="eventArgs"></param>
+    /// <returns></returns>
+    public TBinding? Get (TEvent eventArgs)
+    {
+        if (TryGet (eventArgs, out var binding))
+        {
+            return binding;
+        }
+
+        throw new InvalidOperationException ($"{eventArgs} is not bound.");
+    }
+
+
+    /// <summary>Removes a <see cref="MouseBinding"/> from the collection.</summary>
+    /// <param name="mouseEventArgs"></param>
+    public void Remove (TEvent mouseEventArgs)
+    {
+        if (!TryGet (mouseEventArgs, out var _))
+        {
+            return;
+        }
+
+        _bindings.Remove (mouseEventArgs);
+    }
+}

+ 3 - 3
Terminal.Gui/Input/CommandContext.cs

@@ -7,14 +7,14 @@ namespace Terminal.Gui;
 /// </summary>
 /// <seealso cref="View.Invoke(Command)"/>.
 #pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
-public record struct CommandContext<TBindingType> : ICommandContext
+public record struct CommandContext<TBinding> : ICommandContext
 {
     /// <summary>
     ///     Initializes a new instance with the specified <see cref="Command"/>,
     /// </summary>
     /// <param name="command"></param>
     /// <param name="binding"></param>
-    public CommandContext (Command command, TBindingType? binding)
+    public CommandContext (Command command, TBinding? binding)
     {
         Command = command;
         Binding = binding;
@@ -26,5 +26,5 @@ public record struct CommandContext<TBindingType> : ICommandContext
     /// <summary>
     /// The keyboard or mouse minding that was used to invoke the <see cref="Command"/>, if any.
     /// </summary>
-    public TBindingType? Binding { get; set; }
+    public TBinding? Binding { get; set; }
 }

+ 0 - 0
Terminal.Gui/Input/Mouse/IInputBinding.cs → Terminal.Gui/Input/IInputBinding.cs


+ 2 - 0
Terminal.Gui/Input/Keyboard/Key.cs

@@ -1,3 +1,4 @@
+using System.ComponentModel;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 
@@ -69,6 +70,7 @@ namespace Terminal.Gui;
 ///         </list>
 ///     </para>
 /// </remarks>
+[DefaultValue(KeyCode.Null)]
 public class Key : EventArgs, IEquatable<Key>
 {
     /// <summary>Constructs a new <see cref="Key"/></summary>

+ 14 - 13
Terminal.Gui/Input/Keyboard/KeyBindings.cs

@@ -7,14 +7,15 @@ namespace Terminal.Gui;
 /// <seealso cref="Application.KeyBindings"/>
 /// <seealso cref="View.KeyBindings"/>
 /// <seealso cref="Command"/>
-public class KeyBindings : Bindings<Key,KeyBinding>
+public class KeyBindings : Bindings<Key, KeyBinding>
 {
     /// <summary>Initializes a new instance bound to <paramref name="target"/>.</summary>
-    public KeyBindings (View? target) :base(
-                                            (commands,key)=> new KeyBinding (commands),
-                                            new KeyEqualityComparer ()) { Target = target; }
-
-
+    public KeyBindings (View? target) : base (
+                                              (commands, key) => new (commands),
+                                              new KeyEqualityComparer ())
+    {
+        Target = target;
+    }
 
     /// <summary>
     ///     <para>
@@ -29,11 +30,14 @@ public class KeyBindings : Bindings<Key,KeyBinding>
     /// <remarks>
     /// </remarks>
     /// <param name="key">The key to check.</param>
-    /// <param name="target">The View the commands will be invoked on. If <see langword="null"/>, the key will be bound to <see cref="Application"/>.</param>
+    /// <param name="target">
+    ///     The View the commands will be invoked on. If <see langword="null"/>, the key will be bound to
+    ///     <see cref="Application"/>.
+    /// </param>
     /// <param name="commands">
     ///     The command to invoked on the <see paramref="target"/> when <paramref name="key"/> is pressed. When
     ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
-    ///     consumed if any took effect. 
+    ///     consumed if any took effect.
     /// </param>
     public void Add (Key key, View? target, params Command [] commands)
     {
@@ -45,10 +49,7 @@ public class KeyBindings : Bindings<Key,KeyBinding>
     ///     Gets the bindings.
     /// </summary>
     /// <returns></returns>
-    public IEnumerable<KeyValuePair<Key, KeyBinding>> GetBindings ()
-    {
-        return _bindings;
-    }
+    public IEnumerable<KeyValuePair<Key, KeyBinding>> GetBindings () { return _bindings; }
 
     /// <summary>
     ///     Gets the keys that are bound.
@@ -179,10 +180,10 @@ public class KeyBindings : Bindings<Key,KeyBinding>
         if (newKey == Key.Empty)
         {
             Remove (oldKey);
+
             return;
         }
 
-
         if (TryGet (oldKey, out KeyBinding binding))
         {
             Remove (oldKey);

+ 6 - 150
Terminal.Gui/Input/Mouse/MouseBindings.cs

@@ -1,162 +1,20 @@
 #nullable enable
-using System.Collections;
-using System.Collections.Generic;
-
 namespace Terminal.Gui;
 
-public abstract class Bindings<TKey, TBind>  where TBind : IInputBinding, new()
-{
-    protected readonly Dictionary<TKey, TBind> _bindings;
-    private readonly Func<Command [], TKey, TBind> _constructBinding;
-
-    protected Bindings (Func<Command [], TKey, TBind> constructBinding, IEqualityComparer<TKey> equalityComparer)
-    {
-        _constructBinding = constructBinding;
-        _bindings = new (equalityComparer);
-    }
-
-    /// <summary>Adds a <see cref="MouseBinding"/> to the collection.</summary>
-    /// <param name="mouseEventArgs"></param>
-    /// <param name="binding"></param>
-    public void Add (TKey mouseEventArgs, TBind binding)
-    {
-        if (TryGet (mouseEventArgs, out TBind _))
-        {
-            throw new InvalidOperationException (@$"A binding for {mouseEventArgs} 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 (mouseEventArgs, binding);
-    }
-
-
-    /// <summary>Gets the commands bound with the specified <see cref="MouseFlags"/>.</summary>
-    /// <remarks></remarks>
-    /// <param name="mouseEventArgs">The key to check.</param>
-    /// <param name="binding">
-    ///     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.
-    /// </param>
-    /// <returns><see langword="true"/> if the mouse flags are bound; otherwise <see langword="false"/>.</returns>
-    public bool TryGet (TKey mouseEventArgs, out TBind? binding)
-    {
-        return _bindings.TryGetValue (mouseEventArgs, out binding);
-    }
-
-
-    /// <summary>
-    ///     <para>Adds a new mouse flag combination that will trigger the commands in <paramref name="commands"/>.</para>
-    ///     <para>
-    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
-    ///         <paramref name="commands"/>.
-    ///     </para>
-    /// </summary>
-    /// <remarks>
-    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
-    ///     focus to another view and perform multiple commands there).
-    /// </remarks>
-    /// <param name="mouseFlags">The mouse flags to check.</param>
-    /// <param name="commands">
-    ///     The command to invoked on the <see cref="View"/> when <paramref name="mouseFlags"/> is received. When
-    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="mouseFlags"/> event
-    ///     will be
-    ///     consumed if any took effect.
-    /// </param>
-    public void Add (TKey mouseFlags, params Command [] commands)
-    {
-        if (EqualityComparer<TKey>.Default.Equals (mouseFlags, default))
-        {
-            throw new ArgumentException (@"Invalid MouseFlag", nameof (mouseFlags));
-        }
-
-        if (commands.Length == 0)
-        {
-            throw new ArgumentException (@"At least one command must be specified", nameof (commands));
-        }
-
-        if (TryGet (mouseFlags, out var binding))
-        {
-            throw new InvalidOperationException (@$"A binding for {mouseFlags} exists ({binding}).");
-        }
-
-        Add (mouseFlags, _constructBinding(commands,mouseFlags));
-    }
-
-    /// <summary>
-    ///     Gets the bindings.
-    /// </summary>
-    /// <returns></returns>
-    public IEnumerable<KeyValuePair<TKey, TBind>> GetBindings ()
-    {
-        return _bindings;
-    }
-
-    /// <summary>Removes all <see cref="MouseBinding"/> objects from the collection.</summary>
-    public void Clear () { _bindings.Clear (); }
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    /// <param name="command"></param>
-    public void Clear (params Command [] command)
-    {
-        KeyValuePair<TKey, TBind> [] kvps = _bindings
-                                                         .Where (kvp => kvp.Value.Commands.SequenceEqual (command))
-                                                         .ToArray ();
-
-        foreach (KeyValuePair<TKey, TBind> kvp in kvps)
-        {
-            Remove (kvp.Key);
-        }
-    }
-
-    /// <summary>Gets the <see cref="MouseBinding"/> for the specified combination of <see cref="MouseFlags"/>.</summary>
-    /// <param name="mouseEventArgs"></param>
-    /// <returns></returns>
-    public TBind? Get (TKey mouseEventArgs)
-    {
-        if (TryGet (mouseEventArgs, out var binding))
-        {
-            return binding;
-        }
-
-        throw new InvalidOperationException ($"{mouseEventArgs} is not bound.");
-    }
-
-
-    /// <summary>Removes a <see cref="MouseBinding"/> from the collection.</summary>
-    /// <param name="mouseEventArgs"></param>
-    public void Remove (TKey mouseEventArgs)
-    {
-        if (!TryGet (mouseEventArgs, out var _))
-        {
-            return;
-        }
-
-        _bindings.Remove (mouseEventArgs);
-    }
-}
-
 /// <summary>
 ///     Provides a collection of <see cref="MouseBinding"/> objects bound to a combination of <see cref="MouseFlags"/>.
 /// </summary>
 /// <seealso cref="View.MouseBindings"/>
 /// <seealso cref="Command"/>
-public class MouseBindings : Bindings<MouseFlags,MouseBinding>
+public class MouseBindings : Bindings<MouseFlags, MouseBinding>
 {
     /// <summary>
-    ///     Initializes a new instance. This constructor is used when the <see cref="MouseBindings"/> are not bound to a
-    ///     <see cref="View"/>. This is used for Application.MouseBindings and unit tests.
+    ///     Initializes a new instance.
     /// </summary>
-    public MouseBindings ():base(
-                                 (commands, flags)=> new MouseBinding (commands, flags),
-                                 EqualityComparer<MouseFlags>.Default) { }
-
-    
+    public MouseBindings () : base (
+                                    (commands, flags) => new (commands, flags),
+                                    EqualityComparer<MouseFlags>.Default)
+    { }
 
     /// <summary>
     ///     Gets combination of <see cref="MouseFlags"/> bound to the set of commands specified by
@@ -209,7 +67,6 @@ public class MouseBindings : Bindings<MouseFlags,MouseBinding>
         return _bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key;
     }
 
-
     /// <summary>Replaces the commands already bound to a combination of <see cref="MouseFlags"/>.</summary>
     /// <remarks>
     ///     <para>
@@ -245,7 +102,6 @@ public class MouseBindings : Bindings<MouseFlags,MouseBinding>
             throw new ArgumentException (@"Invalid MouseFlag", nameof (newMouseFlags));
         }
 
-
         if (TryGet (oldMouseFlags, out MouseBinding binding))
         {
             Remove (oldMouseFlags);

+ 2 - 2
Terminal.Gui/Views/RadioGroup.cs

@@ -59,6 +59,7 @@ public class RadioGroup : View, IDesignable, IOrientation
         AddCommand (Command.HotKey,
                     ctx =>
                             {
+                                // If the command did not come from a keyboard event, ignore it
                                 if (ctx is not CommandContext<KeyBinding> keyCommandContext)
                                 {
                                     return false;
@@ -66,10 +67,9 @@ public class RadioGroup : View, IDesignable, IOrientation
 
                                 var item = keyCommandContext.Binding.Data as int?;
 
-
                                 if (HasFocus)
                                 {
-                                    if (keyCommandContext is { Binding : { } } && (keyCommandContext.Binding.Target != this || HotKey == keyCommandContext.Binding.Key?.NoAlt.NoCtrl.NoShift))
+                                    if (keyCommandContext is { Binding : { } } && (item is null || HotKey == keyCommandContext.Binding.Key?.NoAlt.NoCtrl.NoShift))
                                     {
                                         // It's this.HotKey OR Another View (Label?) forwarded the hotkey command to us - Act just like `Space` (Select)
                                         return InvokeCommand (Command.Select);

+ 7 - 7
UnitTests/Input/Keyboard/KeyBindingsTests.cs

@@ -27,13 +27,13 @@ public class KeyBindingsTests ()
         Assert.Contains (Command.Left, resultCommands);
     }
 
-    [Fact]
-    public void Add_Invalid_Key_Throws ()
-    {
-        var keyBindings = new KeyBindings (new View ());
-        List<Command> commands = new ();
-        Assert.Throws<ArgumentException> (() => keyBindings.Add (Key.Empty, Command.Accept));
-    }
+    //[Fact]
+    //public void Add_Invalid_Key_Throws ()
+    //{
+    //    var keyBindings = new KeyBindings (new View ());
+    //    List<Command> commands = new ();
+    //    Assert.Throws<ArgumentException> (() => keyBindings.Add (Key.Empty, Command.Accept));
+    //}
 
     [Fact]
     public void Add_Multiple_Commands_Adds ()

+ 7 - 7
UnitTests/Input/Mouse/MouseBindingsTests.cs

@@ -23,13 +23,13 @@ public class MouseBindingsTests
         Assert.Contains (Command.Left, resultCommands);
     }
 
-    [Fact]
-    public void Add_Invalid_Flag_Throws ()
-    {
-        var mouseBindings = new MouseBindings ();
-        List<Command> commands = new ();
-        Assert.Throws<ArgumentException> (() => mouseBindings.Add (MouseFlags.None, Command.Accept));
-    }
+    //[Fact]
+    //public void Add_Invalid_Flag_Throws ()
+    //{
+    //    var mouseBindings = new MouseBindings ();
+    //    List<Command> commands = new ();
+    //    Assert.Throws<ArgumentException> (() => mouseBindings.Add (MouseFlags.None, Command.Accept));
+    //}
 
     [Fact]
     public void Add_Multiple_Commands_Adds ()