From 9adc3c29752f9653367bcda8c6c0a8b0798ff976 Mon Sep 17 00:00:00 2001 From: TheXamlGuy Date: Tue, 16 Jul 2024 20:22:37 +0100 Subject: [PATCH] Add a ContentBadge control --- .../ContentBadge/ContentBadge.axaml | 15 +++ .../ContentBadge/ContentBadge.cs | 97 +++++++++++++++++++ .../Overflow/Overflow.cs | 10 +- .../Themes/ControlResources.axaml | 1 + 4 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.axaml create mode 100644 Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.cs diff --git a/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.axaml b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.axaml new file mode 100644 index 0000000..26b79d6 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.axaml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.cs b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.cs new file mode 100644 index 0000000..2b0d87a --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/ContentBadge/ContentBadge.cs @@ -0,0 +1,97 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Media; +using Path = Avalonia.Controls.Shapes.Path; + +namespace Toolkit.UI.Controls.Avalonia; + +public class ContentBadge : + ContentControl +{ + public static readonly StyledProperty BadgePathProperty = + AvaloniaProperty.Register(nameof(BadgePath)); + + public static readonly StyledProperty BadgeSizeProperty = + AvaloniaProperty.Register(nameof(BadgeSize), 14); + + private ContentControl? badgeContent; + + public string? BadgePath + { + get => GetValue(BadgePathProperty); + set => SetValue(BadgePathProperty, value); + } + + public double BadgeSize + { + get => GetValue(BadgeSizeProperty); + set => SetValue(BadgeSizeProperty, value); + } + + public void UpdateClip() + { + if (Content is Control content && + badgeContent is not null && + BadgePath is { Length: > 0 } && + Geometry.Parse(BadgePath) is Geometry geometry) + { + double backgroundWidth = DesiredSize.Width; + double backgroundHeight = DesiredSize.Height; + + double scaleX = BadgeSize / geometry.Bounds.Width; + double scaleY = BadgeSize / geometry.Bounds.Height; + + double adjustedStrokeWidth = Math.Min(scaleX, scaleY) * 8; + + Geometry knockoutGeometry = geometry.GetWidenedGeometry(new Pen(new SolidColorBrush(Colors.Transparent), adjustedStrokeWidth); + + TransformGroup transformGroup = new(); + transformGroup.Children.Add(new ScaleTransform(scaleX, scaleY)); + + double scaledWidth = knockoutGeometry.Bounds.Width * scaleX; + double scaledHeight = knockoutGeometry.Bounds.Height * scaleY; + double offsetX = backgroundWidth - scaledWidth; + double offsetY = backgroundHeight - scaledHeight; + + transformGroup.Children.Add(new TranslateTransform(offsetX, offsetY)); + knockoutGeometry.Transform = transformGroup; + + CombinedGeometry combinedGeometry = new() + { + GeometryCombineMode = GeometryCombineMode.Exclude, + Geometry1 = new RectangleGeometry { Rect = new Rect(0, 0, backgroundWidth, backgroundHeight) }, + Geometry2 = knockoutGeometry + }; + + content.Clip = combinedGeometry; + + Geometry overlayGeometry = geometry.Clone(); + + TransformGroup overlayTransformGroup = new(); + overlayTransformGroup.Children.Add(new ScaleTransform(scaleX, scaleY)); + + overlayTransformGroup.Children.Add(new TranslateTransform(offsetX, offsetY)); + overlayGeometry.Transform = overlayTransformGroup; + + badgeContent.Content = new Path + { + Data = overlayGeometry, + Fill = Foreground + }; + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs args) + { + base.OnApplyTemplate(args); + badgeContent = args.NameScope.Get("BadgeContent"); + } + + protected override void OnSizeChanged(SizeChangedEventArgs args) + { + base.OnSizeChanged(args); + UpdateClip(); + } +} + diff --git a/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs b/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs index e6a0754..a8f2b9d 100644 --- a/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs +++ b/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs @@ -84,16 +84,10 @@ public class Overflow : base.OnApplyTemplate(args); primaryListBox = args.NameScope.Get("PrimaryListBox"); - if (primaryListBox is not null) - { - primaryListBox.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection); - } + primaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection); secondaryListBox = args.NameScope.Get("SecondaryListBox"); - if (secondaryListBox is not null) - { - secondaryListBox.SetValue(ItemsControl.ItemsSourceProperty, secondaryCollection); - } + secondaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, secondaryCollection); InitializeCollections(); UpdateOverflow(); diff --git a/Toolkit.UI.Controls.Avalonia/Themes/ControlResources.axaml b/Toolkit.UI.Controls.Avalonia/Themes/ControlResources.axaml index 196bb1a..b943686 100644 --- a/Toolkit.UI.Controls.Avalonia/Themes/ControlResources.axaml +++ b/Toolkit.UI.Controls.Avalonia/Themes/ControlResources.axaml @@ -7,6 +7,7 @@ +