Fix TimelineIndicator memory leak

This commit is contained in:
chinboy
2025-03-02 17:20:57 +08:00
parent 9fcb9721fb
commit a2ffdc7e42
@@ -1,5 +1,7 @@
using System.Reactive.Disposables;
using AtomUI.Controls.Utils;
using AtomUI.IconPkg;
using AtomUI.Theme;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using Avalonia;
@@ -10,13 +12,14 @@ using Avalonia.Media;
namespace AtomUI.Controls;
internal class TimelineIndicator : Control
internal class TimelineIndicator : Control,
ITokenResourceConsumer
{
#region 公共属性定义
public static readonly StyledProperty<Icon?> IndicatorIconProperty =
AvaloniaProperty.Register<TimelineIndicator, Icon?>(nameof(IndicatorIcon));
public static readonly StyledProperty<IBrush?> IndicatorColorProperty =
AvaloniaProperty.Register<TimelineIndicator, IBrush?>(nameof(IndicatorColor));
@@ -25,66 +28,67 @@ internal class TimelineIndicator : Control
get => GetValue(IndicatorIconProperty);
set => SetValue(IndicatorIconProperty, value);
}
public IBrush? IndicatorColor
{
get => GetValue(IndicatorColorProperty);
set => SetValue(IndicatorColorProperty, value);
}
#endregion
#region 内部属性定义
internal static readonly DirectProperty<TimelineIndicator, double> LineHeightRatioProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(LineHeightRatio),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(LineHeightRatio),
o => o.LineHeightRatio,
(o, v) => o.LineHeightRatio = v);
internal static readonly DirectProperty<TimelineIndicator, bool> IsFirstProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, bool>(nameof(IsFirst),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, bool>(nameof(IsFirst),
o => o.IsFirst,
(o, v) => o.IsFirst = v);
internal static readonly DirectProperty<TimelineIndicator, bool> IsLastProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, bool>(nameof(IsLast),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, bool>(nameof(IsLast),
o => o.IsLast,
(o, v) => o.IsLast = v);
internal static readonly DirectProperty<TimelineIndicator, double> IndicatorMinHeightProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(LineHeightRatio),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(LineHeightRatio),
o => o.IndicatorMinHeight,
(o, v) => o.IndicatorMinHeight = v);
internal static readonly DirectProperty<TimelineIndicator, IBrush?> DefaultIndicatorColorProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, IBrush?>(nameof(DefaultIndicatorColor),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, IBrush?>(nameof(DefaultIndicatorColor),
o => o.DefaultIndicatorColor,
(o, v) => o.DefaultIndicatorColor = v);
internal static readonly DirectProperty<TimelineIndicator, double> IndicatorDotBorderWidthProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(IndicatorDotBorderWidth),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(IndicatorDotBorderWidth),
o => o.IndicatorDotBorderWidth,
(o, v) => o.IndicatorDotBorderWidth = v);
internal static readonly DirectProperty<TimelineIndicator, IBrush?> IndicatorTailColorProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, IBrush?>(nameof(IndicatorTailColor),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, IBrush?>(nameof(IndicatorTailColor),
o => o.IndicatorTailColor,
(o, v) => o.IndicatorTailColor = v);
internal static readonly DirectProperty<TimelineIndicator, double> IndicatorTailWidthProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(IndicatorTailWidth),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(IndicatorTailWidth),
o => o.IndicatorTailWidth,
(o, v) => o.IndicatorTailWidth = v);
internal static readonly DirectProperty<TimelineIndicator, double> IndicatorDotSizeProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(IndicatorDotSize),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, double>(nameof(IndicatorDotSize),
o => o.IndicatorDotSize,
(o, v) => o.IndicatorDotSize = v);
internal static readonly DirectProperty<TimelineIndicator, bool> NextIsPendingProperty
= AvaloniaProperty.RegisterDirect<TimelineIndicator, bool>(nameof(NextIsPending),
= AvaloniaProperty.RegisterDirect<TimelineIndicator, bool>(nameof(NextIsPending),
o => o.NextIsPending,
(o, v) => o.NextIsPending = v);
private double _lineHeightRatio;
internal double LineHeightRatio
@@ -92,8 +96,7 @@ internal class TimelineIndicator : Control
get => _lineHeightRatio;
set => SetAndRaise(LineHeightRatioProperty, ref _lineHeightRatio, value);
}
private IBrush? _defaultIndicatorColor;
internal IBrush? DefaultIndicatorColor
@@ -101,7 +104,7 @@ internal class TimelineIndicator : Control
get => _defaultIndicatorColor;
set => SetAndRaise(DefaultIndicatorColorProperty, ref _defaultIndicatorColor, value);
}
private bool _isFirst;
internal bool IsFirst
@@ -109,7 +112,7 @@ internal class TimelineIndicator : Control
get => _isFirst;
set => SetAndRaise(IsFirstProperty, ref _isFirst, value);
}
private bool _isLast;
internal bool IsLast
@@ -117,7 +120,7 @@ internal class TimelineIndicator : Control
get => _isLast;
set => SetAndRaise(IsLastProperty, ref _isLast, value);
}
private double _indicatorMinHeight;
internal double IndicatorMinHeight
@@ -125,7 +128,7 @@ internal class TimelineIndicator : Control
get => _indicatorMinHeight;
set => SetAndRaise(IndicatorMinHeightProperty, ref _indicatorMinHeight, value);
}
private double _indicatorDotBorderWidth;
internal double IndicatorDotBorderWidth
@@ -133,7 +136,7 @@ internal class TimelineIndicator : Control
get => _indicatorDotBorderWidth;
set => SetAndRaise(IndicatorDotBorderWidthProperty, ref _indicatorDotBorderWidth, value);
}
private IBrush? _indicatorTailColor;
internal IBrush? IndicatorTailColor
@@ -141,7 +144,7 @@ internal class TimelineIndicator : Control
get => _indicatorTailColor;
set => SetAndRaise(IndicatorTailColorProperty, ref _indicatorTailColor, value);
}
private double _indicatorTailWidth;
internal double IndicatorTailWidth
@@ -149,7 +152,7 @@ internal class TimelineIndicator : Control
get => _indicatorTailWidth;
set => SetAndRaise(IndicatorTailWidthProperty, ref _indicatorTailWidth, value);
}
private double _indicatorDotSize;
internal double IndicatorDotSize
@@ -157,7 +160,7 @@ internal class TimelineIndicator : Control
get => _indicatorDotSize;
set => SetAndRaise(IndicatorDotSizeProperty, ref _indicatorDotSize, value);
}
private bool _nextIsPending;
internal bool NextIsPending
@@ -165,15 +168,17 @@ internal class TimelineIndicator : Control
get => _nextIsPending;
set => SetAndRaise(NextIsPendingProperty, ref _nextIsPending, value);
}
CompositeDisposable? ITokenResourceConsumer.TokenBindingsDisposable => _tokenBindingsDisposable;
#endregion
private CompositeDisposable? _tokenBindingsDisposable;
static TimelineIndicator()
{
AffectsMeasure<TimelineIndicator>(IsFirstProperty, IsLastProperty);
AffectsMeasure<TimelineIndicator>(IndicatorIconProperty);
AffectsMeasure<TimelineIndicator>(IsFirstProperty, IsLastProperty, IndicatorIconProperty);
AffectsRender<TimelineIndicator>(IndicatorColorProperty);
IndicatorIconProperty.Changed.AddClassHandler<TimelineIndicator>((x, args) => x.HandleIndicatorIconChanged(args));
TextElement.FontSizeProperty.Changed.AddClassHandler<TimelineIndicator>((indicator, args) =>
{
indicator.IndicatorMinHeight = args.GetNewValue<double>() * indicator.LineHeightRatio;
@@ -183,12 +188,27 @@ internal class TimelineIndicator : Control
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
TokenResourceBinder.CreateTokenBinding(this, LineHeightRatioProperty, SharedTokenKey.LineHeightRatio);
TokenResourceBinder.CreateTokenBinding(this, IndicatorDotBorderWidthProperty, TimelineTokenKey.IndicatorDotBorderWidth);
TokenResourceBinder.CreateTokenBinding(this, IndicatorDotSizeProperty, TimelineTokenKey.IndicatorDotSize);
TokenResourceBinder.CreateTokenBinding(this, DefaultIndicatorColorProperty, SharedTokenKey.ColorPrimary);
TokenResourceBinder.CreateTokenBinding(this, IndicatorTailWidthProperty, TimelineTokenKey.IndicatorTailWidth);
TokenResourceBinder.CreateTokenBinding(this, IndicatorTailColorProperty, TimelineTokenKey.IndicatorTailColor);
_tokenBindingsDisposable = new CompositeDisposable();
this.AddTokenBindingDisposable(
TokenResourceBinder.CreateTokenBinding(this, LineHeightRatioProperty, SharedTokenKey.LineHeightRatio));
this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, IndicatorDotBorderWidthProperty,
TimelineTokenKey.IndicatorDotBorderWidth));
this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, IndicatorDotSizeProperty,
TimelineTokenKey.IndicatorDotSize));
this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, DefaultIndicatorColorProperty,
SharedTokenKey.ColorPrimary));
this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, IndicatorTailWidthProperty,
TimelineTokenKey.IndicatorTailWidth));
this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, IndicatorTailColorProperty,
TimelineTokenKey.IndicatorTailColor));
SetupIndicatorIcon();
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
this.DisposeTokenBindings();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
@@ -197,19 +217,35 @@ internal class TimelineIndicator : Control
if (change.Property == LineHeightRatioProperty)
{
IndicatorMinHeight = LineHeightRatio * TextElement.GetFontSize(this);
} else if (change.Property == DefaultIndicatorColorProperty ||
change.Property == IndicatorColorProperty)
}
else if (change.Property == DefaultIndicatorColorProperty ||
change.Property == IndicatorColorProperty)
{
if (IndicatorIcon != null)
{
IndicatorIcon.NormalFilledBrush = IndicatorColor ?? DefaultIndicatorColor;
}
}
if (this.IsAttachedToLogicalTree())
{
if (change.Property == IndicatorIconProperty)
{
if (change.OldValue is Icon oldIcon)
{
oldIcon.SetLogicalParent(null);
LogicalChildren.Remove(oldIcon);
VisualChildren.Remove(oldIcon);
}
SetupIndicatorIcon();
}
}
}
protected override Size MeasureOverride(Size availableSize)
{
var size = base.MeasureOverride(availableSize);
var size = base.MeasureOverride(availableSize);
var height = Math.Max(size.Height, IndicatorMinHeight);
return new Size(size.Width, height);
}
@@ -220,30 +256,24 @@ internal class TimelineIndicator : Control
{
var offsetX = (finalSize.Width - IndicatorIcon.DesiredSize.Width) / 2;
var offsetY = (_indicatorMinHeight - IndicatorIcon.DesiredSize.Height) / 2;
IndicatorIcon.Arrange(new Rect(offsetX, offsetY, IndicatorIcon.DesiredSize.Width, IndicatorIcon.DesiredSize.Height));
IndicatorIcon.Arrange(new Rect(offsetX, offsetY, IndicatorIcon.DesiredSize.Width,
IndicatorIcon.DesiredSize.Height));
}
return finalSize;
}
private void HandleIndicatorIconChanged(AvaloniaPropertyChangedEventArgs args)
private void SetupIndicatorIcon()
{
var oldChild = (Icon?)args.OldValue;
var newChild = (Icon?)args.NewValue;
if (oldChild != null)
if (IndicatorIcon != null)
{
oldChild.SetLogicalParent(null);
LogicalChildren.Clear();
VisualChildren.Remove(oldChild);
}
if (newChild != null)
{
newChild.SetLogicalParent(this);
TokenResourceBinder.CreateTokenBinding(newChild, Icon.WidthProperty, TimelineTokenKey.IndicatorWidth);
TokenResourceBinder.CreateTokenBinding(newChild, Icon.HeightProperty, TimelineTokenKey.IndicatorWidth);
VisualChildren.Add(newChild);
LogicalChildren.Add(newChild);
IndicatorIcon.SetLogicalParent(this);
this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(IndicatorIcon, Icon.WidthProperty,
TimelineTokenKey.IndicatorWidth));
this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(IndicatorIcon, Icon.HeightProperty,
TimelineTokenKey.IndicatorWidth));
VisualChildren.Add(IndicatorIcon);
LogicalChildren.Add(IndicatorIcon);
}
}
@@ -259,7 +289,7 @@ internal class TimelineIndicator : Control
var centerX = (Bounds.Width - IndicatorDotSize) / 2 + dotRadius;
var centerY = (IndicatorMinHeight - IndicatorDotSize) / 2 + dotRadius - 1;
dotBelowLineStartOffsetY = centerY + dotRadius + IndicatorDotBorderWidth / 2;
dotUpLineEndOffsetY = centerY - dotRadius - IndicatorDotBorderWidth / 2;
dotUpLineEndOffsetY = centerY - dotRadius - IndicatorDotBorderWidth / 2;
context.DrawEllipse(null, dotPen, new Point(centerX, centerY), dotRadius, dotRadius);
}
else
@@ -272,7 +302,6 @@ internal class TimelineIndicator : Control
var linePen = new Pen(IndicatorTailColor, IndicatorTailWidth);
if (!IsLast)
{
var dotBelowLineStartPoint = new Point(lineOffsetX, dotBelowLineStartOffsetY);
var dotBelowLineEndPoint = new Point(lineOffsetX, Bounds.Bottom);
context.DrawLine(linePen, dotBelowLineStartPoint, dotBelowLineEndPoint);