diff --git a/src/Artemis.UI.Shared/Controls/NotificationHost.cs b/src/Artemis.UI.Shared/Controls/NotificationHost.cs new file mode 100644 index 000000000..4dce59804 --- /dev/null +++ b/src/Artemis.UI.Shared/Controls/NotificationHost.cs @@ -0,0 +1,54 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Layout; + +namespace Artemis.UI.Shared; + +internal class NotificationHost : ContentControl +{ + private IDisposable? _rootBoundsWatcher; + + public NotificationHost() + { + Background = null; + HorizontalAlignment = HorizontalAlignment.Center; + VerticalAlignment = VerticalAlignment.Center; + } + + protected override Type StyleKeyOverride => typeof(OverlayPopupHost); + + protected override Size MeasureOverride(Size availableSize) + { + _ = base.MeasureOverride(availableSize); + + if (VisualRoot is TopLevel tl) + return tl.ClientSize; + if (VisualRoot is Control c) + return c.Bounds.Size; + + return default; + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + if (e.Root is Control wb) + // OverlayLayer is a Canvas, so we won't get a signal to resize if the window + // bounds change. Subscribe to force update + _rootBoundsWatcher = wb.GetObservable(BoundsProperty).Subscribe(_ => OnRootBoundsChanged()); + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + _rootBoundsWatcher?.Dispose(); + _rootBoundsWatcher = null; + } + + private void OnRootBoundsChanged() + { + InvalidateMeasure(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs b/src/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs index 5dfe0ecb9..805c5cbd0 100644 --- a/src/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs +++ b/src/Artemis.UI.Shared/Services/Builders/NotificationBuilder.cs @@ -2,11 +2,11 @@ using System.Threading.Tasks; using System.Windows.Input; using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Layout; using Avalonia.Threading; using FluentAvalonia.UI.Controls; using ReactiveUI; -using Button = Avalonia.Controls.Button; namespace Artemis.UI.Shared.Services.Builders; @@ -117,34 +117,34 @@ public class NotificationBuilder /// public Action Show() { - Panel? panel = _parent.Find("NotificationContainer"); - if (panel == null) - throw new ArtemisSharedUIException("Can't display a notification on a window without a NotificationContainer."); - Dispatcher.UIThread.Post(() => { - panel.Children.Add(_infoBar); + OverlayLayer? overlayLayer = OverlayLayer.GetOverlayLayer(_parent); + if (overlayLayer == null) + throw new ArtemisSharedUIException("Can't display a notification on a window an overlay layer."); + + NotificationHost container = new() {Content = _infoBar}; + overlayLayer.Children.Add(container); _infoBar.Closed += InfoBarOnClosed; _infoBar.IsOpen = true; - }); - Task.Run(async () => - { - await Task.Delay(_timeout); - Dispatcher.UIThread.Post(() => _infoBar.IsOpen = false); + Dispatcher.UIThread.InvokeAsync(async () => + { + await Task.Delay(_timeout); + _infoBar.IsOpen = false; + }); + + return; + + void InfoBarOnClosed(InfoBar sender, InfoBarClosedEventArgs args) + { + overlayLayer.Children.Remove(container); + _infoBar.Closed -= InfoBarOnClosed; + } }); return () => Dispatcher.UIThread.Post(() => _infoBar.IsOpen = false); } - - private void InfoBarOnClosed(InfoBar sender, InfoBarClosedEventArgs args) - { - _infoBar.Closed -= InfoBarOnClosed; - if (_parent.Content is not Panel panel) - return; - - panel.Children.Remove(_infoBar); - } } /// @@ -180,7 +180,7 @@ public class NotificationButtonBuilder _action = action; return this; } - + /// /// Changes action that is called when the button is clicked. /// @@ -222,9 +222,13 @@ public class NotificationButtonBuilder button.Classes.Add("AppBarButton"); if (_action != null) + { button.Command = ReactiveCommand.Create(() => _action()); + } else if (_asyncAction != null) + { button.Command = ReactiveCommand.CreateFromTask(() => _asyncAction()); + } else if (_command != null) { button.Command = _command; diff --git a/src/Artemis.UI.Shared/Styles/InfoBar.axaml b/src/Artemis.UI.Shared/Styles/InfoBar.axaml index 3f9b4a9bc..7b6f7f62c 100644 --- a/src/Artemis.UI.Shared/Styles/InfoBar.axaml +++ b/src/Artemis.UI.Shared/Styles/InfoBar.axaml @@ -17,6 +17,8 @@ + + diff --git a/src/Artemis.UI.Shared/Styles/Notifications.axaml b/src/Artemis.UI.Shared/Styles/Notifications.axaml index 0e9553ca2..94bc9039c 100644 --- a/src/Artemis.UI.Shared/Styles/Notifications.axaml +++ b/src/Artemis.UI.Shared/Styles/Notifications.axaml @@ -1,17 +1,14 @@  + xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:shared="clr-namespace:Artemis.UI.Shared"> - - -