Skip to content

Commit

Permalink
MouseBindings tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tig committed Dec 8, 2024
1 parent 98244b7 commit e502a13
Show file tree
Hide file tree
Showing 10 changed files with 475 additions and 83 deletions.
11 changes: 6 additions & 5 deletions Terminal.Gui/Application/Application.Keyboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,17 @@ public static bool RaiseKeyDownEvent (Key key)

// Invoke any Application-scoped KeyBindings.
// The first view that handles the key will stop the loop.
foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
// foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
if (KeyBindings.TryGet (key, out KeyBinding binding))
{
if (binding.Value.Target is { })
if (binding.Target is { })
{
if (!binding.Value.Target.Enabled)
if (!binding.Target.Enabled)
{
return false;
}

bool? handled = binding.Value.Target?.InvokeCommands (binding.Value.Commands, binding.Value);
bool? handled = binding.Target?.InvokeCommands (binding.Commands, binding);

if (handled != null && (bool)handled)
{
Expand All @@ -65,7 +66,7 @@ public static bool RaiseKeyDownEvent (Key key)
{
if (!KeyBindings.TryGet (key, out KeyBinding appBinding))
{
continue;
return false;
}

bool? toReturn = null;
Expand Down
11 changes: 3 additions & 8 deletions Terminal.Gui/Input/Keyboard/KeyBindings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,12 @@ public void Add (Key key, View? target, params Command [] commands)
private readonly Dictionary<Key, KeyBinding> _bindings = new (new KeyEqualityComparer ());

/// <summary>
/// Gets the bindings bound to <paramref name="key"/>.
/// Gets the bindings.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public IEnumerable<KeyValuePair<Key, KeyBinding>> GetBindings (Key? key = null)
public IEnumerable<KeyValuePair<Key, KeyBinding>> GetBindings ()
{
if (key is null)
{
return _bindings;
}
return _bindings.Where (b => b.Key == key.KeyCode);
return _bindings;
}

/// <summary>
Expand Down
86 changes: 50 additions & 36 deletions Terminal.Gui/Input/Mouse/MouseBindings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void Add (MouseFlags mouseEventArgs, MouseBinding binding)
// 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);
_bindings.Add (mouseEventArgs, binding);
}

/// <summary>
Expand All @@ -42,16 +42,16 @@ public void Add (MouseFlags mouseEventArgs, MouseBinding binding)
/// 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="mouseEventArgs">The mouse flags to check.</param>
/// <param name="mouseFlags">The mouse flags to check.</param>
/// <param name="commands">
/// The command to invoked on the <see cref="View"/> when <paramref name="mouseEventArgs"/> is received. When
/// multiple commands are provided,they will be applied in sequence. The bound <paramref name="mouseEventArgs"/> event
/// 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 (MouseFlags mouseEventArgs, params Command [] commands)
public void Add (MouseFlags mouseFlags, params Command [] commands)
{
if (mouseEventArgs == MouseFlags.None)
if (!Enum.IsDefined (typeof (MouseFlags), mouseFlags) || mouseFlags == MouseFlags.None)
{
throw new ArgumentException (@"Invalid MouseFlag", nameof (commands));
}
Expand All @@ -61,21 +61,27 @@ public void Add (MouseFlags mouseEventArgs, params Command [] commands)
throw new ArgumentException (@"At least one command must be specified", nameof (commands));
}

if (TryGet (mouseEventArgs, out MouseBinding binding))
if (TryGet (mouseFlags, out MouseBinding binding))
{
throw new InvalidOperationException (@$"A binding for {mouseEventArgs} exists ({binding}).");
throw new InvalidOperationException (@$"A binding for {mouseFlags} exists ({binding}).");
}

Add (mouseEventArgs, new MouseBinding (commands, mouseEventArgs));
Add (mouseFlags, new MouseBinding (commands, mouseFlags));
}

// TODO: Add a dictionary comparer that ignores Scope
// TODO: This should not be public!
/// <summary>The collection of <see cref="MouseBinding"/> objects.</summary>
public Dictionary<MouseFlags, MouseBinding> Bindings { get; } = new ();
private readonly Dictionary<MouseFlags, MouseBinding> _bindings = new ();

/// <summary>
/// Gets the bindings.
/// </summary>
/// <returns></returns>
public IEnumerable<KeyValuePair<MouseFlags, MouseBinding>> GetBindings ()
{
return _bindings;
}

/// <summary>Removes all <see cref="MouseBinding"/> objects from the collection.</summary>
public void Clear () { Bindings.Clear (); }
public void Clear () { _bindings.Clear (); }

/// <summary>
/// Removes all bindings that trigger the given command set. Views can have multiple different events bound to
Expand All @@ -84,7 +90,7 @@ public void Add (MouseFlags mouseEventArgs, params Command [] commands)
/// <param name="command"></param>
public void Clear (params Command [] command)
{
KeyValuePair<MouseFlags, MouseBinding> [] kvps = Bindings
KeyValuePair<MouseFlags, MouseBinding> [] kvps = _bindings
.Where (kvp => kvp.Value.Commands.SequenceEqual (command))
.ToArray ();

Expand Down Expand Up @@ -118,25 +124,25 @@ public MouseBinding Get (MouseFlags mouseEventArgs)
/// </returns>
public IEnumerable<MouseFlags> GetAllMouseFlagsFromCommands (params Command [] commands)
{
return Bindings.Where (a => a.Value.Commands.SequenceEqual (commands)).Select (a => a.Key);
return _bindings.Where (a => a.Value.Commands.SequenceEqual (commands)).Select (a => a.Key);
}

/// <summary>
/// Gets the <see cref="MouseFlags"/> that are bound.
/// </summary>
/// <returns></returns>
public IEnumerable<MouseFlags> GetBoundMouseFlags () { return Bindings.Keys; }
public IEnumerable<MouseFlags> GetBoundMouseFlags () { return _bindings.Keys; }

/// <summary>Gets the array of <see cref="Command"/>s bound to <paramref name="mouseEventArgs"/> if it exists.</summary>
/// <param name="mouseEventArgs">The key to check.</param>
/// <summary>Gets the array of <see cref="Command"/>s bound to <paramref name="mouseFlags"/> if it exists.</summary>
/// <param name="mouseFlags">The key to check.</param>
/// <returns>
/// The array of <see cref="Command"/>s if <paramref name="mouseEventArgs"/> is bound. An empty <see cref="Command"/>
/// The array of <see cref="Command"/>s if <paramref name="mouseFlags"/> is bound. An empty <see cref="Command"/>
/// array
/// if not.
/// </returns>
public Command [] GetCommands (MouseFlags mouseEventArgs)
public Command [] GetCommands (MouseFlags mouseFlags)
{
if (TryGet (mouseEventArgs, out MouseBinding bindings))
if (TryGet (mouseFlags, out MouseBinding bindings))
{
return bindings.Commands;
}
Expand All @@ -153,9 +159,9 @@ public Command [] GetCommands (MouseFlags mouseEventArgs)
/// The first combination of <see cref="MouseFlags"/> bound to the set of commands specified by
/// <paramref name="commands"/>. <see langword="null"/> if the set of caommands was not found.
/// </returns>
public MouseFlags? GetMouseFlagsFromCommands (params Command [] commands)
public MouseFlags GetMouseFlagsFromCommands (params Command [] commands)
{
return Bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key;
return _bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key;
}

/// <summary>Removes a <see cref="MouseBinding"/> from the collection.</summary>
Expand All @@ -167,7 +173,7 @@ public void Remove (MouseFlags mouseEventArgs)
return;
}

Bindings.Remove (mouseEventArgs);
_bindings.Remove (mouseEventArgs);
}

/// <summary>Replaces the commands already bound to a combination of <see cref="MouseFlags"/>.</summary>
Expand All @@ -177,16 +183,17 @@ public void Remove (MouseFlags mouseEventArgs)
/// </para>
/// </remarks>
/// <param name="mouseEventArgs">The combination of <see cref="MouseFlags"/> bound to the command to be replaced.</param>
/// <param name="commands">The set of commands to replace the old ones with.</param>
public void ReplaceCommands (MouseFlags mouseEventArgs, params Command [] commands)
/// <param name="newCommands">The set of commands to replace the old ones with.</param>
public void ReplaceCommands (MouseFlags mouseEventArgs, params Command [] newCommands)
{
if (TryGet (mouseEventArgs, out MouseBinding binding))
{
binding.Commands = commands;
Remove (mouseEventArgs);
Add (mouseEventArgs, newCommands);
}
else
{
Add (mouseEventArgs, commands);
Add (mouseEventArgs, newCommands);
}
}

Expand All @@ -197,16 +204,23 @@ public void ReplaceCommands (MouseFlags mouseEventArgs, params Command [] comman
/// The new <see cref="MouseFlags"/> to be used. If <see cref="Key.Empty"/> no action
/// will be taken.
/// </param>
public void ReplaceKey (MouseFlags oldMouseFlags, MouseFlags newMouseFlags)
public void ReplaceMouseFlag (MouseFlags oldMouseFlags, MouseFlags newMouseFlags)
{
if (!TryGet (oldMouseFlags, out MouseBinding _))
if (newMouseFlags == MouseFlags.None)
{
throw new InvalidOperationException ($"Key {oldMouseFlags} is not bound.");
throw new ArgumentException (@"Invalid MouseFlag", nameof (newMouseFlags));
}

MouseBinding value = Bindings [oldMouseFlags];
Remove (oldMouseFlags);
Add (newMouseFlags, value);

if (TryGet (oldMouseFlags, out MouseBinding binding))
{
Remove (oldMouseFlags);
Add (newMouseFlags, binding);
}
else
{
Add (newMouseFlags, binding);
}
}

/// <summary>Gets the commands bound with the specified <see cref="MouseFlags"/>.</summary>
Expand All @@ -221,6 +235,6 @@ public bool TryGet (MouseFlags mouseEventArgs, out MouseBinding binding)
{
binding = new ([], mouseEventArgs);

return Bindings.TryGetValue (mouseEventArgs, out binding);
return _bindings.TryGetValue (mouseEventArgs, out binding);
}
}
9 changes: 7 additions & 2 deletions Terminal.Gui/View/View.Mouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -595,8 +595,6 @@ protected bool RaiseMouseWheelEvent (MouseEventArgs args)
return true;
}

// Post-conditions

args.Handled = InvokeCommandsBoundToMouse (args) == true;

return args.Handled;
Expand Down Expand Up @@ -643,6 +641,13 @@ private bool RaiseHighlight (CancelEventArgs<HighlightStyle> args)

Highlight?.Invoke (this, args);

//if (args.Cancel)
//{
// return true;
//}

//args.Cancel = InvokeCommandsBoundToMouse (args) == true;

return args.Cancel;
}

Expand Down
2 changes: 1 addition & 1 deletion UnitTests/Application/ApplicationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ public void Init_KeyBindings_Set_To_Custom ()

Assert.Equal (Key.Q.WithCtrl, Application.QuitKey);

Assert.NotEmpty (Application.KeyBindings.GetBindings (Key.Q.WithCtrl));
Assert.True (Application.KeyBindings.TryGet (Key.Q.WithCtrl, out _));

Application.Shutdown ();
Locations = ConfigLocations.Default;
Expand Down
59 changes: 33 additions & 26 deletions UnitTests/Input/Keyboard/KeyBindingsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ namespace Terminal.Gui.InputTests;

public class KeyBindingsTests ()
{
[Fact]
public void Add_Adds ()
{
var keyBindings = new KeyBindings (new ());
Command [] commands = { Command.Right, Command.Left };

var key = new Key (Key.A);
keyBindings.Add (Key.A, commands);
KeyBinding binding = keyBindings.Get (key);
Assert.Contains (Command.Right, binding.Commands);
Assert.Contains (Command.Left, binding.Commands);

binding = keyBindings.Get (key);
Assert.Contains (Command.Right, binding.Commands);
Assert.Contains (Command.Left, binding.Commands);

Command [] resultCommands = keyBindings.GetCommands (key);
Assert.Contains (Command.Right, resultCommands);
Assert.Contains (Command.Left, resultCommands);
}

[Fact]
public void Add_Invalid_Key_Throws ()
{
Expand All @@ -15,7 +36,7 @@ public void Add_Invalid_Key_Throws ()
}

[Fact]
public void Add_Multiple_Adds ()
public void Add_Multiple_Commands_Adds ()
{
var keyBindings = new KeyBindings (new ());
Command [] commands = [Command.Right, Command.Left];
Expand All @@ -40,7 +61,7 @@ public void Add_No_Commands_Throws ()
}

[Fact]
public void Add_Single_Adds ()
public void Add_Single_Command_Adds ()
{
var keyBindings = new KeyBindings (new ());
keyBindings.Add (Key.A, Command.HotKey);
Expand All @@ -55,7 +76,7 @@ public void Add_Single_Adds ()

// Add should not allow duplicates
[Fact]
public void Add_With_Throws_If_Exists ()
public void Add_Throws_If_Exists ()
{
var keyBindings = new KeyBindings (new View ());
keyBindings.Add (Key.A, Command.HotKey);
Expand All @@ -79,7 +100,14 @@ public void Add_With_Throws_If_Exists ()
Assert.Contains (Command.HotKey, resultCommands);

keyBindings = new (new View ());
keyBindings.Add (Key.A, new KeyBinding (new [] { Command.HotKey }));
keyBindings.Add (Key.A, Command.Accept);
Assert.Throws<InvalidOperationException> (() => keyBindings.Add (Key.A, Command.ScrollDown));

resultCommands = keyBindings.GetCommands (Key.A);
Assert.Contains (Command.Accept, resultCommands);

keyBindings = new (new View ());
keyBindings.Add (Key.A, new KeyBinding ([Command.HotKey]));
Assert.Throws<InvalidOperationException> (() => keyBindings.Add (Key.A, new KeyBinding (new [] { Command.Accept })));

resultCommands = keyBindings.GetCommands (Key.A);
Expand Down Expand Up @@ -247,7 +275,7 @@ public void ReplaceKey_Adds_If_DoesNotContain_Old ()
{
var keyBindings = new KeyBindings (new ());
keyBindings.ReplaceKey (Key.A, Key.B);
Assert.NotEmpty (keyBindings.GetBindings (Key.B));
Assert.True (keyBindings.TryGet (Key.B, out _));
}

[Fact]
Expand All @@ -258,27 +286,6 @@ public void ReplaceKey_Throws_If_New_Is_Empty ()
Assert.Throws<InvalidOperationException> (() => keyBindings.ReplaceKey (Key.A, Key.Empty));
}

[Fact]
public void Add_Adds ()
{
var keyBindings = new KeyBindings (new ());
Command [] commands = { Command.Right, Command.Left };

var key = new Key (Key.A);
keyBindings.Add (Key.A, commands);
KeyBinding binding = keyBindings.Get (key);
Assert.Contains (Command.Right, binding.Commands);
Assert.Contains (Command.Left, binding.Commands);

binding = keyBindings.Get (key);
Assert.Contains (Command.Right, binding.Commands);
Assert.Contains (Command.Left, binding.Commands);

Command [] resultCommands = keyBindings.GetCommands (key);
Assert.Contains (Command.Right, resultCommands);
Assert.Contains (Command.Left, resultCommands);
}

[Fact]
public void Get_Gets ()
{
Expand Down
6 changes: 6 additions & 0 deletions UnitTests/Input/Mouse/MouseBindingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Terminal.Gui.InputTests;

public class MouseBindingTests
{
// TODO: Add tests for MouseBinding
}
Loading

0 comments on commit e502a13

Please sign in to comment.