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"
Grid.Column="1"
MinWidth="24"
Margin="{TemplateBinding Level,
Converter={StaticResource MarginConverter}}"
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
Name="PART_HeaderPresenter"
Grid.Column="2"
@@ -170,7 +175,7 @@
<Setter Property="u:NavigationMenuItem.Background" Value="{DynamicResource NavigationMenuItemSelectedBackground}" />
</Style>
<Style Selector="^:closed">
<Style Selector="^:empty:closed:top-level">
<Setter Property="Template">
<ControlTemplate TargetType="u:NavigationMenuItem">
<Border
@@ -179,15 +184,16 @@
Padding="8"
HorizontalAlignment="Center"
Background="{DynamicResource NavigationMenuItemDefaultBackground}"
CornerRadius="6">
CornerRadius="6"
ToolTip.ShowDelay="0"
ToolTip.Tip="{TemplateBinding Header}">
<ContentPresenter
Name="PART_IconPresenter"
Grid.Column="1"
MinWidth="24"
Margin="0"
Content="{TemplateBinding Icon}"
ContentTemplate="{TemplateBinding IconTemplate}"
ToolTip.ShowDelay="0"
ToolTip.Tip="{TemplateBinding Header}" />
ContentTemplate="{TemplateBinding IconTemplate}" />
</Border>
</ControlTemplate>
</Setter>
@@ -199,7 +205,87 @@
</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.Opacity" Value="0" />
</Style>

View File

@@ -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<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();
}
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;
if (children is not null)

View File

@@ -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<bool> IsClosedProperty = AvaloniaProperty.Register<NavigationMenuItem, bool>(
nameof(IsClosed));
@@ -54,18 +58,16 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
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);
private int _level;
public int Level
{
get => _level;
private set => SetAndRaise(LevelProperty, ref _level, value);
}
public static readonly StyledProperty<ICommand> CommandProperty = AvaloniaProperty.Register<NavigationMenuItem, ICommand>(
nameof(Command));
@@ -84,6 +86,24 @@ public class NavigationMenuItem: HeaderedSelectingItemsControl
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()
{
IsClosedProperty.Changed.AddClassHandler<NavigationMenuItem>((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<bool>(a => this.IsClosed = a));
_rootMenu?.UpdateSelectionFromSelectedItem(_rootMenu.SelectedItem);
_popup = e.NameScope.Find<Popup>(PART_Popup);
Level = CalculateDistanceFromLogicalParent<NavigationMenu>(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<NavigationMenu>();
if (_rootMenu is null)
{
var popupRoot = TopLevel.GetTopLevel(this) as PopupRoot;
if (popupRoot?.Parent is Popup popup)
var parents = this.FindLogicalAncestorOfType<NavigationMenu>();
if (parents is not null)
{
Control? c = popup.PlacementTarget;
_rootMenu = c.FindAncestorOfType<NavigationMenu>();
_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<NavigationMenuItem>();
var parent = this.FindLogicalAncestorOfType<NavigationMenuItem>();
if (parent != null)
{
parent.SetSelection(this, selected, true);