feat: support popup on closed mode.

This commit is contained in:
rabbitism
2023-06-23 13:25:37 +08:00
parent f606667a3f
commit 2616c11fbf
4 changed files with 149 additions and 39 deletions

View File

@@ -111,10 +111,15 @@
Name="PART_IconPresenter" Name="PART_IconPresenter"
Grid.Column="1" Grid.Column="1"
MinWidth="24" MinWidth="24"
Margin="{TemplateBinding Level,
Converter={StaticResource MarginConverter}}"
Content="{TemplateBinding Icon}" Content="{TemplateBinding Icon}"
ContentTemplate="{TemplateBinding IconTemplate}" /> ContentTemplate="{TemplateBinding IconTemplate}">
<ContentPresenter.Margin>
<MultiBinding Converter="{StaticResource MarginConverter}">
<Binding Path="Level" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsClosed" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</ContentPresenter.Margin>
</ContentPresenter>
<ContentPresenter <ContentPresenter
Name="PART_HeaderPresenter" Name="PART_HeaderPresenter"
Grid.Column="2" Grid.Column="2"
@@ -170,7 +175,7 @@
<Setter Property="u:NavigationMenuItem.Background" Value="{DynamicResource NavigationMenuItemSelectedBackground}" /> <Setter Property="u:NavigationMenuItem.Background" Value="{DynamicResource NavigationMenuItemSelectedBackground}" />
</Style> </Style>
<Style Selector="^:closed"> <Style Selector="^:empty:closed:top-level">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate TargetType="u:NavigationMenuItem"> <ControlTemplate TargetType="u:NavigationMenuItem">
<Border <Border
@@ -179,15 +184,16 @@
Padding="8" Padding="8"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Background="{DynamicResource NavigationMenuItemDefaultBackground}" Background="{DynamicResource NavigationMenuItemDefaultBackground}"
CornerRadius="6"> CornerRadius="6"
ToolTip.ShowDelay="0"
ToolTip.Tip="{TemplateBinding Header}">
<ContentPresenter <ContentPresenter
Name="PART_IconPresenter" Name="PART_IconPresenter"
Grid.Column="1" Grid.Column="1"
MinWidth="24" MinWidth="24"
Margin="0"
Content="{TemplateBinding Icon}" Content="{TemplateBinding Icon}"
ContentTemplate="{TemplateBinding IconTemplate}" ContentTemplate="{TemplateBinding IconTemplate}" />
ToolTip.ShowDelay="0"
ToolTip.Tip="{TemplateBinding Header}" />
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
@@ -199,7 +205,87 @@
</Style> </Style>
</Style> </Style>
<Style Selector="^:collapsed /template/ ItemsPresenter#PART_ItemsPresenter"> <Style Selector="^:not(:empty):closed">
<Setter Property="Template">
<ControlTemplate TargetType="u:NavigationMenuItem">
<Panel>
<Border
Name="PART_HeaderBorder"
Margin="0,2"
Padding="8"
HorizontalAlignment="Center"
Background="{DynamicResource NavigationMenuItemDefaultBackground}"
CornerRadius="6">
<Grid ColumnDefinitions="Auto, *, Auto">
<ContentPresenter
Name="PART_IconPresenter"
Grid.Column="0"
MinWidth="24"
Margin="0"
Content="{TemplateBinding Icon}"
ContentTemplate="{TemplateBinding IconTemplate}" />
<ContentPresenter
Name="PART_HeaderPresenter"
Grid.Column="1"
VerticalAlignment="Center"
Background="{TemplateBinding Background}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
IsVisible="{TemplateBinding IsTopLevelMenuItem,
Converter={x:Static BoolConverters.Not}}" />
<PathIcon
Grid.Column="2"
Width="8"
Height="8"
Margin="8,0"
Data="M4.08045 7.59809C4.66624 7.01231 5.61599 7.01231 6.20177 7.59809L11.8586 13.2549L17.5155 7.59809C18.1013 7.01231 19.051 7.01231 19.6368 7.59809C20.2226 8.18388 20.2226 9.13363 19.6368 9.71941L12.9193 16.4369C12.3335 17.0227 11.3838 17.0227 10.798 16.4369L4.08045 9.71941C3.49467 9.13363 3.49467 8.18388 4.08045 7.59809Z"
Foreground="{DynamicResource NavigationMenuItemExpandIconForeground}"
IsVisible="{TemplateBinding IsTopLevelMenuItem,
Converter={x:Static BoolConverters.Not}}"
RenderTransform="rotate(-90deg)" />
</Grid>
</Border>
<Popup
Name="{x:Static u:NavigationMenuItem.PART_Popup}"
IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsPopupOpen,
Mode=TwoWay}"
Placement="RightEdgeAlignedTop"
PlacementTarget="PART_HeaderBorder"
WindowManagerAddShadowHint="False">
<Border
MinWidth="{DynamicResource MenuFlyoutMinWidth}"
MinHeight="{DynamicResource MenuFlyoutMinHeight}"
MaxWidth="{DynamicResource MenuFlyoutMaxWidth}"
MaxHeight="{DynamicResource MenuFlyoutMaxHeight}"
Margin="8"
Padding="{DynamicResource MenuFlyoutPadding}"
HorizontalAlignment="Stretch"
Background="{DynamicResource MenuFlyoutBackground}"
BorderBrush="{DynamicResource MenuFlyoutBorderBrush}"
BorderThickness="{DynamicResource MenuFlyoutBorderThickness}"
BoxShadow="{DynamicResource MenuFlyoutBorderBoxShadow}"
CornerRadius="{DynamicResource MenuFlyoutCornerRadius}">
<ScrollViewer Theme="{DynamicResource MenuScrollViewer}">
<ItemsPresenter
Name="PART_ItemsPresenter"
Grid.IsSharedSizeScope="True"
ItemsPanel="{TemplateBinding ItemsPanel}" />
</ScrollViewer>
</Border>
</Popup>
</Panel>
</ControlTemplate>
</Setter>
<Style Selector="^ /template/ Border#PART_HeaderBorder:pointerover">
<Setter Property="Border.Background" Value="{DynamicResource NavigationMenuItemPointeroverBackground}" />
</Style>
<Style Selector="^ /template/ Border#PART_HeaderBorder:pressed">
<Setter Property="Border.Background" Value="{DynamicResource NavigationMenuItemPressedBackground}" />
</Style>
</Style>
<Style Selector="^:not(:closed):collapsed /template/ ItemsPresenter#PART_ItemsPresenter">
<Setter Property="ItemsPresenter.Height" Value="0" /> <Setter Property="ItemsPresenter.Height" Value="0" />
<Setter Property="ItemsPresenter.Opacity" Value="0" /> <Setter Property="ItemsPresenter.Opacity" Value="0" />
</Style> </Style>

View File

@@ -4,20 +4,20 @@ using Avalonia.Data.Converters;
namespace Ursa.Themes.Semi.Converters; namespace Ursa.Themes.Semi.Converters;
public class NavigationMenuItemLevelToMarginConverter: IValueConverter public class NavigationMenuItemLevelToMarginConverter: IMultiValueConverter
{ {
public int Indent { get; set; } public int Indent { get; set; }
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
public object? Convert(IList<object?> 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((i-1) * Indent, 0, 0, 0);
} }
return new Thickness(); return new Thickness();
} }
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
} }

View File

@@ -134,7 +134,7 @@ public class NavigationMenu: HeaderedItemsControl
} }
} }
private void UpdateSelectionFromSelectedItem(object? o) internal void UpdateSelectionFromSelectedItem(object? o)
{ {
var children = this.ItemsPanelRoot?.Children; var children = this.ItemsPanelRoot?.Children;
if (children is not null) if (children is not null)

View File

@@ -15,18 +15,22 @@ using Avalonia.VisualTree;
namespace Ursa.Controls; 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 class NavigationMenuItem: HeaderedSelectingItemsControl
{ {
public const string PC_Closed = ":closed"; public const string PC_Closed = ":closed";
public const string PC_Selected = ":selected"; public const string PC_Selected = ":selected";
public const string PC_Highlighted= ":highlighted"; public const string PC_Highlighted= ":highlighted";
public const string PC_Collapsed = ":collapsed"; public const string PC_Collapsed = ":collapsed";
public const string PC_TopLevel = ":top-level";
public const string PART_Popup = "PART_Popup";
private NavigationMenu? _rootMenu; private NavigationMenu? _rootMenu;
private IDisposable? _ownerSubscription; private IDisposable? _ownerSubscription;
private IDisposable? _itemsBinding; private IDisposable? _itemsBinding;
private bool _isCollapsed; private bool _isCollapsed;
private Popup? _popup;
public static readonly StyledProperty<bool> IsClosedProperty = AvaloniaProperty.Register<NavigationMenuItem, bool>( public static readonly StyledProperty<bool> IsClosedProperty = AvaloniaProperty.Register<NavigationMenuItem, bool>(
nameof(IsClosed)); nameof(IsClosed));
@@ -55,11 +59,9 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
set => SetValue(IconTemplateProperty, value); set => SetValue(IconTemplateProperty, value);
} }
private int _level;
public static readonly DirectProperty<NavigationMenuItem, int> LevelProperty = AvaloniaProperty.RegisterDirect<NavigationMenuItem, int>( public static readonly DirectProperty<NavigationMenuItem, int> LevelProperty = AvaloniaProperty.RegisterDirect<NavigationMenuItem, int>(
nameof(Level), o => o.Level); nameof(Level), o => o.Level);
private int _level;
public int Level public int Level
{ {
get => _level; get => _level;
@@ -84,6 +86,24 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
set => SetValue(CommandParameterProperty, value); set => SetValue(CommandParameterProperty, value);
} }
public static readonly DirectProperty<NavigationMenuItem, bool> IsTopLevelMenuItemProperty = AvaloniaProperty.RegisterDirect<NavigationMenuItem, bool>(
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<bool> IsPopupOpenProperty = AvaloniaProperty.Register<NavigationMenuItem, bool>(
nameof(IsPopupOpen));
public bool IsPopupOpen
{
get => GetValue(IsPopupOpenProperty);
set => SetValue(IsPopupOpenProperty, value);
}
static NavigationMenuItem() static NavigationMenuItem()
{ {
IsClosedProperty.Changed.AddClassHandler<NavigationMenuItem>((o, e) => o.OnIsClosedChanged(e)); IsClosedProperty.Changed.AddClassHandler<NavigationMenuItem>((o, e) => o.OnIsClosedChanged(e));
@@ -96,13 +116,6 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
PseudoClasses.Set(PC_Closed, newValue); PseudoClasses.Set(PC_Closed, newValue);
} }
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
@@ -116,10 +129,19 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
SetCurrentValue(ItemContainerThemeProperty, _rootMenu.ItemContainerTheme); SetCurrentValue(ItemContainerThemeProperty, _rootMenu.ItemContainerTheme);
} }
if (_rootMenu is not null)
{
IsClosed = _rootMenu.IsClosed;
}
_rootMenu?.GetObservable(NavigationMenu.IsClosedProperty) _rootMenu?.GetObservable(NavigationMenu.IsClosedProperty)
.Subscribe(new AnonymousObserver<bool>(a => this.IsClosed = a)); .Subscribe(new AnonymousObserver<bool>(a => this.IsClosed = a));
_rootMenu?.UpdateSelectionFromSelectedItem(_rootMenu.SelectedItem);
_popup = e.NameScope.Find<Popup>(PART_Popup);
Level = CalculateDistanceFromLogicalParent<NavigationMenu>(this) - 1; Level = CalculateDistanceFromLogicalParent<NavigationMenu>(this) - 1;
bool isTopLevel = Level == 1;
IsTopLevelMenuItem = isTopLevel;
PseudoClasses.Set(PC_TopLevel, isTopLevel);
} }
private void GetRootMenu() private void GetRootMenu()
@@ -127,11 +149,10 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
_rootMenu = this.FindAncestorOfType<NavigationMenu>(); _rootMenu = this.FindAncestorOfType<NavigationMenu>();
if (_rootMenu is null) if (_rootMenu is null)
{ {
var popupRoot = TopLevel.GetTopLevel(this) as PopupRoot; var parents = this.FindLogicalAncestorOfType<NavigationMenu>();
if (popupRoot?.Parent is Popup popup) if (parents is not null)
{ {
Control? c = popup.PlacementTarget; _rootMenu = parents;
_rootMenu = c.FindAncestorOfType<NavigationMenu>();
} }
} }
} }
@@ -142,8 +163,7 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
// Leaf menu node, can be selected. // Leaf menu node, can be selected.
if (this.ItemCount == 0) if (this.ItemCount == 0)
{ {
var parents = this.GetSelfAndLogicalAncestors(); if (_rootMenu is not null )
if (_rootMenu is not null && parents.Contains(_rootMenu))
{ {
object? o = this.DataContext ?? this; object? o = this.DataContext ?? this;
_rootMenu.SelectedItem = o; _rootMenu.SelectedItem = o;
@@ -155,6 +175,10 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
{ {
_isCollapsed = !_isCollapsed; _isCollapsed = !_isCollapsed;
this.PseudoClasses.Set(PC_Collapsed, _isCollapsed); this.PseudoClasses.Set(PC_Collapsed, _isCollapsed);
if (_popup is not null)
{
_popup.IsOpen = !_popup.IsOpen;
}
} }
e.Handled = true; e.Handled = true;
Command?.Execute(CommandParameter); Command?.Execute(CommandParameter);
@@ -188,7 +212,7 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
if (propagateToParent) if (propagateToParent)
{ {
var parent = this.FindAncestorOfType<NavigationMenuItem>(); var parent = this.FindLogicalAncestorOfType<NavigationMenuItem>();
if (parent != null) if (parent != null)
{ {
parent.SetSelection(this, selected, true); parent.SetSelection(this, selected, true);