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 @@
-
+