// ----------------------------------------------------------------------- // // Copyright 2015 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Controls { using System; using System.Linq; using System.Reactive.Disposables; using Perspex.Input; using Perspex.LogicalTree; using Perspex.Rendering; using Perspex.Interactivity; using Perspex.Controls.Primitives; /// /// A top-level menu control. /// public class Menu : SelectingItemsControl, IFocusScope, IMainMenu { /// /// Defines the default items panel used by a . /// private static readonly ItemsPanelTemplate DefaultPanel = new ItemsPanelTemplate(() => new StackPanel { Orientation = Orientation.Horizontal }); /// /// Defines the property. /// public static readonly PerspexProperty IsOpenProperty = PerspexProperty.Register(nameof(IsOpen)); /// /// Tracks event handlers added to the root of the visual tree. /// private IDisposable subscription; /// /// Initializes static members of the class. /// static Menu() { ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel); MenuItem.ClickEvent.AddClassHandler(x => x.OnMenuClick); MenuItem.SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); } /// /// Gets a value indicating whether the menu is open. /// public bool IsOpen { get { return this.GetValue(IsOpenProperty); } private set { this.SetValue(IsOpenProperty, value); } } /// /// Gets the selected container. /// private MenuItem SelectedMenuItem { get { return (this.SelectedItem != null) ? (MenuItem)this.ItemContainerGenerator.GetContainerForItem(this.SelectedItem) : null; } } /// /// Closes the menu. /// public void CloseMenu() { foreach (MenuItem i in this.GetLogicalChildren()) { i.IsSubMenuOpen = false; } this.IsOpen = false; this.SelectedIndex = -1; } /// /// Opens the menu in response to the Alt/F10 key. /// public void OpenMenu() { this.SelectedIndex = 0; ((IInputElement)this.SelectedItem)?.Focus(); } /// /// Called when the is attached to the visual tree. /// /// The root of the visual tree. protected override void OnAttachedToVisualTree(IRenderRoot root) { base.OnAttachedToVisualTree(root); var topLevel = root as TopLevel; topLevel.Deactivated += this.Deactivated; var pointerPress = topLevel.AddHandler( InputElement.PointerPressedEvent, this.TopLevelPreviewPointerPress, RoutingStrategies.Tunnel); this.subscription = new CompositeDisposable( pointerPress, Disposable.Create(() => topLevel.Deactivated -= this.Deactivated)); var inputRoot = root as IInputRoot; if (inputRoot != null && inputRoot.AccessKeyHandler != null) { inputRoot.AccessKeyHandler.MainMenu = this; } } /// /// Called when the is detached from the visual tree. /// /// The root of the visual tree being detached from. protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot) { base.OnDetachedFromVisualTree(oldRoot); this.subscription.Dispose(); } /// /// Called when the loses focus. /// /// The event args. protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); //this.CloseMenu(); } /// /// Called when a key is pressed within the menu. /// /// The event args. protected override void OnKeyDown(KeyEventArgs e) { bool menuWasOpen = this.SelectedMenuItem?.IsSubMenuOpen ?? false; base.OnKeyDown(e); if (this.IsOpen && e.Key == Key.Escape) { this.CloseMenu(); e.Handled = true; } else if (menuWasOpen) { // If a menu item was open and we navigate to a new one with the arrow keys, open // that menu and select the first item. var selection = this.SelectedMenuItem; if (selection != null && !selection.IsSubMenuOpen) { selection.IsSubMenuOpen = true; selection.SelectedIndex = 0; } } } /// /// Called when a submenu opens somewhere in the menu. /// /// The event args. protected virtual void OnSubmenuOpened(RoutedEventArgs e) { var menuItem = e.Source as MenuItem; if (menuItem != null && menuItem.Parent == this) { foreach (var child in this.GetLogicalChildren().OfType()) { if (child != menuItem && child.IsSubMenuOpen) { child.IsSubMenuOpen = false; } } } this.IsOpen = true; } /// /// Called when the top-level window is deactivated. /// /// The sender. /// The event args. private void Deactivated(object sender, EventArgs e) { this.CloseMenu(); } /// /// Called when a submenu is clicked somewhere in the menu. /// /// The event args. private void OnMenuClick(RoutedEventArgs e) { this.CloseMenu(); } /// /// Called when the pointer is pressed anywhere on the window. /// /// The sender. /// The event args. private void TopLevelPreviewPointerPress(object sender, PointerPressEventArgs e) { if (this.IsOpen) { var control = e.Source as ILogical; if (!this.IsLogicalParentOf(control)) { this.CloseMenu(); } } } } }