feat: wow, first prototype of navigation menu.

This commit is contained in:
rabbitism
2023-06-22 22:00:28 +08:00
parent f4bb4d379e
commit bacf1a6330
9 changed files with 322 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
<UserControl
x:Class="Ursa.Demo.Pages.NavigationMenuDemo"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="https://irihi.tech/ursa"
xmlns:vm="clr-namespace:Ursa.Demo.ViewModels"
d:DesignHeight="450"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="vm:NavigationMenuDemoViewModel"
mc:Ignorable="d">
<StackPanel>
<u:NavigationMenu Name="menu" ItemsSource="{Binding MenuItems}">
<u:NavigationMenu.ItemTemplate>
<DataTemplate>
<u:NavigationMenuItem Header="{Binding MenuHeader}" ItemsSource="{Binding Children}" />
</DataTemplate>
</u:NavigationMenu.ItemTemplate>
</u:NavigationMenu>
<TreeView ItemsSource="{Binding MenuItems}">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<ContentControl Content="{Binding MenuHeader}" />
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<TextBlock Text="{ReflectionBinding #menu.SelectedMenuItem.MenuHeader}" />
<u:NavigationMenu>
<u:NavigationMenuItem Header="111" />
<u:NavigationMenuItem Header="222" />
<u:NavigationMenuItem Header="333" />
</u:NavigationMenu>
<u:NavigationMenu ItemsSource="{Binding MenuItems}">
<u:NavigationMenu.ItemTemplate>
<DataTemplate>
<u:NavigationMenuItem Header="{Binding MenuHeader}" />
</DataTemplate>
</u:NavigationMenu.ItemTemplate>
</u:NavigationMenu>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,15 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Ursa.Demo.ViewModels;
namespace Ursa.Demo.Pages;
public partial class NavigationMenuDemo : UserControl
{
public NavigationMenuDemo()
{
InitializeComponent();
this.DataContext = new NavigationMenuDemoViewModel();
}
}

View File

@@ -0,0 +1,41 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ursa.Demo.ViewModels;
public class NavigationMenuDemoViewModel: ObservableObject
{
public ObservableCollection<NavigationMenuItemViewModel> MenuItems { get; set; } = new()
{
new NavigationMenuItemViewModel()
{
MenuHeader = "1",
Children = new ObservableCollection<NavigationMenuItemViewModel>()
{
new NavigationMenuItemViewModel(){
MenuHeader = "11" ,
Children = new ObservableCollection<NavigationMenuItemViewModel>()
{
new NavigationMenuItemViewModel(){MenuHeader = "111"},
new NavigationMenuItemViewModel(){MenuHeader = "112"}
}},
new NavigationMenuItemViewModel(){MenuHeader = "12"}
}
},
new NavigationMenuItemViewModel()
{
MenuHeader = "2",
Children = new ObservableCollection<NavigationMenuItemViewModel>()
{
new NavigationMenuItemViewModel(){MenuHeader = "21"},
new NavigationMenuItemViewModel(){MenuHeader = "22"}
}
}
};
}
public class NavigationMenuItemViewModel: ObservableObject
{
public string MenuHeader { get; set; }
public ObservableCollection<NavigationMenuItemViewModel> Children { get; set; } = new();
}

View File

@@ -35,6 +35,9 @@
<TabItem Header="IPv4Box"> <TabItem Header="IPv4Box">
<pages:IPv4BoxDemo /> <pages:IPv4BoxDemo />
</TabItem> </TabItem>
<TabItem Header="Navigation">
<pages:NavigationMenuDemo />
</TabItem>
<TabItem Header="Pagination"> <TabItem Header="Pagination">
<pages:PaginationDemo /> <pages:PaginationDemo />
</TabItem> </TabItem>

View File

@@ -0,0 +1,42 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa">
<!-- Add Resources Here -->
<ControlTheme x:Key="{x:Type u:NavigationMenu}" TargetType="u:NavigationMenu">
<Setter Property="Template">
<ControlTemplate TargetType="u:NavigationMenu">
<Border Background="{TemplateBinding Background}">
<StackPanel>
<ContentPresenter Name="PART_HeaderPresenter" Content="Hello" />
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type u:NavigationMenuItem}" TargetType="u:NavigationMenuItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<ControlTemplate TargetType="u:NavigationMenuItem">
<Border
Padding="4"
BorderBrush="Black"
BorderThickness="1">
<StackPanel>
<ContentPresenter
Name="PART_HeaderPresenter"
Background="{TemplateBinding Background}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}" />
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:selected">
<Setter Property="u:NavigationMenuItem.Background" Value="Red" />
</Style>
</ControlTheme>
</ResourceDictionary>

View File

@@ -5,6 +5,7 @@
<ResourceInclude Source="Banner.axaml" /> <ResourceInclude Source="Banner.axaml" />
<ResourceInclude Source="Divider.axaml" /> <ResourceInclude Source="Divider.axaml" />
<ResourceInclude Source="IPv4Box.axaml" /> <ResourceInclude Source="IPv4Box.axaml" />
<ResourceInclude Source="Navigation.axaml" />
<ResourceInclude Source="Pagination.axaml" /> <ResourceInclude Source="Pagination.axaml" />
<ResourceInclude Source="Timeline.axaml" /> <ResourceInclude Source="Timeline.axaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>

View File

@@ -0,0 +1,68 @@
using System.Collections;
using System.Collections.Specialized;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Metadata;
namespace Ursa.Controls;
public class NavigationMenu: HeaderedSelectingItemsControl
{
public static readonly StyledProperty<object?> FooterProperty = AvaloniaProperty.Register<NavigationMenu, object?>(
nameof(Footer));
public object? Footer
{
get => GetValue(FooterProperty);
set => SetValue(FooterProperty, value);
}
public static readonly StyledProperty<IDataTemplate> FooterTemplateProperty = AvaloniaProperty.Register<NavigationMenu, IDataTemplate>(
nameof(FooterTemplate));
public IDataTemplate FooterTemplate
{
get => GetValue(FooterTemplateProperty);
set => SetValue(FooterTemplateProperty, value);
}
public static readonly StyledProperty<object?> SelectedMenuItemProperty = AvaloniaProperty.Register<NavigationMenu, object?>(
nameof(SelectedMenuItem));
public object? SelectedMenuItem
{
get => GetValue(SelectedMenuItemProperty);
set => SetValue(SelectedMenuItemProperty, value);
}
internal void UpdateSelection(NavigationMenuItem source)
{
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;
}
if (item != null)
{
if(Equals(item, source)) continue;
item.SetSelection(null, false, false);
}
}
}
}
}

View File

@@ -0,0 +1,99 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Reactive;
using Avalonia.VisualTree;
namespace Ursa.Controls;
[PseudoClasses(PC_Closed, PC_Selected, PC_Empty)]
public class NavigationMenuItem: HeaderedSelectingItemsControl
{
public const string PC_Closed = ":closed";
public const string PC_Selected = ":selected";
public const string PC_Empty = ":empty";
private NavigationMenu? _rootMenu;
private IDisposable? _ownerSubscription;
private IDisposable? _itemsBinding;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_rootMenu = this.FindAncestorOfType<NavigationMenu>();
if (ItemTemplate == null && _rootMenu?.ItemTemplate != null)
{
SetCurrentValue(ItemTemplateProperty, _rootMenu.ItemTemplate);
}
if (ItemContainerTheme == null && _rootMenu?.ItemContainerTheme != null)
{
SetCurrentValue(ItemContainerThemeProperty, _rootMenu.ItemContainerTheme);
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
// Leaf menu node, can be selected.
if (this.ItemCount == 0)
{
var parents = this.GetSelfAndLogicalAncestors();
if (_rootMenu is not null && parents.Contains(_rootMenu))
{
object? o = this.DataContext ?? this;
_rootMenu.SelectedMenuItem = o;
}
}
e.Handled = true;
SetSelection(this, true, true);
}
internal void SetSelection(NavigationMenuItem? source, bool selected, bool propagateToParent = false)
{
this.PseudoClasses.Set(PC_Selected, 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;
}
if (item != null)
{
if(Equals(item, source)) continue;
item.SetSelection(this, false, false);
}
}
}
if (propagateToParent)
{
var parent = this.FindAncestorOfType<NavigationMenuItem>();
if (parent != null)
{
parent.SetSelection(this, selected, true);
}
else
{
if (selected)
{
_rootMenu?.UpdateSelection(this);
}
}
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Ursa.Controls;
public class NavigationMenuSeparator: NavigationMenuItem
{
}