Browse Source

Merge branch 'master' into use-immutable-data

pull/6032/head
Jumar Macato 5 years ago
committed by GitHub
parent
commit
0793d3bc5a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 81
      src/Avalonia.Base/Metadata/NullableAttributes.cs
  2. 4
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  3. 8
      src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs
  4. 11
      src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs
  5. 4
      src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs
  6. 10
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  7. 4
      src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs
  8. 4
      src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs
  9. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs
  10. 45
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
  11. 13
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
  12. 3
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs
  13. 35
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  14. 12
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
  15. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs
  16. 23
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
  17. 6
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs
  18. 11
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventsPageViewModel.cs
  19. 22
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/FilterViewModel.cs
  20. 15
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs
  21. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
  22. 23
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  23. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
  24. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
  25. 10
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs
  26. 16
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  27. 39
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
  28. 14
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  29. 12
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ViewModelBase.cs
  30. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  31. 14
      src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs
  32. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml.cs
  33. 8
      src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs
  34. 33
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  35. 24
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
  36. 2
      src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs

81
src/Avalonia.Base/Metadata/NullableAttributes.cs

@ -1,6 +1,5 @@
#pragma warning disable MA0048 // File name must match type name #pragma warning disable MA0048 // File name must match type name
#define INTERNAL_NULLABLE_ATTRIBUTES #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 // https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
@ -10,6 +9,7 @@
namespace System.Diagnostics.CodeAnalysis namespace System.Diagnostics.CodeAnalysis
{ {
#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary> /// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES #if INTERNAL_NULLABLE_ATTRIBUTES
@ -136,5 +136,82 @@ namespace System.Diagnostics.CodeAnalysis
/// <summary>Gets the condition parameter value.</summary> /// <summary>Gets the condition parameter value.</summary>
public bool ParameterValue { get; } 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
/// <summary>
/// Specifies that the method or property will ensure that the listed field and property members have
/// not-<see langword="null"/> values.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif #endif
sealed class MemberNotNullAttribute : Attribute
{
/// <summary>Gets field or property member names.</summary>
public string[] Members { get; }
/// <summary>Initializes the attribute with a field or property member.</summary>
/// <param name="member">The field or property member that is promised to be not-null.</param>
public MemberNotNullAttribute(string member)
{
Members = new[] { member };
}
/// <summary>Initializes the attribute with the list of field and property members.</summary>
/// <param name="members">The list of field and property members that are promised to be not-null.</param>
public MemberNotNullAttribute(params string[] members)
{
Members = members;
}
}
/// <summary>
/// Specifies that the method or property will ensure that the listed field and property members have
/// non-<see langword="null"/> values when returning with the specified return value condition.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class MemberNotNullWhenAttribute : Attribute
{
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
/// <summary>Gets field or property member names.</summary>
public string[] Members { get; }
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value,
/// the associated parameter will not be <see langword="null"/>.
/// </param>
/// <param name="member">The field or property member that is promised to be not-<see langword="null"/>.</param>
public MemberNotNullWhenAttribute(bool returnValue, string member)
{
ReturnValue = returnValue;
Members = new[] { member };
}
/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.
/// </summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value,
/// the associated parameter will not be <see langword="null"/>.
/// </param>
/// <param name="members">The list of field and property members that are promised to be not-null.</param>
public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
{
ReturnValue = returnValue;
Members = members;
}
}
#endif // NETSTANDARD2_1 attributes
}

4
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -3,12 +3,16 @@
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia</RootNamespace> <RootNamespace>Avalonia</RootNamespace>
<PackageId>Avalonia.Diagnostics</PackageId> <PackageId>Avalonia.Diagnostics</PackageId>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Update="**\*.xaml.cs"> <Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon> <DependentUpon>%(Filename)</DependentUpon>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> <ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

8
src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs

@ -10,8 +10,8 @@ namespace Avalonia.Diagnostics.Controls
AvaloniaProperty.RegisterDirect<ThicknessEditor, Thickness>(nameof(Thickness), o => o.Thickness, AvaloniaProperty.RegisterDirect<ThicknessEditor, Thickness>(nameof(Thickness), o => o.Thickness,
(o, v) => o.Thickness = v, defaultBindingMode: BindingMode.TwoWay); (o, v) => o.Thickness = v, defaultBindingMode: BindingMode.TwoWay);
public static readonly DirectProperty<ThicknessEditor, string> HeaderProperty = public static readonly DirectProperty<ThicknessEditor, string?> HeaderProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, string>(nameof(Header), o => o.Header, AvaloniaProperty.RegisterDirect<ThicknessEditor, string?>(nameof(Header), o => o.Header,
(o, v) => o.Header = v); (o, v) => o.Header = v);
public static readonly DirectProperty<ThicknessEditor, bool> IsPresentProperty = public static readonly DirectProperty<ThicknessEditor, bool> IsPresentProperty =
@ -36,7 +36,7 @@ namespace Avalonia.Diagnostics.Controls
AvaloniaProperty.Register<ThicknessEditor, IBrush>(nameof(Highlight)); AvaloniaProperty.Register<ThicknessEditor, IBrush>(nameof(Highlight));
private Thickness _thickness; private Thickness _thickness;
private string _header; private string? _header;
private bool _isPresent = true; private bool _isPresent = true;
private double _left; private double _left;
private double _top; private double _top;
@ -50,7 +50,7 @@ namespace Avalonia.Diagnostics.Controls
set => SetAndRaise(ThicknessProperty, ref _thickness, value); set => SetAndRaise(ThicknessProperty, ref _thickness, value);
} }
public string Header public string? Header
{ {
get => _header; get => _header;
set => SetAndRaise(HeaderProperty, ref _header, value); set => SetAndRaise(HeaderProperty, ref _header, value);

11
src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs

@ -8,12 +8,17 @@ namespace Avalonia.Diagnostics.Converters
{ {
public double Opacity { get; set; } 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(); throw new NotImplementedException();
} }

4
src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs

@ -7,12 +7,12 @@ namespace Avalonia.Diagnostics.Converters
{ {
internal class EnumToCheckedConverter : IValueConverter 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); 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) if (value is bool isChecked && isChecked)
{ {

10
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -6,8 +6,6 @@ using Avalonia.Diagnostics.Views;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
#nullable enable
namespace Avalonia.Diagnostics namespace Avalonia.Diagnostics
{ {
public static class DevTools public static class DevTools
@ -24,7 +22,7 @@ namespace Avalonia.Diagnostics
public static IDisposable Attach(TopLevel root, DevToolsOptions options) 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)) if (options.Gesture.Matches(e))
{ {
@ -71,10 +69,10 @@ namespace Avalonia.Diagnostics
return Disposable.Create(() => window?.Close()); 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; var window = (MainWindow)sender!;
s_open.Remove(window.Root); s_open.Remove(window.Root!);
window.Closed -= DevToolsClosed; window.Closed -= DevToolsClosed;
} }
} }

4
src/Avalonia.Diagnostics/Diagnostics/Models/ConsoleContext.cs

@ -22,8 +22,8 @@ The following commands are available:
clear(): Clear the output history clear(): Clear the output history
"; ";
public dynamic e { get; internal set; } public dynamic? e { get; internal set; }
public dynamic root { get; internal set; } public dynamic? root { get; internal set; }
internal static object NoOutput { get; } = new object(); internal static object NoOutput { get; } = new object();

4
src/Avalonia.Diagnostics/Diagnostics/Models/EventChainLink.cs

@ -7,9 +7,7 @@ namespace Avalonia.Diagnostics.Models
{ {
public EventChainLink(object handler, bool handled, RoutingStrategies route) public EventChainLink(object handler, bool handled, RoutingStrategies route)
{ {
Contract.Requires<ArgumentNullException>(handler != null); Handler = handler ?? throw new ArgumentNullException(nameof(handler));
Handler = handler;
Handled = handled; Handled = handled;
Route = route; Route = route;
} }

4
src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs

@ -9,12 +9,12 @@ namespace Avalonia.Diagnostics
{ {
public IControl Build(object data) 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); var type = Type.GetType(name);
if (type != null) if (type != null)
{ {
return (Control)Activator.CreateInstance(type); return (Control)Activator.CreateInstance(type)!;
} }
else else
{ {

45
src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs

@ -1,17 +1,17 @@
using System.ComponentModel;
using Avalonia.Collections;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class AvaloniaPropertyViewModel : PropertyViewModel internal class AvaloniaPropertyViewModel : PropertyViewModel
{ {
private readonly AvaloniaObject _target; private readonly AvaloniaObject _target;
private string _type; private string _type;
private object _value; private object? _value;
private string _priority; private string _priority;
private string _group; private string _group;
#nullable disable
// Remove "nullable disable" after MemberNotNull will work on our CI.
public AvaloniaPropertyViewModel(AvaloniaObject o, AvaloniaProperty property) public AvaloniaPropertyViewModel(AvaloniaObject o, AvaloniaProperty property)
#nullable restore
{ {
_target = o; _target = o;
Property = property; Property = property;
@ -20,12 +20,6 @@ namespace Avalonia.Diagnostics.ViewModels
$"[{property.OwnerType.Name}.{property.Name}]" : $"[{property.OwnerType.Name}.{property.Name}]" :
property.Name; property.Name;
if (property.IsDirect)
{
_group = "Properties";
Priority = "Direct";
}
Update(); Update();
} }
@ -34,11 +28,7 @@ namespace Avalonia.Diagnostics.ViewModels
public override string Name { get; } public override string Name { get; }
public bool IsAttached => Property.IsAttached; public bool IsAttached => Property.IsAttached;
public string Priority public string Priority => _priority;
{
get => _priority;
private set => RaiseAndSetIfChanged(ref _priority, value);
}
public override string Type => _type; public override string Type => _type;
@ -56,40 +46,37 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public override string Group public override string Group => _group;
{
get => _group;
}
// [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))]
public override void Update() public override void Update()
{ {
if (Property.IsDirect) if (Property.IsDirect)
{ {
RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value)); 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 else
{ {
var val = _target.GetDiagnostic(Property); var val = _target.GetDiagnostic(Property);
RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value)); 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) if (val != null)
{ {
SetGroup(IsAttached ? "Attached Properties" : "Properties"); RaiseAndSetIfChanged(ref _priority, val.Priority.ToString(), nameof(Priority));
Priority = val.Priority.ToString(); RaiseAndSetIfChanged(ref _group, IsAttached ? "Attached Properties" : "Properties", nameof(Group));
} }
else 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));
}
} }
} }

13
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs

@ -1,5 +1,4 @@
using System.ComponentModel; using System.Reflection;
using System.Reflection;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
@ -7,14 +6,17 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
private readonly object _target; private readonly object _target;
private string _type; 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) public ClrPropertyViewModel(object o, PropertyInfo property)
#nullable restore
{ {
_target = o; _target = o;
Property = property; Property = property;
if (!property.DeclaringType.IsInterface) if (property.DeclaringType == null || !property.DeclaringType.IsInterface)
{ {
Name = property.Name; Name = property.Name;
} }
@ -47,11 +49,12 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
// [MemberNotNull(nameof(_type))]
public override void Update() public override void Update()
{ {
var val = Property.GetValue(_target); var val = Property.GetValue(_target);
RaiseAndSetIfChanged(ref _value, val, nameof(Value)); 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));
} }
} }
} }

3
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ConsoleViewModel.cs

@ -15,11 +15,12 @@ namespace Avalonia.Diagnostics.ViewModels
private int _historyIndex = -1; private int _historyIndex = -1;
private string _input; private string _input;
private bool _isVisible; private bool _isVisible;
private ScriptState<object> _state; private ScriptState<object>? _state;
public ConsoleViewModel(Action<ConsoleContext> updateContext) public ConsoleViewModel(Action<ConsoleContext> updateContext)
{ {
_context = new ConsoleContext(this); _context = new ConsoleContext(this);
_input = string.Empty;
_updateContext = updateContext; _updateContext = updateContext;
} }

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

@ -18,10 +18,10 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
private readonly IVisual _control; private readonly IVisual _control;
private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex; private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex;
private AvaloniaPropertyViewModel _selectedProperty; private AvaloniaPropertyViewModel? _selectedProperty;
private bool _snapshotStyles; private bool _snapshotStyles;
private bool _showInactiveStyles; private bool _showInactiveStyles;
private string _styleStatus; private string? _styleStatus;
public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control) public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control)
{ {
@ -83,7 +83,8 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
foreach (var setter in style.Setters) foreach (var setter in style.Setters)
{ {
if (setter is Setter regularSetter) if (setter is Setter regularSetter
&& regularSetter.Property != null)
{ {
var setterValue = regularSetter.Value; var setterValue = regularSetter.Value;
@ -115,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) if (value is StaticResourceExtension staticResource)
{ {
return (staticResource.ResourceKey, false); return (staticResource.ResourceKey, false);
} }
else if (value is DynamicResourceExtension dynamicResource) else if (value is DynamicResourceExtension dynamicResource
&& dynamicResource.ResourceKey != null)
{ {
return (dynamicResource.ResourceKey, true); return (dynamicResource.ResourceKey, true);
} }
@ -137,7 +139,7 @@ namespace Avalonia.Diagnostics.ViewModels
public ObservableCollection<PseudoClassViewModel> PseudoClasses { get; } public ObservableCollection<PseudoClassViewModel> PseudoClasses { get; }
public AvaloniaPropertyViewModel SelectedProperty public AvaloniaPropertyViewModel? SelectedProperty
{ {
get => _selectedProperty; get => _selectedProperty;
set => RaiseAndSetIfChanged(ref _selectedProperty, value); set => RaiseAndSetIfChanged(ref _selectedProperty, value);
@ -155,7 +157,7 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _showInactiveStyles, value); set => RaiseAndSetIfChanged(ref _showInactiveStyles, value);
} }
public string StyleStatus public string? StyleStatus
{ {
get => _styleStatus; get => _styleStatus;
set => RaiseAndSetIfChanged(ref _styleStatus, value); set => RaiseAndSetIfChanged(ref _styleStatus, value);
@ -248,7 +250,7 @@ namespace Avalonia.Diagnostics.ViewModels
.Select(x => new ClrPropertyViewModel(o, x)); .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)) if (_propertyIndex.TryGetValue(e.Property, out var properties))
{ {
@ -261,9 +263,10 @@ namespace Avalonia.Diagnostics.ViewModels
Layout.ControlPropertyChanged(sender, e); 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) foreach (var property in properties)
{ {
@ -277,7 +280,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
private void OnClassesChanged(object sender, NotifyCollectionChangedEventArgs e) private void OnClassesChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
if (!SnapshotStyles) if (!SnapshotStyles)
{ {
@ -349,10 +352,10 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
public static PropertyComparer Instance { get; } = new PropertyComparer(); 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 groupX = GroupIndex(x?.Group);
var groupY = GroupIndex(y.Group); var groupY = GroupIndex(y?.Group);
if (groupX != groupY) if (groupX != groupY)
{ {
@ -360,11 +363,11 @@ namespace Avalonia.Diagnostics.ViewModels
} }
else 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) switch (group)
{ {

12
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs

@ -12,14 +12,14 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly IVisual _control; private readonly IVisual _control;
private Thickness _borderThickness; private Thickness _borderThickness;
private double _height; private double _height;
private string _heightConstraint; private string? _heightConstraint;
private HorizontalAlignment _horizontalAlignment; private HorizontalAlignment _horizontalAlignment;
private Thickness _marginThickness; private Thickness _marginThickness;
private Thickness _paddingThickness; private Thickness _paddingThickness;
private bool _updatingFromControl; private bool _updatingFromControl;
private VerticalAlignment _verticalAlignment; private VerticalAlignment _verticalAlignment;
private double _width; private double _width;
private string _widthConstraint; private string? _widthConstraint;
public ControlLayoutViewModel(IVisual control) public ControlLayoutViewModel(IVisual control)
{ {
@ -80,13 +80,13 @@ namespace Avalonia.Diagnostics.ViewModels
private set => RaiseAndSetIfChanged(ref _height, value); private set => RaiseAndSetIfChanged(ref _height, value);
} }
public string WidthConstraint public string? WidthConstraint
{ {
get => _widthConstraint; get => _widthConstraint;
private set => RaiseAndSetIfChanged(ref _widthConstraint, value); private set => RaiseAndSetIfChanged(ref _widthConstraint, value);
} }
public string HeightConstraint public string? HeightConstraint
{ {
get => _heightConstraint; get => _heightConstraint;
private set => RaiseAndSetIfChanged(ref _heightConstraint, value); private set => RaiseAndSetIfChanged(ref _heightConstraint, value);
@ -112,7 +112,7 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
if (_control is IAvaloniaObject ao) if (_control is IAvaloniaObject ao)
{ {
string CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty) string? CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty)
{ {
bool hasMin = ao.IsSet(minProperty); bool hasMin = ao.IsSet(minProperty);
bool hasMax = ao.IsSet(maxProperty); 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 try
{ {

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventOwnerTreeNode.cs

@ -27,7 +27,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (_updateChildren && value != null) if (_updateChildren && value != null)
{ {
foreach (var child in Children) foreach (var child in Children!)
{ {
try try
{ {

23
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs

@ -11,16 +11,13 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
private readonly EventsPageViewModel _parentViewModel; private readonly EventsPageViewModel _parentViewModel;
private bool _isRegistered; private bool _isRegistered;
private FiredEvent _currentEvent; private FiredEvent? _currentEvent;
public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsPageViewModel vm) public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsPageViewModel vm)
: base(parent, @event.Name) : base(parent, @event.Name)
{ {
Contract.Requires<ArgumentNullException>(@event != null); Event = @event ?? throw new ArgumentNullException(nameof(@event));
Contract.Requires<ArgumentNullException>(vm != null); _parentViewModel = vm ?? throw new ArgumentNullException(nameof(vm));
Event = @event;
_parentViewModel = vm;
} }
public RoutedEvent Event { get; } 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) if (!_isRegistered || IsEnabled == false)
return; return;
if (sender is IVisual v && BelongsToDevTool(v)) if (sender is IVisual v && BelongsToDevTool(v))
return; return;
var s = sender; var s = sender!;
var handled = e.Handled; var handled = e.Handled;
var route = e.Route; var route = e.Route;
Action handler = delegate void handler()
{ {
if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e)) if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e))
{ {
@ -98,14 +95,16 @@ namespace Avalonia.Diagnostics.ViewModels
private static bool BelongsToDevTool(IVisual v) 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; return true;
} }
v = v.VisualParent; current = current.VisualParent;
} }
return false; return false;

6
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNodeBase.cs

@ -10,14 +10,14 @@ namespace Avalonia.Diagnostics.ViewModels
private bool? _isEnabled = false; private bool? _isEnabled = false;
private bool _isVisible; private bool _isVisible;
protected EventTreeNodeBase(EventTreeNodeBase parent, string text) protected EventTreeNodeBase(EventTreeNodeBase? parent, string text)
{ {
Parent = parent; Parent = parent;
Text = text; Text = text;
IsVisible = true; IsVisible = true;
} }
public IAvaloniaReadOnlyList<EventTreeNodeBase> Children public IAvaloniaReadOnlyList<EventTreeNodeBase>? Children
{ {
get; get;
protected set; protected set;
@ -41,7 +41,7 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _isVisible, value); set => RaiseAndSetIfChanged(ref _isVisible, value);
} }
public EventTreeNodeBase Parent public EventTreeNodeBase? Parent
{ {
get; get;
} }

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

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Diagnostics.Models; using Avalonia.Diagnostics.Models;
@ -23,8 +22,8 @@ namespace Avalonia.Diagnostics.ViewModels
}; };
private readonly MainViewModel _mainViewModel; private readonly MainViewModel _mainViewModel;
private FiredEvent _selectedEvent; private FiredEvent? _selectedEvent;
private EventTreeNodeBase _selectedNode; private EventTreeNodeBase? _selectedNode;
public EventsPageViewModel(MainViewModel mainViewModel) public EventsPageViewModel(MainViewModel mainViewModel)
{ {
@ -48,13 +47,13 @@ namespace Avalonia.Diagnostics.ViewModels
public ObservableCollection<FiredEvent> RecordedEvents { get; } = new ObservableCollection<FiredEvent>(); public ObservableCollection<FiredEvent> RecordedEvents { get; } = new ObservableCollection<FiredEvent>();
public FiredEvent SelectedEvent public FiredEvent? SelectedEvent
{ {
get => _selectedEvent; get => _selectedEvent;
set => RaiseAndSetIfChanged(ref _selectedEvent, value); set => RaiseAndSetIfChanged(ref _selectedEvent, value);
} }
public EventTreeNodeBase SelectedNode public EventTreeNodeBase? SelectedNode
{ {
get => _selectedNode; get => _selectedNode;
set => RaiseAndSetIfChanged(ref _selectedNode, value); set => RaiseAndSetIfChanged(ref _selectedNode, value);
@ -99,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) if (node is EventTreeNode eventNode && eventNode.Event == eventType)
{ {

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

@ -11,10 +11,13 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly Dictionary<string, string> _errors = new Dictionary<string, string>(); private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();
private string _filterString = string.Empty; private string _filterString = string.Empty;
private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter; private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter;
private string _processedFilter; private Regex? _filterRegex;
private Regex _filterRegex;
public event EventHandler RefreshFilter; public event EventHandler? RefreshFilter;
public bool HasErrors => _errors.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public bool Filter(string input) public bool Filter(string input)
{ {
@ -31,13 +34,11 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
_processedFilter = FilterString.Trim();
try try
{ {
var options = RegexOptions.Compiled; var options = RegexOptions.Compiled;
var pattern = UseRegexFilter var pattern = UseRegexFilter
? _processedFilter : Regex.Escape(_processedFilter); ? FilterString.Trim() : Regex.Escape(FilterString.Trim());
if (!UseCaseSensitiveFilter) if (!UseCaseSensitiveFilter)
{ {
options |= RegexOptions.IgnoreCase; options |= RegexOptions.IgnoreCase;
@ -109,16 +110,13 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public IEnumerable GetErrors(string propertyName) public IEnumerable GetErrors(string? propertyName)
{ {
if (_errors.TryGetValue(propertyName, out var error)) if (propertyName != null
&& _errors.TryGetValue(propertyName, out var error))
{ {
yield return error; yield return error;
} }
} }
public bool HasErrors => _errors.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
} }
} }

15
src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs

@ -8,15 +8,12 @@ namespace Avalonia.Diagnostics.ViewModels
internal class FiredEvent : ViewModelBase internal class FiredEvent : ViewModelBase
{ {
private readonly RoutedEventArgs _eventArgs; private readonly RoutedEventArgs _eventArgs;
private EventChainLink _handledBy; private EventChainLink? _handledBy;
public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator) public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator)
{ {
Contract.Requires<ArgumentNullException>(eventArgs != null); _eventArgs = eventArgs ?? throw new ArgumentNullException(nameof(eventArgs));
Contract.Requires<ArgumentNullException>(originator != null); Originator = originator ?? throw new ArgumentNullException(nameof(originator));
_eventArgs = eventArgs;
Originator = originator;
AddToChain(originator); AddToChain(originator);
} }
@ -25,7 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels
return e == _eventArgs; return e == _eventArgs;
} }
public RoutedEvent Event => _eventArgs.RoutedEvent; public RoutedEvent Event => _eventArgs.RoutedEvent!;
public bool IsHandled => HandledBy?.Handled == true; public bool IsHandled => HandledBy?.Handled == true;
@ -38,7 +35,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (IsHandled) if (IsHandled)
{ {
return $"{Event.Name} on {Originator.HandlerName};" + Environment.NewLine + 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}"; return $"{Event.Name} on {Originator.HandlerName}; strategies: {Event.RoutingStrategies}";
@ -47,7 +44,7 @@ namespace Avalonia.Diagnostics.ViewModels
public EventChainLink Originator { get; } public EventChainLink Originator { get; }
public EventChainLink HandledBy public EventChainLink? HandledBy
{ {
get => _handledBy; get => _handledBy;
set set

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs

@ -7,22 +7,24 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
internal class LogicalTreeNode : TreeNode internal class LogicalTreeNode : TreeNode
{ {
public LogicalTreeNode(ILogical logical, TreeNode parent) public LogicalTreeNode(ILogical logical, TreeNode? parent)
: base((Control)logical, parent) : base((Control)logical, parent)
{ {
Children = new LogicalTreeNodeCollection(this, logical); Children = new LogicalTreeNodeCollection(this, logical);
} }
public override TreeNodeCollection Children { get; }
public static LogicalTreeNode[] Create(object control) public static LogicalTreeNode[] Create(object control)
{ {
var logical = control as ILogical; var logical = control as ILogical;
return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null; return logical != null ? new[] { new LogicalTreeNode(logical, null) } : Array.Empty<LogicalTreeNode>();
} }
internal class LogicalTreeNodeCollection : TreeNodeCollection internal class LogicalTreeNodeCollection : TreeNodeCollection
{ {
private readonly ILogical _control; private readonly ILogical _control;
private IDisposable _subscription; private IDisposable? _subscription;
public LogicalTreeNodeCollection(TreeNode owner, ILogical control) public LogicalTreeNodeCollection(TreeNode owner, ILogical control)
: base(owner) : base(owner)

23
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -1,10 +1,10 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Diagnostics.Models; using Avalonia.Diagnostics.Models;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
@ -17,13 +17,16 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly IDisposable _pointerOverSubscription; private readonly IDisposable _pointerOverSubscription;
private ViewModelBase _content; private ViewModelBase _content;
private int _selectedTab; private int _selectedTab;
private string _focusedControl; private string? _focusedControl;
private string _pointerOverElement; private string? _pointerOverElement;
private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeMarginPadding = true;
private bool _shouldVisualizeDirtyRects; private bool _shouldVisualizeDirtyRects;
private bool _showFpsOverlay; private bool _showFpsOverlay;
#nullable disable
// Remove "nullable disable" after MemberNotNull will work on our CI.
public MainViewModel(TopLevel root) public MainViewModel(TopLevel root)
#nullable restore
{ {
_root = root; _root = root;
_logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root)); _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root));
@ -84,6 +87,7 @@ namespace Avalonia.Diagnostics.ViewModels
public ViewModelBase Content public ViewModelBase Content
{ {
get { return _content; } get { return _content; }
// [MemberNotNull(nameof(_content))]
private set private set
{ {
if (_content is TreePageViewModel oldTree && if (_content is TreePageViewModel oldTree &&
@ -114,34 +118,35 @@ namespace Avalonia.Diagnostics.ViewModels
public int SelectedTab public int SelectedTab
{ {
get { return _selectedTab; } get { return _selectedTab; }
// [MemberNotNull(nameof(_content))]
set set
{ {
_selectedTab = value; _selectedTab = value;
switch (value) switch (value)
{ {
case 0:
Content = _logicalTree;
break;
case 1: case 1:
Content = _visualTree; Content = _visualTree;
break; break;
case 2: case 2:
Content = _events; Content = _events;
break; break;
default:
Content = _logicalTree;
break;
} }
RaisePropertyChanged(); RaisePropertyChanged();
} }
} }
public string FocusedControl public string? FocusedControl
{ {
get { return _focusedControl; } get { return _focusedControl; }
private set { RaiseAndSetIfChanged(ref _focusedControl, value); } private set { RaiseAndSetIfChanged(ref _focusedControl, value); }
} }
public string PointerOverElement public string? PointerOverElement
{ {
get { return _pointerOverElement; } get { return _pointerOverElement; }
private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
@ -187,7 +192,7 @@ namespace Avalonia.Diagnostics.ViewModels
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; 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)) if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
{ {

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs

@ -18,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels
public abstract string Value { get; set; } public abstract string Value { get; set; }
public abstract void Update(); public abstract void Update();
protected static string ConvertToString(object value) protected static string ConvertToString(object? value)
{ {
if (value is null) if (value is null)
{ {
@ -31,13 +31,13 @@ namespace Avalonia.Diagnostics.ViewModels
if (!converter.CanConvertTo(typeof(string)) || if (!converter.CanConvertTo(typeof(string)) ||
converter.GetType() == typeof(CollectionConverter)) converter.GetType() == typeof(CollectionConverter))
{ {
return value.ToString(); return value.ToString() ?? "(null)";
} }
return converter.ConvertToString(value); 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); var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null);
@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels
throw new InvalidCastException("Unable to convert value."); 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); var converter = TypeDescriptor.GetConverter(targetType);

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs

@ -8,7 +8,7 @@ namespace Avalonia.Diagnostics.ViewModels
public IBrush Tint { get; } 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; Key = resourceKey;
Tint = isDynamic ? Brushes.Orange : Brushes.Brown; Tint = isDynamic ? Brushes.Orange : Brushes.Brown;
@ -16,12 +16,14 @@ namespace Avalonia.Diagnostics.ViewModels
public void CopyResourceKey() public void CopyResourceKey()
{ {
if (Key is null) var textToCopy = Key?.ToString();
if (textToCopy is null)
{ {
return; return;
} }
CopyToClipboard(Key.ToString()); CopyToClipboard(textToCopy);
} }
} }
} }

10
src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs

@ -11,7 +11,7 @@ namespace Avalonia.Diagnostics.ViewModels
public string Name { get; } public string Name { get; }
public object Value { get; } public object? Value { get; }
public bool IsActive public bool IsActive
{ {
@ -25,7 +25,7 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _isVisible, value); set => RaiseAndSetIfChanged(ref _isVisible, value);
} }
public SetterViewModel(AvaloniaProperty property, object value) public SetterViewModel(AvaloniaProperty property, object? value)
{ {
Property = property; Property = property;
Name = property.Name; Name = property.Name;
@ -36,12 +36,14 @@ namespace Avalonia.Diagnostics.ViewModels
public void CopyValue() public void CopyValue()
{ {
if (Value is null) var textToCopy = Value?.ToString();
if (textToCopy is null)
{ {
return; return;
} }
CopyToClipboard(Value.ToString()); CopyToClipboard(textToCopy);
} }
public void CopyPropertyName() public void CopyPropertyName()

16
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@ -9,17 +9,18 @@ using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels 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 string _classes;
private bool _isExpanded; private bool _isExpanded;
public TreeNode(IVisual visual, TreeNode parent) public TreeNode(IVisual visual, TreeNode? parent)
{ {
Parent = parent; Parent = parent;
Type = visual.GetType().Name; Type = visual.GetType().Name;
Visual = visual; Visual = visual;
_classes = string.Empty;
if (visual is IControl control) if (visual is IControl control)
{ {
@ -51,10 +52,9 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public TreeNodeCollection Children public abstract TreeNodeCollection Children
{ {
get; get;
protected set;
} }
public string Classes public string Classes
@ -63,7 +63,7 @@ namespace Avalonia.Diagnostics.ViewModels
private set { RaiseAndSetIfChanged(ref _classes, value); } private set { RaiseAndSetIfChanged(ref _classes, value); }
} }
public string ElementName public string? ElementName
{ {
get; get;
} }
@ -79,7 +79,7 @@ namespace Avalonia.Diagnostics.ViewModels
set { RaiseAndSetIfChanged(ref _isExpanded, value); } set { RaiseAndSetIfChanged(ref _isExpanded, value); }
} }
public TreeNode Parent public TreeNode? Parent
{ {
get; get;
} }
@ -92,7 +92,7 @@ namespace Avalonia.Diagnostics.ViewModels
public void Dispose() public void Dispose()
{ {
_classesSubscription.Dispose(); _classesSubscription?.Dispose();
Children.Dispose(); Children.Dispose();
} }

39
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs

@ -3,46 +3,33 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Collections; using Avalonia.Collections;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable
{ {
private AvaloniaList<TreeNode> _inner; private AvaloniaList<TreeNode>? _inner;
public TreeNodeCollection(TreeNode owner) => Owner = owner; public TreeNodeCollection(TreeNode owner) => Owner = owner;
public TreeNode this[int index] public TreeNode this[int index] => EnsureInitialized()[index];
{
get
{
EnsureInitialized();
return _inner[index];
}
}
public int Count public int Count => EnsureInitialized().Count;
{
get
{
EnsureInitialized();
return _inner.Count;
}
}
protected TreeNode Owner { get; } protected TreeNode Owner { get; }
public event NotifyCollectionChangedEventHandler CollectionChanged public event NotifyCollectionChangedEventHandler? CollectionChanged
{ {
add => _inner.CollectionChanged += value; add => EnsureInitialized().CollectionChanged += value;
remove => _inner.CollectionChanged -= value; remove => EnsureInitialized().CollectionChanged -= value;
} }
public event PropertyChangedEventHandler PropertyChanged public event PropertyChangedEventHandler? PropertyChanged
{ {
add => _inner.PropertyChanged += value; add => EnsureInitialized().PropertyChanged += value;
remove => _inner.PropertyChanged -= value; remove => EnsureInitialized().PropertyChanged -= value;
} }
public virtual void Dispose() public virtual void Dispose()
@ -58,21 +45,21 @@ namespace Avalonia.Diagnostics.ViewModels
public IEnumerator<TreeNode> GetEnumerator() public IEnumerator<TreeNode> GetEnumerator()
{ {
EnsureInitialized(); return EnsureInitialized().GetEnumerator();
return _inner.GetEnumerator();
} }
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
protected abstract void Initialize(AvaloniaList<TreeNode> nodes); protected abstract void Initialize(AvaloniaList<TreeNode> nodes);
private void EnsureInitialized() private AvaloniaList<TreeNode> EnsureInitialized()
{ {
if (_inner is null) if (_inner is null)
{ {
_inner = new AvaloniaList<TreeNode>(); _inner = new AvaloniaList<TreeNode>();
Initialize(_inner); Initialize(_inner);
} }
return _inner;
} }
} }
} }

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

@ -6,8 +6,8 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
internal class TreePageViewModel : ViewModelBase, IDisposable internal class TreePageViewModel : ViewModelBase, IDisposable
{ {
private TreeNode _selectedNode; private TreeNode? _selectedNode;
private ControlDetailsViewModel _details; private ControlDetailsViewModel? _details;
public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes) public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes)
{ {
@ -29,7 +29,7 @@ namespace Avalonia.Diagnostics.ViewModels
public TreeNode[] Nodes { get; protected set; } public TreeNode[] Nodes { get; protected set; }
public TreeNode SelectedNode public TreeNode? SelectedNode
{ {
get => _selectedNode; get => _selectedNode;
private set private set
@ -44,7 +44,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public ControlDetailsViewModel Details public ControlDetailsViewModel? Details
{ {
get => _details; get => _details;
private set private set
@ -68,7 +68,7 @@ namespace Avalonia.Diagnostics.ViewModels
_details?.Dispose(); _details?.Dispose();
} }
public TreeNode FindNode(IControl control) public TreeNode? FindNode(IControl control)
{ {
foreach (var node in Nodes) foreach (var node in Nodes)
{ {
@ -104,7 +104,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
private void ExpandNode(TreeNode node) private void ExpandNode(TreeNode? node)
{ {
if (node != null) if (node != null)
{ {
@ -113,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) if (node.Visual == control)
{ {

12
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.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class ViewModelBase : INotifyPropertyChanged internal class ViewModelBase : INotifyPropertyChanged
{ {
private PropertyChangedEventHandler _propertyChanged; private PropertyChangedEventHandler? _propertyChanged;
private List<string> events = new List<string>(); private List<string> events = new List<string>();
public event PropertyChangedEventHandler PropertyChanged public event PropertyChangedEventHandler? PropertyChanged
{ {
add { _propertyChanged += value; events.Add("added"); } add { _propertyChanged += value; events.Add("added"); }
remove { _propertyChanged -= value; events.Add("removed"); } remove { _propertyChanged -= value; events.Add("removed"); }
@ -20,7 +20,7 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
} }
protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null) protected bool RaiseAndSetIfChanged<T>([NotNullIfNotNull("value")] ref T field, T value, [CallerMemberName] string propertyName = null!)
{ {
if (!EqualityComparer<T>.Default.Equals(field, value)) if (!EqualityComparer<T>.Default.Equals(field, value))
{ {
@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.ViewModels
return false; return false;
} }
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) protected void RaisePropertyChanged([CallerMemberName] string propertyName = null!)
{ {
var e = new PropertyChangedEventArgs(propertyName); var e = new PropertyChangedEventArgs(propertyName);
OnPropertyChanged(e); OnPropertyChanged(e);

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs

@ -7,7 +7,7 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
internal class VisualTreeNode : TreeNode internal class VisualTreeNode : TreeNode
{ {
public VisualTreeNode(IVisual visual, TreeNode parent) public VisualTreeNode(IVisual visual, TreeNode? parent)
: base(visual, parent) : base(visual, parent)
{ {
Children = new VisualTreeNodeCollection(this, visual); Children = new VisualTreeNodeCollection(this, visual);
@ -20,16 +20,18 @@ namespace Avalonia.Diagnostics.ViewModels
public bool IsInTemplate { get; private set; } public bool IsInTemplate { get; private set; }
public override TreeNodeCollection Children { get; }
public static VisualTreeNode[] Create(object control) public static VisualTreeNode[] Create(object control)
{ {
var visual = control as IVisual; var visual = control as IVisual;
return visual != null ? new[] { new VisualTreeNode(visual, null) } : null; return visual != null ? new[] { new VisualTreeNode(visual, null) } : Array.Empty<VisualTreeNode>();
} }
internal class VisualTreeNodeCollection : TreeNodeCollection internal class VisualTreeNodeCollection : TreeNodeCollection
{ {
private readonly IVisual _control; private readonly IVisual _control;
private IDisposable _subscription; private IDisposable? _subscription;
public VisualTreeNodeCollection(TreeNode owner, IVisual control) public VisualTreeNodeCollection(TreeNode owner, IVisual control)
: base(owner) : base(owner)

14
src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs

@ -30,22 +30,26 @@ namespace Avalonia.Diagnostics.Views
AvaloniaXamlLoader.Load(this); 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); 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) switch (e.Key)
{ {
case Key.Enter: case Key.Enter:
vm.Execute(); _ = vm.Execute();
e.Handled = true; e.Handled = true;
break; break;
case Key.Up: case Key.Up:

2
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<FiredEvent> events) if (sender is ObservableCollection<FiredEvent> events)
{ {

8
src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs

@ -27,7 +27,11 @@ namespace Avalonia.Diagnostics.Views
public void ToggleConsole() public void ToggleConsole()
{ {
var vm = (MainViewModel)DataContext; var vm = (MainViewModel?)DataContext;
if (vm is null)
{
return;
}
if (_consoleHeight == -1) if (_consoleHeight == -1)
{ {
@ -54,7 +58,7 @@ namespace Avalonia.Diagnostics.Views
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void PreviewKeyDown(object sender, KeyEventArgs e) private void PreviewKeyDown(object? sender, KeyEventArgs e)
{ {
if (e.Key == Key.Escape) if (e.Key == Key.Escape)
{ {

33
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.Views
internal class MainWindow : Window, IStyleHost internal class MainWindow : Window, IStyleHost
{ {
private readonly IDisposable _keySubscription; private readonly IDisposable _keySubscription;
private TopLevel _root; private TopLevel? _root;
public MainWindow() public MainWindow()
{ {
@ -26,7 +26,7 @@ namespace Avalonia.Diagnostics.Views
.Subscribe(RawKeyDown); .Subscribe(RawKeyDown);
} }
public TopLevel Root public TopLevel? Root
{ {
get => _root; get => _root;
set set
@ -43,7 +43,7 @@ namespace Avalonia.Diagnostics.Views
if (_root != null) if (_root != null)
{ {
_root.Closed += RootClosed; _root.Closed += RootClosed;
DataContext = new MainViewModel(value); DataContext = new MainViewModel(_root);
} }
else else
{ {
@ -53,15 +53,20 @@ namespace Avalonia.Diagnostics.Views
} }
} }
IStyleHost IStyleHost.StylingParent => null; IStyleHost? IStyleHost.StylingParent => null;
protected override void OnClosed(EventArgs e) protected override void OnClosed(EventArgs e)
{ {
base.OnClosed(e); base.OnClosed(e);
_keySubscription.Dispose(); _keySubscription.Dispose();
_root.Closed -= RootClosed;
_root = null; if (_root != null)
((MainViewModel)DataContext)?.Dispose(); {
_root.Closed -= RootClosed;
_root = null;
}
((MainViewModel?)DataContext)?.Dispose();
} }
private void InitializeComponent() private void InitializeComponent()
@ -71,12 +76,20 @@ namespace Avalonia.Diagnostics.Views
private void RawKeyDown(RawKeyEventArgs e) private void RawKeyDown(RawKeyEventArgs e)
{ {
var vm = (MainViewModel?)DataContext;
if (vm is null)
{
return;
}
const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift; const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift;
if (e.Modifiers == modifiers) if (e.Modifiers == modifiers)
{ {
#pragma warning disable CS0618 // Type or member is obsolete
var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default; var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default;
#pragma warning restore CS0618 // Type or member is obsolete
var control = Root.GetVisualsAt(point, x => var control = Root.GetVisualsAt(point, x =>
{ {
if (x is AdornerLayer || !x.IsVisible) return false; if (x is AdornerLayer || !x.IsVisible) return false;
@ -87,7 +100,6 @@ namespace Avalonia.Diagnostics.Views
if (control != null) if (control != null)
{ {
var vm = (MainViewModel)DataContext;
vm.SelectControl((IControl)control); vm.SelectControl((IControl)control);
} }
} }
@ -97,12 +109,11 @@ namespace Avalonia.Diagnostics.Views
{ {
var enable = e.Key == Key.S; var enable = e.Key == Key.S;
var vm = (MainViewModel)DataContext;
vm.EnableSnapshotStyles(enable); vm.EnableSnapshotStyles(enable);
} }
} }
} }
private void RootClosed(object sender, EventArgs e) => Close(); private void RootClosed(object? sender, EventArgs e) => Close();
} }
} }

24
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs

@ -14,12 +14,13 @@ namespace Avalonia.Diagnostics.Views
internal class TreePageView : UserControl internal class TreePageView : UserControl
{ {
private readonly Panel _adorner; private readonly Panel _adorner;
private AdornerLayer _currentLayer; private AdornerLayer? _currentLayer;
private TreeView _tree; private TreeView _tree;
public TreePageView() public TreePageView()
{ {
InitializeComponent(); InitializeComponent();
_tree = this.FindControl<TreeView>("tree");
_tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized; _tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized;
_adorner = new Panel _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; var visual = (Visual)node.Visual;
_currentLayer = AdornerLayer.GetAdornerLayer(visual); _currentLayer = AdornerLayer.GetAdornerLayer(visual);
@ -53,8 +60,6 @@ namespace Avalonia.Diagnostics.Views
_currentLayer.Children.Add(_adorner); _currentLayer.Children.Add(_adorner);
AdornerLayer.SetAdornedElement(_adorner, visual); AdornerLayer.SetAdornedElement(_adorner, visual);
var vm = (TreePageViewModel) DataContext;
if (vm.MainView.ShouldVisualizeMarginPadding) if (vm.MainView.ShouldVisualizeMarginPadding)
{ {
var paddingBorder = (Border)_adorner.Children[0]; 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); 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<Border>()) foreach (var border in _adorner.Children.OfType<Border>())
{ {
@ -90,18 +95,17 @@ namespace Avalonia.Diagnostics.Views
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
_tree = this.FindControl<TreeView>("tree");
} }
private void TreeViewItemMaterialized(object sender, ItemContainerEventArgs e) private void TreeViewItemMaterialized(object? sender, ItemContainerEventArgs e)
{ {
var item = (TreeViewItem)e.Containers[0].ContainerControl; var item = (TreeViewItem)e.Containers[0].ContainerControl;
item.TemplateApplied += TreeViewItemTemplateApplied; 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. // This depends on the default tree item template.
// We want to handle events in the item header but exclude events coming from children. // We want to handle events in the item header but exclude events coming from children.

2
src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs

@ -17,7 +17,7 @@ namespace Avalonia.Diagnostics
private static void PrintVisualTree(IVisual visual, StringBuilder builder, int indent) 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)); builder.Append(Indent(indent - 1));

Loading…
Cancel
Save