#nullable enable
namespace Terminal.Gui;
///
/// An internal class used to represent a menu pop-up menu. Created and managed by and
/// .
///
internal sealed class Menu : View
{
public Menu ()
{
if (Application.Top is { })
{
Application.Top.DrawComplete += Top_DrawComplete;
Application.Top.SizeChanging += Current_TerminalResized;
}
Application.MouseEvent += Application_RootMouseEvent;
Application.UnGrabbedMouse += Application_UnGrabbedMouse;
// 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;
private readonly MenuBarItem? _barItems;
private readonly MenuBar _host;
public override void BeginInit ()
{
base.BeginInit ();
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;
if (_barItems.Children is { })
{
foreach (MenuItem? menuItem in _barItems.Children)
{
if (menuItem is { })
{
menuItem._menuBar = Host;
if (menuItem.ShortcutKey != Key.Empty)
{
KeyBinding keyBinding = new ([Command.Select], this, data: menuItem);
// Remove an existent ShortcutKey
menuItem._menuBar.HotKeyBindings.Remove (menuItem.ShortcutKey!);
menuItem._menuBar.HotKeyBindings.Add (menuItem.ShortcutKey!, keyBinding);
}
}
}
}
if (_barItems is { IsTopLevel: true })
{
// This is a standalone MenuItem on a MenuBar
ColorScheme = _host.ColorScheme;
CanFocus = true;
}
else
{
_currentChild = -1;
for (var i = 0; i < _barItems.Children?.Length; i++)
{
if (_barItems.Children [i]?.IsEnabled () == true)
{
_currentChild = i;
break;
}
}
ColorScheme = _host.ColorScheme;
CanFocus = true;
WantMousePositionReports = _host.WantMousePositionReports;
}
BorderStyle = _host.MenusBorderStyle;
AddCommand (
Command.Right,
() =>
{
_host.NextMenu (
!_barItems.IsTopLevel
|| (_barItems.Children is { Length: > 0 }
&& _currentChild > -1
&& _currentChild < _barItems.Children.Length
&& _barItems.Children [_currentChild]!.IsFromSubMenu),
_barItems.Children is { Length: > 0 }
&& _currentChild > -1
&& _host.UseSubMenusSingleFrame
&& _barItems.SubMenu (
_barItems.Children [_currentChild]!
)
!= null!
);
return true;
}
);
AddKeyBindingsHotKey (_barItems);
}
public override Point? PositionCursor ()
{
if (_host.IsMenuOpen)
{
if (_barItems!.IsTopLevel)
{
return _host.PositionCursor ();
}
Move (2, 1 + _currentChild);
return null; // Don't show the cursor
}
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;
Application.UnGrabbedMouse -= Application_UnGrabbedMouse;
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
bool? handled = null;
return _host.InvokeCommandsBoundToHotKey (keyEvent, ref handled) == 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)
{
return;
}
IEnumerable