feat: Add Badge styles and demos.

This commit is contained in:
NaBian committed 2026-06-17 00:11:40 +08:00
1 parent 4cd78a4b3f
commit 905bc92825
7 files changed
+383 -1

No files matched your search

@@ -55,7 +55,8 @@
[ "ProgressBar", "ProgressBarDemo", "Brush.ProgressBar", "", "Feedback" ],
[ "Rate", "RateDemo", "Brush.Favorite", "", "Feedback" ],
[ "Loading", "LoadingDemo", "Brush.Loading", "", "Feedback" ],
[ "Tag", "TagDemo", "Brush.Tag", "", "DataDisplay" ]
[ "Tag", "TagDemo", "Brush.Tag", "", "DataDisplay" ],
[ "Badge", "BadgeDemo", "Brush.CheckDot", "", "DataDisplay" ]
]
}
]
@@ -0,0 +1,49 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hc="https://handyorg.github.io/handycontrol"
x:Class="HandyControlDemo.UserControl.BadgeDemo"
Background="{DynamicResource RegionBrush}">
<ScrollViewer>
<hc:UniformSpacingPanel Margin="32"
Spacing="32"
ChildWrapping="Wrap"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<hc:Badge Text="New"
BadgeMargin="0,-14,-20,0"
Height="30">
<Button Content="Comment" />
</hc:Badge>
<hc:Badge Text="New"
BadgeMargin="0,-14,-20,0"
Height="30"
Theme="{StaticResource BadgeDanger}">
<Button Content="Comment" />
</hc:Badge>
<hc:Badge Name="CountBadge"
Value="0"
BadgeMargin="0,-14,-10,0"
Height="30"
Theme="{StaticResource BadgePrimary}">
<Button Name="CountButton"
Content="Click to Count" />
</hc:Badge>
<hc:Badge Value="100"
BadgeMargin="0,-14,-20,0"
Height="30"
Theme="{StaticResource BadgeDanger}">
<Button Content="Comment" />
</hc:Badge>
<hc:Badge Status="Dot"
Height="30"
Theme="{StaticResource BadgeDanger}">
<Button Content="Reply" />
</hc:Badge>
<hc:Badge Status="Processing"
Height="30"
Theme="{StaticResource BadgeDanger}">
<Button Content="Reply" />
</hc:Badge>
</hc:UniformSpacingPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,20 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using HandyControl.Controls;
namespace HandyControlDemo.UserControl;
public partial class BadgeDemo : Avalonia.Controls.UserControl
{
public BadgeDemo()
{
InitializeComponent();
var badge = this.FindControl<Badge>("CountBadge");
var button = this.FindControl<Button>("CountButton");
if (button != null && badge != null)
{
button.Click += (_, _) => badge.Value += 1;
}
}
}
@@ -0,0 +1,115 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using HandyControl.Data;
namespace HandyControl.Controls;
/// <summary>
/// 标记
/// </summary>
public class Badge : ContentControl
{
private int? _originalValue;
public static readonly RoutedEvent<FunctionEventArgs<int>> ValueChangedEvent =
RoutedEvent.Register<Badge, FunctionEventArgs<int>>(nameof(ValueChanged), RoutingStrategies.Bubble);
public event EventHandler<FunctionEventArgs<int>> ValueChanged
{
add => AddHandler(ValueChangedEvent, value);
remove => RemoveHandler(ValueChangedEvent, value);
}
public static readonly StyledProperty<string?> TextProperty =
AvaloniaProperty.Register<Badge, string?>(nameof(Text), "0");
public string? Text
{
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly StyledProperty<int> ValueProperty =
AvaloniaProperty.Register<Badge, int>(nameof(Value));
public int Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly StyledProperty<BadgeStatus> StatusProperty =
AvaloniaProperty.Register<Badge, BadgeStatus>(nameof(Status));
public BadgeStatus Status
{
get => GetValue(StatusProperty);
set => SetValue(StatusProperty, value);
}
public static readonly StyledProperty<int> MaximumProperty =
AvaloniaProperty.Register<Badge, int>(nameof(Maximum), 99);
public int Maximum
{
get => GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
public static readonly StyledProperty<Thickness> BadgeMarginProperty =
AvaloniaProperty.Register<Badge, Thickness>(nameof(BadgeMargin));
public Thickness BadgeMargin
{
get => GetValue(BadgeMarginProperty);
set => SetValue(BadgeMarginProperty, value);
}
public static readonly StyledProperty<bool> ShowBadgeProperty =
AvaloniaProperty.Register<Badge, bool>(nameof(ShowBadge), true);
public bool ShowBadge
{
get => GetValue(ShowBadgeProperty);
set => SetValue(ShowBadgeProperty, value);
}
static Badge()
{
ValueProperty.Changed.AddClassHandler<Badge>((badge, e) => badge.OnValueChanged(e));
MaximumProperty.Changed.AddClassHandler<Badge>((badge, _) => badge.UpdateText(badge.Value));
}
private void OnValueChanged(AvaloniaPropertyChangedEventArgs e)
{
var v = e.GetNewValue<int>();
UpdateText(v);
if (IsLoaded)
{
RaiseEvent(new FunctionEventArgs<int>(ValueChangedEvent, this) { Info = v });
}
else
{
_originalValue = v;
}
}
private void UpdateText(int v)
{
SetCurrentValue(TextProperty, v <= Maximum ? v.ToString() : $"{Maximum}+");
}
protected override void OnApplyTemplate(Avalonia.Controls.Primitives.TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
if (_originalValue != null)
{
RaiseEvent(new FunctionEventArgs<int>(ValueChangedEvent, this) { Info = _originalValue.Value });
_originalValue = null;
}
}
}
@@ -0,0 +1,8 @@
namespace HandyControl.Data;
public enum BadgeStatus
{
Text,
Dot,
Processing
}
@@ -0,0 +1,188 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:ClassModifier="internal"
xmlns:hc="clr-namespace:HandyControl.Controls">
<ControlTheme x:Key="BadgeBaseStyle"
TargetType="hc:Badge">
<Setter Property="Focusable"
Value="False" />
<Setter Property="ClipToBounds"
Value="False" />
<Setter Property="Background"
Value="{DynamicResource SecondaryRegionBrush}" />
<Setter Property="Foreground"
Value="{DynamicResource TextIconBrush}" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="BorderBrush"
Value="{DynamicResource RegionBrush}" />
<Setter Property="Padding"
Value="6,2" />
<Setter Property="HorizontalContentAlignment"
Value="Right" />
<Setter Property="VerticalContentAlignment"
Value="Top" />
<Setter Property="BadgeMargin"
Value="0,-10,-10,0" />
<!-- Default: Text template -->
<Setter Property="Template">
<ControlTemplate TargetType="hc:Badge">
<Panel>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Border Name="PART_Badge"
IsVisible="{TemplateBinding ShowBadge}"
RenderTransformOrigin="50%,50%"
Theme="{StaticResource BorderCircular}"
Margin="{TemplateBinding BadgeMargin}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Border.RenderTransform>
<ScaleTransform />
</Border.RenderTransform>
<ContentPresenter Content="{TemplateBinding Text}"
Margin="{TemplateBinding Padding}" />
</Border>
</Panel>
</ControlTemplate>
</Setter>
<!-- Dot template -->
<Style Selector="^[Status=Dot]">
<Setter Property="BorderBrush"
Value="{DynamicResource BorderBrush}" />
<Setter Property="BadgeMargin"
Value="0,-5,-5,0" />
<Setter Property="Template">
<ControlTemplate TargetType="hc:Badge">
<Panel>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Border IsVisible="{TemplateBinding ShowBadge}"
Margin="{TemplateBinding BadgeMargin}"
Width="10"
Height="10"
CornerRadius="5"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" />
</Panel>
</ControlTemplate>
</Setter>
</Style>
<!-- Processing template -->
<Style Selector="^[Status=Processing]">
<Setter Property="BorderBrush"
Value="{DynamicResource BorderBrush}" />
<Setter Property="BadgeMargin"
Value="0,-5,-5,0" />
<Setter Property="Template">
<ControlTemplate TargetType="hc:Badge">
<Panel>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Ellipse Name="PART_Pulse"
IsVisible="{TemplateBinding ShowBadge}"
Width="10"
Height="10"
Fill="{TemplateBinding Background}"
RenderTransformOrigin="50%,50%"
Margin="{TemplateBinding BadgeMargin}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Ellipse.RenderTransform>
<ScaleTransform />
</Ellipse.RenderTransform>
</Ellipse>
<Border IsVisible="{TemplateBinding ShowBadge}"
Margin="{TemplateBinding BadgeMargin}"
Width="10"
Height="10"
CornerRadius="5"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" />
</Panel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="^[Status=Processing] /template/ Ellipse#PART_Pulse">
<Style.Animations>
<Animation IterationCount="Infinite"
Easing="SineEaseOut"
Duration="0:0:1">
<KeyFrame Cue="0%">
<Setter Property="ScaleTransform.ScaleX"
Value="1" />
<Setter Property="ScaleTransform.ScaleY"
Value="1" />
<Setter Property="Opacity"
Value="1" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="ScaleTransform.ScaleX"
Value="2" />
<Setter Property="ScaleTransform.ScaleY"
Value="2" />
<Setter Property="Opacity"
Value="0" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type hc:Badge}"
BasedOn="{StaticResource BadgeBaseStyle}"
TargetType="hc:Badge">
<Setter Property="Foreground"
Value="{DynamicResource PrimaryTextBrush}" />
</ControlTheme>
<ControlTheme x:Key="BadgePrimary"
BasedOn="{StaticResource BadgeBaseStyle}"
TargetType="hc:Badge">
<Setter Property="Background"
Value="{DynamicResource PrimaryBrush}" />
</ControlTheme>
<ControlTheme x:Key="BadgeSuccess"
BasedOn="{StaticResource BadgeBaseStyle}"
TargetType="hc:Badge">
<Setter Property="Background"
Value="{DynamicResource SuccessBrush}" />
</ControlTheme>
<ControlTheme x:Key="BadgeInfo"
BasedOn="{StaticResource BadgeBaseStyle}"
TargetType="hc:Badge">
<Setter Property="Background"
Value="{DynamicResource InfoBrush}" />
</ControlTheme>
<ControlTheme x:Key="BadgeWarning"
BasedOn="{StaticResource BadgeBaseStyle}"
TargetType="hc:Badge">
<Setter Property="Background"
Value="{DynamicResource WarningBrush}" />
</ControlTheme>
<ControlTheme x:Key="BadgeDanger"
BasedOn="{StaticResource BadgeBaseStyle}"
TargetType="hc:Badge">
<Setter Property="Background"
Value="{DynamicResource DangerBrush}" />
</ControlTheme>
</ResourceDictionary>
@@ -71,6 +71,7 @@
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/ButtonGroup.axaml" />
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Gravatar.axaml" />
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Tag.axaml" />
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Badge.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Styles.Resources>