using System; using System.Collections; using System.Linq; using System.Threading.Tasks; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Controls.Notifications { /// /// An that displays notifications in a . /// [TemplatePart("PART_Items", typeof(Panel))] [PseudoClasses(":topleft", ":topright", ":bottomleft", ":bottomright")] public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager { private IList? _items; private AdornerLayer? adornerLayer; /// /// Defines the property. /// public static readonly StyledProperty PositionProperty = AvaloniaProperty.Register(nameof(Position), NotificationPosition.TopRight); /// /// Defines which corner of the screen notifications can be displayed in. /// /// public NotificationPosition Position { get { return GetValue(PositionProperty); } set { SetValue(PositionProperty, value); } } /// /// Defines the property. /// public static readonly StyledProperty MaxItemsProperty = AvaloniaProperty.Register(nameof(MaxItems), 5); /// /// Defines the maximum number of notifications visible at once. /// public int MaxItems { get { return GetValue(MaxItemsProperty); } set { SetValue(MaxItemsProperty, value); } } /// /// Defines the property /// public static readonly DirectProperty HostProperty = AvaloniaProperty.RegisterDirect( nameof(Host), o => o.Host, (o, v) => o.Host = v); private Visual? _Host; /// /// The Host that this NotificationManger should register to. If the Host is null, the Parent will be used. /// public Visual? Host { get { return _Host; } set { SetAndRaise(HostProperty, ref _Host, value); } } /// /// Initializes a new instance of the class. /// /// The TopLevel that will host the control. public WindowNotificationManager(TopLevel? host) : this() { Host = host; } /// /// Initializes a new instance of the class. /// public WindowNotificationManager() { UpdatePseudoClasses(Position); } static WindowNotificationManager() { HorizontalAlignmentProperty.OverrideDefaultValue(Layout.HorizontalAlignment.Stretch); VerticalAlignmentProperty.OverrideDefaultValue(Layout.VerticalAlignment.Stretch); } /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); var itemsControl = e.NameScope.Find("PART_Items"); _items = itemsControl?.Children; } /// public void Show(INotification content) { Show(content, content.Type, content.Expiration, content.OnClick, content.OnClose); } /// public async void Show(object content) { if (content is INotification notification) { Show(notification, notification.Type, notification.Expiration, notification.OnClick, notification.OnClose); } else { Show(content, NotificationType.Information); } } /// /// Shows a Notification /// /// the content of the notification /// the type of the notification /// the expiration time of the notification after which it will automatically close. If the value is Zero then the notification will remain open until the user closes it /// an Action to be run when the notification is clicked /// an Action to be run when the notification is closed /// style classes to apply public async void Show(object content, NotificationType type, TimeSpan? expiration = null, Action? onClick = null, Action? onClose = null, string[]? classes = null) { var notificationControl = new NotificationCard { Content = content, NotificationType = type }; // Add style classes if any if (classes != null) { foreach (var @class in classes) { notificationControl.Classes.Add(@class); } } notificationControl.NotificationClosed += (sender, args) => { onClose?.Invoke(); _items?.Remove(sender); }; notificationControl.PointerPressed += (sender, args) => { onClick?.Invoke(); (sender as NotificationCard)?.Close(); }; Dispatcher.UIThread.Post(() => { _items?.Add(notificationControl); if (_items?.OfType().Count(i => !i.IsClosing) > MaxItems) { _items.OfType().First(i => !i.IsClosing).Close(); } }); if (expiration == TimeSpan.Zero) { return; } await Task.Delay(expiration ?? TimeSpan.FromSeconds(5)); notificationControl.Close(); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == PositionProperty) { UpdatePseudoClasses(change.GetNewValue()); } if (change.Property == HostProperty) { Install(); } } /// /// Installs the within the /// private void Install() { // unregister from AdornerLayer if this control was already installed if (adornerLayer is not null && !adornerLayer.Children.Contains(this)) { adornerLayer.Children.Remove(this); } // Try to get the host. If host was null, use the TopLevel instead. var host = Host ?? Parent as Visual; if (host is null) throw new InvalidOperationException("NotificationControl cannot be installed. Host was not found."); adornerLayer = host is TopLevel ? host.FindDescendantOfType()?.AdornerLayer : AdornerLayer.GetAdornerLayer(host); if (adornerLayer is not null) { adornerLayer.Children.Add(this); AdornerLayer.SetAdornedElement(this, adornerLayer); } } private void UpdatePseudoClasses(NotificationPosition position) { PseudoClasses.Set(":topleft", position == NotificationPosition.TopLeft); PseudoClasses.Set(":topright", position == NotificationPosition.TopRight); PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft); PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight); } } }