From f0b23e1bdfe02e7222b706306548d5f41b726124 Mon Sep 17 00:00:00 2001
From: Zhang Dian <54255897+zdpcdt@users.noreply.github.com>
Date: Thu, 5 Sep 2024 01:36:10 +0800
Subject: [PATCH] feat: add new Toast.
---
demo/Ursa.Demo/Pages/ToastDemo.axaml | 33 +++
demo/Ursa.Demo/Pages/ToastDemo.axaml.cs | 25 +++
.../Ursa.Demo/ViewModels/MainViewViewModel.cs | 1 +
demo/Ursa.Demo/ViewModels/MenuViewModel.cs | 2 +
.../ViewModels/ToastDemoViewModel.cs | 57 +++++
src/Ursa.Themes.Semi/Controls/Toast.axaml | 196 +++++++++++++++++
src/Ursa.Themes.Semi/Controls/_index.axaml | 1 +
.../Controls/Toast/IManagedToastManager.cs | 22 ++
src/Ursa/Controls/Toast/IToast.cs | 38 ++++
src/Ursa/Controls/Toast/IToastManager.cs | 17 ++
src/Ursa/Controls/Toast/Toast.cs | 80 +++++++
src/Ursa/Controls/Toast/ToastCard.cs | 198 ++++++++++++++++++
src/Ursa/Controls/Toast/WindowToastManager.cs | 188 +++++++++++++++++
13 files changed, 858 insertions(+)
create mode 100644 demo/Ursa.Demo/Pages/ToastDemo.axaml
create mode 100644 demo/Ursa.Demo/Pages/ToastDemo.axaml.cs
create mode 100644 demo/Ursa.Demo/ViewModels/ToastDemoViewModel.cs
create mode 100644 src/Ursa.Themes.Semi/Controls/Toast.axaml
create mode 100644 src/Ursa/Controls/Toast/IManagedToastManager.cs
create mode 100644 src/Ursa/Controls/Toast/IToast.cs
create mode 100644 src/Ursa/Controls/Toast/IToastManager.cs
create mode 100644 src/Ursa/Controls/Toast/Toast.cs
create mode 100644 src/Ursa/Controls/Toast/ToastCard.cs
create mode 100644 src/Ursa/Controls/Toast/WindowToastManager.cs
diff --git a/demo/Ursa.Demo/Pages/ToastDemo.axaml b/demo/Ursa.Demo/Pages/ToastDemo.axaml
new file mode 100644
index 0000000..4085f09
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/ToastDemo.axaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Ursa.Demo/Pages/ToastDemo.axaml.cs b/demo/Ursa.Demo/Pages/ToastDemo.axaml.cs
new file mode 100644
index 0000000..7e4f490
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/ToastDemo.axaml.cs
@@ -0,0 +1,25 @@
+using Avalonia;
+using Avalonia.Controls;
+using Ursa.Controls;
+using Ursa.Demo.ViewModels;
+
+namespace Ursa.Demo.Pages;
+
+public partial class ToastDemo : UserControl
+{
+ private ToastDemoViewModel _viewModel;
+
+ public ToastDemo()
+ {
+ InitializeComponent();
+ _viewModel = new ToastDemoViewModel();
+ DataContext = _viewModel;
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+ var topLevel = TopLevel.GetTopLevel(this);
+ _viewModel.ToastManager = new WindowToastManager(topLevel) { MaxItems = 3 };
+ }
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
index 87a92ac..6edd07a 100644
--- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
@@ -67,6 +67,7 @@ public class MainViewViewModel : ViewModelBase
MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(),
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
MenuKeys.MenuKeyThemeToggler => new ThemeTogglerDemoViewModel(),
+ MenuKeys.MenuKeyToast => new ToastDemoViewModel(),
MenuKeys.MenuKeyToolBar => new ToolBarDemoViewModel(),
MenuKeys.MenuKeyTimeBox => new TimeBoxDemoViewModel(),
MenuKeys.MenuKeyPinCode => new PinCodeDemoViewModel(),
diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
index bf9bb6e..ffab9a6 100644
--- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
@@ -53,6 +53,7 @@ public class MenuViewModel: ViewModelBase
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline },
new() { MenuHeader = "TreeComboBox", Key = MenuKeys.MenuKeyTreeComboBox },
new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon},
+ new() { MenuHeader = "Toast", Key = MenuKeys.MenuKeyToast },
new() { MenuHeader = "ToolBar", Key = MenuKeys.MenuKeyToolBar },
new() { MenuHeader = "Time Box", Key = MenuKeys.MenuKeyTimeBox },
};
@@ -102,6 +103,7 @@ public static class MenuKeys
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
public const string MenuKeyThemeToggler = "ThemeToggler";
public const string MenuKeyTreeComboBox = "TreeComboBox";
+ public const string MenuKeyToast = "Toast";
public const string MenuKeyToolBar = "ToolBar";
public const string MenuKeyPinCode = "PinCode";
public const string MenuKeyTimeBox = "TimeBox";
diff --git a/demo/Ursa.Demo/ViewModels/ToastDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/ToastDemoViewModel.cs
new file mode 100644
index 0000000..97be1d8
--- /dev/null
+++ b/demo/Ursa.Demo/ViewModels/ToastDemoViewModel.cs
@@ -0,0 +1,57 @@
+using System;
+using Avalonia.Controls.Notifications;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Ursa.Controls;
+
+namespace Ursa.Demo.ViewModels;
+
+public partial class ToastDemoViewModel : ObservableObject
+{
+ public WindowToastManager? ToastManager { get; set; }
+
+ [RelayCommand]
+ public void ShowNormal(object obj)
+ {
+ if (obj is string s)
+ {
+ Enum.TryParse(s, out var notificationType);
+ ToastManager?.Show(
+ new Toast("This is message"),
+ type: notificationType);
+ }
+
+ // ToastManager?.Show(new ToastDemoViewModel
+ // {
+ // Content = "This is message",
+ // ToastManager = ToastManager
+ // });
+ }
+
+ [RelayCommand]
+ public void ShowLight(object obj)
+ {
+ if (obj is string s)
+ {
+ Enum.TryParse(s, out var notificationType);
+ ToastManager?.Show(
+ new Toast("This is message"),
+ type: notificationType,
+ classes: ["Light"]);
+ }
+ }
+
+ public string? Content { get; set; }
+
+ [RelayCommand]
+ public void YesCommand()
+ {
+ ToastManager?.Show(new Toast("Yes!"));
+ }
+
+ [RelayCommand]
+ public void NoCommand()
+ {
+ ToastManager?.Show(new Toast("No!"));
+ }
+}
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Controls/Toast.axaml b/src/Ursa.Themes.Semi/Controls/Toast.axaml
new file mode 100644
index 0000000..12af584
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/Toast.axaml
@@ -0,0 +1,196 @@
+
+
+
+
+
+ Hello, Semi.Avalonia!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml
index bec20a1..7003457 100644
--- a/src/Ursa.Themes.Semi/Controls/_index.axaml
+++ b/src/Ursa.Themes.Semi/Controls/_index.axaml
@@ -46,6 +46,7 @@
+
diff --git a/src/Ursa/Controls/Toast/IManagedToastManager.cs b/src/Ursa/Controls/Toast/IManagedToastManager.cs
new file mode 100644
index 0000000..1a73d28
--- /dev/null
+++ b/src/Ursa/Controls/Toast/IManagedToastManager.cs
@@ -0,0 +1,22 @@
+using Avalonia.Metadata;
+
+namespace Ursa.Controls;
+
+///
+/// Represents a toast manager that can show arbitrary content.
+/// Managed toast managers can show any content.
+///
+///
+/// Because toast managers of this type are implemented purely in managed code, they
+/// can display arbitrary content, as opposed to toast managers which display toasts
+/// using the host operating system's toast mechanism.
+///
+[NotClientImplementable]
+public interface IManagedToastManager : IToastManager
+{
+ ///
+ /// Shows a toast.
+ ///
+ /// The content to be displayed.
+ void Show(object content);
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/Toast/IToast.cs b/src/Ursa/Controls/Toast/IToast.cs
new file mode 100644
index 0000000..1a9f7eb
--- /dev/null
+++ b/src/Ursa/Controls/Toast/IToast.cs
@@ -0,0 +1,38 @@
+using Avalonia.Controls.Notifications;
+using Avalonia.Metadata;
+
+namespace Ursa.Controls;
+
+///
+/// Represents a toast that can be shown in a window or by the host operating system.
+///
+[NotClientImplementable]
+public interface IToast
+{
+
+ ///
+ /// Gets the toast message.
+ ///
+ string? Content { get; }
+
+ ///
+ /// Gets the of the toast.
+ ///
+ NotificationType Type { get; }
+
+ ///
+ /// Gets the expiration time of the toast after which it will automatically close.
+ /// If the value is then the toast will remain open until the user closes it.
+ ///
+ TimeSpan Expiration { get; }
+
+ ///
+ /// Gets an Action to be run when the toast is clicked.
+ ///
+ Action? OnClick { get; }
+
+ ///
+ /// Gets an Action to be run when the toast is closed.
+ ///
+ Action? OnClose { get; }
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/Toast/IToastManager.cs b/src/Ursa/Controls/Toast/IToastManager.cs
new file mode 100644
index 0000000..20e7c24
--- /dev/null
+++ b/src/Ursa/Controls/Toast/IToastManager.cs
@@ -0,0 +1,17 @@
+using Avalonia.Metadata;
+
+namespace Ursa.Controls;
+
+///
+/// Represents a toast manager that can be used to show toasts in a window or using
+/// the host operating system.
+///
+[NotClientImplementable]
+public interface IToastManager
+{
+ ///
+ /// Show a toast.
+ ///
+ /// The toast to be displayed.
+ void Show(IToast toast);
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/Toast/Toast.cs b/src/Ursa/Controls/Toast/Toast.cs
new file mode 100644
index 0000000..b4267fd
--- /dev/null
+++ b/src/Ursa/Controls/Toast/Toast.cs
@@ -0,0 +1,80 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls.Notifications;
+
+namespace Ursa.Controls;
+
+///
+/// A toast that can be shown in a window or by the host operating system.
+///
+///
+/// This class represents a toast that can be displayed either in a window using
+/// or by the host operating system (to be implemented).
+///
+public class Toast : IToast, INotifyPropertyChanged
+{
+ private string? _content;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The content to be displayed in the toast.
+ /// The of the toast.
+ /// The expiry time at which the toast will close.
+ /// Use for toasts that will remain open.
+ /// An Action to call when the toast is clicked.
+ /// An Action to call when the toast is closed.
+ public Toast(
+ string? content,
+ NotificationType type = NotificationType.Information,
+ TimeSpan? expiration = null,
+ Action? onClick = null,
+ Action? onClose = null)
+ {
+ Content = content;
+ Type = type;
+ Expiration = expiration ?? TimeSpan.FromSeconds(5);
+ OnClick = onClick;
+ OnClose = onClose;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Toast() : this(null)
+ {
+ }
+
+ ///
+ public string? Content
+ {
+ get => _content;
+ set
+ {
+ if (_content != value)
+ {
+ _content = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ ///
+ public NotificationType Type { get; set; }
+
+ ///
+ public TimeSpan Expiration { get; set; }
+
+ ///
+ public Action? OnClick { get; set; }
+
+ ///
+ public Action? OnClose { get; set; }
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/Toast/ToastCard.cs b/src/Ursa/Controls/Toast/ToastCard.cs
new file mode 100644
index 0000000..2542d33
--- /dev/null
+++ b/src/Ursa/Controls/Toast/ToastCard.cs
@@ -0,0 +1,198 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Notifications;
+using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
+
+namespace Ursa.Controls;
+
+///
+/// Control that represents and displays a toast.
+///
+[PseudoClasses(PC_Information, PC_Success, PC_Warning, PC_Error)]
+public class ToastCard : ContentControl
+{
+ public const string PC_Information = ":information";
+ public const string PC_Success = ":success";
+ public const string PC_Warning = ":warning";
+ public const string PC_Error = ":error";
+
+ private bool _isClosing;
+
+ static ToastCard()
+ {
+ CloseOnClickProperty.Changed.AddClassHandler