Menu -> One class per file

This commit is contained in:
Tig
2024-06-09 07:38:55 -06:00
parent 0e4d9c5b6c
commit 364723eaf6
5 changed files with 468 additions and 465 deletions

View File

@@ -1,292 +1,5 @@
namespace Terminal.Gui;
/// <summary>Specifies how a <see cref="MenuItem"/> shows selection state.</summary>
[Flags]
public enum MenuItemCheckStyle
{
/// <summary>The menu item will be shown normally, with no check indicator. The default.</summary>
NoCheck = 0b_0000_0000,
/// <summary>The menu item will indicate checked/un-checked state (see <see cref="Checked"/>).</summary>
Checked = 0b_0000_0001,
/// <summary>The menu item is part of a menu radio group (see <see cref="Checked"/>) and will indicate selected state.</summary>
Radio = 0b_0000_0010
}
/// <summary>
/// A <see cref="MenuItem"/> has title, an associated help text, and an action to execute on activation. MenuItems
/// can also have a checked indicator (see <see cref="Checked"/>).
/// </summary>
public class MenuItem
{
private readonly ShortcutHelper _shortcutHelper;
private bool _allowNullChecked;
private MenuItemCheckStyle _checkType;
private string _title;
// TODO: Update to use Key instead of KeyCode
/// <summary>Initializes a new instance of <see cref="MenuItem"/></summary>
public MenuItem (KeyCode shortcut = KeyCode.Null) : this ("", "", null, null, null, shortcut) { }
// TODO: Update to use Key instead of KeyCode
/// <summary>Initializes a new instance of <see cref="MenuItem"/>.</summary>
/// <param name="title">Title for the menu item.</param>
/// <param name="help">Help text to display.</param>
/// <param name="action">Action to invoke when the menu item is activated.</param>
/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
/// <param name="parent">The <see cref="Parent"/> of this menu item.</param>
/// <param name="shortcut">The <see cref="Shortcut"/> keystroke combination.</param>
public MenuItem (
string title,
string help,
Action action,
Func<bool> canExecute = null,
MenuItem parent = null,
KeyCode shortcut = KeyCode.Null
)
{
Title = title ?? "";
Help = help ?? "";
Action = action;
CanExecute = canExecute;
Parent = parent;
_shortcutHelper = new ();
if (shortcut != KeyCode.Null)
{
Shortcut = shortcut;
}
}
/// <summary>Gets or sets the action to be invoked when the menu item is triggered.</summary>
/// <value>Method to invoke.</value>
public Action Action { get; set; }
/// <summary>
/// Used only if <see cref="CheckType"/> is of <see cref="MenuItemCheckStyle.Checked"/> type. If
/// <see langword="true"/> allows <see cref="Checked"/> to be null, true or false. If <see langword="false"/> only
/// allows <see cref="Checked"/> to be true or false.
/// </summary>
public bool AllowNullChecked
{
get => _allowNullChecked;
set
{
_allowNullChecked = value;
Checked ??= false;
}
}
/// <summary>
/// Gets or sets the action to be invoked to determine if the menu can be triggered. If <see cref="CanExecute"/>
/// returns <see langword="true"/> the menu item will be enabled. Otherwise, it will be disabled.
/// </summary>
/// <value>Function to determine if the action is can be executed or not.</value>
public Func<bool> CanExecute { get; set; }
/// <summary>
/// Sets or gets whether the <see cref="MenuItem"/> shows a check indicator or not. See
/// <see cref="MenuItemCheckStyle"/>.
/// </summary>
public bool? Checked { set; get; }
/// <summary>
/// Sets or gets the <see cref="MenuItemCheckStyle"/> of a menu item where <see cref="Checked"/> is set to
/// <see langword="true"/>.
/// </summary>
public MenuItemCheckStyle CheckType
{
get => _checkType;
set
{
_checkType = value;
if (_checkType == MenuItemCheckStyle.Checked && !_allowNullChecked && Checked is null)
{
Checked = false;
}
}
}
/// <summary>Gets or sets arbitrary data for the menu item.</summary>
/// <remarks>This property is not used internally.</remarks>
public object Data { get; set; }
/// <summary>Gets or sets the help text for the menu item. The help text is drawn to the right of the <see cref="Title"/>.</summary>
/// <value>The help text.</value>
public string Help { get; set; }
/// <summary>Gets the parent for this <see cref="MenuItem"/>.</summary>
/// <value>The parent.</value>
public MenuItem Parent { get; set; }
/// <summary>Gets or sets the title of the menu item .</summary>
/// <value>The title.</value>
public string Title
{
get => _title;
set
{
if (_title == value)
{
return;
}
_title = value;
GetHotKey ();
}
}
/// <summary>Gets if this <see cref="MenuItem"/> is from a sub-menu.</summary>
internal bool IsFromSubMenu => Parent != null;
internal int TitleLength => GetMenuBarItemLength (Title);
//
// ┌─────────────────────────────┐
// │ Quit Quit UI Catalog Ctrl+Q │
// └─────────────────────────────┘
// ┌─────────────────┐
// │ ◌ TopLevel Alt+T │
// └─────────────────┘
// TODO: Replace the `2` literals with named constants
internal int Width => 1
+ // space before Title
TitleLength
+ 2
+ // space after Title - BUGBUG: This should be 1
(Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio)
? 2
: 0)
+ // check glyph + space
(Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0)
+ // Two spaces before Help
(ShortcutTag.GetColumns () > 0
? 2 + ShortcutTag.GetColumns ()
: 0); // Pad two spaces before shortcut tag (which are also aligned right)
/// <summary>Merely a debugging aid to see the interaction with main.</summary>
public bool GetMenuBarItem () { return IsFromSubMenu; }
/// <summary>Merely a debugging aid to see the interaction with main.</summary>
public MenuItem GetMenuItem () { return this; }
/// <summary>
/// Returns <see langword="true"/> if the menu item is enabled. This method is a wrapper around
/// <see cref="CanExecute"/>.
/// </summary>
public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
/// <summary>
/// Toggle the <see cref="Checked"/> between three states if <see cref="AllowNullChecked"/> is
/// <see langword="true"/> or between two states if <see cref="AllowNullChecked"/> is <see langword="false"/>.
/// </summary>
public void ToggleChecked ()
{
if (_checkType != MenuItemCheckStyle.Checked)
{
throw new InvalidOperationException ("This isn't a Checked MenuItemCheckStyle!");
}
bool? previousChecked = Checked;
if (AllowNullChecked)
{
Checked = previousChecked switch
{
null => true,
true => false,
false => null
};
}
else
{
Checked = !Checked;
}
}
private static int GetMenuBarItemLength (string title)
{
return title.EnumerateRunes ()
.Where (ch => ch != MenuBar.HotKeySpecifier)
.Sum (ch => Math.Max (ch.GetColumns (), 1));
}
#region Keyboard Handling
// TODO: Update to use Key instead of Rune
/// <summary>
/// The HotKey is used to activate a <see cref="MenuItem"/> with the keyboard. HotKeys are defined by prefixing the
/// <see cref="Title"/> of a MenuItem with an underscore ('_').
/// <para>
/// Pressing Alt-Hotkey for a <see cref="MenuBarItem"/> (menu items on the menu bar) works even if the menu is
/// not active). Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
/// </para>
/// <para>
/// For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the
/// File menu. Pressing the N key will then activate the New MenuItem.
/// </para>
/// <para>See also <see cref="Shortcut"/> which enable global key-bindings to menu items.</para>
/// </summary>
public Rune HotKey { get; set; }
private void GetHotKey ()
{
var nextIsHot = false;
foreach (char x in _title)
{
if (x == MenuBar.HotKeySpecifier.Value)
{
nextIsHot = true;
}
else
{
if (nextIsHot)
{
HotKey = (Rune)char.ToUpper (x);
break;
}
nextIsHot = false;
HotKey = default (Rune);
}
}
}
// TODO: Update to use Key instead of KeyCode
/// <summary>
/// Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the
/// <see cref="View"/> that is the parent of the <see cref="MenuBar"/> or <see cref="ContextMenu"/> this
/// <see cref="MenuItem"/>.
/// <para>
/// The <see cref="KeyCode"/> will be drawn on the MenuItem to the right of the <see cref="Title"/> and
/// <see cref="Help"/> text. See <see cref="ShortcutTag"/>.
/// </para>
/// </summary>
public KeyCode Shortcut
{
get => _shortcutHelper.Shortcut;
set
{
if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == KeyCode.Null))
{
_shortcutHelper.Shortcut = value;
}
}
}
/// <summary>Gets the text describing the keystroke combination defined by <see cref="Shortcut"/>.</summary>
public string ShortcutTag => _shortcutHelper.Shortcut == KeyCode.Null
? string.Empty
: Key.ToString (_shortcutHelper.Shortcut, MenuBar.ShortcutDelimiter);
#endregion Keyboard Handling
}
/// <summary>
/// An internal class used to represent a menu pop-up menu. Created and managed by <see cref="MenuBar"/> and
/// <see cref="ContextMenu"/>.

View File

@@ -1,183 +1,5 @@
namespace Terminal.Gui;
/// <summary>
/// <see cref="MenuBarItem"/> is a menu item on <see cref="MenuBar"/>. MenuBarItems do not support
/// <see cref="MenuItem.Shortcut"/>.
/// </summary>
public class MenuBarItem : MenuItem
{
/// <summary>Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.</summary>
/// <param name="title">Title for the menu item.</param>
/// <param name="help">Help text to display. Will be displayed next to the Title surrounded by parentheses.</param>
/// <param name="action">Action to invoke when the menu item is activated.</param>
/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
public MenuBarItem (
string title,
string help,
Action action,
Func<bool> canExecute = null,
MenuItem parent = null
) : base (title, help, action, canExecute, parent)
{
SetInitialProperties (title, null, null, true);
}
/// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
/// <param name="title">Title for the menu item.</param>
/// <param name="children">The items in the current menu.</param>
/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
public MenuBarItem (string title, MenuItem [] children, MenuItem parent = null) { SetInitialProperties (title, children, parent); }
/// <summary>Initializes a new <see cref="MenuBarItem"/> with separate list of items.</summary>
/// <param name="title">Title for the menu item.</param>
/// <param name="children">The list of items in the current menu.</param>
/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
public MenuBarItem (string title, List<MenuItem []> children, MenuItem parent = null) { SetInitialProperties (title, children, parent); }
/// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
/// <param name="children">The items in the current menu.</param>
public MenuBarItem (MenuItem [] children) : this ("", children) { }
/// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
public MenuBarItem () : this (new MenuItem [] { }) { }
/// <summary>
/// Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this
/// <see cref="MenuBarItem"/>
/// </summary>
/// <value>The children.</value>
public MenuItem [] Children { get; set; }
internal bool IsTopLevel => Parent is null && (Children is null || Children.Length == 0) && Action != null;
/// <summary>Get the index of a child <see cref="MenuItem"/>.</summary>
/// <param name="children"></param>
/// <returns>Returns a greater than -1 if the <see cref="MenuItem"/> is a child.</returns>
public int GetChildrenIndex (MenuItem children)
{
var i = 0;
if (Children is { })
{
foreach (MenuItem child in Children)
{
if (child == children)
{
return i;
}
i++;
}
}
return -1;
}
/// <summary>Check if a <see cref="MenuItem"/> is a submenu of this MenuBar.</summary>
/// <param name="menuItem"></param>
/// <returns>Returns <c>true</c> if it is a submenu. <c>false</c> otherwise.</returns>
public bool IsSubMenuOf (MenuItem menuItem)
{
foreach (MenuItem child in Children)
{
if (child == menuItem && child.Parent == menuItem.Parent)
{
return true;
}
}
return false;
}
/// <summary>Check if a <see cref="MenuItem"/> is a <see cref="MenuBarItem"/>.</summary>
/// <param name="menuItem"></param>
/// <returns>Returns a <see cref="MenuBarItem"/> or null otherwise.</returns>
public MenuBarItem SubMenu (MenuItem menuItem) { return menuItem as MenuBarItem; }
internal void AddShortcutKeyBindings (MenuBar menuBar)
{
if (Children is null)
{
return;
}
foreach (MenuItem menuItem in Children.Where (m => m is { }))
{
// For MenuBar only add shortcuts for submenus
if (menuItem.Shortcut != KeyCode.Null)
{
KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
menuBar.KeyBindings.Add (menuItem.Shortcut, keyBinding);
}
SubMenu (menuItem)?.AddShortcutKeyBindings (menuBar);
}
}
private void SetInitialProperties (string title, object children, MenuItem parent = null, bool isTopLevel = false)
{
if (!isTopLevel && children is null)
{
throw new ArgumentNullException (
nameof (children),
@"The parameter cannot be null. Use an empty array instead."
);
}
SetTitle (title ?? "");
if (parent is { })
{
Parent = parent;
}
if (children is List<MenuItem []> childrenList)
{
MenuItem [] newChildren = [];
foreach (MenuItem [] grandChild in childrenList)
{
foreach (MenuItem child in grandChild)
{
SetParent (grandChild);
Array.Resize (ref newChildren, newChildren.Length + 1);
newChildren [^1] = child;
}
}
Children = newChildren;
}
else if (children is MenuItem [] items)
{
SetParent (items);
Children = items;
}
else
{
Children = null;
}
}
private void SetParent (MenuItem [] children)
{
foreach (MenuItem child in children)
{
if (child is { Parent: null })
{
child.Parent = this;
}
}
}
private void SetTitle (string title)
{
title ??= string.Empty;
Title = title;
}
}
/// <summary>
/// <para>Provides a menu bar that spans the top of a <see cref="Toplevel"/> View with drop-down and cascading menus.</para>
/// <para>

View File

@@ -0,0 +1,179 @@
namespace Terminal.Gui;
/// <summary>
/// <see cref="MenuBarItem"/> is a menu item on <see cref="MenuBar"/>. MenuBarItems do not support
/// <see cref="MenuItem.Shortcut"/>.
/// </summary>
public class MenuBarItem : MenuItem
{
/// <summary>Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.</summary>
/// <param name="title">Title for the menu item.</param>
/// <param name="help">Help text to display. Will be displayed next to the Title surrounded by parentheses.</param>
/// <param name="action">Action to invoke when the menu item is activated.</param>
/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
public MenuBarItem (
string title,
string help,
Action action,
Func<bool> canExecute = null,
MenuItem parent = null
) : base (title, help, action, canExecute, parent)
{
SetInitialProperties (title, null, null, true);
}
/// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
/// <param name="title">Title for the menu item.</param>
/// <param name="children">The items in the current menu.</param>
/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
public MenuBarItem (string title, MenuItem [] children, MenuItem parent = null) { SetInitialProperties (title, children, parent); }
/// <summary>Initializes a new <see cref="MenuBarItem"/> with separate list of items.</summary>
/// <param name="title">Title for the menu item.</param>
/// <param name="children">The list of items in the current menu.</param>
/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
public MenuBarItem (string title, List<MenuItem []> children, MenuItem parent = null) { SetInitialProperties (title, children, parent); }
/// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
/// <param name="children">The items in the current menu.</param>
public MenuBarItem (MenuItem [] children) : this ("", children) { }
/// <summary>Initializes a new <see cref="MenuBarItem"/>.</summary>
public MenuBarItem () : this (new MenuItem [] { }) { }
/// <summary>
/// Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this
/// <see cref="MenuBarItem"/>
/// </summary>
/// <value>The children.</value>
public MenuItem [] Children { get; set; }
internal bool IsTopLevel => Parent is null && (Children is null || Children.Length == 0) && Action != null;
/// <summary>Get the index of a child <see cref="MenuItem"/>.</summary>
/// <param name="children"></param>
/// <returns>Returns a greater than -1 if the <see cref="MenuItem"/> is a child.</returns>
public int GetChildrenIndex (MenuItem children)
{
var i = 0;
if (Children is { })
{
foreach (MenuItem child in Children)
{
if (child == children)
{
return i;
}
i++;
}
}
return -1;
}
/// <summary>Check if a <see cref="MenuItem"/> is a submenu of this MenuBar.</summary>
/// <param name="menuItem"></param>
/// <returns>Returns <c>true</c> if it is a submenu. <c>false</c> otherwise.</returns>
public bool IsSubMenuOf (MenuItem menuItem)
{
foreach (MenuItem child in Children)
{
if (child == menuItem && child.Parent == menuItem.Parent)
{
return true;
}
}
return false;
}
/// <summary>Check if a <see cref="MenuItem"/> is a <see cref="MenuBarItem"/>.</summary>
/// <param name="menuItem"></param>
/// <returns>Returns a <see cref="MenuBarItem"/> or null otherwise.</returns>
public MenuBarItem SubMenu (MenuItem menuItem) { return menuItem as MenuBarItem; }
internal void AddShortcutKeyBindings (MenuBar menuBar)
{
if (Children is null)
{
return;
}
foreach (MenuItem menuItem in Children.Where (m => m is { }))
{
// For MenuBar only add shortcuts for submenus
if (menuItem.Shortcut != KeyCode.Null)
{
KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
menuBar.KeyBindings.Add (menuItem.Shortcut, keyBinding);
}
SubMenu (menuItem)?.AddShortcutKeyBindings (menuBar);
}
}
private void SetInitialProperties (string title, object children, MenuItem parent = null, bool isTopLevel = false)
{
if (!isTopLevel && children is null)
{
throw new ArgumentNullException (
nameof (children),
@"The parameter cannot be null. Use an empty array instead."
);
}
SetTitle (title ?? "");
if (parent is { })
{
Parent = parent;
}
if (children is List<MenuItem []> childrenList)
{
MenuItem [] newChildren = [];
foreach (MenuItem [] grandChild in childrenList)
{
foreach (MenuItem child in grandChild)
{
SetParent (grandChild);
Array.Resize (ref newChildren, newChildren.Length + 1);
newChildren [^1] = child;
}
}
Children = newChildren;
}
else if (children is MenuItem [] items)
{
SetParent (items);
Children = items;
}
else
{
Children = null;
}
}
private void SetParent (MenuItem [] children)
{
foreach (MenuItem child in children)
{
if (child is { Parent: null })
{
child.Parent = this;
}
}
}
private void SetTitle (string title)
{
title ??= string.Empty;
Title = title;
}
}

View File

@@ -0,0 +1,274 @@
namespace Terminal.Gui;
/// <summary>
/// A <see cref="MenuItem"/> has title, an associated help text, and an action to execute on activation. MenuItems
/// can also have a checked indicator (see <see cref="Checked"/>).
/// </summary>
public class MenuItem
{
private readonly ShortcutHelper _shortcutHelper;
private bool _allowNullChecked;
private MenuItemCheckStyle _checkType;
private string _title;
// TODO: Update to use Key instead of KeyCode
/// <summary>Initializes a new instance of <see cref="MenuItem"/></summary>
public MenuItem (KeyCode shortcut = KeyCode.Null) : this ("", "", null, null, null, shortcut) { }
// TODO: Update to use Key instead of KeyCode
/// <summary>Initializes a new instance of <see cref="MenuItem"/>.</summary>
/// <param name="title">Title for the menu item.</param>
/// <param name="help">Help text to display.</param>
/// <param name="action">Action to invoke when the menu item is activated.</param>
/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
/// <param name="parent">The <see cref="Parent"/> of this menu item.</param>
/// <param name="shortcut">The <see cref="Shortcut"/> keystroke combination.</param>
public MenuItem (
string title,
string help,
Action action,
Func<bool> canExecute = null,
MenuItem parent = null,
KeyCode shortcut = KeyCode.Null
)
{
Title = title ?? "";
Help = help ?? "";
Action = action;
CanExecute = canExecute;
Parent = parent;
_shortcutHelper = new ();
if (shortcut != KeyCode.Null)
{
Shortcut = shortcut;
}
}
/// <summary>Gets or sets the action to be invoked when the menu item is triggered.</summary>
/// <value>Method to invoke.</value>
public Action Action { get; set; }
/// <summary>
/// Used only if <see cref="CheckType"/> is of <see cref="MenuItemCheckStyle.Checked"/> type. If
/// <see langword="true"/> allows <see cref="Checked"/> to be null, true or false. If <see langword="false"/> only
/// allows <see cref="Checked"/> to be true or false.
/// </summary>
public bool AllowNullChecked
{
get => _allowNullChecked;
set
{
_allowNullChecked = value;
Checked ??= false;
}
}
/// <summary>
/// Gets or sets the action to be invoked to determine if the menu can be triggered. If <see cref="CanExecute"/>
/// returns <see langword="true"/> the menu item will be enabled. Otherwise, it will be disabled.
/// </summary>
/// <value>Function to determine if the action is can be executed or not.</value>
public Func<bool> CanExecute { get; set; }
/// <summary>
/// Sets or gets whether the <see cref="MenuItem"/> shows a check indicator or not. See
/// <see cref="MenuItemCheckStyle"/>.
/// </summary>
public bool? Checked { set; get; }
/// <summary>
/// Sets or gets the <see cref="MenuItemCheckStyle"/> of a menu item where <see cref="Checked"/> is set to
/// <see langword="true"/>.
/// </summary>
public MenuItemCheckStyle CheckType
{
get => _checkType;
set
{
_checkType = value;
if (_checkType == MenuItemCheckStyle.Checked && !_allowNullChecked && Checked is null)
{
Checked = false;
}
}
}
/// <summary>Gets or sets arbitrary data for the menu item.</summary>
/// <remarks>This property is not used internally.</remarks>
public object Data { get; set; }
/// <summary>Gets or sets the help text for the menu item. The help text is drawn to the right of the <see cref="Title"/>.</summary>
/// <value>The help text.</value>
public string Help { get; set; }
/// <summary>Gets the parent for this <see cref="MenuItem"/>.</summary>
/// <value>The parent.</value>
public MenuItem Parent { get; set; }
/// <summary>Gets or sets the title of the menu item .</summary>
/// <value>The title.</value>
public string Title
{
get => _title;
set
{
if (_title == value)
{
return;
}
_title = value;
GetHotKey ();
}
}
/// <summary>Gets if this <see cref="MenuItem"/> is from a sub-menu.</summary>
internal bool IsFromSubMenu => Parent != null;
internal int TitleLength => GetMenuBarItemLength (Title);
//
// ┌─────────────────────────────┐
// │ Quit Quit UI Catalog Ctrl+Q │
// └─────────────────────────────┘
// ┌─────────────────┐
// │ ◌ TopLevel Alt+T │
// └─────────────────┘
// TODO: Replace the `2` literals with named constants
internal int Width => 1
+ // space before Title
TitleLength
+ 2
+ // space after Title - BUGBUG: This should be 1
(Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio)
? 2
: 0)
+ // check glyph + space
(Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0)
+ // Two spaces before Help
(ShortcutTag.GetColumns () > 0
? 2 + ShortcutTag.GetColumns ()
: 0); // Pad two spaces before shortcut tag (which are also aligned right)
/// <summary>Merely a debugging aid to see the interaction with main.</summary>
public bool GetMenuBarItem () { return IsFromSubMenu; }
/// <summary>Merely a debugging aid to see the interaction with main.</summary>
public MenuItem GetMenuItem () { return this; }
/// <summary>
/// Returns <see langword="true"/> if the menu item is enabled. This method is a wrapper around
/// <see cref="CanExecute"/>.
/// </summary>
public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
/// <summary>
/// Toggle the <see cref="Checked"/> between three states if <see cref="AllowNullChecked"/> is
/// <see langword="true"/> or between two states if <see cref="AllowNullChecked"/> is <see langword="false"/>.
/// </summary>
public void ToggleChecked ()
{
if (_checkType != MenuItemCheckStyle.Checked)
{
throw new InvalidOperationException ("This isn't a Checked MenuItemCheckStyle!");
}
bool? previousChecked = Checked;
if (AllowNullChecked)
{
Checked = previousChecked switch
{
null => true,
true => false,
false => null
};
}
else
{
Checked = !Checked;
}
}
private static int GetMenuBarItemLength (string title)
{
return title.EnumerateRunes ()
.Where (ch => ch != MenuBar.HotKeySpecifier)
.Sum (ch => Math.Max (ch.GetColumns (), 1));
}
#region Keyboard Handling
// TODO: Update to use Key instead of Rune
/// <summary>
/// The HotKey is used to activate a <see cref="MenuItem"/> with the keyboard. HotKeys are defined by prefixing the
/// <see cref="Title"/> of a MenuItem with an underscore ('_').
/// <para>
/// Pressing Alt-Hotkey for a <see cref="MenuBarItem"/> (menu items on the menu bar) works even if the menu is
/// not active). Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
/// </para>
/// <para>
/// For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the
/// File menu. Pressing the N key will then activate the New MenuItem.
/// </para>
/// <para>See also <see cref="Shortcut"/> which enable global key-bindings to menu items.</para>
/// </summary>
public Rune HotKey { get; set; }
private void GetHotKey ()
{
var nextIsHot = false;
foreach (char x in _title)
{
if (x == MenuBar.HotKeySpecifier.Value)
{
nextIsHot = true;
}
else
{
if (nextIsHot)
{
HotKey = (Rune)char.ToUpper (x);
break;
}
nextIsHot = false;
HotKey = default (Rune);
}
}
}
// TODO: Update to use Key instead of KeyCode
/// <summary>
/// Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the
/// <see cref="View"/> that is the parent of the <see cref="MenuBar"/> or <see cref="ContextMenu"/> this
/// <see cref="MenuItem"/>.
/// <para>
/// The <see cref="KeyCode"/> will be drawn on the MenuItem to the right of the <see cref="Title"/> and
/// <see cref="Help"/> text. See <see cref="ShortcutTag"/>.
/// </para>
/// </summary>
public KeyCode Shortcut
{
get => _shortcutHelper.Shortcut;
set
{
if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == KeyCode.Null))
{
_shortcutHelper.Shortcut = value;
}
}
}
/// <summary>Gets the text describing the keystroke combination defined by <see cref="Shortcut"/>.</summary>
public string ShortcutTag => _shortcutHelper.Shortcut == KeyCode.Null
? string.Empty
: Key.ToString (_shortcutHelper.Shortcut, MenuBar.ShortcutDelimiter);
#endregion Keyboard Handling
}

View File

@@ -0,0 +1,15 @@
namespace Terminal.Gui;
/// <summary>Specifies how a <see cref="MenuItem"/> shows selection state.</summary>
[Flags]
public enum MenuItemCheckStyle
{
/// <summary>The menu item will be shown normally, with no check indicator. The default.</summary>
NoCheck = 0b_0000_0000,
/// <summary>The menu item will indicate checked/un-checked state (see <see cref="Checked"/>).</summary>
Checked = 0b_0000_0001,
/// <summary>The menu item is part of a menu radio group (see <see cref="Checked"/>) and will indicate selected state.</summary>
Radio = 0b_0000_0010
}