diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs
index e0ebe2671..34b63ceb7 100644
--- a/Terminal.Gui/Application/Application.Keyboard.cs
+++ b/Terminal.Gui/Application/Application.Keyboard.cs
@@ -321,7 +321,7 @@ public static partial class Application // Keyboard handling
///
///
///
- /// This version of AddCommand is for commands that do not require a .
+ /// This version of AddCommand is for commands that do not require a .
///
///
/// The command.
diff --git a/Terminal.Gui/Input/CommandContext.cs b/Terminal.Gui/Input/CommandContext.cs
index 613b35320..61c21f4e6 100644
--- a/Terminal.Gui/Input/CommandContext.cs
+++ b/Terminal.Gui/Input/CommandContext.cs
@@ -3,14 +3,9 @@ namespace Terminal.Gui;
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
///
-/// Provides context for a that is being invoked.
+/// Provides context for a invocation.
///
-///
-///
-/// To define a that is invoked with context,
-/// use .
-///
-///
+/// .
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
public record struct CommandContext : ICommandContext
{
@@ -19,22 +14,17 @@ public record struct CommandContext : ICommandContext
///
///
///
- ///
- public CommandContext (Command command, TBindingType? binding, object? data = null)
+ public CommandContext (Command command, TBindingType? binding)
{
Command = command;
Binding = binding;
- Data = data;
}
+ ///
+ public Command Command { get; set; }
+
///
/// The keyboard or mouse minding that was used to invoke the , if any.
///
public TBindingType? Binding { get; set; }
-
- ///
- public Command Command { get; set; }
-
- ///
- public object? Data { get; set; }
}
\ No newline at end of file
diff --git a/Terminal.Gui/Input/CommandEventArgs.cs b/Terminal.Gui/Input/CommandEventArgs.cs
index 9782c7dfd..70e2751d7 100644
--- a/Terminal.Gui/Input/CommandEventArgs.cs
+++ b/Terminal.Gui/Input/CommandEventArgs.cs
@@ -9,7 +9,10 @@ namespace Terminal.Gui;
public class CommandEventArgs : CancelEventArgs
{
///
- /// The context for the command.
+ /// The context for the command, if any.
///
- public required ICommandContext Context { get; init; }
+ ///
+ /// If the command was invoked without context.
+ ///
+ public required ICommandContext? Context { get; init; }
}
diff --git a/Terminal.Gui/Input/ICommandContext.cs b/Terminal.Gui/Input/ICommandContext.cs
index 77124922a..1aa5c8660 100644
--- a/Terminal.Gui/Input/ICommandContext.cs
+++ b/Terminal.Gui/Input/ICommandContext.cs
@@ -11,9 +11,9 @@ public interface ICommandContext
///
public Command Command { get; set; }
- // TODO: Remove this property. With CommandContext being a generic type, there should be no need for arbitrary data.
- ///
- /// Arbitrary data.
- ///
- public object? Data { get; set; }
+ //// TODO: Remove this property. With CommandContext being a generic type, there should be no need for arbitrary data.
+ /////
+ ///// Arbitrary data.
+ /////
+ //public object? Data { get; set; }
}
diff --git a/Terminal.Gui/Input/Keyboard/KeyBinding.cs b/Terminal.Gui/Input/Keyboard/KeyBinding.cs
index e420698e3..a67aabb50 100644
--- a/Terminal.Gui/Input/Keyboard/KeyBinding.cs
+++ b/Terminal.Gui/Input/Keyboard/KeyBinding.cs
@@ -21,20 +21,20 @@ public record struct KeyBinding
{
Commands = commands;
Scope = scope;
- Context = context;
+ Data = context;
}
/// Initializes a new instance.
/// The commands this key binding will invoke.
/// The scope of the .
/// The view the key binding is bound to.
- /// Arbitrary context that can be associated with this key binding.
- public KeyBinding (Command [] commands, KeyBindingScope scope, View? boundView, object? context = null)
+ /// Arbitrary data that can be associated with this key binding.
+ public KeyBinding (Command [] commands, KeyBindingScope scope, View? boundView, object? data = null)
{
Commands = commands;
Scope = scope;
BoundView = boundView;
- Context = context;
+ Data = data;
}
/// The commands this key binding will invoke.
@@ -46,7 +46,7 @@ public record struct KeyBinding
///
/// The Key that is bound to the .
///
- public Key Key { get; set; }
+ public Key? Key { get; set; }
/// The view the key binding is bound to.
public View? BoundView { get; set; }
@@ -54,5 +54,5 @@ public record struct KeyBinding
///
/// Arbitrary context that can be associated with this key binding.
///
- public object? Context { get; set; }
+ public object? Data { get; set; }
}
diff --git a/Terminal.Gui/Input/Keyboard/KeyBindings.cs b/Terminal.Gui/Input/Keyboard/KeyBindings.cs
index 1c989862a..e0cebe7cf 100644
--- a/Terminal.Gui/Input/Keyboard/KeyBindings.cs
+++ b/Terminal.Gui/Input/Keyboard/KeyBindings.cs
@@ -431,6 +431,7 @@ public class KeyBindings
{
if (scope.HasFlag (binding.Scope))
{
+ binding.Key = key;
return true;
}
}
diff --git a/Terminal.Gui/Input/Mouse/MouseBindings.cs b/Terminal.Gui/Input/Mouse/MouseBindings.cs
index 3ceee4aaf..93af18c25 100644
--- a/Terminal.Gui/Input/Mouse/MouseBindings.cs
+++ b/Terminal.Gui/Input/Mouse/MouseBindings.cs
@@ -2,7 +2,7 @@
namespace Terminal.Gui;
///
-/// Provides a collection of objects bound to a combination of .
+/// Provides a collection of objects bound to a combination of .
///
///
///
@@ -15,15 +15,13 @@ public class MouseBindings
public MouseBindings () { }
/// Adds a to the collection.
- ///
+ ///
///
- public void Add (MouseFlags mouseFlag, MouseBinding binding)
+ public void Add (MouseEventArgs mouseEvent, MouseBinding binding)
{
- if (TryGet (mouseFlag, out MouseBinding _))
+ if (TryGet (mouseEvent, out MouseBinding _))
{
- throw new InvalidOperationException (@$"A binding for {mouseFlag} exists ({binding}).");
-
- //Bindings [key] = binding;
+ throw new InvalidOperationException (@$"A binding for {mouseEvent} exists ({binding}).");
}
@@ -31,7 +29,7 @@ public class MouseBindings
// 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 key, but the old key will still be in the dictionary.
// IMPORTANT: See the ConfigurationManager.Illustrate_DeepMemberWiseCopy_Breaks_Dictionary test for details.
- Bindings.Add (mouseFlag, binding);
+ Bindings.Add (mouseEvent, binding);
}
///
@@ -45,15 +43,15 @@ public class MouseBindings
/// 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 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
+ /// 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 (MouseFlags mouseFlags, params Command [] commands)
+ public void Add (MouseEventArgs mouseEvents, params Command [] commands)
{
- if (mouseFlags == MouseFlags.None)
+ if (mouseEvents.Flags == MouseFlags.None)
{
throw new ArgumentException (@"Invalid MouseFlag", nameof (commands));
}
@@ -63,24 +61,24 @@ public class MouseBindings
throw new ArgumentException (@"At least one command must be specified", nameof (commands));
}
- if (TryGet (mouseFlags, out MouseBinding binding))
+ if (TryGet (mouseEvents, out MouseBinding binding))
{
- throw new InvalidOperationException (@$"A binding for {mouseFlags} exists ({binding}).");
+ throw new InvalidOperationException (@$"A binding for {mouseEvents} exists ({binding}).");
}
- Add (mouseFlags, new MouseBinding (commands));
+ Add (mouseEvents, new MouseBinding (commands));
}
// TODO: Add a dictionary comparer that ignores Scope
// TODO: This should not be public!
/// The collection of objects.
- public Dictionary Bindings { get; } = new ();
+ public Dictionary Bindings { get; } = new ();
///
- /// Gets the that are bound.
+ /// Gets the that are bound.
///
///
- public IEnumerable GetBoundMouseFlags ()
+ public IEnumerable GetBoundMouseEventArgs ()
{
return Bindings.Keys;
}
@@ -95,38 +93,38 @@ public class MouseBindings
///
public void Clear (params Command [] command)
{
- KeyValuePair [] kvps = Bindings
+ KeyValuePair [] kvps = Bindings
.Where (kvp => kvp.Value.Commands.SequenceEqual (command))
.ToArray ();
- foreach (KeyValuePair kvp in kvps)
+ foreach (KeyValuePair kvp in kvps)
{
Remove (kvp.Key);
}
}
- /// Gets the for the specified combination of .
- ///
+ /// Gets the for the specified combination of .
+ ///
///
- public MouseBinding Get (MouseFlags mouseFlags)
+ public MouseBinding Get (MouseEventArgs mouseEvents)
{
- if (TryGet (mouseFlags, out MouseBinding binding))
+ if (TryGet (mouseEvents, out MouseBinding binding))
{
return binding;
}
- throw new InvalidOperationException ($"{mouseFlags} is not bound.");
+ throw new InvalidOperationException ($"{mouseEvents} is not bound.");
}
- /// Gets the array of s bound to if it exists.
- /// The key to check.
+ /// Gets the array of s bound to if it exists.
+ /// The key to check.
///
- /// The array of s if is bound. An empty array
+ /// The array of s if is bound. An empty array
/// if not.
///
- public Command [] GetCommands (MouseFlags mouseFlags)
+ public Command [] GetCommands (MouseEventArgs mouseEvents)
{
- if (TryGet (mouseFlags, out MouseBinding bindings))
+ if (TryGet (mouseEvents, out MouseBinding bindings))
{
return bindings.Commands;
}
@@ -134,83 +132,83 @@ public class MouseBindings
return [];
}
- /// Gets the first combination of bound to the set of commands specified by .
+ /// Gets the first combination of bound to the set of commands specified by .
/// The set of commands to search.
- /// The first combination of bound to the set of commands specified by . if the set of caommands was not found.
- public MouseFlags? GetMouseFlagsFromCommands (params Command [] commands)
+ /// The first combination of bound to the set of commands specified by . if the set of caommands was not found.
+ public MouseEventArgs? GetMouseEventArgsFromCommands (params Command [] commands)
{
return Bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key;
}
- /// Gets combination of bound to the set of commands specified by .
+ /// Gets combination of bound to the set of commands specified by .
/// The set of commands to search.
- /// The combination of bound to the set of commands specified by . An empty list if the set of caommands was not found.
- public IEnumerable GetAllMouseFlagsFromCommands (params Command [] commands)
+ /// The combination of bound to the set of commands specified by . An empty list if the set of caommands was not found.
+ public IEnumerable GetAllMouseEventArgsFromCommands (params Command [] commands)
{
return Bindings.Where (a => a.Value.Commands.SequenceEqual (commands)).Select (a => a.Key);
}
/// Removes a from the collection.
- ///
- public void Remove (MouseFlags mouseFlags)
+ ///
+ public void Remove (MouseEventArgs mouseEvents)
{
- if (!TryGet (mouseFlags, out MouseBinding _))
+ if (!TryGet (mouseEvents, out MouseBinding _))
{
return;
}
- Bindings.Remove (mouseFlags);
+ Bindings.Remove (mouseEvents);
}
- /// Replaces the commands already bound to a combination of .
+ /// Replaces the commands already bound to a combination of .
///
///
- /// If the combination of is not already bound, it will be added.
+ /// If the combination of is not already bound, it will be added.
///
///
- /// The combination of bound to the command to be replaced.
+ /// The combination of bound to the command to be replaced.
/// The set of commands to replace the old ones with.
- public void ReplaceCommands (MouseFlags mouseFlags, params Command [] commands)
+ public void ReplaceCommands (MouseEventArgs mouseEvents, params Command [] commands)
{
- if (TryGet (mouseFlags, out MouseBinding binding))
+ if (TryGet (mouseEvents, out MouseBinding binding))
{
binding.Commands = commands;
}
else
{
- Add (mouseFlags, commands);
+ Add (mouseEvents, commands);
}
}
- /// Replaces a combination already bound to a set of s.
+ /// Replaces a combination already bound to a set of s.
///
- /// The to be replaced.
- /// The new to be used. If no action will be taken.
- public void ReplaceKey (MouseFlags oldMouseFlags, MouseFlags newMouseFlags)
+ /// The to be replaced.
+ /// The new to be used. If no action will be taken.
+ public void ReplaceKey (MouseEventArgs oldMouseEventArgs, MouseEventArgs newMouseEventArgs)
{
- if (!TryGet (oldMouseFlags, out MouseBinding _))
+ if (!TryGet (oldMouseEventArgs, out MouseBinding _))
{
- throw new InvalidOperationException ($"Key {oldMouseFlags} is not bound.");
+ throw new InvalidOperationException ($"Key {oldMouseEventArgs} is not bound.");
}
- MouseBinding value = Bindings [oldMouseFlags];
- Remove (oldMouseFlags);
- Add (newMouseFlags, value);
+ MouseBinding value = Bindings [oldMouseEventArgs];
+ Remove (oldMouseEventArgs);
+ Add (newMouseEventArgs, value);
}
- /// Gets the commands bound with the specified .
+ /// Gets the commands bound with the specified .
///
- /// The key to check.
+ /// The key 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 (MouseFlags mouseFlags, out MouseBinding binding)
+ public bool TryGet (MouseEventArgs mouseEvents, out MouseBinding binding)
{
binding = new ([]);
- return Bindings.TryGetValue (mouseFlags, out binding);
+ return Bindings.TryGetValue (mouseEvents, out binding);
}
}
diff --git a/Terminal.Gui/View/View.Command.cs b/Terminal.Gui/View/View.Command.cs
index 06431eb73..c3a921bd6 100644
--- a/Terminal.Gui/View/View.Command.cs
+++ b/Terminal.Gui/View/View.Command.cs
@@ -30,7 +30,7 @@ public partial class View // Command APIs
});
// Space or single-click - Raise Selecting
- AddCommand (Command.Select, (ctx) =>
+ AddCommand (Command.Select, ctx =>
{
if (RaiseSelecting (ctx) is true)
{
@@ -54,7 +54,7 @@ public partial class View // Command APIs
///
///
///
- /// The event should raised after the state of the View has changed (after is raised).
+ /// The event should be raised after the state of the View has changed (after is raised).
///
///
/// If the Accepting event is not handled, will be invoked on the SuperView, enabling default Accept behavior.
@@ -65,11 +65,11 @@ public partial class View // Command APIs
///
///
///
- /// if no event was raised; input proessing should continue.
- /// if the event was raised and was not handled (or cancelled); input proessing should continue.
- /// if the event was raised and handled (or cancelled); input proessing should stop.
+ /// if no event was raised; input processing should continue.
+ /// if the event was raised and was not handled (or cancelled); input processing should continue.
+ /// if the event was raised and handled (or cancelled); input processing should stop.
///
- protected bool? RaiseAccepting (ICommandContext ctx)
+ protected bool? RaiseAccepting (ICommandContext? ctx)
{
CommandEventArgs args = new () { Context = ctx };
@@ -135,14 +135,14 @@ public partial class View // Command APIs
/// event. The default handler calls this method.
///
///
- /// The event should raised after the state of the View has been changed and before see .
+ /// The event should be raised after the state of the View has been changed and before see .
///
///
- /// if no event was raised; input proessing should continue.
- /// if the event was raised and was not handled (or cancelled); input proessing should continue.
- /// if the event was raised and handled (or cancelled); input proessing should stop.
+ /// if no event was raised; input processing should continue.
+ /// if the event was raised and was not handled (or cancelled); input processing should continue.
+ /// if the event was raised and handled (or cancelled); input processing should stop.
///
- protected bool? RaiseSelecting (ICommandContext ctx)
+ protected bool? RaiseSelecting (ICommandContext? ctx)
{
CommandEventArgs args = new () { Context = ctx };
@@ -179,9 +179,9 @@ public partial class View // Command APIs
/// event. The default handler calls this method.
///
///
- /// if no event was raised; input proessing should continue.
- /// if the event was raised and was not handled (or cancelled); input proessing should continue.
- /// if the event was raised and handled (or cancelled); input proessing should stop.
+ /// if no event was raised; input processing should continue.
+ /// if the event was raised and was not handled (or cancelled); input processing should continue.
+ /// if the event was raised and handled (or cancelled); input processing should stop.
///
protected bool? RaiseHandlingHotKey ()
{
@@ -219,11 +219,11 @@ public partial class View // Command APIs
///
/// Function signature commands.
///
- /// Provides information about the circumstances of invoking the command (e.g. )
+ /// Provides context about the circumstances of invoking the command.
///
- /// if no command was found; input proessing should continue.
- /// if the command was invoked and was not handled (or cancelled); input proessing should continue.
- /// if the command was invoked the command was handled (or cancelled); input proessing should stop.
+ /// if no event was raised; input processing should continue.
+ /// if the event was raised and was not handled (or cancelled); input processing should continue.
+ /// if the event was raised and handled (or cancelled); input processing should stop.
///
public delegate bool? CommandImplementation (ICommandContext? ctx);
@@ -239,7 +239,7 @@ public partial class View // Command APIs
///
///
///
- /// This version of AddCommand is for commands that require .
+ /// This version of AddCommand is for commands that require .
///
///
/// The command.
@@ -258,7 +258,7 @@ public partial class View // Command APIs
///
///
///
- /// This version of AddCommand is for commands that do not require a .
+ /// This version of AddCommand is for commands that do not require context.
/// If the command requires context, use
///
///
@@ -275,12 +275,11 @@ public partial class View // Command APIs
/// Invokes the specified commands.
///
/// The set of commands to invoke.
- /// The key that caused the command to be invoked, if any. This will be passed as context with the command.
- /// The key binding that was bound to the key and caused the invocation, if any. This will be passed as context with the command.
+ /// The binding that caused the invocation, if any. This will be passed as context with the command.
///
- /// if no command was found; input proessing should continue.
- /// if at least one command was invoked and was not handled (or cancelled); input proessing should continue.
- /// if at least one command was invoked the command was handled (or cancelled); input proessing should stop.
+ /// if no command was found; input processing should continue.
+ /// if the command was invoked and was not handled (or cancelled); input processing should continue.
+ /// if the command was invoked the command was handled (or cancelled); input processing should stop.
///
public bool? InvokeCommands (Command [] commands, TBindingType binding)
{
@@ -315,30 +314,34 @@ public partial class View // Command APIs
/// Invokes the specified command.
///
/// The command to invoke.
- ///
+ /// The binding that caused the invocation, if any. This will be passed as context with the command.
///
- /// if no command was found; input proessing should continue.
- /// if the command was invoked and was not handled (or cancelled); input proessing should continue.
- /// if the command was invoked the command was handled (or cancelled); input proessing should stop.
+ /// if no command was found; input processing should continue.
+ /// if the command was invoked and was not handled (or cancelled); input processing should continue.
+ /// if the command was invoked the command was handled (or cancelled); input processing should stop.
///
public bool? InvokeCommand (Command command, TBindingType binding)
{
if (CommandImplementations.TryGetValue (command, out CommandImplementation? implementation))
{
- return implementation (binding as ICommandContext);
+ return implementation (new CommandContext ()
+ {
+ Command = command,
+ Binding = binding,
+ });
}
return null;
}
///
- /// Invokes the specified command.
+ /// Invokes the specified command without context.
///
/// The command to invoke.
///
- /// if no command was found; input proessing should continue.
- /// if the command was invoked and was not handled (or cancelled); input proessing should continue.
- /// if the command was invoked the command was handled (or cancelled); input proessing should stop.
+ /// if no command was found; input processing should continue.
+ /// if the command was invoked and was not handled (or cancelled); input processing should continue.
+ /// if the command was invoked the command was handled (or cancelled); input processing should stop.
///
public bool? InvokeCommand (Command command)
{
diff --git a/Terminal.Gui/Views/ColorPicker.16.cs b/Terminal.Gui/Views/ColorPicker.16.cs
index ea1a95f68..f699f5686 100644
--- a/Terminal.Gui/Views/ColorPicker.16.cs
+++ b/Terminal.Gui/Views/ColorPicker.16.cs
@@ -205,9 +205,10 @@ public class ColorPicker16 : View
AddCommand (Command.Select, (ctx) =>
{
bool set = false;
- if (ctx.Data is MouseEventArgs me)
+
+ if (ctx is CommandContext { Binding: { } } mouseCommandContext)
{
- Cursor = new (me.Position.X / _boxWidth, me.Position.Y / _boxHeight);
+ Cursor = new (mouseCommandContext.Binding.Position.X / _boxWidth, mouseCommandContext.Binding.Position.Y / _boxHeight);
set = true;
}
return RaiseAccepting (ctx) == true || set;
diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs
index 4fcdc315a..2e3763a9b 100644
--- a/Terminal.Gui/Views/ComboBox.cs
+++ b/Terminal.Gui/Views/ComboBox.cs
@@ -80,7 +80,11 @@ public class ComboBox : View, IDesignable
// Things this view knows how to do
AddCommand (Command.Accept, (ctx) =>
{
- if (ctx.Data == _search)
+ if (ctx is not CommandContext keyCommandContext)
+ {
+ return false;
+ }
+ if (keyCommandContext.Binding.Data == _search)
{
return null;
}
diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs
index 89ad3c660..6cc15b338 100644
--- a/Terminal.Gui/Views/ListView.cs
+++ b/Terminal.Gui/Views/ListView.cs
@@ -143,8 +143,12 @@ public class ListView : View, IDesignable
AddCommand (Command.SelectAll, (ctx) =>
{
- // BUGBUG: This probably isn't right
- return MarkAll ((bool)ctx!.Data);
+ if (ctx is not CommandContext keyCommandContext)
+ {
+ return false;
+ }
+
+ return keyCommandContext.Binding.Data is { } && MarkAll ((bool)keyCommandContext.Binding.Data);
});
// Default keybindings for all ListViews
diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs
index 6e620c91d..9c7072538 100644
--- a/Terminal.Gui/Views/Menu/Menu.cs
+++ b/Terminal.Gui/Views/Menu/Menu.cs
@@ -1,7 +1,5 @@
#nullable enable
-using static System.Formats.Asn1.AsnWriter;
-
namespace Terminal.Gui;
///
@@ -10,76 +8,115 @@ namespace Terminal.Gui;
///
internal sealed class Menu : View
{
- private readonly MenuBarItem? _barItems;
- private readonly MenuBar _host;
+ public Menu ()
+ {
+ if (Application.Top is { })
+ {
+ Application.Top.DrawComplete += Top_DrawComplete;
+ Application.Top.SizeChanging += Current_TerminalResized;
+ }
+
+ Application.MouseEvent += Application_RootMouseEvent;
+
+ // Things this view knows how to do
+ AddCommand (Command.Up, () => MoveUp ());
+ AddCommand (Command.Down, () => MoveDown ());
+
+ AddCommand (
+ Command.Left,
+ () =>
+ {
+ _host!.PreviousMenu (true);
+
+ return true;
+ }
+ );
+
+ AddCommand (
+ Command.Cancel,
+ () =>
+ {
+ CloseAllMenus ();
+
+ return true;
+ }
+ );
+
+ AddCommand (
+ Command.Accept,
+ () =>
+ {
+ RunSelected ();
+
+ return true;
+ }
+ );
+
+ AddCommand (
+ Command.Select,
+ ctx =>
+ {
+ if (ctx is not CommandContext keyCommandContext)
+ {
+ return false;
+ }
+
+ return _host?.SelectItem ((keyCommandContext.Binding.Data as MenuItem)!);
+ });
+
+ AddCommand (
+ Command.Toggle,
+ ctx =>
+ {
+ if (ctx is not CommandContext keyCommandContext)
+ {
+ return false;
+ }
+
+ return ExpandCollapse ((keyCommandContext.Binding.Data as MenuItem)!);
+ });
+
+ AddCommand (
+ Command.HotKey,
+ ctx =>
+ {
+ if (ctx is not CommandContext keyCommandContext)
+ {
+ return false;
+ }
+
+ return _host?.SelectItem ((keyCommandContext.Binding.Data as MenuItem)!);
+ });
+
+ // Default key bindings for this view
+ KeyBindings.Add (Key.CursorUp, Command.Up);
+ KeyBindings.Add (Key.CursorDown, Command.Down);
+ KeyBindings.Add (Key.CursorLeft, Command.Left);
+ KeyBindings.Add (Key.CursorRight, Command.Right);
+ KeyBindings.Add (Key.Esc, Command.Cancel);
+ }
+
internal int _currentChild;
internal View? _previousSubFocused;
-
- internal static Rectangle MakeFrame (int x, int y, MenuItem? []? items, Menu? parent = null)
- {
- if (items is null || items.Length == 0)
- {
- return Rectangle.Empty;
- }
-
- int minX = x;
- int minY = y;
- const int borderOffset = 2; // This 2 is for the space around
- int maxW = (items.Max (z => z?.Width) ?? 0) + borderOffset;
- int maxH = items.Length + borderOffset;
-
- if (parent is { } && x + maxW > Driver.Cols)
- {
- minX = Math.Max (parent.Frame.Right - parent.Frame.Width - maxW, 0);
- }
-
- if (y + maxH > Driver.Rows)
- {
- minY = Math.Max (Driver.Rows - maxH, 0);
- }
-
- return new (minX, minY, maxW, maxH);
- }
-
- internal required MenuBar Host
- {
- get => _host;
- init
- {
- ArgumentNullException.ThrowIfNull (value);
- _host = value;
- }
- }
-
- internal required MenuBarItem? BarItems
- {
- get => _barItems!;
- init
- {
- ArgumentNullException.ThrowIfNull (value);
- _barItems = value;
-
- // Debugging aid so ToString() is helpful
- Text = _barItems.Title;
- }
- }
-
- internal Menu? Parent { get; init; }
+ private readonly MenuBarItem? _barItems;
+ private readonly MenuBar _host;
public override void BeginInit ()
{
base.BeginInit ();
- var frame = MakeFrame (Frame.X, Frame.Y, _barItems!.Children!, Parent);
+ Rectangle frame = MakeFrame (Frame.X, Frame.Y, _barItems!.Children!, Parent);
if (Frame.X != frame.X)
{
X = frame.X;
}
+
if (Frame.Y != frame.Y)
{
Y = frame.Y;
}
+
Width = frame.Width;
Height = frame.Height;
@@ -94,6 +131,7 @@ internal sealed class Menu : View
if (menuItem.ShortcutKey != Key.Empty)
{
KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
+
// Remove an existent ShortcutKey
menuItem._menuBar.KeyBindings.Remove (menuItem.ShortcutKey!);
menuItem._menuBar.KeyBindings.Add (menuItem.ShortcutKey!, keyBinding);
@@ -155,61 +193,281 @@ internal sealed class Menu : View
AddKeyBindingsHotKey (_barItems);
}
- public Menu ()
+ public override Point? PositionCursor ()
{
- if (Application.Top is { })
+ if (_host.IsMenuOpen)
{
- Application.Top.DrawComplete += Top_DrawComplete;
- Application.Top.SizeChanging += Current_TerminalResized;
+ if (_barItems!.IsTopLevel)
+ {
+ return _host.PositionCursor ();
+ }
+
+ Move (2, 1 + _currentChild);
+
+ return null; // Don't show the cursor
}
- Application.MouseEvent += Application_RootMouseEvent;
-
- // Things this view knows how to do
- AddCommand (Command.Up, () => MoveUp ());
- AddCommand (Command.Down, () => MoveDown ());
-
- AddCommand (
- Command.Left,
- () =>
- {
- _host!.PreviousMenu (true);
-
- return true;
- }
- );
-
- AddCommand (
- Command.Cancel,
- () =>
- {
- CloseAllMenus ();
-
- return true;
- }
- );
-
- AddCommand (
- Command.Accept,
- () =>
- {
- RunSelected ();
-
- return true;
- }
- );
- AddCommand (Command.Select, ctx => _host?.SelectItem ((ctx.Data as MenuItem)!));
- AddCommand (Command.Toggle, ctx => ExpandCollapse ((ctx.Data as MenuItem)!));
- AddCommand (Command.HotKey, ctx => _host?.SelectItem ((ctx.Data as MenuItem)!));
-
- // Default key bindings for this view
- KeyBindings.Add (Key.CursorUp, Command.Up);
- KeyBindings.Add (Key.CursorDown, Command.Down);
- KeyBindings.Add (Key.CursorLeft, Command.Left);
- KeyBindings.Add (Key.CursorRight, Command.Right);
- KeyBindings.Add (Key.Esc, Command.Cancel);
+ return _host.PositionCursor ();
}
+ public void Run (Action? action)
+ {
+ if (action is null)
+ {
+ return;
+ }
+
+ Application.UngrabMouse ();
+ _host.CloseAllMenus ();
+ Application.LayoutAndDraw (true);
+
+ _host.Run (action);
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ RemoveKeyBindingsHotKey (_barItems);
+
+ if (Application.Top is { })
+ {
+ Application.Top.DrawComplete -= Top_DrawComplete;
+ Application.Top.SizeChanging -= Current_TerminalResized;
+ }
+
+ Application.MouseEvent -= Application_RootMouseEvent;
+ base.Dispose (disposing);
+ }
+
+ protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view)
+ {
+ if (!newHasFocus)
+ {
+ _host.LostFocus (previousFocusedView!);
+ }
+ }
+
+ ///
+ protected override bool OnKeyDownNotHandled (Key keyEvent)
+ {
+ // We didn't handle the key, pass it on to host
+ return _host.InvokeCommandsBoundToKey (keyEvent) == true;
+ }
+
+ protected override bool OnMouseEvent (MouseEventArgs me)
+ {
+ if (!_host._handled && !_host.HandleGrabView (me, this))
+ {
+ return false;
+ }
+
+ _host._handled = false;
+ bool disabled;
+
+ if (me.Flags == MouseFlags.Button1Clicked)
+ {
+ disabled = false;
+
+ if (me.Position.Y < 0)
+ {
+ return me.Handled = true;
+ }
+
+ if (me.Position.Y >= _barItems!.Children!.Length)
+ {
+ return me.Handled = true;
+ }
+
+ MenuItem item = _barItems.Children [me.Position.Y]!;
+
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ if (item is null || !item.IsEnabled ())
+ {
+ disabled = true;
+ }
+
+ if (disabled)
+ {
+ return me.Handled = true;
+ }
+
+ _currentChild = me.Position.Y;
+ RunSelected ();
+
+ return me.Handled = true;
+ }
+
+ if (me.Flags != MouseFlags.Button1Pressed
+ && me.Flags != MouseFlags.Button1DoubleClicked
+ && me.Flags != MouseFlags.Button1TripleClicked
+ && me.Flags != MouseFlags.ReportMousePosition
+ && !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
+ {
+ return false;
+ }
+
+ {
+ disabled = false;
+
+ if (me.Position.Y < 0 || me.Position.Y >= _barItems!.Children!.Length)
+ {
+ return me.Handled = true;
+ }
+
+ MenuItem item = _barItems.Children [me.Position.Y]!;
+
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ if (item is null)
+ {
+ return me.Handled = true;
+ }
+
+ if (item.IsEnabled () != true)
+ {
+ disabled = true;
+ }
+
+ if (!disabled)
+ {
+ _currentChild = me.Position.Y;
+ }
+
+ if (_host.UseSubMenusSingleFrame || !CheckSubMenu ())
+ {
+ SetNeedsDraw ();
+ SetParentSetNeedsDisplay ();
+
+ return me.Handled = true;
+ }
+
+ _host.OnMenuOpened ();
+
+ return me.Handled = true;
+ }
+ }
+
+ ///
+ protected override void OnVisibleChanged ()
+ {
+ base.OnVisibleChanged ();
+
+ if (Visible)
+ {
+ Application.MouseEvent += Application_RootMouseEvent;
+ }
+ else
+ {
+ Application.MouseEvent -= Application_RootMouseEvent;
+ }
+ }
+
+ internal required MenuBarItem? BarItems
+ {
+ get => _barItems!;
+ init
+ {
+ ArgumentNullException.ThrowIfNull (value);
+ _barItems = value;
+
+ // Debugging aid so ToString() is helpful
+ Text = _barItems.Title;
+ }
+ }
+
+ internal bool CheckSubMenu ()
+ {
+ if (_currentChild == -1 || _barItems?.Children? [_currentChild] is null)
+ {
+ return true;
+ }
+
+ MenuBarItem? subMenu = _barItems.SubMenu (_barItems.Children [_currentChild]!);
+
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ if (subMenu is { })
+ {
+ int pos = -1;
+
+ if (_host._openSubMenu is { })
+ {
+ pos = _host._openSubMenu.FindIndex (o => o._barItems == subMenu);
+ }
+
+ if (pos == -1
+ && this != _host.OpenCurrentMenu
+ && subMenu.Children != _host.OpenCurrentMenu!._barItems!.Children
+ && !_host.CloseMenu (false, true))
+ {
+ return false;
+ }
+
+ _host.Activate (_host._selected, pos, subMenu);
+ }
+ else if (_host._openSubMenu?.Count == 0 || _host._openSubMenu?.Last ()._barItems!.IsSubMenuOf (_barItems.Children [_currentChild]!) == false)
+ {
+ return _host.CloseMenu (false, true);
+ }
+ else
+ {
+ SetNeedsDraw ();
+ SetParentSetNeedsDisplay ();
+ }
+
+ return true;
+ }
+
+ internal Attribute DetermineColorSchemeFor (MenuItem? item, int index)
+ {
+ if (item is null)
+ {
+ return GetNormalColor ();
+ }
+
+ if (index == _currentChild)
+ {
+ return GetFocusColor ();
+ }
+
+ return !item.IsEnabled () ? ColorScheme!.Disabled : GetNormalColor ();
+ }
+
+ internal required MenuBar Host
+ {
+ get => _host;
+ init
+ {
+ ArgumentNullException.ThrowIfNull (value);
+ _host = value;
+ }
+ }
+
+ internal static Rectangle MakeFrame (int x, int y, MenuItem? []? items, Menu? parent = null)
+ {
+ if (items is null || items.Length == 0)
+ {
+ return Rectangle.Empty;
+ }
+
+ int minX = x;
+ int minY = y;
+ const int borderOffset = 2; // This 2 is for the space around
+ int maxW = (items.Max (z => z?.Width) ?? 0) + borderOffset;
+ int maxH = items.Length + borderOffset;
+
+ if (parent is { } && x + maxW > Driver.Cols)
+ {
+ minX = Math.Max (parent.Frame.Right - parent.Frame.Width - maxW, 0);
+ }
+
+ if (y + maxH > Driver.Rows)
+ {
+ minY = Math.Max (Driver.Rows - maxH, 0);
+ }
+
+ return new (minX, minY, maxW, maxH);
+ }
+
+ internal Menu? Parent { get; init; }
+
private void AddKeyBindingsHotKey (MenuBarItem? menuBarItem)
{
if (menuBarItem is null || menuBarItem.Children is null)
@@ -233,22 +491,47 @@ internal sealed class Menu : View
}
}
- private void RemoveKeyBindingsHotKey (MenuBarItem? menuBarItem)
+ private void Application_RootMouseEvent (object? sender, MouseEventArgs a)
{
- if (menuBarItem is null || menuBarItem.Children is null)
+ if (a.View is { } and (MenuBar or not Menu))
{
return;
}
- IEnumerable