mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-21 16:36:27 +08:00
Fix TimelineIndicator memory leak
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user