feat: add close button.

This commit is contained in:
Zhang Dian
2024-09-05 03:30:02 +08:00
parent f0b23e1bdf
commit f52d62f9f5
7 changed files with 76 additions and 34 deletions

View File

@@ -12,6 +12,7 @@
<vm:ToastDemoViewModel /> <vm:ToastDemoViewModel />
</Design.DataContext> </Design.DataContext>
<StackPanel> <StackPanel>
<ToggleSwitch IsChecked="{Binding ShowClose}" Content="ShowClose"/>
<StackPanel Orientation="Horizontal" Spacing="20"> <StackPanel Orientation="Horizontal" Spacing="20">
<Button Command="{Binding ShowNormal}" CommandParameter="{Binding $self.Content}" Content="Information" /> <Button Command="{Binding ShowNormal}" CommandParameter="{Binding $self.Content}" Content="Information" />
<Button Command="{Binding ShowNormal}" CommandParameter="{Binding $self.Content}" Content="Success" Classes="Success" /> <Button Command="{Binding ShowNormal}" CommandParameter="{Binding $self.Content}" Content="Success" Classes="Success" />

View File

@@ -10,6 +10,9 @@ public partial class ToastDemoViewModel : ObservableObject
{ {
public WindowToastManager? ToastManager { get; set; } public WindowToastManager? ToastManager { get; set; }
[ObservableProperty]
private bool _showClose = true;
[RelayCommand] [RelayCommand]
public void ShowNormal(object obj) public void ShowNormal(object obj)
{ {
@@ -18,6 +21,7 @@ public partial class ToastDemoViewModel : ObservableObject
Enum.TryParse<NotificationType>(s, out var notificationType); Enum.TryParse<NotificationType>(s, out var notificationType);
ToastManager?.Show( ToastManager?.Show(
new Toast("This is message"), new Toast("This is message"),
showClose: ShowClose,
type: notificationType); type: notificationType);
} }
@@ -36,6 +40,7 @@ public partial class ToastDemoViewModel : ObservableObject
Enum.TryParse<NotificationType>(s, out var notificationType); Enum.TryParse<NotificationType>(s, out var notificationType);
ToastManager?.Show( ToastManager?.Show(
new Toast("This is message"), new Toast("This is message"),
showClose: ShowClose,
type: notificationType, type: notificationType,
classes: ["Light"]); classes: ["Light"]);
} }

View File

@@ -3,17 +3,12 @@
xmlns:u="https://irihi.tech/ursa"> xmlns:u="https://irihi.tech/ursa">
<Design.PreviewWith> <Design.PreviewWith>
<ThemeVariantScope RequestedThemeVariant="Dark"> <ThemeVariantScope RequestedThemeVariant="Dark">
<StackPanel> <StackPanel Orientation="Horizontal">
<u:ToastCard ShowClose="False" />
<u:ToastCard> <u:ToastCard>
Hello, Semi.Avalonia! Hello, Semi.Avalonia!
</u:ToastCard> </u:ToastCard>
<u:ToastCard NotificationType="Success"> <u:ToastCard NotificationType="Success" ShowClose="False">
<u:Toast Content="Hello, Semi.Avalonia!" />
</u:ToastCard>
<u:ToastCard NotificationType="Warning">
<u:Toast Content="Hello, Semi.Avalonia!" />
</u:ToastCard>
<u:ToastCard NotificationType="Error">
<u:Toast Content="Hello, Semi.Avalonia!" /> <u:Toast Content="Hello, Semi.Avalonia!" />
</u:ToastCard> </u:ToastCard>
</StackPanel> </StackPanel>
@@ -35,7 +30,6 @@
<ControlTheme x:Key="{x:Type u:ToastCard}" TargetType="u:ToastCard"> <ControlTheme x:Key="{x:Type u:ToastCard}" TargetType="u:ToastCard">
<Setter Property="UseLayoutRounding" Value="True" /> <Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="MinWidth" Value="{DynamicResource NotificationCardMinWidth}" />
<Setter Property="RenderTransformOrigin" Value="50%,75%" /> <Setter Property="RenderTransformOrigin" Value="50%,75%" />
<Setter Property="BorderThickness" Value="{DynamicResource NotificationCardBorderThickness}" /> <Setter Property="BorderThickness" Value="{DynamicResource NotificationCardBorderThickness}" />
<Setter Property="BorderBrush" Value="{DynamicResource NotificationCardBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource NotificationCardBorderBrush}" />
@@ -53,40 +47,44 @@
Padding="{DynamicResource NotificationCardPadding}" Padding="{DynamicResource NotificationCardPadding}"
BoxShadow="{DynamicResource NotificationCardBoxShadow}" BoxShadow="{DynamicResource NotificationCardBoxShadow}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
VerticalAlignment="Stretch"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<DockPanel> <StackPanel Orientation="Horizontal">
<PathIcon <PathIcon
Name="NotificationIcon" Name="NotificationIcon"
Width="{DynamicResource NotificationCardIconWidth}" Width="{DynamicResource NotificationCardIconWidth}"
Height="{DynamicResource NotificationCardIconHeight}" Height="{DynamicResource NotificationCardIconHeight}"
Margin="{DynamicResource NotificationCardIconMargin}" Margin="{DynamicResource NotificationCardIconMargin}"
VerticalAlignment="Top"
IsVisible="False" IsVisible="False"
Data="{DynamicResource NotificationCardInformationIconPathData}" /> Data="{DynamicResource NotificationCardInformationIconPathData}" />
<ContentControl <ContentControl
Name="PART_Content" Name="PART_Content"
Margin="12 0"
VerticalContentAlignment="Center"
Content="{TemplateBinding Content}"> Content="{TemplateBinding Content}">
<ContentControl.DataTemplates> <ContentControl.DataTemplates>
<DataTemplate DataType="u:IToast"> <DataTemplate DataType="u:IToast">
<SelectableTextBlock <SelectableTextBlock
Foreground="{DynamicResource NotificationCardMessageForeground}" Foreground="{DynamicResource NotificationCardTitleForeground}"
FontSize="{DynamicResource NotificationCardMessageFontSize}" FontWeight="{DynamicResource NotificationCardTitleFontWeight}"
FontWeight="{DynamicResource NotificationCardMessageFontWeight}"
Text="{Binding Content}" Text="{Binding Content}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</DataTemplate> </DataTemplate>
<DataTemplate DataType="x:String"> <DataTemplate DataType="x:String">
<SelectableTextBlock <SelectableTextBlock
Foreground="{DynamicResource NotificationCardMessageForeground}" Foreground="{DynamicResource NotificationCardTitleForeground}"
FontSize="{DynamicResource NotificationCardMessageFontSize}" FontWeight="{DynamicResource NotificationCardTitleFontWeight}"
FontWeight="{DynamicResource NotificationCardMessageFontWeight}"
Text="{Binding}" Text="{Binding}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</DataTemplate> </DataTemplate>
</ContentControl.DataTemplates> </ContentControl.DataTemplates>
</ContentControl> </ContentControl>
</DockPanel> <Button
Theme="{StaticResource ToastCloseButton}"
IsVisible="{TemplateBinding ShowClose}"
u:ToastCard.CloseOnClick="True" />
</StackPanel>
</Border> </Border>
</Border> </Border>
</LayoutTransformControl> </LayoutTransformControl>
@@ -193,4 +191,31 @@
</Style> </Style>
</Style> </Style>
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="ToastCloseButton" TargetType="Button">
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Height" Value="24" />
<Setter Property="Width" Value="24" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<ControlTemplate TargetType="Button">
<Border
Name="PART_Border"
Padding="{TemplateBinding Padding}"
Background="Transparent"
CornerRadius="{TemplateBinding CornerRadius}">
<PathIcon
Width="10"
Height="10"
Data="{DynamicResource WindowCloseIconGlyph}" />
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover /template/ Border">
<Setter Property="Background" Value="{DynamicResource ButtonDefaultPointeroverBackground}" />
</Style>
<Style Selector="^:pressed /template/ Border">
<Setter Property="Background" Value="{DynamicResource ButtonDefaultPressedBackground}" />
</Style>
</ControlTheme>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -20,6 +20,11 @@ public interface IToast
/// </summary> /// </summary>
NotificationType Type { get; } NotificationType Type { get; }
/// <summary>
/// Gets a value indicating whether the toast should show a close button.
/// </summary>
bool ShowClose { get; }
/// <summary> /// <summary>
/// Gets the expiration time of the toast after which it will automatically close. /// Gets the expiration time of the toast after which it will automatically close.
/// If the value is <see cref="TimeSpan.Zero"/> then the toast will remain open until the user closes it. /// If the value is <see cref="TimeSpan.Zero"/> then the toast will remain open until the user closes it.

View File

@@ -22,12 +22,14 @@ public class Toast : IToast, INotifyPropertyChanged
/// <param name="type">The <see cref="NotificationType"/> of the toast.</param> /// <param name="type">The <see cref="NotificationType"/> of the toast.</param>
/// <param name="expiration">The expiry time at which the toast will close. /// <param name="expiration">The expiry time at which the toast will close.
/// Use <see cref="TimeSpan.Zero"/> for toasts that will remain open.</param> /// Use <see cref="TimeSpan.Zero"/> for toasts that will remain open.</param>
/// <param name="showClose">A value indicating whether the toast should show a close button.</param>
/// <param name="onClick">An Action to call when the toast is clicked.</param> /// <param name="onClick">An Action to call when the toast is clicked.</param>
/// <param name="onClose">An Action to call when the toast is closed.</param> /// <param name="onClose">An Action to call when the toast is closed.</param>
public Toast( public Toast(
string? content, string? content,
NotificationType type = NotificationType.Information, NotificationType type = NotificationType.Information,
TimeSpan? expiration = null, TimeSpan? expiration = null,
bool showClose = true,
Action? onClick = null, Action? onClick = null,
Action? onClose = null) Action? onClose = null)
{ {
@@ -62,6 +64,9 @@ public class Toast : IToast, INotifyPropertyChanged
/// <inheritdoc/> /// <inheritdoc/>
public NotificationType Type { get; set; } public NotificationType Type { get; set; }
/// <inheritdoc/>
public bool ShowClose { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public TimeSpan Expiration { get; set; } public TimeSpan Expiration { get; set; }

View File

@@ -77,6 +77,14 @@ public class ToastCard : ContentControl
/// </summary> /// </summary>
public static readonly StyledProperty<NotificationType> NotificationTypeProperty = public static readonly StyledProperty<NotificationType> NotificationTypeProperty =
AvaloniaProperty.Register<ToastCard, NotificationType>(nameof(NotificationType)); AvaloniaProperty.Register<ToastCard, NotificationType>(nameof(NotificationType));
public bool ShowClose
{
get => GetValue(ShowCloseProperty);
set => SetValue(ShowCloseProperty, value);
}
public static readonly StyledProperty<bool> ShowCloseProperty =
AvaloniaProperty.Register<ToastCard, bool>(nameof(ShowClose), true);
/// <summary> /// <summary>
/// Defines the <see cref="ToastClosed"/> event. /// Defines the <see cref="ToastClosed"/> event.

View File

@@ -70,7 +70,7 @@ public class WindowToastManager : TemplatedControl, IManagedToastManager
/// <inheritdoc/> /// <inheritdoc/>
public void Show(IToast content) public void Show(IToast content)
{ {
Show(content, content.Type, content.Expiration, content.OnClick, content.OnClose); Show(content, content.Type, content.Expiration, content.ShowClose, content.OnClick, content.OnClose);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -78,7 +78,7 @@ public class WindowToastManager : TemplatedControl, IManagedToastManager
{ {
if (content is IToast toast) if (content is IToast toast)
{ {
Show(toast, toast.Type, toast.Expiration, toast.OnClick, toast.OnClose); Show(toast, toast.Type, toast.Expiration, toast.ShowClose, toast.OnClick, toast.OnClose);
} }
else else
{ {
@@ -92,12 +92,14 @@ public class WindowToastManager : TemplatedControl, IManagedToastManager
/// <param name="content">the content of the toast</param> /// <param name="content">the content of the toast</param>
/// <param name="type">the type of the toast</param> /// <param name="type">the type of the toast</param>
/// <param name="expiration">the expiration time of the toast after which it will automatically close. If the value is Zero then the toast will remain open until the user closes it</param> /// <param name="expiration">the expiration time of the toast after which it will automatically close. If the value is Zero then the toast will remain open until the user closes it</param>
/// <param name="showClose">whether to show the close button</param>
/// <param name="onClick">an Action to be run when the toast is clicked</param> /// <param name="onClick">an Action to be run when the toast is clicked</param>
/// <param name="onClose">an Action to be run when the toast is closed</param> /// <param name="onClose">an Action to be run when the toast is closed</param>
/// <param name="classes">style classes to apply</param> /// <param name="classes">style classes to apply</param>
public async void Show(object content, public async void Show(object content,
NotificationType type, NotificationType type,
TimeSpan? expiration = null, TimeSpan? expiration = null,
bool showClose = true,
Action? onClick = null, Action? onClick = null,
Action? onClose = null, Action? onClose = null,
string[]? classes = null) string[]? classes = null)
@@ -107,11 +109,12 @@ public class WindowToastManager : TemplatedControl, IManagedToastManager
var toastControl = new ToastCard var toastControl = new ToastCard
{ {
Content = content, Content = content,
NotificationType = type NotificationType = type,
ShowClose = showClose
}; };
// Add style classes if any // Add style classes if any
if (classes != null) if (classes is not null)
{ {
foreach (var @class in classes) foreach (var @class in classes)
{ {
@@ -119,19 +122,14 @@ public class WindowToastManager : TemplatedControl, IManagedToastManager
} }
} }
toastControl.ToastClosed += (sender, args) => toastControl.ToastClosed += (sender, _) =>
{ {
onClose?.Invoke(); onClose?.Invoke();
_items?.Remove(sender); _items?.Remove(sender);
}; };
toastControl.PointerPressed += (sender, args) => toastControl.PointerPressed += (_, _) => { onClick?.Invoke(); };
{
onClick?.Invoke();
(sender as ToastCard)?.Close();
};
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
@@ -153,11 +151,6 @@ public class WindowToastManager : TemplatedControl, IManagedToastManager
toastControl.Close(); toastControl.Close();
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
}
/// <summary> /// <summary>
/// Installs the <see cref="WindowToastManager"/> within the <see cref="AdornerLayer"/> /// Installs the <see cref="WindowToastManager"/> within the <see cref="AdornerLayer"/>
/// </summary> /// </summary>