#nullable enable
namespace Terminal.Gui;
///
/// A -derived object to be used as a vertically-oriented menu. Each subview is a .
///
public class Menuv2 : Bar
{
///
public Menuv2 () : this ([]) { }
///
public Menuv2 (IEnumerable? menuItems) : this (menuItems?.Cast ()) { }
///
public Menuv2 (IEnumerable? shortcuts) : base (shortcuts)
{
// Do this to support debugging traces where Title gets set
base.HotKeySpecifier = (Rune)'\xffff';
Orientation = Orientation.Vertical;
Width = Dim.Auto ();
Height = Dim.Auto (DimAutoStyle.Content, 1);
base.ColorScheme = Colors.ColorSchemes ["Menu"];
if (Border is { })
{
Border.Settings &= ~BorderSettings.Title;
}
BorderStyle = DefaultBorderStyle;
Applied += OnConfigurationManagerApplied;
}
private void OnConfigurationManagerApplied (object? sender, ConfigurationManagerEventArgs e)
{
if (SuperView is { })
{
BorderStyle = DefaultBorderStyle;
}
}
///
/// Gets or sets the default Border Style for Menus. The default is .
///
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Rounded;
///
/// Gets or sets the menu item that opened this menu as a sub-menu.
///
public MenuItemv2? SuperMenuItem { get; set; }
///
protected override void OnVisibleChanged ()
{
if (Visible)
{
SelectedMenuItem = SubViews.Where (mi => mi is MenuItemv2).ElementAtOrDefault (0) as MenuItemv2;
}
}
///
protected override void OnSubViewAdded (View view)
{
base.OnSubViewAdded (view);
switch (view)
{
case MenuItemv2 menuItem:
{
menuItem.CanFocus = true;
AddCommand (menuItem.Command, (ctx) =>
{
RaiseAccepted (ctx);
return true;
});
menuItem.Accepted += MenuItemOnAccepted;
break;
void MenuItemOnAccepted (object? sender, CommandEventArgs e)
{
Logging.Debug ($"MenuItemOnAccepted: Calling RaiseAccepted {e.Context?.Source?.Title}");
RaiseAccepted (e.Context);
}
}
case Line line:
// Grow line so we get auto-join line
line.X = Pos.Func (() => -Border!.Thickness.Left);
line.Width = Dim.Fill ()! + Dim.Func (() => Border!.Thickness.Right);
break;
}
}
///
protected override bool OnAccepting (CommandEventArgs args)
{
// When the user accepts a menuItem, Menu.RaiseAccepting is called, and we intercept that here.
Logging.Debug ($"{Title} - {args.Context?.Source?.Title} Command: {args.Context?.Command}");
// TODO: Consider having PopoverMenu subscribe to Accepting instead of us overriding OnAccepting here
// TODO: Doing so would be better encapsulation and might allow us to remove the SuperMenuItem property.
if (SuperView is { })
{
Logging.Debug ($"{Title} - SuperView is null");
//return false;
}
Logging.Debug ($"{Title} - {args.Context}");
if (args.Context is CommandContext { Binding.Key: { } } keyCommandContext && keyCommandContext.Binding.Key == Application.QuitKey)
{
// Special case QuitKey if we are Visible - This supports a MenuItem with Key = Application.QuitKey/Command = Command.Quit
// And causes just the menu to quit.
Logging.Debug ($"{Title} - Returning true - Application.QuitKey/Command = Command.Quit");
return true;
}
// Because we may not have a SuperView (if we are in a PopoverMenu), we need to propagate
// Command.Accept to the SuperMenuItem if it exists.
if (SuperView is null && SuperMenuItem is { })
{
Logging.Debug ($"{Title} - Invoking Accept on SuperMenuItem: {SuperMenuItem?.Title}...");
return SuperMenuItem?.InvokeCommand (Command.Accept, args.Context) is true;
}
return false;
}
// TODO: Consider moving Accepted to Bar?
///
/// Raises the / event indicating an item in this menu (or submenu)
/// was accepted. This is used to determine when to hide the menu.
///
///
///
protected void RaiseAccepted (ICommandContext? ctx)
{
//Logging.Trace ($"RaiseAccepted: {ctx}");
CommandEventArgs args = new () { Context = ctx };
OnAccepted (args);
Accepted?.Invoke (this, args);
}
///
/// Called when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the menu.
///
///
///
///
protected virtual void OnAccepted (CommandEventArgs args) { }
///
/// Raised when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the menu.
///
///
///
/// See for more information.
///
///
public event EventHandler? Accepted;
///
protected override void OnFocusedChanged (View? previousFocused, View? focused)
{
base.OnFocusedChanged (previousFocused, focused);
SelectedMenuItem = focused as MenuItemv2;
RaiseSelectedMenuItemChanged (SelectedMenuItem);
}
///
/// Gets or set the currently selected menu item. This is a helper that
/// tracks .
///
public MenuItemv2? SelectedMenuItem
{
get => Focused as MenuItemv2;
set
{
if (value == Focused)
{
return;
}
// Note we DO NOT set focus here; This property tracks Focused
}
}
internal void RaiseSelectedMenuItemChanged (MenuItemv2? selected)
{
Logging.Debug ($"{Title} ({selected?.Title})");
OnSelectedMenuItemChanged (selected);
SelectedMenuItemChanged?.Invoke (this, selected);
}
///
/// Called when the selected menu item has changed.
///
///
protected virtual void OnSelectedMenuItemChanged (MenuItemv2? selected)
{
}
///
/// Raised when the selected menu item has changed.
///
public event EventHandler? SelectedMenuItemChanged;
///
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
if (disposing)
{
Applied -= OnConfigurationManagerApplied;
}
}
}