mirror of
https://gitee.com/chinware/atomui.git
synced 2024-11-29 18:38:16 +08:00
完成 Notification 关闭功能
This commit is contained in:
parent
81c0b7dada
commit
3b8c81678d
@ -17,15 +17,20 @@ public partial class NotificationShowCase : UserControl
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
_windowNotificationManager = new WindowNotificationManager(topLevel) { MaxItems = 5 };
|
||||
_windowNotificationManager = new WindowNotificationManager(topLevel)
|
||||
{
|
||||
MaxItems = 5,
|
||||
IsPauseOnHover = true
|
||||
};
|
||||
}
|
||||
|
||||
private void ShowSimpleNotification(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
_windowNotificationManager?.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. This is the content of the notification. This is the content of the notification."
|
||||
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."
|
||||
});
|
||||
}
|
||||
}
|
@ -317,7 +317,9 @@ namespace AtomUI.Theme.Styling
|
||||
public static readonly TokenResourceKey NotificationMarginEdge = new TokenResourceKey("Notification.NotificationMarginEdge", "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");
|
||||
public static readonly TokenResourceKey NotificationWidth = new TokenResourceKey("Notification.NotificationWidth", "AtomUI.Token");
|
||||
public static readonly TokenResourceKey NotificationContentMargin = new TokenResourceKey("Notification.NotificationContentMargin", "AtomUI.Token");
|
||||
public static readonly TokenResourceKey HeaderMargin = new TokenResourceKey("Notification.HeaderMargin", "AtomUI.Token");
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,11 @@ public interface INotification
|
||||
/// If the value is <see cref="TimeSpan.Zero"/> then the notification will remain open until the user closes it.
|
||||
/// </summary>
|
||||
TimeSpan Expiration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 显示一个进度条
|
||||
/// </summary>
|
||||
bool ShowProgress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an Action to be run when the notification is clicked.
|
||||
|
@ -7,6 +7,7 @@ public class Notification : INotification, INotifyPropertyChanged
|
||||
{
|
||||
private string? _title;
|
||||
private object? _content;
|
||||
private bool _showProgress;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Notification"/> class.
|
||||
@ -22,6 +23,7 @@ public class Notification : INotification, INotifyPropertyChanged
|
||||
object? content,
|
||||
NotificationType type = NotificationType.Information,
|
||||
TimeSpan? expiration = null,
|
||||
bool showProgress = false,
|
||||
Action? onClick = null,
|
||||
Action? onClose = null)
|
||||
{
|
||||
@ -29,6 +31,7 @@ public class Notification : INotification, INotifyPropertyChanged
|
||||
_content = content;
|
||||
Type = type;
|
||||
Expiration = expiration.HasValue ? expiration.Value : TimeSpan.FromSeconds(5);
|
||||
ShowProgress = showProgress;
|
||||
OnClick = onClick;
|
||||
OnClose = onClose;
|
||||
}
|
||||
@ -63,6 +66,18 @@ public class Notification : INotification, INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowProgress
|
||||
{
|
||||
get => _showProgress;
|
||||
set
|
||||
{
|
||||
if (_showProgress != value) {
|
||||
_showProgress = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public NotificationType Type { get; set; }
|
||||
|
@ -6,9 +6,8 @@ using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
@ -34,6 +33,9 @@ public class NotificationCard : TemplatedControl
|
||||
public static readonly StyledProperty<bool> IsClosedProperty =
|
||||
AvaloniaProperty.Register<NotificationCard, bool>(nameof(IsClosed));
|
||||
|
||||
public static readonly StyledProperty<bool> IsShowProgressProperty =
|
||||
AvaloniaProperty.Register<NotificationCard, bool>(nameof(IsShowProgress), false);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="NotificationType" /> property
|
||||
/// </summary>
|
||||
@ -46,12 +48,6 @@ public class NotificationCard : TemplatedControl
|
||||
public static readonly RoutedEvent<RoutedEventArgs> NotificationClosedEvent =
|
||||
RoutedEvent.Register<NotificationCard, RoutedEventArgs>(nameof(NotificationClosed), RoutingStrategies.Bubble);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the CloseOnClick property.
|
||||
/// </summary>
|
||||
public static readonly AttachedProperty<bool> CloseOnClickProperty =
|
||||
AvaloniaProperty.RegisterAttached<NotificationCard, Button, bool>("CloseOnClick", defaultValue: false);
|
||||
|
||||
public static readonly StyledProperty<string?> TitleProperty =
|
||||
AvaloniaProperty.Register<NotificationCard, string?>(nameof(Title));
|
||||
|
||||
@ -82,6 +78,12 @@ public class NotificationCard : TemplatedControl
|
||||
set => SetValue(IsClosedProperty, value);
|
||||
}
|
||||
|
||||
public bool IsShowProgress
|
||||
{
|
||||
get => GetValue(IsShowProgressProperty);
|
||||
set => SetValue(IsShowProgressProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the notification
|
||||
/// </summary>
|
||||
@ -126,59 +128,39 @@ public class NotificationCard : TemplatedControl
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部属性定义
|
||||
|
||||
internal static readonly DirectProperty<NotificationCard, bool> EffectiveShowProgressProperty =
|
||||
AvaloniaProperty.RegisterDirect<NotificationCard, bool>(nameof(EffectiveShowProgress),
|
||||
o => o.EffectiveShowProgress);
|
||||
|
||||
private bool _effectiveShowProgress;
|
||||
internal bool EffectiveShowProgress
|
||||
{
|
||||
get => _effectiveShowProgress;
|
||||
set => SetAndRaise(EffectiveShowProgressProperty, ref _effectiveShowProgress, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expiration time of the notification after which it will automatically close.
|
||||
/// If the value is null then the notification will remain open until the user closes it.
|
||||
/// </summary>
|
||||
public TimeSpan? Expiration { get; set; }
|
||||
|
||||
public static bool GetCloseOnClick(Button obj)
|
||||
{
|
||||
_ = obj ?? throw new ArgumentNullException(nameof(obj));
|
||||
return (bool)obj.GetValue(CloseOnClickProperty);
|
||||
}
|
||||
|
||||
public static void SetCloseOnClick(Button obj, bool value)
|
||||
{
|
||||
_ = obj ?? throw new ArgumentNullException(nameof(obj));
|
||||
obj.SetValue(CloseOnClickProperty, value);
|
||||
}
|
||||
|
||||
private bool _isClosing;
|
||||
|
||||
static NotificationCard()
|
||||
{
|
||||
CloseOnClickProperty.Changed.AddClassHandler<Button>(OnCloseOnClickPropertyChanged);
|
||||
}
|
||||
|
||||
private NotificationProgressBar? _progressBar;
|
||||
private WindowNotificationManager _notificationManager;
|
||||
private IconButton? _closeButton;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationCard"/> class.
|
||||
/// </summary>
|
||||
public NotificationCard()
|
||||
public NotificationCard(WindowNotificationManager manager)
|
||||
{
|
||||
UpdateNotificationType();
|
||||
ClipToBounds = false;
|
||||
}
|
||||
|
||||
private static void OnCloseOnClickPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
var button = (Button)d;
|
||||
var value = (bool)e.NewValue!;
|
||||
if (value) {
|
||||
button.Click += Button_Click;
|
||||
} else {
|
||||
button.Click -= Button_Click;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a button inside the Notification is clicked.
|
||||
/// </summary>
|
||||
private static void Button_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var btn = sender as ILogical;
|
||||
var notification = btn?.GetLogicalAncestors().OfType<NotificationCard>().FirstOrDefault();
|
||||
notification?.Close();
|
||||
_notificationManager = manager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -201,6 +183,26 @@ public class NotificationCard : TemplatedControl
|
||||
SetupNotificationIcon();
|
||||
UpdateNotificationType();
|
||||
}
|
||||
|
||||
_progressBar = e.NameScope.Find<NotificationProgressBar>(NotificationCardTheme.ProgressBarPart);
|
||||
_closeButton = e.NameScope.Find<IconButton>(NotificationCardTheme.CloseButtonPart);
|
||||
if (_progressBar is not null) {
|
||||
if (Expiration is null) {
|
||||
_progressBar.IsVisible = false;
|
||||
} else {
|
||||
_progressBar.Expiration = Expiration.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (_closeButton is not null) {
|
||||
_closeButton.Click += HandleCloseButtonClose;
|
||||
}
|
||||
SetupEffectiveShowProgress();
|
||||
}
|
||||
|
||||
private void HandleCloseButtonClose(object? sender, EventArgs args)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
@ -225,6 +227,24 @@ public class NotificationCard : TemplatedControl
|
||||
SetupContent();
|
||||
}
|
||||
}
|
||||
|
||||
if (e.Property == IsShowProgressProperty ||
|
||||
e.Property == IsClosedProperty) {
|
||||
SetupEffectiveShowProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupEffectiveShowProgress()
|
||||
{
|
||||
if (!IsShowProgress) {
|
||||
EffectiveShowProgress = false;
|
||||
} else {
|
||||
if (Expiration is not null) {
|
||||
EffectiveShowProgress = true;
|
||||
} else {
|
||||
EffectiveShowProgress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNotificationType()
|
||||
@ -311,14 +331,42 @@ public class NotificationCard : TemplatedControl
|
||||
|
||||
internal bool NotifyCloseTick(TimeSpan cycleDuration)
|
||||
{
|
||||
InvalidateVisual();
|
||||
if (Expiration is null) {
|
||||
return false;
|
||||
}
|
||||
Expiration -= cycleDuration;
|
||||
if (_progressBar is not null) {
|
||||
_progressBar.CurrentExpiration = Expiration.Value;
|
||||
}
|
||||
if (Expiration.Value.TotalMilliseconds < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnPointerEntered(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerEntered(e);
|
||||
if (_notificationManager.IsPauseOnHover) {
|
||||
_notificationManager.StopExpiredTimer();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerMoved(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerMoved(e);
|
||||
if (_notificationManager.IsPauseOnHover) {
|
||||
_notificationManager.StopExpiredTimer();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerExited(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerExited(e);
|
||||
if (_notificationManager.IsPauseOnHover) {
|
||||
_notificationManager.StartExpiredTimer();
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ internal class NotificationCardTheme : BaseControlTheme
|
||||
public const string ContentPart = "PART_Content";
|
||||
public const string CloseButtonPart = "PART_CloseButton";
|
||||
public const string LayoutTransformControlPart = "PART_LayoutTransformControl";
|
||||
public const string ProgressBarPart = "PART_ProgressBar";
|
||||
|
||||
public NotificationCardTheme()
|
||||
: base(typeof(NotificationCard))
|
||||
@ -58,13 +59,15 @@ internal class NotificationCardTheme : BaseControlTheme
|
||||
RowDefinitions = new RowDefinitions()
|
||||
{
|
||||
new RowDefinition(GridLength.Auto),
|
||||
new RowDefinition(GridLength.Star)
|
||||
new RowDefinition(GridLength.Star),
|
||||
new RowDefinition(GridLength.Auto)
|
||||
}
|
||||
};
|
||||
|
||||
frameDecorator.Child = mainLayout;
|
||||
BuildHeader(mainLayout, scope);
|
||||
BuildContent(mainLayout, scope);
|
||||
BuildProgressBar(mainLayout, scope);
|
||||
frameDecorator.RegisterInNameScope(scope);
|
||||
|
||||
layoutTransformControl.Child = frameDecorator;
|
||||
@ -119,6 +122,7 @@ internal class NotificationCardTheme : BaseControlTheme
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
closeIconButton.RegisterInNameScope(scope);
|
||||
TokenResourceBinder.CreateTokenBinding(closeIconButton, IconButton.PaddingProperty,
|
||||
GlobalTokenResourceKey.PaddingXXS, BindingPriority.Template,
|
||||
o =>
|
||||
@ -149,8 +153,9 @@ internal class NotificationCardTheme : BaseControlTheme
|
||||
var contentPresenter = new ContentPresenter()
|
||||
{
|
||||
Name = ContentPart,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
};
|
||||
TokenResourceBinder.CreateTokenBinding(contentPresenter, ContentPresenter.MarginProperty, NotificationTokenResourceKey.NotificationContentMargin);
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty,
|
||||
NotificationCard.CardContentProperty);
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty,
|
||||
@ -160,6 +165,21 @@ internal class NotificationCardTheme : BaseControlTheme
|
||||
layout.Children.Add(contentPresenter);
|
||||
}
|
||||
|
||||
private void BuildProgressBar(Grid layout, INameScope scope)
|
||||
{
|
||||
var progressBar = new NotificationProgressBar()
|
||||
{
|
||||
Name = ProgressBarPart
|
||||
};
|
||||
progressBar.RegisterInNameScope(scope);
|
||||
CreateTemplateParentBinding(progressBar, NotificationProgressBar.IsVisibleProperty, NotificationCard.EffectiveShowProgressProperty);
|
||||
TokenResourceBinder.CreateTokenBinding(progressBar, NotificationProgressBar.MarginProperty, NotificationTokenResourceKey.NotificationProgressMargin);
|
||||
Grid.SetColumn(progressBar, 0);
|
||||
Grid.SetRow(progressBar, 1);
|
||||
Grid.SetColumnSpan(progressBar, 2);
|
||||
layout.Children.Add(progressBar);
|
||||
}
|
||||
|
||||
protected override void BuildStyles()
|
||||
{
|
||||
BuildCommonStyle();
|
||||
@ -218,40 +238,50 @@ internal class NotificationCardTheme : BaseControlTheme
|
||||
private void BuildAnimationStyle()
|
||||
{
|
||||
var commonStyle = new Style(selector => selector.Nesting());
|
||||
var moveRightInMotionConfig = MotionFactory.BuildMoveRightInMotion(400, TimeSpan.FromMilliseconds(400), new QuadraticEaseOut(),
|
||||
FillMode.Forward);
|
||||
foreach (var animation in moveRightInMotionConfig.Animations) {
|
||||
commonStyle.Animations.Add(animation);
|
||||
|
||||
|
||||
{
|
||||
var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart));
|
||||
var moveRightInMotionConfig = MotionFactory.BuildMoveRightInMotion(400, TimeSpan.FromMilliseconds(400), new QuadraticEaseOut(),
|
||||
FillMode.Forward);
|
||||
foreach (var animation in moveRightInMotionConfig.Animations) {
|
||||
layoutTransformStyle.Animations.Add(animation);
|
||||
}
|
||||
|
||||
commonStyle.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 QuadraticEaseOut(), FillMode.Forward);
|
||||
|
||||
foreach (var animation in moveRightOutMotionConfig.Animations) {
|
||||
layoutTransformStyle.Animations.Add(animation);
|
||||
}
|
||||
|
||||
isClosingStyle.Animations.Add(new Animation()
|
||||
{
|
||||
Duration = TimeSpan.FromMilliseconds(600),
|
||||
Easing = new QuadraticEaseOut(),
|
||||
FillMode = FillMode.Forward,
|
||||
Children =
|
||||
var layoutTransformStyle = new Style(selector => selector.Nesting().Template().Name(LayoutTransformControlPart));
|
||||
|
||||
var moveRightOutMotionConfig = MotionFactory.BuildMoveRightOutMotion(400, TimeSpan.FromMilliseconds(400), new QuadraticEaseIn(), FillMode.Forward);
|
||||
|
||||
foreach (var animation in moveRightOutMotionConfig.Animations) {
|
||||
layoutTransformStyle.Animations.Add(animation);
|
||||
}
|
||||
|
||||
isClosingStyle.Animations.Add(new Animation()
|
||||
{
|
||||
new KeyFrame()
|
||||
Duration = TimeSpan.FromMilliseconds(600),
|
||||
Easing = new QuadraticEaseIn(),
|
||||
FillMode = FillMode.Forward,
|
||||
Children =
|
||||
{
|
||||
Cue = new Cue(1.0),
|
||||
Setters =
|
||||
new KeyFrame()
|
||||
{
|
||||
new Setter(NotificationCard.IsClosedProperty, true),
|
||||
new Setter(NotificationCard.MarginProperty, new Thickness(0)),
|
||||
Cue = new Cue(1.0),
|
||||
Setters =
|
||||
{
|
||||
new Setter(NotificationCard.IsClosedProperty, true),
|
||||
new Setter(NotificationCard.MarginProperty, new Thickness(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
isClosingStyle.Add(layoutTransformStyle);
|
||||
});
|
||||
|
||||
isClosingStyle.Add(layoutTransformStyle);
|
||||
}
|
||||
commonStyle.Add(isClosingStyle);
|
||||
|
||||
Add(commonStyle);
|
||||
|
83
src/AtomUI.Controls/Notifications/NotificationProgressBar.cs
Normal file
83
src/AtomUI.Controls/Notifications/NotificationProgressBar.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using AtomUI.Theme.Styling;
|
||||
using AtomUI.Utils;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
internal class NotificationProgressBar : Control
|
||||
{
|
||||
#region 公共属性定义
|
||||
|
||||
public static readonly StyledProperty<double> ProgressIndicatorThicknessProperty =
|
||||
AvaloniaProperty.Register<NotificationProgressBar, double>(nameof(ProgressIndicatorThickness));
|
||||
|
||||
public static readonly StyledProperty<IBrush?> ProgressIndicatorBrushProperty =
|
||||
AvaloniaProperty.Register<NotificationProgressBar, IBrush?>(nameof(ProgressIndicatorBrush));
|
||||
|
||||
public static readonly StyledProperty<TimeSpan> ExpirationProperty =
|
||||
AvaloniaProperty.Register<NotificationProgressBar, TimeSpan>(nameof(Expiration));
|
||||
|
||||
public static readonly StyledProperty<TimeSpan> CurrentExpirationProperty =
|
||||
AvaloniaProperty.Register<NotificationProgressBar, TimeSpan>(nameof(CurrentExpiration));
|
||||
|
||||
internal double ProgressIndicatorThickness
|
||||
{
|
||||
get => GetValue(ProgressIndicatorThicknessProperty);
|
||||
set => SetValue(ProgressIndicatorThicknessProperty, value);
|
||||
}
|
||||
|
||||
public IBrush? ProgressIndicatorBrush
|
||||
{
|
||||
get => GetValue(ProgressIndicatorBrushProperty);
|
||||
set => SetValue(ProgressIndicatorBrushProperty, value);
|
||||
}
|
||||
|
||||
public TimeSpan Expiration
|
||||
{
|
||||
get => GetValue(ExpirationProperty);
|
||||
set => SetValue(ExpirationProperty, value);
|
||||
}
|
||||
|
||||
public TimeSpan CurrentExpiration
|
||||
{
|
||||
get => GetValue(CurrentExpirationProperty);
|
||||
set => SetValue(CurrentExpirationProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
static NotificationProgressBar()
|
||||
{
|
||||
AffectsMeasure<NotificationProgressBar>(ProgressIndicatorThicknessProperty);
|
||||
AffectsRender<NotificationProgressBar>(ProgressIndicatorBrushProperty,
|
||||
ExpirationProperty,
|
||||
CurrentExpirationProperty);
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
return new Size(availableSize.Width, ProgressIndicatorThickness);
|
||||
}
|
||||
|
||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToLogicalTree(e);
|
||||
TokenResourceBinder.CreateTokenBinding(this, ProgressIndicatorThicknessProperty, NotificationTokenResourceKey.NotificationProgressHeight);
|
||||
TokenResourceBinder.CreateTokenBinding(this, ProgressIndicatorBrushProperty, NotificationTokenResourceKey.NotificationProgressBg);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
var indicatorWidth = 0d;
|
||||
var total = Expiration.TotalMilliseconds;
|
||||
if (MathUtils.GreaterThan(total, 0)) {
|
||||
indicatorWidth = (CurrentExpiration.TotalMilliseconds / total) * Bounds.Width;
|
||||
}
|
||||
var offsetY = Bounds.Height - ProgressIndicatorThickness;
|
||||
var indicatorRect = new Rect(new Point(0, offsetY), new Size(indicatorWidth, ProgressIndicatorThickness));
|
||||
context.FillRectangle(ProgressIndicatorBrush!, indicatorRect);
|
||||
}
|
||||
}
|
@ -64,11 +64,21 @@ internal class NotificationToken : AbstractControlDesignToken
|
||||
/// </summary>
|
||||
public double NotificationProgressHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 进度条外边距
|
||||
/// </summary>
|
||||
public Thickness NotificationProgressMargin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 提醒框宽度
|
||||
/// </summary>
|
||||
public double NotificationWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 内容外边距
|
||||
/// </summary>
|
||||
public Thickness NotificationContentMargin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题栏的外边距
|
||||
/// </summary>
|
||||
@ -77,15 +87,17 @@ internal class NotificationToken : AbstractControlDesignToken
|
||||
internal override void CalculateFromAlias()
|
||||
{
|
||||
base.CalculateFromAlias();
|
||||
NotificationPadding = new Thickness(_globalToken.PaddingLG, _globalToken.PaddingMD);
|
||||
NotificationProgressHeight = 2;
|
||||
NotificationProgressMargin = new Thickness(0, 0, 0, 1);
|
||||
NotificationContentMargin = new Thickness(0, 0, 0, _globalToken.PaddingMD);
|
||||
NotificationPadding = new Thickness(_globalToken.PaddingLG, _globalToken.PaddingMD, _globalToken.PaddingLG, 0);
|
||||
NotificationBg = _globalToken.ColorToken.ColorNeutralToken.ColorBgElevated;
|
||||
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;
|
||||
|
||||
NotificationProgressHeight = 2;
|
||||
|
||||
NotificationProgressBg = new LinearGradientBrush()
|
||||
{
|
||||
StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
|
||||
|
@ -23,28 +23,37 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager
|
||||
|
||||
private IList? _items;
|
||||
private Queue<NotificationCard> _cleanupQueue;
|
||||
private DispatcherTimer _dispatcherTimer;
|
||||
private DispatcherTimer _cardExpiredTimer;
|
||||
private DispatcherTimer _cleanupTimer;
|
||||
|
||||
public static readonly StyledProperty<NotificationPosition> PositionProperty =
|
||||
AvaloniaProperty.Register<WindowNotificationManager, NotificationPosition>(
|
||||
nameof(Position), NotificationPosition.TopRight);
|
||||
|
||||
public static readonly StyledProperty<int> MaxItemsProperty =
|
||||
AvaloniaProperty.Register<WindowNotificationManager, int>(nameof(MaxItems), 5);
|
||||
|
||||
public static readonly StyledProperty<bool> IsPauseOnHoverProperty =
|
||||
AvaloniaProperty.Register<WindowNotificationManager, bool>(nameof(IsPauseOnHover), false);
|
||||
|
||||
public NotificationPosition Position
|
||||
{
|
||||
get => GetValue(PositionProperty);
|
||||
set => SetValue(PositionProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<int> MaxItemsProperty =
|
||||
AvaloniaProperty.Register<WindowNotificationManager, int>(nameof(MaxItems), 5);
|
||||
|
||||
public int MaxItems
|
||||
{
|
||||
get => GetValue(MaxItemsProperty);
|
||||
set => SetValue(MaxItemsProperty, value);
|
||||
}
|
||||
|
||||
public bool IsPauseOnHover
|
||||
{
|
||||
get => GetValue(IsPauseOnHoverProperty);
|
||||
set => SetValue(IsPauseOnHoverProperty, value);
|
||||
}
|
||||
|
||||
public WindowNotificationManager(TopLevel? host) : this()
|
||||
{
|
||||
if (host is not null) {
|
||||
@ -55,8 +64,8 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager
|
||||
public WindowNotificationManager()
|
||||
{
|
||||
UpdatePseudoClasses(Position);
|
||||
_dispatcherTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50), Tag = this };
|
||||
_dispatcherTimer.Tick += HandleTimerTick;
|
||||
_cardExpiredTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50), Tag = this };
|
||||
_cardExpiredTimer.Tick += HandleCardExpiredTimer;
|
||||
_cleanupTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50), Tag = this };
|
||||
_cleanupTimer.Tick += HandleCleanupTimerTick;
|
||||
_cleanupQueue = new Queue<NotificationCard>();
|
||||
@ -79,12 +88,12 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTimerTick(object? sender, EventArgs eventArgs)
|
||||
private void HandleCardExpiredTimer(object? sender, EventArgs eventArgs)
|
||||
{
|
||||
if (_items is not null) {
|
||||
foreach (var item in _items) {
|
||||
if (item is NotificationCard card) {
|
||||
if (card.NotifyCloseTick(_dispatcherTimer.Interval)) {
|
||||
if (card.NotifyCloseTick(_cardExpiredTimer.Interval)) {
|
||||
if (!_cleanupQueue.Contains(card)) {
|
||||
_cleanupQueue.Enqueue(card);
|
||||
if (!_cleanupTimer.IsEnabled) {
|
||||
@ -116,9 +125,9 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager
|
||||
{
|
||||
if (_items is not null) {
|
||||
if (_items.Count > 0) {
|
||||
_dispatcherTimer.Start();
|
||||
_cardExpiredTimer.Start();
|
||||
} else {
|
||||
_dispatcherTimer.Stop();
|
||||
_cardExpiredTimer.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -130,12 +139,13 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager
|
||||
var onClose = notification.OnClose;
|
||||
Dispatcher.UIThread.VerifyAccess();
|
||||
|
||||
var notificationControl = new NotificationCard
|
||||
var notificationControl = new NotificationCard(this)
|
||||
{
|
||||
Title = notification.Title,
|
||||
CardContent = notification.Content,
|
||||
NotificationType = notification.Type,
|
||||
Expiration = expiration == TimeSpan.Zero ? null : expiration
|
||||
Expiration = expiration == TimeSpan.Zero ? null : expiration,
|
||||
IsShowProgress = notification.ShowProgress
|
||||
};
|
||||
|
||||
// Add style classes if any
|
||||
@ -210,4 +220,14 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager
|
||||
PseudoClasses.Set(TopCenterPC, position == NotificationPosition.TopCenter);
|
||||
PseudoClasses.Set(BottomCenterPC, position == NotificationPosition.BottomCenter);
|
||||
}
|
||||
|
||||
internal void StopExpiredTimer()
|
||||
{
|
||||
_cardExpiredTimer.Stop();
|
||||
}
|
||||
|
||||
internal void StartExpiredTimer()
|
||||
{
|
||||
_cardExpiredTimer.Start();
|
||||
}
|
||||
}
|
@ -44,6 +44,7 @@ internal class WindowNotificationManagerTheme : BaseControlTheme
|
||||
var topRightStyle = new Style(selector => selector.Nesting().Class(WindowNotificationManager.TopRightPC));
|
||||
{
|
||||
var itemsStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPart));
|
||||
itemsStyle.Add(ReversibleStackPanel.ReverseOrderProperty, true);
|
||||
itemsStyle.Add(ReversibleStackPanel.HorizontalAlignmentProperty, HorizontalAlignment.Right);
|
||||
itemsStyle.Add(ReversibleStackPanel.VerticalAlignmentProperty, VerticalAlignment.Top);
|
||||
topRightStyle.Add(itemsStyle);
|
||||
|
@ -394,8 +394,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()
|
||||
{
|
||||
@ -414,6 +448,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);
|
||||
|
Loading…
Reference in New Issue
Block a user