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);
}
}