// ----------------------------------------------------------------------- // // 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; /// /// A top-level menu control. /// public class Menu : ItemsControl { /// /// 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); } } /// /// 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; this.subscription = new CompositeDisposable( topLevel.AddHandler( InputElement.PointerPressedEvent, this.TopLevelPointerPress, Interactivity.RoutingStrategies.Tunnel), Disposable.Create(() => topLevel.Deactivated -= this.Deactivated)); } /// /// 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 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.Items.OfType()) { if (child != menuItem && child.IsSubMenuOpen) { child.IsSubMenuOpen = false; } } } this.IsOpen = true; } /// /// Closes the menu. /// private void CloseMenu() { foreach (MenuItem i in this.GetLogicalChildren()) { i.IsSubMenuOpen = false; } this.IsOpen = false; } /// /// 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 TopLevelPointerPress(object sender, PointerPressEventArgs e) { if (this.IsOpen) { var control = e.Source as ILogical; if (!this.IsLogicalParentOf(control)) { this.CloseMenu(); } } } } }