// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; namespace Avalonia.Controls { /// /// A top-level menu control. /// public class Menu : SelectingItemsControl, IFocusScope, IMainMenu, IMenu { /// /// Defines the property. /// public static readonly DirectProperty IsOpenProperty = AvaloniaProperty.RegisterDirect( nameof(IsOpen), o => o.IsOpen); /// /// Defines the event. /// public static readonly RoutedEvent MenuOpenedEvent = RoutedEvent.Register(nameof(MenuOpened), RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent MenuClosedEvent = RoutedEvent.Register(nameof(MenuClosed), RoutingStrategies.Bubble); private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Horizontal }); private readonly IMenuInteractionHandler _interaction; private bool _isOpen; /// /// Initializes a new instance of the class. /// public Menu() { _interaction = AvaloniaLocator.Current.GetService() ?? new DefaultMenuInteractionHandler(); } /// /// Initializes a new instance of the class. /// /// The menu iteraction handler. public Menu(IMenuInteractionHandler interactionHandler) { Contract.Requires(interactionHandler != null); _interaction = interactionHandler; } /// /// Initializes static members of the class. /// static Menu() { ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel); MenuItem.SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); } /// /// Gets a value indicating whether the menu is open. /// public bool IsOpen { get { return _isOpen; } private set { SetAndRaise(IsOpenProperty, ref _isOpen, value); } } /// IMenuInteractionHandler IMenu.InteractionHandler => _interaction; /// IMenuItem IMenuElement.SelectedItem { get { var index = SelectedIndex; return (index != -1) ? (IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) : null; } set { SelectedIndex = ItemContainerGenerator.IndexFromContainer(value); } } /// IEnumerable IMenuElement.SubItems { get { return ItemContainerGenerator.Containers .Select(x => x.ContainerControl) .OfType(); } } /// /// Occurs when a is opened. /// public event EventHandler MenuOpened { add { AddHandler(MenuOpenedEvent, value); } remove { RemoveHandler(MenuOpenedEvent, value); } } /// /// Occurs when a is closed. /// public event EventHandler MenuClosed { add { AddHandler(MenuClosedEvent, value); } remove { RemoveHandler(MenuClosedEvent, value); } } /// /// Closes the menu. /// public void Close() { if (IsOpen) { foreach (var i in ((IMenu)this).SubItems) { i.Close(); } IsOpen = false; SelectedIndex = -1; RaiseEvent(new RoutedEventArgs { RoutedEvent = MenuClosedEvent, Source = this, }); } } /// /// Opens the menu in response to the Alt/F10 key. /// public void Open() { if (!IsOpen) { IsOpen = true; RaiseEvent(new RoutedEventArgs { RoutedEvent = MenuOpenedEvent, Source = this, }); } } /// bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap); /// protected override IItemContainerGenerator CreateItemContainerGenerator() { return new ItemContainerGenerator(this, MenuItem.HeaderProperty, null); } /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); var inputRoot = e.Root as IInputRoot; if (inputRoot?.AccessKeyHandler != null) { inputRoot.AccessKeyHandler.MainMenu = this; } _interaction.Attach(this); } /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); _interaction.Detach(this); } /// protected override void OnKeyDown(KeyEventArgs e) { // Don't handle here: let the interaction handler handle it. } /// /// Called when a submenu opens somewhere in the menu. /// /// The event args. protected virtual void OnSubmenuOpened(RoutedEventArgs e) { if (e.Source is MenuItem menuItem && menuItem.Parent == this) { foreach (var child in this.GetLogicalChildren().OfType()) { if (child != menuItem && child.IsSubMenuOpen) { child.IsSubMenuOpen = false; } } } IsOpen = true; } } }