| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- namespace Terminal.Gui.Views;
- /// <summary>
- /// A <see cref="Bar"/>-derived object to be used as a vertically-oriented menu. Each subview is a <see cref="MenuItem"/>.
- /// </summary>
- public class Menu : Bar
- {
- /// <inheritdoc/>
- public Menu () : this ([]) { }
- /// <inheritdoc/>
- public Menu (IEnumerable<MenuItem>? menuItems) : this (menuItems?.Cast<View> ()) { }
- /// <inheritdoc/>
- public Menu (IEnumerable<View>? 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);
- SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Menu);
- if (Border is { })
- {
- Border.Settings &= ~BorderSettings.Title;
- }
- BorderStyle = DefaultBorderStyle;
- ConfigurationManager.Applied += OnConfigurationManagerApplied;
- }
- private void OnConfigurationManagerApplied (object? sender, ConfigurationManagerEventArgs e)
- {
- if (SuperView is { })
- {
- BorderStyle = DefaultBorderStyle;
- }
- }
- /// <summary>
- /// Gets or sets the default Border Style for Menus. The default is <see cref="LineStyle.None"/>.
- /// </summary>
- [ConfigurationProperty (Scope = typeof (ThemeScope))]
- public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.None;
- /// <summary>
- /// Gets or sets the menu item that opened this menu as a sub-menu.
- /// </summary>
- public MenuItem? SuperMenuItem { get; set; }
- /// <inheritdoc />
- protected override void OnVisibleChanged ()
- {
- if (Visible)
- {
- SelectedMenuItem = SubViews.Where (mi => mi is MenuItem).ElementAtOrDefault (0) as MenuItem;
- }
- }
- /// <inheritdoc />
- protected override void OnSubViewAdded (View view)
- {
- base.OnSubViewAdded (view);
- switch (view)
- {
- case MenuItem 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;
- }
- }
- /// <inheritdoc />
- 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<KeyBinding> { 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?
- /// <summary>
- /// Raises the <see cref="OnAccepted"/>/<see cref="Accepted"/> event indicating an item in this menu (or submenu)
- /// was accepted. This is used to determine when to hide the menu.
- /// </summary>
- /// <param name="ctx"></param>
- /// <returns></returns>
- protected void RaiseAccepted (ICommandContext? ctx)
- {
- //Logging.Trace ($"RaiseAccepted: {ctx}");
- CommandEventArgs args = new () { Context = ctx };
- OnAccepted (args);
- Accepted?.Invoke (this, args);
- }
- /// <summary>
- /// Called when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the menu.
- /// </summary>
- /// <remarks>
- /// </remarks>
- /// <param name="args"></param>
- protected virtual void OnAccepted (CommandEventArgs args) { }
- /// <summary>
- /// Raised when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the menu.
- /// </summary>
- /// <remarks>
- /// <para>
- /// See <see cref="RaiseAccepted"/> for more information.
- /// </para>
- /// </remarks>
- public event EventHandler<CommandEventArgs>? Accepted;
- /// <inheritdoc />
- protected override void OnFocusedChanged (View? previousFocused, View? focused)
- {
- base.OnFocusedChanged (previousFocused, focused);
- SelectedMenuItem = focused as MenuItem;
- RaiseSelectedMenuItemChanged (SelectedMenuItem);
- }
- /// <summary>
- /// Gets or set the currently selected menu item. This is a helper that
- /// tracks <see cref="View.Focused"/>.
- /// </summary>
- public MenuItem? SelectedMenuItem
- {
- get => Focused as MenuItem;
- set
- {
- if (value == Focused)
- {
- return;
- }
- // Note we DO NOT set focus here; This property tracks Focused
- }
- }
- internal void RaiseSelectedMenuItemChanged (MenuItem? selected)
- {
- // Logging.Debug ($"{Title} ({selected?.Title})");
- OnSelectedMenuItemChanged (selected);
- SelectedMenuItemChanged?.Invoke (this, selected);
- }
- /// <summary>
- /// Called when the selected menu item has changed.
- /// </summary>
- /// <param name="selected"></param>
- protected virtual void OnSelectedMenuItemChanged (MenuItem? selected)
- {
- }
- /// <summary>
- /// Raised when the selected menu item has changed.
- /// </summary>
- public event EventHandler<MenuItem?>? SelectedMenuItemChanged;
- /// <inheritdoc />
- protected override void Dispose (bool disposing)
- {
- base.Dispose (disposing);
- if (disposing)
- {
- ConfigurationManager.Applied -= OnConfigurationManagerApplied;
- }
- }
- }
|