diff --git a/src/AtomUI.Controls/Timeline/TimelineIndicator.cs b/src/AtomUI.Controls/Timeline/TimelineIndicator.cs index e60fada92..fe8513778 100644 --- a/src/AtomUI.Controls/Timeline/TimelineIndicator.cs +++ b/src/AtomUI.Controls/Timeline/TimelineIndicator.cs @@ -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 IndicatorIconProperty = AvaloniaProperty.Register(nameof(IndicatorIcon)); - + public static readonly StyledProperty IndicatorColorProperty = AvaloniaProperty.Register(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 LineHeightRatioProperty - = AvaloniaProperty.RegisterDirect(nameof(LineHeightRatio), + = AvaloniaProperty.RegisterDirect(nameof(LineHeightRatio), o => o.LineHeightRatio, (o, v) => o.LineHeightRatio = v); - + internal static readonly DirectProperty IsFirstProperty - = AvaloniaProperty.RegisterDirect(nameof(IsFirst), + = AvaloniaProperty.RegisterDirect(nameof(IsFirst), o => o.IsFirst, (o, v) => o.IsFirst = v); - + internal static readonly DirectProperty IsLastProperty - = AvaloniaProperty.RegisterDirect(nameof(IsLast), + = AvaloniaProperty.RegisterDirect(nameof(IsLast), o => o.IsLast, (o, v) => o.IsLast = v); - + internal static readonly DirectProperty IndicatorMinHeightProperty - = AvaloniaProperty.RegisterDirect(nameof(LineHeightRatio), + = AvaloniaProperty.RegisterDirect(nameof(LineHeightRatio), o => o.IndicatorMinHeight, (o, v) => o.IndicatorMinHeight = v); - + internal static readonly DirectProperty DefaultIndicatorColorProperty - = AvaloniaProperty.RegisterDirect(nameof(DefaultIndicatorColor), + = AvaloniaProperty.RegisterDirect(nameof(DefaultIndicatorColor), o => o.DefaultIndicatorColor, (o, v) => o.DefaultIndicatorColor = v); - + internal static readonly DirectProperty IndicatorDotBorderWidthProperty - = AvaloniaProperty.RegisterDirect(nameof(IndicatorDotBorderWidth), + = AvaloniaProperty.RegisterDirect(nameof(IndicatorDotBorderWidth), o => o.IndicatorDotBorderWidth, (o, v) => o.IndicatorDotBorderWidth = v); - + internal static readonly DirectProperty IndicatorTailColorProperty - = AvaloniaProperty.RegisterDirect(nameof(IndicatorTailColor), + = AvaloniaProperty.RegisterDirect(nameof(IndicatorTailColor), o => o.IndicatorTailColor, (o, v) => o.IndicatorTailColor = v); - + internal static readonly DirectProperty IndicatorTailWidthProperty - = AvaloniaProperty.RegisterDirect(nameof(IndicatorTailWidth), + = AvaloniaProperty.RegisterDirect(nameof(IndicatorTailWidth), o => o.IndicatorTailWidth, (o, v) => o.IndicatorTailWidth = v); - + internal static readonly DirectProperty IndicatorDotSizeProperty - = AvaloniaProperty.RegisterDirect(nameof(IndicatorDotSize), + = AvaloniaProperty.RegisterDirect(nameof(IndicatorDotSize), o => o.IndicatorDotSize, (o, v) => o.IndicatorDotSize = v); - + internal static readonly DirectProperty NextIsPendingProperty - = AvaloniaProperty.RegisterDirect(nameof(NextIsPending), + = AvaloniaProperty.RegisterDirect(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(IsFirstProperty, IsLastProperty); - AffectsMeasure(IndicatorIconProperty); + AffectsMeasure(IsFirstProperty, IsLastProperty, IndicatorIconProperty); AffectsRender(IndicatorColorProperty); - IndicatorIconProperty.Changed.AddClassHandler((x, args) => x.HandleIndicatorIconChanged(args)); TextElement.FontSizeProperty.Changed.AddClassHandler((indicator, args) => { indicator.IndicatorMinHeight = args.GetNewValue() * 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);