From 0c07a8a075d9bcffe36be954beec0d2a18ae0fb6 Mon Sep 17 00:00:00 2001 From: polarboy Date: Fri, 30 Aug 2024 14:22:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20Notification=20placement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShowCase/NotificationShowCase.axaml | 71 +++++ .../ShowCase/NotificationShowCase.axaml.cs | 159 ++++++++++- .../TokenResourceConst.g.cs | 3 +- .../Notifications/NotificationCard.cs | 38 ++- .../Notifications/NotificationCardTheme.cs | 256 ++++++++++++++++- .../Notifications/NotificationToken.cs | 13 +- .../WindowNotificationManager.cs | 9 +- .../WindowNotificationManagerTheme.cs | 4 +- .../Utils/Motion/MoveMotionFactory.cs | 270 ++++++++++++++++-- 9 files changed, 774 insertions(+), 49 deletions(-) diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/NotificationShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/NotificationShowCase.axaml index 72763528b..17a194f9e 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/NotificationShowCase.axaml +++ b/samples/AtomUI.Demo.Desktop/ShowCase/NotificationShowCase.axaml @@ -16,5 +16,76 @@ Show Notification + + + + Open the notification box + + + + + + + Success + + + Info + + + Warning + + + Error + + + + + + + + + Top + + + Bottom + + + + + + TopLeft + + + TopRight + + + + + + BottomLeft + + + BottomRight + + + + \ No newline at end of file diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/NotificationShowCase.axaml.cs b/samples/AtomUI.Demo.Desktop/ShowCase/NotificationShowCase.axaml.cs index b3e37bc57..ba7f8f840 100644 --- a/samples/AtomUI.Demo.Desktop/ShowCase/NotificationShowCase.axaml.cs +++ b/samples/AtomUI.Demo.Desktop/ShowCase/NotificationShowCase.axaml.cs @@ -7,7 +7,15 @@ namespace AtomUI.Demo.Desktop.ShowCase; public partial class NotificationShowCase : UserControl { - private WindowNotificationManager? _windowNotificationManager; + private WindowNotificationManager? _basicManager; + private WindowNotificationManager? _topLeftManager; + private WindowNotificationManager? _topManager; + private WindowNotificationManager? _topRightManager; + + private WindowNotificationManager? _bottomLeftManager; + private WindowNotificationManager? _bottomManager; + private WindowNotificationManager? _bottomRightManager; + public NotificationShowCase() { InitializeComponent(); @@ -17,20 +25,159 @@ public partial class NotificationShowCase : UserControl { base.OnAttachedToVisualTree(e); var topLevel = TopLevel.GetTopLevel(this); - _windowNotificationManager = new WindowNotificationManager(topLevel) + _basicManager = new WindowNotificationManager(topLevel) { - MaxItems = 5, - IsPauseOnHover = true + MaxItems = 3 + }; + + _topLeftManager = new WindowNotificationManager(topLevel) + { + MaxItems = 3, + Position = NotificationPosition.TopLeft + }; + + _topManager = new WindowNotificationManager(topLevel) + { + Position = NotificationPosition.TopCenter, + MaxItems = 3 + }; + + _topRightManager = new WindowNotificationManager(topLevel) + { + Position = NotificationPosition.TopRight, + MaxItems = 3 + }; + + _bottomLeftManager = new WindowNotificationManager(topLevel) + { + Position = NotificationPosition.BottomLeft, + MaxItems = 3 + }; + + _bottomManager = new WindowNotificationManager(topLevel) + { + Position = NotificationPosition.BottomCenter, + MaxItems = 3 + }; + + _bottomRightManager = new WindowNotificationManager(topLevel) + { + Position = NotificationPosition.BottomRight, + MaxItems = 3 }; } private void ShowSimpleNotification(object? sender, RoutedEventArgs e) { - _windowNotificationManager?.Show(new Notification() + _basicManager?.Show(new Notification() { ShowProgress = true, Title = "Notification Title", - Content = "This is the content of the notification. This is the content of the notification. This is the content of the notification. This is the content of the notification." + Content = "Hello, AtomUI/Avalonia!" + }); + } + + private void ShowNeverCloseNotification(object? sender, RoutedEventArgs e) + { + _basicManager?.Show(new Notification() + { + Expiration = TimeSpan.Zero, + Title = "Notification Title", + Content = "I will never close automatically. This is a purposely very very long description that has many many characters and words." + }); + } + + private void ShowSuccessNotification(object? sender, RoutedEventArgs e) + { + _basicManager?.Show(new Notification() + { + Type = NotificationType.Success, + Title = "Notification Title", + Content = "This is the content of the notification. This is the content of the notification. This is the content of the notification." + }); + } + + private void ShowInfoNotification(object? sender, RoutedEventArgs e) + { + _basicManager?.Show(new Notification() + { + Type = NotificationType.Information, + Title = "Notification Title", + Content = "This is the content of the notification. This is the content of the notification. This is the content of the notification." + }); + } + + private void ShowWarningNotification(object? sender, RoutedEventArgs e) + { + _basicManager?.Show(new Notification() + { + Type = NotificationType.Warning, + Title = "Notification Title", + Content = "This is the content of the notification. This is the content of the notification. This is the content of the notification." + }); + } + + private void ShowErrorNotification(object? sender, RoutedEventArgs e) + { + _basicManager?.Show(new Notification() + { + Type = NotificationType.Error, + Title = "Notification Title", + Content = "This is the content of the notification. This is the content of the notification. This is the content of the notification." + }); + } + + private void ShowTopNotification(object? sender, RoutedEventArgs e) + { + _topManager?.Show(new Notification() + { + Title = "Notification Top", + Content = "Hello, AtomUI/Avalonia!" + }); + } + + private void ShowBottomNotification(object? sender, RoutedEventArgs e) + { + _bottomManager?.Show(new Notification() + { + Title = "Notification Bottom", + Content = "Hello, AtomUI/Avalonia!" + }); + } + + private void ShowTopLeftNotification(object? sender, RoutedEventArgs e) + { + _topLeftManager?.Show(new Notification() + { + Title = "Notification TopLeft", + Content = "Hello, AtomUI/Avalonia!" + }); + } + + private void ShowTopRightNotification(object? sender, RoutedEventArgs e) + { + _topRightManager?.Show(new Notification() + { + Title = "Notification TopRight", + Content = "Hello, AtomUI/Avalonia!" + }); + } + + private void ShowBottomLeftNotification(object? sender, RoutedEventArgs e) + { + _bottomLeftManager?.Show(new Notification() + { + Title = "Notification BottomLeft", + Content = "Hello, AtomUI/Avalonia!" + }); + } + + private void ShowBottomRightNotification(object? sender, RoutedEventArgs e) + { + _bottomRightManager?.Show(new Notification() + { + Title = "Notification BottomRight", + Content = "Hello, AtomUI/Avalonia!" }); } } \ No newline at end of file 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 58d87f391..e35fefbf7 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 @@ -314,7 +314,8 @@ namespace AtomUI.Theme.Styling public static readonly TokenResourceKey NotificationIconMargin = new TokenResourceKey("Notification.NotificationIconMargin", "AtomUI.Token"); public static readonly TokenResourceKey NotificationCloseButtonSize = new TokenResourceKey("Notification.NotificationCloseButtonSize", "AtomUI.Token"); public static readonly TokenResourceKey NotificationMarginBottom = new TokenResourceKey("Notification.NotificationMarginBottom", "AtomUI.Token"); - public static readonly TokenResourceKey NotificationMarginEdge = new TokenResourceKey("Notification.NotificationMarginEdge", "AtomUI.Token"); + public static readonly TokenResourceKey NotificationTopMargin = new TokenResourceKey("Notification.NotificationTopMargin", "AtomUI.Token"); + public static readonly TokenResourceKey NotificationBottomMargin = new TokenResourceKey("Notification.NotificationBottomMargin", "AtomUI.Token"); public static readonly TokenResourceKey NotificationProgressBg = new TokenResourceKey("Notification.NotificationProgressBg", "AtomUI.Token"); public static readonly TokenResourceKey NotificationProgressHeight = new TokenResourceKey("Notification.NotificationProgressHeight", "AtomUI.Token"); public static readonly TokenResourceKey NotificationProgressMargin = new TokenResourceKey("Notification.NotificationProgressMargin", "AtomUI.Token"); diff --git a/src/AtomUI.Controls/Notifications/NotificationCard.cs b/src/AtomUI.Controls/Notifications/NotificationCard.cs index c2e43009b..d7a331e65 100644 --- a/src/AtomUI.Controls/Notifications/NotificationCard.cs +++ b/src/AtomUI.Controls/Notifications/NotificationCard.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.LogicalTree; namespace AtomUI.Controls; @@ -132,7 +133,14 @@ public class NotificationCard : TemplatedControl internal static readonly DirectProperty EffectiveShowProgressProperty = AvaloniaProperty.RegisterDirect(nameof(EffectiveShowProgress), - o => o.EffectiveShowProgress); + o => o.EffectiveShowProgress, + (o, v) => o.EffectiveShowProgress = v); + + internal static readonly DirectProperty PositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(Position), + o => o.Position, + (o, v) => o.Position = v); private bool _effectiveShowProgress; internal bool EffectiveShowProgress @@ -141,6 +149,13 @@ public class NotificationCard : TemplatedControl set => SetAndRaise(EffectiveShowProgressProperty, ref _effectiveShowProgress, value); } + private NotificationPosition _position; + internal NotificationPosition Position + { + get => _position; + set => SetAndRaise(PositionProperty, ref _position, value); + } + #endregion /// @@ -175,10 +190,15 @@ public class NotificationCard : TemplatedControl IsClosing = true; } + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnAttachedToLogicalTree(e); + UpdatePseudoClasses(Position); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - SetupContent(); if (Icon is null) { SetupNotificationIcon(); UpdateNotificationType(); @@ -232,6 +252,10 @@ public class NotificationCard : TemplatedControl e.Property == IsClosedProperty) { SetupEffectiveShowProgress(); } + + if (e.Property == PositionProperty) { + UpdatePseudoClasses(e.GetNewValue()); + } } private void SetupEffectiveShowProgress() @@ -369,4 +393,14 @@ public class NotificationCard : TemplatedControl _notificationManager.StartExpiredTimer(); } } + + private void UpdatePseudoClasses(NotificationPosition position) + { + PseudoClasses.Set(WindowNotificationManager.TopLeftPC, position == NotificationPosition.TopLeft); + PseudoClasses.Set(WindowNotificationManager.TopRightPC, position == NotificationPosition.TopRight); + PseudoClasses.Set(WindowNotificationManager.BottomLeftPC, position == NotificationPosition.BottomLeft); + PseudoClasses.Set(WindowNotificationManager.BottomRightPC, position == NotificationPosition.BottomRight); + PseudoClasses.Set(WindowNotificationManager.TopCenterPC, position == NotificationPosition.TopCenter); + PseudoClasses.Set(WindowNotificationManager.BottomCenterPC, position == NotificationPosition.BottomCenter); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Notifications/NotificationCardTheme.cs b/src/AtomUI.Controls/Notifications/NotificationCardTheme.cs index 484fd11c1..89e615d45 100644 --- a/src/AtomUI.Controls/Notifications/NotificationCardTheme.cs +++ b/src/AtomUI.Controls/Notifications/NotificationCardTheme.cs @@ -27,6 +27,14 @@ internal class NotificationCardTheme : BaseControlTheme public const string CloseButtonPart = "PART_CloseButton"; public const string LayoutTransformControlPart = "PART_LayoutTransformControl"; public const string ProgressBarPart = "PART_ProgressBar"; + public const string MarginGhostDecoratorPart = "PART_MarginGhostDecorator"; + + public const double AnimationMaxOffsetY = 150d; + public const double AnimationMaxOffsetX = 500d; + public const int AnimationDuration = 400; + + private Easing _quadraticEaseOut = new QuadraticEaseOut(); + private Easing _quadraticEaseIn = new QuadraticEaseIn(); public NotificationCardTheme() : base(typeof(NotificationCard)) @@ -44,11 +52,20 @@ internal class NotificationCardTheme : BaseControlTheme ClipToBounds = false }; layoutTransformControl.RegisterInNameScope(scope); + + // 防止关闭的时候抖动,如果直接设置到 NotificationCard,layoutTransformControl没有办法平滑处理 + var marginGhostDecorator = new Border() + { + Name = MarginGhostDecoratorPart + }; + var frameDecorator = new Border() { Name = FrameDecoratorPart }; + marginGhostDecorator.Child = frameDecorator; + var mainLayout = new Grid() { ColumnDefinitions = new ColumnDefinitions() @@ -70,7 +87,7 @@ internal class NotificationCardTheme : BaseControlTheme BuildProgressBar(mainLayout, scope); frameDecorator.RegisterInNameScope(scope); - layoutTransformControl.Child = frameDecorator; + layoutTransformControl.Child = marginGhostDecorator; return layoutTransformControl; }); } @@ -191,8 +208,56 @@ internal class NotificationCardTheme : BaseControlTheme private void BuildCommonStyle() { var commonStyle = new Style(selector => selector.Nesting()); + + var topLeftStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.TopLeft)); + + { + var marginGhostDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(MarginGhostDecoratorPart)); + marginGhostDecoratorStyle.Add(Border.MarginProperty, NotificationTokenResourceKey.NotificationTopMargin); + topLeftStyle.Add(marginGhostDecoratorStyle); + } + commonStyle.Add(topLeftStyle); + + var topCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.TopCenter)); + { + var marginGhostDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(MarginGhostDecoratorPart)); + marginGhostDecoratorStyle.Add(Border.MarginProperty, NotificationTokenResourceKey.NotificationTopMargin); + topCenterStyle.Add(marginGhostDecoratorStyle); + } + commonStyle.Add(topCenterStyle); + + var topRightStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.TopRight)); + { + var marginGhostDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(MarginGhostDecoratorPart)); + marginGhostDecoratorStyle.Add(Border.MarginProperty, NotificationTokenResourceKey.NotificationTopMargin); + topRightStyle.Add(marginGhostDecoratorStyle); + } + commonStyle.Add(topRightStyle); + + var bottomLeftStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.BottomLeft)); + { + var marginGhostDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(MarginGhostDecoratorPart)); + marginGhostDecoratorStyle.Add(Border.MarginProperty, NotificationTokenResourceKey.NotificationBottomMargin); + bottomLeftStyle.Add(marginGhostDecoratorStyle); + } + commonStyle.Add(bottomLeftStyle); + + var bottomCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.BottomCenter)); + { + var marginGhostDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(MarginGhostDecoratorPart)); + marginGhostDecoratorStyle.Add(Border.MarginProperty, NotificationTokenResourceKey.NotificationBottomMargin); + bottomCenterStyle.Add(marginGhostDecoratorStyle); + } + commonStyle.Add(bottomCenterStyle); + + var bottomRightStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.BottomRight)); + { + var marginGhostDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(MarginGhostDecoratorPart)); + marginGhostDecoratorStyle.Add(Border.MarginProperty, NotificationTokenResourceKey.NotificationBottomMargin); + bottomRightStyle.Add(marginGhostDecoratorStyle); + } + commonStyle.Add(bottomRightStyle); - commonStyle.Add(NotificationCard.MarginProperty, NotificationTokenResourceKey.NotificationMarginEdge); commonStyle.Add(NotificationCard.WidthProperty, NotificationTokenResourceKey.NotificationWidth); var frameDecoratorStyle = new Style(selector => selector.Nesting().Template().Name(FrameDecoratorPart)); @@ -202,6 +267,10 @@ internal class NotificationCardTheme : BaseControlTheme frameDecoratorStyle.Add(Border.CornerRadiusProperty, GlobalTokenResourceKey.BorderRadiusLG); commonStyle.Add(frameDecoratorStyle); + var closedStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.IsClosedProperty, true)); + closedStyle.Add(NotificationCard.MarginProperty, new Thickness(0)); + commonStyle.Add(closedStyle); + Add(commonStyle); } @@ -239,23 +308,51 @@ internal class NotificationCardTheme : BaseControlTheme { var commonStyle = new Style(selector => selector.Nesting()); + var topLeftStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.TopLeft)); + BuildLeftEdgeAnimation(topLeftStyle); + commonStyle.Add(topLeftStyle); + + var topCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.TopCenter)); + + commonStyle.Add(topCenterStyle); + BuildTopCenterEdgeAnimation(topCenterStyle); + var topRightStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.TopRight)); + commonStyle.Add(topRightStyle); + BuildRightEdgeAnimation(topRightStyle); + var bottomLeftStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.BottomLeft)); + BuildLeftEdgeAnimation(bottomLeftStyle); + commonStyle.Add(bottomLeftStyle); + + var bottomCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.BottomCenter)); + BuildBottomCenterEdgeAnimation(bottomCenterStyle); + commonStyle.Add(bottomCenterStyle); + + var bottomRightStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.PositionProperty, NotificationPosition.BottomRight)); + BuildRightEdgeAnimation(bottomRightStyle); + commonStyle.Add(bottomRightStyle); + + Add(commonStyle); + } + + private void BuildRightEdgeAnimation(Style parentStyle) + { { var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart)); - var moveRightInMotionConfig = MotionFactory.BuildMoveRightInMotion(400, TimeSpan.FromMilliseconds(400), new QuadraticEaseOut(), + var moveRightInMotionConfig = MotionFactory.BuildMoveRightInMotion(AnimationMaxOffsetX, TimeSpan.FromMilliseconds(AnimationDuration), _quadraticEaseOut, FillMode.Forward); foreach (var animation in moveRightInMotionConfig.Animations) { layoutTransformStyle.Animations.Add(animation); } - - commonStyle.Add(layoutTransformStyle); + + parentStyle.Add(layoutTransformStyle); } - + var isClosingStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.IsClosingProperty, true)); { var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart)); - var moveRightOutMotionConfig = MotionFactory.BuildMoveRightOutMotion(400, TimeSpan.FromMilliseconds(400), new QuadraticEaseIn(), FillMode.Forward); + var moveRightOutMotionConfig = MotionFactory.BuildMoveRightOutMotion(AnimationMaxOffsetX, TimeSpan.FromMilliseconds(AnimationDuration), _quadraticEaseIn, FillMode.Forward); foreach (var animation in moveRightOutMotionConfig.Animations) { layoutTransformStyle.Animations.Add(animation); @@ -263,8 +360,53 @@ internal class NotificationCardTheme : BaseControlTheme isClosingStyle.Animations.Add(new Animation() { - Duration = TimeSpan.FromMilliseconds(600), - Easing = new QuadraticEaseIn(), + Duration = TimeSpan.FromMilliseconds(AnimationDuration * 1.2), + Easing = _quadraticEaseIn, + FillMode = FillMode.Forward, + Children = + { + new KeyFrame() + { + Cue = new Cue(1.0), + Setters = + { + new Setter(NotificationCard.IsClosedProperty, true) + } + } + } + }); + + isClosingStyle.Add(layoutTransformStyle); + } + parentStyle.Add(isClosingStyle); + } + + private void BuildLeftEdgeAnimation(Style parentStyle) + { + { + var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart)); + var moveLeftInMotionConfig = MotionFactory.BuildMoveLeftInMotion(AnimationMaxOffsetX, TimeSpan.FromMilliseconds(AnimationDuration), _quadraticEaseOut, + FillMode.Forward); + foreach (var animation in moveLeftInMotionConfig.Animations) { + layoutTransformStyle.Animations.Add(animation); + } + + parentStyle.Add(layoutTransformStyle); + } + + var isClosingStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.IsClosingProperty, true)); + { + var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart)); + var moveRightOutMotionConfig = MotionFactory.BuildMoveLeftOutMotion(AnimationMaxOffsetX, TimeSpan.FromMilliseconds(AnimationDuration), _quadraticEaseIn, FillMode.Forward); + + foreach (var animation in moveRightOutMotionConfig.Animations) { + layoutTransformStyle.Animations.Add(animation); + } + + isClosingStyle.Animations.Add(new Animation() + { + Duration = TimeSpan.FromMilliseconds(AnimationDuration * 1.2), + Easing = _quadraticEaseIn, FillMode = FillMode.Forward, Children = { @@ -274,7 +416,6 @@ internal class NotificationCardTheme : BaseControlTheme Setters = { new Setter(NotificationCard.IsClosedProperty, true), - new Setter(NotificationCard.MarginProperty, new Thickness(0)), } } } @@ -282,8 +423,99 @@ internal class NotificationCardTheme : BaseControlTheme isClosingStyle.Add(layoutTransformStyle); } - commonStyle.Add(isClosingStyle); + parentStyle.Add(isClosingStyle); + } + + private void BuildTopCenterEdgeAnimation(Style parentStyle) + { + { + var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart)); + var moveRightInMotionConfig = MotionFactory.BuildMoveUpInMotion(AnimationMaxOffsetY, TimeSpan.FromMilliseconds(AnimationDuration), _quadraticEaseOut, + FillMode.Forward); + foreach (var animation in moveRightInMotionConfig.Animations) { + layoutTransformStyle.Animations.Add(animation); + } - Add(commonStyle); + parentStyle.Add(layoutTransformStyle); + } + + var isClosingStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.IsClosingProperty, true)); + { + var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart)); + + var moveRightOutMotionConfig = MotionFactory.BuildMoveUpOutMotion(AnimationMaxOffsetY, TimeSpan.FromMilliseconds(AnimationDuration), _quadraticEaseIn, FillMode.Forward); + + foreach (var animation in moveRightOutMotionConfig.Animations) { + layoutTransformStyle.Animations.Add(animation); + } + + isClosingStyle.Animations.Add(new Animation() + { + Duration = TimeSpan.FromMilliseconds(AnimationDuration * 1.2), + Easing = _quadraticEaseIn, + FillMode = FillMode.Forward, + Children = + { + new KeyFrame() + { + Cue = new Cue(1.0), + Setters = + { + new Setter(NotificationCard.IsClosedProperty, true) + } + } + } + }); + + isClosingStyle.Add(layoutTransformStyle); + } + parentStyle.Add(isClosingStyle); + } + + + private void BuildBottomCenterEdgeAnimation(Style parentStyle) + { + { + var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart)); + var moveRightInMotionConfig = MotionFactory.BuildMoveDownInMotion(AnimationMaxOffsetY, TimeSpan.FromMilliseconds(AnimationDuration), _quadraticEaseOut, + FillMode.Forward); + foreach (var animation in moveRightInMotionConfig.Animations) { + layoutTransformStyle.Animations.Add(animation); + } + + parentStyle.Add(layoutTransformStyle); + } + + var isClosingStyle = new Style(selector => selector.Nesting().PropertyEquals(NotificationCard.IsClosingProperty, true)); + { + var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart)); + + var moveRightOutMotionConfig = MotionFactory.BuildMoveDownOutMotion(AnimationMaxOffsetY, TimeSpan.FromMilliseconds(AnimationDuration), _quadraticEaseIn, FillMode.Forward); + + foreach (var animation in moveRightOutMotionConfig.Animations) { + layoutTransformStyle.Animations.Add(animation); + } + + isClosingStyle.Animations.Add(new Animation() + { + Duration = TimeSpan.FromMilliseconds(AnimationDuration * 1.2), + Easing = _quadraticEaseIn, + FillMode = FillMode.Forward, + Children = + { + new KeyFrame() + { + Cue = new Cue(1.0), + Setters = + { + new Setter(NotificationCard.IsClosedProperty, true) + } + } + } + }); + + isClosingStyle.Add(layoutTransformStyle); + } + parentStyle.Add(isClosingStyle); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Notifications/NotificationToken.cs b/src/AtomUI.Controls/Notifications/NotificationToken.cs index f6c627818..d543ce5a4 100644 --- a/src/AtomUI.Controls/Notifications/NotificationToken.cs +++ b/src/AtomUI.Controls/Notifications/NotificationToken.cs @@ -50,9 +50,14 @@ internal class NotificationToken : AbstractControlDesignToken public Thickness NotificationMarginBottom { get; set; } /// - /// 提醒框边缘外边距 + /// 提醒框上边缘外边距 /// - public Thickness NotificationMarginEdge { get; set; } + public Thickness NotificationTopMargin { get; set; } + + /// + /// 提醒框下边缘外边距 + /// + public Thickness NotificationBottomMargin { get; set; } /// /// 提醒框进度条背景色 @@ -95,8 +100,8 @@ internal class NotificationToken : AbstractControlDesignToken NotificationIconSize = _globalToken.FontToken.FontSizeLG * _globalToken.FontToken.LineHeightLG; NotificationCloseButtonSize = _globalToken.HeightToken.ControlHeightLG * 0.55; NotificationMarginBottom = new Thickness(0, 0, 0, _globalToken.Margin); - NotificationMarginEdge = new Thickness(_globalToken.MarginLG, _globalToken.MarginLG, _globalToken.MarginLG, 0); - AnimationMaxHeight = 150; + NotificationTopMargin = new Thickness(_globalToken.MarginLG, _globalToken.MarginLG, _globalToken.MarginLG, 0); + NotificationBottomMargin = new Thickness(_globalToken.MarginLG, 0, _globalToken.MarginLG, _globalToken.MarginLG); NotificationProgressBg = new LinearGradientBrush() { diff --git a/src/AtomUI.Controls/Notifications/WindowNotificationManager.cs b/src/AtomUI.Controls/Notifications/WindowNotificationManager.cs index 32c032a77..5c4f3fbf3 100644 --- a/src/AtomUI.Controls/Notifications/WindowNotificationManager.cs +++ b/src/AtomUI.Controls/Notifications/WindowNotificationManager.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Specialized; +using AtomUI.Data; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; @@ -147,6 +148,7 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager Expiration = expiration == TimeSpan.Zero ? null : expiration, IsShowProgress = notification.ShowProgress }; + BindUtils.RelayBind(this, PositionProperty, notificationControl, NotificationCard.PositionProperty); // Add style classes if any if (classes != null) { @@ -162,13 +164,6 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager _items?.Remove(sender); }; - notificationControl.PointerPressed += (sender, args) => - { - onClick?.Invoke(); - - (sender as NotificationCard)?.Close(); - }; - Dispatcher.UIThread.Post(() => { _items?.Add(notificationControl); diff --git a/src/AtomUI.Controls/Notifications/WindowNotificationManagerTheme.cs b/src/AtomUI.Controls/Notifications/WindowNotificationManagerTheme.cs index 2afa2a15d..2d416e7b3 100644 --- a/src/AtomUI.Controls/Notifications/WindowNotificationManagerTheme.cs +++ b/src/AtomUI.Controls/Notifications/WindowNotificationManagerTheme.cs @@ -34,6 +34,7 @@ internal class WindowNotificationManagerTheme : BaseControlTheme var topLeftStyle = new Style(selector => selector.Nesting().Class(WindowNotificationManager.TopLeftPC)); { var itemsStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPart)); + itemsStyle.Add(ReversibleStackPanel.ReverseOrderProperty, true); itemsStyle.Add(ReversibleStackPanel.HorizontalAlignmentProperty, HorizontalAlignment.Left); itemsStyle.Add(ReversibleStackPanel.VerticalAlignmentProperty, VerticalAlignment.Top); topLeftStyle.Add(itemsStyle); @@ -54,6 +55,7 @@ internal class WindowNotificationManagerTheme : BaseControlTheme var topCenterStyle = new Style(selector => selector.Nesting().Class(WindowNotificationManager.TopCenterPC)); { var itemsStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPart)); + itemsStyle.Add(ReversibleStackPanel.ReverseOrderProperty, true); itemsStyle.Add(ReversibleStackPanel.HorizontalAlignmentProperty, HorizontalAlignment.Center); itemsStyle.Add(ReversibleStackPanel.VerticalAlignmentProperty, VerticalAlignment.Top); topCenterStyle.Add(itemsStyle); @@ -65,7 +67,7 @@ internal class WindowNotificationManagerTheme : BaseControlTheme var itemsStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPart)); itemsStyle.Add(ReversibleStackPanel.HorizontalAlignmentProperty, HorizontalAlignment.Left); itemsStyle.Add(ReversibleStackPanel.VerticalAlignmentProperty, VerticalAlignment.Bottom); - topLeftStyle.Add(itemsStyle); + bottomLeftStyle.Add(itemsStyle); } Add(bottomLeftStyle); diff --git a/src/AtomUI.Controls/Utils/Motion/MoveMotionFactory.cs b/src/AtomUI.Controls/Utils/Motion/MoveMotionFactory.cs index c6d4c6bcc..44e2a6620 100644 --- a/src/AtomUI.Controls/Utils/Motion/MoveMotionFactory.cs +++ b/src/AtomUI.Controls/Utils/Motion/MoveMotionFactory.cs @@ -11,7 +11,7 @@ public static partial class MotionFactory public static MotionConfig BuildMoveDownInMotion(double offset, TimeSpan duration, Easing? easing = null, FillMode fillMode = FillMode.None) { - easing ??= new QuinticEaseOut(); + easing ??= new QuinticEaseOut(); var animations = new List(); RelativePoint transformOrigin = default; var animation = new Animation() @@ -39,9 +39,42 @@ public static partial class MotionFactory Value = offset }; startFrame.Setters.Add(translateYSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 0.0 + }; + startFrame.Setters.Add(scaleYSetter); } animation.Children.Add(startFrame); + var middleFrame = new KeyFrame() + { + Cue = new Cue(0.6) + }; + { + var opacitySetter = new Setter() + { + Property = Visual.OpacityProperty, + Value = 0.1 + }; + middleFrame.Setters.Add(opacitySetter); + var translateYSetter = new Setter() + { + Property = TranslateTransform.YProperty, + Value = offset / 4 + }; + middleFrame.Setters.Add(translateYSetter); + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + middleFrame.Setters.Add(scaleYSetter); + } + animation.Children.Add(middleFrame); + var endFrame = new KeyFrame() { Cue = new Cue(1.0) @@ -59,9 +92,15 @@ public static partial class MotionFactory Value = 0.0 }; endFrame.Setters.Add(translateYSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + endFrame.Setters.Add(scaleYSetter); } animation.Children.Add(endFrame); - transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); animations.Add(animation); @@ -71,7 +110,7 @@ public static partial class MotionFactory public static MotionConfig BuildMoveDownOutMotion(double offset, TimeSpan duration, Easing? easing = null, FillMode fillMode = FillMode.None) { - easing ??= new QuinticEaseIn(); + easing ??= new QuinticEaseIn(); var animations = new List(); RelativePoint transformOrigin = default; var animation = new Animation() @@ -99,9 +138,42 @@ public static partial class MotionFactory Value = 0.0 }; startFrame.Setters.Add(translateYSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + startFrame.Setters.Add(scaleYSetter); } animation.Children.Add(startFrame); + var middleFrame = new KeyFrame() + { + Cue = new Cue(0.35) + }; + { + var opacitySetter = new Setter() + { + Property = Visual.OpacityProperty, + Value = 0.0 + }; + middleFrame.Setters.Add(opacitySetter); + var translateYSetter = new Setter() + { + Property = TranslateTransform.YProperty, + Value = offset / 2 + }; + middleFrame.Setters.Add(translateYSetter); + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + middleFrame.Setters.Add(scaleYSetter); + } + animation.Children.Add(middleFrame); + var endFrame = new KeyFrame() { Cue = new Cue(1.0) @@ -119,6 +191,13 @@ public static partial class MotionFactory Value = offset }; endFrame.Setters.Add(translateYSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 0.0 + }; + endFrame.Setters.Add(scaleYSetter); } animation.Children.Add(endFrame); transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative); @@ -130,7 +209,7 @@ public static partial class MotionFactory public static MotionConfig BuildMoveUpInMotion(double offset, TimeSpan duration, Easing? easing = null, FillMode fillMode = FillMode.None) { - easing ??= new QuinticEaseOut(); + easing ??= new QuinticEaseOut(); var animations = new List(); RelativePoint transformOrigin = default; var animation = new Animation() @@ -158,9 +237,42 @@ public static partial class MotionFactory Value = -offset }; startFrame.Setters.Add(translateYSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 0.0 + }; + startFrame.Setters.Add(scaleYSetter); } animation.Children.Add(startFrame); + var middleFrame = new KeyFrame() + { + Cue = new Cue(0.6) + }; + { + var opacitySetter = new Setter() + { + Property = Visual.OpacityProperty, + Value = 0.1 + }; + middleFrame.Setters.Add(opacitySetter); + var translateYSetter = new Setter() + { + Property = TranslateTransform.YProperty, + Value = -offset / 4 + }; + middleFrame.Setters.Add(translateYSetter); + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + middleFrame.Setters.Add(scaleYSetter); + } + animation.Children.Add(middleFrame); + var endFrame = new KeyFrame() { Cue = new Cue(1.0) @@ -178,6 +290,13 @@ public static partial class MotionFactory Value = 0.0 }; endFrame.Setters.Add(translateYSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + endFrame.Setters.Add(scaleYSetter); } animation.Children.Add(endFrame); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); @@ -189,7 +308,7 @@ public static partial class MotionFactory public static MotionConfig BuildMoveUpOutMotion(double offset, TimeSpan duration, Easing? easing = null, FillMode fillMode = FillMode.None) { - easing ??= new QuinticEaseIn(); + easing ??= new QuinticEaseIn(); var animations = new List(); RelativePoint transformOrigin = default; var animation = new Animation() @@ -214,12 +333,45 @@ public static partial class MotionFactory var translateYSetter = new Setter() { Property = TranslateTransform.YProperty, - Value = -offset + Value = 0.0 }; startFrame.Setters.Add(translateYSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + startFrame.Setters.Add(scaleYSetter); } animation.Children.Add(startFrame); + var middleFrame = new KeyFrame() + { + Cue = new Cue(0.35) + }; + { + var opacitySetter = new Setter() + { + Property = Visual.OpacityProperty, + Value = 0.0 + }; + middleFrame.Setters.Add(opacitySetter); + var translateYSetter = new Setter() + { + Property = TranslateTransform.YProperty, + Value = -offset / 2 + }; + middleFrame.Setters.Add(translateYSetter); + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + middleFrame.Setters.Add(scaleYSetter); + } + animation.Children.Add(middleFrame); + var endFrame = new KeyFrame() { Cue = new Cue(1.0) @@ -234,9 +386,16 @@ public static partial class MotionFactory var translateYSetter = new Setter() { Property = TranslateTransform.YProperty, - Value = 0.0 + Value = -offset }; endFrame.Setters.Add(translateYSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 0.0 + }; + endFrame.Setters.Add(scaleYSetter); } animation.Children.Add(endFrame); transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative); @@ -276,9 +435,42 @@ public static partial class MotionFactory Value = -offset }; startFrame.Setters.Add(translateXSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 0.0 + }; + startFrame.Setters.Add(scaleYSetter); } animation.Children.Add(startFrame); + var middleFrame = new KeyFrame() + { + Cue = new Cue(0.7) + }; + { + var opacitySetter = new Setter() + { + Property = Visual.OpacityProperty, + Value = 0.0 + }; + middleFrame.Setters.Add(opacitySetter); + var translateXSetter = new Setter() + { + Property = TranslateTransform.XProperty, + Value = -offset + }; + middleFrame.Setters.Add(translateXSetter); + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + middleFrame.Setters.Add(scaleYSetter); + } + animation.Children.Add(middleFrame); + var endFrame = new KeyFrame() { Cue = new Cue(1.0) @@ -296,6 +488,13 @@ public static partial class MotionFactory Value = 0.0 }; endFrame.Setters.Add(translateXSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + endFrame.Setters.Add(scaleYSetter); } animation.Children.Add(endFrame); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); @@ -335,9 +534,42 @@ public static partial class MotionFactory Value = 0.0 }; startFrame.Setters.Add(translateXSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + startFrame.Setters.Add(scaleYSetter); } animation.Children.Add(startFrame); + var middleFrame = new KeyFrame() + { + Cue = new Cue(0.75) + }; + { + var opacitySetter = new Setter() + { + Property = Visual.OpacityProperty, + Value = 1.0 + }; + middleFrame.Setters.Add(opacitySetter); + var translateXSetter = new Setter() + { + Property = TranslateTransform.XProperty, + Value = -offset + }; + middleFrame.Setters.Add(translateXSetter); + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 1.0 + }; + middleFrame.Setters.Add(scaleYSetter); + } + animation.Children.Add(middleFrame); + var endFrame = new KeyFrame() { Cue = new Cue(1.0) @@ -355,6 +587,13 @@ public static partial class MotionFactory Value = -offset }; endFrame.Setters.Add(translateXSetter); + + var scaleYSetter = new Setter() + { + Property = ScaleTransform.ScaleYProperty, + Value = 0.0 + }; + endFrame.Setters.Add(scaleYSetter); } animation.Children.Add(endFrame); transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative); @@ -394,7 +633,7 @@ public static partial class MotionFactory Value = offset }; startFrame.Setters.Add(translateXSetter); - + var scaleYSetter = new Setter() { Property = ScaleTransform.ScaleYProperty, @@ -403,8 +642,7 @@ public static partial class MotionFactory startFrame.Setters.Add(scaleYSetter); } animation.Children.Add(startFrame); - - + var middleFrame = new KeyFrame() { Cue = new Cue(0.7) @@ -448,7 +686,7 @@ public static partial class MotionFactory Value = 0.0 }; endFrame.Setters.Add(translateXSetter); - + var scaleYSetter = new Setter() { Property = ScaleTransform.ScaleYProperty, @@ -494,7 +732,7 @@ public static partial class MotionFactory Value = 0.0 }; startFrame.Setters.Add(translateXSetter); - + var scaleYSetter = new Setter() { Property = ScaleTransform.ScaleYProperty, @@ -503,7 +741,7 @@ public static partial class MotionFactory startFrame.Setters.Add(scaleYSetter); } animation.Children.Add(startFrame); - + var middleFrame = new KeyFrame() { Cue = new Cue(0.75) @@ -512,7 +750,7 @@ public static partial class MotionFactory var opacitySetter = new Setter() { Property = Visual.OpacityProperty, - Value = 0.0 + Value = 1.0 }; middleFrame.Setters.Add(opacitySetter); var translateXSetter = new Setter() @@ -529,7 +767,7 @@ public static partial class MotionFactory middleFrame.Setters.Add(scaleYSetter); } animation.Children.Add(middleFrame); - + var endFrame = new KeyFrame() { Cue = new Cue(1.0) @@ -547,7 +785,7 @@ public static partial class MotionFactory Value = offset }; endFrame.Setters.Add(translateXSetter); - + var scaleYSetter = new Setter() { Property = ScaleTransform.ScaleYProperty,