Browse Source

Merge branch 'master' into fixes/5988-showactivated-extendclientarea

pull/5989/head
Dan Walmsley 5 years ago
committed by GitHub
parent
commit
5839d31b82
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      src/Avalonia.Animation/AnimatorDrivenTransition.cs
  2. 32
      src/Avalonia.Animation/AnimatorTransitionObservable.cs
  3. 9
      src/Avalonia.Animation/Clock.cs
  4. 12
      src/Avalonia.Animation/ClockBase.cs
  5. 6
      src/Avalonia.Animation/Transition.cs
  6. 60
      src/Avalonia.Animation/TransitionInstance.cs
  7. 58
      src/Avalonia.Animation/TransitionObservableBase.cs
  8. 13
      src/Avalonia.Animation/Transitions/DoubleTransition.cs
  9. 13
      src/Avalonia.Animation/Transitions/FloatTransition.cs
  10. 13
      src/Avalonia.Animation/Transitions/IntegerTransition.cs
  11. 2
      src/Avalonia.Base/Data/Core/StreamNode.cs
  12. 97
      src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml
  13. 52
      src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs
  14. 33
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  15. 25
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs
  16. 124
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs
  17. 96
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  18. 36
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  19. 9
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
  20. 1
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
  21. 7
      src/Avalonia.Themes.Default/TextBox.xaml
  22. 13
      src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs
  23. 12
      src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs
  24. 13
      src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
  25. 13
      src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
  26. 13
      src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
  27. 13
      src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
  28. 18
      src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs
  29. 13
      src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
  30. 78
      tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs

20
src/Avalonia.Animation/AnimatorDrivenTransition.cs

@ -0,0 +1,20 @@
using System;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
/// <see cref="Transition{T}"/> using an <see cref="Animator{T}"/> to transition between values.
/// </summary>
/// <typeparam name="T">Type of the transitioned value.</typeparam>
/// <typeparam name="TAnimator">Type of the animator.</typeparam>
public abstract class AnimatorDrivenTransition<T, TAnimator> : Transition<T> where TAnimator : Animator<T>, new()
{
private static readonly TAnimator s_animator = new TAnimator();
public override IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue)
{
return new AnimatorTransitionObservable<T, TAnimator>(s_animator, progress, Easing, oldValue, newValue);
}
}
}

32
src/Avalonia.Animation/AnimatorTransitionObservable.cs

@ -0,0 +1,32 @@
using System;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
namespace Avalonia.Animation
{
/// <summary>
/// Transition observable based on an <see cref="Animator{T}"/> producing a value.
/// </summary>
/// <typeparam name="T">Type of the transitioned value.</typeparam>
/// <typeparam name="TAnimator">Type of the animator.</typeparam>
public class AnimatorTransitionObservable<T, TAnimator> : TransitionObservableBase<T> where TAnimator : Animator<T>
{
private readonly TAnimator _animator;
private readonly Easing _easing;
private readonly T _oldValue;
private readonly T _newValue;
public AnimatorTransitionObservable(TAnimator animator, IObservable<double> progress, Easing easing, T oldValue, T newValue) : base(progress, easing)
{
_animator = animator;
_easing = easing;
_oldValue = oldValue;
_newValue = newValue;
}
protected override T ProduceValue(double progress)
{
return _animator.Interpolate(progress, _oldValue, _newValue);
}
}
}

9
src/Avalonia.Animation/Clock.cs

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Text;
using Avalonia.Reactive;
namespace Avalonia.Animation
{
@ -10,10 +6,9 @@ namespace Avalonia.Animation
{
public static IClock GlobalClock => AvaloniaLocator.Current.GetService<IGlobalClock>();
private IDisposable _parentSubscription;
private readonly IDisposable _parentSubscription;
public Clock()
:this(GlobalClock)
public Clock() : this(GlobalClock)
{
}

12
src/Avalonia.Animation/ClockBase.cs

@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Text;
using Avalonia.Reactive;
namespace Avalonia.Animation
{
public class ClockBase : IClock
{
private ClockObservable _observable;
private IObservable<TimeSpan> _connectedObservable;
private readonly ClockObservable _observable;
private TimeSpan? _previousTime;
private TimeSpan _internalTime;
@ -18,7 +13,6 @@ namespace Avalonia.Animation
protected ClockBase()
{
_observable = new ClockObservable();
_connectedObservable = _observable.Publish().RefCount();
}
protected bool HasSubscriptions => _observable.HasSubscriptions;
@ -58,10 +52,10 @@ namespace Avalonia.Animation
public IDisposable Subscribe(IObserver<TimeSpan> observer)
{
return _connectedObservable.Subscribe(observer);
return _observable.Subscribe(observer);
}
private class ClockObservable : LightweightObservableBase<TimeSpan>
private sealed class ClockObservable : LightweightObservableBase<TimeSpan>
{
public bool HasSubscriptions { get; private set; }
public void Pulse(TimeSpan time) => PublishNext(time);

6
src/Avalonia.Animation/Transition`1.cs → src/Avalonia.Animation/Transition.cs

@ -1,7 +1,5 @@
using System;
using System.Reactive.Linq;
using System;
using Avalonia.Animation.Easings;
using Avalonia.Animation.Utils;
namespace Avalonia.Animation
{
@ -56,4 +54,4 @@ namespace Avalonia.Animation
return control.Bind<T>((AvaloniaProperty<T>)Property, transition, Data.BindingPriority.Animation);
}
}
}
}

60
src/Avalonia.Animation/TransitionInstance.cs

@ -1,8 +1,5 @@
using Avalonia.Metadata;
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Easings;
using Avalonia.Animation.Utils;
using System.Runtime.ExceptionServices;
using Avalonia.Reactive;
using Avalonia.Utilities;
@ -11,13 +8,13 @@ namespace Avalonia.Animation
/// <summary>
/// Handles the timing and lifetime of a <see cref="Transition{T}"/>.
/// </summary>
internal class TransitionInstance : SingleSubscriberObservableBase<double>
internal class TransitionInstance : SingleSubscriberObservableBase<double>, IObserver<TimeSpan>
{
private IDisposable _timerSubscription;
private TimeSpan _delay;
private TimeSpan _duration;
private readonly IClock _baseClock;
private IClock _clock;
private TransitionClock _clock;
public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration)
{
@ -75,9 +72,56 @@ namespace Avalonia.Animation
protected override void Subscribed()
{
_clock = new Clock(_baseClock);
_timerSubscription = _clock.Subscribe(TimerTick);
_clock = new TransitionClock(_baseClock);
_timerSubscription = _clock.Subscribe(this);
PublishNext(0.0d);
}
void IObserver<TimeSpan>.OnCompleted()
{
PublishCompleted();
}
void IObserver<TimeSpan>.OnError(Exception error)
{
PublishError(error);
}
void IObserver<TimeSpan>.OnNext(TimeSpan value)
{
TimerTick(value);
}
/// <summary>
/// TODO: This clock is still fairly expensive due to <see cref="ClockBase"/> implementation.
/// </summary>
private sealed class TransitionClock : ClockBase, IObserver<TimeSpan>
{
private readonly IDisposable _parentSubscription;
public TransitionClock(IClock parent)
{
_parentSubscription = parent.Subscribe(this);
}
protected override void Stop()
{
_parentSubscription.Dispose();
}
void IObserver<TimeSpan>.OnNext(TimeSpan value)
{
Pulse(value);
}
void IObserver<TimeSpan>.OnCompleted()
{
}
void IObserver<TimeSpan>.OnError(Exception error)
{
ExceptionDispatchInfo.Capture(error).Throw();
}
}
}
}

58
src/Avalonia.Animation/TransitionObservableBase.cs

@ -0,0 +1,58 @@
using System;
using Avalonia.Animation.Easings;
using Avalonia.Reactive;
#nullable enable
namespace Avalonia.Animation
{
/// <summary>
/// Provides base for observables implementing transitions.
/// </summary>
/// <typeparam name="T">Type of the transitioned value.</typeparam>
public abstract class TransitionObservableBase<T> : SingleSubscriberObservableBase<T>, IObserver<double>
{
private readonly Easing _easing;
private readonly IObservable<double> _progress;
private IDisposable? _progressSubscription;
protected TransitionObservableBase(IObservable<double> progress, Easing easing)
{
_progress = progress;
_easing = easing;
}
/// <summary>
/// Produces value at given progress time point.
/// </summary>
/// <param name="progress">Transition progress.</param>
protected abstract T ProduceValue(double progress);
protected override void Subscribed()
{
_progressSubscription = _progress.Subscribe(this);
}
protected override void Unsubscribed()
{
_progressSubscription?.Dispose();
}
void IObserver<double>.OnCompleted()
{
PublishCompleted();
}
void IObserver<double>.OnError(Exception error)
{
PublishError(error);
}
void IObserver<double>.OnNext(double value)
{
double progress = _easing.Ease(value);
PublishNext(ProduceValue(progress));
}
}
}

13
src/Avalonia.Animation/Transitions/DoubleTransition.cs

@ -1,6 +1,3 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
@ -8,15 +5,7 @@ namespace Avalonia.Animation
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="double"/> types.
/// </summary>
public class DoubleTransition : Transition<double>
public class DoubleTransition : AnimatorDrivenTransition<double, DoubleAnimator>
{
private static readonly DoubleAnimator s_animator = new DoubleAnimator();
/// <inheritdocs/>
public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

13
src/Avalonia.Animation/Transitions/FloatTransition.cs

@ -1,6 +1,3 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
@ -8,15 +5,7 @@ namespace Avalonia.Animation
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="float"/> types.
/// </summary>
public class FloatTransition : Transition<float>
public class FloatTransition : AnimatorDrivenTransition<float, FloatAnimator>
{
private static readonly FloatAnimator s_animator = new FloatAnimator();
/// <inheritdocs/>
public override IObservable<float> DoTransition(IObservable<double> progress, float oldValue, float newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

13
src/Avalonia.Animation/Transitions/IntegerTransition.cs

@ -1,6 +1,3 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
@ -8,15 +5,7 @@ namespace Avalonia.Animation
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="int"/> types.
/// </summary>
public class IntegerTransition : Transition<int>
public class IntegerTransition : AnimatorDrivenTransition<int, Int32Animator>
{
private static readonly Int32Animator s_animator = new Int32Animator();
/// <inheritdocs/>
public override IObservable<int> DoTransition(IObservable<double> progress, int oldValue, int newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

2
src/Avalonia.Base/Data/Core/StreamNode.cs

@ -20,7 +20,7 @@ namespace Avalonia.Data.Core
protected override void StartListeningCore(WeakReference<object> reference)
{
GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged);
_subscription = GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged);
}
protected override void StopListeningCore()

97
src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml

@ -0,0 +1,97 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Diagnostics.Controls"
x:CompileBindings="True"
x:DataType="controls:FilterTextBox">
<Design.PreviewWith>
<Border Padding="10">
<controls:FilterTextBox Width="200" Text="Hello World" />
</Border>
</Design.PreviewWith>
<Style Selector="TextBox.filter-text-box">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="InnerRightContent">
<Template>
<StackPanel Orientation="Horizontal" Spacing="1">
<Button Margin="0,0,2,0"
Classes="textBoxClearButton"
ToolTip.Tip="Clear"
Cursor="Hand"
Command="{ReflectionBinding $parent[TextBox].Clear}"
Opacity="0.5" />
<ToggleButton Classes="filter-text-box-toggle"
ToolTip.Tip="Match Case"
IsChecked="{Binding $parent[controls:FilterTextBox].UseCaseSensitiveFilter}">
<Image>
<DrawingImage>
<GeometryDrawing Brush="{Binding $parent[ToggleButton].Foreground}">
M7.495 9.052L8.386 11.402H9.477L6.237 3H5.217L2 11.402H3.095L3.933 9.052H7.495ZM5.811 4.453L5.855 4.588L7.173 8.162H4.255L5.562 4.588L5.606 4.453L5.644 4.297L5.676 4.145L5.697 4.019H5.72L5.744 4.145L5.773 4.297L5.811 4.453ZM13.795 10.464V11.4H14.755V7.498C14.755 6.779 14.575 6.226 14.216 5.837C13.857 5.448 13.327 5.254 12.628 5.254C12.429 5.254 12.227 5.273 12.022 5.31C11.817 5.347 11.622 5.394 11.439 5.451C11.256 5.508 11.091 5.569 10.944 5.636C10.797 5.703 10.683 5.765 10.601 5.824V6.808C10.867 6.578 11.167 6.397 11.505 6.268C11.843 6.139 12.194 6.075 12.557 6.075C12.745 6.075 12.915 6.103 13.07 6.16C13.225 6.217 13.357 6.306 13.466 6.427C13.575 6.548 13.659 6.706 13.718 6.899C13.777 7.092 13.806 7.326 13.806 7.599L11.995 7.851C11.651 7.898 11.355 7.977 11.107 8.088C10.859 8.199 10.654 8.339 10.492 8.507C10.33 8.675 10.21 8.868 10.132 9.087C10.054 9.306 10.015 9.546 10.015 9.808C10.015 10.054 10.057 10.283 10.139 10.496C10.221 10.709 10.342 10.893 10.502 11.047C10.662 11.201 10.862 11.323 11.1 11.413C11.338 11.503 11.613 11.548 11.926 11.548C12.328 11.548 12.686 11.456 13.001 11.27C13.316 11.084 13.573 10.816 13.772 10.464H13.795ZM11.667 8.721C11.843 8.657 12.068 8.607 12.341 8.572L13.806 8.367V8.976C13.806 9.222 13.765 9.451 13.683 9.664C13.601 9.877 13.486 10.063 13.34 10.221C13.194 10.379 13.019 10.503 12.816 10.593C12.613 10.683 12.39 10.728 12.148 10.728C11.961 10.728 11.795 10.703 11.653 10.652C11.511 10.601 11.392 10.53 11.296 10.441C11.2 10.352 11.127 10.247 11.076 10.125C11.025 10.003 11 9.873 11 9.732C11 9.568 11.018 9.421 11.055 9.292C11.092 9.163 11.16 9.051 11.257 8.958C11.354 8.865 11.491 8.785 11.667 8.721Z
</GeometryDrawing>
</DrawingImage>
</Image>
</ToggleButton>
<ToggleButton Classes="filter-text-box-toggle"
ToolTip.Tip="Match Whole Word"
IsChecked="{Binding $parent[controls:FilterTextBox].UseWholeWordFilter}">
<Image>
<DrawingImage>
<GeometryDrawing Brush="{Binding $parent[ToggleButton].Foreground}">
M1 2H15V3H1V2ZM14 4H13V12H14V4ZM11.272 8.387C11.194 8.088 11.073 7.825 10.912 7.601C10.751 7.377 10.547 7.2 10.303 7.071C10.059 6.942 9.769 6.878 9.437 6.878C9.239 6.878 9.057 6.902 8.89 6.951C8.725 7 8.574 7.068 8.437 7.156C8.301 7.244 8.18 7.35 8.072 7.474L7.893 7.732V4.578H7V12H7.893V11.425L8.019 11.6C8.106 11.702 8.208 11.79 8.323 11.869C8.44 11.947 8.572 12.009 8.721 12.055C8.87 12.101 9.035 12.123 9.219 12.123C9.572 12.123 9.885 12.052 10.156 11.911C10.428 11.768 10.655 11.573 10.838 11.325C11.021 11.075 11.159 10.782 11.252 10.446C11.345 10.108 11.392 9.743 11.392 9.349C11.391 9.007 11.352 8.686 11.272 8.387ZM9.793 7.78C9.944 7.851 10.075 7.956 10.183 8.094C10.292 8.234 10.377 8.407 10.438 8.611C10.489 8.785 10.52 8.982 10.527 9.198L10.52 9.323C10.52 9.65 10.487 9.943 10.42 10.192C10.353 10.438 10.259 10.645 10.142 10.806C10.025 10.968 9.882 11.091 9.721 11.172C9.399 11.334 8.961 11.338 8.652 11.187C8.499 11.112 8.366 11.012 8.259 10.891C8.174 10.795 8.103 10.675 8.041 10.524C8.041 10.524 7.862 10.077 7.862 9.577C7.862 9.077 8.041 8.575 8.041 8.575C8.103 8.398 8.177 8.257 8.265 8.145C8.379 8.002 8.521 7.886 8.689 7.8C8.857 7.714 9.054 7.671 9.276 7.671C9.466 7.671 9.64 7.708 9.793 7.78ZM15 13H1V14H15V13ZM2.813 10L2.085 12.031H1L1.025 11.959L3.466 4.87305H4.407L6.892 12.031H5.81L5.032 10H2.813ZM3.934 6.42205H3.912L3.007 9.17505H4.848L3.934 6.42205Z
</GeometryDrawing>
</DrawingImage>
</Image>
</ToggleButton>
<ToggleButton Classes="filter-text-box-toggle"
ToolTip.Tip="Use Regular Expression"
IsChecked="{Binding $parent[controls:FilterTextBox].UseRegexFilter}">
<Image>
<DrawingImage>
<GeometryDrawing Brush="{Binding $parent[ToggleButton].Foreground}">
M10.0122 2H10.9879V5.11346L13.5489 3.55609L14.034 4.44095L11.4702 6L14.034 7.55905L13.5489 8.44391L10.9879 6.88654V10H10.0122V6.88654L7.45114 8.44391L6.96606 7.55905L9.5299 6L6.96606 4.44095L7.45114 3.55609L10.0122 5.11346V2ZM2 10H6V14H2V10Z
</GeometryDrawing>
</DrawingImage>
</Image>
</ToggleButton>
</StackPanel>
</Template>
</Setter>
</Style>
<Style Selector="TextBox.filter-text-box Button.textBoxClearButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="TextBox.filter-text-box[AcceptsReturn=False][IsReadOnly=False]:focus:not(TextBox:empty) Button.textBoxClearButton">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="ToggleButton.filter-text-box-toggle > Image">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Setter Property="Opacity" Value="0.5" />
</Style>
<Style Selector="ToggleButton.filter-text-box-toggle:pointerover > Image">
<Setter Property="Opacity" Value="0.7" />
</Style>
<Style Selector="ToggleButton.filter-text-box-toggle:pressed > Image, ToggleButton.filter-text-box-toggle:checked > Image">
<Setter Property="Opacity" Value="0.7" />
</Style>
<Style Selector="ToggleButton.filter-text-box-toggle">
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Padding" Value="0,1" />
<Setter Property="Width" Value="24" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="VerticalAlignment" Value="Top" />
</Style>
<Style Selector="ToggleButton.filter-text-box-toggle /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="CornerRadius" Value="0" />
</Style>
<Style Selector="ToggleButton.filter-text-box-toggle:pressed /template/ ContentPresenter, ToggleButton.filter-text-box-toggle:checked /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeAccentColor}" />
<Setter Property="Background" Value="{DynamicResource ThemeAccentColor4}" />
</Style>
</Styles>

52
src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs

@ -0,0 +1,52 @@
using System;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Styling;
namespace Avalonia.Diagnostics.Controls
{
internal class FilterTextBox : TextBox, IStyleable
{
public static readonly DirectProperty<FilterTextBox, bool> UseRegexFilterProperty =
AvaloniaProperty.RegisterDirect<FilterTextBox, bool>(nameof(UseRegexFilter),
o => o.UseRegexFilter, (o, v) => o.UseRegexFilter = v,
defaultBindingMode: BindingMode.TwoWay);
public static readonly DirectProperty<FilterTextBox, bool> UseCaseSensitiveFilterProperty =
AvaloniaProperty.RegisterDirect<FilterTextBox, bool>(nameof(UseCaseSensitiveFilter),
o => o.UseCaseSensitiveFilter, (o, v) => o.UseCaseSensitiveFilter = v,
defaultBindingMode: BindingMode.TwoWay);
public static readonly DirectProperty<FilterTextBox, bool> UseWholeWordFilterProperty =
AvaloniaProperty.RegisterDirect<FilterTextBox, bool>(nameof(UseWholeWordFilter),
o => o.UseWholeWordFilter, (o, v) => o.UseWholeWordFilter = v,
defaultBindingMode: BindingMode.TwoWay);
private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter;
public FilterTextBox()
{
Classes.Add("filter-text-box");
}
public bool UseRegexFilter
{
get => _useRegexFilter;
set => SetAndRaise(UseRegexFilterProperty, ref _useRegexFilter, value);
}
public bool UseCaseSensitiveFilter
{
get => _useCaseSensitiveFilter;
set => SetAndRaise(UseCaseSensitiveFilterProperty, ref _useCaseSensitiveFilter, value);
}
public bool UseWholeWordFilter
{
get => _useWholeWordFilter;
set => SetAndRaise(UseWholeWordFilterProperty, ref _useWholeWordFilter, value);
}
Type IStyleable.StyleKey => typeof(TextBox);
}
}

33
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -19,7 +19,6 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly IVisual _control;
private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex;
private AvaloniaPropertyViewModel _selectedProperty;
private string _styleFilter;
private bool _snapshotStyles;
private bool _showInactiveStyles;
private string _styleStatus;
@ -144,12 +143,6 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _selectedProperty, value);
}
public string StyleFilter
{
get => _styleFilter;
set => RaiseAndSetIfChanged(ref _styleFilter, value);
}
public bool SnapshotStyles
{
get => _snapshotStyles;
@ -174,11 +167,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
base.OnPropertyChanged(e);
if (e.PropertyName == nameof(StyleFilter))
{
UpdateStyleFilters();
}
else if (e.PropertyName == nameof(SnapshotStyles))
if (e.PropertyName == nameof(SnapshotStyles))
{
if (!SnapshotStyles)
{
@ -187,19 +176,15 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
private void UpdateStyleFilters()
public void UpdateStyleFilters()
{
var filter = StyleFilter;
bool hasFilter = !string.IsNullOrEmpty(filter);
foreach (var style in AppliedStyles)
{
var hasVisibleSetter = false;
foreach (var setter in style.Setters)
{
setter.IsVisible =
!hasFilter || setter.Name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
setter.IsVisible = TreePage.SettersFilter.Filter(setter.Name);
hasVisibleSetter |= setter.IsVisible;
}
@ -357,17 +342,7 @@ namespace Avalonia.Diagnostics.ViewModels
private bool FilterProperty(object arg)
{
if (!string.IsNullOrWhiteSpace(TreePage.PropertyFilter) && arg is PropertyViewModel property)
{
if (TreePage.UseRegexFilter)
{
return TreePage.FilterRegex?.IsMatch(property.Name) ?? true;
}
return property.Name.IndexOf(TreePage.PropertyFilter, StringComparison.OrdinalIgnoreCase) != -1;
}
return true;
return !(arg is PropertyViewModel property) || TreePage.PropertiesFilter.Filter(property.Name);
}
private class PropertyComparer : IComparer<PropertyViewModel>

25
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs

@ -23,7 +23,6 @@ namespace Avalonia.Diagnostics.ViewModels
};
private readonly MainViewModel _mainViewModel;
private string _eventTypeFilter;
private FiredEvent _selectedEvent;
private EventTreeNodeBase _selectedNode;
@ -37,6 +36,9 @@ namespace Avalonia.Diagnostics.ViewModels
.Select(g => new EventOwnerTreeNode(g.Key, g, this))
.ToArray();
EventsFilter = new FilterViewModel();
EventsFilter.RefreshFilter += (s, e) => UpdateEventFilters();
EnableDefault();
}
@ -58,11 +60,7 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _selectedNode, value);
}
public string EventTypeFilter
{
get => _eventTypeFilter;
set => RaiseAndSetIfChanged(ref _eventTypeFilter, value);
}
public FilterViewModel EventsFilter { get; }
public void Clear()
{
@ -125,16 +123,6 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == nameof(EventTypeFilter))
{
UpdateEventFilters();
}
}
private void EvaluateNodeEnabled(Func<EventTreeNode, bool> eval)
{
void ProcessNode(EventTreeNodeBase node)
@ -161,9 +149,6 @@ namespace Avalonia.Diagnostics.ViewModels
private void UpdateEventFilters()
{
var filter = EventTypeFilter;
bool hasFilter = !string.IsNullOrEmpty(filter);
foreach (var node in Nodes)
{
FilterNode(node, false);
@ -171,7 +156,7 @@ namespace Avalonia.Diagnostics.ViewModels
bool FilterNode(EventTreeNodeBase node, bool isParentVisible)
{
bool matchesFilter = !hasFilter || node.Text.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
bool matchesFilter = EventsFilter.Filter(node.Text);
bool hasVisibleChild = false;
if (node.Children != null)

124
src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs

@ -0,0 +1,124 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.RegularExpressions;
namespace Avalonia.Diagnostics.ViewModels
{
internal class FilterViewModel : ViewModelBase, INotifyDataErrorInfo
{
private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();
private string _filterString = string.Empty;
private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter;
private string _processedFilter;
private Regex _filterRegex;
public event EventHandler RefreshFilter;
public bool Filter(string input)
{
return _filterRegex?.IsMatch(input) ?? true;
}
private void UpdateFilterRegex()
{
void ClearError()
{
if (_errors.Remove(nameof(FilterString)))
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(FilterString)));
}
}
_processedFilter = FilterString.Trim();
try
{
var options = RegexOptions.Compiled;
var pattern = UseRegexFilter
? _processedFilter : Regex.Escape(_processedFilter);
if (!UseCaseSensitiveFilter)
{
options |= RegexOptions.IgnoreCase;
}
if (UseWholeWordFilter)
{
pattern = $"\\b(?:{pattern})\\b";
}
_filterRegex = new Regex(pattern, options);
ClearError();
}
catch (Exception exception)
{
_errors[nameof(FilterString)] = exception.Message;
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(FilterString)));
}
}
public string FilterString
{
get => _filterString;
set
{
if (RaiseAndSetIfChanged(ref _filterString, value))
{
UpdateFilterRegex();
RefreshFilter?.Invoke(this, EventArgs.Empty);
}
}
}
public bool UseRegexFilter
{
get => _useRegexFilter;
set
{
if (RaiseAndSetIfChanged(ref _useRegexFilter, value))
{
UpdateFilterRegex();
RefreshFilter?.Invoke(this, EventArgs.Empty);
}
}
}
public bool UseCaseSensitiveFilter
{
get => _useCaseSensitiveFilter;
set
{
if (RaiseAndSetIfChanged(ref _useCaseSensitiveFilter, value))
{
UpdateFilterRegex();
RefreshFilter?.Invoke(this, EventArgs.Empty);
}
}
}
public bool UseWholeWordFilter
{
get => _useWholeWordFilter;
set
{
if (RaiseAndSetIfChanged(ref _useWholeWordFilter, value))
{
UpdateFilterRegex();
RefreshFilter?.Invoke(this, EventArgs.Empty);
}
}
}
public IEnumerable GetErrors(string propertyName)
{
if (_errors.TryGetValue(propertyName, out var error))
{
yield return error;
}
}
public bool HasErrors => _errors.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
}

96
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -1,29 +1,32 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.RegularExpressions;
using Avalonia.Controls;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{
internal class TreePageViewModel : ViewModelBase, IDisposable, INotifyDataErrorInfo
internal class TreePageViewModel : ViewModelBase, IDisposable
{
private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();
private TreeNode _selectedNode;
private ControlDetailsViewModel _details;
private string _propertyFilter = string.Empty;
private bool _useRegexFilter;
public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes)
{
MainView = mainView;
Nodes = nodes;
PropertiesFilter = new FilterViewModel();
PropertiesFilter.RefreshFilter += (s, e) => Details?.PropertiesView.Refresh();
SettersFilter = new FilterViewModel();
SettersFilter.RefreshFilter += (s, e) => Details?.UpdateStyleFilters();
}
public MainViewModel MainView { get; }
public FilterViewModel PropertiesFilter { get; }
public FilterViewModel SettersFilter { get; }
public TreeNode[] Nodes { get; protected set; }
public TreeNode SelectedNode
@ -31,18 +34,12 @@ namespace Avalonia.Diagnostics.ViewModels
get => _selectedNode;
private set
{
var oldDetails = Details;
if (RaiseAndSetIfChanged(ref _selectedNode, value))
{
Details = value != null ?
new ControlDetailsViewModel(this, value.Visual) :
null;
if (Details != null && oldDetails != null)
{
Details.StyleFilter = oldDetails.StyleFilter;
}
Details?.UpdateStyleFilters();
}
}
}
@ -61,63 +58,6 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public Regex FilterRegex { get; set; }
private void UpdateFilterRegex()
{
void ClearError()
{
if (_errors.Remove(nameof(PropertyFilter)))
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter)));
}
}
if (UseRegexFilter)
{
try
{
FilterRegex = new Regex(PropertyFilter, RegexOptions.Compiled);
ClearError();
}
catch (Exception exception)
{
_errors[nameof(PropertyFilter)] = exception.Message;
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter)));
}
}
else
{
ClearError();
}
}
public string PropertyFilter
{
get => _propertyFilter;
set
{
if (RaiseAndSetIfChanged(ref _propertyFilter, value))
{
UpdateFilterRegex();
Details.PropertiesView.Refresh();
}
}
}
public bool UseRegexFilter
{
get => _useRegexFilter;
set
{
if (RaiseAndSetIfChanged(ref _useRegexFilter, value))
{
UpdateFilterRegex();
Details.PropertiesView.Refresh();
}
}
}
public void Dispose()
{
foreach (var node in Nodes)
@ -194,17 +134,5 @@ namespace Avalonia.Diagnostics.ViewModels
return null;
}
public IEnumerable GetErrors(string propertyName)
{
if (_errors.TryGetValue(propertyName, out var error))
{
yield return error;
}
}
public bool HasErrors => _errors.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
}

36
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters"
xmlns:local="clr-namespace:Avalonia.Diagnostics.Views"
xmlns:controls="clr-namespace:Avalonia.Diagnostics.Controls"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
x:Class="Avalonia.Diagnostics.Views.ControlDetailsView"
x:Name="Main">
@ -12,24 +13,19 @@
<Grid ColumnDefinitions="*,Auto,320">
<Grid Grid.Column="0" ColumnDefinitions="*,Auto,Auto,Auto" RowDefinitions="Auto,*">
<TextBox Grid.Row="0"
Grid.Column="0"
BorderThickness="0"
Text="{Binding TreePage.PropertyFilter}"
Watermark="Filter properties" />
<CheckBox Grid.Row="0"
Grid.Column="1"
Margin="0,0,4,0"
Content="Regex"
IsChecked="{Binding TreePage.UseRegexFilter}" />
<Grid Grid.Column="0" RowDefinitions="Auto,*">
<controls:FilterTextBox Grid.Row="0"
BorderThickness="0"
DataContext="{Binding TreePage.PropertiesFilter}"
Text="{Binding FilterString}"
Watermark="Filter properties"
UseCaseSensitiveFilter="{Binding UseCaseSensitiveFilter}"
UseWholeWordFilter="{Binding UseWholeWordFilter}"
UseRegexFilter="{Binding UseRegexFilter}" />
<DataGrid Items="{Binding PropertiesView}"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
BorderThickness="0"
RowBackground="Transparent"
SelectedItem="{Binding SelectedProperty, Mode=TwoWay}"
@ -71,7 +67,13 @@
<ToggleButton Margin="2,0,0,0" Grid.Column="3" ToolTip.Tip="Snapshot current styles (Alt+S/Alt+D to enable/disable within debugged window)" Content="Snapshot" IsChecked="{Binding SnapshotStyles}" />
</Grid>
<TextBox Grid.Row="1" Margin="2" Grid.Column="0" Watermark="Filter" Text="{Binding StyleFilter}" />
<controls:FilterTextBox Grid.Row="1" Margin="2" Grid.Column="0"
DataContext="{Binding TreePage.SettersFilter}"
Text="{Binding FilterString}"
Watermark="Filter setters"
UseCaseSensitiveFilter="{Binding UseCaseSensitiveFilter}"
UseWholeWordFilter="{Binding UseWholeWordFilter}"
UseRegexFilter="{Binding UseRegexFilter}" />
</Grid>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled">

9
src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml

@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters"
xmlns:controls="clr-namespace:Avalonia.Diagnostics.Controls"
x:Class="Avalonia.Diagnostics.Views.EventsPageView"
Margin="2">
<UserControl.Styles>
@ -35,7 +36,13 @@
<Grid Grid.Column="0" RowDefinitions="Auto,*,Auto">
<TextBox Classes="clearButton" Grid.Row="0" Margin="0,0,0,2" Text="{Binding EventTypeFilter}" Watermark="Search event types" />
<controls:FilterTextBox Grid.Row="0" Margin="0,0,0,2"
DataContext="{Binding EventsFilter}"
Text="{Binding FilterString}"
Watermark="Filter events"
UseCaseSensitiveFilter="{Binding UseCaseSensitiveFilter}"
UseWholeWordFilter="{Binding UseWholeWordFilter}"
UseRegexFilter="{Binding UseRegexFilter}" />
<TreeView Grid.Row="1" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" >
<TreeView.DataTemplates>

1
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml

@ -16,6 +16,7 @@
<Setter Property="Foreground" Value="Black"/>
</Style>
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml" />
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml" />
</Window.Styles>
<views:MainView/>

7
src/Avalonia.Themes.Default/TextBox.xaml

@ -113,13 +113,13 @@
</Style>
<Style Selector="Button.textBoxClearButton">
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="PART_ButtonLayoutBorder"
BorderThickness="{TemplateBinding BorderThickness}"
Background="Transparent"
Cursor="Arrow">
Background="Transparent">
<Path x:Name="PART_GlyphElement"
Fill="{DynamicResource ThemeForegroundColor}"
Data="{StaticResource TextBoxClearButtonData}"
@ -146,13 +146,14 @@
<Setter Property="(ToolTip.Tip)" Value="Show Password" />
</Style>
</Panel.Styles>
<ToggleButton Classes="passwordBoxRevealButton" Background="Transparent" Cursor="Arrow" IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}" />
<ToggleButton Classes="passwordBoxRevealButton" Background="Transparent" IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}" />
</Panel>
</Template>
</Setter>
</Style>
<Style Selector="ToggleButton.passwordBoxRevealButton">
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">

13
src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs

@ -1,6 +1,3 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Media;
@ -9,15 +6,7 @@ namespace Avalonia.Animation
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="BoxShadows"/> type.
/// </summary>
public class BoxShadowsTransition : Transition<BoxShadows>
public class BoxShadowsTransition : AnimatorDrivenTransition<BoxShadows, BoxShadowsAnimator>
{
private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator();
/// <inheritdocs/>
public override IObservable<BoxShadows> DoTransition(IObservable<double> progress, BoxShadows oldValue, BoxShadows newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

12
src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs

@ -0,0 +1,12 @@
using Avalonia.Animation.Animators;
using Avalonia.Media;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Color"/> type.
/// </summary>
public class ColorTransition : AnimatorDrivenTransition<Color, ColorAnimator>
{
}
}

13
src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs

@ -1,6 +1,3 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
@ -8,15 +5,7 @@ namespace Avalonia.Animation
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="CornerRadius"/> type.
/// </summary>
public class CornerRadiusTransition : Transition<CornerRadius>
public class CornerRadiusTransition : AnimatorDrivenTransition<CornerRadius, CornerRadiusAnimator>
{
private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator();
/// <inheritdocs/>
public override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue, CornerRadius newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

13
src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs

@ -1,6 +1,3 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
@ -8,15 +5,7 @@ namespace Avalonia.Animation
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Point"/> type.
/// </summary>
public class PointTransition : Transition<Point>
public class PointTransition : AnimatorDrivenTransition<Point, PointAnimator>
{
private static readonly PointAnimator s_animator = new PointAnimator();
/// <inheritdocs/>
public override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

13
src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs

@ -1,6 +1,3 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
@ -8,15 +5,7 @@ namespace Avalonia.Animation
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Size"/> type.
/// </summary>
public class SizeTransition : Transition<Size>
public class SizeTransition : AnimatorDrivenTransition<Size, SizeAnimator>
{
private static readonly SizeAnimator s_animator = new SizeAnimator();
/// <inheritdocs/>
public override IObservable<Size> DoTransition(IObservable<double> progress, Size oldValue, Size newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

13
src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs

@ -1,6 +1,3 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
@ -8,15 +5,7 @@ namespace Avalonia.Animation
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Thickness"/> type.
/// </summary>
public class ThicknessTransition : Transition<Thickness>
public class ThicknessTransition : AnimatorDrivenTransition<Thickness, ThicknessAnimator>
{
private static readonly ThicknessAnimator s_animator = new ThicknessAnimator();
/// <inheritdocs/>
public override IObservable<Thickness> DoTransition(IObservable<double> progress, Thickness oldValue, Thickness newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

18
src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs

@ -1,28 +1,26 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Media;
using Avalonia.Media.Transformation;
#nullable enable
namespace Avalonia.Animation
{
public class TransformOperationsTransition : Transition<ITransform>
{
private static readonly TransformOperationsAnimator _operationsAnimator = new TransformOperationsAnimator();
private static readonly TransformOperationsAnimator s_operationsAnimator = new TransformOperationsAnimator();
public override IObservable<ITransform> DoTransition(IObservable<double> progress,
public override IObservable<ITransform> DoTransition(
IObservable<double> progress,
ITransform oldValue,
ITransform newValue)
{
var oldTransform = TransformOperationsAnimator.EnsureOperations(oldValue);
var newTransform = TransformOperationsAnimator.EnsureOperations(newValue);
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return _operationsAnimator.Interpolate(f, oldTransform, newTransform);
});
return new AnimatorTransitionObservable<TransformOperations, TransformOperationsAnimator>(
s_operationsAnimator, progress, Easing, oldTransform, newTransform);
}
}
}

13
src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs

@ -1,6 +1,3 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
@ -8,15 +5,7 @@ namespace Avalonia.Animation
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Vector"/> type.
/// </summary>
public class VectorTransition : Transition<Vector>
public class VectorTransition : AnimatorDrivenTransition<Vector, VectorAnimator>
{
private static readonly VectorAnimator s_animator = new VectorAnimator();
/// <inheritdocs/>
public override IObservable<Vector> DoTransition(IObservable<double> progress, Vector oldValue, Vector newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

78
tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Runtime.CompilerServices;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Layout;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Animations
{
[MemoryDiagnoser]
public class TransitionBenchmark
{
private readonly AddValueObserver _observer;
private readonly List<double> _producedValues;
private readonly Subject<double> _timeProducer;
private readonly DoubleTransition _transition;
public TransitionBenchmark()
{
_transition = new DoubleTransition
{
Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty
};
_timeProducer = new Subject<double>();
_producedValues = new List<double>(FrameCount);
_observer = new AddValueObserver(_producedValues);
}
[Params(10, 100)] public int FrameCount { get; set; }
[Benchmark]
[MethodImpl(MethodImplOptions.NoInlining)]
public void NewTransition()
{
var transitionObs = _transition.DoTransition(_timeProducer, 0, 1);
_producedValues.Clear();
using var transitionSub = transitionObs.Subscribe(_observer);
for (int i = 0; i < FrameCount; i++)
{
_timeProducer.OnNext(i / 1000d);
}
Debug.Assert(_producedValues.Count == FrameCount);
}
private class AddValueObserver : IObserver<double>
{
private readonly List<double> _values;
public AddValueObserver(List<double> values)
{
_values = values;
}
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(double value)
{
_values.Add(value);
}
}
}
}
Loading…
Cancel
Save