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..870345e543 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]; @@ -125,10 +127,6 @@ public: _shown = true; - dispatch_async(dispatch_get_main_queue(), ^{ - [Window updateShadow]; - }); - return S_OK; } } @@ -195,9 +193,11 @@ public: { if(ret == nullptr) return E_POINTER; + auto frame = [View frame]; ret->Width = frame.size.width; ret->Height = frame.size.height; + return S_OK; } } @@ -258,6 +258,12 @@ public: y = maxSize.height; } + if(!_shown) + { + BaseEvents->Resized(AvnSize{x,y}); + } + + [StandardContainer setFrameSize:NSSize{x,y}]; [Window setContentSize:NSSize{x, y}]; return S_OK; @@ -1842,19 +1848,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 +2006,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _lastScaling = [self backingScaleFactor]; [self setOpaque:NO]; [self setBackgroundColor: [NSColor clearColor]]; - [self invalidateShadow]; _isExtended = false; return self; } @@ -2254,6 +2246,7 @@ protected: { if (Window != nullptr) { + [StandardContainer setFrameSize:NSSize{x,y}]; [Window setContentSize:NSSize{x, y}]; [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))]; 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 diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml b/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml index e15637aa0f..f0e079ad91 100644 --- a/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml +++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.axaml @@ -84,13 +84,13 @@ - - - diff --git a/samples/RenderDemo/Pages/TransitionsPage.xaml b/samples/RenderDemo/Pages/TransitionsPage.xaml index f9f69fb341..1985074b0f 100644 --- a/samples/RenderDemo/Pages/TransitionsPage.xaml +++ b/samples/RenderDemo/Pages/TransitionsPage.xaml @@ -141,6 +141,39 @@ + + + + + + + + @@ -166,6 +199,9 @@ + + + diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 6f601a3e13..0e1882ce75 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -5,6 +5,7 @@ using Avalonia.Animation.Animators; using Avalonia.Animation.Utils; using Avalonia.Data; using Avalonia.Reactive; +using JetBrains.Annotations; namespace Avalonia.Animation { @@ -45,8 +46,9 @@ namespace Avalonia.Animation _onCompleteAction = OnComplete; _interpolator = Interpolator; _baseClock = baseClock; - _neutralValue = (T)_targetControl.GetValue(_animator.Property); + control.PropertyChanged += ControlPropertyChanged; + UpdateNeutralValue(); FetchProperties(); } @@ -216,5 +218,22 @@ namespace Avalonia.Animation } } } + + private void UpdateNeutralValue() + { + var property = _animator.Property; + var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue); + + _neutralValue = baseValue != AvaloniaProperty.UnsetValue ? + (T)baseValue : (T)_targetControl.GetValue(property); + } + + private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == _animator.Property && e.Priority > BindingPriority.Animation) + { + UpdateNeutralValue(); + } + } } } diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs new file mode 100644 index 0000000000..88c8ec5ec1 --- /dev/null +++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs @@ -0,0 +1,20 @@ +using System; +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(); + + public override IObservable DoTransition(IObservable progress, T oldValue, T newValue) + { + return new AnimatorTransitionObservable(s_animator, progress, Easing, oldValue, newValue); + } + } +} diff --git a/src/Avalonia.Animation/AnimatorTransitionObservable.cs b/src/Avalonia.Animation/AnimatorTransitionObservable.cs new file mode 100644 index 0000000000..3cc185179b --- /dev/null +++ b/src/Avalonia.Animation/AnimatorTransitionObservable.cs @@ -0,0 +1,32 @@ +using System; +using Avalonia.Animation.Animators; +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; + private readonly Easing _easing; + private readonly T _oldValue; + private readonly T _newValue; + + public AnimatorTransitionObservable(TAnimator animator, IObservable progress, Easing easing, T oldValue, T newValue) : base(progress, easing) + { + _animator = animator; + _easing = easing; + _oldValue = oldValue; + _newValue = newValue; + } + + protected override T ProduceValue(double progress) + { + return _animator.Interpolate(progress, _oldValue, _newValue); + } + } +} diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index bea6c75982..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 { @@ -10,10 +6,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/ClockBase.cs b/src/Avalonia.Animation/ClockBase.cs index a2b29e728e..c6e5a363be 100644 --- a/src/Avalonia.Animation/ClockBase.cs +++ b/src/Avalonia.Animation/ClockBase.cs @@ -1,16 +1,11 @@ using System; -using System.Collections.Generic; -using System.Reactive.Linq; -using System.Text; using Avalonia.Reactive; namespace Avalonia.Animation { public class ClockBase : IClock { - private ClockObservable _observable; - - private IObservable _connectedObservable; + private readonly ClockObservable _observable; private TimeSpan? _previousTime; private TimeSpan _internalTime; @@ -18,7 +13,6 @@ namespace Avalonia.Animation protected ClockBase() { _observable = new ClockObservable(); - _connectedObservable = _observable.Publish().RefCount(); } protected bool HasSubscriptions => _observable.HasSubscriptions; @@ -58,10 +52,10 @@ namespace Avalonia.Animation public IDisposable Subscribe(IObserver observer) { - return _connectedObservable.Subscribe(observer); + 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/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..b522d1961e 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -1,8 +1,5 @@ -using Avalonia.Metadata; using System; -using System.Reactive.Linq; -using Avalonia.Animation.Easings; -using Avalonia.Animation.Utils; +using System.Runtime.ExceptionServices; using Avalonia.Reactive; using Avalonia.Utilities; @@ -11,13 +8,13 @@ namespace Avalonia.Animation /// /// Handles the timing and lifetime of a . /// - internal class TransitionInstance : SingleSubscriberObservableBase + internal class TransitionInstance : SingleSubscriberObservableBase, IObserver { private IDisposable _timerSubscription; private TimeSpan _delay; private TimeSpan _duration; private readonly IClock _baseClock; - private IClock _clock; + private TransitionClock _clock; public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration) { @@ -75,9 +72,56 @@ namespace Avalonia.Animation protected override void Subscribed() { - _clock = new Clock(_baseClock); - _timerSubscription = _clock.Subscribe(TimerTick); + _clock = new TransitionClock(_baseClock); + _timerSubscription = _clock.Subscribe(this); PublishNext(0.0d); } + + void IObserver.OnCompleted() + { + PublishCompleted(); + } + + void IObserver.OnError(Exception error) + { + PublishError(error); + } + + void IObserver.OnNext(TimeSpan value) + { + 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 new file mode 100644 index 0000000000..c4ac803135 --- /dev/null +++ b/src/Avalonia.Animation/TransitionObservableBase.cs @@ -0,0 +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, Easing easing) + { + _progress = progress; + _easing = easing; + } + + /// + /// Produces value at given progress time point. + /// + /// Transition progress. + protected abstract T ProduceValue(double progress); + + protected override void Subscribed() + { + _progressSubscription = _progress.Subscribe(this); + } + + protected override void Unsubscribed() + { + _progressSubscription?.Dispose(); + } + + void IObserver.OnCompleted() + { + PublishCompleted(); + } + + void IObserver.OnError(Exception error) + { + PublishError(error); + } + + void IObserver.OnNext(double value) + { + double progress = _easing.Ease(value); + + PublishNext(ProduceValue(progress)); + } + } +} diff --git a/src/Avalonia.Animation/Transitions/DoubleTransition.cs b/src/Avalonia.Animation/Transitions/DoubleTransition.cs index d5bb1aac20..7232d87863 100644 --- a/src/Avalonia.Animation/Transitions/DoubleTransition.cs +++ b/src/Avalonia.Animation/Transitions/DoubleTransition.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,15 +5,7 @@ namespace Avalonia.Animation /// /// Transition class that handles with types. /// - public class DoubleTransition : Transition + public class DoubleTransition : AnimatorDrivenTransition { - 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.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/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) 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() 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.Base/Metadata/NullableAttributes.cs b/src/Avalonia.Base/Metadata/NullableAttributes.cs index 91f5e81863..b6f0f3a47c 100644 --- a/src/Avalonia.Base/Metadata/NullableAttributes.cs +++ b/src/Avalonia.Base/Metadata/NullableAttributes.cs @@ -1,6 +1,5 @@ #pragma warning disable MA0048 // File name must match type name #define INTERNAL_NULLABLE_ATTRIBUTES -#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 // https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs @@ -10,6 +9,7 @@ namespace System.Diagnostics.CodeAnalysis { +#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 /// Specifies that null is allowed as an input even if the corresponding type disallows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] #if INTERNAL_NULLABLE_ATTRIBUTES @@ -136,5 +136,82 @@ namespace System.Diagnostics.CodeAnalysis /// Gets the condition parameter value. public bool ParameterValue { get; } } -} +#endif // NETSTANDARD2_0 attributes + +#if NETSTANDARD2_1 || NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 + /// + /// Specifies that the method or property will ensure that the listed field and property members have + /// not- values. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public #endif + sealed class MemberNotNullAttribute : Attribute + { + /// Gets field or property member names. + public string[] Members { get; } + + /// Initializes the attribute with a field or property member. + /// The field or property member that is promised to be not-null. + public MemberNotNullAttribute(string member) + { + Members = new[] { member }; + } + + /// Initializes the attribute with the list of field and property members. + /// The list of field and property members that are promised to be not-null. + public MemberNotNullAttribute(params string[] members) + { + Members = members; + } + } + + /// + /// Specifies that the method or property will ensure that the listed field and property members have + /// non- values when returning with the specified return value condition. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class MemberNotNullWhenAttribute : Attribute + { + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } + + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, + /// the associated parameter will not be . + /// + /// The field or property member that is promised to be not-. + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// + /// The return value condition. If the method returns this value, + /// the associated parameter will not be . + /// + /// The list of field and property members that are promised to be not-null. + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + } +#endif // NETSTANDARD2_1 attributes +} + 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/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index 8d893154eb..43bc7d1df9 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Interactivity; using System; using System.Collections.Generic; @@ -88,7 +89,8 @@ namespace Avalonia.Controls /// public static readonly DirectProperty SelectedDateProperty = AvaloniaProperty.RegisterDirect(nameof(SelectedDate), - x => x.SelectedDate, (x, v) => x.SelectedDate = v); + x => x.SelectedDate, (x, v) => x.SelectedDate = v, + defaultBindingMode: BindingMode.TwoWay); // Template Items private Button _flyoutButton; diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index d0cf772c01..6b3f66912f 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; +using Avalonia.Data; using System; using System.Globalization; @@ -44,7 +45,8 @@ namespace Avalonia.Controls /// public static readonly DirectProperty SelectedTimeProperty = AvaloniaProperty.RegisterDirect(nameof(SelectedTime), - x => x.SelectedTime, (x, v) => x.SelectedTime = v); + x => x.SelectedTime, (x, v) => x.SelectedTime = v, + defaultBindingMode: BindingMode.TwoWay); // Template Items private TimePickerPresenter _presenter; diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index 0a01767a07..57861163d6 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -3,6 +3,7 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using System; +using Avalonia.Media.Immutable; namespace Avalonia.Controls { @@ -90,7 +91,7 @@ namespace Avalonia.Controls } else { - _borderRenderHelper.Render(context, Bounds.Size, new Thickness(), CornerRadius, new SolidColorBrush(Material.FallbackColor), null, default); + _borderRenderHelper.Render(context, Bounds.Size, new Thickness(), CornerRadius, new ImmutableSolidColorBrush(Material.FallbackColor), null, default); } } 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.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 3bec46a9ac..ff63e5644f 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -6,6 +6,7 @@ using Avalonia.Metadata; using Avalonia.Threading; using Avalonia.VisualTree; using Avalonia.Layout; +using Avalonia.Media.Immutable; namespace Avalonia.Controls.Presenters { @@ -360,32 +361,31 @@ namespace Avalonia.Controls.Presenters RenderInternal(context); - if (selectionStart == selectionEnd) + if (selectionStart == selectionEnd && _caretBlink) { - var caretBrush = CaretBrush; + var caretBrush = CaretBrush?.ToImmutable(); if (caretBrush is null) { - var backgroundColor = (Background as SolidColorBrush)?.Color; + var backgroundColor = (Background as ISolidColorBrush)?.Color; if (backgroundColor.HasValue) { byte red = (byte)~(backgroundColor.Value.R); byte green = (byte)~(backgroundColor.Value.G); byte blue = (byte)~(backgroundColor.Value.B); - caretBrush = new SolidColorBrush(Color.FromRgb(red, green, blue)); + caretBrush = new ImmutableSolidColorBrush(Color.FromRgb(red, green, blue)); } else + { caretBrush = Brushes.Black; + } } - if (_caretBlink) - { - var (p1, p2) = GetCaretPoints(); - context.DrawLine( - new Pen(caretBrush, 1), - p1, p2); - } + var (p1, p2) = GetCaretPoints(); + context.DrawLine( + new ImmutablePen(caretBrush, 1), + p1, p2); } } diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs index 1d655bb691..74d804f2bf 100644 --- a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs @@ -52,8 +52,7 @@ namespace Avalonia.Controls.Primitives { if (InputPassThroughElement is object) { - var p = point.Transform(this.TransformToVisual(VisualRoot)!.Value); - var hit = VisualRoot.GetVisualAt(p, x => x != this); + var hit = VisualRoot.GetVisualAt(point, x => x != this); if (hit is object) { diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 0b7595ec9a..0d1d9e3ffe 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Collections; using Avalonia.Media; +using Avalonia.Media.Immutable; #nullable enable @@ -199,8 +200,29 @@ namespace Avalonia.Controls.Shapes if (geometry != null) { - var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset), - StrokeLineCap, StrokeJoin); + var stroke = Stroke; + + ImmutablePen? pen = null; + + if (stroke != null) + { + var strokeDashArray = StrokeDashArray; + + ImmutableDashStyle? dashStyle = null; + + if (strokeDashArray != null && strokeDashArray.Count > 0) + { + dashStyle = new ImmutableDashStyle(strokeDashArray, StrokeDashOffset); + } + + pen = new ImmutablePen( + stroke.ToImmutable(), + StrokeThickness, + dashStyle, + StrokeLineCap, + StrokeJoin); + } + context.DrawGeometry(Fill, pen, geometry); } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 3c221cbf27..1bee15bccd 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -119,9 +119,9 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(RevealPassword)); public static readonly DirectProperty CanCutProperty = - AvaloniaProperty.RegisterDirect( - nameof(CanCut), - o => o.CanCut); + AvaloniaProperty.RegisterDirect( + nameof(CanCut), + o => o.CanCut); public static readonly DirectProperty CanCopyProperty = AvaloniaProperty.RegisterDirect( @@ -129,9 +129,21 @@ namespace Avalonia.Controls o => o.CanCopy); public static readonly DirectProperty CanPasteProperty = - AvaloniaProperty.RegisterDirect( - nameof(CanPaste), - o => o.CanPaste); + AvaloniaProperty.RegisterDirect( + nameof(CanPaste), + o => o.CanPaste); + + public static readonly StyledProperty IsUndoEnabledProperty = + AvaloniaProperty.Register( + nameof(IsUndoEnabled), + defaultValue: true); + + public static readonly DirectProperty UndoLimitProperty = + AvaloniaProperty.RegisterDirect( + nameof(UndoLimit), + o => o.UndoLimit, + (o, v) => o.UndoLimit = v, + unsetValue: -1); struct UndoRedoState : IEquatable { @@ -218,7 +230,7 @@ namespace Avalonia.Controls value = CoerceCaretIndex(value); SetAndRaise(CaretIndexProperty, ref _caretIndex, value); UndoRedoState state; - if (_undoRedoHelper.TryGetLastState(out state) && state.Text == Text) + if (IsUndoEnabled && _undoRedoHelper.TryGetLastState(out state) && state.Text == Text) _undoRedoHelper.UpdateLastState(); } } @@ -316,7 +328,7 @@ namespace Avalonia.Controls SelectionEnd = CoerceCaretIndex(SelectionEnd, value); CaretIndex = CoerceCaretIndex(caretIndex, value); - if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing) + if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) { _undoRedoHelper.Clear(); } @@ -329,7 +341,7 @@ namespace Avalonia.Controls get { return GetSelection(); } set { - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); if (string.IsNullOrEmpty(value)) { DeleteSelection(); @@ -338,7 +350,7 @@ namespace Avalonia.Controls { HandleTextInput(value); } - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); } } @@ -446,6 +458,36 @@ namespace Avalonia.Controls private set { SetAndRaise(CanPasteProperty, ref _canPaste, value); } } + /// + /// Property for determining whether undo/redo is enabled + /// + public bool IsUndoEnabled + { + get { return GetValue(IsUndoEnabledProperty); } + set { SetValue(IsUndoEnabledProperty, value); } + } + + public int UndoLimit + { + get { return _undoRedoHelper.Limit; } + set + { + if (_undoRedoHelper.Limit != value) + { + // can't use SetAndRaise due to using _undoRedoHelper.Limit + // (can't send a ref of a property to SetAndRaise), + // so use RaisePropertyChanged instead. + var oldValue = _undoRedoHelper.Limit; + _undoRedoHelper.Limit = value; + RaisePropertyChanged(UndoLimitProperty, oldValue, value); + } + // from docs at + // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: + // "Setting UndoLimit clears the undo queue." + _undoRedoHelper.Clear(); + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); @@ -465,6 +507,15 @@ namespace Avalonia.Controls UpdatePseudoclasses(); UpdateCommandStates(); } + else if (change.Property == IsUndoEnabledProperty && change.NewValue.GetValueOrDefault() == false) + { + // from docs at + // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: + // "Setting this property to false clears the undo stack. + // Therefore, if you disable undo and then re-enable it, undo commands still do not work + // because the undo stack was emptied when you disabled undo." + _undoRedoHelper.Clear(); + } } private void UpdateCommandStates() @@ -551,7 +602,10 @@ namespace Avalonia.Controls SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); CaretIndex += input.Length; ClearSelection(); - _undoRedoHelper.DiscardRedo(); + if (IsUndoEnabled) + { + _undoRedoHelper.DiscardRedo(); + } } } @@ -570,10 +624,10 @@ namespace Avalonia.Controls var text = GetSelection(); if (text is null) return; - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); Copy(); DeleteSelection(); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); } public async void Copy() @@ -591,9 +645,9 @@ namespace Avalonia.Controls if (text is null) return; - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); HandleTextInput(text); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); } protected override void OnKeyDown(KeyEventArgs e) @@ -638,7 +692,7 @@ namespace Avalonia.Controls Paste(); handled = true; } - else if (Match(keymap.Undo)) + else if (Match(keymap.Undo) && IsUndoEnabled) { try { @@ -652,7 +706,7 @@ namespace Avalonia.Controls handled = true; } - else if (Match(keymap.Redo)) + else if (Match(keymap.Redo) && IsUndoEnabled) { try { @@ -752,7 +806,7 @@ namespace Avalonia.Controls break; case Key.Back: - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); if (hasWholeWordModifiers && SelectionStart == SelectionEnd) { SetSelectionForControlBackspace(); @@ -776,13 +830,13 @@ namespace Avalonia.Controls CaretIndex -= removedCharacters; ClearSelection(); } - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; break; case Key.Delete: - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); if (hasWholeWordModifiers && SelectionStart == SelectionEnd) { SetSelectionForControlDelete(); @@ -804,7 +858,7 @@ namespace Avalonia.Controls SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters)); } - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; break; @@ -812,9 +866,9 @@ namespace Avalonia.Controls case Key.Enter: if (AcceptsReturn) { - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); HandleTextInput(NewLine); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; } @@ -823,9 +877,9 @@ namespace Avalonia.Controls case Key.Tab: if (AcceptsTab) { - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); HandleTextInput("\t"); - _undoRedoHelper.Snapshot(); + SnapshotUndoRedo(); handled = true; } else @@ -1251,5 +1305,13 @@ namespace Avalonia.Controls ClearSelection(); } } + + private void SnapshotUndoRedo() + { + if (IsUndoEnabled) + { + _undoRedoHelper.Snapshot(); + } + } } } diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs index 6ea5277a55..237bc2ce1d 100644 --- a/src/Avalonia.Controls/TickBar.cs +++ b/src/Avalonia.Controls/TickBar.cs @@ -1,6 +1,7 @@ using Avalonia.Collections; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Utilities; namespace Avalonia.Controls @@ -295,7 +296,7 @@ namespace Avalonia.Controls endPoint = pt; } - var pen = new Pen(Fill, 1.0d); + var pen = new ImmutablePen(Fill?.ToImmutable(), 1.0d); // Is it Vertical? if (Placement == TickBarPlacement.Left || Placement == TickBarPlacement.Right) diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index 3128753781..f8ab58d46e 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Platform; using Avalonia.Utilities; @@ -114,9 +115,9 @@ namespace Avalonia.Controls.Utils var borderThickness = _borderThickness.Top; IPen pen = null; - if (borderThickness > 0) + if (borderBrush != null && borderThickness > 0) { - pen = new Pen(borderBrush, borderThickness); + pen = new ImmutablePen(borderBrush.ToImmutable(), borderThickness); } var rect = new Rect(_size); diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index 17cf681f15..7374f20a0c 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -22,6 +22,10 @@ namespace Avalonia.Controls.Utils private LinkedListNode _currentNode; + /// + /// Maximum number of states this helper can store for undo/redo. + /// If -1, no limit is imposed. + /// public int Limit { get; set; } = 10; public UndoRedoHelper(IUndoRedoHost host) @@ -54,7 +58,10 @@ namespace Avalonia.Controls.Utils public bool HasState => _currentNode != null; public void UpdateLastState(TState state) { - _states.Last.Value = state; + if (_states.Last != null) + { + _states.Last.Value = state; + } } public void UpdateLastState() @@ -86,7 +93,7 @@ namespace Avalonia.Controls.Utils DiscardRedo(); _states.AddLast(current); _currentNode = _states.Last; - if (_states.Count > Limit) + if (Limit != -1 && _states.Count > Limit) _states.RemoveFirst(); } } 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; } diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index db8684747d..35de491668 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -3,12 +3,16 @@ netstandard2.0 Avalonia Avalonia.Diagnostics + enable %(Filename) + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml new file mode 100644 index 0000000000..b7995c38e3 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs new file mode 100644 index 0000000000..66fad557d5 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs @@ -0,0 +1,52 @@ +using System; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Styling; + +namespace Avalonia.Diagnostics.Controls +{ + internal class FilterTextBox : TextBox, IStyleable + { + public static readonly DirectProperty 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/Controls/ThicknessEditor.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs index e5b3b080e2..cb98fb70f3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs @@ -10,8 +10,8 @@ namespace Avalonia.Diagnostics.Controls AvaloniaProperty.RegisterDirect(nameof(Thickness), o => o.Thickness, (o, v) => o.Thickness = v, defaultBindingMode: BindingMode.TwoWay); - public static readonly DirectProperty HeaderProperty = - AvaloniaProperty.RegisterDirect(nameof(Header), o => o.Header, + public static readonly DirectProperty HeaderProperty = + AvaloniaProperty.RegisterDirect(nameof(Header), o => o.Header, (o, v) => o.Header = v); public static readonly DirectProperty IsPresentProperty = @@ -36,7 +36,7 @@ namespace Avalonia.Diagnostics.Controls AvaloniaProperty.Register(nameof(Highlight)); private Thickness _thickness; - private string _header; + private string? _header; private bool _isPresent = true; private double _left; private double _top; @@ -50,7 +50,7 @@ namespace Avalonia.Diagnostics.Controls set => SetAndRaise(ThicknessProperty, ref _thickness, value); } - public string Header + public string? Header { get => _header; set => SetAndRaise(HeaderProperty, ref _header, value); diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs index 63ac3ab62f..0b9044e65e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs @@ -8,12 +8,17 @@ namespace Avalonia.Diagnostics.Converters { public double Opacity { get; set; } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - return (bool)value ? 1d : Opacity; + if (value is bool boolean && boolean) + { + return 1d; + } + + return Opacity; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs index 8d10981ba7..4863782f44 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs @@ -7,12 +7,12 @@ namespace Avalonia.Diagnostics.Converters { internal class EnumToCheckedConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { return Equals(value, parameter); } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is bool isChecked && isChecked) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 7942d22962..0e36c8f9cb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -6,8 +6,6 @@ using Avalonia.Diagnostics.Views; using Avalonia.Input; using Avalonia.Interactivity; -#nullable enable - namespace Avalonia.Diagnostics { public static class DevTools @@ -24,7 +22,7 @@ namespace Avalonia.Diagnostics public static IDisposable Attach(TopLevel root, DevToolsOptions options) { - void PreviewKeyDown(object sender, KeyEventArgs e) + void PreviewKeyDown(object? sender, KeyEventArgs e) { if (options.Gesture.Matches(e)) { @@ -71,10 +69,10 @@ namespace Avalonia.Diagnostics return Disposable.Create(() => window?.Close()); } - private static void DevToolsClosed(object sender, EventArgs e) + private static void DevToolsClosed(object? sender, EventArgs e) { - var window = (MainWindow)sender; - s_open.Remove(window.Root); + var window = (MainWindow)sender!; + s_open.Remove(window.Root!); window.Closed -= DevToolsClosed; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs b/src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs index 5927bd785e..4f4579c7d9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs @@ -22,8 +22,8 @@ The following commands are available: clear(): Clear the output history "; - public dynamic e { get; internal set; } - public dynamic root { get; internal set; } + public dynamic? e { get; internal set; } + public dynamic? root { get; internal set; } internal static object NoOutput { get; } = new object(); diff --git a/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs b/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs index 36fe12d89c..4f493bdcc2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs @@ -7,9 +7,7 @@ namespace Avalonia.Diagnostics.Models { public EventChainLink(object handler, bool handled, RoutingStrategies route) { - Contract.Requires(handler != null); - - Handler = handler; + Handler = handler ?? throw new ArgumentNullException(nameof(handler)); Handled = handled; Route = route; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs index be3564e781..16852001da 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs @@ -9,12 +9,12 @@ namespace Avalonia.Diagnostics { public IControl Build(object data) { - var name = data.GetType().FullName.Replace("ViewModel", "View"); + var name = data.GetType().FullName!.Replace("ViewModel", "View"); var type = Type.GetType(name); if (type != null) { - return (Control)Activator.CreateInstance(type); + return (Control)Activator.CreateInstance(type)!; } else { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index a9353eba8b..e4c4ca6115 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -1,17 +1,17 @@ -using System.ComponentModel; -using Avalonia.Collections; - namespace Avalonia.Diagnostics.ViewModels { internal class AvaloniaPropertyViewModel : PropertyViewModel { private readonly AvaloniaObject _target; private string _type; - private object _value; + private object? _value; private string _priority; private string _group; +#nullable disable + // Remove "nullable disable" after MemberNotNull will work on our CI. public AvaloniaPropertyViewModel(AvaloniaObject o, AvaloniaProperty property) +#nullable restore { _target = o; Property = property; @@ -20,25 +20,17 @@ namespace Avalonia.Diagnostics.ViewModels $"[{property.OwnerType.Name}.{property.Name}]" : property.Name; - if (property.IsDirect) - { - _group = "Properties"; - Priority = "Direct"; - } - Update(); } public AvaloniaProperty Property { get; } public override object Key => Property; public override string Name { get; } - public bool IsAttached => Property.IsAttached; + public override bool? IsAttached => + Property.IsAttached; - public string Priority - { - get => _priority; - private set => RaiseAndSetIfChanged(ref _priority, value); - } + public override string Priority => + _priority; public override string Type => _type; @@ -56,40 +48,37 @@ namespace Avalonia.Diagnostics.ViewModels } } - public override string Group - { - get => _group; - } + public override string Group => _group; + // [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))] public override void Update() { if (Property.IsDirect) { RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); + RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); + RaiseAndSetIfChanged(ref _priority, "Direct", nameof(Priority)); + + _group = "Properties"; } else { var val = _target.GetDiagnostic(Property); RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); + RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); if (val != null) { - SetGroup(IsAttached ? "Attached Properties" : "Properties"); - Priority = val.Priority.ToString(); + RaiseAndSetIfChanged(ref _priority, val.Priority.ToString(), nameof(Priority)); + RaiseAndSetIfChanged(ref _group, IsAttached == true ? "Attached Properties" : "Properties", nameof(Group)); } else { - SetGroup(Priority = "Unset"); + RaiseAndSetIfChanged(ref _priority, "Unset", nameof(Priority)); + RaiseAndSetIfChanged(ref _group, "Unset", nameof(Group)); } } } - - private void SetGroup(string group) - { - RaiseAndSetIfChanged(ref _group, group, nameof(Group)); - } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index af5e254204..65626aeea5 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -1,5 +1,4 @@ -using System.ComponentModel; -using System.Reflection; +using System.Reflection; namespace Avalonia.Diagnostics.ViewModels { @@ -7,14 +6,17 @@ namespace Avalonia.Diagnostics.ViewModels { private readonly object _target; private string _type; - private object _value; + private object? _value; +#nullable disable + // Remove "nullable disable" after MemberNotNull will work on our CI. public ClrPropertyViewModel(object o, PropertyInfo property) +#nullable restore { _target = o; Property = property; - if (!property.DeclaringType.IsInterface) + if (property.DeclaringType == null || !property.DeclaringType.IsInterface) { Name = property.Name; } @@ -47,11 +49,18 @@ namespace Avalonia.Diagnostics.ViewModels } } + public override string Priority => + string.Empty; + + public override bool? IsAttached => + default; + + // [MemberNotNull(nameof(_type))] public override void Update() { var val = Property.GetValue(_target); RaiseAndSetIfChanged(ref _value, val, nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name, nameof(Type)); + RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs index 0e0c44ded8..717b49d074 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs @@ -15,11 +15,12 @@ namespace Avalonia.Diagnostics.ViewModels private int _historyIndex = -1; private string _input; private bool _isVisible; - private ScriptState _state; + private ScriptState? _state; public ConsoleViewModel(Action updateContext) { _context = new ConsoleContext(this); + _input = string.Empty; _updateContext = updateContext; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 32592559e5..3790951b0c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -18,11 +18,10 @@ namespace Avalonia.Diagnostics.ViewModels { private readonly IVisual _control; private readonly IDictionary> _propertyIndex; - private AvaloniaPropertyViewModel _selectedProperty; - private string _styleFilter; + private PropertyViewModel? _selectedProperty; private bool _snapshotStyles; private bool _showInactiveStyles; - private string _styleStatus; + private string? _styleStatus; public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control) { @@ -84,7 +83,8 @@ namespace Avalonia.Diagnostics.ViewModels { foreach (var setter in style.Setters) { - if (setter is Setter regularSetter) + if (setter is Setter regularSetter + && regularSetter.Property != null) { var setterValue = regularSetter.Value; @@ -116,13 +116,14 @@ namespace Avalonia.Diagnostics.ViewModels } } - private (object resourceKey, bool isDynamic)? GetResourceInfo(object value) + private (object resourceKey, bool isDynamic)? GetResourceInfo(object? value) { if (value is StaticResourceExtension staticResource) { return (staticResource.ResourceKey, false); } - else if (value is DynamicResourceExtension dynamicResource) + else if (value is DynamicResourceExtension dynamicResource + && dynamicResource.ResourceKey != null) { return (dynamicResource.ResourceKey, true); } @@ -138,18 +139,12 @@ namespace Avalonia.Diagnostics.ViewModels public ObservableCollection PseudoClasses { get; } - public AvaloniaPropertyViewModel SelectedProperty + public PropertyViewModel? SelectedProperty { get => _selectedProperty; set => RaiseAndSetIfChanged(ref _selectedProperty, value); } - public string StyleFilter - { - get => _styleFilter; - set => RaiseAndSetIfChanged(ref _styleFilter, value); - } - public bool SnapshotStyles { get => _snapshotStyles; @@ -162,7 +157,7 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _showInactiveStyles, value); } - public string StyleStatus + public string? StyleStatus { get => _styleStatus; set => RaiseAndSetIfChanged(ref _styleStatus, value); @@ -174,11 +169,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 +178,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; } @@ -263,7 +250,7 @@ namespace Avalonia.Diagnostics.ViewModels .Select(x => new ClrPropertyViewModel(o, x)); } - private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + private void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { if (_propertyIndex.TryGetValue(e.Property, out var properties)) { @@ -276,9 +263,10 @@ namespace Avalonia.Diagnostics.ViewModels Layout.ControlPropertyChanged(sender, e); } - private void ControlPropertyChanged(object sender, PropertyChangedEventArgs e) + private void ControlPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (_propertyIndex.TryGetValue(e.PropertyName, out var properties)) + if (e.PropertyName != null + && _propertyIndex.TryGetValue(e.PropertyName, out var properties)) { foreach (var property in properties) { @@ -292,7 +280,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private void OnClassesChanged(object sender, NotifyCollectionChangedEventArgs e) + private void OnClassesChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (!SnapshotStyles) { @@ -357,27 +345,17 @@ 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 { public static PropertyComparer Instance { get; } = new PropertyComparer(); - public int Compare(PropertyViewModel x, PropertyViewModel y) + public int Compare(PropertyViewModel? x, PropertyViewModel? y) { - var groupX = GroupIndex(x.Group); - var groupY = GroupIndex(y.Group); + var groupX = GroupIndex(x?.Group); + var groupY = GroupIndex(y?.Group); if (groupX != groupY) { @@ -385,11 +363,11 @@ namespace Avalonia.Diagnostics.ViewModels } else { - return string.CompareOrdinal(x.Name, y.Name); + return string.CompareOrdinal(x?.Name, y?.Name); } } - private int GroupIndex(string group) + private int GroupIndex(string? group) { switch (group) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs index b0718bc6ce..4dc0c34c0a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs @@ -12,14 +12,14 @@ namespace Avalonia.Diagnostics.ViewModels private readonly IVisual _control; private Thickness _borderThickness; private double _height; - private string _heightConstraint; + private string? _heightConstraint; private HorizontalAlignment _horizontalAlignment; private Thickness _marginThickness; private Thickness _paddingThickness; private bool _updatingFromControl; private VerticalAlignment _verticalAlignment; private double _width; - private string _widthConstraint; + private string? _widthConstraint; public ControlLayoutViewModel(IVisual control) { @@ -80,13 +80,13 @@ namespace Avalonia.Diagnostics.ViewModels private set => RaiseAndSetIfChanged(ref _height, value); } - public string WidthConstraint + public string? WidthConstraint { get => _widthConstraint; private set => RaiseAndSetIfChanged(ref _widthConstraint, value); } - public string HeightConstraint + public string? HeightConstraint { get => _heightConstraint; private set => RaiseAndSetIfChanged(ref _heightConstraint, value); @@ -112,7 +112,7 @@ namespace Avalonia.Diagnostics.ViewModels { if (_control is IAvaloniaObject ao) { - string CreateConstraintInfo(StyledProperty minProperty, StyledProperty maxProperty) + string? CreateConstraintInfo(StyledProperty minProperty, StyledProperty maxProperty) { bool hasMin = ao.IsSet(minProperty); bool hasMax = ao.IsSet(maxProperty); @@ -179,7 +179,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - public void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + public void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { try { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs index b56374d353..5b7ddc98ee 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs @@ -27,7 +27,7 @@ namespace Avalonia.Diagnostics.ViewModels if (_updateChildren && value != null) { - foreach (var child in Children) + foreach (var child in Children!) { try { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs index ea54302ebd..65fd81cc78 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs @@ -11,16 +11,13 @@ namespace Avalonia.Diagnostics.ViewModels { private readonly EventsPageViewModel _parentViewModel; private bool _isRegistered; - private FiredEvent _currentEvent; + private FiredEvent? _currentEvent; public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsPageViewModel vm) : base(parent, @event.Name) { - Contract.Requires(@event != null); - Contract.Requires(vm != null); - - Event = @event; - _parentViewModel = vm; + Event = @event ?? throw new ArgumentNullException(nameof(@event)); + _parentViewModel = vm ?? throw new ArgumentNullException(nameof(vm)); } public RoutedEvent Event { get; } @@ -62,18 +59,18 @@ namespace Avalonia.Diagnostics.ViewModels } } - private void HandleEvent(object sender, RoutedEventArgs e) + private void HandleEvent(object? sender, RoutedEventArgs e) { if (!_isRegistered || IsEnabled == false) return; if (sender is IVisual v && BelongsToDevTool(v)) return; - var s = sender; + var s = sender!; var handled = e.Handled; var route = e.Route; - Action handler = delegate + void handler() { if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e)) { @@ -98,14 +95,16 @@ namespace Avalonia.Diagnostics.ViewModels private static bool BelongsToDevTool(IVisual v) { - while (v != null) + var current = v; + + while (current != null) { - if (v is MainView || v is MainWindow) + if (current is MainView || current is MainWindow) { return true; } - v = v.VisualParent; + current = current.VisualParent; } return false; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs index c27cad29e8..e6d7335297 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs @@ -10,14 +10,14 @@ namespace Avalonia.Diagnostics.ViewModels private bool? _isEnabled = false; private bool _isVisible; - protected EventTreeNodeBase(EventTreeNodeBase parent, string text) + protected EventTreeNodeBase(EventTreeNodeBase? parent, string text) { Parent = parent; Text = text; IsVisible = true; } - public IAvaloniaReadOnlyList Children + public IAvaloniaReadOnlyList? Children { get; protected set; @@ -41,7 +41,7 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _isVisible, value); } - public EventTreeNodeBase Parent + public EventTreeNodeBase? Parent { get; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs index dd85fcf14c..fbcedb2e74 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; using System.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Models; @@ -23,9 +22,8 @@ namespace Avalonia.Diagnostics.ViewModels }; private readonly MainViewModel _mainViewModel; - private string _eventTypeFilter; - private FiredEvent _selectedEvent; - private EventTreeNodeBase _selectedNode; + private FiredEvent? _selectedEvent; + private EventTreeNodeBase? _selectedNode; public EventsPageViewModel(MainViewModel mainViewModel) { @@ -37,6 +35,9 @@ namespace Avalonia.Diagnostics.ViewModels .Select(g => new EventOwnerTreeNode(g.Key, g, this)) .ToArray(); + EventsFilter = new FilterViewModel(); + EventsFilter.RefreshFilter += (s, e) => UpdateEventFilters(); + EnableDefault(); } @@ -46,23 +47,19 @@ namespace Avalonia.Diagnostics.ViewModels public ObservableCollection RecordedEvents { get; } = new ObservableCollection(); - public FiredEvent SelectedEvent + public FiredEvent? SelectedEvent { get => _selectedEvent; set => RaiseAndSetIfChanged(ref _selectedEvent, value); } - public EventTreeNodeBase SelectedNode + public EventTreeNodeBase? SelectedNode { get => _selectedNode; set => RaiseAndSetIfChanged(ref _selectedNode, value); } - public string EventTypeFilter - { - get => _eventTypeFilter; - set => RaiseAndSetIfChanged(ref _eventTypeFilter, value); - } + public FilterViewModel EventsFilter { get; } public void Clear() { @@ -101,7 +98,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - static EventTreeNodeBase FindNode(EventTreeNodeBase node, RoutedEvent eventType) + static EventTreeNodeBase? FindNode(EventTreeNodeBase node, RoutedEvent eventType) { if (node is EventTreeNode eventNode && eventNode.Event == eventType) { @@ -125,16 +122,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 +148,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 +155,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/ViewModels/FilterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs new file mode 100644 index 0000000000..5b27236f2e --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs @@ -0,0 +1,122 @@ +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 _filterString = string.Empty; + private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter; + private Regex? _filterRegex; + + public event EventHandler? RefreshFilter; + + public bool HasErrors => _errors.Count > 0; + + public event EventHandler? ErrorsChanged; + + public bool Filter(string input) + { + return _filterRegex?.IsMatch(input) ?? true; + } + + private void UpdateFilterRegex() + { + void ClearError() + { + if (_errors.Remove(nameof(FilterString))) + { + ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(FilterString))); + } + } + + try + { + var options = RegexOptions.Compiled; + var pattern = UseRegexFilter + ? FilterString.Trim() : Regex.Escape(FilterString.Trim()); + if (!UseCaseSensitiveFilter) + { + options |= RegexOptions.IgnoreCase; + } + if (UseWholeWordFilter) + { + pattern = $"\\b(?:{pattern})\\b"; + } + + _filterRegex = new Regex(pattern, options); + ClearError(); + } + catch (Exception exception) + { + _errors[nameof(FilterString)] = exception.Message; + ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(FilterString))); + } + } + + public string FilterString + { + get => _filterString; + set + { + if (RaiseAndSetIfChanged(ref _filterString, value)) + { + UpdateFilterRegex(); + RefreshFilter?.Invoke(this, EventArgs.Empty); + } + } + } + + public bool UseRegexFilter + { + get => _useRegexFilter; + set + { + if (RaiseAndSetIfChanged(ref _useRegexFilter, value)) + { + UpdateFilterRegex(); + RefreshFilter?.Invoke(this, EventArgs.Empty); + } + } + } + + public bool UseCaseSensitiveFilter + { + get => _useCaseSensitiveFilter; + set + { + if (RaiseAndSetIfChanged(ref _useCaseSensitiveFilter, value)) + { + UpdateFilterRegex(); + RefreshFilter?.Invoke(this, EventArgs.Empty); + } + } + } + + public bool UseWholeWordFilter + { + get => _useWholeWordFilter; + set + { + if (RaiseAndSetIfChanged(ref _useWholeWordFilter, value)) + { + UpdateFilterRegex(); + RefreshFilter?.Invoke(this, EventArgs.Empty); + } + } + } + + public IEnumerable GetErrors(string? propertyName) + { + if (propertyName != null + && _errors.TryGetValue(propertyName, out var error)) + { + yield return error; + } + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs index 5fb528eead..32df2f8745 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs @@ -8,15 +8,12 @@ namespace Avalonia.Diagnostics.ViewModels internal class FiredEvent : ViewModelBase { private readonly RoutedEventArgs _eventArgs; - private EventChainLink _handledBy; + private EventChainLink? _handledBy; public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator) { - Contract.Requires(eventArgs != null); - Contract.Requires(originator != null); - - _eventArgs = eventArgs; - Originator = originator; + _eventArgs = eventArgs ?? throw new ArgumentNullException(nameof(eventArgs)); + Originator = originator ?? throw new ArgumentNullException(nameof(originator)); AddToChain(originator); } @@ -25,7 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels return e == _eventArgs; } - public RoutedEvent Event => _eventArgs.RoutedEvent; + public RoutedEvent Event => _eventArgs.RoutedEvent!; public bool IsHandled => HandledBy?.Handled == true; @@ -38,7 +35,7 @@ namespace Avalonia.Diagnostics.ViewModels if (IsHandled) { return $"{Event.Name} on {Originator.HandlerName};" + Environment.NewLine + - $"strategies: {Event.RoutingStrategies}; handled by: {HandledBy.HandlerName}"; + $"strategies: {Event.RoutingStrategies}; handled by: {HandledBy!.HandlerName}"; } return $"{Event.Name} on {Originator.HandlerName}; strategies: {Event.RoutingStrategies}"; @@ -47,7 +44,7 @@ namespace Avalonia.Diagnostics.ViewModels public EventChainLink Originator { get; } - public EventChainLink HandledBy + public EventChainLink? HandledBy { get => _handledBy; set diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs index 38788ef8ee..04215fa8ae 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs @@ -7,22 +7,24 @@ namespace Avalonia.Diagnostics.ViewModels { internal class LogicalTreeNode : TreeNode { - public LogicalTreeNode(ILogical logical, TreeNode parent) + public LogicalTreeNode(ILogical logical, TreeNode? parent) : base((Control)logical, parent) { Children = new LogicalTreeNodeCollection(this, logical); } + public override TreeNodeCollection Children { get; } + public static LogicalTreeNode[] Create(object control) { var logical = control as ILogical; - return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null; + return logical != null ? new[] { new LogicalTreeNode(logical, null) } : Array.Empty(); } internal class LogicalTreeNodeCollection : TreeNodeCollection { private readonly ILogical _control; - private IDisposable _subscription; + private IDisposable? _subscription; public LogicalTreeNodeCollection(TreeNode owner, ILogical control) : base(owner) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 49263eafdc..72491bebc2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,10 +1,10 @@ using System; using System.ComponentModel; + using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; using Avalonia.Threading; -using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels { @@ -17,13 +17,16 @@ namespace Avalonia.Diagnostics.ViewModels private readonly IDisposable _pointerOverSubscription; private ViewModelBase _content; private int _selectedTab; - private string _focusedControl; - private string _pointerOverElement; + private string? _focusedControl; + private string? _pointerOverElement; private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; +#nullable disable + // Remove "nullable disable" after MemberNotNull will work on our CI. public MainViewModel(TopLevel root) +#nullable restore { _root = root; _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root)); @@ -84,6 +87,7 @@ namespace Avalonia.Diagnostics.ViewModels public ViewModelBase Content { get { return _content; } + // [MemberNotNull(nameof(_content))] private set { if (_content is TreePageViewModel oldTree && @@ -114,34 +118,35 @@ namespace Avalonia.Diagnostics.ViewModels public int SelectedTab { get { return _selectedTab; } + // [MemberNotNull(nameof(_content))] set { _selectedTab = value; switch (value) { - case 0: - Content = _logicalTree; - break; case 1: Content = _visualTree; break; case 2: Content = _events; break; + default: + Content = _logicalTree; + break; } RaisePropertyChanged(); } } - public string FocusedControl + public string? FocusedControl { get { return _focusedControl; } private set { RaiseAndSetIfChanged(ref _focusedControl, value); } } - public string PointerOverElement + public string? PointerOverElement { get { return _pointerOverElement; } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } @@ -187,7 +192,7 @@ namespace Avalonia.Diagnostics.ViewModels FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; } - private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e) + private void KeyboardPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index e23d6f1471..fdbd8c1aa3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -16,9 +16,11 @@ namespace Avalonia.Diagnostics.ViewModels public abstract string Group { get; } public abstract string Type { get; } public abstract string Value { get; set; } - public abstract void Update(); + public abstract string Priority { get; } + public abstract bool? IsAttached { get; } + public abstract void Update(); - protected static string ConvertToString(object value) + protected static string ConvertToString(object? value) { if (value is null) { @@ -31,13 +33,13 @@ namespace Avalonia.Diagnostics.ViewModels if (!converter.CanConvertTo(typeof(string)) || converter.GetType() == typeof(CollectionConverter)) { - return value.ToString(); + return value.ToString() ?? "(null)"; } return converter.ConvertToString(value); } - private static object InvokeParse(string s, Type targetType) + private static object? InvokeParse(string s, Type targetType) { var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null); @@ -56,7 +58,7 @@ namespace Avalonia.Diagnostics.ViewModels throw new InvalidCastException("Unable to convert value."); } - protected static object ConvertFromString(string s, Type targetType) + protected static object? ConvertFromString(string s, Type targetType) { var converter = TypeDescriptor.GetConverter(targetType); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs index a82e13fcfa..e93dc7361b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs @@ -8,7 +8,7 @@ namespace Avalonia.Diagnostics.ViewModels public IBrush Tint { get; } - public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object resourceValue, bool isDynamic) : base(property, resourceValue) + public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic) : base(property, resourceValue) { Key = resourceKey; Tint = isDynamic ? Brushes.Orange : Brushes.Brown; @@ -16,12 +16,14 @@ namespace Avalonia.Diagnostics.ViewModels public void CopyResourceKey() { - if (Key is null) + var textToCopy = Key?.ToString(); + + if (textToCopy is null) { return; } - CopyToClipboard(Key.ToString()); + CopyToClipboard(textToCopy); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs index e835f5a878..38cbefcb93 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs @@ -11,7 +11,7 @@ namespace Avalonia.Diagnostics.ViewModels public string Name { get; } - public object Value { get; } + public object? Value { get; } public bool IsActive { @@ -25,7 +25,7 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _isVisible, value); } - public SetterViewModel(AvaloniaProperty property, object value) + public SetterViewModel(AvaloniaProperty property, object? value) { Property = property; Name = property.Name; @@ -36,12 +36,14 @@ namespace Avalonia.Diagnostics.ViewModels public void CopyValue() { - if (Value is null) + var textToCopy = Value?.ToString(); + + if (textToCopy is null) { return; } - CopyToClipboard(Value.ToString()); + CopyToClipboard(textToCopy); } public void CopyPropertyName() diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 9363c28705..4cb470eeac 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -9,17 +9,18 @@ using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels { - internal class TreeNode : ViewModelBase, IDisposable + internal abstract class TreeNode : ViewModelBase, IDisposable { - private IDisposable _classesSubscription; + private IDisposable? _classesSubscription; private string _classes; private bool _isExpanded; - public TreeNode(IVisual visual, TreeNode parent) + public TreeNode(IVisual visual, TreeNode? parent) { Parent = parent; Type = visual.GetType().Name; Visual = visual; + _classes = string.Empty; if (visual is IControl control) { @@ -51,10 +52,9 @@ namespace Avalonia.Diagnostics.ViewModels } } - public TreeNodeCollection Children + public abstract TreeNodeCollection Children { get; - protected set; } public string Classes @@ -63,7 +63,7 @@ namespace Avalonia.Diagnostics.ViewModels private set { RaiseAndSetIfChanged(ref _classes, value); } } - public string ElementName + public string? ElementName { get; } @@ -79,7 +79,7 @@ namespace Avalonia.Diagnostics.ViewModels set { RaiseAndSetIfChanged(ref _isExpanded, value); } } - public TreeNode Parent + public TreeNode? Parent { get; } @@ -92,7 +92,7 @@ namespace Avalonia.Diagnostics.ViewModels public void Dispose() { - _classesSubscription.Dispose(); + _classesSubscription?.Dispose(); Children.Dispose(); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs index 8b4f03bd23..c007411f49 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs @@ -3,46 +3,33 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; + using Avalonia.Collections; namespace Avalonia.Diagnostics.ViewModels { internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList, IDisposable { - private AvaloniaList _inner; + private AvaloniaList? _inner; public TreeNodeCollection(TreeNode owner) => Owner = owner; - public TreeNode this[int index] - { - get - { - EnsureInitialized(); - return _inner[index]; - } - } + public TreeNode this[int index] => EnsureInitialized()[index]; - public int Count - { - get - { - EnsureInitialized(); - return _inner.Count; - } - } + public int Count => EnsureInitialized().Count; protected TreeNode Owner { get; } - public event NotifyCollectionChangedEventHandler CollectionChanged + public event NotifyCollectionChangedEventHandler? CollectionChanged { - add => _inner.CollectionChanged += value; - remove => _inner.CollectionChanged -= value; + add => EnsureInitialized().CollectionChanged += value; + remove => EnsureInitialized().CollectionChanged -= value; } - public event PropertyChangedEventHandler PropertyChanged + public event PropertyChangedEventHandler? PropertyChanged { - add => _inner.PropertyChanged += value; - remove => _inner.PropertyChanged -= value; + add => EnsureInitialized().PropertyChanged += value; + remove => EnsureInitialized().PropertyChanged -= value; } public virtual void Dispose() @@ -58,21 +45,21 @@ namespace Avalonia.Diagnostics.ViewModels public IEnumerator GetEnumerator() { - EnsureInitialized(); - return _inner.GetEnumerator(); + return EnsureInitialized().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); protected abstract void Initialize(AvaloniaList nodes); - private void EnsureInitialized() + private AvaloniaList EnsureInitialized() { if (_inner is null) { _inner = new AvaloniaList(); Initialize(_inner); } + return _inner; } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index 6b779cd6ac..4b18cf414a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -1,53 +1,50 @@ 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; + private TreeNode? _selectedNode; + private ControlDetailsViewModel? _details; public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes) { MainView = mainView; Nodes = nodes; + + PropertiesFilter = new FilterViewModel(); + PropertiesFilter.RefreshFilter += (s, e) => Details?.PropertiesView.Refresh(); + + SettersFilter = new FilterViewModel(); + SettersFilter.RefreshFilter += (s, e) => Details?.UpdateStyleFilters(); } public MainViewModel MainView { get; } + public FilterViewModel PropertiesFilter { get; } + + public FilterViewModel SettersFilter { get; } + public TreeNode[] Nodes { get; protected set; } - public TreeNode SelectedNode + public TreeNode? SelectedNode { 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(); } } } - public ControlDetailsViewModel Details + public ControlDetailsViewModel? Details { get => _details; private set @@ -61,63 +58,6 @@ namespace Avalonia.Diagnostics.ViewModels } } - public Regex FilterRegex { get; set; } - - private void UpdateFilterRegex() - { - void ClearError() - { - if (_errors.Remove(nameof(PropertyFilter))) - { - ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter))); - } - } - - if (UseRegexFilter) - { - try - { - FilterRegex = new Regex(PropertyFilter, RegexOptions.Compiled); - ClearError(); - } - catch (Exception exception) - { - _errors[nameof(PropertyFilter)] = exception.Message; - ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(PropertyFilter))); - } - } - else - { - ClearError(); - } - } - - public string PropertyFilter - { - get => _propertyFilter; - set - { - if (RaiseAndSetIfChanged(ref _propertyFilter, value)) - { - UpdateFilterRegex(); - Details.PropertiesView.Refresh(); - } - } - } - - public bool UseRegexFilter - { - get => _useRegexFilter; - set - { - if (RaiseAndSetIfChanged(ref _useRegexFilter, value)) - { - UpdateFilterRegex(); - Details.PropertiesView.Refresh(); - } - } - } - public void Dispose() { foreach (var node in Nodes) @@ -128,7 +68,7 @@ namespace Avalonia.Diagnostics.ViewModels _details?.Dispose(); } - public TreeNode FindNode(IControl control) + public TreeNode? FindNode(IControl control) { foreach (var node in Nodes) { @@ -164,7 +104,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private void ExpandNode(TreeNode node) + private void ExpandNode(TreeNode? node) { if (node != null) { @@ -173,7 +113,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - private TreeNode FindNode(TreeNode node, IControl control) + private TreeNode? FindNode(TreeNode node, IControl control) { if (node.Visual == control) { @@ -194,17 +134,5 @@ namespace Avalonia.Diagnostics.ViewModels return null; } - - public IEnumerable GetErrors(string propertyName) - { - if (_errors.TryGetValue(propertyName, out var error)) - { - yield return error; - } - } - - public bool HasErrors => _errors.Count > 0; - - public event EventHandler ErrorsChanged; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs index 66e9c34657..a2ee37c625 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs @@ -1,16 +1,16 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace Avalonia.Diagnostics.ViewModels { internal class ViewModelBase : INotifyPropertyChanged { - private PropertyChangedEventHandler _propertyChanged; + private PropertyChangedEventHandler? _propertyChanged; private List events = new List(); - public event PropertyChangedEventHandler PropertyChanged + public event PropertyChangedEventHandler? PropertyChanged { add { _propertyChanged += value; events.Add("added"); } remove { _propertyChanged -= value; events.Add("removed"); } @@ -20,7 +20,7 @@ namespace Avalonia.Diagnostics.ViewModels { } - protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string propertyName = null) + protected bool RaiseAndSetIfChanged([NotNullIfNotNull("value")] ref T field, T value, [CallerMemberName] string propertyName = null!) { if (!EqualityComparer.Default.Equals(field, value)) { @@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.ViewModels return false; } - protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) + protected void RaisePropertyChanged([CallerMemberName] string propertyName = null!) { var e = new PropertyChangedEventArgs(propertyName); OnPropertyChanged(e); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index bc40edf477..48fa636664 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -7,7 +7,7 @@ namespace Avalonia.Diagnostics.ViewModels { internal class VisualTreeNode : TreeNode { - public VisualTreeNode(IVisual visual, TreeNode parent) + public VisualTreeNode(IVisual visual, TreeNode? parent) : base(visual, parent) { Children = new VisualTreeNodeCollection(this, visual); @@ -20,16 +20,18 @@ namespace Avalonia.Diagnostics.ViewModels public bool IsInTemplate { get; private set; } + public override TreeNodeCollection Children { get; } + public static VisualTreeNode[] Create(object control) { var visual = control as IVisual; - return visual != null ? new[] { new VisualTreeNode(visual, null) } : null; + return visual != null ? new[] { new VisualTreeNode(visual, null) } : Array.Empty(); } internal class VisualTreeNodeCollection : TreeNodeCollection { private readonly IVisual _control; - private IDisposable _subscription; + private IDisposable? _subscription; public VisualTreeNodeCollection(TreeNode owner, IVisual control) : base(owner) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs index ae70b59fde..ab523fb75a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs @@ -30,22 +30,26 @@ namespace Avalonia.Diagnostics.Views AvaloniaXamlLoader.Load(this); } - private void HistoryChanged(object sender, NotifyCollectionChangedEventArgs e) + private void HistoryChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems[0] is IControl control) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems?[0] is IControl control) { DispatcherTimer.RunOnce(control.BringIntoView, TimeSpan.Zero); } } - private void InputKeyDown(object sender, KeyEventArgs e) + private void InputKeyDown(object? sender, KeyEventArgs e) { - var vm = (ConsoleViewModel)DataContext; + var vm = (ConsoleViewModel?)DataContext; + if (vm is null) + { + return; + } switch (e.Key) { case Key.Enter: - vm.Execute(); + _ = vm.Execute(); e.Handled = true; break; case Key.Up: diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index ab651b2a06..4b37438993 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 @@ - - - - - - + + + + - + 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 @@ - + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs index 687a20c5f6..ba7ab41e35 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs @@ -53,7 +53,7 @@ namespace Avalonia.Diagnostics.Views } } - private void OnRecordedEventsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void OnRecordedEventsChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (sender is ObservableCollection events) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs index 783709e54b..b688ad7676 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs @@ -27,7 +27,11 @@ namespace Avalonia.Diagnostics.Views public void ToggleConsole() { - var vm = (MainViewModel)DataContext; + var vm = (MainViewModel?)DataContext; + if (vm is null) + { + return; + } if (_consoleHeight == -1) { @@ -54,7 +58,7 @@ namespace Avalonia.Diagnostics.Views AvaloniaXamlLoader.Load(this); } - private void PreviewKeyDown(object sender, KeyEventArgs e) + private void PreviewKeyDown(object? sender, KeyEventArgs e) { if (e.Key == Key.Escape) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 7628ee2d8c..70d70f0b79 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -16,6 +16,7 @@ + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 330121321a..bbb8e76551 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.Views internal class MainWindow : Window, IStyleHost { private readonly IDisposable _keySubscription; - private TopLevel _root; + private TopLevel? _root; public MainWindow() { @@ -26,7 +26,7 @@ namespace Avalonia.Diagnostics.Views .Subscribe(RawKeyDown); } - public TopLevel Root + public TopLevel? Root { get => _root; set @@ -43,7 +43,7 @@ namespace Avalonia.Diagnostics.Views if (_root != null) { _root.Closed += RootClosed; - DataContext = new MainViewModel(value); + DataContext = new MainViewModel(_root); } else { @@ -53,15 +53,20 @@ namespace Avalonia.Diagnostics.Views } } - IStyleHost IStyleHost.StylingParent => null; + IStyleHost? IStyleHost.StylingParent => null; protected override void OnClosed(EventArgs e) { base.OnClosed(e); _keySubscription.Dispose(); - _root.Closed -= RootClosed; - _root = null; - ((MainViewModel)DataContext)?.Dispose(); + + if (_root != null) + { + _root.Closed -= RootClosed; + _root = null; + } + + ((MainViewModel?)DataContext)?.Dispose(); } private void InitializeComponent() @@ -71,12 +76,20 @@ namespace Avalonia.Diagnostics.Views private void RawKeyDown(RawKeyEventArgs e) { + var vm = (MainViewModel?)DataContext; + if (vm is null) + { + return; + } + const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift; if (e.Modifiers == modifiers) { +#pragma warning disable CS0618 // Type or member is obsolete var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default; - +#pragma warning restore CS0618 // Type or member is obsolete + var control = Root.GetVisualsAt(point, x => { if (x is AdornerLayer || !x.IsVisible) return false; @@ -87,7 +100,6 @@ namespace Avalonia.Diagnostics.Views if (control != null) { - var vm = (MainViewModel)DataContext; vm.SelectControl((IControl)control); } } @@ -97,12 +109,11 @@ namespace Avalonia.Diagnostics.Views { var enable = e.Key == Key.S; - var vm = (MainViewModel)DataContext; vm.EnableSnapshotStyles(enable); } } } - private void RootClosed(object sender, EventArgs e) => Close(); + private void RootClosed(object? sender, EventArgs e) => Close(); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs index 3e1a238b36..3543b1adea 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs @@ -14,12 +14,13 @@ namespace Avalonia.Diagnostics.Views internal class TreePageView : UserControl { private readonly Panel _adorner; - private AdornerLayer _currentLayer; + private AdornerLayer? _currentLayer; private TreeView _tree; public TreePageView() { InitializeComponent(); + _tree = this.FindControl("tree"); _tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized; _adorner = new Panel @@ -37,9 +38,15 @@ namespace Avalonia.Diagnostics.Views }; } - protected void AddAdorner(object sender, PointerEventArgs e) + protected void AddAdorner(object? sender, PointerEventArgs e) { - var node = (TreeNode)((Control)sender).DataContext; + var node = (TreeNode?)((Control)sender!).DataContext; + var vm = (TreePageViewModel?)DataContext; + if (node is null || vm is null) + { + return; + } + var visual = (Visual)node.Visual; _currentLayer = AdornerLayer.GetAdornerLayer(visual); @@ -53,8 +60,6 @@ namespace Avalonia.Diagnostics.Views _currentLayer.Children.Add(_adorner); AdornerLayer.SetAdornedElement(_adorner, visual); - var vm = (TreePageViewModel) DataContext; - if (vm.MainView.ShouldVisualizeMarginPadding) { var paddingBorder = (Border)_adorner.Children[0]; @@ -74,7 +79,7 @@ namespace Avalonia.Diagnostics.Views return new Thickness(-input.Left, -input.Top, -input.Right, -input.Bottom); } - protected void RemoveAdorner(object sender, PointerEventArgs e) + protected void RemoveAdorner(object? sender, PointerEventArgs e) { foreach (var border in _adorner.Children.OfType()) { @@ -90,18 +95,17 @@ namespace Avalonia.Diagnostics.Views private void InitializeComponent() { AvaloniaXamlLoader.Load(this); - _tree = this.FindControl("tree"); } - private void TreeViewItemMaterialized(object sender, ItemContainerEventArgs e) + private void TreeViewItemMaterialized(object? sender, ItemContainerEventArgs e) { var item = (TreeViewItem)e.Containers[0].ContainerControl; item.TemplateApplied += TreeViewItemTemplateApplied; } - private void TreeViewItemTemplateApplied(object sender, TemplateAppliedEventArgs e) + private void TreeViewItemTemplateApplied(object? sender, TemplateAppliedEventArgs e) { - var item = (TreeViewItem)sender; + var item = (TreeViewItem)sender!; // This depends on the default tree item template. // We want to handle events in the item header but exclude events coming from children. diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs index 6f699339e7..4adcd32302 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs @@ -17,7 +17,7 @@ namespace Avalonia.Diagnostics private static void PrintVisualTree(IVisual visual, StringBuilder builder, int indent) { - Control control = visual as Control; + Control? control = visual as Control; builder.Append(Indent(indent - 1)); 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 5f26f65512..2292f5c518 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() @@ -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.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!); } } diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 29b611279e..37598d4b79 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -113,13 +113,13 @@ - +