From 090d17a1c81e146b9899ce6b741f9a53401ed6b5 Mon Sep 17 00:00:00 2001
From: NaBian <836904362@qq.com>
Date: Sun, 14 Jun 2026 13:17:45 +0800
Subject: [PATCH] feat: Add Circle and Wave progress bars and demo.
---
.../Data/DemoInfo.json | 3 +-
.../Controls/ProgressBarDemo.axaml | 88 +++++
.../Controls/ProgressBarDemo.axaml.cs | 9 +
.../Controls/ProgressBar/CircleProgressBar.cs | 87 +++++
.../Controls/ProgressBar/WaveProgressBar.cs | 120 +++++++
.../Themes/Styles/ProgressBar.axaml | 336 +++++++++++++++++-
6 files changed, 641 insertions(+), 2 deletions(-)
create mode 100644 src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/ProgressBarDemo.axaml
create mode 100644 src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/ProgressBarDemo.axaml.cs
create mode 100644 src/Avalonia/HandyControl_Avalonia/Controls/ProgressBar/CircleProgressBar.cs
create mode 100644 src/Avalonia/HandyControl_Avalonia/Controls/ProgressBar/WaveProgressBar.cs
diff --git a/src/Avalonia/HandyControlDemo_Avalonia/Data/DemoInfo.json b/src/Avalonia/HandyControlDemo_Avalonia/Data/DemoInfo.json
index 87e98710..b7a4576b 100644
--- a/src/Avalonia/HandyControlDemo_Avalonia/Data/DemoInfo.json
+++ b/src/Avalonia/HandyControlDemo_Avalonia/Data/DemoInfo.json
@@ -47,7 +47,8 @@
[ "FlexPanel", "FlexPanelDemo", "Brush.FlowLayoutPanel", "", "Layout" ],
[ "WaterfallPanel", "WaterfallPanelDemo", "Brush.Panel", "", "Layout" ],
[ "HoneycombPanel", "HoneycombPanelDemo", "Brush.PolygonHexagon", "", "Layout" ],
- [ "UniformSpacingPanel", "UniformSpacingPanelDemo", "Brush.IncreaseHorizontalSpacing", "", "Layout" ]
+ [ "UniformSpacingPanel", "UniformSpacingPanelDemo", "Brush.IncreaseHorizontalSpacing", "", "Layout" ],
+ [ "ProgressBar", "ProgressBarDemo", "Brush.ProgressBar", "", "Feedback" ]
]
}
]
diff --git a/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/ProgressBarDemo.axaml b/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/ProgressBarDemo.axaml
new file mode 100644
index 00000000..708b0b3f
--- /dev/null
+++ b/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/ProgressBarDemo.axaml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/ProgressBarDemo.axaml.cs b/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/ProgressBarDemo.axaml.cs
new file mode 100644
index 00000000..4e60d7c3
--- /dev/null
+++ b/src/Avalonia/HandyControlDemo_Avalonia/UserControl/Controls/ProgressBarDemo.axaml.cs
@@ -0,0 +1,9 @@
+namespace HandyControlDemo.UserControl;
+
+public partial class ProgressBarDemo : Avalonia.Controls.UserControl
+{
+ public ProgressBarDemo()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/Avalonia/HandyControl_Avalonia/Controls/ProgressBar/CircleProgressBar.cs b/src/Avalonia/HandyControl_Avalonia/Controls/ProgressBar/CircleProgressBar.cs
new file mode 100644
index 00000000..4ba2b9c6
--- /dev/null
+++ b/src/Avalonia/HandyControl_Avalonia/Controls/ProgressBar/CircleProgressBar.cs
@@ -0,0 +1,87 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Shapes;
+
+namespace HandyControl.Controls;
+
+[TemplatePart(IndicatorTemplateName, typeof(Arc))]
+public class CircleProgressBar : RangeBase
+{
+ private const string IndicatorTemplateName = "PART_Indicator";
+
+ public static readonly StyledProperty ArcThicknessProperty =
+ AvaloniaProperty.Register(nameof(ArcThickness));
+
+ public static readonly StyledProperty ShowTextProperty =
+ AvaloniaProperty.Register(nameof(ShowText), true);
+
+ public static readonly StyledProperty TextProperty =
+ AvaloniaProperty.Register(nameof(Text));
+
+ public static readonly StyledProperty IsIndeterminateProperty =
+ AvaloniaProperty.Register(nameof(IsIndeterminate));
+
+ private Arc? _indicator;
+
+ static CircleProgressBar()
+ {
+ FocusableProperty.OverrideDefaultValue(false);
+ MaximumProperty.OverrideDefaultValue(100d);
+ }
+
+ public string? Text
+ {
+ get => GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ public bool ShowText
+ {
+ get => GetValue(ShowTextProperty);
+ set => SetValue(ShowTextProperty, value);
+ }
+
+ public double ArcThickness
+ {
+ get => GetValue(ArcThicknessProperty);
+ set => SetValue(ArcThicknessProperty, value);
+ }
+
+ public bool IsIndeterminate
+ {
+ get => GetValue(IsIndeterminateProperty);
+ set => SetValue(IsIndeterminateProperty, value);
+ }
+
+ private void SetProgressBarIndicatorAngle()
+ {
+ if (_indicator == null) return;
+ var minimum = Minimum;
+ var maximum = Maximum;
+ var num = Value;
+ _indicator.StartAngle = 0;
+ _indicator.SweepAngle = (maximum <= minimum ? 0 : (num - minimum) / (maximum - minimum)) * 360;
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ _indicator = e.NameScope.Find(IndicatorTemplateName);
+ SetProgressBarIndicatorAngle();
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == MinimumProperty
+ || change.Property == MaximumProperty
+ || change.Property == ValueProperty)
+ {
+ SetProgressBarIndicatorAngle();
+ }
+ }
+}
diff --git a/src/Avalonia/HandyControl_Avalonia/Controls/ProgressBar/WaveProgressBar.cs b/src/Avalonia/HandyControl_Avalonia/Controls/ProgressBar/WaveProgressBar.cs
new file mode 100644
index 00000000..7ede4115
--- /dev/null
+++ b/src/Avalonia/HandyControl_Avalonia/Controls/ProgressBar/WaveProgressBar.cs
@@ -0,0 +1,120 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
+using Avalonia.Media;
+using System;
+
+namespace HandyControl.Controls;
+
+///
+/// 波浪进度条
+///
+[TemplatePart(ElementWave, typeof(Control))]
+[TemplatePart(ElementClip, typeof(Control))]
+public class WaveProgressBar : RangeBase
+{
+ private const string ElementWave = "PART_Wave";
+
+ private const string ElementClip = "PART_Clip";
+
+ private const double TranslateTransformMinY = -20;
+
+ private Control? _waveElement;
+
+ private double _translateTransformYRange;
+
+ private TranslateTransform? _yTransform;
+
+ static WaveProgressBar()
+ {
+ FocusableProperty.OverrideDefaultValue(false);
+ MaximumProperty.OverrideDefaultValue(100d);
+ }
+
+ public static readonly StyledProperty TextProperty =
+ AvaloniaProperty.Register(nameof(Text));
+
+ public string? Text
+ {
+ get => GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ public static readonly StyledProperty ShowTextProperty =
+ AvaloniaProperty.Register(nameof(ShowText), true);
+
+ public bool ShowText
+ {
+ get => GetValue(ShowTextProperty);
+ set => SetValue(ShowTextProperty, value);
+ }
+
+ public static readonly StyledProperty WaveFillProperty =
+ AvaloniaProperty.Register(nameof(WaveFill));
+
+ public IBrush? WaveFill
+ {
+ get => GetValue(WaveFillProperty);
+ set => SetValue(WaveFillProperty, value);
+ }
+
+ public static readonly StyledProperty WaveThicknessProperty =
+ AvaloniaProperty.Register(nameof(WaveThickness));
+
+ public double WaveThickness
+ {
+ get => GetValue(WaveThicknessProperty);
+ set => SetValue(WaveThicknessProperty, value);
+ }
+
+ public static readonly StyledProperty WaveStrokeProperty =
+ AvaloniaProperty.Register(nameof(WaveStroke));
+
+ public IBrush? WaveStroke
+ {
+ get => GetValue(WaveStrokeProperty);
+ set => SetValue(WaveStrokeProperty, value);
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+
+ _waveElement = e.NameScope.Find(ElementWave);
+ var clipElement = e.NameScope.Find(ElementClip);
+
+ if (_waveElement != null && clipElement != null)
+ {
+ var clipElementHeight = clipElement.Height;
+
+ _yTransform = new TranslateTransform { Y = clipElementHeight };
+ _translateTransformYRange = clipElementHeight - TranslateTransformMinY;
+ _waveElement.RenderTransform = _yTransform;
+
+ UpdateWave(Value);
+ }
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == ValueProperty
+ || change.Property == MinimumProperty
+ || change.Property == MaximumProperty)
+ {
+ UpdateWave(Value);
+ }
+ }
+
+ private void UpdateWave(double value)
+ {
+ if (_yTransform == null) return;
+ var maximum = Maximum;
+ if (Math.Abs(maximum) < 1e-6) return;
+ var scale = 1 - value / maximum;
+ var y = _translateTransformYRange * scale + TranslateTransformMinY;
+ _yTransform.Y = y;
+ }
+}
diff --git a/src/Avalonia/HandyControl_Avalonia/Themes/Styles/ProgressBar.axaml b/src/Avalonia/HandyControl_Avalonia/Themes/Styles/ProgressBar.axaml
index 80e4ca1b..b913a842 100644
--- a/src/Avalonia/HandyControl_Avalonia/Themes/Styles/ProgressBar.axaml
+++ b/src/Avalonia/HandyControl_Avalonia/Themes/Styles/ProgressBar.axaml
@@ -1,7 +1,8 @@
+ xmlns:hc="clr-namespace:HandyControl.Controls"
+ xmlns:shapes="clr-namespace:Avalonia.Controls.Shapes;assembly=Avalonia.Controls">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+