diff --git a/demo/Ursa.Demo/Pages/NavMenuDemo.axaml b/demo/Ursa.Demo/Pages/NavMenuDemo.axaml index d0f1648..984bea3 100644 --- a/demo/Ursa.Demo/Pages/NavMenuDemo.axaml +++ b/demo/Ursa.Demo/Pages/NavMenuDemo.axaml @@ -1,27 +1,54 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/Ursa.Demo/Pages/NavMenuDemo.axaml.cs b/demo/Ursa.Demo/Pages/NavMenuDemo.axaml.cs index 30898d6..635ab59 100644 --- a/demo/Ursa.Demo/Pages/NavMenuDemo.axaml.cs +++ b/demo/Ursa.Demo/Pages/NavMenuDemo.axaml.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Ursa.Demo.ViewModels; namespace Ursa.Demo.Pages; @@ -9,5 +10,6 @@ public partial class NavMenuDemo : UserControl public NavMenuDemo() { InitializeComponent(); + this.DataContext = new NavMenuDemoViewModel(); } } \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/NavMenuDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/NavMenuDemoViewModel.cs index c99730f..aea2ff2 100644 --- a/demo/Ursa.Demo/ViewModels/NavMenuDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/NavMenuDemoViewModel.cs @@ -1,8 +1,49 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; namespace Ursa.Demo.ViewModels; public class NavMenuDemoViewModel: ObservableObject { - + public ObservableCollection MenuItems { get; set; } = new ObservableCollection + { + new MenuItem { Header = "Introduction" , Children = + { + new MenuItem() { Header = "Getting Started" }, + new MenuItem() { Header = "Design Principles" }, + new MenuItem() { Header = "Contributing" }, + }}, + new MenuItem { Header = "Badge" }, + new MenuItem { Header = "Banner" }, + new MenuItem { Header = "ButtonGroup" }, + new MenuItem { Header = "Class Input" }, + new MenuItem { Header = "Dialog" }, + new MenuItem { Header = "Divider" }, + new MenuItem { Header = "Drawer" }, + new MenuItem { Header = "DualBadge" }, + new MenuItem { Header = "EnumSelector" }, + new MenuItem { Header = "ImageViewer" }, + new MenuItem { Header = "IPv4Box" }, + new MenuItem { Header = "IconButton" }, + new MenuItem { Header = "KeyGestureInput" }, + new MenuItem { Header = "Loading" }, + new MenuItem { Header = "MessageBox" }, + new MenuItem { Header = "Navigation" }, + new MenuItem { Header = "NavMenu" }, + new MenuItem { Header = "NumericUpDown" }, + new MenuItem { Header = "Pagination" }, + new MenuItem { Header = "RangeSlider" }, + new MenuItem { Header = "SelectionList" }, + new MenuItem { Header = "TagInput" }, + new MenuItem { Header = "Timeline" }, + new MenuItem { Header = "TwoTonePathIcon" }, + new MenuItem { Header = "ThemeToggler" } + }; +} + +public class MenuItem +{ + public string? Header { get; set; } + public string? Icon { get; set; } + public ObservableCollection Children { get; set; } = new ObservableCollection(); } \ No newline at end of file diff --git a/src/Ursa/Controls/NavMenu/NavMenu.cs b/src/Ursa/Controls/NavMenu/NavMenu.cs index f0b99b6..7469e42 100644 --- a/src/Ursa/Controls/NavMenu/NavMenu.cs +++ b/src/Ursa/Controls/NavMenu/NavMenu.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.LogicalTree; +using Avalonia.Metadata; namespace Ursa.Controls; @@ -18,6 +19,39 @@ public class NavMenu: ItemsControl set => SetValue(SelectedItemProperty, value); } + public static readonly StyledProperty IconBindingProperty = AvaloniaProperty.Register( + nameof(IconBinding)); + + [AssignBinding] + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IBinding? IconBinding + { + get => GetValue(IconBindingProperty); + set => SetValue(IconBindingProperty, value); + } + + public static readonly StyledProperty HeaderBindingProperty = AvaloniaProperty.Register( + nameof(HeaderBinding)); + + [AssignBinding] + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IBinding? HeaderBinding + { + get => GetValue(HeaderBindingProperty); + set => SetValue(HeaderBindingProperty, value); + } + + public static readonly StyledProperty SubMenuBindingProperty = AvaloniaProperty.Register( + nameof(SubMenuBinding)); + + [AssignBinding] + [InheritDataTypeFromItems(nameof(ItemsSource))] + public IBinding? SubMenuBinding + { + get => GetValue(SubMenuBindingProperty); + set => SetValue(SubMenuBindingProperty, value); + } + static NavMenu() { SelectedItemProperty.Changed.AddClassHandler((o, e) => o.OnSelectedItemChange(e)); @@ -37,7 +71,27 @@ public class NavMenu: ItemsControl { return new NavMenuItem(); } - + + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + { + base.PrepareContainerForItemOverride(container, item, index); + if (container is NavMenuItem navMenuItem) + { + if (IconBinding is not null) + { + navMenuItem[!NavMenuItem.IconProperty] = IconBinding; + } + if (HeaderBinding is not null) + { + navMenuItem[!HeaderedItemsControl.HeaderProperty] = HeaderBinding; + } + if (SubMenuBinding is not null) + { + navMenuItem[!ItemsSourceProperty] = SubMenuBinding; + } + } + } + internal void SelectItem(NavMenuItem item) { if (item.IsSelected) return; diff --git a/src/Ursa/Controls/NavMenu/NavMenuItem.cs b/src/Ursa/Controls/NavMenu/NavMenuItem.cs index 6195ed9..ccd9ffd 100644 --- a/src/Ursa/Controls/NavMenu/NavMenuItem.cs +++ b/src/Ursa/Controls/NavMenu/NavMenuItem.cs @@ -1,5 +1,7 @@ -using Avalonia; +using System.Windows.Input; +using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -9,8 +11,11 @@ using Avalonia.VisualTree; namespace Ursa.Controls; +[PseudoClasses(PC_Highlighted)] public class NavMenuItem: HeaderedSelectingItemsControl { + public const string PC_Highlighted = "highlighted"; + private NavMenu? _rootMenu; public static readonly StyledProperty IconProperty = AvaloniaProperty.Register( @@ -31,6 +36,15 @@ public class NavMenuItem: HeaderedSelectingItemsControl set => SetValue(IconTemplateProperty, value); } + public static readonly StyledProperty CommandProperty = AvaloniaProperty.Register( + nameof(Command)); + + public ICommand? Command + { + get => GetValue(CommandProperty); + set => SetValue(CommandProperty, value); + } + public new static readonly StyledProperty IsSelectedProperty = SelectingItemsControl.IsSelectedProperty.AddOwner(); @@ -39,6 +53,8 @@ public class NavMenuItem: HeaderedSelectingItemsControl get => GetValue(IsSelectedProperty); set => SetValue(IsSelectedProperty, value); } + + static NavMenuItem() { @@ -56,26 +72,34 @@ public class NavMenuItem: HeaderedSelectingItemsControl return new NavMenuItem(); } + protected override void PrepareContainerForItemOverride(Control container, object? item, int index) + { + base.PrepareContainerForItemOverride(container, item, index); + if (container is NavMenuItem navMenuItem) + { + if (_rootMenu?.HeaderBinding is not null) + { + container[!HeaderProperty] = _rootMenu.HeaderBinding; + } + } + } + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); _rootMenu = GetRootMenu(); - UpdateSelection(1); } protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); _rootMenu?.SelectItem(this); + e.Handled = true; } private NavMenu? GetRootMenu() { - var root = this.FindAncestorOfType(); - if (root is null) - { - root = this.FindLogicalAncestorOfType(); - } + var root = this.FindAncestorOfType() ?? this.FindLogicalAncestorOfType(); return root; } } \ No newline at end of file