From c2744effa3ec3fb2fb0eb77af2720d962df8eead Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:02:31 +0800 Subject: [PATCH] agh --- samples/Sandbox/App.axaml | 2 +- samples/Sandbox/MainWindow.axaml | 28 ++++++ samples/Sandbox/Sandbox.csproj | 5 +- src/Avalonia.Base/Animation/Animatable.cs | 3 +- src/Avalonia.Base/Animation/Animation.cs | 2 +- .../Animation/Animators/Animator`1.cs | 7 +- .../DisposeAnimationInstanceSubject.cs | 69 ++++++++++---- src/Avalonia.Base/Visual.cs | 95 +++++++------------ 8 files changed, 120 insertions(+), 91 deletions(-) diff --git a/samples/Sandbox/App.axaml b/samples/Sandbox/App.axaml index cf3e5e445a..e51c481729 100644 --- a/samples/Sandbox/App.axaml +++ b/samples/Sandbox/App.axaml @@ -3,6 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Sandbox.App"> - + diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 6929f192c7..d8f2ebfe87 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,4 +1,32 @@ + + A progress bar control + + + Maximum + + + + Minimum + + + + Progress Text Format + + + + + + + + + + + + + + diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj index fac565b55a..243e92ffca 100644 --- a/samples/Sandbox/Sandbox.csproj +++ b/samples/Sandbox/Sandbox.csproj @@ -2,9 +2,8 @@ WinExe - net6.0 - true - true + net8.0 + true diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs index 0a1fae9dbb..ab786fe149 100644 --- a/src/Avalonia.Base/Animation/Animatable.cs +++ b/src/Avalonia.Base/Animation/Animatable.cs @@ -32,8 +32,7 @@ namespace Avalonia.Animation private Dictionary? _transitionState; private NotifyCollectionChangedEventHandler? _collectionChanged; - protected bool _animationsEnabled = true; - internal LightweightSubject? animationsStateSubject; + internal LightweightSubject? animationsGateSubject; private NotifyCollectionChangedEventHandler TransitionsCollectionChangedHandler => _collectionChanged ??= TransitionsCollectionChanged; diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs index 652f1e6e41..27ada3be40 100644 --- a/src/Avalonia.Base/Animation/Animation.cs +++ b/src/Avalonia.Base/Animation/Animation.cs @@ -277,7 +277,7 @@ namespace Avalonia.Animation var (animators, subscriptions) = InterpretKeyframes(control); var overrideSubject = new LightweightSubject(); - control.animationsStateSubject = overrideSubject; + control.animationsGateSubject = overrideSubject; if (animators.Count == 1) { diff --git a/src/Avalonia.Base/Animation/Animators/Animator`1.cs b/src/Avalonia.Base/Animation/Animators/Animator`1.cs index 6c7ec7e3b8..30fb86cd2f 100644 --- a/src/Avalonia.Base/Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Base/Animation/Animators/Animator`1.cs @@ -20,8 +20,11 @@ namespace Avalonia.Animation.Animators public virtual IDisposable? Apply(Animation animation, Animatable target, IClock? clock, IObservable match, IObservable state, Action? onComplete) { - var subject = new DisposeAnimationInstanceSubject(this, animation, target, clock, state, onComplete); - return new CompositeDisposable(match.Subscribe(subject), subject); + var disposable = new CompositeDisposable(); + var subject = new DisposeAnimationInstanceSubject(this, animation, target, clock, onComplete, state, disposable); + disposable.Add(match.Subscribe(subject)); + disposable.Add(subject); + return disposable; } protected T InterpolationHandler(double animationTime, T neutralValue) diff --git a/src/Avalonia.Base/Animation/DisposeAnimationInstanceSubject.cs b/src/Avalonia.Base/Animation/DisposeAnimationInstanceSubject.cs index 64a8bc0b93..0c2cafad6a 100644 --- a/src/Avalonia.Base/Animation/DisposeAnimationInstanceSubject.cs +++ b/src/Avalonia.Base/Animation/DisposeAnimationInstanceSubject.cs @@ -1,5 +1,7 @@ using System; +using System.Threading; using Avalonia.Animation.Animators; +using Avalonia.Reactive; namespace Avalonia.Animation { @@ -9,29 +11,32 @@ namespace Avalonia.Animation internal class DisposeAnimationInstanceSubject : IObserver, IDisposable { private IDisposable? _lastInstance; - private bool _lastMatch; + private bool _lastMatch, _lastGate; private readonly Animator _animator; private readonly Animation _animation; private readonly Animatable _control; private readonly Action? _onComplete; private readonly IClock? _clock; + private readonly object _lock = new object(); - public DisposeAnimationInstanceSubject(Animator animator, - Animation animation, Animatable control, IClock? clock, IObservable state, Action? onComplete) + public DisposeAnimationInstanceSubject(Animator animator, + Animation animation, Animatable control, IClock? clock, Action? onComplete, IObservable state, CompositeDisposable disposable) { this._animator = animator; this._animation = animation; this._control = control; this._onComplete = onComplete; this._clock = clock; - - // TODO: this causes weird bugs. - state.Subscribe(this); + + disposable.Add(state.Subscribe(AnimationStateChange)); } public void Dispose() { - _lastInstance?.Dispose(); + lock (_lock) + { + _lastInstance?.Dispose(); + } } public void OnCompleted() @@ -40,27 +45,49 @@ namespace Avalonia.Animation public void OnError(Exception error) { - _lastInstance?.Dispose(); - _lastInstance = null; + lock (_lock) + { + _lastInstance?.Dispose(); + _lastInstance = null; + } } - public void OnNext(bool matchVal) + private void AnimationStateChange(bool gateState) { - if (matchVal != _lastMatch) + lock (_lock) { - _lastInstance?.Dispose(); + if(Volatile.Read(ref _lastGate) == gateState) return; + Volatile.Write(ref _lastGate, gateState); + StateChange(); + } + } - if (matchVal) - { - _lastInstance = _animator.Run(_animation, _control, _clock, _onComplete); - } - else - { - _lastInstance = null; - } + public void OnNext(bool matchVal) + { + lock (_lock) + { + if (Volatile.Read(ref _lastMatch) == matchVal) return; + Volatile.Write(ref _lastMatch, matchVal); + StateChange(); + } + } + + void StateChange() + { + bool match = Volatile.Read(ref _lastMatch); + bool gate = Volatile.Read(ref _lastGate); + + _lastInstance?.Dispose(); - _lastMatch = matchVal; + if (match & gate) + { + _lastInstance = _animator.Run(_animation, _control, _clock, _onComplete); + } + else + { + _lastInstance = null; } } + } } diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 36430a2c80..3f9c0a26b0 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -104,6 +104,14 @@ namespace Avalonia /// public static readonly DirectProperty VisualParentProperty = AvaloniaProperty.RegisterDirect(nameof(VisualParent), o => o._visualParent); + + /// + /// Defines the property. + /// + public static readonly DirectProperty IsEffectivelyVisibleProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsEffectivelyVisible), + o => o._isEffectivelyVisible); /// /// Defines the property. @@ -121,7 +129,9 @@ namespace Avalonia private Visual? _visualParent; private bool _hasMirrorTransform; private TargetWeakEventSubscriber? _affectsRenderWeakSubscriber; - + private bool _isEffectivelyVisible = true; + + /// /// Initializes static members of the class. /// @@ -143,6 +153,7 @@ namespace Avalonia /// public Visual() { + SetAnimationGateState(false); // Disable transitions until we're added to the visual tree. DisableTransitions(); @@ -193,25 +204,7 @@ namespace Avalonia /// /// Gets a value indicating whether this control and all its parents are visible. /// - public bool IsEffectivelyVisible - { - get - { - Visual? node = this; - - while (node != null) - { - if (!node.IsVisible) - { - return false; - } - - node = node.VisualParent; - } - - return true; - } - } + public bool IsEffectivelyVisible { get; } /// /// Gets or sets a value indicating whether this control is visible. @@ -496,7 +489,7 @@ namespace Avalonia mutableTransform.Changed += RenderTransformChanged; } - EnableAnimations(); + SetAnimationGateState(true); EnableTransitions(); if (_visualRoot.Renderer is IRendererWithCompositor compositingRenderer) @@ -540,7 +533,7 @@ namespace Avalonia mutableTransform.Changed -= RenderTransformChanged; } - DisableAnimations(); + SetAnimationGateState(false); DisableTransitions(); OnDetachedFromVisualTree(e); DetachFromCompositor(); @@ -668,53 +661,33 @@ namespace Avalonia /// Called when the property changes on any control. /// /// The event args. - private static void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e) + private void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e) { - if (e.Sender is not Visual visual) return; - - if (visual.IsEffectivelyVisible) + if (e.NewValue is bool newVisibility) { - visual.EnableAnimations(); - } - else - { - visual.DisableAnimations(); - } + CalculateEffectiveVisibility(newVisibility); + } } - - internal void EnableAnimations() + + private void SetAnimationGateState(bool gateState) { - if (!_animationsEnabled) + if (animationsGateSubject is { } animationsGate) { - _animationsEnabled = true; - - if (animationsStateSubject is { } ds) - { - ds.OnNext(true); - } - - foreach (var child in this.GetVisualDescendants()) - { - child.EnableAnimations(); - } - } + animationsGate.OnNext(gateState); + } } - - internal void DisableAnimations() + + internal void CalculateEffectiveVisibility(bool newVisibility) { - if (_animationsEnabled) + if(_isEffectivelyVisible == newVisibility) return; + + SetAndRaise(IsEffectivelyVisibleProperty, ref _isEffectivelyVisible, newVisibility); + + SetAnimationGateState(newVisibility); + + foreach (var child in this.GetVisualDescendants()) { - _animationsEnabled = false; - - if (animationsStateSubject is { } ds) - { - ds.OnNext(false); - } - - foreach (var child in this.GetVisualDescendants()) - { - child.DisableAnimations(); - } + child.CalculateEffectiveVisibility(newVisibility); } }