WindowNotificationManager.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. using System;
  2. using System.Collections;
  3. using System.Linq;
  4. using System.Reactive.Linq;
  5. using System.Threading.Tasks;
  6. using Avalonia.Controls.Primitives;
  7. using Avalonia.Rendering;
  8. using Avalonia.Data;
  9. using Avalonia.VisualTree;
  10. namespace Avalonia.Controls.Notifications
  11. {
  12. /// <summary>
  13. /// An <see cref="INotificationManager"/> that displays notifications in a <see cref="Window"/>.
  14. /// </summary>
  15. public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
  16. {
  17. private IList _items;
  18. /// <summary>
  19. /// Defines the <see cref="Position"/> property.
  20. /// </summary>
  21. public static readonly StyledProperty<NotificationPosition> PositionProperty =
  22. AvaloniaProperty.Register<WindowNotificationManager, NotificationPosition>(nameof(Position), NotificationPosition.TopRight);
  23. /// <summary>
  24. /// Defines which corner of the screen notifications can be displayed in.
  25. /// </summary>
  26. /// <seealso cref="NotificationPosition"/>
  27. public NotificationPosition Position
  28. {
  29. get { return GetValue(PositionProperty); }
  30. set { SetValue(PositionProperty, value); }
  31. }
  32. /// <summary>
  33. /// Defines the <see cref="MaxItems"/> property.
  34. /// </summary>
  35. public static readonly StyledProperty<int> MaxItemsProperty =
  36. AvaloniaProperty.Register<WindowNotificationManager, int>(nameof(MaxItems), 5);
  37. /// <summary>
  38. /// Defines the maximum number of notifications visible at once.
  39. /// </summary>
  40. public int MaxItems
  41. {
  42. get { return GetValue(MaxItemsProperty); }
  43. set { SetValue(MaxItemsProperty, value); }
  44. }
  45. /// <summary>
  46. /// Initializes a new instance of the <see cref="WindowNotificationManager"/> class.
  47. /// </summary>
  48. /// <param name="host">The window that will host the control.</param>
  49. public WindowNotificationManager(Window host)
  50. {
  51. if (VisualChildren.Count != 0)
  52. {
  53. Install(host);
  54. }
  55. else
  56. {
  57. Observable.FromEventPattern<TemplateAppliedEventArgs>(host, nameof(host.TemplateApplied)).Take(1)
  58. .Subscribe(_ =>
  59. {
  60. Install(host);
  61. });
  62. }
  63. UpdatePseudoClasses(Position);
  64. }
  65. static WindowNotificationManager()
  66. {
  67. HorizontalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.HorizontalAlignment.Stretch);
  68. VerticalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.VerticalAlignment.Stretch);
  69. }
  70. /// <inheritdoc/>
  71. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  72. {
  73. var itemsControl = e.NameScope.Find<Panel>("PART_Items");
  74. _items = itemsControl?.Children;
  75. }
  76. /// <inheritdoc/>
  77. public void Show(INotification content)
  78. {
  79. Show(content as object);
  80. }
  81. /// <inheritdoc/>
  82. public async void Show(object content)
  83. {
  84. var notification = content as INotification;
  85. var notificationControl = new NotificationCard
  86. {
  87. Content = content
  88. };
  89. if (notification != null)
  90. {
  91. notificationControl.NotificationClosed += (sender, args) =>
  92. {
  93. notification.OnClose?.Invoke();
  94. _items.Remove(sender);
  95. };
  96. }
  97. notificationControl.PointerPressed += (sender, args) =>
  98. {
  99. if (notification != null && notification.OnClick != null)
  100. {
  101. notification.OnClick.Invoke();
  102. }
  103. (sender as NotificationCard)?.Close();
  104. };
  105. _items.Add(notificationControl);
  106. if (_items.OfType<NotificationCard>().Count(i => !i.IsClosing) > MaxItems)
  107. {
  108. _items.OfType<NotificationCard>().First(i => !i.IsClosing).Close();
  109. }
  110. if (notification != null && notification.Expiration == TimeSpan.Zero)
  111. {
  112. return;
  113. }
  114. await Task.Delay(notification?.Expiration ?? TimeSpan.FromSeconds(5));
  115. notificationControl.Close();
  116. }
  117. protected override void OnPropertyChanged<T>(
  118. AvaloniaProperty<T> property,
  119. Optional<T> oldValue,
  120. BindingValue<T> newValue,
  121. BindingPriority priority)
  122. {
  123. base.OnPropertyChanged(property, oldValue, newValue, priority);
  124. if (property == PositionProperty)
  125. {
  126. UpdatePseudoClasses(newValue.GetValueOrDefault<NotificationPosition>());
  127. }
  128. }
  129. /// <summary>
  130. /// Installs the <see cref="WindowNotificationManager"/> within the <see cref="AdornerLayer"/>
  131. /// of the host <see cref="Window"/>.
  132. /// </summary>
  133. /// <param name="host">The <see cref="Window"/> that will be the host.</param>
  134. private void Install(Window host)
  135. {
  136. var adornerLayer = host.FindDescendantOfType<VisualLayerManager>()?.AdornerLayer;
  137. adornerLayer?.Children.Add(this);
  138. }
  139. private void UpdatePseudoClasses(NotificationPosition position)
  140. {
  141. PseudoClasses.Set(":topleft", position == NotificationPosition.TopLeft);
  142. PseudoClasses.Set(":topright", position == NotificationPosition.TopRight);
  143. PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft);
  144. PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight);
  145. }
  146. public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
  147. }
  148. }