From 9d219301f2e486b7c9a42e5733d1833b3f8a1a55 Mon Sep 17 00:00:00 2001 From: polarboy Date: Thu, 19 Sep 2024 12:53:14 +0800 Subject: [PATCH] Refactor ArrowDecoratedBox --- AtomUI.sln.DotSettings | 1 + .../ArrowDecoratedBox/ArrowDecoratedBox.cs | 139 ++++++------------ .../ArrowDecoratedBoxTheme.cs | 84 +++++++++-- .../ArrowDecoratedBoxToken.cs | 10 +- src/AtomUI.Controls/Flyouts/Flyout.cs | 13 +- .../Flyouts/MenuFlyoutPresenter.cs | 2 +- .../TokenResourceConst.g.cs | 1 + src/AtomUI.Controls/Popup/Popup.cs | 31 ++-- 8 files changed, 148 insertions(+), 133 deletions(-) diff --git a/AtomUI.sln.DotSettings b/AtomUI.sln.DotSettings index 0a9b71f..e039917 100644 --- a/AtomUI.sln.DotSettings +++ b/AtomUI.sln.DotSettings @@ -23,4 +23,5 @@ True True True + True True \ No newline at end of file diff --git a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs index 10f8e59..01d27af 100644 --- a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs +++ b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs @@ -117,6 +117,9 @@ public class ArrowDecoratedBox : ContentControl, internal static readonly StyledProperty ArrowSizeProperty = AvaloniaProperty.Register(nameof(ArrowSize)); + internal static readonly StyledProperty ArrowDirectionProperty + = AvaloniaProperty.Register(nameof(ArrowDirection)); + /// /// 箭头的大小 /// @@ -125,7 +128,13 @@ public class ArrowDecoratedBox : ContentControl, get => GetValue(ArrowSizeProperty); set => SetValue(ArrowSizeProperty, value); } - + + internal Direction ArrowDirection + { + get => GetValue(ArrowDirectionProperty); + set => SetValue(ArrowDirectionProperty, value); + } + #endregion // 指针最顶点位置 @@ -133,13 +142,19 @@ public class ArrowDecoratedBox : ContentControl, private (double, double) _arrowVertexPoint; internal (double, double) ArrowVertexPoint => GetArrowVertexPoint(); private Geometry? _arrowGeometry; - private Rect _contentRect; private Rect _arrowRect; private bool _needGenerateArrowVertexPoint = true; + private Border? _contentDecorator; static ArrowDecoratedBox() { - AffectsMeasure(ArrowPositionProperty, IsShowArrowProperty); + AffectsMeasure(IsShowArrowProperty); + } + + public ArrowDecoratedBox() + { + IsShowArrow = true; + ArrowPosition = ArrowPosition.BottomEdgeAlignedLeft; } public static Direction GetDirection(ArrowPosition arrowPosition) @@ -179,17 +194,19 @@ public class ArrowDecoratedBox : ContentControl, public Rect GetMaskBounds() { - return GetContentRect(DesiredSize).Deflate(0.5); + if (_contentDecorator is not null) + { + return _contentDecorator.Bounds; + } + + return Bounds; } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - HandleTemplateApplied(e.NameScope); - } - - private void HandleTemplateApplied(INameScope scope) - { + _contentDecorator = e.NameScope.Get(ArrowDecoratedBoxTheme.ContentDecoratorPart); if (IsShowArrow) { BuildGeometry(true); @@ -219,14 +236,22 @@ public class ArrowDecoratedBox : ContentControl, { // 当开启的时候,但是还没有加入的渲染树,这个时候我们取不到 Token 需要在取值的时候重新生成一下 _needGenerateArrowVertexPoint = true; + } if (VisualRoot is not null) { BuildGeometry(true); _arrowRect = GetArrowRect(DesiredSize); + + InvalidateArrange(); } } + + if (e.Property == ArrowPositionProperty) + { + ArrowDirection = GetDirection(ArrowPosition); + } } private void BuildGeometry(bool force = false) @@ -241,9 +266,10 @@ public class ArrowDecoratedBox : ContentControl, { if (IsShowArrow) { + var direction = GetDirection(ArrowPosition); var matrix = Matrix.CreateTranslation(-ArrowSize / 2, -ArrowSize / 2); - + if (direction == Direction.Right) { matrix *= Matrix.CreateRotation(MathUtils.Deg2Rad(90)); @@ -263,97 +289,12 @@ public class ArrowDecoratedBox : ContentControl, matrix *= Matrix.CreateRotation(MathUtils.Deg2Rad(180)); matrix *= Matrix.CreateTranslation(ArrowSize / 2, ArrowSize / 2); } - matrix *= Matrix.CreateTranslation(_arrowRect.X, _arrowRect.Y); _arrowGeometry!.Transform = new MatrixTransform(matrix); context.DrawGeometry(Background, null, _arrowGeometry); } } - - protected override Size MeasureOverride(Size availableSize) - { - var size = base.MeasureOverride(availableSize); - var targetWidth = size.Width; - var targetHeight = size.Height; - targetHeight = Math.Max(MinHeight, targetHeight); - - if (IsShowArrow) - { - BuildGeometry(); - var realArrowSize = Math.Min(_arrowGeometry!.Bounds.Size.Height, _arrowGeometry!.Bounds.Size.Width); - var direction = GetDirection(ArrowPosition); - if (direction == Direction.Left || direction == Direction.Right) - { - targetWidth += realArrowSize; - } - else - { - targetHeight += realArrowSize; - } - } - - var targetSize = new Size(targetWidth, targetHeight); - _arrowRect = GetArrowRect(targetSize); - return targetSize; - } - - protected override Size ArrangeOverride(Size finalSize) - { - var visualChildren = VisualChildren; - var visualCount = visualChildren.Count; - _contentRect = GetContentRect(finalSize); - for (var i = 0; i < visualCount; ++i) - { - var child = visualChildren[i]; - if (child is Layoutable layoutable) - { - layoutable.Arrange(_contentRect); - } - } - - return finalSize; - } - - internal Rect GetContentRect(Size finalSize) - { - var offsetX = 0d; - var offsetY = 0d; - var targetWidth = finalSize.Width; - var targetHeight = finalSize.Height; - if (IsShowArrow) - { - var arrowSize = Math.Min(_arrowGeometry!.Bounds.Size.Height, _arrowGeometry!.Bounds.Size.Width) + 0.5; - var direction = GetDirection(ArrowPosition); - if (direction == Direction.Left || direction == Direction.Right) - { - targetWidth -= arrowSize; - } - else - { - targetHeight -= arrowSize; - } - - if (direction == Direction.Right) - { - offsetX = 0.5; - } - else if (direction == Direction.Bottom) - { - offsetY = 0.5; - } - else if (direction == Direction.Top) - { - offsetY = arrowSize - 0.5; - } - else - { - offsetX = arrowSize - 0.5; - } - } - - return new Rect(offsetX, offsetY, targetWidth, targetHeight); - } - + private Rect GetArrowRect(Size finalSize) { var offsetX = 0d; @@ -371,6 +312,7 @@ public class ArrowDecoratedBox : ContentControl, position == ArrowPosition.LeftEdgeAlignedTop || position == ArrowPosition.LeftEdgeAlignedBottom) { + offsetX = 0.5d; targetWidth = minValue; targetHeight = maxValue; if (position == ArrowPosition.Left) @@ -404,6 +346,7 @@ public class ArrowDecoratedBox : ContentControl, position == ArrowPosition.TopEdgeAlignedLeft || position == ArrowPosition.TopEdgeAlignedRight) { + offsetY = 0.5d; if (position == ArrowPosition.TopEdgeAlignedLeft) { offsetX = maxValue; @@ -424,7 +367,7 @@ public class ArrowDecoratedBox : ContentControl, position == ArrowPosition.RightEdgeAlignedTop || position == ArrowPosition.RightEdgeAlignedBottom) { - offsetX = finalSize.Width - minValue; + offsetX = finalSize.Width - minValue - 1.0d; if (position == ArrowPosition.Right) { offsetY = (finalSize.Height - maxValue) / 2; @@ -457,7 +400,7 @@ public class ArrowDecoratedBox : ContentControl, } else { - offsetY = finalSize.Height - minValue; + offsetY = finalSize.Height - minValue - 1.0d; targetWidth = maxValue; targetHeight = minValue; if (position == ArrowPosition.BottomEdgeAlignedLeft) diff --git a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxTheme.cs b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxTheme.cs index 4b7b038..21cc132 100644 --- a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxTheme.cs +++ b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxTheme.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Layout; +using Avalonia.Media; using Avalonia.Styling; namespace AtomUI.Controls; @@ -13,8 +14,10 @@ namespace AtomUI.Controls; [ControlThemeProvider] internal class ArrowDecoratedBoxTheme : BaseControlTheme { - public const string DecoratorPart = "PART_Decorator"; + public const string ContentDecoratorPart = "PART_ContentDecorator"; + public const string ContentLayoutPart = "PART_ContentLayout"; public const string ContentPresenterPart = "PART_ContentPresenter"; + public const string ArrowContentPart = "PART_ArrowContent"; public ArrowDecoratedBoxTheme() : this(typeof(ArrowDecoratedBox)) { @@ -28,32 +31,48 @@ internal class ArrowDecoratedBoxTheme : BaseControlTheme { return new FuncControlTemplate((box, scope) => { - var decorator = new Border + var contentLayout = new DockPanel() { - Name = DecoratorPart, - Margin = new Thickness(0) + Name = ContentLayoutPart, + LastChildFill = true }; - decorator.RegisterInNameScope(scope); - - decorator.Child = BuildContent(scope); - - CreateTemplateParentBinding(decorator, Border.BackgroundSizingProperty, + var arrowContent = new Control() + { + Name = ArrowContentPart + }; + CreateTemplateParentBinding(arrowContent, Border.IsVisibleProperty, + ArrowDecoratedBox.IsShowArrowProperty); + contentLayout.Children.Add(arrowContent); + arrowContent.RegisterInNameScope(scope); + + var content = BuildContent(scope); + var contentDecorator = new Border + { + Name = ContentDecoratorPart, + Margin = new Thickness(0) + }; + contentDecorator.RegisterInNameScope(scope); +; CreateTemplateParentBinding(contentDecorator, Border.BackgroundSizingProperty, TemplatedControl.BackgroundSizingProperty); - CreateTemplateParentBinding(decorator, Border.BackgroundProperty, TemplatedControl.BackgroundProperty); - CreateTemplateParentBinding(decorator, Border.CornerRadiusProperty, TemplatedControl.CornerRadiusProperty); - CreateTemplateParentBinding(decorator, Decorator.PaddingProperty, TemplatedControl.PaddingProperty); + CreateTemplateParentBinding(contentDecorator, Border.BackgroundProperty, TemplatedControl.BackgroundProperty); + CreateTemplateParentBinding(contentDecorator, Border.CornerRadiusProperty, TemplatedControl.CornerRadiusProperty); + CreateTemplateParentBinding(contentDecorator, Decorator.PaddingProperty, TemplatedControl.PaddingProperty); - return decorator; + contentDecorator.Child = content; + + contentLayout.Children.Add(contentDecorator); + return contentLayout; }); } - + protected virtual Control BuildContent(INameScope scope) { var contentPresenter = new ContentPresenter { Name = ContentPresenterPart }; + CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, ContentControl.ContentProperty); CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, ContentControl.ContentTemplateProperty); @@ -69,6 +88,43 @@ internal class ArrowDecoratedBoxTheme : BaseControlTheme commonStyle.Add(TemplatedControl.PaddingProperty, ArrowDecoratedBoxTokenResourceKey.Padding); commonStyle.Add(ArrowDecoratedBox.ArrowSizeProperty, ArrowDecoratedBoxTokenResourceKey.ArrowSize); commonStyle.Add(TemplatedControl.CornerRadiusProperty, GlobalTokenResourceKey.BorderRadius); + BuildArrowDirectionStyle(commonStyle); Add(commonStyle); } + + private void BuildArrowDirectionStyle(Style commonStyle) + { + var topDirectionStyle = new Style(selector => selector.Nesting().PropertyEquals(ArrowDecoratedBox.ArrowDirectionProperty, Direction.Top)); + { + var arrowContentStyle = new Style(selector => selector.Nesting().Template().Name(ArrowContentPart)); + arrowContentStyle.Add(DockPanel.DockProperty, Dock.Top); + arrowContentStyle.Add(Control.HeightProperty, ArrowDecoratedBoxTokenResourceKey.ArrowContentThickness); + topDirectionStyle.Add(arrowContentStyle); + } + commonStyle.Add(topDirectionStyle); + var rightDirectionStyle = new Style(selector => selector.Nesting().PropertyEquals(ArrowDecoratedBox.ArrowDirectionProperty, Direction.Right)); + { + var arrowContentStyle = new Style(selector => selector.Nesting().Template().Name(ArrowContentPart)); + arrowContentStyle.Add(DockPanel.DockProperty, Dock.Right); + arrowContentStyle.Add(Control.WidthProperty, ArrowDecoratedBoxTokenResourceKey.ArrowContentThickness); + rightDirectionStyle.Add(arrowContentStyle); + } + commonStyle.Add(rightDirectionStyle); + var bottomDirectionStyle = new Style(selector => selector.Nesting().PropertyEquals(ArrowDecoratedBox.ArrowDirectionProperty, Direction.Bottom)); + { + var arrowContentStyle = new Style(selector => selector.Nesting().Template().Name(ArrowContentPart)); + arrowContentStyle.Add(DockPanel.DockProperty, Dock.Bottom); + arrowContentStyle.Add(Control.HeightProperty, ArrowDecoratedBoxTokenResourceKey.ArrowContentThickness); + bottomDirectionStyle.Add(arrowContentStyle); + } + commonStyle.Add(bottomDirectionStyle); + var leftDirectionStyle = new Style(selector => selector.Nesting().PropertyEquals(ArrowDecoratedBox.ArrowDirectionProperty, Direction.Left)); + { + var arrowContentStyle = new Style(selector => selector.Nesting().Template().Name(ArrowContentPart)); + arrowContentStyle.Add(DockPanel.DockProperty, Dock.Left); + arrowContentStyle.Add(Control.WidthProperty, ArrowDecoratedBoxTokenResourceKey.ArrowContentThickness); + leftDirectionStyle.Add(arrowContentStyle); + } + commonStyle.Add(leftDirectionStyle); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxToken.cs b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxToken.cs index 03560d5..1189cfc 100644 --- a/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxToken.cs +++ b/src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxToken.cs @@ -12,6 +12,11 @@ public class ArrowDecoratedBoxToken : AbstractControlDesignToken /// 箭头三角形大小 /// public double ArrowSize { get; set; } + + /// + /// 绘制箭头的厚度,跟位置有关 + /// + public double ArrowContentThickness { get; set; } /// /// 默认的内边距 @@ -26,7 +31,8 @@ public class ArrowDecoratedBoxToken : AbstractControlDesignToken internal override void CalculateFromAlias() { base.CalculateFromAlias(); - ArrowSize = _globalToken.SeedToken.SizePopupArrow / 1.3; - Padding = new Thickness(_globalToken.PaddingXS); + ArrowSize = _globalToken.SeedToken.SizePopupArrow / 1.3; + ArrowContentThickness = ArrowSize / 2; + Padding = new Thickness(_globalToken.PaddingXS); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Flyouts/Flyout.cs b/src/AtomUI.Controls/Flyouts/Flyout.cs index a6efdba..1dfb299 100644 --- a/src/AtomUI.Controls/Flyouts/Flyout.cs +++ b/src/AtomUI.Controls/Flyouts/Flyout.cs @@ -6,6 +6,7 @@ using AtomUI.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Metadata; @@ -149,9 +150,9 @@ public class Flyout : PopupFlyoutBase } } - var placement = Placement; - var anchor = PlacementAnchor; - var gravity = PlacementGravity; + var placement = popup.Placement; + var anchor = popup.PlacementAnchor; + var gravity = popup.PlacementGravity; if (flyoutPresenter is not null) { @@ -178,11 +179,11 @@ public class Flyout : PopupFlyoutBase protected internal override void NotifyPopupCreated(Popup popup) { base.NotifyPopupCreated(popup); - BindUtils.RelayBind(this, PlacementProperty, popup, Avalonia.Controls.Primitives.Popup.PlacementProperty); + BindUtils.RelayBind(this, PlacementProperty, popup, Popup.PlacementProperty); BindUtils.RelayBind(this, PlacementAnchorProperty, popup, - Avalonia.Controls.Primitives.Popup.PlacementAnchorProperty); + Popup.PlacementAnchorProperty); BindUtils.RelayBind(this, PlacementGravityProperty, popup, - Avalonia.Controls.Primitives.Popup.PlacementGravityProperty); + Popup.PlacementGravityProperty); BindUtils.RelayBind(this, MaskShadowsProperty, popup, Popup.MaskShadowsProperty); SetupArrowPosition(popup); } diff --git a/src/AtomUI.Controls/Flyouts/MenuFlyoutPresenter.cs b/src/AtomUI.Controls/Flyouts/MenuFlyoutPresenter.cs index cc82f82..dbbf87c 100644 --- a/src/AtomUI.Controls/Flyouts/MenuFlyoutPresenter.cs +++ b/src/AtomUI.Controls/Flyouts/MenuFlyoutPresenter.cs @@ -167,7 +167,7 @@ public class MenuFlyoutPresenter : MenuBase, IShadowMaskInfoProvider { if (_arrowDecoratedBox is not null) { - var contentRect = _arrowDecoratedBox.GetContentRect(Bounds.Size); + var contentRect = _arrowDecoratedBox.GetMaskBounds(); var adjustedPos = _arrowDecoratedBox.TranslatePoint(contentRect.Position, this) ?? default; return new Rect(adjustedPos, contentRect.Size); } diff --git a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs index 2eac2ef..d9082b6 100644 --- a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs +++ b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs @@ -41,6 +41,7 @@ namespace AtomUI.Theme.Styling public static class ArrowDecoratedBoxTokenResourceKey { public static readonly TokenResourceKey ArrowSize = new TokenResourceKey("ArrowDecoratedBox.ArrowSize", "AtomUI.Token"); + public static readonly TokenResourceKey ArrowContentThickness = new TokenResourceKey("ArrowDecoratedBox.ArrowContentThickness", "AtomUI.Token"); public static readonly TokenResourceKey Padding = new TokenResourceKey("ArrowDecoratedBox.Padding", "AtomUI.Token"); } diff --git a/src/AtomUI.Controls/Popup/Popup.cs b/src/AtomUI.Controls/Popup/Popup.cs index f583de0..049009f 100644 --- a/src/AtomUI.Controls/Popup/Popup.cs +++ b/src/AtomUI.Controls/Popup/Popup.cs @@ -67,6 +67,7 @@ public class Popup : AvaloniaPopup private bool _initialized; private ManagedPopupPositionerInfo? _managedPopupPositioner; protected bool _animating; + private bool _isNeedFlip = true; // 当鼠标移走了,但是打开动画还没完成,我们需要记录下来这个信号 internal bool RequestCloseWhereAnimationCompleted { get; set; } @@ -147,11 +148,14 @@ public class Popup : AvaloniaPopup "Unable to create shadow layer, top level for PlacementTarget is null."); } - if (Placement != PlacementMode.Pointer && Placement != PlacementMode.Center) + if (_isNeedFlip) { - AdjustPopupHostPosition(placementTarget); + if (Placement != PlacementMode.Pointer && Placement != PlacementMode.Center) + { + AdjustPopupHostPosition(placementTarget!); + } } - + if (!_animating) { CreateShadowLayer(); @@ -291,8 +295,6 @@ public class Popup : AvaloniaPopup /// /// 在这里处理翻转问题 /// - /// - /// internal void AdjustPopupHostPosition(Control placementTarget) { var offsetX = HorizontalOffset; @@ -307,8 +309,7 @@ public class Popup : AvaloniaPopup var direction = PopupUtils.GetDirection(Placement); var topLevel = TopLevel.GetTopLevel(placementTarget)!; - if (Placement != PlacementMode.Center && - Placement != PlacementMode.Pointer) + if (Placement != PlacementMode.Center && Placement != PlacementMode.Pointer) { // 计算是否 flip var parameters = new PopupPositionerParameters(); @@ -422,16 +423,22 @@ public class Popup : AvaloniaPopup _animating = true; + var placementTarget = GetEffectivePlacementTarget(); + Open(); + if (Placement != PlacementMode.Pointer && Placement != PlacementMode.Center) + { + AdjustPopupHostPosition(placementTarget!); + _isNeedFlip = false; + } var popupRoot = (Host as PopupRoot)!; // 获取 popup 的具体位置,这个就是非常准确的位置,还有大小 // TODO 暂时只支持 WindowBase popup popupRoot.Hide(); - var popupOffset = popupRoot.PlatformImpl!.Position; - var offset = new Point(popupOffset.X, popupOffset.Y); - var placementTarget = GetEffectivePlacementTarget(); - var topLevel = TopLevel.GetTopLevel(placementTarget); - var scaling = topLevel?.RenderScaling ?? 1.0; + var popupOffset = popupRoot.PlatformImpl!.Position; + var offset = new Point(popupOffset.X, popupOffset.Y); + var topLevel = TopLevel.GetTopLevel(placementTarget); + var scaling = topLevel?.RenderScaling ?? 1.0; // 调度动画 var director = Director.Instance;