From 5765007ecdbd3c6ec8b9dac281076bd90e40f313 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 01:03:41 +0200 Subject: [PATCH 01/55] Avoid closures and extra allocations caused by transition observables. --- .../AnimatorDrivenTransition.cs | 15 +++ .../AnimatorTransitionObservable.cs | 24 +++++ .../{Transition`1.cs => Transition.cs} | 6 +- src/Avalonia.Animation/TransitionInstance.cs | 23 +++-- .../TransitionObservableBase.cs | 45 +++++++++ .../Transitions/DoubleTransition.cs | 7 +- .../TransformOperationsTransition.cs | 18 ++-- .../Animations/TransitionBenchmark.cs | 92 +++++++++++++++++++ 8 files changed, 208 insertions(+), 22 deletions(-) create mode 100644 src/Avalonia.Animation/AnimatorDrivenTransition.cs create mode 100644 src/Avalonia.Animation/AnimatorTransitionObservable.cs rename src/Avalonia.Animation/{Transition`1.cs => Transition.cs} (96%) create mode 100644 src/Avalonia.Animation/TransitionObservableBase.cs create mode 100644 tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs new file mode 100644 index 0000000000..eb4d6f978d --- /dev/null +++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs @@ -0,0 +1,15 @@ +using System; +using Avalonia.Animation.Animators; + +namespace Avalonia.Animation +{ + public class AnimatorDrivenTransition : Transition where TAnimator : Animator, new() + { + private static readonly TAnimator s_animator = new TAnimator(); + + public override IObservable DoTransition(IObservable progress, T oldValue, T newValue) + { + return new AnimatorTransitionObservable(s_animator, progress, oldValue, newValue); + } + } +} diff --git a/src/Avalonia.Animation/AnimatorTransitionObservable.cs b/src/Avalonia.Animation/AnimatorTransitionObservable.cs new file mode 100644 index 0000000000..4f888e1f15 --- /dev/null +++ b/src/Avalonia.Animation/AnimatorTransitionObservable.cs @@ -0,0 +1,24 @@ +using System; +using Avalonia.Animation.Animators; + +namespace Avalonia.Animation +{ + public class AnimatorTransitionObservable : TransitionObservableBase where TAnimator : Animator + { + private readonly TAnimator _animator; + private readonly T _oldValue; + private readonly T _newValue; + + public AnimatorTransitionObservable(TAnimator animator, IObservable progress, T oldValue, T newValue) : base(progress) + { + _animator = animator; + _oldValue = oldValue; + _newValue = newValue; + } + + protected override T ProduceValue(double progress) + { + return _animator.Interpolate(progress, _oldValue, _newValue); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition.cs similarity index 96% rename from src/Avalonia.Animation/Transition`1.cs rename to src/Avalonia.Animation/Transition.cs index 4542a137e5..4115c95c0f 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/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((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } } -} +} \ No newline at end of file diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index 5184341324..58dd71e8a5 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -1,8 +1,4 @@ -using Avalonia.Metadata; using System; -using System.Reactive.Linq; -using Avalonia.Animation.Easings; -using Avalonia.Animation.Utils; using Avalonia.Reactive; using Avalonia.Utilities; @@ -11,7 +7,7 @@ namespace Avalonia.Animation /// /// Handles the timing and lifetime of a . /// - internal class TransitionInstance : SingleSubscriberObservableBase + internal class TransitionInstance : SingleSubscriberObservableBase, IObserver { private IDisposable _timerSubscription; private TimeSpan _delay; @@ -76,8 +72,23 @@ namespace Avalonia.Animation protected override void Subscribed() { _clock = new Clock(_baseClock); - _timerSubscription = _clock.Subscribe(TimerTick); + _timerSubscription = _clock.Subscribe(this); PublishNext(0.0d); } + + void IObserver.OnCompleted() + { + PublishCompleted(); + } + + void IObserver.OnError(Exception error) + { + PublishError(error); + } + + void IObserver.OnNext(TimeSpan value) + { + TimerTick(value); + } } } diff --git a/src/Avalonia.Animation/TransitionObservableBase.cs b/src/Avalonia.Animation/TransitionObservableBase.cs new file mode 100644 index 0000000000..e18b6859f5 --- /dev/null +++ b/src/Avalonia.Animation/TransitionObservableBase.cs @@ -0,0 +1,45 @@ +using System; +using Avalonia.Reactive; + +#nullable enable + +namespace Avalonia.Animation +{ + public abstract class TransitionObservableBase : SingleSubscriberObservableBase, IObserver + { + private readonly IObservable _progress; + private IDisposable? _progressSubscription; + + protected TransitionObservableBase(IObservable progress) + { + _progress = progress; + } + + protected override void Unsubscribed() + { + _progressSubscription?.Dispose(); + } + + protected override void Subscribed() + { + _progressSubscription = _progress.Subscribe(this); + } + + protected abstract T ProduceValue(double progress); + + void IObserver.OnCompleted() + { + PublishCompleted(); + } + + void IObserver.OnError(Exception error) + { + PublishError(error); + } + + void IObserver.OnNext(double value) + { + PublishNext(ProduceValue(value)); + } + } +} diff --git a/src/Avalonia.Animation/Transitions/DoubleTransition.cs b/src/Avalonia.Animation/Transitions/DoubleTransition.cs index d5bb1aac20..28f630eb98 100644 --- a/src/Avalonia.Animation/Transitions/DoubleTransition.cs +++ b/src/Avalonia.Animation/Transitions/DoubleTransition.cs @@ -1,6 +1,5 @@ using System; using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,7 +7,11 @@ namespace Avalonia.Animation /// /// Transition class that handles with types. /// - public class DoubleTransition : Transition + public class DoubleTransition : AnimatorDrivenTransition + { + } + + public class DoubleTransitionOld : Transition { private static readonly DoubleAnimator s_animator = new DoubleAnimator(); diff --git a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs index 104acb71ad..ca1528a561 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs +++ b/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 { - private static readonly TransformOperationsAnimator _operationsAnimator = new TransformOperationsAnimator(); + private static readonly TransformOperationsAnimator s_operationsAnimator = new TransformOperationsAnimator(); - public override IObservable DoTransition(IObservable progress, + public override IObservable DoTransition( + IObservable 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( + s_operationsAnimator, progress, oldTransform, newTransform); } } } diff --git a/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs new file mode 100644 index 0000000000..16aad0c21b --- /dev/null +++ b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reactive.Subjects; +using System.Runtime.CompilerServices; +using Avalonia.Animation; +using Avalonia.Layout; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Animations +{ + [MemoryDiagnoser] + public class TransitionBenchmark + { + private readonly DoubleTransition _transition; + private readonly DoubleTransitionOld _oldTransition; + private readonly int _frameCount; + private readonly Subject _timeProducer; + private readonly List _producedValues; + + public TransitionBenchmark() + { + _frameCount = 100; + + _oldTransition = new DoubleTransitionOld + { + Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty + }; + + _transition = new DoubleTransition + { + Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty + }; + + _timeProducer = new Subject(); + _producedValues = new List(_frameCount); + } + + [Benchmark(Baseline = true)] + [MethodImpl(MethodImplOptions.NoInlining)] + public void OldTransition() + { + TransitionCommon(_oldTransition); + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public void NewTransition() + { + TransitionCommon(_transition); + } + + private void TransitionCommon(Transition transition) + { + var transitionObs = transition.DoTransition(_timeProducer, 0, 1); + + _producedValues.Clear(); + + using var transitionSub = transitionObs.Subscribe(new AddValueObserver(_producedValues)); + + for (int i = 0; i < _frameCount; i++) + { + _timeProducer.OnNext(TimeSpan.FromMilliseconds(i).TotalSeconds); + } + + Debug.Assert(_producedValues.Count == _frameCount); + } + + private class AddValueObserver : IObserver + { + private readonly List _values; + + public AddValueObserver(List values) + { + _values = values; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(double value) + { + _values.Add(value); + } + } + } +} From 9bf33631ab267e6cd8a42439bc4a0cdec6655fa6 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 01:31:27 +0200 Subject: [PATCH 02/55] Make base transition abstract. --- src/Avalonia.Animation/AnimatorDrivenTransition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs index eb4d6f978d..13139f8e8d 100644 --- a/src/Avalonia.Animation/AnimatorDrivenTransition.cs +++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs @@ -3,7 +3,7 @@ using Avalonia.Animation.Animators; namespace Avalonia.Animation { - public class AnimatorDrivenTransition : Transition where TAnimator : Animator, new() + public abstract class AnimatorDrivenTransition : Transition where TAnimator : Animator, new() { private static readonly TAnimator s_animator = new TAnimator(); From 82ca716cca8cd879becc13bd5351d296ef67693e Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 01:32:23 +0200 Subject: [PATCH 03/55] Remove ref counting from ClockBase. --- src/Avalonia.Animation/ClockBase.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Animation/ClockBase.cs b/src/Avalonia.Animation/ClockBase.cs index a2b29e728e..9fa6a85a6c 100644 --- a/src/Avalonia.Animation/ClockBase.cs +++ b/src/Avalonia.Animation/ClockBase.cs @@ -8,9 +8,7 @@ namespace Avalonia.Animation { public class ClockBase : IClock { - private ClockObservable _observable; - - private IObservable _connectedObservable; + private readonly ClockObservable _observable; private TimeSpan? _previousTime; private TimeSpan _internalTime; @@ -18,7 +16,6 @@ namespace Avalonia.Animation protected ClockBase() { _observable = new ClockObservable(); - _connectedObservable = _observable.Publish().RefCount(); } protected bool HasSubscriptions => _observable.HasSubscriptions; @@ -58,7 +55,7 @@ namespace Avalonia.Animation public IDisposable Subscribe(IObserver observer) { - return _connectedObservable.Subscribe(observer); + return _observable.Subscribe(observer); } private class ClockObservable : LightweightObservableBase From 54090bc1f7263da7c4674616be1c342fa8e8aac7 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 01:47:37 +0200 Subject: [PATCH 04/55] Apply easing. --- src/Avalonia.Animation/AnimatorDrivenTransition.cs | 2 +- src/Avalonia.Animation/AnimatorTransitionObservable.cs | 9 +++++++-- .../Transitions/TransformOperationsTransition.cs | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs index 13139f8e8d..3f6ac47556 100644 --- a/src/Avalonia.Animation/AnimatorDrivenTransition.cs +++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs @@ -9,7 +9,7 @@ namespace Avalonia.Animation public override IObservable DoTransition(IObservable progress, T oldValue, T newValue) { - return new AnimatorTransitionObservable(s_animator, progress, oldValue, newValue); + return new AnimatorTransitionObservable(s_animator, progress, Easing, oldValue, newValue); } } } diff --git a/src/Avalonia.Animation/AnimatorTransitionObservable.cs b/src/Avalonia.Animation/AnimatorTransitionObservable.cs index 4f888e1f15..10c19bb62b 100644 --- a/src/Avalonia.Animation/AnimatorTransitionObservable.cs +++ b/src/Avalonia.Animation/AnimatorTransitionObservable.cs @@ -1,24 +1,29 @@ using System; using Avalonia.Animation.Animators; +using Avalonia.Animation.Easings; namespace Avalonia.Animation { public class AnimatorTransitionObservable : TransitionObservableBase where TAnimator : Animator { private readonly TAnimator _animator; + private readonly Easing _easing; private readonly T _oldValue; private readonly T _newValue; - public AnimatorTransitionObservable(TAnimator animator, IObservable progress, T oldValue, T newValue) : base(progress) + public AnimatorTransitionObservable(TAnimator animator, IObservable progress, Easing easing, T oldValue, T newValue) : base(progress) { _animator = animator; + _easing = easing; _oldValue = oldValue; _newValue = newValue; } protected override T ProduceValue(double progress) { + progress = _easing.Ease(progress); + return _animator.Interpolate(progress, _oldValue, _newValue); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs index ca1528a561..73fc23ad99 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs @@ -20,7 +20,7 @@ namespace Avalonia.Animation var newTransform = TransformOperationsAnimator.EnsureOperations(newValue); return new AnimatorTransitionObservable( - s_operationsAnimator, progress, oldTransform, newTransform); + s_operationsAnimator, progress, Easing, oldTransform, newTransform); } } } From 4f82d510c5642059acdf27157472e42143be9cc8 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 19 May 2021 23:57:11 -0400 Subject: [PATCH 05/55] Enable AvaloniaObject nullablity --- src/Avalonia.Base/AvaloniaObject.cs | 39 +++++++++++-------- .../Diagnostics/IAvaloniaObjectDebug.cs | 4 +- src/Avalonia.Base/IAvaloniaObject.cs | 9 ++++- src/Avalonia.Styling/StyledElement.cs | 2 +- .../Styling/PropertySetterInstance.cs | 4 +- .../Styling/PropertySetterLazyInstance.cs | 4 +- 6 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index fdd688cf9d..6a9cff6b71 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -7,6 +7,8 @@ using Avalonia.Logging; using Avalonia.PropertyStore; using Avalonia.Threading; +#nullable enable + namespace Avalonia { /// @@ -17,12 +19,12 @@ namespace Avalonia /// public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IValueSink { - private IAvaloniaObject _inheritanceParent; - private List _directBindings; - private PropertyChangedEventHandler _inpcChanged; - private EventHandler _propertyChanged; - private List _inheritanceChildren; - private ValueStore _values; + private IAvaloniaObject? _inheritanceParent; + private List? _directBindings; + private PropertyChangedEventHandler? _inpcChanged; + private EventHandler? _propertyChanged; + private List? _inheritanceChildren; + private ValueStore? _values; private bool _batchUpdate; /// @@ -36,7 +38,7 @@ namespace Avalonia /// /// Raised when a value changes on this object. /// - public event EventHandler PropertyChanged + public event EventHandler? PropertyChanged { add { _propertyChanged += value; } remove { _propertyChanged -= value; } @@ -58,7 +60,7 @@ namespace Avalonia /// /// The inheritance parent. /// - protected IAvaloniaObject InheritanceParent + protected IAvaloniaObject? InheritanceParent { get { @@ -289,7 +291,8 @@ namespace Avalonia /// True if the property is animating, otherwise false. public bool IsAnimating(AvaloniaProperty property) { - Contract.Requires(property != null); + property = property ?? throw new ArgumentNullException(nameof(property)); + VerifyAccess(); return _values?.IsAnimating(property) ?? false; @@ -306,7 +309,8 @@ namespace Avalonia /// public bool IsSet(AvaloniaProperty property) { - Contract.Requires(property != null); + property = property ?? throw new ArgumentNullException(nameof(property)); + VerifyAccess(); return _values?.IsSet(property) ?? false; @@ -320,7 +324,7 @@ namespace Avalonia /// The priority of the value. public void SetValue( AvaloniaProperty property, - object value, + object? value, BindingPriority priority = BindingPriority.LocalValue) { property = property ?? throw new ArgumentNullException(nameof(property)); @@ -338,7 +342,7 @@ namespace Avalonia /// /// An if setting the property can be undone, otherwise null. /// - public IDisposable SetValue( + public IDisposable? SetValue( StyledPropertyBase property, T value, BindingPriority priority = BindingPriority.LocalValue) @@ -497,7 +501,7 @@ namespace Avalonia } /// - Delegate[] IAvaloniaObjectDebug.GetPropertyChangedSubscribers() + Delegate[]? IAvaloniaObjectDebug.GetPropertyChangedSubscribers() { return _propertyChanged?.GetInvocationList(); } @@ -723,7 +727,8 @@ namespace Avalonia { var values = o._values; - if (values?.TryGetValue(property, maxPriority, out value) == true) + if (values != null + && values.TryGetValue(property, maxPriority, out value) == true) { return value; } @@ -873,7 +878,7 @@ namespace Avalonia } else { - LogBindingError(property, value.Error); + LogBindingError(property, value.Error!); } } } @@ -907,14 +912,14 @@ namespace Avalonia { _owner = owner; _property = property; - _owner._directBindings.Add(this); + _owner._directBindings!.Add(this); _subscription = source.Subscribe(this); } public void Dispose() { _subscription.Dispose(); - _owner._directBindings.Remove(this); + _owner._directBindings!.Remove(this); } public void OnCompleted() => Dispose(); diff --git a/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs b/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs index 7f09425905..4b9f12ddf8 100644 --- a/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs +++ b/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Avalonia.Diagnostics { /// @@ -14,6 +16,6 @@ namespace Avalonia.Diagnostics /// /// The subscribers or null if no subscribers. /// - Delegate[] GetPropertyChangedSubscribers(); + Delegate[]? GetPropertyChangedSubscribers(); } } diff --git a/src/Avalonia.Base/IAvaloniaObject.cs b/src/Avalonia.Base/IAvaloniaObject.cs index 0452f77d4c..2e992f8616 100644 --- a/src/Avalonia.Base/IAvaloniaObject.cs +++ b/src/Avalonia.Base/IAvaloniaObject.cs @@ -1,6 +1,8 @@ using System; using Avalonia.Data; +#nullable enable + namespace Avalonia { /// @@ -11,7 +13,7 @@ namespace Avalonia /// /// Raised when a value changes on this object. /// - event EventHandler PropertyChanged; + event EventHandler? PropertyChanged; /// /// Clears an 's local value. @@ -75,7 +77,10 @@ namespace Avalonia /// The property. /// The value. /// The priority of the value. - IDisposable SetValue( + /// + /// An if setting the property can be undone, otherwise null. + /// + IDisposable? SetValue( StyledPropertyBase property, T value, BindingPriority priority = BindingPriority.LocalValue); diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 5f26f65512..ae14b12dae 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -300,7 +300,7 @@ namespace Avalonia bool IStyleHost.IsStylesInitialized => _styles != null; /// - IStyleHost? IStyleHost.StylingParent => (IStyleHost)InheritanceParent; + IStyleHost? IStyleHost.StylingParent => (IStyleHost?)InheritanceParent; /// public virtual void BeginInit() diff --git a/src/Avalonia.Styling/Styling/PropertySetterInstance.cs b/src/Avalonia.Styling/Styling/PropertySetterInstance.cs index b52ae6b146..1c3055fed6 100644 --- a/src/Avalonia.Styling/Styling/PropertySetterInstance.cs +++ b/src/Avalonia.Styling/Styling/PropertySetterInstance.cs @@ -50,7 +50,7 @@ namespace Avalonia.Styling } else { - _subscription = _target.Bind(_directProperty, this); + _subscription = _target.Bind(_directProperty!, this); } } else @@ -100,7 +100,7 @@ namespace Avalonia.Styling } else { - _target.ClearValue(_directProperty); + _target.ClearValue(_directProperty!); } } diff --git a/src/Avalonia.Styling/Styling/PropertySetterLazyInstance.cs b/src/Avalonia.Styling/Styling/PropertySetterLazyInstance.cs index 03d33faff9..92653d0064 100644 --- a/src/Avalonia.Styling/Styling/PropertySetterLazyInstance.cs +++ b/src/Avalonia.Styling/Styling/PropertySetterLazyInstance.cs @@ -53,7 +53,7 @@ namespace Avalonia.Styling } else { - _subscription = _target.Bind(_directProperty, this); + _subscription = _target.Bind(_directProperty!, this); } } @@ -91,7 +91,7 @@ namespace Avalonia.Styling } else { - _target.ClearValue(_directProperty); + _target.ClearValue(_directProperty!); } } From 3984ba2ec5bdb24b8fa268bfcc52928e697b0bc7 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 20 May 2021 00:29:45 -0400 Subject: [PATCH 06/55] Enable nullability for InputElement, Interactive, Visual --- src/Avalonia.Controls/Control.cs | 2 +- src/Avalonia.Controls/IControl.cs | 4 +- src/Avalonia.Input/IInputElement.cs | 2 + src/Avalonia.Input/InputElement.cs | 2 + src/Avalonia.Input/KeyboardDevice.cs | 4 +- src/Avalonia.Input/MouseDevice.cs | 8 +-- src/Avalonia.Interactivity/IInteractive.cs | 2 + src/Avalonia.Interactivity/Interactive.cs | 2 + src/Avalonia.Layout/Layoutable.cs | 2 +- .../Controls/ISetInheritanceParent.cs | 6 ++- .../Controls/ISetLogicalParent.cs | 6 ++- src/Avalonia.Styling/IDataContextProvider.cs | 6 ++- src/Avalonia.Styling/IStyledElement.cs | 6 ++- src/Avalonia.Styling/StyledElement.cs | 2 +- src/Avalonia.Styling/Styling/ISetterValue.cs | 4 +- src/Avalonia.Visuals/Visual.cs | 50 ++++++++++--------- src/Avalonia.Visuals/VisualTree/IVisual.cs | 16 +++--- 17 files changed, 74 insertions(+), 50 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index e964673845..a7ec7cc5d2 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -201,7 +201,7 @@ namespace Avalonia.Controls { base.OnLostFocus(e); - if (_focusAdorner != null) + if (_focusAdorner?.Parent != null) { var adornerLayer = (IPanel)_focusAdorner.Parent; adornerLayer.Children.Remove(_focusAdorner); diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index 6298531a16..598e6b6f4e 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -3,6 +3,8 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.VisualTree; +#nullable enable + namespace Avalonia.Controls { /// @@ -15,6 +17,6 @@ namespace Avalonia.Controls INamed, IStyledElement { - new IControl Parent { get; } + new IControl? Parent { get; } } } diff --git a/src/Avalonia.Input/IInputElement.cs b/src/Avalonia.Input/IInputElement.cs index 7aa9c32bca..2245ff9986 100644 --- a/src/Avalonia.Input/IInputElement.cs +++ b/src/Avalonia.Input/IInputElement.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using Avalonia.Interactivity; using Avalonia.VisualTree; +#nullable enable + namespace Avalonia.Input { /// diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 8f99770b3b..65b9acae76 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -9,6 +9,8 @@ using Avalonia.Input.TextInput; using Avalonia.Interactivity; using Avalonia.VisualTree; +#nullable enable + namespace Avalonia.Input { /// diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 5899824c29..79152e20d2 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -60,7 +60,7 @@ namespace Avalonia.Input ie.IsKeyboardFocusWithin = false; } - el = (IInputElement)el.VisualParent; + el = (IInputElement?)el.VisualParent; } } @@ -212,7 +212,7 @@ namespace Avalonia.Input Source = element, }; - IVisual currentHandler = element; + IVisual? currentHandler = element; while (currentHandler != null && !ev.Handled && keyInput.Type == RawKeyEventType.KeyDown) { var bindings = (currentHandler as IInputElement)?.KeyBindings; diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 6e937b7e13..cfa3690daf 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -377,7 +377,7 @@ namespace Avalonia.Input e.Source = element; e.Handled = false; element.RaiseEvent(e); - element = (IInputElement)element.VisualParent; + element = (IInputElement?)element.VisualParent; } root.PointerOverElement = null; @@ -444,7 +444,7 @@ namespace Avalonia.Input branch = el; break; } - el = (IInputElement)el.VisualParent; + el = (IInputElement?)el.VisualParent; } el = root.PointerOverElement; @@ -460,7 +460,7 @@ namespace Avalonia.Input e.Source = el; e.Handled = false; el.RaiseEvent(e); - el = (IInputElement)el.VisualParent; + el = (IInputElement?)el.VisualParent; } el = root.PointerOverElement = element; @@ -471,7 +471,7 @@ namespace Avalonia.Input e.Source = el; e.Handled = false; el.RaiseEvent(e); - el = (IInputElement)el.VisualParent; + el = (IInputElement?)el.VisualParent; } } diff --git a/src/Avalonia.Interactivity/IInteractive.cs b/src/Avalonia.Interactivity/IInteractive.cs index 5c01f870ab..afda29e329 100644 --- a/src/Avalonia.Interactivity/IInteractive.cs +++ b/src/Avalonia.Interactivity/IInteractive.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Avalonia.Interactivity { /// diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 580704bb19..4cd810af20 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using Avalonia.Layout; using Avalonia.VisualTree; +#nullable enable + namespace Avalonia.Interactivity { /// diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index a1d00017ed..7568ea8e09 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -782,7 +782,7 @@ namespace Avalonia.Layout } /// - protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent) + protected sealed override void OnVisualParentChanged(IVisual? oldParent, IVisual? newParent) { LayoutHelper.InvalidateSelfAndChildrenMeasure(this); diff --git a/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs b/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs index ef3c1d5b89..dbf8c68892 100644 --- a/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs +++ b/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs @@ -1,3 +1,5 @@ +#nullable enable + namespace Avalonia.Controls { /// @@ -14,6 +16,6 @@ namespace Avalonia.Controls /// Sets the control's inheritance parent. /// /// The parent. - void SetParent(IAvaloniaObject parent); + void SetParent(IAvaloniaObject? parent); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Styling/Controls/ISetLogicalParent.cs b/src/Avalonia.Styling/Controls/ISetLogicalParent.cs index 0c0cd1c1bb..85bda05961 100644 --- a/src/Avalonia.Styling/Controls/ISetLogicalParent.cs +++ b/src/Avalonia.Styling/Controls/ISetLogicalParent.cs @@ -1,5 +1,7 @@ using Avalonia.LogicalTree; +#nullable enable + namespace Avalonia.Controls { /// @@ -14,6 +16,6 @@ namespace Avalonia.Controls /// Sets the control's parent. /// /// The parent. - void SetParent(ILogical parent); + void SetParent(ILogical? parent); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Styling/IDataContextProvider.cs b/src/Avalonia.Styling/IDataContextProvider.cs index 31639c5784..1172adcaa4 100644 --- a/src/Avalonia.Styling/IDataContextProvider.cs +++ b/src/Avalonia.Styling/IDataContextProvider.cs @@ -1,4 +1,6 @@ -namespace Avalonia +#nullable enable + +namespace Avalonia { /// /// Defines an element with a data context that can be used for binding. @@ -8,6 +10,6 @@ /// /// Gets or sets the element's data context. /// - object DataContext { get; set; } + object? DataContext { get; set; } } } diff --git a/src/Avalonia.Styling/IStyledElement.cs b/src/Avalonia.Styling/IStyledElement.cs index 046a6f9872..a068d4a5bf 100644 --- a/src/Avalonia.Styling/IStyledElement.cs +++ b/src/Avalonia.Styling/IStyledElement.cs @@ -4,6 +4,8 @@ using Avalonia.Controls; using Avalonia.LogicalTree; using Avalonia.Styling; +#nullable enable + namespace Avalonia { public interface IStyledElement : @@ -17,7 +19,7 @@ namespace Avalonia /// /// Occurs when the control has finished initialization. /// - event EventHandler Initialized; + event EventHandler? Initialized; /// /// Gets a value that indicates whether the element has finished initialization. @@ -32,6 +34,6 @@ namespace Avalonia /// /// Gets the control's logical parent. /// - IStyledElement Parent { get; } + IStyledElement? Parent { get; } } } diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index ae14b12dae..2292f5c518 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -465,7 +465,7 @@ namespace Avalonia /// Sets the styled element's inheritance parent. /// /// The parent. - void ISetInheritanceParent.SetParent(IAvaloniaObject parent) + void ISetInheritanceParent.SetParent(IAvaloniaObject? parent) { InheritanceParent = parent; } diff --git a/src/Avalonia.Styling/Styling/ISetterValue.cs b/src/Avalonia.Styling/Styling/ISetterValue.cs index 63c544cf7d..0fd245a429 100644 --- a/src/Avalonia.Styling/Styling/ISetterValue.cs +++ b/src/Avalonia.Styling/Styling/ISetterValue.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Styling +#nullable enable + +namespace Avalonia.Styling { /// /// Customizes the behavior of a class when added as a value to an . diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 80d4195421..2c96d87bb6 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -11,6 +11,8 @@ using Avalonia.Rendering; using Avalonia.Utilities; using Avalonia.VisualTree; +#nullable enable + namespace Avalonia { /// @@ -45,8 +47,8 @@ namespace Avalonia /// /// Defines the property. /// - public static readonly StyledProperty ClipProperty = - AvaloniaProperty.Register(nameof(Clip)); + public static readonly StyledProperty ClipProperty = + AvaloniaProperty.Register(nameof(Clip)); /// /// Defines the property. @@ -63,14 +65,14 @@ namespace Avalonia /// /// Defines the property. /// - public static readonly StyledProperty OpacityMaskProperty = - AvaloniaProperty.Register(nameof(OpacityMask)); + public static readonly StyledProperty OpacityMaskProperty = + AvaloniaProperty.Register(nameof(OpacityMask)); /// /// Defines the property. /// - public static readonly StyledProperty RenderTransformProperty = - AvaloniaProperty.Register(nameof(RenderTransform)); + public static readonly StyledProperty RenderTransformProperty = + AvaloniaProperty.Register(nameof(RenderTransform)); /// /// Defines the property. @@ -81,8 +83,8 @@ namespace Avalonia /// /// Defines the property. /// - public static readonly DirectProperty VisualParentProperty = - AvaloniaProperty.RegisterDirect("VisualParent", o => o._visualParent); + public static readonly DirectProperty VisualParentProperty = + AvaloniaProperty.RegisterDirect(nameof(IVisual.VisualParent), o => o._visualParent); /// /// Defines the property. @@ -92,8 +94,8 @@ namespace Avalonia private Rect _bounds; private TransformedBounds? _transformedBounds; - private IRenderRoot _visualRoot; - private IVisual _visualParent; + private IRenderRoot? _visualRoot; + private IVisual? _visualParent; /// /// Initializes static members of the class. @@ -128,12 +130,12 @@ namespace Avalonia /// /// Raised when the control is attached to a rooted visual tree. /// - public event EventHandler AttachedToVisualTree; + public event EventHandler? AttachedToVisualTree; /// /// Raised when the control is detached from a rooted visual tree. /// - public event EventHandler DetachedFromVisualTree; + public event EventHandler? DetachedFromVisualTree; /// /// Gets the bounds of the control relative to its parent. @@ -161,7 +163,7 @@ namespace Avalonia /// /// Gets or sets the geometry clip for this visual. /// - public Geometry Clip + public Geometry? Clip { get { return GetValue(ClipProperty); } set { SetValue(ClipProperty, value); } @@ -174,7 +176,7 @@ namespace Avalonia { get { - IVisual node = this; + IVisual? node = this; while (node != null) { @@ -211,7 +213,7 @@ namespace Avalonia /// /// Gets or sets the opacity mask of the control. /// - public IBrush OpacityMask + public IBrush? OpacityMask { get { return GetValue(OpacityMaskProperty); } set { SetValue(OpacityMaskProperty, value); } @@ -220,7 +222,7 @@ namespace Avalonia /// /// Gets or sets the render transform of the control. /// - public ITransform RenderTransform + public ITransform? RenderTransform { get { return GetValue(RenderTransformProperty); } set { SetValue(RenderTransformProperty, value); } @@ -261,7 +263,7 @@ namespace Avalonia /// /// Gets the root of the visual tree, if the control is attached to a visual tree. /// - protected IRenderRoot VisualRoot => _visualRoot ?? (this as IRenderRoot); + protected IRenderRoot? VisualRoot => _visualRoot ?? (this as IRenderRoot); /// /// Gets a value indicating whether this control is attached to a visual root. @@ -276,12 +278,12 @@ namespace Avalonia /// /// Gets the control's parent visual. /// - IVisual IVisual.VisualParent => _visualParent; + IVisual? IVisual.VisualParent => _visualParent; /// /// Gets the root of the visual tree, if the control is attached to a visual tree. /// - IRenderRoot IVisual.VisualRoot => VisualRoot; + IRenderRoot? IVisual.VisualRoot => VisualRoot; TransformedBounds? IVisual.TransformedBounds { @@ -476,12 +478,12 @@ namespace Avalonia /// /// The old visual parent. /// The new visual parent. - protected virtual void OnVisualParentChanged(IVisual oldParent, IVisual newParent) + protected virtual void OnVisualParentChanged(IVisual? oldParent, IVisual? newParent) { RaisePropertyChanged( VisualParentProperty, - new Optional(oldParent), - new BindingValue(newParent), + new Optional(oldParent), + new BindingValue(newParent), BindingPriority.LocalValue); } @@ -582,7 +584,7 @@ namespace Avalonia /// Sets the visual parent of the Visual. /// /// The visual parent. - private void SetVisualParent(Visual value) + private void SetVisualParent(Visual? value) { if (_visualParent == value) { @@ -634,7 +636,7 @@ namespace Avalonia } } - private static void SetVisualParent(IList children, Visual parent) + private static void SetVisualParent(IList children, Visual? parent) { var count = children.Count; diff --git a/src/Avalonia.Visuals/VisualTree/IVisual.cs b/src/Avalonia.Visuals/VisualTree/IVisual.cs index 50787655d9..97c4554de6 100644 --- a/src/Avalonia.Visuals/VisualTree/IVisual.cs +++ b/src/Avalonia.Visuals/VisualTree/IVisual.cs @@ -3,6 +3,8 @@ using Avalonia.Collections; using Avalonia.Media; using Avalonia.Rendering; +#nullable enable + namespace Avalonia.VisualTree { /// @@ -21,12 +23,12 @@ namespace Avalonia.VisualTree /// /// Raised when the control is attached to a rooted visual tree. /// - event EventHandler AttachedToVisualTree; + event EventHandler? AttachedToVisualTree; /// /// Raised when the control is detached from a rooted visual tree. /// - event EventHandler DetachedFromVisualTree; + event EventHandler? DetachedFromVisualTree; /// /// Gets the bounds of the control relative to its parent. @@ -46,7 +48,7 @@ namespace Avalonia.VisualTree /// /// Gets or sets the geometry clip for this visual. /// - Geometry Clip { get; set; } + Geometry? Clip { get; set; } /// /// Gets a value indicating whether this control is attached to a visual root. @@ -71,12 +73,12 @@ namespace Avalonia.VisualTree /// /// Gets or sets the opacity mask for the control. /// - IBrush OpacityMask { get; set; } + IBrush? OpacityMask { get; set; } /// /// Gets or sets the render transform of the control. /// - ITransform RenderTransform { get; set; } + ITransform? RenderTransform { get; set; } /// /// Gets or sets the render transform origin of the control. @@ -91,12 +93,12 @@ namespace Avalonia.VisualTree /// /// Gets the control's parent visual. /// - IVisual VisualParent { get; } + IVisual? VisualParent { get; } /// /// Gets the root of the visual tree, if the control is attached to a visual tree. /// - IRenderRoot VisualRoot { get; } + IRenderRoot? VisualRoot { get; } /// /// Gets or sets the Z index of the node. From 60ad7eae12fb366475fab8eb615432af3ea05093 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 20:35:07 +0200 Subject: [PATCH 07/55] More cleanup. --- src/Avalonia.Animation/Clock.cs | 5 ++-- .../TransitionObservableBase.cs | 24 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index bea6c75982..6c7ae8349a 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -10,10 +10,9 @@ namespace Avalonia.Animation { public static IClock GlobalClock => AvaloniaLocator.Current.GetService(); - private IDisposable _parentSubscription; + private readonly IDisposable _parentSubscription; - public Clock() - :this(GlobalClock) + public Clock() : this(GlobalClock) { } diff --git a/src/Avalonia.Animation/TransitionObservableBase.cs b/src/Avalonia.Animation/TransitionObservableBase.cs index e18b6859f5..9f4fb8093c 100644 --- a/src/Avalonia.Animation/TransitionObservableBase.cs +++ b/src/Avalonia.Animation/TransitionObservableBase.cs @@ -15,18 +15,6 @@ namespace Avalonia.Animation _progress = progress; } - protected override void Unsubscribed() - { - _progressSubscription?.Dispose(); - } - - protected override void Subscribed() - { - _progressSubscription = _progress.Subscribe(this); - } - - protected abstract T ProduceValue(double progress); - void IObserver.OnCompleted() { PublishCompleted(); @@ -41,5 +29,17 @@ namespace Avalonia.Animation { PublishNext(ProduceValue(value)); } + + protected override void Unsubscribed() + { + _progressSubscription?.Dispose(); + } + + protected override void Subscribed() + { + _progressSubscription = _progress.Subscribe(this); + } + + protected abstract T ProduceValue(double progress); } } From a5fb0919503af001159ba89e6b60e5c0b35d5030 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 May 2021 17:28:53 +0100 Subject: [PATCH 08/55] remove shadow invalidation hacks. --- native/Avalonia.Native/src/OSX/window.h | 1 - native/Avalonia.Native/src/OSX/window.mm | 18 ------------------ 2 files changed, 19 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index 6a4ca2b8d8..b1f64bca88 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -34,7 +34,6 @@ class WindowBaseImpl; -(double) getScaling; -(double) getExtendedTitleBarHeight; -(void) setIsExtended:(bool)value; --(void) updateShadow; @end struct INSWindowHolder diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index e7c144b2d3..fb5b1c99a2 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -125,10 +125,6 @@ public: _shown = true; - dispatch_async(dispatch_get_main_queue(), ^{ - [Window updateShadow]; - }); - return S_OK; } } @@ -1842,19 +1838,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent double _lastScaling; } -- (void)updateShadow -{ - // Common problem in Cocoa where [invalidateShadow] does work, - // This hack forces Cocoa to invalidate the shadow. - - NSRect frame = [self frame]; - NSRect updatedFrame = NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width + 1.0, frame.size.height + 1.0); - [self setFrame:updatedFrame display:YES]; - [self setFrame:frame display:YES]; - - [self invalidateShadow]; -} - -(void) setIsExtended:(bool)value; { _isExtended = value; @@ -2013,7 +1996,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _lastScaling = [self backingScaleFactor]; [self setOpaque:NO]; [self setBackgroundColor: [NSColor clearColor]]; - [self invalidateShadow]; _isExtended = false; return self; } From 30035f3134901ca09ec7fcab456263161b429dfd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 May 2021 17:29:17 +0100 Subject: [PATCH 09/55] set the content before Show is called, so that contentview is set after the size has been set. --- native/Avalonia.Native/src/OSX/window.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index fb5b1c99a2..bdc6e4f828 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -50,7 +50,6 @@ public: [Window setBackingType:NSBackingStoreBuffered]; [Window setOpaque:false]; - [Window setContentView: StandardContainer]; } virtual HRESULT ObtainNSWindowHandle(void** ret) override @@ -112,6 +111,9 @@ public: { SetPosition(lastPositionSet); UpdateStyle(); + + [Window setContentView: StandardContainer]; + if(ShouldTakeFocusOnShow() && activate) { [Window makeKeyAndOrderFront:Window]; From ad9519102e7b8af0ff146ab7a4c94ff0af053307 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 May 2021 17:41:27 +0100 Subject: [PATCH 10/55] always set the window startuplocation before show. --- src/Avalonia.Controls/Window.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 273ed790c3..700c3d9bad 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -668,10 +668,11 @@ namespace Avalonia.Controls Owner = parent; parent?.AddChild(this, false); - - PlatformImpl?.Show(ShowActivated); - Renderer?.Start(); + SetWindowStartupLocation(Owner?.PlatformImpl); + + PlatformImpl?.Show(ShowActivated); + Renderer?.Start(); } OnOpened(EventArgs.Empty); } @@ -739,6 +740,9 @@ namespace Avalonia.Controls PlatformImpl?.SetParent(owner.PlatformImpl); Owner = owner; owner.AddChild(this, true); + + SetWindowStartupLocation(owner.PlatformImpl); + PlatformImpl?.Show(ShowActivated); Renderer?.Start(); @@ -756,8 +760,6 @@ namespace Avalonia.Controls OnOpened(EventArgs.Empty); } - SetWindowStartupLocation(owner.PlatformImpl); - return result.Task; } From 99cdff2d7060700f94e2a769fe38a6c5fe751432 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 May 2021 22:07:55 +0100 Subject: [PATCH 11/55] fix screen centering on OSX before window has been shown. --- native/Avalonia.Native/src/OSX/window.mm | 21 +++++++++++++++++---- src/Avalonia.Controls/Window.cs | 6 +++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index bdc6e4f828..78092e12c6 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -25,6 +25,7 @@ public: ComPtr _glContext; NSObject* renderTarget; AvnPoint lastPositionSet; + CGSize _lastRequestedSize; NSString* _lastTitle; IAvnMenu* _mainMenu; @@ -193,9 +194,18 @@ public: { if(ret == nullptr) return E_POINTER; - auto frame = [View frame]; - ret->Width = frame.size.width; - ret->Height = frame.size.height; + + if(_shown) + { + auto frame = [View frame]; + ret->Width = frame.size.width; + ret->Height = frame.size.height; + } + else + { + ret->Width = _lastRequestedSize.width; + ret->Height = _lastRequestedSize.height; + } return S_OK; } } @@ -256,6 +266,9 @@ public: y = maxSize.height; } + _lastRequestedSize.width = x; + _lastRequestedSize.height = y; + [Window setContentSize:NSSize{x, y}]; return S_OK; @@ -530,7 +543,7 @@ private: _decorations = SystemDecorationsFull; _transitioningWindowState = false; _inSetWindowState = false; - _lastWindowState = Normal; + _lastWindowState = (AvnWindowState)-1; WindowEvents = events; [Window setCanBecomeKeyAndMain]; [Window disableCursorRects]; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 700c3d9bad..ab6e16d8dd 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -653,7 +653,11 @@ namespace Avalonia.Controls { using (BeginAutoSizing()) { - PlatformImpl?.Resize(initialSize); + if (PlatformImpl != null) + { + PlatformImpl.Resize(initialSize); + ClientSize = PlatformImpl.ClientSize; + } } } From a194fc12e2d3578b022cbeb0387f63b392ebf6df Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 May 2021 22:48:36 +0100 Subject: [PATCH 12/55] OSX backend initial sizing now has same behavior as windows. When window is not shown, and Resize is called, it will trigger a Resize event, that updates ClientSize. --- native/Avalonia.Native/src/OSX/window.mm | 13 +++++-------- src/Avalonia.Controls/Window.cs | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 78092e12c6..a4929c7c91 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -25,7 +25,6 @@ public: ComPtr _glContext; NSObject* renderTarget; AvnPoint lastPositionSet; - CGSize _lastRequestedSize; NSString* _lastTitle; IAvnMenu* _mainMenu; @@ -201,11 +200,7 @@ public: ret->Width = frame.size.width; ret->Height = frame.size.height; } - else - { - ret->Width = _lastRequestedSize.width; - ret->Height = _lastRequestedSize.height; - } + return S_OK; } } @@ -266,8 +261,10 @@ public: y = maxSize.height; } - _lastRequestedSize.width = x; - _lastRequestedSize.height = y; + if(!_shown) + { + BaseEvents->Resized(AvnSize{x,y}); + } [Window setContentSize:NSSize{x, y}]; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ab6e16d8dd..d2226503d2 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -656,7 +656,6 @@ namespace Avalonia.Controls if (PlatformImpl != null) { PlatformImpl.Resize(initialSize); - ClientSize = PlatformImpl.ClientSize; } } } From 92a29820b801a9b5fa6f996467cd81b942c3967a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 May 2021 22:49:53 +0100 Subject: [PATCH 13/55] remove redundant if. --- native/Avalonia.Native/src/OSX/window.mm | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index a4929c7c91..63c221d64e 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -194,12 +194,9 @@ public: if(ret == nullptr) return E_POINTER; - if(_shown) - { - auto frame = [View frame]; - ret->Width = frame.size.width; - ret->Height = frame.size.height; - } + auto frame = [View frame]; + ret->Width = frame.size.width; + ret->Height = frame.size.height; return S_OK; } From c42cdebef80cc15c0cfa541004d15f5df638171c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 May 2021 22:51:03 +0100 Subject: [PATCH 14/55] restore code. --- native/Avalonia.Native/src/OSX/window.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 63c221d64e..eb63cbd9f9 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -537,7 +537,7 @@ private: _decorations = SystemDecorationsFull; _transitioningWindowState = false; _inSetWindowState = false; - _lastWindowState = (AvnWindowState)-1; + _lastWindowState = Normal; WindowEvents = events; [Window setCanBecomeKeyAndMain]; [Window disableCursorRects]; From 75a7e365471faad4cea224e01845a5501e85c3be Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 21 May 2021 22:52:41 +0100 Subject: [PATCH 15/55] restore code --- src/Avalonia.Controls/Window.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index d2226503d2..700c3d9bad 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -653,10 +653,7 @@ namespace Avalonia.Controls { using (BeginAutoSizing()) { - if (PlatformImpl != null) - { - PlatformImpl.Resize(initialSize); - } + PlatformImpl?.Resize(initialSize); } } From 21b7b88b0f53afb715f76adcb1332a9a6863e874 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 24 May 2021 01:52:19 +0200 Subject: [PATCH 16/55] Cleanup and refactor leftover transition types. --- .../AnimatorDrivenTransition.cs | 5 +++ .../AnimatorTransitionObservable.cs | 9 +++-- src/Avalonia.Animation/Clock.cs | 4 -- src/Avalonia.Animation/ClockBase.cs | 5 +-- src/Avalonia.Animation/TransitionInstance.cs | 37 +++++++++++++++++- .../TransitionObservableBase.cs | 39 ++++++++++++------- .../Transitions/DoubleTransition.cs | 14 ------- .../Transitions/FloatTransition.cs | 13 +------ .../Transitions/IntegerTransition.cs | 13 +------ .../Transitions/BoxShadowsTransition.cs | 13 +------ .../Animation/Transitions/ColorTransition.cs | 12 ++++++ .../Transitions/CornerRadiusTransition.cs | 13 +------ .../Animation/Transitions/PointTransition.cs | 13 +------ .../Animation/Transitions/SizeTransition.cs | 13 +------ .../Transitions/ThicknessTransition.cs | 13 +------ .../Animation/Transitions/VectorTransition.cs | 13 +------ .../Animations/TransitionBenchmark.cs | 37 +++++++++++++----- 17 files changed, 120 insertions(+), 146 deletions(-) create mode 100644 src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs index 3f6ac47556..88c8ec5ec1 100644 --- a/src/Avalonia.Animation/AnimatorDrivenTransition.cs +++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs @@ -3,6 +3,11 @@ using Avalonia.Animation.Animators; namespace Avalonia.Animation { + /// + /// using an to transition between values. + /// + /// Type of the transitioned value. + /// Type of the animator. public abstract class AnimatorDrivenTransition : Transition where TAnimator : Animator, new() { private static readonly TAnimator s_animator = new TAnimator(); diff --git a/src/Avalonia.Animation/AnimatorTransitionObservable.cs b/src/Avalonia.Animation/AnimatorTransitionObservable.cs index 10c19bb62b..3cc185179b 100644 --- a/src/Avalonia.Animation/AnimatorTransitionObservable.cs +++ b/src/Avalonia.Animation/AnimatorTransitionObservable.cs @@ -4,6 +4,11 @@ using Avalonia.Animation.Easings; namespace Avalonia.Animation { + /// + /// Transition observable based on an producing a value. + /// + /// Type of the transitioned value. + /// Type of the animator. public class AnimatorTransitionObservable : TransitionObservableBase where TAnimator : Animator { private readonly TAnimator _animator; @@ -11,7 +16,7 @@ namespace Avalonia.Animation private readonly T _oldValue; private readonly T _newValue; - public AnimatorTransitionObservable(TAnimator animator, IObservable progress, Easing easing, T oldValue, T newValue) : base(progress) + public AnimatorTransitionObservable(TAnimator animator, IObservable progress, Easing easing, T oldValue, T newValue) : base(progress, easing) { _animator = animator; _easing = easing; @@ -21,8 +26,6 @@ namespace Avalonia.Animation protected override T ProduceValue(double progress) { - progress = _easing.Ease(progress); - return _animator.Interpolate(progress, _oldValue, _newValue); } } diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index 6c7ae8349a..5c2b7ce0dd 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/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 { diff --git a/src/Avalonia.Animation/ClockBase.cs b/src/Avalonia.Animation/ClockBase.cs index 9fa6a85a6c..c6e5a363be 100644 --- a/src/Avalonia.Animation/ClockBase.cs +++ b/src/Avalonia.Animation/ClockBase.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Reactive.Linq; -using System.Text; using Avalonia.Reactive; namespace Avalonia.Animation @@ -58,7 +55,7 @@ namespace Avalonia.Animation return _observable.Subscribe(observer); } - private class ClockObservable : LightweightObservableBase + private sealed class ClockObservable : LightweightObservableBase { public bool HasSubscriptions { get; private set; } public void Pulse(TimeSpan time) => PublishNext(time); diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index 58dd71e8a5..b522d1961e 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.ExceptionServices; using Avalonia.Reactive; using Avalonia.Utilities; @@ -13,7 +14,7 @@ namespace Avalonia.Animation private TimeSpan _delay; private TimeSpan _duration; private readonly IClock _baseClock; - private IClock _clock; + private TransitionClock _clock; public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration) { @@ -71,7 +72,7 @@ namespace Avalonia.Animation protected override void Subscribed() { - _clock = new Clock(_baseClock); + _clock = new TransitionClock(_baseClock); _timerSubscription = _clock.Subscribe(this); PublishNext(0.0d); } @@ -90,5 +91,37 @@ namespace Avalonia.Animation { TimerTick(value); } + + /// + /// TODO: This clock is still fairly expensive due to implementation. + /// + private sealed class TransitionClock : ClockBase, IObserver + { + private readonly IDisposable _parentSubscription; + + public TransitionClock(IClock parent) + { + _parentSubscription = parent.Subscribe(this); + } + + protected override void Stop() + { + _parentSubscription.Dispose(); + } + + void IObserver.OnNext(TimeSpan value) + { + Pulse(value); + } + + void IObserver.OnCompleted() + { + } + + void IObserver.OnError(Exception error) + { + ExceptionDispatchInfo.Capture(error).Throw(); + } + } } } diff --git a/src/Avalonia.Animation/TransitionObservableBase.cs b/src/Avalonia.Animation/TransitionObservableBase.cs index 9f4fb8093c..c4ac803135 100644 --- a/src/Avalonia.Animation/TransitionObservableBase.cs +++ b/src/Avalonia.Animation/TransitionObservableBase.cs @@ -1,45 +1,58 @@ using System; +using Avalonia.Animation.Easings; using Avalonia.Reactive; #nullable enable namespace Avalonia.Animation { + /// + /// Provides base for observables implementing transitions. + /// + /// Type of the transitioned value. public abstract class TransitionObservableBase : SingleSubscriberObservableBase, IObserver { + private readonly Easing _easing; private readonly IObservable _progress; private IDisposable? _progressSubscription; - protected TransitionObservableBase(IObservable progress) + protected TransitionObservableBase(IObservable progress, Easing easing) { _progress = progress; + _easing = easing; } - void IObserver.OnCompleted() + /// + /// Produces value at given progress time point. + /// + /// Transition progress. + protected abstract T ProduceValue(double progress); + + protected override void Subscribed() { - PublishCompleted(); + _progressSubscription = _progress.Subscribe(this); } - void IObserver.OnError(Exception error) + protected override void Unsubscribed() { - PublishError(error); + _progressSubscription?.Dispose(); } - void IObserver.OnNext(double value) + void IObserver.OnCompleted() { - PublishNext(ProduceValue(value)); + PublishCompleted(); } - protected override void Unsubscribed() + void IObserver.OnError(Exception error) { - _progressSubscription?.Dispose(); + PublishError(error); } - protected override void Subscribed() + void IObserver.OnNext(double value) { - _progressSubscription = _progress.Subscribe(this); - } + double progress = _easing.Ease(value); - protected abstract T ProduceValue(double progress); + PublishNext(ProduceValue(progress)); + } } } diff --git a/src/Avalonia.Animation/Transitions/DoubleTransition.cs b/src/Avalonia.Animation/Transitions/DoubleTransition.cs index 28f630eb98..7232d87863 100644 --- a/src/Avalonia.Animation/Transitions/DoubleTransition.cs +++ b/src/Avalonia.Animation/Transitions/DoubleTransition.cs @@ -1,5 +1,3 @@ -using System; -using System.Reactive.Linq; using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -10,16 +8,4 @@ namespace Avalonia.Animation public class DoubleTransition : AnimatorDrivenTransition { } - - public class DoubleTransitionOld : Transition - { - private static readonly DoubleAnimator s_animator = new DoubleAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, double oldValue, double newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } - } } diff --git a/src/Avalonia.Animation/Transitions/FloatTransition.cs b/src/Avalonia.Animation/Transitions/FloatTransition.cs index 37b644fa96..a96db8ba5b 100644 --- a/src/Avalonia.Animation/Transitions/FloatTransition.cs +++ b/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 /// /// Transition class that handles with types. /// - public class FloatTransition : Transition + public class FloatTransition : AnimatorDrivenTransition { - private static readonly FloatAnimator s_animator = new FloatAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, float oldValue, float newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Animation/Transitions/IntegerTransition.cs b/src/Avalonia.Animation/Transitions/IntegerTransition.cs index 223b2ba531..343da7b689 100644 --- a/src/Avalonia.Animation/Transitions/IntegerTransition.cs +++ b/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 /// /// Transition class that handles with types. /// - public class IntegerTransition : Transition + public class IntegerTransition : AnimatorDrivenTransition { - private static readonly Int32Animator s_animator = new Int32Animator(); - - /// - public override IObservable DoTransition(IObservable progress, int oldValue, int newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs index 008613fb40..8a070836e9 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs +++ b/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 /// /// Transition class that handles with type. /// - public class BoxShadowsTransition : Transition + public class BoxShadowsTransition : AnimatorDrivenTransition { - private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, BoxShadows oldValue, BoxShadows newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs new file mode 100644 index 0000000000..9b925a3779 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs @@ -0,0 +1,12 @@ +using Avalonia.Animation.Animators; +using Avalonia.Media; + +namespace Avalonia.Animation +{ + /// + /// Transition class that handles with type. + /// + public class ColorTransition : AnimatorDrivenTransition + { + } +} diff --git a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs index 9ffdf53694..62b77a64e1 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs +++ b/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 /// /// Transition class that handles with type. /// - public class CornerRadiusTransition : Transition + public class CornerRadiusTransition : AnimatorDrivenTransition { - private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, CornerRadius oldValue, CornerRadius newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs index fbe24c6d55..0985aaa8f8 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs +++ b/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 /// /// Transition class that handles with type. /// - public class PointTransition : Transition + public class PointTransition : AnimatorDrivenTransition { - private static readonly PointAnimator s_animator = new PointAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, Point oldValue, Point newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs index 464f83bec7..6e59fb3631 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs +++ b/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 /// /// Transition class that handles with type. /// - public class SizeTransition : Transition + public class SizeTransition : AnimatorDrivenTransition { - private static readonly SizeAnimator s_animator = new SizeAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, Size oldValue, Size newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs index 9fb3380780..f50929348f 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs +++ b/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 /// /// Transition class that handles with type. /// - public class ThicknessTransition : Transition + public class ThicknessTransition : AnimatorDrivenTransition { - private static readonly ThicknessAnimator s_animator = new ThicknessAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, Thickness oldValue, Thickness newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs index 5038117faa..596989fbae 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs +++ b/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 /// /// Transition class that handles with type. /// - public class VectorTransition : Transition + public class VectorTransition : AnimatorDrivenTransition { - private static readonly VectorAnimator s_animator = new VectorAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, Vector oldValue, Vector newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs index 16aad0c21b..aba59d4585 100644 --- a/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs @@ -1,9 +1,11 @@ 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; @@ -14,26 +16,29 @@ namespace Avalonia.Benchmarks.Animations { private readonly DoubleTransition _transition; private readonly DoubleTransitionOld _oldTransition; - private readonly int _frameCount; private readonly Subject _timeProducer; private readonly List _producedValues; + private readonly AddValueObserver _observer; + + [Params(10, 100)] + public int FrameCount { get; set; } public TransitionBenchmark() { - _frameCount = 100; - _oldTransition = new DoubleTransitionOld { - Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty + Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty }; _transition = new DoubleTransition { - Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty + Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty }; _timeProducer = new Subject(); - _producedValues = new List(_frameCount); + _producedValues = new List(FrameCount); + + _observer = new AddValueObserver(_producedValues); } [Benchmark(Baseline = true)] @@ -56,14 +61,26 @@ namespace Avalonia.Benchmarks.Animations _producedValues.Clear(); - using var transitionSub = transitionObs.Subscribe(new AddValueObserver(_producedValues)); + using var transitionSub = transitionObs.Subscribe(_observer); - for (int i = 0; i < _frameCount; i++) + for (int i = 0; i < FrameCount; i++) { - _timeProducer.OnNext(TimeSpan.FromMilliseconds(i).TotalSeconds); + _timeProducer.OnNext(i/1000d); } - Debug.Assert(_producedValues.Count == _frameCount); + Debug.Assert(_producedValues.Count == FrameCount); + } + + private class DoubleTransitionOld : Transition + { + private static readonly DoubleAnimator s_animator = new DoubleAnimator(); + + /// + public override IObservable DoTransition(IObservable progress, double oldValue, double newValue) + { + return progress + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); + } } private class AddValueObserver : IObserver From 183a20acb81514c7fbb4ca59969ef0189ece151a Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 24 May 2021 13:28:58 +0200 Subject: [PATCH 17/55] Avoid boxing nullable values in BindingValue. --- src/Avalonia.Base/Data/BindingValue.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index e79980518f..93948e54ee 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/src/Avalonia.Base/Data/BindingValue.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Utilities; @@ -358,6 +359,7 @@ namespace Avalonia.Data e); } + [Conditional("DEBUG")] private static void ValidateValue([AllowNull] T value) { if (value is UnsetValueType) From 3373849c2b2f33a1620b53faf5e206ebe3d9d17a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 24 May 2021 14:06:30 +0100 Subject: [PATCH 18/55] force osx shadow invalidation on programatic resize. --- native/Avalonia.Native/src/OSX/window.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index eb63cbd9f9..95a638ad72 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -265,6 +265,11 @@ public: [Window setContentSize:NSSize{x, y}]; + + // Forces the shadow to invalidate on resize. + [Window setContentView: nullptr]; + [Window setContentView: StandardContainer]; + return S_OK; } } From 9d9cfbd37a90561dd1ccae32810cddba4dd6224a Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 24 May 2021 18:51:09 +0200 Subject: [PATCH 19/55] Various memory optimizations. --- .../SceneGraph/DeferredDrawingContextImpl.cs | 7 +++-- .../Rendering/SceneGraph/RectangleNode.cs | 2 +- .../Rendering/SceneGraph/SceneBuilder.cs | 30 +++++++++++++++---- .../Rendering/SceneGraph/VisualNode.cs | 7 +++-- .../Rendering/ZIndexComparer.cs | 4 ++- 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index e6092574c5..3594cb59ee 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -408,9 +408,12 @@ namespace Avalonia.Rendering.SceneGraph var dirty = Owner.Layers.GetOrAdd(Owner._node.LayerRoot).Dirty; - foreach (var operation in Owner._node.DrawOperations) + var drawOperations = Owner._node.DrawOperations; + var drawOperationsCount = drawOperations.Count; + + for (var i = 0; i < drawOperationsCount; i++) { - dirty.Add(operation.Item.Bounds); + dirty.Add(drawOperations[i].Item.Bounds); } Owner._node = Node; diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index d0c4566485..187c1da0a9 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -84,7 +84,7 @@ namespace Avalonia.Rendering.SceneGraph return transform == Transform && Equals(brush, Brush) && Equals(Pen, pen) && - Media.BoxShadows.Equals(BoxShadows, boxShadows) && + BoxShadows.Equals(boxShadows) && rect.Equals(Rect); } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 7d5d62a091..56f05db04e 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using System.Collections.Generic; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Threading; @@ -247,11 +247,26 @@ namespace Avalonia.Rendering.SceneGraph if (forceRecurse) { - foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)) + var visualChildren = (IList) visual.VisualChildren; + + if (visualChildren.Count == 1) { - var childNode = GetOrCreateChildNode(scene, child, node); + var childNode = GetOrCreateChildNode(scene, visualChildren[0], node); Update(context, scene, (VisualNode)childNode, clip, forceRecurse); } + else if (visualChildren.Count > 1) + { + var sortedChildren = new IVisual[visualChildren.Count]; + visualChildren.CopyTo(sortedChildren, 0); + + Array.Sort(sortedChildren, ZIndexComparer.ComparisonInstance); + + foreach (var child in sortedChildren) + { + var childNode = GetOrCreateChildNode(scene, child, node); + Update(context, scene, (VisualNode)childNode, clip, forceRecurse); + } + } node.SubTreeUpdated = true; contextImpl.TrimChildren(); @@ -308,13 +323,17 @@ namespace Avalonia.Rendering.SceneGraph private static void Deindex(Scene scene, VisualNode node) { - foreach (VisualNode child in node.Children) + var nodeChildren = node.Children; + var nodeChildrenCount = nodeChildren.Count; + + for (var i = 0; i < nodeChildrenCount; i++) { - if (child is VisualNode visual) + if (nodeChildren[i] is VisualNode visual) { Deindex(scene, visual); } } + scene.Remove(node); node.SubTreeUpdated = true; @@ -323,7 +342,6 @@ namespace Avalonia.Rendering.SceneGraph node.Visual.TransformedBounds = null; - if (node.LayerRoot == node.Visual && node.Visual != scene.Root.Visual) { scene.Layers.Remove(node.LayerRoot); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index 8fb6b2542a..d0439feed2 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -365,9 +365,12 @@ namespace Avalonia.Rendering.SceneGraph { var result = new Rect(); - foreach (var operation in DrawOperations) + if (_drawOperations != null) { - result = result.Union(operation.Item.Bounds); + foreach (var operation in _drawOperations) + { + result = result.Union(operation.Item.Bounds); + } } _bounds = result; diff --git a/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs b/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs index 3ed076f22e..e9700c70a5 100644 --- a/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs +++ b/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.VisualTree; namespace Avalonia.Rendering @@ -6,6 +7,7 @@ namespace Avalonia.Rendering public class ZIndexComparer : IComparer { public static readonly ZIndexComparer Instance = new ZIndexComparer(); + public static readonly Comparison ComparisonInstance = Instance.Compare; public int Compare(IVisual x, IVisual y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0); } From a0a039860cbff4f7c6f9b4addc7880d4df4de6cf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 25 May 2021 11:24:56 +0100 Subject: [PATCH 20/55] fix shadow ensuring the window frame and avalonia standard container are both resized. --- native/Avalonia.Native/src/OSX/window.mm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 95a638ad72..870345e543 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -263,13 +263,9 @@ public: BaseEvents->Resized(AvnSize{x,y}); } + [StandardContainer setFrameSize:NSSize{x,y}]; [Window setContentSize:NSSize{x, y}]; - - // Forces the shadow to invalidate on resize. - [Window setContentView: nullptr]; - [Window setContentView: StandardContainer]; - return S_OK; } } @@ -2250,6 +2246,7 @@ protected: { if (Window != nullptr) { + [StandardContainer setFrameSize:NSSize{x,y}]; [Window setContentSize:NSSize{x, y}]; [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))]; From 3f57356a2794e658457e9a8d08f764ecf5ad6a6d Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 26 May 2021 22:46:39 +0100 Subject: [PATCH 21/55] Fixed documentation site URLs --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 67b706f428..7959bc5540 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ To see the status of some of our features, please see our [Roadmap](https://gith ## 🚀 Getting Started -The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](https://avaloniaui.net/docs/quickstart/create-new-project). +The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](https://docs.avaloniaui.net/docs/getting-started). Avalonia is delivered via NuGet package manager. You can find the packages here: https://www.nuget.org/packages/Avalonia/ @@ -52,7 +52,7 @@ We also have a [nightly build](https://github.com/AvaloniaUI/Avalonia/wiki/Using ## Documentation -Documentation can be found on our website at https://avaloniaui.net/docs/. We also have a [tutorial](https://avaloniaui.net/docs/tutorial/) over there for newcomers. +Documentation can be found at https://docs.avaloniaui.net. We also have a [tutorial](https://docs.avaloniaui.net/docs/getting-started/programming-with-avalonia) over there for newcomers. ## Building and Using From 35d0c688ca5a5e28e96b207a6535859563d73226 Mon Sep 17 00:00:00 2001 From: ShadowsInRain Date: Fri, 28 May 2021 19:50:33 +0500 Subject: [PATCH 22/55] Fix a memory leak in StreamNode. --- src/Avalonia.Base/Data/Core/StreamNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/Core/StreamNode.cs b/src/Avalonia.Base/Data/Core/StreamNode.cs index 023999f5c5..e868b71fcd 100644 --- a/src/Avalonia.Base/Data/Core/StreamNode.cs +++ b/src/Avalonia.Base/Data/Core/StreamNode.cs @@ -20,7 +20,7 @@ namespace Avalonia.Data.Core protected override void StartListeningCore(WeakReference reference) { - GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged); + _subscription = GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged); } protected override void StopListeningCore() From 78f7726289cf8a002c36f33ac49153e33407528b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 28 May 2021 17:09:14 +0200 Subject: [PATCH 23/55] Don't activate window when extending client area. Fixes #5988 --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 082aca1109..3a3342fd14 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -900,7 +900,7 @@ namespace Avalonia.Win32 IntPtr.Zero, rcWindow.left, rcWindow.top, rcClient.Width, rcClient.Height, - SetWindowPosFlags.SWP_FRAMECHANGED); + SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOACTIVATE); if (_isClientAreaExtended && WindowState != WindowState.FullScreen) { From 81fd48a2f77cd7b181bcd630b90c2b4e15d1b596 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 May 2021 01:54:53 -0400 Subject: [PATCH 24/55] Create FilterViewModel and move abstract filtering to it --- .../ViewModels/ControlDetailsViewModel.cs | 12 +- .../Diagnostics/ViewModels/FilterViewModel.cs | 124 ++++++++++++++++++ .../ViewModels/TreePageViewModel.cs | 83 +----------- 3 files changed, 131 insertions(+), 88 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 32592559e5..6d0345d6e0 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -357,17 +357,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 diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs new file mode 100644 index 0000000000..2d7d2a769c --- /dev/null +++ b/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 _errors = new Dictionary(); + private string _propertyFilter = 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(PropertyFilter))) + { + ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter))); + } + } + + _processedFilter = PropertyFilter.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(PropertyFilter)] = exception.Message; + ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter))); + } + } + + public string PropertyFilter + { + get => _propertyFilter; + set + { + if (RaiseAndSetIfChanged(ref _propertyFilter, 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 ErrorsChanged; + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index 6b779cd6ac..28df4d06f0 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -1,29 +1,27 @@ 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 _errors = new Dictionary(); 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(); } public MainViewModel MainView { get; } + public FilterViewModel PropertiesFilter { get; } + public TreeNode[] Nodes { get; protected set; } public TreeNode SelectedNode @@ -61,63 +59,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 +135,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 ErrorsChanged; } } From 0efda32774174f3204446ef7ef8b151deea0f70c Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 May 2021 01:57:25 -0400 Subject: [PATCH 25/55] Create FilterTextBox --- .../Diagnostics/Controls/FilterTextBox.axaml | 88 +++++++++++++++++++ .../Diagnostics/Controls/FilterTextBox.cs | 54 ++++++++++++ .../Diagnostics/ViewModels/FilterViewModel.cs | 18 ++-- .../Diagnostics/Views/ControlDetailsView.xaml | 28 +++--- .../Diagnostics/Views/MainWindow.xaml | 1 + 5 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml new file mode 100644 index 0000000000..0cacc0031d --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs new file mode 100644 index 0000000000..f81ef56549 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs @@ -0,0 +1,54 @@ +using System; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Styling; + +namespace Avalonia.Diagnostics.Controls +{ + internal class FilterTextBox : TextBox, IStyleable + { + public static readonly DirectProperty UseRegexFilterProperty = + AvaloniaProperty.RegisterDirect(nameof(UseRegexFilter), + o => o.UseRegexFilter, (o, v) => o.UseRegexFilter = v, + defaultBindingMode: BindingMode.TwoWay); + + + public static readonly DirectProperty UseCaseSensitiveFilterProperty = + AvaloniaProperty.RegisterDirect(nameof(UseCaseSensitiveFilter), + o => o.UseCaseSensitiveFilter, (o, v) => o.UseCaseSensitiveFilter = v, + defaultBindingMode: BindingMode.TwoWay); + + + public static readonly DirectProperty UseWholeWordFilterProperty = + AvaloniaProperty.RegisterDirect(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); + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs index 2d7d2a769c..f3145a2470 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs @@ -9,7 +9,7 @@ namespace Avalonia.Diagnostics.ViewModels internal class FilterViewModel : ViewModelBase, INotifyDataErrorInfo { private readonly Dictionary _errors = new Dictionary(); - private string _propertyFilter = string.Empty; + private string _propertyString = string.Empty; private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter; private string _processedFilter; private Regex _filterRegex; @@ -25,13 +25,13 @@ namespace Avalonia.Diagnostics.ViewModels { void ClearError() { - if (_errors.Remove(nameof(PropertyFilter))) + if (_errors.Remove(nameof(FilterString))) { - ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter))); + ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(FilterString))); } } - _processedFilter = PropertyFilter.Trim(); + _processedFilter = FilterString.Trim(); try { @@ -52,17 +52,17 @@ namespace Avalonia.Diagnostics.ViewModels } catch (Exception exception) { - _errors[nameof(PropertyFilter)] = exception.Message; - ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter))); + _errors[nameof(FilterString)] = exception.Message; + ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(FilterString))); } } - public string PropertyFilter + public string FilterString { - get => _propertyFilter; + get => _propertyString; set { - if (RaiseAndSetIfChanged(ref _propertyFilter, value)) + if (RaiseAndSetIfChanged(ref _propertyString, value)) { UpdateFilterRegex(); RefreshFilter?.Invoke(this, EventArgs.Empty); diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index ab651b2a06..e6e19643a2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/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 @@ - - - - - - + + + + + From 92cd1932378a1e26b82d7bda074e20eef95fff47 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 May 2021 02:25:46 -0400 Subject: [PATCH 26/55] Improve setters filter --- .../ViewModels/ControlDetailsViewModel.cs | 21 +++---------------- .../ViewModels/TreePageViewModel.cs | 13 ++++++------ .../Diagnostics/Views/ControlDetailsView.xaml | 8 ++++++- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 6d0345d6e0..b1ff8ae98d 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -19,7 +19,6 @@ namespace Avalonia.Diagnostics.ViewModels private readonly IVisual _control; private readonly IDictionary> _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; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index 28df4d06f0..85a7cb69a3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -16,12 +16,17 @@ namespace Avalonia.Diagnostics.ViewModels 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 @@ -29,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(); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index e6e19643a2..4b37438993 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -67,7 +67,13 @@ - + From f83d8e55ea80a73da2b2a24377a30f082684b0b9 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 May 2021 02:25:56 -0400 Subject: [PATCH 27/55] Improve events filter --- .../Diagnostics/Controls/FilterTextBox.cs | 2 -- .../ViewModels/EventsPageViewModel.cs | 25 ++++--------------- .../Diagnostics/Views/EventsPageView.xaml | 9 ++++++- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs index f81ef56549..66fad557d5 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs @@ -12,13 +12,11 @@ namespace Avalonia.Diagnostics.Controls o => o.UseRegexFilter, (o, v) => o.UseRegexFilter = v, defaultBindingMode: BindingMode.TwoWay); - public static readonly DirectProperty UseCaseSensitiveFilterProperty = AvaloniaProperty.RegisterDirect(nameof(UseCaseSensitiveFilter), o => o.UseCaseSensitiveFilter, (o, v) => o.UseCaseSensitiveFilter = v, defaultBindingMode: BindingMode.TwoWay); - public static readonly DirectProperty UseWholeWordFilterProperty = AvaloniaProperty.RegisterDirect(nameof(UseWholeWordFilter), o => o.UseWholeWordFilter, (o, v) => o.UseWholeWordFilter = v, diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs index dd85fcf14c..7a157dec62 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs +++ b/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 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) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml index d16fc40aac..a9c2688a18 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml +++ b/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"> @@ -35,7 +36,13 @@ - + From 67812f4e9023e573c28dd55b0dfbfa2efc09a78b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 29 May 2021 15:14:17 -0400 Subject: [PATCH 28/55] Changes after review --- .../Diagnostics/Controls/FilterTextBox.axaml | 15 ++++++++++++--- .../Diagnostics/ViewModels/FilterViewModel.cs | 6 +++--- src/Avalonia.Themes.Default/TextBox.xaml | 7 ++++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml index 0cacc0031d..52720e652f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml @@ -17,9 +17,13 @@