Selaa lähdekoodia

Merge branch 'master' of github.com:AvaloniaUI/Avalonia into compiledbinding-methods

Jeremy Koritzinsky 3 vuotta sitten
vanhempi
sitoutus
079f5f673b
92 muutettua tiedostoa jossa 1047 lisäystä ja 400 poistoa
  1. 6 0
      readme.md
  2. 6 2
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  3. 2 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  4. 56 0
      src/Android/Avalonia.Android/Stubs.cs
  5. 10 0
      src/Avalonia.Base/AvaloniaLocator.cs
  6. 1 5
      src/Avalonia.Base/Threading/Dispatcher.cs
  7. 1 67
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  8. 1 1
      src/Avalonia.Controls/Grid.cs
  9. 3 6
      src/Avalonia.Controls/SystemDialog.cs
  10. 1 1
      src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
  11. 12 6
      src/Avalonia.Input/Gestures.cs
  12. 4 5
      src/Avalonia.Input/PointerEventArgs.cs
  13. 35 9
      src/Avalonia.Input/TouchDevice.cs
  14. 3 3
      src/Avalonia.Layout/AttachedLayout.cs
  15. 1 0
      src/Avalonia.Layout/Avalonia.Layout.csproj
  16. 22 24
      src/Avalonia.Layout/ElementManager.cs
  17. 34 34
      src/Avalonia.Layout/FlowLayoutAlgorithm.cs
  18. 2 2
      src/Avalonia.Layout/IFlowLayoutAlgorithmDelegates.cs
  19. 2 2
      src/Avalonia.Layout/LayoutContext.cs
  20. 1 1
      src/Avalonia.Layout/LayoutContextAdapter.cs
  21. 1 1
      src/Avalonia.Layout/LayoutHelper.cs
  22. 1 0
      src/Avalonia.Layout/LayoutQueue.cs
  23. 1 1
      src/Avalonia.Layout/Layoutable.cs
  24. 2 2
      src/Avalonia.Layout/NonVirtualizingLayoutContext.cs
  25. 11 11
      src/Avalonia.Layout/StackLayout.cs
  26. 13 13
      src/Avalonia.Layout/UniformGridLayout.cs
  27. 1 1
      src/Avalonia.Layout/UniformGridLayoutState.cs
  28. 0 5
      src/Avalonia.Layout/Utils/ListUtils.cs
  29. 2 2
      src/Avalonia.Layout/VirtualLayoutContextAdapter.cs
  30. 1 1
      src/Avalonia.Layout/VirtualizingLayoutContext.cs
  31. 1 1
      src/Avalonia.Layout/WrapLayout/UvMeasure.cs
  32. 1 1
      src/Avalonia.Layout/WrapLayout/WrapItem.cs
  33. 3 3
      src/Avalonia.Layout/WrapLayout/WrapLayout.cs
  34. 6 3
      src/Avalonia.Layout/WrapLayout/WrapLayoutState.cs
  35. 7 0
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  36. 5 0
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  37. 1 0
      src/Avalonia.Styling/Avalonia.Styling.csproj
  38. 6 6
      src/Avalonia.Styling/Controls/ChildNameScope.cs
  39. 2 2
      src/Avalonia.Styling/Controls/INameScope.cs
  40. 14 16
      src/Avalonia.Styling/Controls/NameScope.cs
  41. 14 14
      src/Avalonia.Styling/Controls/NameScopeExtensions.cs
  42. 4 4
      src/Avalonia.Styling/Controls/NameScopeLocator.cs
  43. 1 1
      src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs
  44. 1 1
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  45. 3 3
      src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs
  46. 1 1
      src/Avalonia.Styling/INamed.cs
  47. 8 8
      src/Avalonia.Styling/LogicalTree/ControlLocator.cs
  48. 3 3
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  49. 13 13
      src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs
  50. 2 5
      src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs
  51. 9 9
      src/Avalonia.Styling/StyledElement.cs
  52. 1 1
      src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs
  53. 1 1
      src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
  54. 3 3
      src/Avalonia.Styling/Styling/ChildSelector.cs
  55. 1 1
      src/Avalonia.Styling/Styling/OrSelector.cs
  56. 6 1
      src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs
  57. 29 21
      src/Avalonia.Styling/Styling/Selectors.cs
  58. 7 7
      src/Avalonia.Styling/Styling/Styles.cs
  59. 3 3
      src/Avalonia.Styling/Styling/TemplateSelector.cs
  60. 5 1
      src/Avalonia.Themes.Default/CalendarDatePicker.xaml
  61. 5 1
      src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml
  62. 1 3
      src/Avalonia.Visuals/Animation/RenderLoopClock.cs
  63. 1 2
      src/Avalonia.Visuals/Media/CombinedGeometry.cs
  64. 1 2
      src/Avalonia.Visuals/Media/EllipseGeometry.cs
  65. 2 5
      src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs
  66. 1 2
      src/Avalonia.Visuals/Media/FormattedText.cs
  67. 1 2
      src/Avalonia.Visuals/Media/GeometryGroup.cs
  68. 1 2
      src/Avalonia.Visuals/Media/GlyphRun.cs
  69. 1 2
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  70. 1 2
      src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
  71. 1 2
      src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs
  72. 1 2
      src/Avalonia.Visuals/Media/LineGeometry.cs
  73. 1 2
      src/Avalonia.Visuals/Media/PathGeometry.cs
  74. 1 2
      src/Avalonia.Visuals/Media/PolylineGeometry.cs
  75. 1 2
      src/Avalonia.Visuals/Media/RectangleGeometry.cs
  76. 1 2
      src/Avalonia.Visuals/Media/StreamGeometry.cs
  77. 1 2
      src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs
  78. 1 1
      src/Avalonia.Visuals/Visual.cs
  79. 2 4
      src/Avalonia.X11/X11CursorFactory.cs
  80. 70 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  81. 2 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  82. 66 2
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  83. 53 8
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
  84. 2 2
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  85. 1 2
      src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
  86. 1 2
      src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs
  87. 1 2
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  88. 5 1
      tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs
  89. 272 0
      tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs
  90. 36 0
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs
  91. 49 0
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs
  92. 67 0
      tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs

+ 6 - 0
readme.md

@@ -75,6 +75,12 @@ For more information see the [.NET Foundation Code of Conduct](https://dotnetfou
 
 Avalonia is licenced under the [MIT licence](licence.md).
 
+## Support Avalonia
+
+**BTC**: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx
+
+This will be shared with the community and awarded for significant contributions.
+
 ### Backers
 
 Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)]

+ 6 - 2
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@@ -32,7 +32,7 @@
     <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
     <BundleAssemblies>False</BundleAssemblies>
     <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
-    <AndroidSupportedAbis>armeabi-v7a;x86</AndroidSupportedAbis>
+    <AndroidSupportedAbis>armeabi-v7a;x86;x86_64</AndroidSupportedAbis>
     <Debugger>Xamarin</Debugger>
     <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
     <AotAssemblies>False</AotAssemblies>
@@ -51,7 +51,7 @@
     <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
     <BundleAssemblies>False</BundleAssemblies>
     <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
-    <AndroidSupportedAbis>armeabi-v7a,x86</AndroidSupportedAbis>
+    <AndroidSupportedAbis>armeabi-v7a,x86;x86_64</AndroidSupportedAbis>
     <Debugger>Xamarin</Debugger>
     <AotAssemblies>False</AotAssemblies>
     <EnableLLVM>False</EnableLLVM>
@@ -125,6 +125,10 @@
       <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
       <Name>Avalonia.Layout</Name>
     </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj">
+      <Project>{c42d2fc1-a531-4ed4-84b9-89aec7c962fc}</Project>
+      <Name>Avalonia.Themes.Fluent</Name>
+    </ProjectReference>
     <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
       <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
       <Name>Avalonia.Visuals</Name>

+ 2 - 1
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -43,11 +43,12 @@ namespace Avalonia.Android
             AvaloniaLocator.CurrentMutable
                 .Bind<IClipboard>().ToTransient<ClipboardImpl>()
                 .Bind<ICursorFactory>().ToTransient<CursorFactory>()
+                .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
                 .Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
                 .Bind<IPlatformSettings>().ToConstant(Instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
                 .Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
-                .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
+                .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
                 .Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
                 .Bind<IRenderLoop>().ToConstant(new RenderLoop())
                 .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()

+ 56 - 0
src/Android/Avalonia.Android/Stubs.cs

@@ -0,0 +1,56 @@
+using System;
+using System.IO;
+using Avalonia.Platform;
+
+namespace Avalonia.Android
+{
+    class WindowingPlatformStub : IWindowingPlatform
+    {
+        public IWindowImpl CreateWindow() => throw new NotSupportedException();
+
+        public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException();
+
+        public ITrayIconImpl CreateTrayIcon() => null;
+    }
+
+    class PlatformIconLoaderStub : IPlatformIconLoader
+    {
+        public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
+        {
+            using (var stream = new MemoryStream())
+            {
+                bitmap.Save(stream);
+                return LoadIcon(stream);
+            }
+        }
+
+        public IWindowIconImpl LoadIcon(Stream stream)
+        {
+            var ms = new MemoryStream();
+            stream.CopyTo(ms);
+            return new IconStub(ms);
+        }
+
+        public IWindowIconImpl LoadIcon(string fileName)
+        {
+            using (var file = File.Open(fileName, FileMode.Open))
+                return LoadIcon(file);
+        }
+    }
+
+    public class IconStub : IWindowIconImpl
+    {
+        private readonly MemoryStream _ms;
+
+        public IconStub(MemoryStream stream)
+        {
+            _ms = stream;
+        }
+
+        public void Save(Stream outputStream)
+        {
+            _ms.Position = 0;
+            _ms.CopyTo(outputStream);
+        }
+    }
+}

+ 10 - 0
src/Avalonia.Base/AvaloniaLocator.cs

@@ -125,6 +125,16 @@ namespace Avalonia
         {
             return (T?) resolver.GetService(typeof (T));
         }
+
+        public static object GetRequiredService(this IAvaloniaDependencyResolver resolver, Type t)
+        {
+            return resolver.GetService(t) ?? throw new InvalidOperationException($"Unable to locate '{t}'.");
+        }
+
+        public static T GetRequiredService<T>(this IAvaloniaDependencyResolver resolver)
+        {
+            return (T?)resolver.GetService(typeof(T)) ?? throw new InvalidOperationException($"Unable to locate '{typeof(T)}'.");
+        }
     }
 }
 

+ 1 - 5
src/Avalonia.Base/Threading/Dispatcher.cs

@@ -56,11 +56,7 @@ namespace Avalonia.Threading
         /// </param>
         public void MainLoop(CancellationToken cancellationToken)
         {
-            var platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
-
-            if (platform is null)
-                throw new InvalidOperationException("Unable to locate IPlatformThreadingInterface");
-
+            var platform = AvaloniaLocator.Current.GetRequiredService<IPlatformThreadingInterface>();
             cancellationToken.Register(() => platform.Signal(DispatcherPriority.Send));
             platform.RunLoop(cancellationToken);
         }

+ 1 - 67
src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

@@ -1,4 +1,4 @@
-// (c) Copyright Microsoft Corporation.
+// (c) Copyright Microsoft Corporation.
 // This source is subject to the Microsoft Public License (Ms-PL).
 // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 // All other rights reserved.
@@ -425,9 +425,6 @@ namespace Avalonia.Controls
         {
             FocusableProperty.OverrideDefaultValue<CalendarDatePicker>(true);
 
-            DisplayDateProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnDisplayDateChanged(e));
-            DisplayDateStartProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnDisplayDateStartChanged(e));
-            DisplayDateEndProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnDisplayDateEndChanged(e));
             IsDropDownOpenProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnIsDropDownOpenChanged(e));
             SelectedDateProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnSelectedDateChanged(e));
             SelectedDateFormatProperty.Changed.AddClassHandler<CalendarDatePicker>((x,e) => x.OnSelectedDateFormatChanged(e));
@@ -459,18 +456,12 @@ namespace Avalonia.Controls
             if (_calendar != null)
             {
                 _calendar.SelectionMode = CalendarSelectionMode.SingleDate;
-                _calendar.SelectedDate = SelectedDate;
-                SetCalendarDisplayDate(DisplayDate);
-                SetCalendarDisplayDateStart(DisplayDateStart);
-                SetCalendarDisplayDateEnd(DisplayDateEnd);
 
                 _calendar.DayButtonMouseUp += Calendar_DayButtonMouseUp;
                 _calendar.DisplayDateChanged += Calendar_DisplayDateChanged;
                 _calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged;
                 _calendar.PointerReleased += Calendar_PointerReleased;
                 _calendar.KeyDown += Calendar_KeyDown;
-                //_calendar.SizeChanged += new SizeChangedEventHandler(Calendar_SizeChanged);
-                //_calendar.IsTabStop = true;
 
                 var currentBlackoutDays = BlackoutDates;
                 BlackoutDates = _calendar.BlackoutDates;
@@ -587,58 +578,6 @@ namespace Avalonia.Controls
             SetSelectedDate();
         }
         
-        private void SetCalendarDisplayDate(DateTime value)
-        {
-            if (DateTimeHelper.CompareYearMonth(_calendar.DisplayDate, value) != 0)
-            {
-                _calendar.DisplayDate = DisplayDate;
-                if (DateTime.Compare(_calendar.DisplayDate, DisplayDate) != 0)
-                {
-                    DisplayDate = _calendar.DisplayDate;
-                }
-            }
-        }
-        private void OnDisplayDateChanged(AvaloniaPropertyChangedEventArgs e)
-        {
-            if (_calendar != null)
-            {
-                var value = (DateTime)e.NewValue;
-                SetCalendarDisplayDate(value);
-            }
-        }
-        private void SetCalendarDisplayDateStart(DateTime? value)
-        {
-            _calendar.DisplayDateStart = value;
-            if (_calendar.DisplayDateStart.HasValue && DisplayDateStart.HasValue && DateTime.Compare(_calendar.DisplayDateStart.Value, DisplayDateStart.Value) != 0)
-            {
-                DisplayDateStart = _calendar.DisplayDateStart;
-            }
-        }
-        private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e)
-        {
-            if (_calendar != null)
-            {
-                var value = (DateTime?)e.NewValue;
-                SetCalendarDisplayDateStart(value);
-            }
-        }
-        private void SetCalendarDisplayDateEnd(DateTime? value)
-        {
-            _calendar.DisplayDateEnd = value;
-            if (_calendar.DisplayDateEnd.HasValue && DisplayDateEnd.HasValue && DateTime.Compare(_calendar.DisplayDateEnd.Value, DisplayDateEnd.Value) != 0)
-            {
-                DisplayDateEnd = _calendar.DisplayDateEnd;
-            }
-
-        }
-        private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e)
-        {
-            if (_calendar != null)
-            {
-                var value = (DateTime?)e.NewValue;
-                SetCalendarDisplayDateEnd(value);
-            }
-        }
         private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e)
         {
             var oldValue = (bool)e.OldValue;
@@ -670,11 +609,6 @@ namespace Avalonia.Controls
             var addedDate = (DateTime?)e.NewValue;
             var removedDate = (DateTime?)e.OldValue;
 
-            if (_calendar != null && addedDate != _calendar.SelectedDate)
-            {
-                _calendar.SelectedDate = addedDate;
-            }
-
             if (SelectedDate != null)
             {
                 DateTime day = SelectedDate.Value;

+ 1 - 1
src/Avalonia.Controls/Grid.cs

@@ -978,7 +978,7 @@ namespace Avalonia.Controls
         /// width is not registered in columns.</param>
         /// <param name="forceInfinityV">Passed through to MeasureCell.
         /// When "true" cells' desired height is not registered in rows.</param>
-        /// <param name="hasDesiredSizeUChanged">return true when desired size has changed</param>
+        /// <param name="hasDesiredSizeUChanged">When the method exits, indicates whether the desired size has changed.</param>
         private void MeasureCellsGroup(
             int cellsHead,
             Size referenceSize,

+ 3 - 6
src/Avalonia.Controls/SystemDialog.cs

@@ -66,8 +66,7 @@ namespace Avalonia.Controls
         {
             if(parent == null)
                 throw new ArgumentNullException(nameof(parent));
-            var service = AvaloniaLocator.Current.GetService<ISystemDialogImpl>() ??
-                throw new InvalidOperationException("Unable to locate ISystemDialogImpl.");
+            var service = AvaloniaLocator.Current.GetRequiredService<ISystemDialogImpl>();
             return (await service.ShowFileDialogAsync(this, parent) ??
              Array.Empty<string>()).FirstOrDefault();
         }
@@ -95,8 +94,7 @@ namespace Avalonia.Controls
         {
             if(parent == null)
                 throw new ArgumentNullException(nameof(parent));
-            var service = AvaloniaLocator.Current.GetService<ISystemDialogImpl>() ??
-                throw new InvalidOperationException("Unable to locate ISystemDialogImpl.");
+            var service = AvaloniaLocator.Current.GetRequiredService<ISystemDialogImpl>();
             return service.ShowFileDialogAsync(this, parent);
         }
     }
@@ -125,8 +123,7 @@ namespace Avalonia.Controls
         {
             if(parent == null)
                 throw new ArgumentNullException(nameof(parent));
-            var service = AvaloniaLocator.Current.GetService<ISystemDialogImpl>() ??
-                throw new InvalidOperationException("Unable to locate ISystemDialogImpl.");
+            var service = AvaloniaLocator.Current.GetRequiredService<ISystemDialogImpl>();
             return service.ShowFolderDialogAsync(this, parent);
         }
     }

+ 1 - 1
src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs

@@ -85,7 +85,7 @@ namespace Avalonia.Input.GestureRecognizers
         {
             if (e.Pointer == _tracking)
             {
-                var rootPoint = e.GetPosition(null);
+                var rootPoint = e.GetPosition(_target);
                 if (!_scrolling)
                 {
                     if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance)

+ 12 - 6
src/Avalonia.Input/Gestures.cs

@@ -6,6 +6,7 @@ namespace Avalonia.Input
 {
     public static class Gestures
     {
+        private static bool s_isDoubleTapped = false;
         public static readonly RoutedEvent<TappedEventArgs> TappedEvent = RoutedEvent.Register<TappedEventArgs>(
             "Tapped",
             RoutingStrategies.Bubble,
@@ -81,20 +82,23 @@ namespace Avalonia.Input
                 var e = (PointerPressedEventArgs)ev;
                 var visual = (IVisual)ev.Source;
 
-#pragma warning disable CS0618 // Type or member is obsolete
-                var clickCount = e.ClickCount;
-#pragma warning restore CS0618 // Type or member is obsolete
-                if (clickCount <= 1)
+                if (e.ClickCount <= 1)
                 {
+                    s_isDoubleTapped = false;
                     s_lastPress.SetTarget(ev.Source);
                 }
-                else if (clickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
+                else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
                 {
                     if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
                     {
+                        s_isDoubleTapped = true;
                         e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
                     }
                 }
+                else
+                {
+                    s_isDoubleTapped = false;
+                }
             }
         }
 
@@ -112,7 +116,9 @@ namespace Avalonia.Input
                         {
                             e.Source.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
                         }
-                        else
+                        //s_isDoubleTapped needed here to prevent invoking Tapped event when DoubleTapped is called.
+                        //This behaviour matches UWP behaviour.
+                        else if (s_isDoubleTapped == false)
                         {
                             e.Source.RaiseEvent(new TappedEventArgs(TappedEvent, e));
                         }

+ 4 - 5
src/Avalonia.Input/PointerEventArgs.cs

@@ -114,7 +114,7 @@ namespace Avalonia.Input
 
     public class PointerPressedEventArgs : PointerEventArgs
     {
-        private readonly int _obsoleteClickCount;
+        private readonly int _clickCount;
 
         public PointerPressedEventArgs(
             IInteractive source,
@@ -123,15 +123,14 @@ namespace Avalonia.Input
             ulong timestamp,
             PointerPointProperties properties,
             KeyModifiers modifiers,
-            int obsoleteClickCount = 1)
+            int clickCount = 1)
             : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition,
                 timestamp, properties, modifiers)
         {
-            _obsoleteClickCount = obsoleteClickCount;
+            _clickCount = clickCount;
         }
 
-        [Obsolete("Use DoubleTapped event or Gestures.DoubleRightTapped attached event")]
-        public int ClickCount => _obsoleteClickCount;
+        public int ClickCount => _clickCount;
 
         [Obsolete("Use PointerPressedEventArgs.GetCurrentPoint(this).Properties")]
         public MouseButton MouseButton => Properties.PointerUpdateKind.GetMouseButton();

+ 35 - 9
src/Avalonia.Input/TouchDevice.cs

@@ -2,7 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia.Input.Raw;
-using Avalonia.VisualTree;
+using Avalonia.Platform;
 
 namespace Avalonia.Input
 {
@@ -16,7 +16,9 @@ namespace Avalonia.Input
     {
         private readonly Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>();
         private bool _disposed;
-        
+        private int _clickCount;
+        private Rect _lastClickRect;
+        private ulong _lastClickTime;
         KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) =>
             (KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask);
 
@@ -27,10 +29,10 @@ namespace Avalonia.Input
                 rv |= RawInputModifiers.LeftMouseButton;
             return rv;
         }
-        
+
         public void ProcessRawEvent(RawInputEventArgs ev)
         {
-            if(_disposed)
+            if (_disposed)
                 return;
             var args = (RawTouchEventArgs)ev;
             if (!_pointers.TryGetValue(args.TouchPointId, out var pointer))
@@ -43,16 +45,40 @@ namespace Avalonia.Input
                     PointerType.Touch, _pointers.Count == 0);
                 pointer.Capture(hit);
             }
-            
+
 
             var target = pointer.Captured ?? args.Root;
             if (args.Type == RawPointerEventType.TouchBegin)
             {
+                if (_pointers.Count > 1)
+                {
+                    _clickCount = 1;
+                    _lastClickTime = 0;
+                    _lastClickRect = new Rect();
+                }
+                else
+                {
+                    var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
+                    if (settings == null)
+                    {
+                        throw new Exception("IPlatformSettings can not be null");
+                    }
+                    if (!_lastClickRect.Contains(args.Position)
+                        || ev.Timestamp - _lastClickTime > settings.DoubleClickTime.TotalMilliseconds)
+                    {
+                        _clickCount = 0;
+                    }
+                    ++_clickCount;
+                    _lastClickTime = ev.Timestamp;
+                    _lastClickRect = new Rect(args.Position, new Size())
+                        .Inflate(new Thickness(16, 16));
+                }
+
                 target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
                     args.Root, args.Position, ev.Timestamp,
                     new PointerPointProperties(GetModifiers(args.InputModifiers, true),
                         PointerUpdateKind.LeftButtonPressed),
-                    GetKeyModifiers(args.InputModifiers)));
+                    GetKeyModifiers(args.InputModifiers), _clickCount));
             }
 
             if (args.Type == RawPointerEventType.TouchEnd)
@@ -84,12 +110,12 @@ namespace Avalonia.Input
                     GetKeyModifiers(args.InputModifiers)));
             }
 
-            
+
         }
 
         public void Dispose()
         {
-            if(_disposed)
+            if (_disposed)
                 return;
             var values = _pointers.Values.ToList();
             _pointers.Clear();
@@ -97,6 +123,6 @@ namespace Avalonia.Input
             foreach (var p in values)
                 p.Dispose();
         }
-        
+
     }
 }

+ 3 - 3
src/Avalonia.Layout/AttachedLayout.cs

@@ -12,17 +12,17 @@ namespace Avalonia.Layout
     /// </summary>
     public abstract class AttachedLayout : AvaloniaObject
     {
-        public string LayoutId { get; set; }
+        public string? LayoutId { get; set; }
 
         /// <summary>
         /// Occurs when the measurement state (layout) has been invalidated.
         /// </summary>
-        public event EventHandler MeasureInvalidated;
+        public event EventHandler? MeasureInvalidated;
 
         /// <summary>
         /// Occurs when the arrange state (layout) has been invalidated.
         /// </summary>
-        public event EventHandler ArrangeInvalidated;
+        public event EventHandler? ArrangeInvalidated;
 
         /// <summary>
         /// Initializes any per-container state the layout requires when it is attached to an

+ 1 - 0
src/Avalonia.Layout/Avalonia.Layout.csproj

@@ -9,4 +9,5 @@
   </ItemGroup>  
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ApiDiff.props" />
+  <Import Project="..\..\build\NullableEnable.props" />
 </Project>

+ 22 - 24
src/Avalonia.Layout/ElementManager.cs

@@ -13,10 +13,10 @@ namespace Avalonia.Layout
 {
     internal class ElementManager
     {
-        private readonly List<ILayoutable> _realizedElements = new List<ILayoutable>();
+        private readonly List<ILayoutable?> _realizedElements = new List<ILayoutable?>();
         private readonly List<Rect> _realizedElementLayoutBounds = new List<Rect>();
         private int _firstRealizedDataIndex;
-        private VirtualizingLayoutContext _context;
+        private VirtualizingLayoutContext? _context;
 
         private bool IsVirtualizingContext
         {
@@ -58,7 +58,7 @@ namespace Avalonia.Layout
                         // Make sure there is enough space for the bounds.
                         // Note: We could optimize when the count becomes smaller, but keeping
                         // it always up to date is the simplest option for now.
-                        _realizedElementLayoutBounds.Resize(count);
+                        _realizedElementLayoutBounds.Resize(count, default);
                     }
                 }
             }
@@ -66,34 +66,32 @@ namespace Avalonia.Layout
 
         public int GetRealizedElementCount()
         {
-            return IsVirtualizingContext ? _realizedElements.Count : _context.ItemCount;
+            return IsVirtualizingContext ? _realizedElements.Count : _context!.ItemCount;
         }
 
         public ILayoutable GetAt(int realizedIndex)
         {
-            ILayoutable element;
+            ILayoutable? element;
 
             if (IsVirtualizingContext)
             {
-                if (_realizedElements[realizedIndex] == null)
+                element = _realizedElements[realizedIndex];
+
+                if (element == null)
                 {
                     // Sentinel. Create the element now since we need it.
                     int dataIndex = GetDataIndexFromRealizedRangeIndex(realizedIndex);
                     Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "Creating element for sentinal with data index {Index}", dataIndex);
-                    element = _context.GetOrCreateElementAt(
+                    element = _context!.GetOrCreateElementAt(
                         dataIndex,
                         ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
                     _realizedElements[realizedIndex] = element;
                 }
-                else
-                {
-                    element = _realizedElements[realizedIndex];
-                }
             }
             else
             {
                 // realizedIndex and dataIndex are the same (everything is realized)
-                element = _context.GetOrCreateElementAt(
+                element = _context!.GetOrCreateElementAt(
                     realizedIndex,
                     ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
             }
@@ -112,7 +110,7 @@ namespace Avalonia.Layout
             _realizedElementLayoutBounds.Add(default);
         }
 
-        public void Insert(int realizedIndex, int dataIndex, ILayoutable element)
+        public void Insert(int realizedIndex, int dataIndex, ILayoutable? element)
         {
             if (realizedIndex == 0)
             {
@@ -136,7 +134,7 @@ namespace Avalonia.Layout
 
                 if (elementRef != null)
                 {
-                    _context.RecycleElement(elementRef);
+                    _context!.RecycleElement(elementRef);
                 }
             }
 
@@ -203,26 +201,26 @@ namespace Avalonia.Layout
             else
             {
                 // Non virtualized - everything is realized
-                return index >= 0 && index < _context.ItemCount;
+                return index >= 0 && index < _context!.ItemCount;
             }
         }
 
-        public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context.ItemCount;
+        public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context!.ItemCount;
 
-        public ILayoutable GetRealizedElement(int dataIndex)
+        public ILayoutable? GetRealizedElement(int dataIndex)
         {
             return IsVirtualizingContext ?
                 GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) :
-                _context.GetOrCreateElementAt(
+                _context!.GetOrCreateElementAt(
                     dataIndex,
                     ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
         }
 
-        public void EnsureElementRealized(bool forward, int dataIndex, string layoutId)
+        public void EnsureElementRealized(bool forward, int dataIndex, string? layoutId)
         {
             if (IsDataIndexRealized(dataIndex) == false)
             {
-                var element = _context.GetOrCreateElementAt(
+                var element = _context!.GetOrCreateElementAt(
                     dataIndex,
                     ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
 
@@ -273,14 +271,14 @@ namespace Avalonia.Layout
                 {
                     case NotifyCollectionChangedAction.Add:
                         {
-                            OnItemsAdded(args.NewStartingIndex, args.NewItems.Count);
+                            OnItemsAdded(args.NewStartingIndex, args.NewItems!.Count);
                         }
                         break;
 
                     case NotifyCollectionChangedAction.Replace:
                         {
-                            int oldSize = args.OldItems.Count;
-                            int newSize = args.NewItems.Count;
+                            int oldSize = args.OldItems!.Count;
+                            int newSize = args.NewItems!.Count;
                             int oldStartIndex = args.OldStartingIndex;
                             int newStartIndex = args.NewStartingIndex;
 
@@ -301,7 +299,7 @@ namespace Avalonia.Layout
 
                                     if (elementRef != null)
                                     {
-                                        _context.RecycleElement(elementRef);
+                                        _context!.RecycleElement(elementRef);
                                         _realizedElements[realizedIndex] = null;
                                     }
                                 }

+ 34 - 34
src/Avalonia.Layout/FlowLayoutAlgorithm.cs

@@ -16,8 +16,8 @@ namespace Avalonia.Layout
         private Size _lastAvailableSize;
         private double _lastItemSpacing;
         private bool _collectionChangePending;
-        private VirtualizingLayoutContext _context;
-        private IFlowLayoutAlgorithmDelegates _algorithmCallbacks;
+        private VirtualizingLayoutContext? _context;
+        private IFlowLayoutAlgorithmDelegates? _algorithmCallbacks;
         private Rect _lastExtent;
         private int _firstRealizedDataIndexInsideRealizationWindow = -1;
         private int _lastRealizedDataIndexInsideRealizationWindow = -1;
@@ -46,7 +46,7 @@ namespace Avalonia.Layout
             }
         }
 
-        private Rect RealizationRect => IsVirtualizingContext ? _context.RealizationRect : new Rect(Size.Infinity);
+        private Rect RealizationRect => IsVirtualizingContext ? _context!.RealizationRect : new Rect(Size.Infinity);
 
         public void InitializeForContext(VirtualizingLayoutContext context, IFlowLayoutAlgorithmDelegates callbacks)
         {
@@ -76,7 +76,7 @@ namespace Avalonia.Layout
             int maxItemsPerLine,
             ScrollOrientation orientation,
             bool disableVirtualization,
-            string layoutId)
+            string? layoutId)
         {
             _orientation.ScrollOrientation = orientation;
 
@@ -87,7 +87,7 @@ namespace Avalonia.Layout
                 layoutId,
                 realizationRect);
 
-            var suggestedAnchorIndex = _context.RecommendedAnchorIndex;
+            var suggestedAnchorIndex = _context!.RecommendedAnchorIndex;
             if (_elementManager.IsIndexValidInData(suggestedAnchorIndex))
             {
                 var anchorRealized = _elementManager.IsDataIndexRealized(suggestedAnchorIndex);
@@ -124,7 +124,7 @@ namespace Avalonia.Layout
             VirtualizingLayoutContext context,
             bool isWrapping,
             LineAlignment lineAlignment,
-            string layoutId)
+            string? layoutId)
         {
             Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: ArrangeLayout", layoutId);
             ArrangeVirtualizingLayout(finalSize, lineAlignment, isWrapping, layoutId);
@@ -149,7 +149,7 @@ namespace Avalonia.Layout
             Size availableSize,
             VirtualizingLayoutContext context)
         {
-            var measureSize = _algorithmCallbacks.Algorithm_GetMeasureSize(index, availableSize, context);
+            var measureSize = _algorithmCallbacks!.Algorithm_GetMeasureSize(index, availableSize, context);
             element.Measure(measureSize);
             var provisionalArrangeSize = _algorithmCallbacks.Algorithm_GetProvisionalArrangeSize(index, measureSize, element.DesiredSize, context);
             _algorithmCallbacks.Algorithm_OnElementMeasured(element, index, availableSize, measureSize, element.DesiredSize, provisionalArrangeSize, context);
@@ -161,7 +161,7 @@ namespace Avalonia.Layout
             Size availableSize,
             bool isWrapping,
             double minItemSpacing,
-            string layoutId)
+            string? layoutId)
         {
             int anchorIndex = -1;
             var anchorPosition= new Point();
@@ -170,7 +170,7 @@ namespace Avalonia.Layout
             if (!IsVirtualizingContext)
             {
                 // Non virtualizing host, start generating from the element 0
-                anchorIndex = context.ItemCount > 0 ? 0 : -1;
+                anchorIndex = context!.ItemCount > 0 ? 0 : -1;
             }
             else
             {       
@@ -183,7 +183,7 @@ namespace Avalonia.Layout
                     _lastItemSpacing != minItemSpacing ||
                     _collectionChangePending);
 
-                var suggestedAnchorIndex = _context.RecommendedAnchorIndex;
+                var suggestedAnchorIndex = _context!.RecommendedAnchorIndex;
 
                 var isAnchorSuggestionValid = suggestedAnchorIndex >= 0 &&
                     _elementManager.IsDataIndexRealized(suggestedAnchorIndex);
@@ -191,10 +191,10 @@ namespace Avalonia.Layout
                 if (isAnchorSuggestionValid)
                 {
                     Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Using suggested anchor {Anchor}", layoutId, suggestedAnchorIndex);
-                    anchorIndex = _algorithmCallbacks.Algorithm_GetAnchorForTargetElement(
+                    anchorIndex = _algorithmCallbacks!.Algorithm_GetAnchorForTargetElement(
                         suggestedAnchorIndex,
                         availableSize,
-                        context).Index;
+                        context!).Index;
 
                     if (_elementManager.IsDataIndexRealized(anchorIndex))
                     {
@@ -235,7 +235,7 @@ namespace Avalonia.Layout
 
                     // The anchor is based on the realization window because a connected ItemsRepeater might intersect the realization window
                     // but not the visible window. In that situation, we still need to produce a valid anchor.
-                    var anchorInfo = _algorithmCallbacks.Algorithm_GetAnchorForRealizationRect(availableSize, context);
+                    var anchorInfo = _algorithmCallbacks!.Algorithm_GetAnchorForRealizationRect(availableSize, context!);
                     anchorIndex = anchorInfo.Index;
                     anchorPosition = _orientation.MinorMajorPoint(0, anchorInfo.Offset);
                 }
@@ -259,12 +259,12 @@ namespace Avalonia.Layout
                     Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId} Disconnected Window - throwing away all realized elements", layoutId);
                     _elementManager.ClearRealizedRange();
 
-                    var anchor = _context.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
+                    var anchor = _context!.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
                     _elementManager.Add(anchor, anchorIndex);
                 }
 
                 var anchorElement = _elementManager.GetRealizedElement(anchorIndex);
-                var desiredSize = MeasureElement(anchorElement, anchorIndex, availableSize, _context);
+                var desiredSize = MeasureElement(anchorElement!, anchorIndex, availableSize, _context!);
                 var layoutBounds = new Rect(anchorPosition.X, anchorPosition.Y, desiredSize.Width, desiredSize.Height);
                 _elementManager.SetLayoutBoundsForDataIndex(anchorIndex, layoutBounds);
 
@@ -296,7 +296,7 @@ namespace Avalonia.Layout
             double lineSpacing,
             int maxItemsPerLine,
             bool disableVirtualization,
-            string layoutId)
+            string? layoutId)
         {
             if (anchorIndex != -1)
             {
@@ -322,7 +322,7 @@ namespace Avalonia.Layout
                     // Ensure layout element.
                     _elementManager.EnsureElementRealized(direction == GenerateDirection.Forward, currentIndex, layoutId);
                     var currentElement = _elementManager.GetRealizedElement(currentIndex);
-                    var desiredSize = MeasureElement(currentElement, currentIndex, availableSize, _context);
+                    var desiredSize = MeasureElement(currentElement!, currentIndex, availableSize, _context!);
                     ++count;
 
                     // Lay it out.
@@ -333,7 +333,7 @@ namespace Avalonia.Layout
                     if (direction == GenerateDirection.Forward)
                     {
                         double remainingSpace = _orientation.Minor(availableSize) - (_orientation.MinorStart(previousElementBounds) + _orientation.MinorSize(previousElementBounds) + minItemSpacing + _orientation.Minor(desiredSize));
-                        if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
+                        if (countInLine >= maxItemsPerLine || _algorithmCallbacks!.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
                         {
                             // No more space in this row. wrap to next row.
                             _orientation.SetMinorStart(ref currentBounds, 0);
@@ -371,7 +371,7 @@ namespace Avalonia.Layout
                     {
                         // Backward 
                         double remainingSpace = _orientation.MinorStart(previousElementBounds) - (_orientation.Minor(desiredSize) + minItemSpacing);
-                        if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
+                        if (countInLine >= maxItemsPerLine || _algorithmCallbacks!.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
                         {
                             // Does not fit, wrap to the previous row
                             var availableSizeMinor = _orientation.Minor(availableSize);
@@ -432,13 +432,13 @@ namespace Avalonia.Layout
                 // account for that element in the indices inside the realization window.
                 if (direction == GenerateDirection.Forward)
                 {
-                    int dataCount = _context.ItemCount;
+                    int dataCount = _context!.ItemCount;
                     _lastRealizedDataIndexInsideRealizationWindow = previousIndex == dataCount - 1 ? dataCount - 1 : previousIndex - 1;
                     _lastRealizedDataIndexInsideRealizationWindow = Math.Max(0, _lastRealizedDataIndexInsideRealizationWindow);
                 }
                 else
                 {
-                    int dataCount = _context.ItemCount;
+                    int dataCount = _context!.ItemCount;
                     _firstRealizedDataIndexInsideRealizationWindow = previousIndex == 0 ? 0 : previousIndex + 1;
                     _firstRealizedDataIndexInsideRealizationWindow = Math.Min(dataCount - 1, _firstRealizedDataIndexInsideRealizationWindow);
                 }
@@ -454,7 +454,7 @@ namespace Avalonia.Layout
         {
             _elementManager.ClearRealizedRange();
             // FlowLayout requires that the anchor is the first element in the row.
-            var internalAnchor = _algorithmCallbacks.Algorithm_GetAnchorForTargetElement(index, availableSize, context);
+            var internalAnchor = _algorithmCallbacks!.Algorithm_GetAnchorForTargetElement(index, availableSize, context);
 
             // No need to set the position of the anchor.
             // (0,0) is fine for now since the extent can
@@ -487,7 +487,7 @@ namespace Avalonia.Layout
             }
             else
             {
-                var realizationRect = _context.RealizationRect;
+                var realizationRect = _context!.RealizationRect;
                 var elementBounds = _elementManager.GetLayoutBoundsForDataIndex(index);
 
                 var elementMajorStart = _orientation.MajorStart(elementBounds);
@@ -510,11 +510,11 @@ namespace Avalonia.Layout
             return shouldContinue;
         }
 
-        private Rect EstimateExtent(Size availableSize, string layoutId)
+        private Rect EstimateExtent(Size availableSize, string? layoutId)
         {
-            ILayoutable firstRealizedElement = null;
+            ILayoutable? firstRealizedElement = null;
             Rect firstBounds = new Rect();
-            ILayoutable lastRealizedElement = null;
+            ILayoutable? lastRealizedElement = null;
             Rect lastBounds = new Rect();
             int firstDataIndex = -1;
             int lastDataIndex = -1;
@@ -531,9 +531,9 @@ namespace Avalonia.Layout
                 lastBounds = _elementManager.GetLayoutBoundsForRealizedIndex(last);
             }
 
-            Rect extent = _algorithmCallbacks.Algorithm_GetExtent(
+            Rect extent = _algorithmCallbacks!.Algorithm_GetExtent(
                 availableSize,
-                _context,
+                _context!,
                 firstRealizedElement,
                 firstDataIndex,
                 firstBounds,
@@ -563,7 +563,7 @@ namespace Avalonia.Layout
                         if (_orientation.MajorStart(currentBounds) != currentLineOffset)
                         {
                             // Staring a new line
-                            _algorithmCallbacks.Algorithm_OnLineArranged(currentDataIndex - countInLine, countInLine, currentLineSize, _context);
+                            _algorithmCallbacks!.Algorithm_OnLineArranged(currentDataIndex - countInLine, countInLine, currentLineSize, _context!);
                             countInLine = 0;
                             currentLineOffset = _orientation.MajorStart(currentBounds);
                             currentLineSize = 0;
@@ -575,7 +575,7 @@ namespace Avalonia.Layout
                     }
 
                     // Raise for the last line.
-                    _algorithmCallbacks.Algorithm_OnLineArranged(_lastRealizedDataIndexInsideRealizationWindow - countInLine + 1, countInLine, currentLineSize, _context);
+                    _algorithmCallbacks!.Algorithm_OnLineArranged(_lastRealizedDataIndexInsideRealizationWindow - countInLine + 1, countInLine, currentLineSize, _context!);
                 }
             }
         }
@@ -584,7 +584,7 @@ namespace Avalonia.Layout
             Size finalSize,
             LineAlignment lineAlignment,
             bool isWrapping,
-            string layoutId)
+            string? layoutId)
         {
             // Walk through the realized elements one line at a time and 
             // align them, Then call element.Arrange with the arranged bounds.
@@ -636,7 +636,7 @@ namespace Avalonia.Layout
             LineAlignment lineAlignment,
             bool isWrapping,
             Size finalSize,
-            string layoutId)
+            string? layoutId)
         {
             for (int rangeIndex = lineStartIndex; rangeIndex < lineStartIndex + countInLine; ++rangeIndex)
             {
@@ -723,11 +723,11 @@ namespace Avalonia.Layout
         {
             if (IsVirtualizingContext)
             {
-                _context.LayoutOrigin = new Point(_lastExtent.X, _lastExtent.Y);
+                _context!.LayoutOrigin = new Point(_lastExtent.X, _lastExtent.Y);
             }
         }
 
-        public ILayoutable GetElementIfRealized(int dataIndex)
+        public ILayoutable? GetElementIfRealized(int dataIndex)
         {
             if (_elementManager.IsDataIndexRealized(dataIndex))
             {

+ 2 - 2
src/Avalonia.Layout/IFlowLayoutAlgorithmDelegates.cs

@@ -21,10 +21,10 @@ namespace Avalonia.Layout
         Rect Algorithm_GetExtent(
             Size availableSize,
             VirtualizingLayoutContext context,
-            ILayoutable firstRealized,
+            ILayoutable? firstRealized,
             int firstRealizedItemIndex,
             Rect firstRealizedLayoutBounds,
-            ILayoutable lastRealized,
+            ILayoutable? lastRealized,
             int lastRealizedItemIndex,
             Rect lastRealizedLayoutBounds);
         void Algorithm_OnElementMeasured(

+ 2 - 2
src/Avalonia.Layout/LayoutContext.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Layout
         /// <summary>
         /// Gets or sets an object that represents the state of a layout.
         /// </summary>
-        public object LayoutState 
+        public object? LayoutState 
         {
             get => LayoutStateCore;
             set => LayoutStateCore = value;
@@ -23,6 +23,6 @@ namespace Avalonia.Layout
         /// <summary>
         /// Implements the behavior of <see cref="LayoutState"/> in a derived or custom LayoutContext.
         /// </summary>
-        protected virtual object LayoutStateCore { get; set; }
+        protected virtual object? LayoutStateCore { get; set; }
     }
 }

+ 1 - 1
src/Avalonia.Layout/LayoutContextAdapter.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Layout
             _nonVirtualizingContext = nonVirtualizingContext;
         }
 
-        protected override object LayoutStateCore 
+        protected override object? LayoutStateCore 
         { 
             get => _nonVirtualizingContext.LayoutState;
             set => _nonVirtualizingContext.LayoutState = value; 

+ 1 - 1
src/Avalonia.Layout/LayoutHelper.cs

@@ -101,7 +101,7 @@ namespace Avalonia.Layout
 
             if (result == 0 || double.IsNaN(result) || double.IsInfinity(result))
             {
-                throw new Exception($"Invalid LayoutScaling returned from {visualRoot.GetType()}");
+                throw new Exception($"Invalid LayoutScaling returned from {visualRoot!.GetType()}");
             }
 
             return result;

+ 1 - 0
src/Avalonia.Layout/LayoutQueue.cs

@@ -5,6 +5,7 @@ using System.Collections.Generic;
 namespace Avalonia.Layout
 {
     internal class LayoutQueue<T> : IReadOnlyCollection<T>, IDisposable
+        where T : notnull
     {
         private struct Info
         {

+ 1 - 1
src/Avalonia.Layout/Layoutable.cs

@@ -794,7 +794,7 @@ namespace Avalonia.Layout
         /// </summary>
         /// <param name="sender">The sender.</param>
         /// <param name="e">The event args.</param>
-        private void LayoutManagedLayoutUpdated(object sender, EventArgs e) => _layoutUpdated?.Invoke(this, e);
+        private void LayoutManagedLayoutUpdated(object? sender, EventArgs e) => _layoutUpdated?.Invoke(this, e);
 
         /// <summary>
         /// Tests whether any of a <see cref="Rect"/>'s properties include negative values,

+ 2 - 2
src/Avalonia.Layout/NonVirtualizingLayoutContext.cs

@@ -12,7 +12,7 @@ namespace Avalonia.Layout
     /// </summary>
     public abstract class NonVirtualizingLayoutContext : LayoutContext
     {
-        private VirtualizingLayoutContext _contextAdapter;
+        private VirtualizingLayoutContext? _contextAdapter;
 
         /// <summary>
         /// Gets the collection of child controls from the container that provides the context.
@@ -26,6 +26,6 @@ namespace Avalonia.Layout
         protected abstract IReadOnlyList<ILayoutable> ChildrenCore { get; }
 
         internal VirtualizingLayoutContext GetVirtualizingContextAdapter() =>
-            _contextAdapter ?? (_contextAdapter = new LayoutContextAdapter(this));
+            _contextAdapter ??= new LayoutContextAdapter(this);
     }
 }

+ 11 - 11
src/Avalonia.Layout/StackLayout.cs

@@ -78,10 +78,10 @@ namespace Avalonia.Layout
         internal Rect GetExtent(
             Size availableSize,
             VirtualizingLayoutContext context,
-            ILayoutable firstRealized,
+            ILayoutable? firstRealized,
             int firstRealizedItemIndex,
             Rect firstRealizedLayoutBounds,
-            ILayoutable lastRealized,
+            ILayoutable? lastRealized,
             int lastRealizedItemIndex,
             Rect lastRealizedLayoutBounds)
         {
@@ -89,7 +89,7 @@ namespace Avalonia.Layout
 
             // Constants
             int itemsCount = context.ItemCount;
-            var stackState = (StackLayoutState)context.LayoutState;
+            var stackState = (StackLayoutState)context.LayoutState!;
             double averageElementSize = GetAverageElementSize(availableSize, context, stackState) + Spacing;
 
             _orientation.SetMinorSize(ref extent, stackState.MaxArrangeBounds);
@@ -131,7 +131,7 @@ namespace Avalonia.Layout
         {
             if (context is VirtualizingLayoutContext virtualContext)
             {
-                var stackState = (StackLayoutState)virtualContext.LayoutState;
+                var stackState = (StackLayoutState)virtualContext.LayoutState!;
                 var provisionalArrangeSizeWinRt = provisionalArrangeSize;
                 stackState.OnElementMeasured(
                     index,
@@ -177,7 +177,7 @@ namespace Avalonia.Layout
             if (targetIndex >= 0 && targetIndex < itemsCount)
             {
                 index = targetIndex;
-                var state = (StackLayoutState)context.LayoutState;
+                var state = (StackLayoutState)context.LayoutState!;
                 double averageElementSize = GetAverageElementSize(availableSize, context, state) + Spacing;
                 offset = index * averageElementSize + _orientation.MajorStart(state.FlowAlgorithm.LastExtent);
             }
@@ -188,10 +188,10 @@ namespace Avalonia.Layout
         Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent(
             Size availableSize,
             VirtualizingLayoutContext context,
-            ILayoutable firstRealized,
+            ILayoutable? firstRealized,
             int firstRealizedItemIndex,
             Rect firstRealizedLayoutBounds,
-            ILayoutable lastRealized,
+            ILayoutable? lastRealized,
             int lastRealizedItemIndex,
             Rect lastRealizedLayoutBounds)
         {
@@ -234,7 +234,7 @@ namespace Avalonia.Layout
             if (itemsCount > 0)
             {
                 var realizationRect = context.RealizationRect;
-                var state = (StackLayoutState)context.LayoutState;
+                var state = (StackLayoutState)context.LayoutState!;
                 var lastExtent = state.FlowAlgorithm.LastExtent;
 
                 double averageElementSize = GetAverageElementSize(availableSize, context, state) + Spacing;
@@ -279,13 +279,13 @@ namespace Avalonia.Layout
 
         protected internal override void UninitializeForContextCore(VirtualizingLayoutContext context)
         {
-            var stackState = (StackLayoutState)context.LayoutState;
+            var stackState = (StackLayoutState)context.LayoutState!;
             stackState.UninitializeForContext(context);
         }
 
         protected internal override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize)
         {
-            ((StackLayoutState)context.LayoutState).OnMeasureStart();
+            ((StackLayoutState)context.LayoutState!).OnMeasureStart();
 
             var desiredSize = GetFlowAlgorithm(context).Measure(
                 availableSize,
@@ -358,6 +358,6 @@ namespace Avalonia.Layout
 
         private void InvalidateLayout() => InvalidateMeasure();
 
-        private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((StackLayoutState)context.LayoutState).FlowAlgorithm;
+        private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((StackLayoutState)context.LayoutState!).FlowAlgorithm;
     }
 }

+ 13 - 13
src/Avalonia.Layout/UniformGridLayout.cs

@@ -258,7 +258,7 @@ namespace Avalonia.Layout
             Size availableSize,
             VirtualizingLayoutContext context)
         {
-            var gridState = (UniformGridLayoutState)context.LayoutState;
+            var gridState = (UniformGridLayoutState)context.LayoutState!;
             return new Size(gridState.EffectiveItemWidth, gridState.EffectiveItemHeight);
         }
 
@@ -268,7 +268,7 @@ namespace Avalonia.Layout
             Size desiredSize,
             VirtualizingLayoutContext context)
         {
-            var gridState = (UniformGridLayoutState)context.LayoutState;
+            var gridState = (UniformGridLayoutState)context.LayoutState!;
             return new Size(gridState.EffectiveItemWidth, gridState.EffectiveItemHeight);
         }
 
@@ -285,7 +285,7 @@ namespace Avalonia.Layout
             var realizationRect = context.RealizationRect;
             if (itemsCount > 0 && _orientation.MajorSize(realizationRect) > 0)
             {
-                var gridState = (UniformGridLayoutState)context.LayoutState;
+                var gridState = (UniformGridLayoutState)context.LayoutState!;
                 var lastExtent = gridState.FlowAlgorithm.LastExtent;
                 var itemsPerLine = Math.Min( // note use of unsigned ints
                     Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
@@ -324,7 +324,7 @@ namespace Avalonia.Layout
                     Math.Max(1u, _maximumRowsOrColumns));
                 int indexOfFirstInLine = (targetIndex / itemsPerLine) * itemsPerLine;
                 index = indexOfFirstInLine;
-                var state = context.LayoutState as UniformGridLayoutState;
+                var state = (UniformGridLayoutState)context.LayoutState!;
                 offset = _orientation.MajorStart(GetLayoutRectForDataIndex(availableSize, indexOfFirstInLine, state.FlowAlgorithm.LastExtent, context));
             }
 
@@ -338,10 +338,10 @@ namespace Avalonia.Layout
         Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent(
             Size availableSize,
             VirtualizingLayoutContext context,
-            ILayoutable firstRealized,
+            ILayoutable? firstRealized,
             int firstRealizedItemIndex,
             Rect firstRealizedLayoutBounds,
-            ILayoutable lastRealized,
+            ILayoutable? lastRealized,
             int lastRealizedItemIndex,
             Rect lastRealizedLayoutBounds)
         {
@@ -421,7 +421,7 @@ namespace Avalonia.Layout
 
         protected internal override void UninitializeForContextCore(VirtualizingLayoutContext context)
         {
-            var gridState = (UniformGridLayoutState)context.LayoutState;
+            var gridState = (UniformGridLayoutState)context.LayoutState!;
             gridState.UninitializeForContext(context);
         }
 
@@ -429,7 +429,7 @@ namespace Avalonia.Layout
         {
             // Set the width and height on the grid state. If the user already set them then use the preset. 
             // If not, we have to measure the first element and get back a size which we're going to be using for the rest of the items.
-            var gridState = (UniformGridLayoutState)context.LayoutState;
+            var gridState = (UniformGridLayoutState)context.LayoutState!;
             gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing, _maximumRowsOrColumns);
 
             var desiredSize = GetFlowAlgorithm(context).Measure(
@@ -467,7 +467,7 @@ namespace Avalonia.Layout
             // Always invalidate layout to keep the view accurate.
             InvalidateLayout();
 
-            var gridState = (UniformGridLayoutState)context.LayoutState;
+            var gridState = (UniformGridLayoutState)context.LayoutState!;
             gridState.ClearElementOnDataSourceChange(context, args);
         }
 
@@ -517,7 +517,7 @@ namespace Avalonia.Layout
         private double GetMinorSizeWithSpacing(VirtualizingLayoutContext context)
         {
             var minItemSpacing = MinItemSpacing;
-            var gridState = (UniformGridLayoutState)context.LayoutState;
+            var gridState = (UniformGridLayoutState)context.LayoutState!;
             return _orientation.ScrollOrientation == ScrollOrientation.Vertical?
                 gridState.EffectiveItemWidth + minItemSpacing :
                 gridState.EffectiveItemHeight + minItemSpacing;
@@ -526,7 +526,7 @@ namespace Avalonia.Layout
         private double GetMajorSizeWithSpacing(VirtualizingLayoutContext context)
         {
             var lineSpacing = LineSpacing;
-            var gridState = (UniformGridLayoutState)context.LayoutState;
+            var gridState = (UniformGridLayoutState)context.LayoutState!;
             return _orientation.ScrollOrientation == ScrollOrientation.Vertical ?
                 gridState.EffectiveItemHeight + lineSpacing :
                 gridState.EffectiveItemWidth + lineSpacing;
@@ -544,7 +544,7 @@ namespace Avalonia.Layout
             int rowIndex = (int)(index / itemsPerLine);
             int indexInRow = index - (rowIndex * itemsPerLine);
 
-            var gridState = (UniformGridLayoutState)context.LayoutState;
+            var gridState = (UniformGridLayoutState)context.LayoutState!;
             Rect bounds = _orientation.MinorMajorRect(
                 indexInRow * GetMinorSizeWithSpacing(context) + _orientation.MinorStart(lastExtent),
                 rowIndex * GetMajorSizeWithSpacing(context) + _orientation.MajorStart(lastExtent),
@@ -556,6 +556,6 @@ namespace Avalonia.Layout
 
         private void InvalidateLayout() => InvalidateMeasure();
 
-        private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((UniformGridLayoutState)context.LayoutState).FlowAlgorithm;
+        private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((UniformGridLayoutState)context.LayoutState!).FlowAlgorithm;
     }
 }

+ 1 - 1
src/Avalonia.Layout/UniformGridLayoutState.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Layout
         // If it does not, then we need to do context.GetElement(0) at which point we have requested an element and are on point to clear it.
         // If we are responsible for clearing element 0 we keep m_cachedFirstElement valid. 
         // If we are not (because FlowLayoutAlgorithm is holding it for us) then we just null out this field and use the one from FlowLayoutAlgorithm.
-        private ILayoutable _cachedFirstElement;
+        private ILayoutable? _cachedFirstElement;
 
         internal FlowLayoutAlgorithm FlowAlgorithm { get; } = new FlowLayoutAlgorithm();
         internal double EffectiveItemWidth { get; private set; }

+ 0 - 5
src/Avalonia.Layout/Utils/ListUtils.cs

@@ -23,10 +23,5 @@ namespace Avalonia.Layout.Utils
                 list.AddRange(Enumerable.Repeat(value, size - cur));
             }
         }
-
-        public static void Resize<T>(this List<T> list, int count)
-        {
-            Resize(list, count, default);
-        }
     }
 }

+ 2 - 2
src/Avalonia.Layout/VirtualLayoutContextAdapter.cs

@@ -6,14 +6,14 @@ namespace Avalonia.Layout
     public class VirtualLayoutContextAdapter : NonVirtualizingLayoutContext
     {
         private readonly VirtualizingLayoutContext _virtualizingContext;
-        private ChildrenCollection _children;
+        private ChildrenCollection? _children;
 
         public VirtualLayoutContextAdapter(VirtualizingLayoutContext virtualizingContext)
         {
             _virtualizingContext = virtualizingContext;
         }
 
-        protected override object LayoutStateCore
+        protected override object? LayoutStateCore
         {
             get => _virtualizingContext.LayoutState;
             set => _virtualizingContext.LayoutState = value;

+ 1 - 1
src/Avalonia.Layout/VirtualizingLayoutContext.cs

@@ -43,7 +43,7 @@ namespace Avalonia.Layout
     /// </summary>
     public abstract class VirtualizingLayoutContext : LayoutContext
     {
-        private NonVirtualizingLayoutContext _contextAdapter;
+        private NonVirtualizingLayoutContext? _contextAdapter;
 
         /// <summary>
         /// Gets the number of items in the data.

+ 1 - 1
src/Avalonia.Layout/WrapLayout/UvMeasure.cs

@@ -27,7 +27,7 @@ namespace Avalonia.Layout
             }
         }
 
-        public override bool Equals(object obj)
+        public override bool Equals(object? obj)
         {
             if (obj is UvMeasure measure)
             {

+ 1 - 1
src/Avalonia.Layout/WrapLayout/WrapItem.cs

@@ -20,6 +20,6 @@ namespace Avalonia.Layout
 
         public UvMeasure? Position { get; internal set; }
 
-        public ILayoutable Element { get; internal set; }
+        public ILayoutable? Element { get; internal set; }
     }
 }

+ 3 - 3
src/Avalonia.Layout/WrapLayout/WrapLayout.cs

@@ -88,7 +88,7 @@ namespace Avalonia.Layout
         /// <inheritdoc />
         protected internal override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args)
         {
-            var state = (WrapLayoutState)context.LayoutState;
+            var state = (WrapLayoutState)context.LayoutState!;
 
             switch (args.Action)
             {
@@ -126,7 +126,7 @@ namespace Avalonia.Layout
             var realizationBounds = new UvBounds(Orientation, context.RealizationRect);
             var position = UvMeasure.Zero;
 
-            var state = (WrapLayoutState)context.LayoutState;
+            var state = (WrapLayoutState)context.LayoutState!;
             if (state.Orientation != Orientation)
             {
                 state.SetOrientation(Orientation);
@@ -261,7 +261,7 @@ namespace Avalonia.Layout
                 var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
                 var realizationBounds = new UvBounds(Orientation, context.RealizationRect);
 
-                var state = (WrapLayoutState)context.LayoutState;
+                var state = (WrapLayoutState)context.LayoutState!;
                 bool Arrange(WrapItem item, bool isLast = false)
                 {
                     if (item.Measure.HasValue == false)

+ 6 - 3
src/Avalonia.Layout/WrapLayout/WrapLayoutState.cs

@@ -62,8 +62,11 @@ namespace Avalonia.Layout
 
         internal void SetOrientation(Orientation orientation)
         {
-            foreach (var item in _items.Where(i => i.Measure.HasValue))
+            foreach (var item in _items)
             {
+                if (!item.Measure.HasValue)
+                    continue;
+
                 UvMeasure measure = item.Measure.Value;
                 double v = measure.V;
                 measure.V = measure.U;
@@ -120,10 +123,10 @@ namespace Avalonia.Layout
                 }
 
                 lastPosition = item.Position;
-                maxV = Math.Max(maxV, item.Measure.Value.V);
+                maxV = Math.Max(maxV, item.Measure!.Value.V);
             }
 
-            double totalHeight = lastPosition.Value.V + maxV;
+            double totalHeight = lastPosition!.Value.V + maxV;
             if (calculateAverage)
             {
                 return (totalHeight / itemCount) * _context.ItemCount;

+ 7 - 0
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@@ -142,6 +142,13 @@ namespace Avalonia.Native
 
         private void DoLayoutReset(bool forceUpdate = false)
         {
+            var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
+
+            if (macOpts != null && macOpts.DisableNativeMenus)
+            {
+                return;
+            }
+
             if (_resetQueued || forceUpdate)
             {
                 _resetQueued = false;

+ 5 - 0
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@@ -73,5 +73,10 @@ namespace Avalonia
         /// You can prevent Avalonia from adding those items to the OSX Application Menu with this property. The default value is false.
         /// </summary>
         public bool DisableDefaultApplicationMenuItems { get; set; }
+        
+        /// <summary>
+        /// Gets or sets a value indicating whether the native macOS menu bar will be enabled for the application.
+        /// </summary>
+        public bool DisableNativeMenus { get; set; }
     }
 }

+ 1 - 0
src/Avalonia.Styling/Avalonia.Styling.csproj

@@ -9,4 +9,5 @@
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
   </ItemGroup>
   <Import Project="..\..\build\ApiDiff.props" />
+  <Import Project="..\..\build\NullableEnable.props" />
 </Project>

+ 6 - 6
src/Avalonia.Styling/Controls/ChildNameScope.cs

@@ -15,20 +15,20 @@ namespace Avalonia.Controls
         
         public void Register(string name, object element) => _inner.Register(name, element);
 
-        public SynchronousCompletionAsyncResult<object> FindAsync(string name)
+        public SynchronousCompletionAsyncResult<object?> FindAsync(string name)
         {
             var found = Find(name);
             if (found != null)
-                return new SynchronousCompletionAsyncResult<object>(found);
+                return new SynchronousCompletionAsyncResult<object?>(found);
             // Not found and both current and parent scope are in completed state
             if(IsCompleted)
-                return new SynchronousCompletionAsyncResult<object>(null);
+                return new SynchronousCompletionAsyncResult<object?>(null);
             return DoFindAsync(name);
         }
 
-        public SynchronousCompletionAsyncResult<object> DoFindAsync(string name)
+        public SynchronousCompletionAsyncResult<object?> DoFindAsync(string name)
         {
-            var src = new SynchronousCompletionAsyncResultSource<object>();
+            var src = new SynchronousCompletionAsyncResultSource<object?>();
 
             void ParentSearch()
             {
@@ -56,7 +56,7 @@ namespace Avalonia.Controls
             return src.AsyncResult;
         }
 
-        public object Find(string name)
+        public object? Find(string name)
         {
             var found = _inner.Find(name);
             if (found != null)

+ 2 - 2
src/Avalonia.Styling/Controls/INameScope.cs

@@ -22,14 +22,14 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="name">The name.</param>
         /// <returns>The element, or null if the name was not found.</returns>
-        SynchronousCompletionAsyncResult<object> FindAsync(string name);
+        SynchronousCompletionAsyncResult<object?> FindAsync(string name);
         
         /// <summary>
         /// Finds a named element in the name scope, returns immediately, doesn't traverse the name scope stack
         /// </summary>
         /// <param name="name">The name.</param>
         /// <returns>The element, or null if the name was not found.</returns>
-        object Find(string name);
+        object? Find(string name);
 
         /// <summary>
         /// Marks the name scope as completed, no further registrations will be allowed

+ 14 - 16
src/Avalonia.Styling/Controls/NameScope.cs

@@ -22,8 +22,8 @@ namespace Avalonia.Controls
         
         private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
 
-        private readonly Dictionary<string, SynchronousCompletionAsyncResultSource<object>> _pendingSearches =
-            new Dictionary<string, SynchronousCompletionAsyncResultSource<object>>();
+        private readonly Dictionary<string, SynchronousCompletionAsyncResultSource<object?>> _pendingSearches =
+            new Dictionary<string, SynchronousCompletionAsyncResultSource<object?>>();
         
         /// <summary>
         /// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
@@ -32,7 +32,7 @@ namespace Avalonia.Controls
         /// <returns>The value of the NameScope attached property.</returns>
         public static INameScope GetNameScope(StyledElement styled)
         {
-            Contract.Requires<ArgumentNullException>(styled != null);
+            _ = styled ?? throw new ArgumentNullException(nameof(styled));
 
             return styled.GetValue(NameScopeProperty);
         }
@@ -44,7 +44,7 @@ namespace Avalonia.Controls
         /// <param name="value">The value to set.</param>
         public static void SetNameScope(StyledElement styled, INameScope value)
         {
-            Contract.Requires<ArgumentNullException>(styled != null);
+            _ = styled ?? throw new ArgumentNullException(nameof(styled));
 
             styled.SetValue(NameScopeProperty, value);
         }
@@ -54,12 +54,11 @@ namespace Avalonia.Controls
         {
             if (IsCompleted)
                 throw new InvalidOperationException("NameScope is completed, no further registrations are allowed");
-            Contract.Requires<ArgumentNullException>(name != null);
-            Contract.Requires<ArgumentNullException>(element != null);
 
-            object existing;
+            _ = name ?? throw new ArgumentNullException(nameof(name));
+            _ = element ?? throw new ArgumentNullException(nameof(element));
 
-            if (_inner.TryGetValue(name, out existing))
+            if (_inner.TryGetValue(name, out var existing))
             {
                 if (existing != element)
                 {
@@ -77,27 +76,26 @@ namespace Avalonia.Controls
             }
         }
 
-        public SynchronousCompletionAsyncResult<object> FindAsync(string name)
+        public SynchronousCompletionAsyncResult<object?> FindAsync(string name)
         {
             var found = Find(name);
             if (found != null)
-                return new SynchronousCompletionAsyncResult<object>(found);
+                return new SynchronousCompletionAsyncResult<object?>(found);
             if (IsCompleted)
-                return new SynchronousCompletionAsyncResult<object>((object)null);
+                return new SynchronousCompletionAsyncResult<object?>(null);
             if (!_pendingSearches.TryGetValue(name, out var tcs))
                 // We are intentionally running continuations synchronously here
-                _pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource<object>();
+                _pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource<object?>();
 
             return tcs.AsyncResult;
         }
 
         /// <inheritdoc />
-        public object Find(string name)
+        public object? Find(string name)
         {
-            Contract.Requires<ArgumentNullException>(name != null);
+            _ = name ?? throw new ArgumentNullException(nameof(name));
 
-            object result;
-            _inner.TryGetValue(name, out result);
+            _inner.TryGetValue(name, out var result);
             return result;
         }
 

+ 14 - 14
src/Avalonia.Styling/Controls/NameScopeExtensions.cs

@@ -17,11 +17,11 @@ namespace Avalonia.Controls
         /// <param name="nameScope">The name scope.</param>
         /// <param name="name">The name.</param>
         /// <returns>The named element or null if not found.</returns>
-        public static T Find<T>(this INameScope nameScope, string name)
+        public static T? Find<T>(this INameScope nameScope, string name)
             where T : class
         {
-            Contract.Requires<ArgumentNullException>(nameScope != null);
-            Contract.Requires<ArgumentNullException>(name != null);
+            _ = nameScope ?? throw new ArgumentNullException(nameof(nameScope));
+            _ = name ?? throw new ArgumentNullException(nameof(name));
 
             var result = nameScope.Find(name);
 
@@ -31,7 +31,7 @@ namespace Avalonia.Controls
                     $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
             }
 
-            return (T)result;
+            return (T?)result;
         }
 
         /// <summary>
@@ -41,11 +41,11 @@ namespace Avalonia.Controls
         /// <param name="anchor">The control to take the name scope from.</param>
         /// <param name="name">The name.</param>
         /// <returns>The named element or null if not found.</returns>
-        public static T Find<T>(this ILogical anchor, string name)
+        public static T? Find<T>(this ILogical anchor, string name)
             where T : class
         {
-            Contract.Requires<ArgumentNullException>(anchor != null);
-            Contract.Requires<ArgumentNullException>(name != null);
+            _ = anchor ?? throw new ArgumentNullException(nameof(anchor));
+            _ = name ?? throw new ArgumentNullException(nameof(name));
             var styledAnchor = anchor as StyledElement;
             if (styledAnchor == null)
                 return null;
@@ -64,8 +64,8 @@ namespace Avalonia.Controls
         public static T Get<T>(this INameScope nameScope, string name)
             where T : class
         {
-            Contract.Requires<ArgumentNullException>(nameScope != null);
-            Contract.Requires<ArgumentNullException>(name != null);
+            _ = nameScope ?? throw new ArgumentNullException(nameof(nameScope));
+            _ = name ?? throw new ArgumentNullException(nameof(name));
 
             var result = nameScope.Find(name);
 
@@ -94,9 +94,9 @@ namespace Avalonia.Controls
         public static T Get<T>(this ILogical anchor, string name)
             where T : class
         {
-            Contract.Requires<ArgumentNullException>(anchor != null);
-            Contract.Requires<ArgumentNullException>(name != null);
-               
+            _ = anchor ?? throw new ArgumentNullException(nameof(anchor));
+            _ = name ?? throw new ArgumentNullException(nameof(name));
+
             var nameScope = (anchor as INameScope) ?? NameScope.GetNameScope((StyledElement)anchor);
             if (nameScope == null)
                 throw new InvalidOperationException(
@@ -105,9 +105,9 @@ namespace Avalonia.Controls
             return nameScope.Get<T>(name);
         }
         
-        public static INameScope FindNameScope(this ILogical control)
+        public static INameScope? FindNameScope(this ILogical control)
         {
-            Contract.Requires<ArgumentNullException>(control != null);
+            _ = control ?? throw new ArgumentNullException(nameof(control));
 
             var scope = control.GetSelfAndLogicalAncestors()
                 .OfType<StyledElement>()

+ 4 - 4
src/Avalonia.Styling/Controls/NameScopeLocator.cs

@@ -11,9 +11,9 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="scope">The scope relative from which the object should be resolved.</param>
         /// <param name="name">The name of the object to find.</param>
-        public static IObservable<object> Track(INameScope scope, string name)
+        public static IObservable<object?> Track(INameScope scope, string name)
         {
-            return new NeverEndingSynchronousCompletionAsyncResultObservable<object>(scope.FindAsync(name));
+            return new NeverEndingSynchronousCompletionAsyncResultObservable<object?>(scope.FindAsync(name));
         }
         
         // This class is implemented in such weird way because for some reason
@@ -22,7 +22,7 @@ namespace Avalonia.Controls
 
         private class NeverEndingSynchronousCompletionAsyncResultObservable<T> : IObservable<T>
         {
-            private T _value;
+            private T? _value;
             private SynchronousCompletionAsyncResult<T>? _asyncResult;
 
             public NeverEndingSynchronousCompletionAsyncResultObservable(SynchronousCompletionAsyncResult<T> task)
@@ -47,7 +47,7 @@ namespace Avalonia.Controls
                         observer.OnNext(_asyncResult.Value.GetResult());
                     });
                 else
-                    observer.OnNext(_value);
+                    observer.OnNext(_value!);
                 
                 return Disposable.Empty;
             }

+ 1 - 1
src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs

@@ -12,7 +12,7 @@ namespace Avalonia.Controls
         /// <param name="value">True to add the pseudoclass or false to remove.</param>
         public static void Set(this IPseudoClasses classes, string name, bool value)
         {
-            Contract.Requires<ArgumentNullException>(classes != null);
+            _ = classes ?? throw new ArgumentNullException(nameof(classes));
 
             if (value)
             {

+ 1 - 1
src/Avalonia.Styling/Controls/ResourceDictionary.cs

@@ -177,7 +177,7 @@ namespace Avalonia.Controls
             }
         }
 
-        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
         {
             Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
         }

+ 3 - 3
src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs

@@ -109,7 +109,7 @@ namespace Avalonia.Controls
                 observer.OnNext(Convert(_target.FindResource(_key)));
             }
 
-            private void ResourcesChanged(object sender, ResourcesChangedEventArgs e)
+            private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
             {
                 PublishNext(Convert(_target.FindResource(_key)));
             }
@@ -159,7 +159,7 @@ namespace Avalonia.Controls
                 }
             }
 
-            private void OwnerChanged(object sender, EventArgs e)
+            private void OwnerChanged(object? sender, EventArgs e)
             {
                 if (_owner is object)
                 {
@@ -176,7 +176,7 @@ namespace Avalonia.Controls
                 PublishNext();
             }
 
-            private void ResourcesChanged(object sender, ResourcesChangedEventArgs e)
+            private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
             {
                 PublishNext();
             }

+ 1 - 1
src/Avalonia.Styling/INamed.cs

@@ -8,6 +8,6 @@ namespace Avalonia
         /// <summary>
         /// Gets the element name.
         /// </summary>
-        string Name { get; }
+        string? Name { get; }
     }
 }

+ 8 - 8
src/Avalonia.Styling/LogicalTree/ControlLocator.cs

@@ -9,19 +9,19 @@ namespace Avalonia.LogicalTree
     /// </summary>
     public static class ControlLocator
     {
-        public static IObservable<ILogical> Track(ILogical relativeTo, int ancestorLevel, Type ancestorType = null)
+        public static IObservable<ILogical?> Track(ILogical relativeTo, int ancestorLevel, Type? ancestorType = null)
         {
             return new ControlTracker(relativeTo, ancestorLevel, ancestorType);
         }
 
-        private class ControlTracker : LightweightObservableBase<ILogical>
+        private class ControlTracker : LightweightObservableBase<ILogical?>
         {
             private readonly ILogical _relativeTo;
             private readonly int _ancestorLevel;
-            private readonly Type _ancestorType;
-            private ILogical _value;
+            private readonly Type? _ancestorType;
+            private ILogical? _value;
 
-            public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType)
+            public ControlTracker(ILogical relativeTo, int ancestorLevel, Type? ancestorType)
             {
                 _relativeTo = relativeTo;
                 _ancestorLevel = ancestorLevel;
@@ -43,18 +43,18 @@ namespace Avalonia.LogicalTree
                 _value = null;
             }
 
-            protected override void Subscribed(IObserver<ILogical> observer, bool first)
+            protected override void Subscribed(IObserver<ILogical?> observer, bool first)
             {
                 observer.OnNext(_value);
             }
 
-            private void Attached(object sender, LogicalTreeAttachmentEventArgs e)
+            private void Attached(object? sender, LogicalTreeAttachmentEventArgs e)
             {
                 Update();
                 PublishNext(_value);
             }
 
-            private void Detached(object sender, LogicalTreeAttachmentEventArgs e)
+            private void Detached(object? sender, LogicalTreeAttachmentEventArgs e)
             {
                 _value = null;
                 PublishNext(null);

+ 3 - 3
src/Avalonia.Styling/LogicalTree/ILogical.cs

@@ -12,12 +12,12 @@ namespace Avalonia.LogicalTree
         /// <summary>
         /// Raised when the control is attached to a rooted logical tree.
         /// </summary>
-        event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
+        event EventHandler<LogicalTreeAttachmentEventArgs>? AttachedToLogicalTree;
 
         /// <summary>
         /// Raised when the control is detached from a rooted logical tree.
         /// </summary>
-        event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
+        event EventHandler<LogicalTreeAttachmentEventArgs>? DetachedFromLogicalTree;
 
         /// <summary>
         /// Gets a value indicating whether the element is attached to a rooted logical tree.
@@ -27,7 +27,7 @@ namespace Avalonia.LogicalTree
         /// <summary>
         /// Gets the logical parent.
         /// </summary>
-        ILogical LogicalParent { get; }
+        ILogical? LogicalParent { get; }
 
         /// <summary>
         /// Gets the logical children.

+ 13 - 13
src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs

@@ -15,14 +15,14 @@ namespace Avalonia.LogicalTree
         /// <returns>The logical's ancestors.</returns>
         public static IEnumerable<ILogical> GetLogicalAncestors(this ILogical logical)
         {
-            Contract.Requires<ArgumentNullException>(logical != null);
+            _ = logical ?? throw new ArgumentNullException(nameof(logical));
 
-            logical = logical.LogicalParent;
+            ILogical? l = logical.LogicalParent;
 
-            while (logical != null)
+            while (l != null)
             {
-                yield return logical;
-                logical = logical.LogicalParent;
+                yield return l;
+                l = l.LogicalParent;
             }
         }
 
@@ -48,14 +48,14 @@ namespace Avalonia.LogicalTree
         /// <param name="logical">The logical.</param>
         /// <param name="includeSelf">If given logical should be included in search.</param>
         /// <returns>First ancestor of given type.</returns>
-        public static T FindLogicalAncestorOfType<T>(this ILogical logical, bool includeSelf = false) where T : class
+        public static T? FindLogicalAncestorOfType<T>(this ILogical logical, bool includeSelf = false) where T : class
         {
             if (logical is null)
             {
                 return null;
             }
 
-            ILogical parent = includeSelf ? logical : logical.LogicalParent;
+            var parent = includeSelf ? logical : logical.LogicalParent;
 
             while (parent != null)
             {
@@ -120,7 +120,7 @@ namespace Avalonia.LogicalTree
         /// <param name="logical">The logical.</param>
         /// <param name="includeSelf">If given logical should be included in search.</param>
         /// <returns>First descendant of given type.</returns>
-        public static T FindLogicalDescendantOfType<T>(this ILogical logical, bool includeSelf = false) where T : class
+        public static T? FindLogicalDescendantOfType<T>(this ILogical logical, bool includeSelf = false) where T : class
         {
             if (logical is null)
             {
@@ -140,7 +140,7 @@ namespace Avalonia.LogicalTree
         /// </summary>
         /// <param name="logical">The logical.</param>
         /// <returns>The parent, or null if the logical is unparented.</returns>
-        public static ILogical GetLogicalParent(this ILogical logical)
+        public static ILogical? GetLogicalParent(this ILogical logical)
         {
             return logical.LogicalParent;
         }
@@ -153,7 +153,7 @@ namespace Avalonia.LogicalTree
         /// <returns>
         /// The parent, or null if the logical is unparented or its parent is not of type <typeparamref name="T"/>.
         /// </returns>
-        public static T GetLogicalParent<T>(this ILogical logical) where T : class
+        public static T? GetLogicalParent<T>(this ILogical logical) where T : class
         {
             return logical.LogicalParent as T;
         }
@@ -165,7 +165,7 @@ namespace Avalonia.LogicalTree
         /// <returns>The logical siblings.</returns>
         public static IEnumerable<ILogical> GetLogicalSiblings(this ILogical logical)
         {
-            ILogical parent = logical.LogicalParent;
+            var parent = logical.LogicalParent;
 
             if (parent != null)
             {
@@ -187,7 +187,7 @@ namespace Avalonia.LogicalTree
         /// </returns>
         public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target)
         {
-            ILogical current = target?.LogicalParent;
+            var current = target?.LogicalParent;
 
             while (current != null)
             {
@@ -202,7 +202,7 @@ namespace Avalonia.LogicalTree
             return false;
         }
 
-        private static T FindDescendantOfTypeCore<T>(ILogical logical) where T : class
+        private static T? FindDescendantOfTypeCore<T>(ILogical logical) where T : class
         {
             var logicalChildren = logical.LogicalChildren;
             var logicalChildrenCount = logicalChildren.Count;

+ 2 - 5
src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs

@@ -19,11 +19,8 @@ namespace Avalonia.LogicalTree
             ILogical source,
             ILogical parent)
         {
-            Contract.Requires<ArgumentNullException>(root != null);
-            Contract.Requires<ArgumentNullException>(source != null);
-
-            Root = root;
-            Source = source;
+            Root = root ?? throw new ArgumentNullException(nameof(root));
+            Source = source ?? throw new ArgumentNullException(nameof(source));
             Parent = parent;
         }
 

+ 9 - 9
src/Avalonia.Styling/StyledElement.cs

@@ -427,7 +427,7 @@ namespace Avalonia
 
                 if (_logicalRoot != null)
                 {
-                    var e = new LogicalTreeAttachmentEventArgs(_logicalRoot, this, old);
+                    var e = new LogicalTreeAttachmentEventArgs(_logicalRoot, this, old!);
                     OnDetachedFromLogicalTreeCore(e);
                 }
 
@@ -435,7 +435,7 @@ namespace Avalonia
 
                 if (newRoot is object)
                 {
-                    var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent);
+                    var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent!);
                     OnAttachedToLogicalTreeCore(e);
                 }
                 else if (parent is null)
@@ -495,21 +495,21 @@ namespace Avalonia
             DetachStylesFromThisAndDescendents(allStyles);
         }
 
-        protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
         {
             switch (e.Action)
             {
                 case NotifyCollectionChangedAction.Add:
-                    SetLogicalParent(e.NewItems);
+                    SetLogicalParent(e.NewItems!);
                     break;
 
                 case NotifyCollectionChangedAction.Remove:
-                    ClearLogicalParent(e.OldItems);
+                    ClearLogicalParent(e.OldItems!);
                     break;
 
                 case NotifyCollectionChangedAction.Replace:
-                    ClearLogicalParent(e.OldItems);
-                    SetLogicalParent(e.NewItems);
+                    ClearLogicalParent(e.OldItems!);
+                    SetLogicalParent(e.NewItems!);
                     break;
 
                 case NotifyCollectionChangedAction.Reset:
@@ -729,7 +729,7 @@ namespace Avalonia
 
             for (var i = 0; i < count; i++)
             {
-                var logical = (ILogical) children[i];
+                var logical = (ILogical) children[i]!;
                 
                 if (logical.LogicalParent is null)
                 {
@@ -744,7 +744,7 @@ namespace Avalonia
 
             for (var i = 0; i < count; i++)
             {
-                var logical = (ILogical) children[i];
+                var logical = (ILogical) children[i]!;
                 
                 if (logical.LogicalParent == this)
                 {

+ 1 - 1
src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs

@@ -37,7 +37,7 @@ namespace Avalonia.Styling.Activators
             _provider.ChildIndexChanged -= ChildIndexChanged;
         }
 
-        private void ChildIndexChanged(object sender, ChildIndexChangedEventArgs e)
+        private void ChildIndexChanged(object? sender, ChildIndexChangedEventArgs e)
         {
             // Run matching again if:
             // 1. Selector is reversed, so other item insertion/deletion might affect total count without changing subscribed item index.

+ 1 - 1
src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs

@@ -66,7 +66,7 @@ namespace Avalonia.Styling.Activators
             _classes.CollectionChanged -= ClassesChangedHandler;
         }
 
-        private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e)
+        private void ClassesChanged(object? sender, NotifyCollectionChangedEventArgs e)
         {
             if (e.Action != NotifyCollectionChangedAction.Move)
             {

+ 3 - 3
src/Avalonia.Styling/Styling/ChildSelector.cs

@@ -6,7 +6,7 @@ namespace Avalonia.Styling
     internal class ChildSelector : Selector
     {
         private readonly Selector _parent;
-        private string _selectorString;
+        private string? _selectorString;
 
         public ChildSelector(Selector parent)
         {
@@ -25,7 +25,7 @@ namespace Avalonia.Styling
         public override bool IsCombinator => true;
 
         /// <inheritdoc/>
-        public override Type TargetType => null;
+        public override Type? TargetType => null;
 
         public override string ToString()
         {
@@ -64,6 +64,6 @@ namespace Avalonia.Styling
             }
         }
 
-        protected override Selector MovePrevious() => null;
+        protected override Selector? MovePrevious() => null;
     }
 }

+ 1 - 1
src/Avalonia.Styling/Styling/OrSelector.cs

@@ -120,7 +120,7 @@ namespace Avalonia.Styling
                 }
                 else
                 {
-                    while (!result.IsAssignableFrom(selector.TargetType))
+                    while (result is not null && !result.IsAssignableFrom(selector.TargetType))
                     {
                         result = result.BaseType;
                     }

+ 6 - 1
src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs

@@ -53,11 +53,16 @@ namespace Avalonia.Styling
 
                 if (_property.IsAttached)
                 {
+                    builder.Append('(');
                     builder.Append(_property.OwnerType.Name);
                     builder.Append('.');
                 }
 
                 builder.Append(_property.Name);
+                if (_property.IsAttached)
+                {
+                    builder.Append(')');
+                }
                 builder.Append('=');
                 builder.Append(_value ?? string.Empty);
                 builder.Append(']');
@@ -104,7 +109,7 @@ namespace Avalonia.Styling
             var converter = TypeDescriptor.GetConverter(propertyType);
             if (converter?.CanConvertFrom(valueType) == true)
             {
-                return Equals(propertyValue, converter.ConvertFrom(null, CultureInfo.InvariantCulture, value));
+                return Equals(propertyValue, converter.ConvertFrom(null, CultureInfo.InvariantCulture, value!));
             }
 
             return false;

+ 29 - 21
src/Avalonia.Styling/Styling/Selectors.cs

@@ -25,10 +25,14 @@ namespace Avalonia.Styling
         /// <param name="previous">The previous selector.</param>
         /// <param name="name">The name of the style class.</param>
         /// <returns>The selector.</returns>
-        public static Selector Class(this Selector previous, string name)
+        public static Selector Class(this Selector? previous, string name)
         {
-            Contract.Requires<ArgumentNullException>(name != null);
-            Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(name));
+            _ = name ?? throw new ArgumentNullException(nameof(name));
+
+            if (string.IsNullOrWhiteSpace(name))
+            {
+                throw new ArgumentException("Name may not be empty", nameof(name));
+            }
 
             var tac = previous as TypeNameAndClassSelector;
 
@@ -48,7 +52,7 @@ namespace Avalonia.Styling
         /// </summary>
         /// <param name="previous">The previous selector.</param>
         /// <returns>The selector.</returns>
-        public static Selector Descendant(this Selector previous)
+        public static Selector Descendant(this Selector? previous)
         {
             return new DescendantSelector(previous);
         }
@@ -59,9 +63,9 @@ namespace Avalonia.Styling
         /// <param name="previous">The previous selector.</param>
         /// <param name="type">The type.</param>
         /// <returns>The selector.</returns>
-        public static Selector Is(this Selector previous, Type type)
+        public static Selector Is(this Selector? previous, Type type)
         {
-            Contract.Requires<ArgumentNullException>(type != null);
+            _ = type ?? throw new ArgumentNullException(nameof(type));
 
             return TypeNameAndClassSelector.Is(previous, type);
         }
@@ -72,7 +76,7 @@ namespace Avalonia.Styling
         /// <typeparam name="T">The type.</typeparam>
         /// <param name="previous">The previous selector.</param>
         /// <returns>The selector.</returns>
-        public static Selector Is<T>(this Selector previous) where T : IStyleable
+        public static Selector Is<T>(this Selector? previous) where T : IStyleable
         {
             return previous.Is(typeof(T));
         }
@@ -83,10 +87,14 @@ namespace Avalonia.Styling
         /// <param name="previous">The previous selector.</param>
         /// <param name="name">The name.</param>
         /// <returns>The selector.</returns>
-        public static Selector Name(this Selector previous, string name)
+        public static Selector Name(this Selector? previous, string name)
         {
-            Contract.Requires<ArgumentNullException>(name != null);
-            Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(name));
+            _ = name ?? throw new ArgumentNullException(nameof(name));
+
+            if (string.IsNullOrWhiteSpace(name))
+            {
+                throw new ArgumentException("Name may not be empty", nameof(name));
+            }
 
             var tac = previous as TypeNameAndClassSelector;
 
@@ -107,7 +115,7 @@ namespace Avalonia.Styling
         /// <param name="previous">The previous selector.</param>
         /// <param name="argument">The selector to be not-ed.</param>
         /// <returns>The selector.</returns>
-        public static Selector Not(this Selector previous, Func<Selector, Selector> argument)
+        public static Selector Not(this Selector? previous, Func<Selector?, Selector> argument)
         {
             return new NotSelector(previous, argument(null));
         }
@@ -118,7 +126,7 @@ namespace Avalonia.Styling
         /// <param name="previous">The previous selector.</param>
         /// <param name="argument">The selector to be not-ed.</param>
         /// <returns>The selector.</returns>
-        public static Selector Not(this Selector previous, Selector argument)
+        public static Selector Not(this Selector? previous, Selector argument)
         {
             return new NotSelector(previous, argument);
         }
@@ -126,7 +134,7 @@ namespace Avalonia.Styling
         /// <inheritdoc cref="NthChildSelector"/>
         /// <inheritdoc cref="NthChildSelector(Selector?, int, int)"/>
         /// <returns>The selector.</returns>
-        public static Selector NthChild(this Selector previous, int step, int offset)
+        public static Selector NthChild(this Selector? previous, int step, int offset)
         {
             return new NthChildSelector(previous, step, offset);
         }
@@ -134,7 +142,7 @@ namespace Avalonia.Styling
         /// <inheritdoc cref="NthLastChildSelector"/>
         /// <inheritdoc cref="NthLastChildSelector(Selector?, int, int)"/>
         /// <returns>The selector.</returns>
-        public static Selector NthLastChild(this Selector previous, int step, int offset)
+        public static Selector NthLastChild(this Selector? previous, int step, int offset)
         {
             return new NthLastChildSelector(previous, step, offset);
         }
@@ -145,9 +153,9 @@ namespace Avalonia.Styling
         /// <param name="previous">The previous selector.</param>
         /// <param name="type">The type.</param>
         /// <returns>The selector.</returns>
-        public static Selector OfType(this Selector previous, Type type)
+        public static Selector OfType(this Selector? previous, Type type)
         {
-            Contract.Requires<ArgumentNullException>(type != null);
+            _ = type ?? throw new ArgumentNullException(nameof(type));
 
             return TypeNameAndClassSelector.OfType(previous, type);
         }
@@ -158,7 +166,7 @@ namespace Avalonia.Styling
         /// <typeparam name="T">The type.</typeparam>
         /// <param name="previous">The previous selector.</param>
         /// <returns>The selector.</returns>
-        public static Selector OfType<T>(this Selector previous) where T : IStyleable
+        public static Selector OfType<T>(this Selector? previous) where T : IStyleable
         {
             return previous.OfType(typeof(T));
         }
@@ -191,9 +199,9 @@ namespace Avalonia.Styling
         /// <param name="property">The property.</param>
         /// <param name="value">The property value.</param>
         /// <returns>The selector.</returns>
-        public static Selector PropertyEquals<T>(this Selector previous, AvaloniaProperty<T> property, object value)
+        public static Selector PropertyEquals<T>(this Selector? previous, AvaloniaProperty<T> property, object? value)
         {
-            Contract.Requires<ArgumentNullException>(property != null);
+            _ = property ?? throw new ArgumentNullException(nameof(property));
 
             return new PropertyEqualsSelector(previous, property, value);
         }
@@ -205,9 +213,9 @@ namespace Avalonia.Styling
         /// <param name="property">The property.</param>
         /// <param name="value">The property value.</param>
         /// <returns>The selector.</returns>
-        public static Selector PropertyEquals(this Selector previous, AvaloniaProperty property, object value)
+        public static Selector PropertyEquals(this Selector? previous, AvaloniaProperty property, object? value)
         {
-            Contract.Requires<ArgumentNullException>(property != null);
+            _ = property ?? throw new ArgumentNullException(nameof(property));
 
             return new PropertyEqualsSelector(previous, property, value);
         }

+ 7 - 7
src/Avalonia.Styling/Styling/Styles.cs

@@ -262,7 +262,7 @@ namespace Avalonia.Styling
             }
         }
 
-        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
         {
             static IReadOnlyList<T> ToReadOnlyList<T>(IList list)
             {
@@ -282,7 +282,7 @@ namespace Avalonia.Styling
             {
                 for (var i = 0; i < items.Count; ++i)
                 {
-                    var style = (IStyle)items[i];
+                    var style = (IStyle)items[i]!;
 
                     if (Owner is object && style is IResourceProvider resourceProvider)
                     {
@@ -299,7 +299,7 @@ namespace Avalonia.Styling
             {
                 for (var i = 0; i < items.Count; ++i)
                 {
-                    var style = (IStyle)items[i];
+                    var style = (IStyle)items[i]!;
 
                     if (Owner is object && style is IResourceProvider resourceProvider)
                     {
@@ -315,14 +315,14 @@ namespace Avalonia.Styling
             switch (e.Action)
             {
                 case NotifyCollectionChangedAction.Add:
-                    Add(e.NewItems);
+                    Add(e.NewItems!);
                     break;
                 case NotifyCollectionChangedAction.Remove:
-                    Remove(e.OldItems);
+                    Remove(e.OldItems!);
                     break;
                 case NotifyCollectionChangedAction.Replace:
-                    Remove(e.OldItems);
-                    Add(e.NewItems);
+                    Remove(e.OldItems!);
+                    Add(e.NewItems!);
                     break;
                 case NotifyCollectionChangedAction.Reset:
                     throw new InvalidOperationException("Reset should not be called on Styles.");

+ 3 - 3
src/Avalonia.Styling/Styling/TemplateSelector.cs

@@ -5,7 +5,7 @@ namespace Avalonia.Styling
     internal class TemplateSelector : Selector
     {
         private readonly Selector _parent;
-        private string _selectorString;
+        private string? _selectorString;
 
         public TemplateSelector(Selector parent)
         {
@@ -24,7 +24,7 @@ namespace Avalonia.Styling
         public override bool IsCombinator => true;
 
         /// <inheritdoc/>
-        public override Type TargetType => null;
+        public override Type? TargetType => null;
 
         public override string ToString()
         {
@@ -48,6 +48,6 @@ namespace Avalonia.Styling
             return _parent.Match(templatedParent, subscribe);
         }
 
-        protected override Selector MovePrevious() => null;
+        protected override Selector? MovePrevious() => null;
     }
 }

+ 5 - 1
src/Avalonia.Themes.Default/CalendarDatePicker.xaml

@@ -115,7 +115,11 @@
                  StaysOpen="False">
             <Calendar Name="PART_Calendar"
                       FirstDayOfWeek="{TemplateBinding FirstDayOfWeek}"
-                      IsTodayHighlighted="{TemplateBinding IsTodayHighlighted}"/>
+                      IsTodayHighlighted="{TemplateBinding IsTodayHighlighted}"
+                      SelectedDate="{TemplateBinding SelectedDate, Mode=TwoWay}"
+                      DisplayDate="{TemplateBinding DisplayDate}"
+                      DisplayDateStart="{TemplateBinding DisplayDateStart}"
+                      DisplayDateEnd="{TemplateBinding DisplayDateEnd}" />
           </Popup>
         </Grid>
       </ControlTemplate>

+ 5 - 1
src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml

@@ -129,7 +129,11 @@
                  IsLightDismissEnabled="True">
             <Calendar Name="PART_Calendar"
                       FirstDayOfWeek="{TemplateBinding FirstDayOfWeek}"
-                      IsTodayHighlighted="{TemplateBinding IsTodayHighlighted}"/>
+                      IsTodayHighlighted="{TemplateBinding IsTodayHighlighted}"
+                      SelectedDate="{TemplateBinding SelectedDate, Mode=TwoWay}"
+                      DisplayDate="{TemplateBinding DisplayDate}"
+                      DisplayDateStart="{TemplateBinding DisplayDateStart}"
+                      DisplayDateEnd="{TemplateBinding DisplayDateEnd}" />
           </Popup>
         </Grid>
       </ControlTemplate>

+ 1 - 3
src/Avalonia.Visuals/Animation/RenderLoopClock.cs

@@ -9,9 +9,7 @@ namespace Avalonia.Animation
     {
         protected override void Stop()
         {
-            var loop = AvaloniaLocator.Current.GetService<IRenderLoop>() ??
-                throw new InvalidOperationException("Unable to locate IRenderLoop.");
-            loop.Remove(this);
+            AvaloniaLocator.Current.GetRequiredService<IRenderLoop>().Remove(this);
         }
 
         bool IRenderLoopTask.NeedsUpdate => HasSubscriptions;

+ 1 - 2
src/Avalonia.Visuals/Media/CombinedGeometry.cs

@@ -154,8 +154,7 @@ namespace Avalonia.Media
 
             if (g1 is object && g2 is object)
             {
-                var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                    throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+                var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
                 return factory.CreateCombinedGeometry(GeometryCombineMode, g1, g2);
             }
             else if (GeometryCombineMode == GeometryCombineMode.Intersect)

+ 1 - 2
src/Avalonia.Visuals/Media/EllipseGeometry.cs

@@ -98,8 +98,7 @@ namespace Avalonia.Media
         /// <inheritdoc/>
         protected override IGeometryImpl? CreateDefiningGeometry()
         {
-            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+            var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
 
             if (Rect != default) return factory.CreateEllipseGeometry(Rect);
             

+ 2 - 5
src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs

@@ -32,9 +32,7 @@ namespace Avalonia.Media.Fonts
         /// <returns></returns>
         private static IEnumerable<Uri> GetFontAssetsBySource(FontFamilyKey fontFamilyKey)
         {
-            var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>() ??
-                throw new InvalidOperationException("Unable to locate IAssetLoader.");
-
+            var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
             var availableAssets = assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri);
 
             var matchingAssets =
@@ -51,8 +49,7 @@ namespace Avalonia.Media.Fonts
         /// <returns></returns>
         private static IEnumerable<Uri> GetFontAssetsByExpression(FontFamilyKey fontFamilyKey)
         {
-            var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>() ??
-                throw new InvalidOperationException("Unable to locate IAssetLoader.");
+            var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
 
             var fileName = GetFileName(fontFamilyKey, out var fileExtension, out var location);
 

+ 1 - 2
src/Avalonia.Visuals/Media/FormattedText.cs

@@ -24,8 +24,7 @@ namespace Avalonia.Media
         /// </summary>
         public FormattedText()
         {
-            _platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+            _platform = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
         }
 
         /// <summary>

+ 1 - 2
src/Avalonia.Visuals/Media/GeometryGroup.cs

@@ -61,8 +61,7 @@ namespace Avalonia.Media
         {
             if (_children?.Count > 0)
             {
-                var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                    throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+                var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
                 return factory.CreateGeometryGroup(FillRule, _children);
             }
 

+ 1 - 2
src/Avalonia.Visuals/Media/GlyphRun.cs

@@ -627,8 +627,7 @@ namespace Avalonia.Media
                 throw new InvalidOperationException();
             }
 
-            var platformRenderInterface = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface");
+            var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
 
             _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this);
         }

+ 1 - 2
src/Avalonia.Visuals/Media/Imaging/Bitmap.cs

@@ -169,8 +169,7 @@ namespace Avalonia.Media.Imaging
 
         private static IPlatformRenderInterface GetFactory()
         {
-            return AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+            return AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
         }
     }
 }

+ 1 - 2
src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs

@@ -54,8 +54,7 @@ namespace Avalonia.Media.Imaging
         /// <returns>The platform-specific implementation.</returns>
         private static IRenderTargetBitmapImpl CreateImpl(PixelSize size, Vector dpi)
         {
-            IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+            IPlatformRenderInterface factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
             return factory.CreateRenderTargetBitmap(size, dpi);
         }
 

+ 1 - 2
src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs

@@ -93,8 +93,7 @@ namespace Avalonia.Media.Imaging
 
         private static IPlatformRenderInterface GetFactory()
         {
-            return AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+            return AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
         }
     }
 }

+ 1 - 2
src/Avalonia.Visuals/Media/LineGeometry.cs

@@ -70,8 +70,7 @@ namespace Avalonia.Media
         /// <inheritdoc/>
         protected override IGeometryImpl? CreateDefiningGeometry()
         {
-            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+            var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
 
             return factory.CreateLineGeometry(StartPoint, EndPoint);
         }

+ 1 - 2
src/Avalonia.Visuals/Media/PathGeometry.cs

@@ -88,8 +88,7 @@ namespace Avalonia.Media
             if (figures is null)
                 return null;
 
-            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+            var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
             var geometry = factory.CreateStreamGeometry();
 
             using (var ctx = new StreamGeometryContext(geometry.Open()))

+ 1 - 2
src/Avalonia.Visuals/Media/PolylineGeometry.cs

@@ -76,8 +76,7 @@ namespace Avalonia.Media
 
         protected override IGeometryImpl? CreateDefiningGeometry()
         {
-            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+            var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
             var geometry = factory.CreateStreamGeometry();
 
             using (var context = geometry.Open())

+ 1 - 2
src/Avalonia.Visuals/Media/RectangleGeometry.cs

@@ -49,8 +49,7 @@ namespace Avalonia.Media
 
         protected override IGeometryImpl? CreateDefiningGeometry()
         {
-            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+            var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
 
             return factory.CreateRectangleGeometry(Rect);
         }

+ 1 - 2
src/Avalonia.Visuals/Media/StreamGeometry.cs

@@ -66,8 +66,7 @@ namespace Avalonia.Media
         {
             if (_impl == null)
             {
-                var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                    throw new InvalidOperationException("Unable to locate IPlatformRenderInterface.");
+                var factory = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
                 _impl = factory.CreateStreamGeometry();
             }
 

+ 1 - 2
src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs

@@ -77,8 +77,7 @@ namespace Avalonia.Rendering
         /// </remarks>
         protected virtual IDisposable StartCore(Action<TimeSpan> tick)
         {
-            _runtime ??= AvaloniaLocator.Current.GetService<IRuntimePlatform>() ??
-                throw new InvalidOperationException("Unable to locate IRuntimePlatform.");
+            _runtime ??= AvaloniaLocator.Current.GetRequiredService<IRuntimePlatform>();
 
             return _runtime.StartSystemTimer(
                 TimeSpan.FromSeconds(1.0 / FramesPerSecond),

+ 1 - 1
src/Avalonia.Visuals/Visual.cs

@@ -377,7 +377,7 @@ namespace Avalonia
             }
         }
 
-        protected override void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
         {
             base.LogicalChildrenCollectionChanged(sender, e);
             VisualRoot?.Renderer?.RecalculateChildren(this);

+ 2 - 4
src/Avalonia.X11/X11CursorFactory.cs

@@ -94,10 +94,8 @@ namespace Avalonia.X11
             {
                 var size = Marshal.SizeOf<XcursorImage>() +
                     (bitmap.PixelSize.Width * bitmap.PixelSize.Height * 4);
-                var runtimePlatform = AvaloniaLocator.Current.GetService<IRuntimePlatform>() ??
-                    throw new InvalidOperationException("Unable to locate IRuntimePlatform");
-                var platformRenderInterface = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() ??
-                    throw new InvalidOperationException("Unable to locate IPlatformRenderInterface");
+                var runtimePlatform = AvaloniaLocator.Current.GetRequiredService<IRuntimePlatform>();
+                var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
 
                 _pixelSize = bitmap.PixelSize;
                 _blob = runtimePlatform.AllocBlob(size);

+ 70 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@@ -40,6 +40,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 
             var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
             var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
+            var avaloniaAttachedPropertyT = context.GetAvaloniaTypes().AvaloniaAttachedPropertyT;
             XamlIlSelectorNode Create(IEnumerable<SelectorGrammar.ISyntax> syntax,
                 Func<string, string, XamlAstClrTypeReference> typeResolver)
             {
@@ -85,6 +86,47 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                             result = new XamlIlPropertyEqualsSelector(result, targetProperty, typedValue);
                             break;
                         }
+                        case SelectorGrammar.AttachedPropertySyntax attachedProperty:
+                            {
+                                var targetType = result?.TargetType;
+                                if (targetType == null)
+                                {
+                                    throw new XamlParseException("Attached Property selectors must be applied to a type.",node);
+                                }
+                                var attachedPropertyOwnerType = typeResolver(attachedProperty.Xmlns, attachedProperty.TypeName).Type;
+
+                                if (attachedPropertyOwnerType is null)
+                                {
+                                    throw new XamlParseException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node);
+                                }
+
+                                var attachedPropertyName = attachedProperty.Property + "Property";
+
+                                var targetPropertyField = attachedPropertyOwnerType.GetAllFields()
+                                    .FirstOrDefault(f => f.IsStatic
+                                        && f.IsPublic
+                                        && f.Name == attachedPropertyName
+                                        && f.FieldType.GenericTypeDefinition == avaloniaAttachedPropertyT
+                                        );
+
+                                if (targetPropertyField is null)
+                                {
+                                    throw new XamlParseException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node);
+                                }
+
+                                var targetPropertyType = XamlIlAvaloniaPropertyHelper
+                                    .GetAvaloniaPropertyType(targetPropertyField, context.GetAvaloniaTypes(), node);
+
+                                if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
+                                    new XamlAstTextNode(node, attachedProperty.Value, context.Configuration.WellKnownTypes.String),
+                                    targetPropertyType, out var typedValue))
+                                        throw new XamlParseException(
+                                            $"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}",
+                                            node);
+
+                                result = new XamlIlAttacchedPropertyEqualsSelector(result, targetPropertyField, typedValue);
+                                break;
+                            }
                         case SelectorGrammar.ChildSyntax child:
                             result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Child);
                             break;
@@ -338,6 +380,34 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         }
     }
 
+
+    class XamlIlAttacchedPropertyEqualsSelector : XamlIlSelectorNode
+    {
+        public XamlIlAttacchedPropertyEqualsSelector(XamlIlSelectorNode previous,
+            IXamlField propertyFiled,
+            IXamlAstValueNode value)
+            : base(previous)
+        {
+            PropertyFiled = propertyFiled;
+            Value = value;
+        }
+
+        public IXamlField PropertyFiled { get; set; }
+        public IXamlAstValueNode Value { get; set; }
+
+        public override IXamlType TargetType => Previous?.TargetType;
+        protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
+        {
+            codeGen.Ldsfld(PropertyFiled);
+            context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
+            EmitCall(context, codeGen,
+                m => m.Name == "PropertyEquals"
+                     && m.Parameters.Count == 3
+                     && m.Parameters[1].FullName == "Avalonia.AvaloniaProperty"
+                     && m.Parameters[2].Equals(context.Configuration.WellKnownTypes.Object));
+        }
+    }
+
     class XamlIlOrSelectorNode : XamlIlSelectorNode
     {
         List<XamlIlSelectorNode> _selectors = new List<XamlIlSelectorNode>();

+ 2 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@@ -15,6 +15,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         public IXamlType AvaloniaObjectExtensions { get; }
         public IXamlType AvaloniaProperty { get; }
         public IXamlType AvaloniaPropertyT { get; }
+        public IXamlType AvaloniaAttachedPropertyT { get; }
         public IXamlType IBinding { get; }
         public IXamlMethod AvaloniaObjectBindMethod { get; }
         public IXamlMethod AvaloniaObjectSetValueMethod { get; }
@@ -96,6 +97,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             AvaloniaObjectExtensions = cfg.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
             AvaloniaProperty = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty");
             AvaloniaPropertyT = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty`1");
+            AvaloniaAttachedPropertyT = cfg.TypeSystem.GetType("Avalonia.AttachedProperty`1");
             BindingPriority = cfg.TypeSystem.GetType("Avalonia.Data.BindingPriority");
             IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding");
             IDisposable = cfg.TypeSystem.GetType("System.IDisposable");

+ 66 - 2
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@@ -25,6 +25,7 @@ namespace Avalonia.Markup.Parsers
             Traversal,
             TypeName,
             Property,
+            AttachedProperty,
             Template,
             End,
         }
@@ -74,6 +75,9 @@ namespace Avalonia.Markup.Parsers
                     case State.Name:
                         (state, syntax) = ParseName(ref r);
                         break;
+                    case State.AttachedProperty:
+                        (state, syntax) = ParseAttachedProperty(ref r);
+                        break;
                 }
                 if (syntax != null)
                 {
@@ -270,11 +274,15 @@ namespace Avalonia.Markup.Parsers
             return (State.CanHaveType, ParseType(ref r, new OfTypeSyntax()));
         }
 
-        private static (State, ISyntax) ParseProperty(ref CharacterReader r)
+        private static (State, ISyntax?) ParseProperty(ref CharacterReader r)
         {
             var property = r.ParseIdentifier();
 
-            if (!r.TakeIf('='))
+            if (r.TakeIf('('))
+            {
+                return (State.AttachedProperty, default);
+            }
+            else if (!r.TakeIf('='))
             {
                 throw new ExpressionParseException(r.Position, $"Expected '=', got '{r.Peek}'");
             }
@@ -286,6 +294,42 @@ namespace Avalonia.Markup.Parsers
             return (State.CanHaveType, new PropertySyntax { Property = property.ToString(), Value = value.ToString() });
         }
 
+        private static (State, ISyntax) ParseAttachedProperty(ref CharacterReader r)
+        {
+            var syntax = ParseType(ref r, new AttachedPropertySyntax());
+            if (!r.TakeIf('.'))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected '.', got '{r.Peek}'");
+            }
+            var property = r.ParseIdentifier();
+            if (property.IsEmpty)
+            {
+                throw new ExpressionParseException(r.Position, $"Expected Attached Property Name, got '{r.Peek}'");
+            }
+            syntax.Property = property.ToString();
+
+            if (!r.TakeIf(')'))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected ')', got '{r.Peek}'");
+            }
+
+            if (!r.TakeIf('='))
+            {
+                throw new ExpressionParseException(r.Position, $"Expected '=', got '{r.Peek}'");
+            }
+
+            var value = r.TakeUntil(']');
+
+            syntax.Value = value.ToString();
+
+            r.Take();
+
+            var state = r.End
+                ? State.End
+                : State.Middle;
+            return (state, syntax);
+        }
+
         private static TSyntax ParseType<TSyntax>(ref CharacterReader r, TSyntax syntax)
             where TSyntax : ITypeSyntax
         {
@@ -461,6 +505,26 @@ namespace Avalonia.Markup.Parsers
             }
         }
 
+        public class AttachedPropertySyntax : ISyntax, ITypeSyntax
+        {
+            public string Xmlns { get; set; } = string.Empty;
+
+            public string TypeName { get; set; } = string.Empty;
+
+            public string Property { get; set; } = string.Empty;
+
+            public string Value { get; set; } = string.Empty;
+
+            public override bool Equals(object? obj)
+            {
+                return obj is AttachedPropertySyntax syntax
+                    && syntax.Xmlns == Xmlns
+                    && syntax.TypeName == TypeName
+                    && syntax.Property == Property
+                    && syntax.Value == Value;
+            }
+        }
+
         public class IsSyntax : ISyntax, ITypeSyntax
         {
             public string TypeName { get; set; } = string.Empty;

+ 53 - 8
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using Avalonia.Styling;
 using Avalonia.Utilities;
+using System.Linq;
 
 namespace Avalonia.Markup.Parsers
 {
@@ -75,34 +76,78 @@ namespace Avalonia.Markup.Parsers
                                 throw new InvalidOperationException($"Cannot find '{property.Property}' on '{type}");
                             }
 
+                            {
+                                object? typedValue;
+
+                                if (TypeUtilities.TryConvert(
+                                        targetProperty.PropertyType,
+                                        property.Value,
+                                        CultureInfo.InvariantCulture,
+                                        out typedValue))
+                                {
+                                    result = result.PropertyEquals(targetProperty, typedValue);
+                                }
+                                else
+                                {
+                                    throw new InvalidOperationException(
+                                        $"Could not convert '{property.Value}' to '{targetProperty.PropertyType}");
+                                }
+                            }
+                            break;
+                        }
+                    case SelectorGrammar.AttachedPropertySyntax attachedProperty:
+                        var targetType = result?.TargetType;
+
+                        if (targetType == null)
+                        {
+                            throw new InvalidOperationException("Attached Property selectors must be applied to a type.");
+                        }
+
+                        var attachedPropertyOwnerType = Resolve(attachedProperty.Xmlns, attachedProperty.TypeName);
+
+                        if (attachedPropertyOwnerType is null)
+                        {
+                            throw new InvalidOperationException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}");
+                        }
+
+                        var targetAttachedProperty = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(targetType)
+                            .FirstOrDefault(ap => ap.OwnerType == attachedPropertyOwnerType && ap.Name == attachedProperty.Property);
+
+                        if (targetAttachedProperty == null)
+                        {
+                            throw new InvalidOperationException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType}");
+                        }
+
+                        {
                             object? typedValue;
 
                             if (TypeUtilities.TryConvert(
-                                    targetProperty.PropertyType,
-                                    property.Value,
+                                    targetAttachedProperty.PropertyType,
+                                    attachedProperty.Value,
                                     CultureInfo.InvariantCulture,
                                     out typedValue))
                             {
-                                result = result.PropertyEquals(targetProperty, typedValue);
+                                result = result.PropertyEquals(targetAttachedProperty, typedValue);
                             }
                             else
                             {
                                 throw new InvalidOperationException(
-                                    $"Could not convert '{property.Value}' to '{targetProperty.PropertyType}");
+                                    $"Could not convert '{attachedProperty.Value}' to '{targetAttachedProperty.PropertyType}");
                             }
-                            break;
                         }
+
+                        break;
                     case SelectorGrammar.ChildSyntax child:
-                        result = result.Child();
+                        result = result!.Child();
                         break;
                     case SelectorGrammar.DescendantSyntax descendant:
                         result = result.Descendant();
                         break;
                     case SelectorGrammar.TemplateSyntax template:
-                        result = result.Template();
+                        result = result!.Template();
                         break;
                     case SelectorGrammar.NotSyntax not:
-                        result = result.Not(x => Create(not.Argument));
+                        result = result.Not(x => Create(not.Argument)!);
                         break;
                     case SelectorGrammar.NthChildSyntax nth:
                         result = result.NthChild(nth.Step, nth.Offset);

+ 2 - 2
src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs

@@ -20,8 +20,8 @@ namespace Avalonia.Skia
         /// <summary>
         /// Creates an offscreen render target surface
         /// </summary>
-        /// <param name="size">size in pixels</param>
-        /// <param name="session">current Skia render session</param>
+        /// <param name="size">size in pixels.</param>
+        /// <param name="session">An optional custom render session.</param>
         ISkiaSurface TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session);
     }
     

+ 1 - 2
src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs

@@ -106,8 +106,7 @@ namespace Avalonia.Web.Blazor
 
         public IRenderer CreateRenderer(IRenderRoot root)
         {
-            var loop = AvaloniaLocator.Current.GetService<IRenderLoop>() ??
-                throw new InvalidOperationException("Unable to locate IRenderLoop.");
+            var loop = AvaloniaLocator.Current.GetRequiredService<IRenderLoop>();
             return new DeferredRenderer(root, loop);
         }
 

+ 1 - 2
src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs

@@ -98,8 +98,7 @@ namespace Avalonia.Web.Blazor
 
         private static IRuntimePlatform GetRuntimePlatform()
         {
-            return AvaloniaLocator.Current.GetService<IRuntimePlatform>() ??
-                throw new InvalidOperationException("Unable to locate IRuntimePlatform.");
+            return AvaloniaLocator.Current.GetRequiredService<IRuntimePlatform>();
         }
     }
 }

+ 1 - 2
src/Windows/Avalonia.Win32/FramebufferManager.cs

@@ -107,8 +107,7 @@ namespace Avalonia.Win32
 
         private static FramebufferData AllocateFramebufferData(int width, int height)
         {
-            var service = AvaloniaLocator.Current.GetService<IRuntimePlatform>() ??
-                throw new InvalidOperationException("Unable to locate IRuntimePlatform.");
+            var service = AvaloniaLocator.Current.GetRequiredService<IRuntimePlatform>();
             var bitmapBlob = service.AllocBlob(width * height * _bytesPerPixel);
 
             return new FramebufferData(bitmapBlob, width, height);

+ 5 - 1
tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs

@@ -105,7 +105,11 @@ namespace Avalonia.Controls.UnitTests
                 var calendar =
                     new Calendar
                     {
-                        Name = "PART_Calendar"
+                        Name = "PART_Calendar", 
+                        [!Calendar.SelectedDateProperty] = control[!CalendarDatePicker.SelectedDateProperty],
+                        [!Calendar.DisplayDateProperty] = control[!CalendarDatePicker.DisplayDateProperty],
+                        [!Calendar.DisplayDateStartProperty] = control[!CalendarDatePicker.DisplayDateStartProperty],
+                        [!Calendar.DisplayDateEndProperty] = control[!CalendarDatePicker.DisplayDateEndProperty]
                     }.RegisterInNameScope(scope);
                 var popup =
                     new Popup

+ 272 - 0
tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs

@@ -0,0 +1,272 @@
+using System;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+using Avalonia.UnitTests;
+using Moq;
+using Xunit;
+
+namespace Avalonia.Input.UnitTests
+{
+    public class TouchDeviceTests
+    {
+        [Fact]
+        public void Tapped_Event_Is_Fired_With_Touch()
+        {
+            using (UnitTestApplication.Start(
+                new TestServices(inputManager: new InputManager())))
+            {
+                var root = new TestRoot();
+                var touchDevice = new TouchDevice();
+
+                var isTapped = false;
+                var executedTimes = 0;
+                root.Tapped += (a, e) =>
+                {
+                    isTapped = true;
+                    executedTimes++;
+                };
+                TapOnce(InputManager.Instance, touchDevice, root);
+                Assert.True(isTapped);
+                Assert.Equal(1, executedTimes);
+            }
+        }
+
+        [Fact]
+        public void DoubleTapped_Event_Is_Fired_With_Touch()
+        {
+            var platformSettingsMock = new Mock<IPlatformSettings>();
+            platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
+            AvaloniaLocator.CurrentMutable.BindToSelf(this)
+               .Bind<IPlatformSettings>().ToConstant(platformSettingsMock.Object);
+            using (UnitTestApplication.Start(
+                new TestServices(inputManager: new InputManager())))
+            {
+                var root = new TestRoot();
+                var touchDevice = new TouchDevice();
+
+                var isDoubleTapped = false;
+                var doubleTappedExecutedTimes = 0;
+                var tappedExecutedTimes = 0;
+                root.DoubleTapped += (a, e) =>
+                {
+                    isDoubleTapped = true;
+                    doubleTappedExecutedTimes++;
+                };
+                root.Tapped += (a, e) =>
+                {
+                    tappedExecutedTimes++;
+                };
+                TapOnce(InputManager.Instance, touchDevice, root);
+                TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 1);
+                Assert.Equal(1, tappedExecutedTimes);
+                Assert.True(isDoubleTapped);
+                Assert.Equal(1, doubleTappedExecutedTimes);
+            }
+        }
+
+        [Theory]
+        [InlineData(1)]
+        [InlineData(2)]
+        [InlineData(3)]
+        [InlineData(4)]
+        [InlineData(5)]
+        public void PointerPressed_Counts_Clicks_Correctly(int clickCount)
+        {
+            var platformSettingsMock = new Mock<IPlatformSettings>();
+            platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
+            AvaloniaLocator.CurrentMutable.BindToSelf(this)
+               .Bind<IPlatformSettings>().ToConstant(platformSettingsMock.Object);
+            using (UnitTestApplication.Start(
+                new TestServices(inputManager: new InputManager())))
+            {
+                var root = new TestRoot();
+                var touchDevice = new TouchDevice();
+
+                var pointerPressedExecutedTimes = 0;
+                var pointerPressedClicks = 0;
+                root.PointerPressed += (a, e) =>
+                {
+                    pointerPressedClicks = e.ClickCount;
+                    pointerPressedExecutedTimes++;
+                };
+                for (int i = 0; i < clickCount; i++)
+                {
+                    TapOnce(InputManager.Instance, touchDevice, root, touchPointId: i);
+                }
+
+                Assert.Equal(clickCount, pointerPressedExecutedTimes);
+                Assert.Equal(pointerPressedClicks, clickCount);
+            }
+        }
+
+        [Fact]
+        public void DoubleTapped_Not_Fired_When_Click_Too_Late()
+        {
+            var platformSettingsMock = new Mock<IPlatformSettings>();
+            platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(0, 0, 0, 0, 20));
+            AvaloniaLocator.CurrentMutable.BindToSelf(this)
+               .Bind<IPlatformSettings>().ToConstant(platformSettingsMock.Object);
+            using (UnitTestApplication.Start(
+                new TestServices(inputManager: new InputManager())))
+            {
+                var root = new TestRoot();
+                var touchDevice = new TouchDevice();
+
+                var isDoubleTapped = false;
+                var doubleTappedExecutedTimes = 0;
+                var tappedExecutedTimes = 0;
+                root.DoubleTapped += (a, e) =>
+                {
+                    isDoubleTapped = true;
+                    doubleTappedExecutedTimes++;
+                };
+                root.Tapped += (a, e) =>
+                {
+                    tappedExecutedTimes++;
+                };
+                TapOnce(InputManager.Instance, touchDevice, root);
+                TapOnce(InputManager.Instance, touchDevice, root, 21, 1);
+                Assert.Equal(2, tappedExecutedTimes);
+                Assert.False(isDoubleTapped);
+                Assert.Equal(0, doubleTappedExecutedTimes);
+            }
+        }
+
+        [Fact]
+        public void DoubleTapped_Not_Fired_When_Second_Click_Is_From_Different_Touch_Contact()
+        {
+            var tmp = new Mock<IPlatformSettings>();
+            tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
+            AvaloniaLocator.CurrentMutable.BindToSelf(this)
+               .Bind<IPlatformSettings>().ToConstant(tmp.Object);
+            using (UnitTestApplication.Start(
+                new TestServices(inputManager: new InputManager())))
+            {
+                var root = new TestRoot();
+                var touchDevice = new TouchDevice();
+
+                var isDoubleTapped = false;
+                var doubleTappedExecutedTimes = 0;
+                var tappedExecutedTimes = 0;
+                root.DoubleTapped += (a, e) =>
+                {
+                    isDoubleTapped = true;
+                    doubleTappedExecutedTimes++;
+                };
+                root.Tapped += (a, e) =>
+                {
+                    tappedExecutedTimes++;
+                };
+                SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1);
+                SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1);
+                Assert.Equal(2, tappedExecutedTimes);
+                Assert.False(isDoubleTapped);
+                Assert.Equal(0, doubleTappedExecutedTimes);
+            }
+        }
+
+        [Fact]
+        public void Click_Counting_Should_Work_Correctly_With_Few_Touch_Contacts()
+        {
+            var tmp = new Mock<IPlatformSettings>();
+            tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
+            AvaloniaLocator.CurrentMutable.BindToSelf(this)
+               .Bind<IPlatformSettings>().ToConstant(tmp.Object);
+            using (UnitTestApplication.Start(
+                new TestServices(inputManager: new InputManager())))
+            {
+                var root = new TestRoot();
+                var touchDevice = new TouchDevice();
+
+                var pointerPressedExecutedTimes = 0;
+                var tappedExecutedTimes = 0;
+                var isDoubleTapped = false;
+                var doubleTappedExecutedTimes = 0;
+                root.PointerPressed += (a, e) =>
+                {
+                    pointerPressedExecutedTimes++;
+                    switch (pointerPressedExecutedTimes)
+                    {
+                        case <= 2:
+                            Assert.True(e.ClickCount == 1);
+                            break;
+                        case 3:
+                            Assert.True(e.ClickCount == 2);
+                            break;
+                        case 4:
+                            Assert.True(e.ClickCount == 3);
+                            break;
+                        case 5:
+                            Assert.True(e.ClickCount == 4);
+                            break;
+                        case 6:
+                            Assert.True(e.ClickCount == 5);
+                            break;
+                        case 7:
+                            Assert.True(e.ClickCount == 1);
+                            break;
+                        case 8:
+                            Assert.True(e.ClickCount == 1);
+                            break;
+                        case 9:
+                            Assert.True(e.ClickCount == 2);
+                            break;
+                        default:
+                            break;
+                    }
+                };
+                root.DoubleTapped += (a, e) =>
+                {
+                    isDoubleTapped = true;
+                    doubleTappedExecutedTimes++;
+                };
+                root.Tapped += (a, e) =>
+                {
+                    tappedExecutedTimes++;
+                };
+                SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1);
+                SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1);
+                TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 2);
+                TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 3);
+                TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 4);
+                SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 5, 6, 7);
+                SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 5, 6, 7);
+                TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 8);
+                Assert.Equal(6, tappedExecutedTimes);
+                Assert.Equal(9, pointerPressedExecutedTimes);
+                Assert.True(isDoubleTapped);
+                Assert.Equal(3, doubleTappedExecutedTimes);
+
+            }
+        }
+        private static void SendXTouchContactsWithIds(IInputManager inputManager, TouchDevice device, IInputRoot root, RawPointerEventType type, params long[] touchPointIds)
+        {
+            for (int i = 0; i < touchPointIds.Length; i++)
+            {
+                inputManager.ProcessInput(new RawTouchEventArgs(device, 0,
+                                                              root,
+                                                              type,
+                                                              new Point(0, 0),
+                                                              RawInputModifiers.None,
+                                                              touchPointIds[i]));
+            }
+        }
+
+
+        private static void TapOnce(IInputManager inputManager, TouchDevice device, IInputRoot root, ulong timestamp = 0, long touchPointId = 0)
+        {
+            inputManager.ProcessInput(new RawTouchEventArgs(device, timestamp,
+                                               root,
+                                               RawPointerEventType.TouchBegin,
+                                               new Point(0, 0),
+                                               RawInputModifiers.None,
+                                               touchPointId));
+            inputManager.ProcessInput(new RawTouchEventArgs(device, timestamp,
+                                                root,
+                                                RawPointerEventType.TouchEnd,
+                                                new Point(0, 0),
+                                                RawInputModifiers.None,
+                                                touchPointId));
+        }
+    }
+}

+ 36 - 0
tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs

@@ -197,6 +197,42 @@ namespace Avalonia.Markup.UnitTests.Parsers
                 result);
         }
 
+        [Fact]
+        public void OfType_AttachedProperty()
+        {
+            var result = SelectorGrammar.Parse("Button[(Grid.Column)=1]");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
+                    new SelectorGrammar.AttachedPropertySyntax { 
+                        Xmlns = string.Empty,
+                        TypeName="Grid",
+                        Property = "Column",
+                        Value = "1" },
+                },
+                result);
+        }
+
+        [Fact]
+        public void OfType_AttachedProperty_WithNamespace()
+        {
+            var result = SelectorGrammar.Parse("Button[(x|Grid.Column)=1]");
+
+            Assert.Equal(
+                new SelectorGrammar.ISyntax[]
+                {
+                    new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
+                    new SelectorGrammar.AttachedPropertySyntax {
+                        Xmlns = "x",
+                        TypeName="Grid",
+                        Property = "Column",
+                        Value = "1" },
+                },
+                result);
+        }
+
         [Fact]
         public void Not_OfType()
         {

+ 49 - 0
tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs

@@ -7,6 +7,25 @@ namespace Avalonia.Markup.UnitTests.Parsers
 {
     public class SelectorParserTests
     {
+        static SelectorParserTests()
+        {
+            //Ensure the attached properties are registered before run tests
+            System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Grid).TypeHandle);
+            System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Auth).TypeHandle);
+        }
+
+        class Auth
+        {
+            public readonly static AttachedProperty<string> NameProperty =
+                AvaloniaProperty.RegisterAttached<Auth, AvaloniaObject, string>("Name");
+
+            public static string GetName(AvaloniaObject avaloniaObject) =>
+                avaloniaObject.GetValue(NameProperty);
+
+            public static void SetName(AvaloniaObject avaloniaObject, string value) =>
+                avaloniaObject.SetValue(NameProperty, value);
+        }
+
         [Fact]
         public void Parses_Boolean_Property_Selector()
         {
@@ -14,6 +33,36 @@ namespace Avalonia.Markup.UnitTests.Parsers
             var result = target.Parse("TextBlock[IsPointerOver=True]");
         }
 
+        [Fact]
+        public void Parses_AttacchedProperty_Selector_With_Namespace()
+        {
+            var target = new SelectorParser((ns, type) =>
+                {
+                    return (ns, type) switch
+                    {
+                        ("", nameof(TextBlock)) => typeof(TextBlock),
+                        ("l",nameof(Auth)) => typeof(Auth),
+                        _ => null
+                    };
+                });
+            var result = target.Parse("TextBlock[(l|Auth.Name)=Admin]");
+        }
+
+        [Fact]
+        public void Parses_AttacchedProperty_Selector()
+        {
+            var target = new SelectorParser((ns, type) =>
+            {
+                return (ns, type) switch
+                {
+                    ("", nameof(TextBlock)) => typeof(TextBlock),
+                    ("", nameof(Grid)) => typeof(Grid),
+                    _ => null
+                };
+            });
+            var result = target.Parse("TextBlock[(Grid.Column)=1]");
+        }
+
         [Fact]
         public void Parses_Comma_Separated_Selectors()
         {

+ 67 - 0
tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs

@@ -8,6 +8,73 @@ namespace Avalonia.Styling.UnitTests
 {
     public class SelectorTests_PropertyEquals
     {
+        static SelectorTests_PropertyEquals()
+        {
+            //Ensure the attached properties are registered before run tests
+            System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Grid).TypeHandle);
+            System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Auth).TypeHandle);
+        }
+
+        class Auth
+        {
+            public readonly static AttachedProperty<string> NameProperty =
+                AvaloniaProperty.RegisterAttached<Auth, AvaloniaObject, string>("Name");
+
+            public static string GetName(AvaloniaObject avaloniaObject) =>
+                avaloniaObject.GetValue(NameProperty);
+
+            public static void SetName(AvaloniaObject avaloniaObject, string value) =>
+                avaloniaObject.SetValue(NameProperty, value);
+        }
+
+        [Fact]
+        public async Task PropertyEquals_Attached_Property_Matching_Value()
+        {
+            var target = new Markup.Parsers.SelectorParser((ns, type) =>
+            {
+                return (ns, type) switch
+                {
+                    ("", nameof(TextBlock)) => typeof(TextBlock),
+                    ("", nameof(Grid)) => typeof(Grid),
+                    _ => null
+                };
+            }).Parse("TextBlock[(Grid.Column)=1]");
+            
+
+            var control = new TextBlock();
+            var activator = target.Match(control).Activator.ToObservable();
+
+            Assert.False(await activator.Take(1));
+            Grid.SetColumn(control, 1);
+            Assert.True(await activator.Take(1));
+            Grid.SetColumn(control, 0);
+            Assert.False(await activator.Take(1));
+        }
+
+        [Fact]
+        public async Task PropertyEquals_Attached_Property_With_Namespace_Matching_Value()
+        {
+            var target = new Markup.Parsers.SelectorParser((ns, type) =>
+            {
+                return (ns, type) switch
+                {
+                    ("", nameof(TextBlock)) => typeof(TextBlock),
+                    ("l", nameof(Auth)) => typeof(Auth),
+                    _ => null
+                };
+            }).Parse("TextBlock[(l|Auth.Name)=Admin]");
+
+
+            var control = new TextBlock();
+            var activator = target.Match(control).Activator.ToObservable();
+
+            Assert.False(await activator.Take(1));
+            Auth.SetName(control, "Admin");
+            Assert.True(await activator.Take(1));
+            Auth.SetName(control, null);
+            Assert.False(await activator.Take(1));
+        }
+
         [Fact]
         public async Task PropertyEquals_Matches_When_Property_Has_Matching_Value()
         {