feat: update to finish functionality, start to build theme.
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Collections.Specialized;
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
@@ -12,8 +13,14 @@ using Avalonia.Metadata;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class NavigationMenu: HeaderedSelectingItemsControl
|
||||
[PseudoClasses(PC_Closed)]
|
||||
[TemplatePart(Name = PART_CloseButton, Type = typeof(ToggleButton))]
|
||||
|
||||
public class NavigationMenu: HeaderedItemsControl
|
||||
{
|
||||
public const string PC_Closed = ":closed";
|
||||
public const string PART_CloseButton = "PART_CloseButton";
|
||||
|
||||
public static readonly StyledProperty<object?> FooterProperty = AvaloniaProperty.Register<NavigationMenu, object?>(
|
||||
nameof(Footer));
|
||||
|
||||
@@ -32,13 +39,82 @@ public class NavigationMenu: HeaderedSelectingItemsControl
|
||||
set => SetValue(FooterTemplateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<object?> SelectedMenuItemProperty = AvaloniaProperty.Register<NavigationMenu, object?>(
|
||||
nameof(SelectedMenuItem));
|
||||
public static readonly StyledProperty<object?> IconProperty = AvaloniaProperty.Register<NavigationMenu, object?>(
|
||||
nameof(Icon));
|
||||
|
||||
public object? SelectedMenuItem
|
||||
public object? Icon
|
||||
{
|
||||
get => GetValue(SelectedMenuItemProperty);
|
||||
set => SetValue(SelectedMenuItemProperty, value);
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
|
||||
public static readonly StyledProperty<object?> SelectedItemProperty = AvaloniaProperty.Register<NavigationMenu, object?>(
|
||||
nameof(SelectedItem));
|
||||
|
||||
public object? SelectedItem
|
||||
{
|
||||
get => GetValue(SelectedItemProperty);
|
||||
set => SetValue(SelectedItemProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> ShowCollapseButtonProperty = AvaloniaProperty.Register<NavigationMenu, bool>(
|
||||
nameof(ShowCollapseButton));
|
||||
|
||||
public bool ShowCollapseButton
|
||||
{
|
||||
get => GetValue(ShowCollapseButtonProperty);
|
||||
set => SetValue(ShowCollapseButtonProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsClosedProperty = AvaloniaProperty.Register<NavigationMenu, bool>(
|
||||
nameof(IsClosed));
|
||||
|
||||
public bool IsClosed
|
||||
{
|
||||
get => GetValue(IsClosedProperty);
|
||||
set => SetValue(IsClosedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<double> OpenedWidthProperty = AvaloniaProperty.Register<NavigationMenu, double>(
|
||||
nameof(OpenedWidth));
|
||||
|
||||
public double OpenedWidth
|
||||
{
|
||||
get => GetValue(OpenedWidthProperty);
|
||||
set => SetValue(OpenedWidthProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<double> ClosedWidthProperty = AvaloniaProperty.Register<NavigationMenu, double>(
|
||||
nameof(ClosedWidth));
|
||||
|
||||
public double ClosedWidth
|
||||
{
|
||||
get => GetValue(ClosedWidthProperty);
|
||||
set => SetValue(ClosedWidthProperty, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static NavigationMenu()
|
||||
{
|
||||
SelectedItemProperty.Changed.AddClassHandler<NavigationMenu>((o, e) => o.OnSelectionItemChanged(e));
|
||||
IsClosedProperty.Changed.AddClassHandler<NavigationMenu>((o,e)=>o.OnIsClosedChanged(e));
|
||||
}
|
||||
|
||||
private void OnSelectionItemChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
{
|
||||
var newItem = args.GetNewValue<object?>();
|
||||
if (newItem is not null)
|
||||
{
|
||||
UpdateSelectionFromSelectedItem(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIsClosedChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
{
|
||||
bool newValue = args.GetNewValue<bool>();
|
||||
PseudoClasses.Set(PC_Closed, newValue);
|
||||
}
|
||||
|
||||
internal void UpdateSelection(NavigationMenuItem source)
|
||||
@@ -48,15 +124,7 @@ public class NavigationMenu: HeaderedSelectingItemsControl
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
NavigationMenuItem? item = null;
|
||||
if (child is NavigationMenuItem i)
|
||||
{
|
||||
item = i;
|
||||
}
|
||||
else if (child is ContentPresenter { Child: NavigationMenuItem i2 })
|
||||
{
|
||||
item = i2;
|
||||
}
|
||||
NavigationMenuItem? item = NavigationMenuItem.GetMenuItemFromControl(child);
|
||||
if (item != null)
|
||||
{
|
||||
if(Equals(item, source)) continue;
|
||||
@@ -65,4 +133,18 @@ public class NavigationMenu: HeaderedSelectingItemsControl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSelectionFromSelectedItem(object? o)
|
||||
{
|
||||
var children = this.ItemsPanelRoot?.Children;
|
||||
if (children is not null)
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
NavigationMenuItem? item = NavigationMenuItem.GetMenuItemFromControl(child);
|
||||
if(item is null) continue;
|
||||
item.UpdateSelectionFromSelectedItem(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.LogicalTree;
|
||||
@@ -12,16 +14,68 @@ using Avalonia.VisualTree;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[PseudoClasses(PC_Closed, PC_Selected, PC_Empty)]
|
||||
[PseudoClasses(PC_Closed, PC_Selected, PC_Highlighted, PC_Collapsed)]
|
||||
public class NavigationMenuItem: HeaderedSelectingItemsControl
|
||||
{
|
||||
public const string PC_Closed = ":closed";
|
||||
public const string PC_Selected = ":selected";
|
||||
public const string PC_Empty = ":empty";
|
||||
public const string PC_Highlighted= ":highlighted";
|
||||
public const string PC_Collapsed = ":collapsed";
|
||||
|
||||
private NavigationMenu? _rootMenu;
|
||||
private IDisposable? _ownerSubscription;
|
||||
private IDisposable? _itemsBinding;
|
||||
private bool _isCollapsed;
|
||||
|
||||
public static readonly StyledProperty<bool> IsClosedProperty = AvaloniaProperty.Register<NavigationMenuItem, bool>(
|
||||
nameof(IsClosed));
|
||||
|
||||
public bool IsClosed
|
||||
{
|
||||
get => GetValue(IsClosedProperty);
|
||||
set => SetValue(IsClosedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<object?> IconProperty = AvaloniaProperty.Register<NavigationMenuItem, object?>(
|
||||
nameof(Icon));
|
||||
|
||||
public object? Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IDataTemplate> IconTemplateProperty = AvaloniaProperty.Register<NavigationMenuItem, IDataTemplate>(
|
||||
nameof(IconTemplate));
|
||||
|
||||
public IDataTemplate IconTemplate
|
||||
{
|
||||
get => GetValue(IconTemplateProperty);
|
||||
set => SetValue(IconTemplateProperty, value);
|
||||
}
|
||||
|
||||
private int _level;
|
||||
|
||||
public static readonly DirectProperty<NavigationMenuItem, int> LevelProperty = AvaloniaProperty.RegisterDirect<NavigationMenuItem, int>(
|
||||
nameof(Level), o => o.Level);
|
||||
|
||||
public int Level
|
||||
{
|
||||
get => _level;
|
||||
private set => SetAndRaise(LevelProperty, ref _level, value);
|
||||
}
|
||||
|
||||
static NavigationMenuItem()
|
||||
{
|
||||
IsClosedProperty.Changed.AddClassHandler<NavigationMenuItem>((o, e) => o.OnIsClosedChanged(e));
|
||||
}
|
||||
|
||||
private void OnIsClosedChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
{
|
||||
bool newValue = args.GetNewValue<bool>();
|
||||
PseudoClasses.Set(PC_Closed, newValue);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
@@ -35,6 +89,11 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
|
||||
{
|
||||
SetCurrentValue(ItemContainerThemeProperty, _rootMenu.ItemContainerTheme);
|
||||
}
|
||||
|
||||
_rootMenu?.GetObservable(NavigationMenu.IsClosedProperty)
|
||||
.Subscribe(new AnonymousObserver<bool>(a => this.IsClosed = a));
|
||||
|
||||
Level = CalculateDistanceFromLogicalParent<NavigationMenu>(this) - 1;
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
@@ -47,31 +106,38 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
|
||||
if (_rootMenu is not null && parents.Contains(_rootMenu))
|
||||
{
|
||||
object? o = this.DataContext ?? this;
|
||||
_rootMenu.SelectedMenuItem = o;
|
||||
_rootMenu.SelectedItem = o;
|
||||
}
|
||||
SetSelection(this, true, true);
|
||||
}
|
||||
// Non-leaf node, act as a toggle button.
|
||||
else
|
||||
{
|
||||
_isCollapsed = !_isCollapsed;
|
||||
this.PseudoClasses.Set(PC_Collapsed, _isCollapsed);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
SetSelection(this, true, true);
|
||||
|
||||
}
|
||||
|
||||
internal void SetSelection(NavigationMenuItem? source, bool selected, bool propagateToParent = false)
|
||||
{
|
||||
this.PseudoClasses.Set(PC_Selected, selected);
|
||||
if (Equals(this, source) && this.ItemCount == 0)
|
||||
{
|
||||
this.PseudoClasses.Set(PC_Highlighted, selected);
|
||||
this.PseudoClasses.Set(PC_Selected, selected);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.PseudoClasses.Set(PC_Selected, false);
|
||||
this.PseudoClasses.Set(PC_Highlighted, selected);
|
||||
}
|
||||
var children = this.ItemsPanelRoot?.Children;
|
||||
if (children is not null)
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
NavigationMenuItem? item = null;
|
||||
if (child is NavigationMenuItem i)
|
||||
{
|
||||
item = i;
|
||||
}
|
||||
else if (child is ContentPresenter { Child: NavigationMenuItem i2 })
|
||||
{
|
||||
item = i2;
|
||||
}
|
||||
NavigationMenuItem? item = GetMenuItemFromControl(child);
|
||||
if (item != null)
|
||||
{
|
||||
if(Equals(item, source)) continue;
|
||||
@@ -96,4 +162,54 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateSelectionFromSelectedItem(object? o)
|
||||
{
|
||||
if (o is null)
|
||||
{
|
||||
this.SetSelection(this, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Equals(this, o) || Equals(this.DataContext, o))
|
||||
{
|
||||
this.SetSelection(this, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var children = this.ItemsPanelRoot?.Children;
|
||||
if (children is not null)
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
NavigationMenuItem? item = GetMenuItemFromControl(child);
|
||||
if (item != null)
|
||||
{
|
||||
item.UpdateSelectionFromSelectedItem(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int CalculateDistanceFromLogicalParent<T>(ILogical? logical, int @default = -1) where T : class
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
while (logical != null && !(logical is T))
|
||||
{
|
||||
++result;
|
||||
logical = logical.LogicalParent;
|
||||
}
|
||||
|
||||
return logical != null ? result : @default;
|
||||
}
|
||||
|
||||
public static NavigationMenuItem? GetMenuItemFromControl(Control? control)
|
||||
{
|
||||
if (control is null) return null;
|
||||
if (control is NavigationMenuItem item) return item;
|
||||
if (control is ContentPresenter { Child: NavigationMenuItem item2 }) return item2;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user