Browse Source

agh

pull/13954/head
Jumar Macato 2 years ago
parent
commit
c2744effa3
  1. 2
      samples/Sandbox/App.axaml
  2. 28
      samples/Sandbox/MainWindow.axaml
  3. 5
      samples/Sandbox/Sandbox.csproj
  4. 3
      src/Avalonia.Base/Animation/Animatable.cs
  5. 2
      src/Avalonia.Base/Animation/Animation.cs
  6. 7
      src/Avalonia.Base/Animation/Animators/Animator`1.cs
  7. 69
      src/Avalonia.Base/Animation/DisposeAnimationInstanceSubject.cs
  8. 95
      src/Avalonia.Base/Visual.cs

2
samples/Sandbox/App.axaml

@ -3,6 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Sandbox.App"> x:Class="Sandbox.App">
<Application.Styles> <Application.Styles>
<FluentTheme /> <SimpleTheme />
</Application.Styles> </Application.Styles>
</Application> </Application>

28
samples/Sandbox/MainWindow.axaml

@ -1,4 +1,32 @@
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="Sandbox.MainWindow"> x:Class="Sandbox.MainWindow">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">A progress bar control</TextBlock>
<StackPanel Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Maximum</TextBlock>
<NumericUpDown x:Name="maximum" Value="100" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Minimum</TextBlock>
<NumericUpDown x:Name="minimum" Value="0" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Progress Text Format</TextBlock>
<TextBox x:Name="stringFormat" Text="{}{0:0}%" VerticalAlignment="Center"/>
</StackPanel>
<CheckBox x:Name="showProgress" Margin="10,16,0,0" Content="Show Progress Text" />
<CheckBox x:Name="isIndeterminate" Margin="10,16,0,0" Content="Toggle Indeterminate" />
<CheckBox x:Name="isVisible" Margin="10,16,0,0" Content="Toggle Visibility" />
<StackPanel IsVisible="{Binding #isVisible.IsChecked}" Orientation="Horizontal" Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16">
<StackPanel Spacing="16">
<ProgressBar Height="200" IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="1.0"
Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}" ProgressTextFormat="{Binding #stringFormat.Text}"/>
</StackPanel>
</StackPanel>
</StackPanel>
</StackPanel>
</Window> </Window>

5
samples/Sandbox/Sandbox.csproj

@ -2,9 +2,8 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> <IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators>
<IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators>
<!-- <AvaloniaXamlIlDebuggerLaunch>true</AvaloniaXamlIlDebuggerLaunch>--> <!-- <AvaloniaXamlIlDebuggerLaunch>true</AvaloniaXamlIlDebuggerLaunch>-->
</PropertyGroup> </PropertyGroup>

3
src/Avalonia.Base/Animation/Animatable.cs

@ -32,8 +32,7 @@ namespace Avalonia.Animation
private Dictionary<ITransition, TransitionState>? _transitionState; private Dictionary<ITransition, TransitionState>? _transitionState;
private NotifyCollectionChangedEventHandler? _collectionChanged; private NotifyCollectionChangedEventHandler? _collectionChanged;
protected bool _animationsEnabled = true; internal LightweightSubject<bool>? animationsGateSubject;
internal LightweightSubject<bool>? animationsStateSubject;
private NotifyCollectionChangedEventHandler TransitionsCollectionChangedHandler => private NotifyCollectionChangedEventHandler TransitionsCollectionChangedHandler =>
_collectionChanged ??= TransitionsCollectionChanged; _collectionChanged ??= TransitionsCollectionChanged;

2
src/Avalonia.Base/Animation/Animation.cs

@ -277,7 +277,7 @@ namespace Avalonia.Animation
var (animators, subscriptions) = InterpretKeyframes(control); var (animators, subscriptions) = InterpretKeyframes(control);
var overrideSubject = new LightweightSubject<bool>(); var overrideSubject = new LightweightSubject<bool>();
control.animationsStateSubject = overrideSubject; control.animationsGateSubject = overrideSubject;
if (animators.Count == 1) if (animators.Count == 1)
{ {

7
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, public virtual IDisposable? Apply(Animation animation, Animatable target, IClock? clock,
IObservable<bool> match, IObservable<bool> state, Action? onComplete) IObservable<bool> match, IObservable<bool> state, Action? onComplete)
{ {
var subject = new DisposeAnimationInstanceSubject<T>(this, animation, target, clock, state, onComplete); var disposable = new CompositeDisposable();
return new CompositeDisposable(match.Subscribe(subject), subject); var subject = new DisposeAnimationInstanceSubject<T>(this, animation, target, clock, onComplete, state, disposable);
disposable.Add(match.Subscribe(subject));
disposable.Add(subject);
return disposable;
} }
protected T InterpolationHandler(double animationTime, T neutralValue) protected T InterpolationHandler(double animationTime, T neutralValue)

69
src/Avalonia.Base/Animation/DisposeAnimationInstanceSubject.cs

@ -1,5 +1,7 @@
using System; using System;
using System.Threading;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using Avalonia.Reactive;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
@ -9,29 +11,32 @@ namespace Avalonia.Animation
internal class DisposeAnimationInstanceSubject<T> : IObserver<bool>, IDisposable internal class DisposeAnimationInstanceSubject<T> : IObserver<bool>, IDisposable
{ {
private IDisposable? _lastInstance; private IDisposable? _lastInstance;
private bool _lastMatch; private bool _lastMatch, _lastGate;
private readonly Animator<T> _animator; private readonly Animator<T> _animator;
private readonly Animation _animation; private readonly Animation _animation;
private readonly Animatable _control; private readonly Animatable _control;
private readonly Action? _onComplete; private readonly Action? _onComplete;
private readonly IClock? _clock; private readonly IClock? _clock;
private readonly object _lock = new object();
public DisposeAnimationInstanceSubject(Animator<T> animator, public DisposeAnimationInstanceSubject(Animator<T> animator,
Animation animation, Animatable control, IClock? clock, IObservable<bool> state, Action? onComplete) Animation animation, Animatable control, IClock? clock, Action? onComplete, IObservable<bool> state, CompositeDisposable disposable)
{ {
this._animator = animator; this._animator = animator;
this._animation = animation; this._animation = animation;
this._control = control; this._control = control;
this._onComplete = onComplete; this._onComplete = onComplete;
this._clock = clock; this._clock = clock;
// TODO: this causes weird bugs. disposable.Add(state.Subscribe(AnimationStateChange));
state.Subscribe(this);
} }
public void Dispose() public void Dispose()
{ {
_lastInstance?.Dispose(); lock (_lock)
{
_lastInstance?.Dispose();
}
} }
public void OnCompleted() public void OnCompleted()
@ -40,27 +45,49 @@ namespace Avalonia.Animation
public void OnError(Exception error) public void OnError(Exception error)
{ {
_lastInstance?.Dispose(); lock (_lock)
_lastInstance = null; {
_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) public void OnNext(bool matchVal)
{ {
_lastInstance = _animator.Run(_animation, _control, _clock, _onComplete); lock (_lock)
} {
else if (Volatile.Read(ref _lastMatch) == matchVal) return;
{ Volatile.Write(ref _lastMatch, matchVal);
_lastInstance = null; 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;
} }
} }
} }
} }

95
src/Avalonia.Base/Visual.cs

@ -104,6 +104,14 @@ namespace Avalonia
/// </summary> /// </summary>
public static readonly DirectProperty<Visual, Visual?> VisualParentProperty = public static readonly DirectProperty<Visual, Visual?> VisualParentProperty =
AvaloniaProperty.RegisterDirect<Visual, Visual?>(nameof(VisualParent), o => o._visualParent); AvaloniaProperty.RegisterDirect<Visual, Visual?>(nameof(VisualParent), o => o._visualParent);
/// <summary>
/// Defines the <see cref="IsEffectivelyVisible"/> property.
/// </summary>
public static readonly DirectProperty<Visual, bool> IsEffectivelyVisibleProperty =
AvaloniaProperty.RegisterDirect<Visual, bool>(
nameof(IsEffectivelyVisible),
o => o._isEffectivelyVisible);
/// <summary> /// <summary>
/// Defines the <see cref="ZIndex"/> property. /// Defines the <see cref="ZIndex"/> property.
@ -121,7 +129,9 @@ namespace Avalonia
private Visual? _visualParent; private Visual? _visualParent;
private bool _hasMirrorTransform; private bool _hasMirrorTransform;
private TargetWeakEventSubscriber<Visual, EventArgs>? _affectsRenderWeakSubscriber; private TargetWeakEventSubscriber<Visual, EventArgs>? _affectsRenderWeakSubscriber;
private bool _isEffectivelyVisible = true;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Visual"/> class. /// Initializes static members of the <see cref="Visual"/> class.
/// </summary> /// </summary>
@ -143,6 +153,7 @@ namespace Avalonia
/// </summary> /// </summary>
public Visual() public Visual()
{ {
SetAnimationGateState(false);
// Disable transitions until we're added to the visual tree. // Disable transitions until we're added to the visual tree.
DisableTransitions(); DisableTransitions();
@ -193,25 +204,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets a value indicating whether this control and all its parents are visible. /// Gets a value indicating whether this control and all its parents are visible.
/// </summary> /// </summary>
public bool IsEffectivelyVisible public bool IsEffectivelyVisible { get; }
{
get
{
Visual? node = this;
while (node != null)
{
if (!node.IsVisible)
{
return false;
}
node = node.VisualParent;
}
return true;
}
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this control is visible. /// Gets or sets a value indicating whether this control is visible.
@ -496,7 +489,7 @@ namespace Avalonia
mutableTransform.Changed += RenderTransformChanged; mutableTransform.Changed += RenderTransformChanged;
} }
EnableAnimations(); SetAnimationGateState(true);
EnableTransitions(); EnableTransitions();
if (_visualRoot.Renderer is IRendererWithCompositor compositingRenderer) if (_visualRoot.Renderer is IRendererWithCompositor compositingRenderer)
@ -540,7 +533,7 @@ namespace Avalonia
mutableTransform.Changed -= RenderTransformChanged; mutableTransform.Changed -= RenderTransformChanged;
} }
DisableAnimations(); SetAnimationGateState(false);
DisableTransitions(); DisableTransitions();
OnDetachedFromVisualTree(e); OnDetachedFromVisualTree(e);
DetachFromCompositor(); DetachFromCompositor();
@ -668,53 +661,33 @@ namespace Avalonia
/// Called when the <see cref="IsVisible"/> property changes on any control. /// Called when the <see cref="IsVisible"/> property changes on any control.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private static void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e) private void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
{ {
if (e.Sender is not Visual visual) return; if (e.NewValue is bool newVisibility)
if (visual.IsEffectivelyVisible)
{ {
visual.EnableAnimations(); CalculateEffectiveVisibility(newVisibility);
} }
else
{
visual.DisableAnimations();
}
} }
internal void EnableAnimations() private void SetAnimationGateState(bool gateState)
{ {
if (!_animationsEnabled) if (animationsGateSubject is { } animationsGate)
{ {
_animationsEnabled = true; animationsGate.OnNext(gateState);
}
if (animationsStateSubject is { } ds)
{
ds.OnNext(true);
}
foreach (var child in this.GetVisualDescendants())
{
child.EnableAnimations();
}
}
} }
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; child.CalculateEffectiveVisibility(newVisibility);
if (animationsStateSubject is { } ds)
{
ds.OnNext(false);
}
foreach (var child in this.GetVisualDescendants())
{
child.DisableAnimations();
}
} }
} }

Loading…
Cancel
Save