完成 Popup 菜单分割控件

This commit is contained in:
polarboy 2024-07-25 11:36:43 +08:00
parent 198a40617c
commit 44d61f86b5
17 changed files with 248 additions and 73 deletions

View File

@ -23,6 +23,9 @@
<PackageVersion Include="xunit.extensibility.execution" Version="2.8.0" /> <PackageVersion Include="xunit.extensibility.execution" Version="2.8.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.0" /> <PackageVersion Include="xunit.runner.visualstudio" Version="2.8.0" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.1.0" /> <PackageVersion Include="CommunityToolkit.Mvvm" Version="8.1.0" />
<!-- 开发支持 -->
<PackageVersion Include="Nlnet.Avalonia.DevTools" Version="1.0.1-beta.22" />
<!-- 源码生成 --> <!-- 源码生成 -->
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0-3.final" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0-3.final" />

View File

@ -24,6 +24,7 @@
<PackageReference Include="Avalonia.Themes.Fluent" /> <PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="Avalonia.Controls.DataGrid" /> <PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Nlnet.Avalonia.DevTools" />
<PackageReference Include="CommunityToolkit.Mvvm" /> <PackageReference Include="CommunityToolkit.Mvvm" />
</ItemGroup> </ItemGroup>

View File

@ -3,6 +3,7 @@ using AtomUI.Icon.AntDesign;
using Avalonia; using Avalonia;
using Avalonia.Dialogs; using Avalonia.Dialogs;
using Avalonia.Media; using Avalonia.Media;
using Nlnet.Avalonia.DevTools;
namespace AtomUI.Demo.Desktop; namespace AtomUI.Demo.Desktop;
@ -30,6 +31,7 @@ class Program
.UseManagedSystemDialogs() .UseManagedSystemDialogs()
.UsePlatformDetect() .UsePlatformDetect()
.UseAtomUI() .UseAtomUI()
.UseDevToolsForAvalonia()
.UseIconPackage<AntDesignIconPackage>(true) .UseIconPackage<AntDesignIconPackage>(true)
.With(new Win32PlatformOptions()) .With(new Win32PlatformOptions())
.LogToTrace(); .LogToTrace();

View File

@ -126,6 +126,8 @@ public class Alert : TemplatedControl, IControlCustomStyle
_customStyle.HandleTemplateApplied(e.NameScope); _customStyle.HandleTemplateApplied(e.NameScope);
} }
#region IControlCustomStyle
void IControlCustomStyle.HandleTemplateApplied(INameScope scope) void IControlCustomStyle.HandleTemplateApplied(INameScope scope)
{ {
BindUtils.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness, BindUtils.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness,
@ -133,8 +135,6 @@ public class Alert : TemplatedControl, IControlCustomStyle
new RenderScaleAwareThicknessConfigure(this)); new RenderScaleAwareThicknessConfigure(this));
SetupCloseButton(); SetupCloseButton();
} }
#region IControlCustomStyle
void IControlCustomStyle.HandlePropertyChangedForStyle(AvaloniaPropertyChangedEventArgs e) void IControlCustomStyle.HandlePropertyChangedForStyle(AvaloniaPropertyChangedEventArgs e)
{ {

View File

@ -118,15 +118,14 @@ namespace AtomUI.Styling
{ {
public static readonly TokenResourceKey MenuPopupBorderRadius = new TokenResourceKey("Menu.MenuPopupBorderRadius"); public static readonly TokenResourceKey MenuPopupBorderRadius = new TokenResourceKey("Menu.MenuPopupBorderRadius");
public static readonly TokenResourceKey MenuPopupBoxShadows = new TokenResourceKey("Menu.MenuPopupBoxShadows"); public static readonly TokenResourceKey MenuPopupBoxShadows = new TokenResourceKey("Menu.MenuPopupBoxShadows");
public static readonly TokenResourceKey MenuPopupContentPadding = new TokenResourceKey("Menu.MenuPopupContentPadding");
public static readonly TokenResourceKey MenuPopupMinWidth = new TokenResourceKey("Menu.MenuPopupMinWidth"); public static readonly TokenResourceKey MenuPopupMinWidth = new TokenResourceKey("Menu.MenuPopupMinWidth");
public static readonly TokenResourceKey MenuPopupMaxWidth = new TokenResourceKey("Menu.MenuPopupMaxWidth"); public static readonly TokenResourceKey MenuPopupMaxWidth = new TokenResourceKey("Menu.MenuPopupMaxWidth");
public static readonly TokenResourceKey MenuPopupMinHeight = new TokenResourceKey("Menu.MenuPopupMinHeight"); public static readonly TokenResourceKey MenuPopupMinHeight = new TokenResourceKey("Menu.MenuPopupMinHeight");
public static readonly TokenResourceKey MenuPopupMaxHeight = new TokenResourceKey("Menu.MenuPopupMaxHeight"); public static readonly TokenResourceKey MenuPopupMaxHeight = new TokenResourceKey("Menu.MenuPopupMaxHeight");
public static readonly TokenResourceKey MenuArrowSize = new TokenResourceKey("Menu.MenuArrowSize"); public static readonly TokenResourceKey MenuArrowSize = new TokenResourceKey("Menu.MenuArrowSize");
public static readonly TokenResourceKey MenuArrowOffset = new TokenResourceKey("Menu.MenuArrowOffset"); public static readonly TokenResourceKey MenuArrowOffset = new TokenResourceKey("Menu.MenuArrowOffset");
public static readonly TokenResourceKey MenuMargin = new TokenResourceKey("Menu.MenuMargin");
public static readonly TokenResourceKey MenuTearOffHeight = new TokenResourceKey("Menu.MenuTearOffHeight"); public static readonly TokenResourceKey MenuTearOffHeight = new TokenResourceKey("Menu.MenuTearOffHeight");
public static readonly TokenResourceKey MenuContentPadding = new TokenResourceKey("Menu.MenuContentPadding");
public static readonly TokenResourceKey MenuBgColor = new TokenResourceKey("Menu.MenuBgColor"); public static readonly TokenResourceKey MenuBgColor = new TokenResourceKey("Menu.MenuBgColor");
public static readonly TokenResourceKey ItemColor = new TokenResourceKey("Menu.ItemColor"); public static readonly TokenResourceKey ItemColor = new TokenResourceKey("Menu.ItemColor");
public static readonly TokenResourceKey ItemHoverColor = new TokenResourceKey("Menu.ItemHoverColor"); public static readonly TokenResourceKey ItemHoverColor = new TokenResourceKey("Menu.ItemHoverColor");
@ -159,6 +158,7 @@ namespace AtomUI.Styling
public static readonly TokenResourceKey TopLevelItemLineHeightLG = new TokenResourceKey("Menu.TopLevelItemLineHeightLG"); public static readonly TokenResourceKey TopLevelItemLineHeightLG = new TokenResourceKey("Menu.TopLevelItemLineHeightLG");
public static readonly TokenResourceKey TopLevelItemLineHeightSM = new TokenResourceKey("Menu.TopLevelItemLineHeightSM"); public static readonly TokenResourceKey TopLevelItemLineHeightSM = new TokenResourceKey("Menu.TopLevelItemLineHeightSM");
public static readonly TokenResourceKey TopLevelItemPopupMarginToAnchor = new TokenResourceKey("Menu.TopLevelItemPopupMarginToAnchor"); public static readonly TokenResourceKey TopLevelItemPopupMarginToAnchor = new TokenResourceKey("Menu.TopLevelItemPopupMarginToAnchor");
public static readonly TokenResourceKey SeparatorItemHeight = new TokenResourceKey("Menu.SeparatorItemHeight");
} }
public static class OptionButtonResourceKey public static class OptionButtonResourceKey

View File

@ -1,12 +1,28 @@
using AtomUI.Styling; using AtomUI.Styling;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Media;
namespace AtomUI.Controls; namespace AtomUI.Controls;
[ControlThemeProvider] [ControlThemeProvider]
public class MenuItemTheme : ControlTheme internal class MenuItemTheme : ControlTheme
{ {
public MenuItemTheme() public MenuItemTheme()
: base(typeof(MenuItem)) : base(typeof(MenuItem))
{ {
} }
protected override IControlTemplate? BuildControlTemplate()
{
return new FuncControlTemplate<MenuItem>((item, scope) =>
{
return new Border()
{
Height = 30,
Background = new SolidColorBrush(Colors.Chocolate),
};
});
}
} }

View File

@ -1,8 +1,42 @@
namespace AtomUI.Controls; using AtomUI.Styling;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
namespace AtomUI.Controls;
using AvaloniaScrollViewer = Avalonia.Controls.ScrollViewer; using AvaloniaScrollViewer = Avalonia.Controls.ScrollViewer;
public class MenuScrollViewer : AvaloniaScrollViewer [TemplatePart(MenuScrollViewerTheme.ScrollDownButtonPart, typeof(IconButton))]
[TemplatePart(MenuScrollViewerTheme.ScrollUpButtonPart, typeof(IconButton))]
[TemplatePart(MenuScrollViewerTheme.ScrollViewContentPart, typeof(ScrollContentPresenter))]
public class MenuScrollViewer : AvaloniaScrollViewer, IControlCustomStyle
{ {
private readonly IControlCustomStyle _customStyle;
private IconButton? _scrollUpButton;
private IconButton? _scrollDownButton;
private ScrollContentPresenter? _scrollViewContent;
public MenuScrollViewer()
{
_customStyle = this;
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_customStyle.HandleTemplateApplied(e.NameScope);
}
#region IControlCustomStyle
void IControlCustomStyle.HandleTemplateApplied(INameScope scope)
{
_scrollUpButton = scope.Find<IconButton>(MenuScrollViewerTheme.ScrollUpButtonPart);
_scrollDownButton = scope.Find<IconButton>(MenuScrollViewerTheme.ScrollDownButtonPart);
_scrollViewContent = scope.Find<ScrollContentPresenter>(MenuScrollViewerTheme.ScrollViewContentPart);
}
#endregion
} }

View File

@ -1,12 +1,82 @@
using AtomUI.Styling; using AtomUI.Styling;
using AtomUI.Utils;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Input.GestureRecognizers;
namespace AtomUI.Controls; namespace AtomUI.Controls;
[ControlThemeProvider] [ControlThemeProvider]
public class MenuScrollViewerTheme : ControlTheme internal class MenuScrollViewerTheme : ControlTheme
{ {
public const string ScrollUpButtonPart = "Part_ScrollUpButton";
public const string ScrollDownButtonPart = "Part_ScrollDownButton";
public const string ScrollViewContentPart = "PART_ContentPresenter";
public const string MainContainerPart = "Part_MainContainer";
public MenuScrollViewerTheme() public MenuScrollViewerTheme()
: base(typeof(MenuScrollViewer)) : base(typeof(MenuScrollViewer)) { }
protected override IControlTemplate BuildControlTemplate()
{ {
return new FuncControlTemplate<MenuScrollViewer>((viewer, scope) =>
{
var dockPanel = new DockPanel();
var scrollUpButton = new IconButton()
{
Name = ScrollUpButtonPart,
};
DockPanel.SetDock(scrollUpButton, Dock.Top);
var scrollDownButton = new IconButton()
{
Name = ScrollDownButtonPart
};
DockPanel.SetDock(scrollDownButton, Dock.Bottom);
var scrollViewContent = CreateScrollContentPresenter(viewer);
dockPanel.Children.Add(scrollUpButton);
dockPanel.Children.Add(scrollDownButton);
dockPanel.Children.Add(scrollViewContent);
scrollUpButton.RegisterInNameScope(scope);
scrollDownButton.RegisterInNameScope(scope);
scrollViewContent.RegisterInNameScope(scope);
return dockPanel;
});
}
private ScrollContentPresenter CreateScrollContentPresenter(MenuScrollViewer viewer)
{
var scrollViewContent = new ScrollContentPresenter()
{
Name = ScrollViewContentPart
};
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.MarginProperty,
MenuScrollViewer.PaddingProperty);
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.HorizontalSnapPointsAlignmentProperty,
MenuScrollViewer.HorizontalSnapPointsAlignmentProperty);
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.HorizontalSnapPointsTypeProperty,
MenuScrollViewer.HorizontalSnapPointsTypeProperty);
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.VerticalSnapPointsAlignmentProperty,
MenuScrollViewer.VerticalSnapPointsAlignmentProperty);
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.VerticalSnapPointsTypeProperty,
MenuScrollViewer.VerticalSnapPointsTypeProperty);
var scrollGestureRecognizer = new ScrollGestureRecognizer();
BindUtils.RelayBind(scrollViewContent, ScrollContentPresenter.CanHorizontallyScrollProperty, scrollGestureRecognizer,
ScrollGestureRecognizer.CanHorizontallyScrollProperty);
BindUtils.RelayBind(scrollViewContent, ScrollContentPresenter.CanVerticallyScrollProperty, scrollGestureRecognizer,
ScrollGestureRecognizer.CanVerticallyScrollProperty);
CreateTemplateParentBinding(scrollGestureRecognizer, ScrollGestureRecognizer.IsScrollInertiaEnabledProperty,
MenuScrollViewer.IsScrollInertiaEnabledProperty);
scrollViewContent.GestureRecognizers.Add(scrollGestureRecognizer);
return scrollViewContent;
}
protected override void BuildStyles()
{
} }
} }

View File

@ -1,6 +1,16 @@
namespace AtomUI.Controls; using Avalonia;
using Avalonia.Media;
public class MenuSeparator namespace AtomUI.Controls;
using AvaloniaSeparator = Avalonia.Controls.Separator;
public class MenuSeparator : AvaloniaSeparator
{ {
public override void Render(DrawingContext context)
{
var linePen = new Pen(BorderBrush);
var offsetY = Bounds.Height / 2.0;
context.DrawLine(linePen, new Point(0, offsetY), new Point(Bounds.Right, offsetY));
}
} }

View File

@ -1,4 +1,5 @@
using AtomUI.Styling; using AtomUI.Styling;
using Avalonia.Styling;
namespace AtomUI.Controls; namespace AtomUI.Controls;
@ -9,4 +10,12 @@ public class MenuSeparatorTheme : ControlTheme
: base(typeof(MenuSeparator)) : base(typeof(MenuSeparator))
{ {
} }
protected override void BuildStyles()
{
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(MenuSeparator.MinHeightProperty, MenuResourceKey.SeparatorItemHeight);
commonStyle.Add(MenuSeparator.BorderBrushProperty, GlobalResourceKey.ColorBorder);
Add(commonStyle);
}
} }

View File

@ -53,8 +53,7 @@ public class MenuTheme : ControlTheme
protected override void BuildStyles() protected override void BuildStyles()
{ {
var commonStyle = new Style(selector => selector.Nesting()); var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(Menu.BackgroundProperty, GlobalResourceKey.ColorBgContainer); commonStyle.Add(Menu.BackgroundProperty, MenuResourceKey.MenuBgColor);
commonStyle.Add(Menu.PaddingProperty, new Thickness(0));
commonStyle.Add(Menu.BorderBrushProperty, GlobalResourceKey.ColorBorder); commonStyle.Add(Menu.BorderBrushProperty, GlobalResourceKey.ColorBorder);
var largeSizeType = new Style(selector => selector.Nesting().PropertyEquals(Menu.SizeTypeProperty, SizeType.Large)); var largeSizeType = new Style(selector => selector.Nesting().PropertyEquals(Menu.SizeTypeProperty, SizeType.Large));
largeSizeType.Add(Menu.MinHeightProperty, GlobalResourceKey.ControlHeightLG); largeSizeType.Add(Menu.MinHeightProperty, GlobalResourceKey.ControlHeightLG);

View File

@ -25,6 +25,11 @@ internal class MenuToken : AbstractControlDesignToken
/// </summary> /// </summary>
public BoxShadows MenuPopupBoxShadows { get; set; } public BoxShadows MenuPopupBoxShadows { get; set; }
/// <summary>
/// 菜单内容边距
/// </summary>
public Thickness MenuPopupContentPadding { get; set; }
/// <summary> /// <summary>
/// 菜单 Popup 最小宽度 /// 菜单 Popup 最小宽度
/// </summary> /// </summary>
@ -55,22 +60,12 @@ internal class MenuToken : AbstractControlDesignToken
/// </summary> /// </summary>
public double MenuArrowOffset { get; set; } public double MenuArrowOffset { get; set; }
/// <summary>
/// 菜单间距
/// </summary>
public Thickness MenuMargin { get; set; }
/// <summary> /// <summary>
/// 分离菜单项的高度,这个用于菜单中快捷功能的图标显示 /// 分离菜单项的高度,这个用于菜单中快捷功能的图标显示
/// TODO 暂时还没实现,但是最终会实现 /// TODO 暂时还没实现,但是最终会实现
/// </summary> /// </summary>
public double MenuTearOffHeight { get; set; } public double MenuTearOffHeight { get; set; }
/// <summary>
/// 菜单内容边距
/// </summary>
public double MenuContentPadding { get; set; }
/// <summary> /// <summary>
/// 弹出框背景色 /// 弹出框背景色
/// </summary> /// </summary>
@ -231,7 +226,11 @@ internal class MenuToken : AbstractControlDesignToken
/// </summary> /// </summary>
public double TopLevelItemPopupMarginToAnchor { get; set; } public double TopLevelItemPopupMarginToAnchor { get; set; }
/// <summary>
/// 菜单分割项的高度
/// </summary>
public double SeparatorItemHeight { get; set; }
internal override void CalculateFromAlias() internal override void CalculateFromAlias()
{ {
base.CalculateFromAlias(); base.CalculateFromAlias();
@ -271,13 +270,6 @@ internal class MenuToken : AbstractControlDesignToken
ItemPaddingInline = new Thickness(padding); ItemPaddingInline = new Thickness(padding);
ItemIconSize = fontSize; ItemIconSize = fontSize;
ItemIconMarginInlineEnd = controlHeightSM - fontSize; ItemIconMarginInlineEnd = controlHeightSM - fontSize;
MenuArrowSize = (fontSize / 7.0) * 5.0;
MenuArrowOffset = MenuArrowSize * 0.5;
MenuTearOffHeight = ItemHeight * 1.2; // 暂时这么定义吧
MenuContentPadding = _globalToken.PaddingXXS / 2; // 先默认一个最小的内容间距
MenuMargin = new Thickness(1);
MenuPopupBoxShadows = _globalToken.BoxShadowsSecondary;
TopLevelItemColor = colorNeutralToken.ColorText; TopLevelItemColor = colorNeutralToken.ColorText;
TopLevelItemSelectedColor = colorNeutralToken.ColorTextSecondary; TopLevelItemSelectedColor = colorNeutralToken.ColorTextSecondary;
@ -322,5 +314,13 @@ internal class MenuToken : AbstractControlDesignToken
MenuPopupMinHeight = ItemHeight * 3; MenuPopupMinHeight = ItemHeight * 3;
MenuPopupMaxHeight = ItemHeight * 30; MenuPopupMaxHeight = ItemHeight * 30;
SeparatorItemHeight = _globalToken.SeedToken.LineWidth * 5; // 上下两像素,留一像素给自己
MenuArrowSize = (fontSize / 7.0) * 5.0;
MenuArrowOffset = MenuArrowSize * 0.5;
MenuTearOffHeight = ItemHeight * 1.2; // 暂时这么定义吧
MenuPopupContentPadding = new Thickness(_globalToken.PaddingXXS, MenuPopupBorderRadius.TopLeft / 2);
MenuPopupBoxShadows = _globalToken.BoxShadowsSecondary;
} }
} }

View File

@ -1,13 +1,11 @@
using AtomUI.Styling; using AtomUI.Styling;
using AtomUI.Utils; using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
namespace AtomUI.Controls; namespace AtomUI.Controls;
@ -19,6 +17,7 @@ public class TopLevelMenuItemTheme : ControlTheme
public const string PopupPart = "PART_Popup"; public const string PopupPart = "PART_Popup";
public const string HeaderPresenterPart = "PART_HeaderPresenter"; public const string HeaderPresenterPart = "PART_HeaderPresenter";
public const string ItemsPresenterPart = "PART_ItemsPresenter";
public TopLevelMenuItemTheme() : base(typeof(MenuItem)) {} public TopLevelMenuItemTheme() : base(typeof(MenuItem)) {}
@ -59,6 +58,7 @@ public class TopLevelMenuItemTheme : ControlTheme
private Popup CreateMenuPopup(MenuItem menuItem) private Popup CreateMenuPopup(MenuItem menuItem)
{ {
var popup = new Popup() var popup = new Popup()
{ {
Name = PopupPart, Name = PopupPart,
@ -66,8 +66,31 @@ public class TopLevelMenuItemTheme : ControlTheme
IsLightDismissEnabled = true, IsLightDismissEnabled = true,
Placement = PlacementMode.BottomEdgeAlignedLeft, Placement = PlacementMode.BottomEdgeAlignedLeft,
OverlayInputPassThroughElement = menuItem, OverlayInputPassThroughElement = menuItem,
Child = new Border()
}; };
var border = new Border();
BindUtils.CreateTokenBinding(border, Border.BackgroundProperty, GlobalResourceKey.ColorBgContainer);
BindUtils.CreateTokenBinding(border, Border.CornerRadiusProperty, MenuResourceKey.MenuPopupBorderRadius);
BindUtils.CreateTokenBinding(border, Border.MinWidthProperty, MenuResourceKey.MenuPopupMinWidth);
BindUtils.CreateTokenBinding(border, Border.MaxWidthProperty, MenuResourceKey.MenuPopupMaxWidth);
BindUtils.CreateTokenBinding(border, Border.MinHeightProperty, MenuResourceKey.MenuPopupMinHeight);
BindUtils.CreateTokenBinding(border, Border.MaxHeightProperty, MenuResourceKey.MenuPopupMaxHeight);
BindUtils.CreateTokenBinding(border, Border.PaddingProperty, MenuResourceKey.MenuPopupContentPadding);
var scrollViewer = new MenuScrollViewer();
var itemsPresenter = new ItemsPresenter()
{
Name = ItemsPresenterPart,
};
CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, MenuItem.ItemsPanelProperty);
Grid.SetIsSharedSizeScope(itemsPresenter, true);
scrollViewer.Content = itemsPresenter;
border.Child = scrollViewer;
popup.Child = border;
BindUtils.CreateTokenBinding(popup, Popup.MarginToAnchorProperty, MenuResourceKey.TopLevelItemPopupMarginToAnchor);
BindUtils.CreateTokenBinding(popup, Popup.MaskShadowsProperty, MenuResourceKey.MenuPopupBoxShadows);
CreateTemplateParentBinding(popup, Popup.IsOpenProperty, MenuItem.IsSubMenuOpenProperty, BindingMode.TwoWay); CreateTemplateParentBinding(popup, Popup.IsOpenProperty, MenuItem.IsSubMenuOpenProperty, BindingMode.TwoWay);
return popup; return popup;
} }
@ -77,7 +100,6 @@ public class TopLevelMenuItemTheme : ControlTheme
BuildCommonStyle(); BuildCommonStyle();
BuildSizeTypeStyle(); BuildSizeTypeStyle();
BuildDisabledStyle(); BuildDisabledStyle();
BuildPopupStyle();
} }
private void BuildCommonStyle() private void BuildCommonStyle()
@ -132,25 +154,6 @@ public class TopLevelMenuItemTheme : ControlTheme
Add(smallSizeStyle); Add(smallSizeStyle);
} }
private void BuildPopupStyle()
{
var popupStyle = new Style(selector => selector.Nesting().Template().OfType<Popup>());
popupStyle.Add(Popup.MarginToAnchorProperty, MenuResourceKey.TopLevelItemPopupMarginToAnchor);
popupStyle.Add(Popup.MaskShadowsProperty, MenuResourceKey.MenuPopupBoxShadows);
Add(popupStyle);
var borderStyle = new Style(selector => selector.Nesting().Template().OfType<Popup>().Child().OfType<Border>());
borderStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainer);
borderStyle.Add(Border.CornerRadiusProperty, MenuResourceKey.MenuPopupBorderRadius);
borderStyle.Add(Border.MinWidthProperty, MenuResourceKey.MenuPopupMinWidth);
borderStyle.Add(Border.MaxWidthProperty, MenuResourceKey.MenuPopupMaxWidth);
borderStyle.Add(Border.MinHeightProperty, MenuResourceKey.MenuPopupMinHeight);
borderStyle.Add(Border.MaxHeightProperty, MenuResourceKey.MenuPopupMaxHeight);
Add(borderStyle);
}
private void BuildDisabledStyle() private void BuildDisabledStyle()
{ {
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled)); var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));

View File

@ -7,14 +7,14 @@ using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace AtomUI.Controls; namespace AtomUI.Controls;
using AvaloniaSeparator = Avalonia.Controls.Separator;
public enum SeparatorTitlePosition public enum SeparatorTitlePosition
{ {
Left, Left,
@ -22,7 +22,7 @@ public enum SeparatorTitlePosition
Center Center
} }
public partial class Separator : TemplatedControl, IControlCustomStyle public class Separator : AvaloniaSeparator, IControlCustomStyle
{ {
#region #region

View File

@ -10,7 +10,7 @@ namespace AtomUI.Controls;
[ControlThemeProvider] [ControlThemeProvider]
internal class SeparatorTheme : ControlTheme internal class SeparatorTheme : ControlTheme
{ {
public const string TitlePart = "PART_CloseBtn"; public const string TitlePart = "PART_Title";
public SeparatorTheme() public SeparatorTheme()
: base(typeof(Separator)) { } : base(typeof(Separator)) { }

View File

@ -1,5 +1,4 @@
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
@ -11,9 +10,9 @@ using AvaloniaControlTheme = Avalonia.Styling.ControlTheme;
public class ControlTheme : AvaloniaControlTheme public class ControlTheme : AvaloniaControlTheme
{ {
public ControlTheme() {} public ControlTheme() { }
public ControlTheme(Type targetType) : base(targetType) {} public ControlTheme(Type targetType) : base(targetType) { }
public void Build() public void Build()
{ {
NotifyPreBuild(); NotifyPreBuild();
@ -22,6 +21,7 @@ public class ControlTheme : AvaloniaControlTheme
if (template is not null) { if (template is not null) {
Add(new Setter(TemplatedControl.TemplateProperty, template)); Add(new Setter(TemplatedControl.TemplateProperty, template));
} }
NotifyBuildCompleted(); NotifyBuildCompleted();
} }
@ -30,24 +30,52 @@ public class ControlTheme : AvaloniaControlTheme
return default; return default;
} }
protected virtual IControlTemplate? BuildControlTemplate() { return default; } protected virtual IControlTemplate? BuildControlTemplate()
protected virtual void BuildStyles() {} {
protected virtual void NotifyPreBuild() {} return default;
protected virtual void NotifyBuildCompleted() {} }
protected static IDisposable CreateTemplateParentBinding(Control control, AvaloniaProperty property, string templateParentPath, protected virtual void BuildStyles() { }
protected virtual void NotifyPreBuild() { }
protected virtual void NotifyBuildCompleted() { }
protected static IDisposable CreateTemplateParentBinding(AvaloniaObject target, AvaloniaProperty property,
string templateParentPath,
BindingMode mode = BindingMode.Default) BindingMode mode = BindingMode.Default)
{ {
return control.Bind(property, new Binding(templateParentPath) return target.Bind(property, new Binding(templateParentPath)
{ {
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Mode = mode Mode = mode
}); });
} }
protected static IDisposable CreateTemplateParentBinding(Control control, AvaloniaProperty property, AvaloniaProperty templateParentProperty, protected static IDisposable CreateTemplateParentBinding<T>(AvaloniaObject target, StyledProperty<T> property,
string templateParentPath,
BindingMode mode = BindingMode.Default)
{
return target.Bind(property, new Binding(templateParentPath)
{
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Mode = mode
});
}
protected static IDisposable CreateTemplateParentBinding<T>(AvaloniaObject target, DirectPropertyBase<T> property,
string templateParentPath,
BindingMode mode = BindingMode.Default)
{
return target.Bind(property, new Binding(templateParentPath)
{
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Mode = mode
});
}
protected static IDisposable CreateTemplateParentBinding(AvaloniaObject target, AvaloniaProperty property,
AvaloniaProperty templateParentProperty,
BindingMode mode = BindingMode.Default) BindingMode mode = BindingMode.Default)
{ {
return CreateTemplateParentBinding(control, property, templateParentProperty.Name, mode); return CreateTemplateParentBinding(target, property, templateParentProperty.Name, mode);
} }
} }

View File

@ -11,7 +11,7 @@ public static class BindUtils
{ {
public static IDisposable RelayBind(AvaloniaObject source, AvaloniaProperty sourceProperty, AvaloniaObject target, public static IDisposable RelayBind(AvaloniaObject source, AvaloniaProperty sourceProperty, AvaloniaObject target,
AvaloniaProperty? targetProperty = null, AvaloniaProperty? targetProperty = null,
BindingMode mode = BindingMode.OneWay) BindingMode mode = BindingMode.Default)
{ {
targetProperty ??= sourceProperty; targetProperty ??= sourceProperty;
var registry = AvaloniaPropertyRegistry.Instance; var registry = AvaloniaPropertyRegistry.Instance;