diff --git a/src/Ursa.Themes.Semi/Controls/Navigation.axaml b/src/Ursa.Themes.Semi/Controls/Navigation.axaml index 4492674..421ad30 100644 --- a/src/Ursa.Themes.Semi/Controls/Navigation.axaml +++ b/src/Ursa.Themes.Semi/Controls/Navigation.axaml @@ -111,10 +111,15 @@ Name="PART_IconPresenter" Grid.Column="1" MinWidth="24" - Margin="{TemplateBinding Level, - Converter={StaticResource MarginConverter}}" Content="{TemplateBinding Icon}" - ContentTemplate="{TemplateBinding IconTemplate}" /> + ContentTemplate="{TemplateBinding IconTemplate}"> + + + + + + + - - + + + + diff --git a/src/Ursa.Themes.Semi/Converters/NavigationMenuItemLevelToMarginConverter.cs b/src/Ursa.Themes.Semi/Converters/NavigationMenuItemLevelToMarginConverter.cs index 33d63f4..bcacb57 100644 --- a/src/Ursa.Themes.Semi/Converters/NavigationMenuItemLevelToMarginConverter.cs +++ b/src/Ursa.Themes.Semi/Converters/NavigationMenuItemLevelToMarginConverter.cs @@ -4,20 +4,20 @@ using Avalonia.Data.Converters; namespace Ursa.Themes.Semi.Converters; -public class NavigationMenuItemLevelToMarginConverter: IValueConverter +public class NavigationMenuItemLevelToMarginConverter: IMultiValueConverter { public int Indent { get; set; } - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) { - if (value is int i) + if (values[0] is int i && values[1] is bool b) { + if (b) + { + return new Thickness(); + } return new Thickness((i-1) * Indent, 0, 0, 0); } return new Thickness(); } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/src/Ursa/Controls/Navigation/NavigationMenu.cs b/src/Ursa/Controls/Navigation/NavigationMenu.cs index 478c556..52575b6 100644 --- a/src/Ursa/Controls/Navigation/NavigationMenu.cs +++ b/src/Ursa/Controls/Navigation/NavigationMenu.cs @@ -134,7 +134,7 @@ public class NavigationMenu: HeaderedItemsControl } } - private void UpdateSelectionFromSelectedItem(object? o) + internal void UpdateSelectionFromSelectedItem(object? o) { var children = this.ItemsPanelRoot?.Children; if (children is not null) diff --git a/src/Ursa/Controls/Navigation/NavigationMenuItem.cs b/src/Ursa/Controls/Navigation/NavigationMenuItem.cs index dfc2963..946af82 100644 --- a/src/Ursa/Controls/Navigation/NavigationMenuItem.cs +++ b/src/Ursa/Controls/Navigation/NavigationMenuItem.cs @@ -15,18 +15,22 @@ using Avalonia.VisualTree; namespace Ursa.Controls; -[PseudoClasses(PC_Closed, PC_Selected, PC_Highlighted, PC_Collapsed)] +[PseudoClasses(PC_Closed, PC_Selected, PC_Highlighted, PC_Collapsed, PC_TopLevel)] +[TemplatePart(PART_Popup, typeof(Popup))] public class NavigationMenuItem: HeaderedSelectingItemsControl { public const string PC_Closed = ":closed"; public const string PC_Selected = ":selected"; public const string PC_Highlighted= ":highlighted"; public const string PC_Collapsed = ":collapsed"; + public const string PC_TopLevel = ":top-level"; + public const string PART_Popup = "PART_Popup"; private NavigationMenu? _rootMenu; private IDisposable? _ownerSubscription; private IDisposable? _itemsBinding; private bool _isCollapsed; + private Popup? _popup; public static readonly StyledProperty IsClosedProperty = AvaloniaProperty.Register( nameof(IsClosed)); @@ -54,18 +58,16 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl get => GetValue(IconTemplateProperty); set => SetValue(IconTemplateProperty, value); } - - private int _level; - + public static readonly DirectProperty LevelProperty = AvaloniaProperty.RegisterDirect( nameof(Level), o => o.Level); - + private int _level; public int Level { get => _level; private set => SetAndRaise(LevelProperty, ref _level, value); } - + public static readonly StyledProperty CommandProperty = AvaloniaProperty.Register( nameof(Command)); @@ -84,6 +86,24 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl set => SetValue(CommandParameterProperty, value); } + public static readonly DirectProperty IsTopLevelMenuItemProperty = AvaloniaProperty.RegisterDirect( + nameof(IsTopLevelMenuItem), o => o.IsTopLevelMenuItem, (o, v) => o.IsTopLevelMenuItem = v); + private bool _isTopLevelMenuItem; + public bool IsTopLevelMenuItem + { + get => _isTopLevelMenuItem; + set => SetAndRaise(IsTopLevelMenuItemProperty, ref _isTopLevelMenuItem, value); + } + + public static readonly StyledProperty IsPopupOpenProperty = AvaloniaProperty.Register( + nameof(IsPopupOpen)); + + public bool IsPopupOpen + { + get => GetValue(IsPopupOpenProperty); + set => SetValue(IsPopupOpenProperty, value); + } + static NavigationMenuItem() { IsClosedProperty.Changed.AddClassHandler((o, e) => o.OnIsClosedChanged(e)); @@ -96,13 +116,6 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl PseudoClasses.Set(PC_Closed, newValue); } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -116,10 +129,19 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl SetCurrentValue(ItemContainerThemeProperty, _rootMenu.ItemContainerTheme); } + if (_rootMenu is not null) + { + IsClosed = _rootMenu.IsClosed; + } + _rootMenu?.GetObservable(NavigationMenu.IsClosedProperty) .Subscribe(new AnonymousObserver(a => this.IsClosed = a)); - + _rootMenu?.UpdateSelectionFromSelectedItem(_rootMenu.SelectedItem); + _popup = e.NameScope.Find(PART_Popup); Level = CalculateDistanceFromLogicalParent(this) - 1; + bool isTopLevel = Level == 1; + IsTopLevelMenuItem = isTopLevel; + PseudoClasses.Set(PC_TopLevel, isTopLevel); } private void GetRootMenu() @@ -127,11 +149,10 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl _rootMenu = this.FindAncestorOfType(); if (_rootMenu is null) { - var popupRoot = TopLevel.GetTopLevel(this) as PopupRoot; - if (popupRoot?.Parent is Popup popup) + var parents = this.FindLogicalAncestorOfType(); + if (parents is not null) { - Control? c = popup.PlacementTarget; - _rootMenu = c.FindAncestorOfType(); + _rootMenu = parents; } } } @@ -142,8 +163,7 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl // Leaf menu node, can be selected. if (this.ItemCount == 0) { - var parents = this.GetSelfAndLogicalAncestors(); - if (_rootMenu is not null && parents.Contains(_rootMenu)) + if (_rootMenu is not null ) { object? o = this.DataContext ?? this; _rootMenu.SelectedItem = o; @@ -155,6 +175,10 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl { _isCollapsed = !_isCollapsed; this.PseudoClasses.Set(PC_Collapsed, _isCollapsed); + if (_popup is not null) + { + _popup.IsOpen = !_popup.IsOpen; + } } e.Handled = true; Command?.Execute(CommandParameter); @@ -188,7 +212,7 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl if (propagateToParent) { - var parent = this.FindAncestorOfType(); + var parent = this.FindLogicalAncestorOfType(); if (parent != null) { parent.SetSelection(this, selected, true);