From 9208b1cd7eecd94150a916426ba200180e7f5107 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Mon, 20 Feb 2023 11:13:51 +0800 Subject: [PATCH] feat: Add Badge and Badge demo. --- demo/Ursa.Demo/App.axaml | 2 +- demo/Ursa.Demo/Pages/BadgeDemo.axaml | 190 ++++++++++++++++++ demo/Ursa.Demo/Pages/BadgeDemo.axaml.cs | 18 ++ demo/Ursa.Demo/Pages/BannerDemo.axaml.cs | 5 + demo/Ursa.Demo/Views/MainWindow.axaml | 20 +- demo/Ursa.Demo/Views/MainWindow.axaml.cs | 13 ++ src/Ursa.Themes.Semi/Controls/Badge.axaml | 134 ++++++++++++ src/Ursa.Themes.Semi/Controls/_index.axaml | 1 + src/Ursa.Themes.Semi/Index.axaml | 4 +- src/Ursa.Themes.Semi/Themes/Dark/Badge.axaml | 10 + src/Ursa.Themes.Semi/Themes/Dark/Banner.axaml | 10 + src/Ursa.Themes.Semi/Themes/Dark/_index.axaml | 1 + src/Ursa.Themes.Semi/Themes/Light/Badge.axaml | 10 + .../Themes/Light/_index.axaml | 1 + .../Themes/Shared/Badge.axaml | 8 + .../Themes/Shared/_index.axaml | 1 + src/Ursa/Common/CornerPosition.cs | 9 + src/Ursa/Controls/Badge.cs | 105 ++++++++++ .../BadgeContentOverflowConverter.cs | 20 ++ 19 files changed, 554 insertions(+), 8 deletions(-) create mode 100644 demo/Ursa.Demo/Pages/BadgeDemo.axaml create mode 100644 demo/Ursa.Demo/Pages/BadgeDemo.axaml.cs create mode 100644 src/Ursa.Themes.Semi/Controls/Badge.axaml create mode 100644 src/Ursa.Themes.Semi/Themes/Dark/Badge.axaml create mode 100644 src/Ursa.Themes.Semi/Themes/Light/Badge.axaml create mode 100644 src/Ursa.Themes.Semi/Themes/Shared/Badge.axaml create mode 100644 src/Ursa/Common/CornerPosition.cs create mode 100644 src/Ursa/Controls/Badge.cs create mode 100644 src/Ursa/Converters/BadgeContentOverflowConverter.cs diff --git a/demo/Ursa.Demo/App.axaml b/demo/Ursa.Demo/App.axaml index 218b0f4..e9083c2 100644 --- a/demo/Ursa.Demo/App.axaml +++ b/demo/Ursa.Demo/App.axaml @@ -4,7 +4,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Ursa.Demo"> - + diff --git a/demo/Ursa.Demo/Pages/BadgeDemo.axaml b/demo/Ursa.Demo/Pages/BadgeDemo.axaml new file mode 100644 index 0000000..4a3bfd0 --- /dev/null +++ b/demo/Ursa.Demo/Pages/BadgeDemo.axaml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/Ursa.Demo/Pages/BadgeDemo.axaml.cs b/demo/Ursa.Demo/Pages/BadgeDemo.axaml.cs new file mode 100644 index 0000000..eec79bf --- /dev/null +++ b/demo/Ursa.Demo/Pages/BadgeDemo.axaml.cs @@ -0,0 +1,18 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class BadgeDemo : UserControl +{ + public BadgeDemo() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/Pages/BannerDemo.axaml.cs b/demo/Ursa.Demo/Pages/BannerDemo.axaml.cs index 8ea8b76..b4d2c86 100644 --- a/demo/Ursa.Demo/Pages/BannerDemo.axaml.cs +++ b/demo/Ursa.Demo/Pages/BannerDemo.axaml.cs @@ -14,6 +14,11 @@ public partial class BannerDemo : UserControl InitializeComponent(); this.DataContext = new BannerDemoViewModel(); } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } } public class BannerDemoViewModel: ViewModelBase diff --git a/demo/Ursa.Demo/Views/MainWindow.axaml b/demo/Ursa.Demo/Views/MainWindow.axaml index 860266a..a5c4284 100644 --- a/demo/Ursa.Demo/Views/MainWindow.axaml +++ b/demo/Ursa.Demo/Views/MainWindow.axaml @@ -16,10 +16,20 @@ - - - - - + + + + + + + + + + + diff --git a/demo/Ursa.Demo/Views/MainWindow.axaml.cs b/demo/Ursa.Demo/Views/MainWindow.axaml.cs index 45aa4b8..ff545b9 100644 --- a/demo/Ursa.Demo/Views/MainWindow.axaml.cs +++ b/demo/Ursa.Demo/Views/MainWindow.axaml.cs @@ -1,4 +1,7 @@ +using Avalonia; using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Styling; namespace Ursa.Demo.Views; @@ -8,4 +11,14 @@ public partial class MainWindow : Window { InitializeComponent(); } + + private void ToggleButton_OnIsCheckedChanged(object? sender, RoutedEventArgs e) + { + var app = Application.Current; + if (app is not null) + { + var theme = app.ActualThemeVariant; + app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark; + } + } } \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/Badge.axaml b/src/Ursa.Themes.Semi/Controls/Badge.axaml new file mode 100644 index 0000000..cf6896f --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/Badge.axaml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 8827a5d..8b9ac73 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -1,6 +1,7 @@ + diff --git a/src/Ursa.Themes.Semi/Index.axaml b/src/Ursa.Themes.Semi/Index.axaml index 5c182e5..e1d3523 100644 --- a/src/Ursa.Themes.Semi/Index.axaml +++ b/src/Ursa.Themes.Semi/Index.axaml @@ -8,10 +8,10 @@ - + - + diff --git a/src/Ursa.Themes.Semi/Themes/Dark/Badge.axaml b/src/Ursa.Themes.Semi/Themes/Dark/Badge.axaml new file mode 100644 index 0000000..f621202 --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Dark/Badge.axaml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Themes/Dark/Banner.axaml b/src/Ursa.Themes.Semi/Themes/Dark/Banner.axaml index 9017e8d..74df0ab 100644 --- a/src/Ursa.Themes.Semi/Themes/Dark/Banner.axaml +++ b/src/Ursa.Themes.Semi/Themes/Dark/Banner.axaml @@ -1,3 +1,13 @@ + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml b/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml index 8827a5d..8b9ac73 100644 --- a/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Dark/_index.axaml @@ -1,6 +1,7 @@ + diff --git a/src/Ursa.Themes.Semi/Themes/Light/Badge.axaml b/src/Ursa.Themes.Semi/Themes/Light/Badge.axaml new file mode 100644 index 0000000..f621202 --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Light/Badge.axaml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Themes/Light/_index.axaml b/src/Ursa.Themes.Semi/Themes/Light/_index.axaml index 8827a5d..8b9ac73 100644 --- a/src/Ursa.Themes.Semi/Themes/Light/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Light/_index.axaml @@ -1,6 +1,7 @@ + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/Badge.axaml b/src/Ursa.Themes.Semi/Themes/Shared/Badge.axaml new file mode 100644 index 0000000..688be66 --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Shared/Badge.axaml @@ -0,0 +1,8 @@ + + + 18 + 8 + 6,0 + 1 + 8 + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml index 8827a5d..8b9ac73 100644 --- a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml @@ -1,6 +1,7 @@ + diff --git a/src/Ursa/Common/CornerPosition.cs b/src/Ursa/Common/CornerPosition.cs new file mode 100644 index 0000000..ba85776 --- /dev/null +++ b/src/Ursa/Common/CornerPosition.cs @@ -0,0 +1,9 @@ +namespace Ursa.Common; + +public enum CornerPosition +{ + TopLeft, + TopRight, + BottomLeft, + BottomRight, +} \ No newline at end of file diff --git a/src/Ursa/Controls/Badge.cs b/src/Ursa/Controls/Badge.cs new file mode 100644 index 0000000..72b2c0f --- /dev/null +++ b/src/Ursa/Controls/Badge.cs @@ -0,0 +1,105 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Media; +using Avalonia.Styling; +using Ursa.Common; + +namespace Ursa.Controls; + +[TemplatePart(PART_ContentPresenter, typeof(ContentPresenter))] +[TemplatePart(PART_BadgeContainer, typeof(Border))] +[TemplatePart(PART_BadgeContentPresenter, typeof(ContentPresenter))] +public class Badge: ContentControl +{ + public const string PART_ContentPresenter = "PART_ContentPresenter"; + public const string PART_BadgeContainer = "PART_BadgeContainer"; + public const string PART_BadgeContentPresenter = "PART_BadgeContentPresenter"; + + private ContentPresenter? _content; + private Border? _badgeContainer; + private ContentPresenter? _badgeContent; + + public static readonly StyledProperty BadgeThemeProperty = AvaloniaProperty.Register( + nameof(BadgeTheme)); + public ControlTheme BadgeTheme + { + get => GetValue(BadgeThemeProperty); + set => SetValue(BadgeThemeProperty, value); + } + + public static readonly StyledProperty DotProperty = AvaloniaProperty.Register( + nameof(Dot)); + public bool Dot + { + get => GetValue(DotProperty); + set => SetValue(DotProperty, value); + } + + public static readonly StyledProperty BadgeContentProperty = AvaloniaProperty.Register( + nameof(BadgeContent)); + public object? BadgeContent + { + get => GetValue(BadgeContentProperty); + set => SetValue(BadgeContentProperty, value); + } + + public static readonly StyledProperty CornerPositionProperty = AvaloniaProperty.Register( + nameof(CornerPosition)); + public CornerPosition CornerPosition + { + get => GetValue(CornerPositionProperty); + set => SetValue(CornerPositionProperty, value); + } + + public static readonly StyledProperty OverflowCountProperty = AvaloniaProperty.Register( + nameof(OverflowCount)); + public int OverflowCount + { + get => GetValue(OverflowCountProperty); + set => SetValue(OverflowCountProperty, value); + } + + static Badge() + { + BadgeContentProperty.Changed.AddClassHandler((badge, args) => badge.UpdateBadgePosition()); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _content = e.NameScope.Find(PART_ContentPresenter); + _badgeContainer = e.NameScope.Find(PART_BadgeContainer); + _badgeContent = e.NameScope.Find(PART_BadgeContentPresenter); + } + + protected override void OnLoaded() + { + base.OnLoaded(); + UpdateBadgePosition(); + } + + protected override Size ArrangeOverride(Size finalSize) + { + UpdateBadgePosition(); + return base.ArrangeOverride(finalSize); + } + + private void UpdateBadgePosition() + { + var vertical = CornerPosition is CornerPosition.BottomLeft or CornerPosition.BottomRight ? 1 : -1; + var horizontal = CornerPosition is CornerPosition.TopRight or CornerPosition.BottomRight ? 1 : -1; + if (_badgeContainer is not null && _content?.Child is not null) + { + _badgeContainer.RenderTransform = new TransformGroup() + { + Children = new Transforms() + { + new TranslateTransform(horizontal*_badgeContainer.Bounds.Width/2,vertical*_badgeContainer.Bounds.Height/2) + } + }; + } + } +} \ No newline at end of file diff --git a/src/Ursa/Converters/BadgeContentOverflowConverter.cs b/src/Ursa/Converters/BadgeContentOverflowConverter.cs new file mode 100644 index 0000000..922c44f --- /dev/null +++ b/src/Ursa/Converters/BadgeContentOverflowConverter.cs @@ -0,0 +1,20 @@ +using System.Globalization; +using Avalonia.Data.Converters; + +namespace Ursa.Converters; + +public class BadgeContentOverflowConverter: IMultiValueConverter +{ + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + string overflowMark = parameter is string s ? s : "+"; + if (double.TryParse(values[0]?.ToString(), out var b) && values[1] is int i and > 0) + { + if (b > i) + { + return i + overflowMark; + } + } + return values[0]; + } +} \ No newline at end of file