12 changed files with 323 additions and 336 deletions
@ -1,176 +1,40 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.LogicalTree; |
|||
|
|||
namespace Avalonia.Controls.Notifications |
|||
{ |
|||
public class Notification : ContentControl |
|||
/// <summary>
|
|||
/// Defines content for a <see cref="NotificationCard"/> control.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This notification content type is compatible with native notifications.
|
|||
/// </remarks>
|
|||
public class Notification : INotification |
|||
{ |
|||
private bool _isClosed; |
|||
private bool _isClosing; |
|||
|
|||
static Notification() |
|||
{ |
|||
IsClosedProperty.Changed.AddClassHandler<Notification>(IsClosedChanged); |
|||
} |
|||
|
|||
public Notification() |
|||
{ |
|||
this.GetObservable(ContentProperty) |
|||
.OfType<SimpleNotification>() |
|||
.Subscribe(x => |
|||
{ |
|||
switch (x.Type) |
|||
{ |
|||
case NotificationType.Error: |
|||
PseudoClasses.Add(":error"); |
|||
break; |
|||
|
|||
case NotificationType.Information: |
|||
PseudoClasses.Add(":information"); |
|||
break; |
|||
|
|||
case NotificationType.Success: |
|||
PseudoClasses.Add(":success"); |
|||
break; |
|||
|
|||
case NotificationType.Warning: |
|||
PseudoClasses.Add(":warning"); |
|||
break; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines if the notification is already closing.
|
|||
/// </summary>
|
|||
public bool IsClosing |
|||
{ |
|||
get { return _isClosing; } |
|||
private set { SetAndRaise(IsClosingProperty, ref _isClosing, value); } |
|||
} |
|||
|
|||
public static readonly DirectProperty<Notification, bool> IsClosingProperty = |
|||
AvaloniaProperty.RegisterDirect<Notification, bool>(nameof(IsClosing), o => o.IsClosing); |
|||
|
|||
/// <summary>
|
|||
/// Determines if the notification is closed.
|
|||
/// </summary>
|
|||
public bool IsClosed |
|||
{ |
|||
get { return _isClosed; } |
|||
set { SetAndRaise(IsClosedProperty, ref _isClosed, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsClosed"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<Notification, bool> IsClosedProperty = |
|||
AvaloniaProperty.RegisterDirect<Notification, bool>(nameof(IsClosed), o => o.IsClosed, (o, v) => o.IsClosed = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="NotificationCloseInvoked"/> event.
|
|||
/// </summary>
|
|||
public static readonly RoutedEvent<RoutedEventArgs> NotificationCloseInvokedEvent = |
|||
RoutedEvent.Register<Notification, RoutedEventArgs>(nameof(NotificationCloseInvoked), RoutingStrategies.Bubble); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="NotificationClosed"/> event.
|
|||
/// </summary>
|
|||
public static readonly RoutedEvent<RoutedEventArgs> NotificationClosedEvent = |
|||
RoutedEvent.Register<Notification, RoutedEventArgs>(nameof(NotificationClosed), RoutingStrategies.Bubble); |
|||
|
|||
/// <summary>
|
|||
/// Raised when notification close event is invoked.
|
|||
/// </summary>
|
|||
public event EventHandler<RoutedEventArgs> NotificationCloseInvoked |
|||
{ |
|||
add { AddHandler(NotificationCloseInvokedEvent, value); } |
|||
remove { RemoveHandler(NotificationCloseInvokedEvent, value); } |
|||
} |
|||
|
|||
public event EventHandler<RoutedEventArgs> NotificationClosed |
|||
{ |
|||
add { AddHandler(NotificationClosedEvent, value); } |
|||
remove { RemoveHandler(NotificationClosedEvent, value); } |
|||
} |
|||
|
|||
public static bool GetCloseOnClick(Notification obj) |
|||
{ |
|||
return (bool)obj.GetValue(CloseOnClickProperty); |
|||
} |
|||
|
|||
public static void SetCloseOnClick(Notification obj, bool value) |
|||
{ |
|||
obj.SetValue(CloseOnClickProperty, value); |
|||
} |
|||
|
|||
public static readonly AvaloniaProperty CloseOnClickProperty = |
|||
AvaloniaProperty.RegisterAttached<Button, bool>("CloseOnClick", typeof(Notification), validate: CloseOnClickChanged); |
|||
|
|||
private static bool CloseOnClickChanged(Button button, bool value) |
|||
{ |
|||
if (value) |
|||
{ |
|||
button.Click += Button_Click; |
|||
} |
|||
else |
|||
{ |
|||
button.Click -= Button_Click; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private static void Button_Click(object sender, RoutedEventArgs e) |
|||
{ |
|||
var btn = sender as ILogical; |
|||
var notification = btn.GetLogicalAncestors().OfType<Notification>().FirstOrDefault(); |
|||
notification?.Close(); |
|||
} |
|||
|
|||
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
|||
public Notification(string title, |
|||
string message, |
|||
NotificationType type = NotificationType.Information, |
|||
TimeSpan? expiration = null, |
|||
Action onClick = null, |
|||
Action onClose = null) |
|||
{ |
|||
base.OnTemplateApplied(e); |
|||
var closeButton = this.FindControl<Button>("PART_CloseButton"); |
|||
if (closeButton != null) |
|||
closeButton.Click += OnCloseButtonOnClick; |
|||
|
|||
Title = title; |
|||
Message = message; |
|||
Type = type; |
|||
Expiration = expiration.HasValue ? expiration.Value : TimeSpan.FromSeconds(5); |
|||
OnClick = onClick; |
|||
OnClose = onClose; |
|||
} |
|||
|
|||
private void OnCloseButtonOnClick(object sender, RoutedEventArgs args) |
|||
{ |
|||
var button = sender as Button; |
|||
if (button == null) |
|||
return; |
|||
|
|||
button.Click -= OnCloseButtonOnClick; |
|||
Close(); |
|||
} |
|||
public string Title { get; private set; } |
|||
|
|||
public void Close() |
|||
{ |
|||
if (IsClosing) |
|||
{ |
|||
return; |
|||
} |
|||
public string Message { get; private set; } |
|||
|
|||
IsClosing = true; |
|||
public NotificationType Type { get; private set; } |
|||
|
|||
RaiseEvent(new RoutedEventArgs(NotificationCloseInvokedEvent)); |
|||
} |
|||
public TimeSpan Expiration { get; private set; } |
|||
|
|||
private static void IsClosedChanged(Notification target, AvaloniaPropertyChangedEventArgs arg2) |
|||
{ |
|||
if (!target.IsClosing & !target.IsClosed) |
|||
{ |
|||
return; |
|||
} |
|||
public Action OnClick { get; private set; } |
|||
|
|||
target.RaiseEvent(new RoutedEventArgs(NotificationClosedEvent)); |
|||
} |
|||
public Action OnClose { get; private set; } |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,176 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.LogicalTree; |
|||
|
|||
namespace Avalonia.Controls.Notifications |
|||
{ |
|||
public class NotificationCard : ContentControl |
|||
{ |
|||
private bool _isClosed; |
|||
private bool _isClosing; |
|||
|
|||
static NotificationCard() |
|||
{ |
|||
IsClosedProperty.Changed.AddClassHandler<NotificationCard>(IsClosedChanged); |
|||
} |
|||
|
|||
public NotificationCard() |
|||
{ |
|||
this.GetObservable(ContentProperty) |
|||
.OfType<Notification>() |
|||
.Subscribe(x => |
|||
{ |
|||
switch (x.Type) |
|||
{ |
|||
case NotificationType.Error: |
|||
PseudoClasses.Add(":error"); |
|||
break; |
|||
|
|||
case NotificationType.Information: |
|||
PseudoClasses.Add(":information"); |
|||
break; |
|||
|
|||
case NotificationType.Success: |
|||
PseudoClasses.Add(":success"); |
|||
break; |
|||
|
|||
case NotificationType.Warning: |
|||
PseudoClasses.Add(":warning"); |
|||
break; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines if the notification is already closing.
|
|||
/// </summary>
|
|||
public bool IsClosing |
|||
{ |
|||
get { return _isClosing; } |
|||
private set { SetAndRaise(IsClosingProperty, ref _isClosing, value); } |
|||
} |
|||
|
|||
public static readonly DirectProperty<NotificationCard, bool> IsClosingProperty = |
|||
AvaloniaProperty.RegisterDirect<NotificationCard, bool>(nameof(IsClosing), o => o.IsClosing); |
|||
|
|||
/// <summary>
|
|||
/// Determines if the notification is closed.
|
|||
/// </summary>
|
|||
public bool IsClosed |
|||
{ |
|||
get { return _isClosed; } |
|||
set { SetAndRaise(IsClosedProperty, ref _isClosed, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsClosed"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<NotificationCard, bool> IsClosedProperty = |
|||
AvaloniaProperty.RegisterDirect<NotificationCard, bool>(nameof(IsClosed), o => o.IsClosed, (o, v) => o.IsClosed = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="NotificationCloseInvoked"/> event.
|
|||
/// </summary>
|
|||
public static readonly RoutedEvent<RoutedEventArgs> NotificationCloseInvokedEvent = |
|||
RoutedEvent.Register<NotificationCard, RoutedEventArgs>(nameof(NotificationCloseInvoked), RoutingStrategies.Bubble); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="NotificationClosed"/> event.
|
|||
/// </summary>
|
|||
public static readonly RoutedEvent<RoutedEventArgs> NotificationClosedEvent = |
|||
RoutedEvent.Register<NotificationCard, RoutedEventArgs>(nameof(NotificationClosed), RoutingStrategies.Bubble); |
|||
|
|||
/// <summary>
|
|||
/// Raised when notification close event is invoked.
|
|||
/// </summary>
|
|||
public event EventHandler<RoutedEventArgs> NotificationCloseInvoked |
|||
{ |
|||
add { AddHandler(NotificationCloseInvokedEvent, value); } |
|||
remove { RemoveHandler(NotificationCloseInvokedEvent, value); } |
|||
} |
|||
|
|||
public event EventHandler<RoutedEventArgs> NotificationClosed |
|||
{ |
|||
add { AddHandler(NotificationClosedEvent, value); } |
|||
remove { RemoveHandler(NotificationClosedEvent, value); } |
|||
} |
|||
|
|||
public static bool GetCloseOnClick(NotificationCard obj) |
|||
{ |
|||
return (bool)obj.GetValue(CloseOnClickProperty); |
|||
} |
|||
|
|||
public static void SetCloseOnClick(NotificationCard obj, bool value) |
|||
{ |
|||
obj.SetValue(CloseOnClickProperty, value); |
|||
} |
|||
|
|||
public static readonly AvaloniaProperty CloseOnClickProperty = |
|||
AvaloniaProperty.RegisterAttached<Button, bool>("CloseOnClick", typeof(NotificationCard), validate: CloseOnClickChanged); |
|||
|
|||
private static bool CloseOnClickChanged(Button button, bool value) |
|||
{ |
|||
if (value) |
|||
{ |
|||
button.Click += Button_Click; |
|||
} |
|||
else |
|||
{ |
|||
button.Click -= Button_Click; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private static void Button_Click(object sender, RoutedEventArgs e) |
|||
{ |
|||
var btn = sender as ILogical; |
|||
var notification = btn.GetLogicalAncestors().OfType<NotificationCard>().FirstOrDefault(); |
|||
notification?.Close(); |
|||
} |
|||
|
|||
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnTemplateApplied(e); |
|||
var closeButton = this.FindControl<Button>("PART_CloseButton"); |
|||
if (closeButton != null) |
|||
closeButton.Click += OnCloseButtonOnClick; |
|||
|
|||
} |
|||
|
|||
private void OnCloseButtonOnClick(object sender, RoutedEventArgs args) |
|||
{ |
|||
var button = sender as Button; |
|||
if (button == null) |
|||
return; |
|||
|
|||
button.Click -= OnCloseButtonOnClick; |
|||
Close(); |
|||
} |
|||
|
|||
public void Close() |
|||
{ |
|||
if (IsClosing) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
IsClosing = true; |
|||
|
|||
RaiseEvent(new RoutedEventArgs(NotificationCloseInvokedEvent)); |
|||
} |
|||
|
|||
private static void IsClosedChanged(NotificationCard target, AvaloniaPropertyChangedEventArgs arg2) |
|||
{ |
|||
if (!target.IsClosing & !target.IsClosed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
target.RaiseEvent(new RoutedEventArgs(NotificationClosedEvent)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace Avalonia.Controls.Notifications |
|||
{ |
|||
public enum NotificationType |
|||
{ |
|||
Information, |
|||
Success, |
|||
Warning, |
|||
Error |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Controls.Notifications |
|||
{ |
|||
/// <summary>
|
|||
/// Defines content for a <see cref="Notification"/> control.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This notification content type is compatible with native notifications.
|
|||
/// </remarks>
|
|||
public class SimpleNotification : INotification |
|||
{ |
|||
public static INotification Create ( |
|||
string title, |
|||
string message, |
|||
NotificationType type = NotificationType.Information, |
|||
TimeSpan? expiration = null, |
|||
Action onClick = null, |
|||
Action onClose = null) |
|||
{ |
|||
var result = new SimpleNotification |
|||
{ |
|||
Title = title, |
|||
Message = message, |
|||
Type = type, |
|||
Expiration = expiration.HasValue ? expiration.Value : TimeSpan.FromSeconds(5), |
|||
OnClick = onClick, |
|||
OnClose = onClose |
|||
}; |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public string Title { get; private set; } |
|||
|
|||
public string Message { get; private set; } |
|||
|
|||
public NotificationType Type { get; private set; } |
|||
|
|||
public TimeSpan Expiration { get; private set; } |
|||
|
|||
public Action OnClick { get; private set; } |
|||
|
|||
public Action OnClose { get; private set; } |
|||
} |
|||
|
|||
public enum NotificationType |
|||
{ |
|||
Information, |
|||
Success, |
|||
Warning, |
|||
Error |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Style Selector="NotificationCard"> |
|||
<Setter Property="UseLayoutRounding" Value="True"/> |
|||
<Setter Property="Width" Value="350"/> |
|||
<Setter Property="FontSize" Value="14"/> |
|||
<Setter Property="Foreground" Value="White"/> |
|||
<Setter Property="RenderTransformOrigin" Value="50%,75%"/> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<LayoutTransformControl Name="PART_LayoutTransformControl" UseRenderTransform="True"> |
|||
<Border Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
Margin="8,8,0,0"> |
|||
<ContentControl MinHeight="150" Content="{TemplateBinding Content}" /> |
|||
</Border> |
|||
</LayoutTransformControl> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
|
|||
<Style.Animations> |
|||
<Animation Duration="0:0:0.45" Easing="QuadraticEaseIn" FillMode="Forward"> |
|||
<KeyFrame Cue="0%"> |
|||
<Setter Property="Opacity" Value="0"/> |
|||
<Setter Property="TranslateTransform.Y" Value="20"/> |
|||
<Setter Property="ScaleTransform.ScaleX" Value="0.85"/> |
|||
<Setter Property="ScaleTransform.ScaleY" Value="0.85"/> |
|||
</KeyFrame> |
|||
<KeyFrame Cue="30%"> |
|||
<Setter Property="TranslateTransform.Y" Value="-20"/> |
|||
</KeyFrame> |
|||
<KeyFrame Cue="100%"> |
|||
<Setter Property="Opacity" Value="1"/> |
|||
<Setter Property="TranslateTransform.Y" Value="0"/> |
|||
<Setter Property="ScaleTransform.ScaleX" Value="1"/> |
|||
<Setter Property="ScaleTransform.ScaleY" Value="1"/> |
|||
</KeyFrame> |
|||
</Animation> |
|||
</Style.Animations> |
|||
</Style> |
|||
|
|||
<Style Selector="NotificationCard[IsClosing=true] /template/ LayoutTransformControl#PART_LayoutTransformControl"> |
|||
<Setter Property="RenderTransformOrigin" Value="50%,0%"/> |
|||
<Style.Animations> |
|||
<Animation Duration="0:0:0.75" Easing="QuadraticEaseOut" FillMode="Forward"> |
|||
<KeyFrame Cue="0%"> |
|||
<Setter Property="TranslateTransform.X" Value="0"/> |
|||
<Setter Property="ScaleTransform.ScaleY" Value="1"/> |
|||
</KeyFrame> |
|||
<KeyFrame Cue="70%"> |
|||
<Setter Property="TranslateTransform.X" Value="800"/> |
|||
<Setter Property="ScaleTransform.ScaleY" Value="1"/> |
|||
</KeyFrame> |
|||
<KeyFrame Cue="100%"> |
|||
<Setter Property="ScaleTransform.ScaleY" Value="0"/> |
|||
<Setter Property="TranslateTransform.X" Value="800"/> |
|||
</KeyFrame> |
|||
</Animation> |
|||
</Style.Animations> |
|||
</Style> |
|||
|
|||
<Style Selector="NotificationCard[IsClosing=true]"> |
|||
<Style.Animations> |
|||
<Animation Duration="0:0:1.25" Easing="QuadraticEaseOut" FillMode="Forward"> |
|||
<KeyFrame Cue="100%"> |
|||
<Setter Property="IsClosed" Value="True"/> |
|||
</KeyFrame> |
|||
</Animation> |
|||
</Style.Animations> |
|||
</Style> |
|||
|
|||
<Style Selector="NotificationCard /template/ Button#PART_CloseButton"> |
|||
<Setter Property="Template"> |
|||
<Setter.Value> |
|||
<ControlTemplate TargetType="Button"> |
|||
<TextBlock Text="X" FontSize="12" /> |
|||
</ControlTemplate> |
|||
</Setter.Value> |
|||
</Setter> |
|||
<Setter Property="HorizontalAlignment" Value="Right"/> |
|||
<Setter Property="VerticalAlignment" Value="Top"/> |
|||
</Style> |
|||
|
|||
<Style Selector="NotificationCard"> |
|||
<Setter Property="Background" Value="#444444"/> |
|||
</Style> |
|||
<Style Selector="NotificationCard:information"> |
|||
<Setter Property="Background" Value="Teal"/> |
|||
</Style> |
|||
<Style Selector="NotificationCard:success"> |
|||
<Setter Property="Background" Value="LimeGreen"/> |
|||
</Style> |
|||
<Style Selector="NotificationCard:warning"> |
|||
<Setter Property="Background" Value="Orange"/> |
|||
</Style> |
|||
<Style Selector="NotificationCard:error"> |
|||
<Setter Property="Background" Value="OrangeRed"/> |
|||
</Style> |
|||
</Styles> |
|||
Loading…
Reference in new issue