Просмотр исходного кода

Merge branch 'master' into Stylus

Max Katz 3 лет назад
Родитель
Сommit
f6db567025
100 измененных файлов с 3883 добавлено и 3664 удалено
  1. 27 1
      Avalonia.sln
  2. 1 1
      build/ImageSharp.props
  3. 5 0
      native/Avalonia.Native/inc/rendertarget.h
  4. 17 0
      native/Avalonia.Native/src/OSX/AutoFitContentView.h
  5. 106 0
      native/Avalonia.Native/src/OSX/AutoFitContentView.mm
  6. 72 6
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  7. 5 1
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme
  8. 11 0
      native/Avalonia.Native/src/OSX/AvnPanelWindow.mm
  9. 27 0
      native/Avalonia.Native/src/OSX/AvnView.h
  10. 712 0
      native/Avalonia.Native/src/OSX/AvnView.mm
  11. 441 0
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  12. 17 0
      native/Avalonia.Native/src/OSX/INSWindowHolder.h
  13. 18 0
      native/Avalonia.Native/src/OSX/IWindowStateChanged.h
  14. 9 0
      native/Avalonia.Native/src/OSX/PopupImpl.h
  15. 68 0
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  16. 24 0
      native/Avalonia.Native/src/OSX/ResizeScope.h
  17. 18 0
      native/Avalonia.Native/src/OSX/ResizeScope.mm
  18. 1 1
      native/Avalonia.Native/src/OSX/SystemDialogs.mm
  19. 130 0
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  20. 589 0
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  21. 96 0
      native/Avalonia.Native/src/OSX/WindowImpl.h
  22. 552 0
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  23. 17 0
      native/Avalonia.Native/src/OSX/WindowInterfaces.h
  24. 25 0
      native/Avalonia.Native/src/OSX/WindowProtocol.h
  25. 0 12
      native/Avalonia.Native/src/OSX/app.mm
  26. 2 2
      native/Avalonia.Native/src/OSX/automation.h
  27. 2 1
      native/Avalonia.Native/src/OSX/automation.mm
  28. 1 2
      native/Avalonia.Native/src/OSX/common.h
  29. 0 1
      native/Avalonia.Native/src/OSX/cursor.mm
  30. 1 6
      native/Avalonia.Native/src/OSX/main.mm
  31. 0 1
      native/Avalonia.Native/src/OSX/menu.h
  32. 2 4
      native/Avalonia.Native/src/OSX/menu.mm
  33. 0 4
      native/Avalonia.Native/src/OSX/rendertarget.mm
  34. 0 77
      native/Avalonia.Native/src/OSX/window.h
  35. 0 2590
      native/Avalonia.Native/src/OSX/window.mm
  36. 0 1
      samples/ControlCatalog.NetCore/Program.cs
  37. 12 1
      samples/ControlCatalog/App.xaml.cs
  38. 1 0
      samples/ControlCatalog/ControlCatalog.csproj
  39. 3 0
      samples/ControlCatalog/MainView.xaml
  40. 8 4
      samples/ControlCatalog/MainView.xaml.cs
  41. 79 0
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  42. 19 0
      samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs
  43. 10 0
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  44. 2 0
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  45. 10 0
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  46. 13 0
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  47. 1 1
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  48. 2 3
      samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
  49. 1 0
      samples/Sandbox/Sandbox.csproj
  50. 4 3
      src/Avalonia.Base/Animation/Animatable.cs
  51. 48 29
      src/Avalonia.Base/AvaloniaObject.cs
  52. 43 191
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  53. 8 15
      src/Avalonia.Base/AvaloniaProperty.cs
  54. 43 0
      src/Avalonia.Base/AvaloniaPropertyChangedExtensions.cs
  55. 6 0
      src/Avalonia.Base/Collections/AvaloniaList.cs
  56. 4 7
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  57. 12 7
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  58. 32 12
      src/Avalonia.Base/DirectPropertyBase.cs
  59. 1 1
      src/Avalonia.Base/GeometryGroup.cs
  60. 9 95
      src/Avalonia.Base/IAvaloniaObject.cs
  61. 4 4
      src/Avalonia.Base/Input/InputElement.cs
  62. 1 1
      src/Avalonia.Base/Input/KeyboardNavigation.cs
  63. 3 2
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  64. 0 24
      src/Avalonia.Base/Interactivity/IInteractive.cs
  65. 56 3
      src/Avalonia.Base/Layout/LayoutHelper.cs
  66. 12 2
      src/Avalonia.Base/Layout/Layoutable.cs
  67. 2 2
      src/Avalonia.Base/Layout/StackLayout.cs
  68. 10 10
      src/Avalonia.Base/Layout/UniformGridLayout.cs
  69. 1 1
      src/Avalonia.Base/Layout/WrapLayout/WrapLayout.cs
  70. 0 51
      src/Avalonia.Base/Logging/ILogSink.cs
  71. 3 35
      src/Avalonia.Base/Logging/TraceLogSink.cs
  72. 2 2
      src/Avalonia.Base/Media/ConicGradientBrush.cs
  73. 2 3
      src/Avalonia.Base/Media/DashStyle.cs
  74. 1 1
      src/Avalonia.Base/Media/DrawingImage.cs
  75. 47 32
      src/Avalonia.Base/Media/GlyphRun.cs
  76. 17 11
      src/Avalonia.Base/Media/Pen.cs
  77. 1 3
      src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs
  78. 47 18
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  79. 66 53
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  80. 76 103
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  81. 5 0
      src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs
  82. 1 0
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  83. 8 9
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  84. 6 7
      src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
  85. 1 1
      src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
  86. 1 2
      src/Avalonia.Base/PropertyStore/IValue.cs
  87. 0 17
      src/Avalonia.Base/PropertyStore/IValueSink.cs
  88. 2 3
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  89. 33 34
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  90. 45 0
      src/Avalonia.Base/PropertyStore/ValueOwner.cs
  91. 1 1
      src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
  92. 19 11
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  93. 6 1
      src/Avalonia.Base/StyledElement.cs
  94. 32 12
      src/Avalonia.Base/StyledPropertyBase.cs
  95. 1 1
      src/Avalonia.Base/Styling/IStyle.cs
  96. 2 63
      src/Avalonia.Base/Styling/Setter.cs
  97. 1 1
      src/Avalonia.Base/Styling/Styles.cs
  98. 0 26
      src/Avalonia.Base/Threading/IDispatcher.cs
  99. 1 2
      src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs
  100. 0 32
      src/Avalonia.Base/Utilities/IAvaloniaPropertyVisitor.cs

+ 27 - 1
Avalonia.sln

@@ -99,7 +99,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
 		build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
 		build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
 		build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
-		build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
 		build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
 		build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
 		build\Moq.props = build\Moq.props
@@ -118,6 +117,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
 		build\System.Memory.props = build\System.Memory.props
 		build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
 		build\XUnit.props = build\XUnit.props
+		build\ImageSharp.props = build\ImageSharp.props
 	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
@@ -169,6 +169,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "sam
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ColorPicker", "src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj", "{1ECC012A-8837-4AE2-9BDA-3E2857898727}"
+EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
@@ -1963,6 +1965,30 @@ Global
 		{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhone.Build.0 = Release|Any CPU
 		{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.Build.0 = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 1 - 1
build/Magick.NET-Q16-AnyCPU.props → build/ImageSharp.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.9.0.2" />
+    <PackageReference Include="SixLabors.ImageSharp" Version="2.1.1" />
   </ItemGroup>
 </Project>

+ 5 - 0
native/Avalonia.Native/inc/rendertarget.h

@@ -1,3 +1,8 @@
+#pragma once
+
+#include "com.h"
+#include "comimpl.h"
+#include "avalonia-native.h"
 
 @protocol IRenderTarget
 -(void) setNewLayer: (CALayer*) layer;

+ 17 - 0
native/Avalonia.Native/src/OSX/AutoFitContentView.h

@@ -0,0 +1,17 @@
+//
+// Created by Dan Walmsley on 05/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#pragma once
+
+#import <Foundation/Foundation.h>
+#include "avalonia-native.h"
+
+@interface AutoFitContentView : NSView
+-(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;
+-(void) ShowTitleBar: (bool) show;
+-(void) SetTitleBarHeightHint: (double) height;
+
+-(void) ShowBlur: (bool) show;
+@end

+ 106 - 0
native/Avalonia.Native/src/OSX/AutoFitContentView.mm

@@ -0,0 +1,106 @@
+//
+// Created by Dan Walmsley on 05/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#include "AvnView.h"
+#include "AutoFitContentView.h"
+#include "WindowInterfaces.h"
+#include "WindowProtocol.h"
+
+@implementation AutoFitContentView
+{
+    NSVisualEffectView* _titleBarMaterial;
+    NSBox* _titleBarUnderline;
+    NSView* _content;
+    NSVisualEffectView* _blurBehind;
+    double _titleBarHeightHint;
+    bool _settingSize;
+}
+
+-(AutoFitContentView* _Nonnull) initWithContent:(NSView *)content
+{
+    _titleBarHeightHint = -1;
+    _content = content;
+    _settingSize = false;
+
+    [self setAutoresizesSubviews:true];
+    [self setWantsLayer:true];
+
+    _titleBarMaterial = [NSVisualEffectView new];
+    [_titleBarMaterial setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
+    [_titleBarMaterial setMaterial:NSVisualEffectMaterialTitlebar];
+    [_titleBarMaterial setWantsLayer:true];
+    _titleBarMaterial.hidden = true;
+
+    _titleBarUnderline = [NSBox new];
+    _titleBarUnderline.boxType = NSBoxSeparator;
+    _titleBarUnderline.fillColor = [NSColor underPageBackgroundColor];
+    _titleBarUnderline.hidden = true;
+
+    [self addSubview:_titleBarMaterial];
+    [self addSubview:_titleBarUnderline];
+
+    _blurBehind = [NSVisualEffectView new];
+    [_blurBehind setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
+    [_blurBehind setMaterial:NSVisualEffectMaterialLight];
+    [_blurBehind setWantsLayer:true];
+    _blurBehind.hidden = true;
+
+    [_blurBehind setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+    [_content setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+
+    [self addSubview:_blurBehind];
+    [self addSubview:_content];
+
+    [self setWantsLayer:true];
+    return self;
+}
+
+-(void) ShowBlur:(bool)show
+{
+    _blurBehind.hidden = !show;
+}
+
+-(void) ShowTitleBar: (bool) show
+{
+    _titleBarMaterial.hidden = !show;
+    _titleBarUnderline.hidden = !show;
+}
+
+-(void) SetTitleBarHeightHint: (double) height
+{
+    _titleBarHeightHint = height;
+
+    [self setFrameSize:self.frame.size];
+}
+
+-(void)setFrameSize:(NSSize)newSize
+{
+    if(_settingSize)
+    {
+        return;
+    }
+
+    _settingSize = true;
+    [super setFrameSize:newSize];
+
+    auto window = static_cast<id <AvnWindowProtocol>>([self window]);
+
+    // TODO get actual titlebar size
+
+    double height = _titleBarHeightHint == -1 ? [window getExtendedTitleBarHeight] : _titleBarHeightHint;
+
+    NSRect tbar;
+    tbar.origin.x = 0;
+    tbar.origin.y = newSize.height - height;
+    tbar.size.width = newSize.width;
+    tbar.size.height = height;
+
+    [_titleBarMaterial setFrame:tbar];
+    tbar.size.height = height < 1 ? 0 : 1;
+    [_titleBarUnderline setFrame:tbar];
+
+    _settingSize = false;
+}
+@end

+ 72 - 6
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@@ -7,6 +7,24 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391E45702740FE9DD69695 /* ResizeScope.mm */; };
+		1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 183919BF108EB72A029F7671 /* WindowImpl.mm */; };
+		183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391DB45C7D892E61BF388C /* WindowInterfaces.h */; };
+		1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391884C7476DA4E53A492D /* AvnPanelWindow.mm */; };
+		183916173528EC2737DBE5E1 /* WindowBaseImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */; };
+		1839171DCC651B0638603AC4 /* INSWindowHolder.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391BBB7782C296D424071F /* INSWindowHolder.h */; };
+		1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391676ECF0E983F4964357 /* WindowBaseImpl.mm */; };
+		183919D91DB9AAB5D700C2EA /* WindowImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391CD090AA776E7E841AC9 /* WindowImpl.h */; };
+		18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839166350F32661F3ABD70F /* AutoFitContentView.mm */; };
+		18391AC16726CBC45856233B /* AvnWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839155B28B20FFB672D29C6 /* AvnWindow.mm */; };
+		18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 183910513F396141938832B5 /* PopupImpl.h */; };
+		18391C28BF1823B5464FDD36 /* ResizeScope.h in Headers */ = {isa = PBXBuildFile; fileRef = 1839171D898F9BFC1373631A /* ResizeScope.h */; };
+		18391CF07316F819E76B617C /* IWindowStateChanged.h in Headers */ = {isa = PBXBuildFile; fileRef = 183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */; };
+		18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839132D0E2454D911F1D1F9 /* AvnView.mm */; };
+		18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391BB698579F40F1783F31 /* PopupImpl.mm */; };
+		18391E1381E2D5BFD60265A9 /* AutoFitContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */; };
+		18391ED5F611FF62C45F196D /* AvnView.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391D1669284AD2EC9E866A /* AvnView.h */; };
+		18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 1839122E037567BDD1D09DEB /* WindowProtocol.h */; };
 		1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
 		1A1852DC23E05814008F0DED /* deadlock.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A1852DB23E05814008F0DED /* deadlock.mm */; };
 		1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; };
@@ -28,13 +46,30 @@
 		AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
 		AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
 		AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
-		AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; };
 		AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
 		BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
 		BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		183910513F396141938832B5 /* PopupImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopupImpl.h; sourceTree = "<group>"; };
+		1839122E037567BDD1D09DEB /* WindowProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowProtocol.h; sourceTree = "<group>"; };
+		1839132D0E2454D911F1D1F9 /* AvnView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnView.mm; sourceTree = "<group>"; };
+		183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IWindowStateChanged.h; sourceTree = "<group>"; };
+		1839155B28B20FFB672D29C6 /* AvnWindow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnWindow.mm; sourceTree = "<group>"; };
+		183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowBaseImpl.h; sourceTree = "<group>"; };
+		18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoFitContentView.h; sourceTree = "<group>"; };
+		1839166350F32661F3ABD70F /* AutoFitContentView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AutoFitContentView.mm; sourceTree = "<group>"; };
+		18391676ECF0E983F4964357 /* WindowBaseImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowBaseImpl.mm; sourceTree = "<group>"; };
+		1839171D898F9BFC1373631A /* ResizeScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResizeScope.h; sourceTree = "<group>"; };
+		18391884C7476DA4E53A492D /* AvnPanelWindow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnPanelWindow.mm; sourceTree = "<group>"; };
+		183919BF108EB72A029F7671 /* WindowImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowImpl.mm; sourceTree = "<group>"; };
+		18391BB698579F40F1783F31 /* PopupImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PopupImpl.mm; sourceTree = "<group>"; };
+		18391BBB7782C296D424071F /* INSWindowHolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INSWindowHolder.h; sourceTree = "<group>"; };
+		18391CD090AA776E7E841AC9 /* WindowImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowImpl.h; sourceTree = "<group>"; };
+		18391D1669284AD2EC9E866A /* AvnView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvnView.h; sourceTree = "<group>"; };
+		18391DB45C7D892E61BF388C /* WindowInterfaces.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowInterfaces.h; sourceTree = "<group>"; };
+		18391E45702740FE9DD69695 /* ResizeScope.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ResizeScope.mm; sourceTree = "<group>"; };
 		1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; };
 		1A1852DB23E05814008F0DED /* deadlock.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = deadlock.mm; sourceTree = "<group>"; };
 		1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = rendertarget.mm; sourceTree = "<group>"; };
@@ -48,7 +83,6 @@
 		37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
 		37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
 		37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; };
-		37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = "<group>"; };
 		37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; };
 		37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; };
 		37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
@@ -62,7 +96,6 @@
 		AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
 		AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
 		AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
-		AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = "<group>"; };
 		AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
 		AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libAvalonia.Native.OSX.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
 		AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = "<group>"; };
@@ -118,8 +151,6 @@
 				AB661C212148288600291242 /* common.h */,
 				379860FE214DA0C000CD0246 /* KeyTransform.h */,
 				37E2330E21583241000CB7E2 /* KeyTransform.mm */,
-				AB661C1F2148286E00291242 /* window.mm */,
-				37C09D8A21581EF2006A6758 /* window.h */,
 				AB00E4F62147CA920032A60A /* main.mm */,
 				37155CE3233C00EB0034DCE9 /* menu.h */,
 				520624B222973F4100C4DCEF /* menu.mm */,
@@ -130,6 +161,24 @@
 				37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
 				AB7A61F02147C815003C5833 /* Products */,
 				AB661C1C2148230E00291242 /* Frameworks */,
+				18391676ECF0E983F4964357 /* WindowBaseImpl.mm */,
+				183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */,
+				18391BBB7782C296D424071F /* INSWindowHolder.h */,
+				183919BF108EB72A029F7671 /* WindowImpl.mm */,
+				18391CD090AA776E7E841AC9 /* WindowImpl.h */,
+				183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */,
+				18391E45702740FE9DD69695 /* ResizeScope.mm */,
+				1839171D898F9BFC1373631A /* ResizeScope.h */,
+				1839132D0E2454D911F1D1F9 /* AvnView.mm */,
+				18391D1669284AD2EC9E866A /* AvnView.h */,
+				1839166350F32661F3ABD70F /* AutoFitContentView.mm */,
+				18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */,
+				18391884C7476DA4E53A492D /* AvnPanelWindow.mm */,
+				1839122E037567BDD1D09DEB /* WindowProtocol.h */,
+				1839155B28B20FFB672D29C6 /* AvnWindow.mm */,
+				18391DB45C7D892E61BF388C /* WindowInterfaces.h */,
+				18391BB698579F40F1783F31 /* PopupImpl.mm */,
+				183910513F396141938832B5 /* PopupImpl.h */,
 			);
 			sourceTree = "<group>";
 		};
@@ -150,6 +199,16 @@
 			files = (
 				37155CE4233C00EB0034DCE9 /* menu.h in Headers */,
 				BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */,
+				183916173528EC2737DBE5E1 /* WindowBaseImpl.h in Headers */,
+				1839171DCC651B0638603AC4 /* INSWindowHolder.h in Headers */,
+				183919D91DB9AAB5D700C2EA /* WindowImpl.h in Headers */,
+				18391CF07316F819E76B617C /* IWindowStateChanged.h in Headers */,
+				18391C28BF1823B5464FDD36 /* ResizeScope.h in Headers */,
+				18391ED5F611FF62C45F196D /* AvnView.h in Headers */,
+				18391E1381E2D5BFD60265A9 /* AutoFitContentView.h in Headers */,
+				18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */,
+				183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */,
+				18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -228,7 +287,14 @@
 				1A465D10246AB61600C5858B /* dnd.mm in Sources */,
 				AB00E4F72147CA920032A60A /* main.mm in Sources */,
 				37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
-				AB661C202148286E00291242 /* window.mm in Sources */,
+				1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */,
+				1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */,
+				18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */,
+				18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */,
+				18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */,
+				1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
+				18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
+				18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 5 - 1
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme

@@ -56,10 +56,14 @@
       </MacroExpansion>
       <CommandLineArguments>
          <CommandLineArgument
-            argument = "bin/Debug/netcoreapp3.1/ControlCatalog.NetCore.dll"
+            argument = "bin/Debug/net6.0/ControlCatalog.NetCore.dll"
             isEnabled = "YES">
          </CommandLineArgument>
       </CommandLineArguments>
+      <LocationScenarioReference
+         identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
+         referenceType = "1">
+      </LocationScenarioReference>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 11 - 0
native/Avalonia.Native/src/OSX/AvnPanelWindow.mm

@@ -0,0 +1,11 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#pragma once
+
+#define IS_NSPANEL
+
+#include "AvnWindow.mm"
+

+ 27 - 0
native/Avalonia.Native/src/OSX/AvnView.h

@@ -0,0 +1,27 @@
+//
+// Created by Dan Walmsley on 05/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+#pragma once
+#import <Foundation/Foundation.h>
+
+
+#import <Foundation/Foundation.h>
+#import <AppKit/AppKit.h>
+#include "common.h"
+#include "WindowImpl.h"
+#include "KeyTransform.h"
+
+@class AvnAccessibilityElement;
+
+@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
+-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
+-(NSEvent* _Nonnull) lastMouseDownEvent;
+-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
+-(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
+-(void) onClosed;
+
+-(AvnPlatformResizeReason) getResizeReason;
+-(void) setResizeReason:(AvnPlatformResizeReason)reason;
++ (AvnPoint)toAvnPoint:(CGPoint)p;
+@end

+ 712 - 0
native/Avalonia.Native/src/OSX/AvnView.mm

@@ -0,0 +1,712 @@
+//
+// Created by Dan Walmsley on 05/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#include "AvnView.h"
+#include "automation.h"
+#import "WindowInterfaces.h"
+
+@implementation AvnView
+{
+    ComPtr<WindowBaseImpl> _parent;
+    NSTrackingArea* _area;
+    bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed;
+    AvnInputModifiers _modifierState;
+    NSEvent* _lastMouseDownEvent;
+    bool _lastKeyHandled;
+    AvnPixelSize _lastPixelSize;
+    NSObject<IRenderTarget>* _renderTarget;
+    AvnPlatformResizeReason _resizeReason;
+    AvnAccessibilityElement* _accessibilityChild;
+}
+
+- (void)onClosed
+{
+    @synchronized (self)
+    {
+        _parent = nullptr;
+    }
+}
+
+- (NSEvent*) lastMouseDownEvent
+{
+    return _lastMouseDownEvent;
+}
+
+- (void) updateRenderTarget
+{
+    [_renderTarget resize:_lastPixelSize withScale:static_cast<float>([[self window] backingScaleFactor])];
+    [self setNeedsDisplayInRect:[self frame]];
+}
+
+-(AvnView*)  initWithParent: (WindowBaseImpl*) parent
+{
+    self = [super init];
+    _renderTarget = parent->renderTarget;
+    [self setWantsLayer:YES];
+    [self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize];
+
+    _parent = parent;
+    _area = nullptr;
+    _lastPixelSize.Height = 100;
+    _lastPixelSize.Width = 100;
+    [self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]];
+
+    _modifierState = AvnInputModifiersNone;
+    return self;
+}
+
+- (BOOL)isFlipped
+{
+    return YES;
+}
+
+- (BOOL)wantsUpdateLayer
+{
+    return YES;
+}
+
+- (void)setLayer:(CALayer *)layer
+{
+    [_renderTarget setNewLayer: layer];
+    [super setLayer: layer];
+}
+
+- (BOOL)isOpaque
+{
+    return YES;
+}
+
+- (BOOL)acceptsFirstResponder
+{
+    return true;
+}
+
+- (BOOL)acceptsFirstMouse:(NSEvent *)event
+{
+    return true;
+}
+
+- (BOOL)canBecomeKeyView
+{
+    return true;
+}
+
+-(void)setFrameSize:(NSSize)newSize
+{
+    [super setFrameSize:newSize];
+
+    if(_area != nullptr)
+    {
+        [self removeTrackingArea:_area];
+        _area = nullptr;
+    }
+
+    if (_parent == nullptr)
+    {
+        return;
+    }
+
+    NSRect rect = NSZeroRect;
+    rect.size = newSize;
+
+    NSTrackingAreaOptions options = NSTrackingActiveAlways | NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingEnabledDuringMouseDrag;
+    _area = [[NSTrackingArea alloc] initWithRect:rect options:options owner:self userInfo:nullptr];
+    [self addTrackingArea:_area];
+
+    _parent->UpdateCursor();
+
+    auto fsize = [self convertSizeToBacking: [self frame].size];
+
+    if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
+    {
+        _lastPixelSize.Width = (int)fsize.width;
+        _lastPixelSize.Height = (int)fsize.height;
+        [self updateRenderTarget];
+
+        auto reason = [self inLiveResize] ? ResizeUser : _resizeReason;
+        _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason);
+    }
+}
+
+- (void)updateLayer
+{
+    AvnInsidePotentialDeadlock deadlock;
+    if (_parent == nullptr)
+    {
+        return;
+    }
+
+    _parent->BaseEvents->RunRenderPriorityJobs();
+
+    if (_parent == nullptr)
+    {
+        return;
+    }
+
+    _parent->BaseEvents->Paint();
+}
+
+- (void)drawRect:(NSRect)dirtyRect
+{
+    return;
+}
+
+-(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose
+{
+    @autoreleasepool {
+        [_renderTarget setSwFrame:fb];
+        dispose->Release();
+    }
+}
+
+- (AvnPoint) translateLocalPoint:(AvnPoint)pt
+{
+    pt.Y = [self bounds].size.height - pt.Y;
+    return pt;
+}
+
++ (AvnPoint)toAvnPoint:(CGPoint)p
+{
+    AvnPoint result;
+
+    result.X = p.x;
+    result.Y = p.y;
+
+    return result;
+}
+
+- (void) viewDidChangeBackingProperties
+{
+    auto fsize = [self convertSizeToBacking: [self frame].size];
+    _lastPixelSize.Width = (int)fsize.width;
+    _lastPixelSize.Height = (int)fsize.height;
+    [self updateRenderTarget];
+
+    if(_parent != nullptr)
+    {
+        _parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
+    }
+
+    [super viewDidChangeBackingProperties];
+}
+
+- (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
+{
+    if(_parent == nullptr)
+    {
+        return TRUE;
+    }
+
+    auto parentWindow = _parent->GetWindowProtocol();
+
+    if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
+    {
+        if(trigerInputWhenDisabled)
+        {
+            auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
+
+            if(window != nullptr)
+            {
+                window->WindowEvents->GotInputWhenDisabled();
+            }
+        }
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
+{
+    bool triggerInputWhenDisabled = type != Move;
+
+    if([self ignoreUserInput: triggerInputWhenDisabled])
+    {
+        return;
+    }
+
+    auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
+    auto avnPoint = [AvnView toAvnPoint:localPoint];
+    auto point = [self translateLocalPoint:avnPoint];
+    AvnVector delta = { 0, 0};
+
+    if(type == Wheel)
+    {
+        auto speed = 5;
+
+        if([event hasPreciseScrollingDeltas])
+        {
+            speed = 50;
+        }
+
+        delta.X = [event scrollingDeltaX] / speed;
+        delta.Y = [event scrollingDeltaY] / speed;
+
+        if(delta.X == 0 && delta.Y == 0)
+        {
+            return;
+        }
+    }
+    else if (type == Magnify)
+    {
+        delta.X = delta.Y = [event magnification];
+    }
+    else if (type == Rotate)
+    {
+        delta.X = delta.Y = [event rotation];
+    }
+    else if (type == Swipe)
+    {
+        delta.X = [event deltaX];
+        delta.Y = [event deltaY];
+    }
+
+    uint32 timestamp = static_cast<uint32>([event timestamp] * 1000);
+    auto modifiers = [self getModifiers:[event modifierFlags]];
+
+    if(type != Move ||
+            (
+                    [self window] != nil &&
+                            (
+                                    [[self window] firstResponder] == nil
+                                            || ![[[self window] firstResponder] isKindOfClass: [NSView class]]
+                            )
+            )
+            )
+        [self becomeFirstResponder];
+
+    if(_parent != nullptr)
+    {
+        _parent->BaseEvents->RawMouseEvent(type, timestamp, modifiers, point, delta);
+    }
+
+    [super mouseMoved:event];
+}
+
+- (BOOL) resignFirstResponder
+{
+    _parent->BaseEvents->LostFocus();
+    return YES;
+}
+
+- (void)mouseMoved:(NSEvent *)event
+{
+    [self mouseEvent:event withType:Move];
+}
+
+- (void)mouseDown:(NSEvent *)event
+{
+    _isLeftPressed = true;
+    _lastMouseDownEvent = event;
+    [self mouseEvent:event withType:LeftButtonDown];
+}
+
+- (void)otherMouseDown:(NSEvent *)event
+{
+    _lastMouseDownEvent = event;
+
+    switch(event.buttonNumber)
+    {
+        case 2:
+        case 3:
+            _isMiddlePressed = true;
+            [self mouseEvent:event withType:MiddleButtonDown];
+            break;
+        case 4:
+            _isXButton1Pressed = true;
+            [self mouseEvent:event withType:XButton1Down];
+            break;
+        case 5:
+            _isXButton2Pressed = true;
+            [self mouseEvent:event withType:XButton2Down];
+            break;
+
+        default:
+            break;
+    }
+}
+
+- (void)rightMouseDown:(NSEvent *)event
+{
+    _isRightPressed = true;
+    _lastMouseDownEvent = event;
+    [self mouseEvent:event withType:RightButtonDown];
+}
+
+- (void)mouseUp:(NSEvent *)event
+{
+    _isLeftPressed = false;
+    [self mouseEvent:event withType:LeftButtonUp];
+}
+
+- (void)otherMouseUp:(NSEvent *)event
+{
+    switch(event.buttonNumber)
+    {
+        case 2:
+        case 3:
+            _isMiddlePressed = false;
+            [self mouseEvent:event withType:MiddleButtonUp];
+            break;
+        case 4:
+            _isXButton1Pressed = false;
+            [self mouseEvent:event withType:XButton1Up];
+            break;
+        case 5:
+            _isXButton2Pressed = false;
+            [self mouseEvent:event withType:XButton2Up];
+            break;
+
+        default:
+            break;
+    }
+}
+
+- (void)rightMouseUp:(NSEvent *)event
+{
+    _isRightPressed = false;
+    [self mouseEvent:event withType:RightButtonUp];
+}
+
+- (void)mouseDragged:(NSEvent *)event
+{
+    [self mouseEvent:event withType:Move];
+    [super mouseDragged:event];
+}
+
+- (void)otherMouseDragged:(NSEvent *)event
+{
+    [self mouseEvent:event withType:Move];
+    [super otherMouseDragged:event];
+}
+
+- (void)rightMouseDragged:(NSEvent *)event
+{
+    [self mouseEvent:event withType:Move];
+    [super rightMouseDragged:event];
+}
+
+- (void)scrollWheel:(NSEvent *)event
+{
+    [self mouseEvent:event withType:Wheel];
+    [super scrollWheel:event];
+}
+
+- (void)magnifyWithEvent:(NSEvent *)event
+{
+    [self mouseEvent:event withType:Magnify];
+    [super magnifyWithEvent:event];
+}
+
+- (void)rotateWithEvent:(NSEvent *)event
+{
+    [self mouseEvent:event withType:Rotate];
+    [super rotateWithEvent:event];
+}
+
+- (void)swipeWithEvent:(NSEvent *)event
+{
+    [self mouseEvent:event withType:Swipe];
+    [super swipeWithEvent:event];
+}
+
+- (void)mouseEntered:(NSEvent *)event
+{
+    [super mouseEntered:event];
+}
+
+- (void)mouseExited:(NSEvent *)event
+{
+    [self mouseEvent:event withType:LeaveWindow];
+    [super mouseExited:event];
+}
+
+- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
+{
+    if([self ignoreUserInput: false])
+    {
+        return;
+    }
+
+    auto key = s_KeyMap[[event keyCode]];
+
+    uint32_t timestamp = static_cast<uint32_t>([event timestamp] * 1000);
+    auto modifiers = [self getModifiers:[event modifierFlags]];
+
+    if(_parent != nullptr)
+    {
+        _lastKeyHandled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
+    }
+}
+
+- (BOOL)performKeyEquivalent:(NSEvent *)event
+{
+    bool result = _lastKeyHandled;
+
+    _lastKeyHandled = false;
+
+    return result;
+}
+
+- (void)flagsChanged:(NSEvent *)event
+{
+    auto newModifierState = [self getModifiers:[event modifierFlags]];
+
+    bool isAltCurrentlyPressed = (_modifierState & Alt) == Alt;
+    bool isControlCurrentlyPressed = (_modifierState & Control) == Control;
+    bool isShiftCurrentlyPressed = (_modifierState & Shift) == Shift;
+    bool isCommandCurrentlyPressed = (_modifierState & Windows) == Windows;
+
+    bool isAltPressed = (newModifierState & Alt) == Alt;
+    bool isControlPressed = (newModifierState & Control) == Control;
+    bool isShiftPressed = (newModifierState & Shift) == Shift;
+    bool isCommandPressed = (newModifierState & Windows) == Windows;
+
+
+    if (isAltPressed && !isAltCurrentlyPressed)
+    {
+        [self keyboardEvent:event withType:KeyDown];
+    }
+    else if (isAltCurrentlyPressed && !isAltPressed)
+    {
+        [self keyboardEvent:event withType:KeyUp];
+    }
+
+    if (isControlPressed && !isControlCurrentlyPressed)
+    {
+        [self keyboardEvent:event withType:KeyDown];
+    }
+    else if (isControlCurrentlyPressed && !isControlPressed)
+    {
+        [self keyboardEvent:event withType:KeyUp];
+    }
+
+    if (isShiftPressed && !isShiftCurrentlyPressed)
+    {
+        [self keyboardEvent:event withType:KeyDown];
+    }
+    else if(isShiftCurrentlyPressed && !isShiftPressed)
+    {
+        [self keyboardEvent:event withType:KeyUp];
+    }
+
+    if(isCommandPressed && !isCommandCurrentlyPressed)
+    {
+        [self keyboardEvent:event withType:KeyDown];
+    }
+    else if(isCommandCurrentlyPressed && ! isCommandPressed)
+    {
+        [self keyboardEvent:event withType:KeyUp];
+    }
+
+    _modifierState = newModifierState;
+
+    [[self inputContext] handleEvent:event];
+    [super flagsChanged:event];
+}
+
+- (void)keyDown:(NSEvent *)event
+{
+    [self keyboardEvent:event withType:KeyDown];
+    [[self inputContext] handleEvent:event];
+    [super keyDown:event];
+}
+
+- (void)keyUp:(NSEvent *)event
+{
+    [self keyboardEvent:event withType:KeyUp];
+    [super keyUp:event];
+}
+
+- (AvnInputModifiers)getModifiers:(NSEventModifierFlags)mod
+{
+    unsigned int rv = 0;
+
+    if (mod & NSEventModifierFlagControl)
+        rv |= Control;
+    if (mod & NSEventModifierFlagShift)
+        rv |= Shift;
+    if (mod & NSEventModifierFlagOption)
+        rv |= Alt;
+    if (mod & NSEventModifierFlagCommand)
+        rv |= Windows;
+
+    if (_isLeftPressed)
+        rv |= LeftMouseButton;
+    if (_isMiddlePressed)
+        rv |= MiddleMouseButton;
+    if (_isRightPressed)
+        rv |= RightMouseButton;
+    if (_isXButton1Pressed)
+        rv |= XButton1MouseButton;
+    if (_isXButton2Pressed)
+        rv |= XButton2MouseButton;
+
+    return (AvnInputModifiers)rv;
+}
+
+- (BOOL)hasMarkedText
+{
+    return _lastKeyHandled;
+}
+
+- (NSRange)markedRange
+{
+    return NSMakeRange(NSNotFound, 0);
+}
+
+- (NSRange)selectedRange
+{
+    return NSMakeRange(NSNotFound, 0);
+}
+
+- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
+{
+
+}
+
+- (void)unmarkText
+{
+
+}
+
+- (NSArray<NSString *> *)validAttributesForMarkedText
+{
+    return [NSArray new];
+}
+
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
+{
+    return [NSAttributedString new];
+}
+
+- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
+{
+    if(!_lastKeyHandled)
+    {
+        if(_parent != nullptr)
+        {
+            _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(0, [string UTF8String]);
+        }
+    }
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)point
+{
+    return 0;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange
+{
+    CGRect result = { 0 };
+
+    return result;
+}
+
+- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id <NSDraggingInfo>)info
+{
+    auto localPoint = [self convertPoint:[info draggingLocation] toView:self];
+    auto avnPoint = [AvnView toAvnPoint:localPoint];
+    auto point = [self translateLocalPoint:avnPoint];
+    auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
+    NSDragOperation nsop = [info draggingSourceOperationMask];
+
+    auto effects = ConvertDragDropEffects(nsop);
+    int reffects = (int)_parent->BaseEvents
+            ->DragEvent(type, point, modifiers, effects,
+                    CreateClipboard([info draggingPasteboard], nil),
+                    GetAvnDataObjectHandleFromDraggingInfo(info));
+
+    NSDragOperation ret = static_cast<NSDragOperation>(0);
+
+    // Ensure that the managed part didn't add any new effects
+    reffects = (int)effects & reffects;
+
+    // OSX requires exactly one operation
+    if((reffects & (int)AvnDragDropEffects::Copy) != 0)
+        ret = NSDragOperationCopy;
+    else if((reffects & (int)AvnDragDropEffects::Move) != 0)
+        ret = NSDragOperationMove;
+    else if((reffects & (int)AvnDragDropEffects::Link) != 0)
+        ret = NSDragOperationLink;
+    if(ret == 0)
+        ret = NSDragOperationNone;
+    return ret;
+}
+
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+    return [self triggerAvnDragEvent: AvnDragEventType::Enter info:sender];
+}
+
+- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+    return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender];
+}
+
+- (void)draggingExited:(id <NSDraggingInfo>)sender
+{
+    [self triggerAvnDragEvent: AvnDragEventType::Leave info:sender];
+}
+
+- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+    return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender] != NSDragOperationNone;
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+    return [self triggerAvnDragEvent: AvnDragEventType::Drop info:sender] != NSDragOperationNone;
+}
+
+- (void)concludeDragOperation:(nullable id <NSDraggingInfo>)sender
+{
+
+}
+
+- (AvnPlatformResizeReason)getResizeReason
+{
+    return _resizeReason;
+}
+
+- (void)setResizeReason:(AvnPlatformResizeReason)reason
+{
+    _resizeReason = reason;
+}
+
+- (AvnAccessibilityElement *) accessibilityChild
+{
+    if (_accessibilityChild == nil)
+    {
+        auto peer = _parent->BaseEvents->GetAutomationPeer();
+
+        if (peer == nil)
+            return nil;
+
+        _accessibilityChild = [AvnAccessibilityElement acquire:peer];
+    }
+
+    return _accessibilityChild;
+}
+
+- (NSArray *)accessibilityChildren
+{
+    auto child = [self accessibilityChild];
+    return NSAccessibilityUnignoredChildrenForOnlyChild(child);
+}
+
+- (id)accessibilityHitTest:(NSPoint)point
+{
+    return [[self accessibilityChild] accessibilityHitTest:point];
+}
+
+- (id)accessibilityFocusedUIElement
+{
+    return [[self accessibilityChild] accessibilityFocusedUIElement];
+}
+
+@end

+ 441 - 0
native/Avalonia.Native/src/OSX/AvnWindow.mm

@@ -0,0 +1,441 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+
+#import <AppKit/AppKit.h>
+#import "WindowProtocol.h"
+#import "WindowBaseImpl.h"
+
+#ifdef IS_NSPANEL
+#define BASE_CLASS NSPanel
+#define CLASS_NAME AvnPanel
+#else
+#define BASE_CLASS NSWindow
+#define CLASS_NAME AvnWindow
+#endif
+
+#import <AppKit/AppKit.h>
+#include "common.h"
+#include "menu.h"
+#include "automation.h"
+#include "WindowBaseImpl.h"
+#include "WindowImpl.h"
+#include "AvnView.h"
+#include "WindowInterfaces.h"
+#include "PopupImpl.h"
+
+@implementation CLASS_NAME
+{
+    ComPtr<WindowBaseImpl> _parent;
+    bool _closed;
+    bool _isEnabled;
+    bool _isExtended;
+    AvnMenu* _menu;
+}
+
+-(void) setIsExtended:(bool)value;
+{
+    _isExtended = value;
+}
+
+-(bool) isDialog
+{
+    return _parent->IsDialog();
+}
+
+-(double) getExtendedTitleBarHeight
+{
+    if(_isExtended)
+    {
+        for (id subview in self.contentView.superview.subviews)
+        {
+            if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")])
+            {
+                NSView *titlebarView = [subview subviews][0];
+
+                return (double)titlebarView.frame.size.height;
+            }
+        }
+
+        return -1;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+- (void)performClose:(id)sender
+{
+    if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
+    {
+        if(![[self delegate] windowShouldClose:self]) return;
+    }
+    else if([self respondsToSelector:@selector(windowShouldClose:)])
+    {
+        if(![self windowShouldClose:self]) return;
+    }
+
+    [self close];
+}
+
+- (void)pollModalSession:(nonnull NSModalSession)session
+{
+    auto response = [NSApp runModalSession:session];
+
+    if(response == NSModalResponseContinue)
+    {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [self pollModalSession:session];
+        });
+    }
+    else if (!_closed)
+    {
+        [self orderOut:self];
+        [NSApp endModalSession:session];
+    }
+}
+
+-(void) showWindowMenuWithAppMenu
+{
+    if(_menu != nullptr)
+    {
+        auto appMenuItem = ::GetAppMenuItem();
+
+        if(appMenuItem != nullptr)
+        {
+            auto appMenu = [appMenuItem menu];
+
+            [appMenu removeItem:appMenuItem];
+
+            [_menu insertItem:appMenuItem atIndex:0];
+
+            [_menu setHasGlobalMenuItem:true];
+        }
+
+        [NSApp setMenu:_menu];
+    }
+    else
+    {
+        [self showAppMenuOnly];
+    }
+}
+
+-(void) showAppMenuOnly
+{
+    auto appMenuItem = ::GetAppMenuItem();
+
+    if(appMenuItem != nullptr)
+    {
+        auto appMenu = ::GetAppMenu();
+
+        auto nativeAppMenu = dynamic_cast<AvnAppMenu*>(appMenu);
+
+        [[appMenuItem menu] removeItem:appMenuItem];
+
+        if(_menu != nullptr)
+        {
+            [_menu setHasGlobalMenuItem:false];
+        }
+
+        [nativeAppMenu->GetNative() addItem:appMenuItem];
+
+        [NSApp setMenu:nativeAppMenu->GetNative()];
+    }
+}
+
+-(void) applyMenu:(AvnMenu *)menu
+{
+    if(menu == nullptr)
+    {
+        menu = [AvnMenu new];
+    }
+
+    _menu = menu;
+}
+
+-(CLASS_NAME*)  initWithParent: (WindowBaseImpl*) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
+{
+    // https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/
+    // create nswindow with specific contentRect, otherwise we wont be able to resize the window
+    // until several ms after the window is physically on the screen.
+    self = [super initWithContentRect:contentRect styleMask: styleMask backing:NSBackingStoreBuffered defer:false];
+
+    [self setReleasedWhenClosed:false];
+    _parent = parent;
+    [self setDelegate:self];
+    _closed = false;
+    _isEnabled = true;
+
+    [self backingScaleFactor];
+    [self setOpaque:NO];
+    [self setBackgroundColor: [NSColor clearColor]];
+
+    _isExtended = false;
+    return self;
+}
+
+- (BOOL)windowShouldClose:(NSWindow *)sender
+{
+    auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
+
+    if(window != nullptr)
+    {
+        return !window->WindowEvents->Closing();
+    }
+
+    return true;
+}
+
+- (void)windowDidChangeBackingProperties:(NSNotification *)notification
+{
+    [self backingScaleFactor];
+}
+
+- (void)windowWillClose:(NSNotification *)notification
+{
+    _closed = true;
+    if(_parent)
+    {
+        ComPtr<WindowBaseImpl> parent = _parent;
+        _parent = NULL;
+        [self restoreParentWindow];
+        parent->BaseEvents->Closed();
+        [parent->View onClosed];
+    }
+}
+
+-(BOOL)canBecomeKeyWindow
+{
+    // If the window has a child window being shown as a dialog then don't allow it to become the key window.
+    for(NSWindow* uch in [self childWindows])
+    {
+        auto ch = static_cast<id <AvnWindowProtocol>>(uch);
+        if(ch == nil)
+            continue;
+        if (ch.isDialog)
+            return false;
+    }
+
+    return true;
+}
+
+-(BOOL)canBecomeMainWindow
+{
+#ifdef IS_NSPANEL
+    return false;
+#else
+    return true;
+#endif
+}
+
+-(bool)shouldTryToHandleEvents
+{
+    return _isEnabled;
+}
+
+-(void) setEnabled:(bool)enable
+{
+    _isEnabled = enable;
+}
+
+-(void)becomeKeyWindow
+{
+    [self showWindowMenuWithAppMenu];
+
+    if(_parent != nullptr)
+    {
+        _parent->BaseEvents->Activated();
+    }
+
+    [super becomeKeyWindow];
+}
+
+-(void) restoreParentWindow;
+{
+    auto parent = [self parentWindow];
+
+    if(parent != nil)
+    {
+        [parent removeChildWindow:self];
+    }
+}
+
+- (void)windowDidMiniaturize:(NSNotification *)notification
+{
+    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
+
+    if(parent != nullptr)
+    {
+        parent->WindowStateChanged();
+    }
+}
+
+- (void)windowDidDeminiaturize:(NSNotification *)notification
+{
+    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
+
+    if(parent != nullptr)
+    {
+        parent->WindowStateChanged();
+    }
+}
+
+- (void)windowDidResize:(NSNotification *)notification
+{
+    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
+
+    if(parent != nullptr)
+    {
+        parent->WindowStateChanged();
+    }
+}
+
+- (void)windowWillExitFullScreen:(NSNotification *)notification
+{
+    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
+
+    if(parent != nullptr)
+    {
+        parent->StartStateTransition();
+    }
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification
+{
+    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
+
+    if(parent != nullptr)
+    {
+        parent->EndStateTransition();
+
+        if(parent->Decorations() != SystemDecorationsFull && parent->WindowState() == Maximized)
+        {
+            NSRect screenRect = [[self screen] visibleFrame];
+            [self setFrame:screenRect display:YES];
+        }
+
+        if(parent->WindowState() == Minimized)
+        {
+            [self miniaturize:nullptr];
+        }
+
+        parent->WindowStateChanged();
+    }
+}
+
+- (void)windowWillEnterFullScreen:(NSNotification *)notification
+{
+    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
+
+    if(parent != nullptr)
+    {
+        parent->StartStateTransition();
+    }
+}
+
+- (void)windowDidEnterFullScreen:(NSNotification *)notification
+{
+    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
+
+    if(parent != nullptr)
+    {
+        parent->EndStateTransition();
+        parent->WindowStateChanged();
+    }
+}
+
+- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
+{
+    return true;
+}
+
+-(void)resignKeyWindow
+{
+    if(_parent)
+        _parent->BaseEvents->Deactivated();
+
+    [self showAppMenuOnly];
+
+    [super resignKeyWindow];
+}
+
+- (void)windowDidMove:(NSNotification *)notification
+{
+    AvnPoint position;
+
+    if(_parent != nullptr)
+    {
+        auto cparent = dynamic_cast<WindowImpl*>(_parent.getRaw());
+
+        if(cparent != nullptr)
+        {
+            if(cparent->WindowState() == Maximized)
+            {
+                cparent->SetWindowState(Normal);
+            }
+        }
+
+        _parent->GetPosition(&position);
+        _parent->BaseEvents->PositionChanged(position);
+    }
+}
+
+- (AvnPoint) translateLocalPoint:(AvnPoint)pt
+{
+    pt.Y = [self frame].size.height - pt.Y;
+    return pt;
+}
+
+- (void)sendEvent:(NSEvent *)event
+{
+    [super sendEvent:event];
+
+    /// This is to detect non-client clicks. This can only be done on Windows... not popups, hence the dynamic_cast.
+    if(_parent != nullptr && dynamic_cast<WindowImpl*>(_parent.getRaw()) != nullptr)
+    {
+        switch(event.type)
+        {
+            case NSEventTypeLeftMouseDown:
+            {
+                AvnView* view = _parent->View;
+                NSPoint windowPoint = [event locationInWindow];
+                NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
+
+                if (!NSPointInRect(viewPoint, view.bounds))
+                {
+                    auto avnPoint = [AvnView toAvnPoint:windowPoint];
+                    auto point = [self translateLocalPoint:avnPoint];
+                    AvnVector delta = { 0, 0 };
+
+                    _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
+                }
+            }
+                break;
+
+            case NSEventTypeMouseEntered:
+            {
+                _parent->UpdateCursor();
+            }
+                break;
+
+            case NSEventTypeMouseExited:
+            {
+                [[NSCursor arrowCursor] set];
+            }
+                break;
+
+            default:
+                break;
+        }
+    }
+}
+
+- (void)disconnectParent {
+    _parent = nullptr;
+}
+
+@end
+

+ 17 - 0
native/Avalonia.Native/src/OSX/INSWindowHolder.h

@@ -0,0 +1,17 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H
+#define AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H
+
+@class AvnView;
+
+struct INSWindowHolder
+{
+    virtual NSWindow* _Nonnull GetNSWindow () = 0;
+    virtual NSView* _Nonnull GetNSView () = 0;
+};
+
+#endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H

+ 18 - 0
native/Avalonia.Native/src/OSX/IWindowStateChanged.h

@@ -0,0 +1,18 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_IWINDOWSTATECHANGED_H
+#define AVALONIA_NATIVE_OSX_IWINDOWSTATECHANGED_H
+
+struct IWindowStateChanged
+{
+    virtual void WindowStateChanged () = 0;
+    virtual void StartStateTransition () = 0;
+    virtual void EndStateTransition () = 0;
+    virtual SystemDecorations Decorations () = 0;
+    virtual AvnWindowState WindowState () = 0;
+};
+
+#endif //AVALONIA_NATIVE_OSX_IWINDOWSTATECHANGED_H

+ 9 - 0
native/Avalonia.Native/src/OSX/PopupImpl.h

@@ -0,0 +1,9 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_POPUPIMPL_H
+#define AVALONIA_NATIVE_OSX_POPUPIMPL_H
+
+#endif //AVALONIA_NATIVE_OSX_POPUPIMPL_H

+ 68 - 0
native/Avalonia.Native/src/OSX/PopupImpl.mm

@@ -0,0 +1,68 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#include "WindowInterfaces.h"
+#include "AvnView.h"
+#include "WindowImpl.h"
+#include "automation.h"
+#include "menu.h"
+#include "common.h"
+#import "WindowBaseImpl.h"
+#import "WindowProtocol.h"
+#import <AppKit/AppKit.h>
+#include "PopupImpl.h"
+
+class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup
+{
+private:
+    BEGIN_INTERFACE_MAP()
+    INHERIT_INTERFACE_MAP(WindowBaseImpl)
+    INTERFACE_MAP_ENTRY(IAvnPopup, IID_IAvnPopup)
+    END_INTERFACE_MAP()
+    virtual ~PopupImpl(){}
+    ComPtr<IAvnWindowEvents> WindowEvents;
+    PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
+    {
+        WindowEvents = events;
+        [Window setLevel:NSPopUpMenuWindowLevel];
+    }
+protected:
+    virtual NSWindowStyleMask GetStyle() override
+    {
+        return NSWindowStyleMaskBorderless;
+    }
+
+    virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
+    {
+        START_COM_CALL;
+
+        @autoreleasepool
+        {
+            if (Window != nullptr)
+            {
+                [Window setContentSize:NSSize{x, y}];
+
+                [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))];
+            }
+
+            return S_OK;
+        }
+    }
+public:
+    virtual bool ShouldTakeFocusOnShow() override
+    {
+        return false;
+    }
+};
+
+
+extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
+{
+    @autoreleasepool
+    {
+        IAvnPopup* ptr = dynamic_cast<IAvnPopup*>(new PopupImpl(events, gl));
+        return ptr;
+    }
+}

+ 24 - 0
native/Avalonia.Native/src/OSX/ResizeScope.h

@@ -0,0 +1,24 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_RESIZESCOPE_H
+#define AVALONIA_NATIVE_OSX_RESIZESCOPE_H
+
+#include "avalonia-native.h"
+
+@class AvnView;
+
+class ResizeScope
+{
+public:
+    ResizeScope(AvnView* _Nonnull view, AvnPlatformResizeReason reason);
+
+    ~ResizeScope();
+private:
+    AvnView* _Nonnull _view;
+    AvnPlatformResizeReason _restore;
+};
+
+#endif //AVALONIA_NATIVE_OSX_RESIZESCOPE_H

+ 18 - 0
native/Avalonia.Native/src/OSX/ResizeScope.mm

@@ -0,0 +1,18 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#include "ResizeScope.h"
+#include "AvnView.h"
+
+ResizeScope::ResizeScope(AvnView *view, AvnPlatformResizeReason reason) {
+    _view = view;
+    _restore = [view getResizeReason];
+    [view setResizeReason:reason];
+}
+
+ResizeScope::~ResizeScope() {
+    [_view setResizeReason:_restore];
+}

+ 1 - 1
native/Avalonia.Native/src/OSX/SystemDialogs.mm

@@ -1,5 +1,5 @@
 #include "common.h"
-#include "window.h"
+#include "INSWindowHolder.h"
 
 class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs>
 {

+ 130 - 0
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@@ -0,0 +1,130 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H
+#define AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H
+
+#include "rendertarget.h"
+#include "INSWindowHolder.h"
+
+@class AutoFitContentView;
+@class AvnMenu;
+@protocol AvnWindowProtocol;
+
+class WindowBaseImpl : public virtual ComObject,
+                       public virtual IAvnWindowBase,
+                       public INSWindowHolder {
+private:
+    NSCursor *cursor;
+
+public:
+    FORWARD_IUNKNOWN()
+
+BEGIN_INTERFACE_MAP()
+        INTERFACE_MAP_ENTRY(IAvnWindowBase, IID_IAvnWindowBase)
+    END_INTERFACE_MAP()
+
+    virtual ~WindowBaseImpl();
+
+    AutoFitContentView *StandardContainer;
+    AvnView *View;
+    NSWindow * Window;
+    ComPtr<IAvnWindowBaseEvents> BaseEvents;
+    ComPtr<IAvnGlContext> _glContext;
+    NSObject <IRenderTarget> *renderTarget;
+    AvnPoint lastPositionSet;
+    NSSize lastSize;
+    NSSize lastMinSize;
+    NSSize lastMaxSize;
+    AvnMenu* lastMenu;
+    NSString *_lastTitle;
+
+    bool _shown;
+    bool _inResize;
+
+    WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl);
+
+    virtual HRESULT ObtainNSWindowHandle(void **ret) override;
+
+    virtual HRESULT ObtainNSWindowHandleRetained(void **ret) override;
+
+    virtual HRESULT ObtainNSViewHandle(void **ret) override;
+
+    virtual HRESULT ObtainNSViewHandleRetained(void **ret) override;
+
+    virtual NSWindow *GetNSWindow() override;
+
+    virtual NSView *GetNSView() override;
+
+    virtual HRESULT Show(bool activate, bool isDialog) override;
+
+    virtual bool ShouldTakeFocusOnShow();
+
+    virtual HRESULT Hide() override;
+
+    virtual HRESULT Activate() override;
+
+    virtual HRESULT SetTopMost(bool value) override;
+
+    virtual HRESULT Close() override;
+
+    virtual HRESULT GetClientSize(AvnSize *ret) override;
+
+    virtual HRESULT GetFrameSize(AvnSize *ret) override;
+
+    virtual HRESULT GetScaling(double *ret) override;
+
+    virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) override;
+
+    virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override;
+
+    virtual HRESULT Invalidate(__attribute__((unused)) AvnRect rect) override;
+
+    virtual HRESULT SetMainMenu(IAvnMenu *menu) override;
+
+    virtual HRESULT BeginMoveDrag() override;
+
+    virtual HRESULT BeginResizeDrag(__attribute__((unused)) AvnWindowEdge edge) override;
+
+    virtual HRESULT GetPosition(AvnPoint *ret) override;
+
+    virtual HRESULT SetPosition(AvnPoint point) override;
+
+    virtual HRESULT PointToClient(AvnPoint point, AvnPoint *ret) override;
+
+    virtual HRESULT PointToScreen(AvnPoint point, AvnPoint *ret) override;
+
+    virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) override;
+
+    virtual HRESULT SetCursor(IAvnCursor *cursor) override;
+
+    virtual void UpdateCursor();
+
+    virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) override;
+
+    virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost **retOut) override;
+
+    virtual HRESULT SetBlurEnabled(bool enable) override;
+
+    virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
+            IAvnClipboard *clipboard, IAvnDndResultCallback *cb,
+            void *sourceHandle) override;
+
+    virtual bool IsDialog();
+
+    id<AvnWindowProtocol> GetWindowProtocol ();
+
+protected:
+    virtual NSWindowStyleMask GetStyle();
+
+    void UpdateStyle();
+
+private:
+    void CreateNSWindow (bool isDialog);
+    void CleanNSWindow ();
+    void InitialiseNSWindow ();
+};
+
+#endif //AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H

+ 589 - 0
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@@ -0,0 +1,589 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#include "common.h"
+#include "AvnView.h"
+#include "menu.h"
+#include "automation.h"
+#include "cursor.h"
+#include "ResizeScope.h"
+#include "AutoFitContentView.h"
+#import "WindowProtocol.h"
+#import "WindowInterfaces.h"
+#include "WindowBaseImpl.h"
+
+
+WindowBaseImpl::~WindowBaseImpl() {
+    View = nullptr;
+    Window = nullptr;
+}
+
+WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) {
+    _shown = false;
+    _inResize = false;
+    BaseEvents = events;
+    _glContext = gl;
+    renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext:gl];
+    View = [[AvnView alloc] initWithParent:this];
+    StandardContainer = [[AutoFitContentView new] initWithContent:View];
+
+    lastPositionSet.X = 100;
+    lastPositionSet.Y = 100;
+    lastSize = NSSize { 100, 100 };
+    lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
+    lastMinSize = NSSize { 0, 0 };
+    _lastTitle = @"";
+
+    Window = nullptr;
+    lastMenu = nullptr;
+}
+
+HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) {
+    START_COM_CALL;
+
+    if (ret == nullptr) {
+        return E_POINTER;
+    }
+
+    *ret = (__bridge void *) View;
+
+    return S_OK;
+}
+
+HRESULT WindowBaseImpl::ObtainNSViewHandleRetained(void **ret) {
+    START_COM_CALL;
+
+    if (ret == nullptr) {
+        return E_POINTER;
+    }
+
+    *ret = (__bridge_retained void *) View;
+
+    return S_OK;
+}
+
+NSWindow *WindowBaseImpl::GetNSWindow() {
+    return Window;
+}
+
+NSView *WindowBaseImpl::GetNSView() {
+    return View;
+}
+
+HRESULT WindowBaseImpl::ObtainNSWindowHandleRetained(void **ret) {
+    START_COM_CALL;
+
+    if (ret == nullptr) {
+        return E_POINTER;
+    }
+
+    *ret = (__bridge_retained void *) Window;
+
+    return S_OK;
+}
+
+HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        CreateNSWindow(isDialog);
+        InitialiseNSWindow();
+
+        SetPosition(lastPositionSet);
+        UpdateStyle();
+
+        [Window setTitle:_lastTitle];
+
+        if (ShouldTakeFocusOnShow() && activate) {
+            [Window orderFront:Window];
+            [Window makeKeyAndOrderFront:Window];
+            [Window makeFirstResponder:View];
+            [NSApp activateIgnoringOtherApps:YES];
+        } else {
+            [Window orderFront:Window];
+        }
+
+        _shown = true;
+
+        return S_OK;
+    }
+}
+
+bool WindowBaseImpl::ShouldTakeFocusOnShow() {
+    return true;
+}
+
+HRESULT WindowBaseImpl::ObtainNSWindowHandle(void **ret) {
+    START_COM_CALL;
+
+    if (ret == nullptr) {
+        return E_POINTER;
+    }
+
+    *ret = (__bridge void *) Window;
+
+    return S_OK;
+}
+
+HRESULT WindowBaseImpl::Hide() {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (Window != nullptr) {
+            [Window orderOut:Window];
+
+            [GetWindowProtocol() restoreParentWindow];
+        }
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::Activate() {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (Window != nullptr) {
+            [Window makeKeyAndOrderFront:nil];
+            [NSApp activateIgnoringOtherApps:YES];
+        }
+    }
+
+    return S_OK;
+}
+
+HRESULT WindowBaseImpl::SetTopMost(bool value) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        [Window setLevel:value ? NSFloatingWindowLevel : NSNormalWindowLevel];
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::Close() {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (Window != nullptr) {
+            auto window = Window;
+            Window = nullptr;
+
+            try {
+                // Seems to throw sometimes on application exit.
+                [window close];
+            }
+            catch (NSException *) {}
+        }
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::GetClientSize(AvnSize *ret) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (ret == nullptr)
+            return E_POINTER;
+
+        auto frame = [View frame];
+        ret->Width = frame.size.width;
+        ret->Height = frame.size.height;
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::GetFrameSize(AvnSize *ret) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (ret == nullptr)
+            return E_POINTER;
+
+        auto frame = [Window frame];
+        ret->Width = frame.size.width;
+        ret->Height = frame.size.height;
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::GetScaling(double *ret) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (ret == nullptr)
+            return E_POINTER;
+
+        if (Window == nullptr) {
+            *ret = 1;
+            return S_OK;
+        }
+
+        *ret = [Window backingScaleFactor];
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::SetMinMaxSize(AvnSize minSize, AvnSize maxSize) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        lastMinSize = ToNSSize(minSize);
+        lastMaxSize = ToNSSize(maxSize);
+
+        if(Window != nullptr) {
+            [Window setContentMinSize:lastMinSize];
+            [Window setContentMaxSize:lastMaxSize];
+        }
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reason) {
+    if (_inResize) {
+        return S_OK;
+    }
+
+    _inResize = true;
+
+    START_COM_CALL;
+    auto resizeBlock = ResizeScope(View, reason);
+
+    @autoreleasepool {
+        auto maxSize = lastMaxSize;
+        auto minSize = lastMinSize;
+
+        if (x < minSize.width) {
+            x = minSize.width;
+        }
+
+        if (y < minSize.height) {
+            y = minSize.height;
+        }
+
+        if (x > maxSize.width) {
+            x = maxSize.width;
+        }
+
+        if (y > maxSize.height) {
+            y = maxSize.height;
+        }
+
+        @try {
+            if (!_shown) {
+                BaseEvents->Resized(AvnSize{x, y}, reason);
+            }
+
+            lastSize = NSSize {x, y};
+
+            if(Window != nullptr) {
+                [Window setContentSize:lastSize];
+            }
+        }
+        @finally {
+            _inResize = false;
+        }
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::Invalidate(__attribute__((unused)) AvnRect rect) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        [View setNeedsDisplayInRect:[View frame]];
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::SetMainMenu(IAvnMenu *menu) {
+    START_COM_CALL;
+
+    auto nativeMenu = dynamic_cast<AvnAppMenu *>(menu);
+
+    lastMenu = nativeMenu->GetNative();
+
+    if(Window != nullptr) {
+        [GetWindowProtocol() applyMenu:lastMenu];
+
+        if ([Window isKeyWindow]) {
+            [GetWindowProtocol() showWindowMenuWithAppMenu];
+        }
+    }
+
+    return S_OK;
+}
+
+HRESULT WindowBaseImpl::BeginMoveDrag() {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        auto lastEvent = [View lastMouseDownEvent];
+
+        if (lastEvent == nullptr) {
+            return S_OK;
+        }
+
+        [Window performWindowDragWithEvent:lastEvent];
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::BeginResizeDrag(__attribute__((unused)) AvnWindowEdge edge) {
+    START_COM_CALL;
+
+    return S_OK;
+}
+
+HRESULT WindowBaseImpl::GetPosition(AvnPoint *ret) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (ret == nullptr) {
+            return E_POINTER;
+        }
+
+        auto frame = [Window frame];
+
+        ret->X = frame.origin.x;
+        ret->Y = frame.origin.y + frame.size.height;
+
+        *ret = ConvertPointY(*ret);
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::SetPosition(AvnPoint point) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        lastPositionSet = point;
+        [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))];
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::PointToClient(AvnPoint point, AvnPoint *ret) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (ret == nullptr) {
+            return E_POINTER;
+        }
+
+        point = ConvertPointY(point);
+        NSRect convertRect = [Window convertRectFromScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
+        auto viewPoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);
+
+        *ret = [View translateLocalPoint:ToAvnPoint(viewPoint)];
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::PointToScreen(AvnPoint point, AvnPoint *ret) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (ret == nullptr) {
+            return E_POINTER;
+        }
+
+        auto cocoaViewPoint = ToNSPoint([View translateLocalPoint:point]);
+        NSRect convertRect = [Window convertRectToScreen:NSMakeRect(cocoaViewPoint.x, cocoaViewPoint.y, 0.0, 0.0)];
+        auto cocoaScreenPoint = NSPointFromCGPoint(NSMakePoint(convertRect.origin.x, convertRect.origin.y));
+        *ret = ConvertPointY(ToAvnPoint(cocoaScreenPoint));
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowBaseImpl::ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) {
+    START_COM_CALL;
+
+    [View setSwRenderedFrame:fb dispose:dispose];
+    return S_OK;
+}
+
+HRESULT WindowBaseImpl::SetCursor(IAvnCursor *cursor) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        Cursor *avnCursor = dynamic_cast<Cursor *>(cursor);
+        this->cursor = avnCursor->GetNative();
+        UpdateCursor();
+
+        if (avnCursor->IsHidden()) {
+            [NSCursor hide];
+        } else {
+            [NSCursor unhide];
+        }
+
+        return S_OK;
+    }
+}
+
+void WindowBaseImpl::UpdateCursor() {
+    if (cursor != nil) {
+        [cursor set];
+    }
+}
+
+HRESULT WindowBaseImpl::CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) {
+    START_COM_CALL;
+
+    if (View == NULL)
+        return E_FAIL;
+    *ppv = [renderTarget createSurfaceRenderTarget];
+    return static_cast<HRESULT>(*ppv == nil ? E_FAIL : S_OK);
+}
+
+HRESULT WindowBaseImpl::CreateNativeControlHost(IAvnNativeControlHost **retOut) {
+    START_COM_CALL;
+
+    if (View == NULL)
+        return E_FAIL;
+    *retOut = ::CreateNativeControlHost(View);
+    return S_OK;
+}
+
+HRESULT WindowBaseImpl::SetBlurEnabled(bool enable) {
+    START_COM_CALL;
+
+    [StandardContainer ShowBlur:enable];
+
+    return S_OK;
+}
+
+HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) {
+    START_COM_CALL;
+
+    auto item = TryGetPasteboardItem(clipboard);
+    [item setString:@"" forType:GetAvnCustomDataType()];
+    if (item == nil)
+        return E_INVALIDARG;
+    if (View == NULL)
+        return E_FAIL;
+
+    auto nsevent = [NSApp currentEvent];
+    auto nseventType = [nsevent type];
+
+    // If current event isn't a mouse one (probably due to malfunctioning user app)
+    // attempt to forge a new one
+    if (!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited)
+            || (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged))) {
+        NSRect convertRect = [Window convertRectToScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
+        auto nspoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);
+        CGPoint cgpoint = NSPointToCGPoint(nspoint);
+        auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft);
+        nsevent = [NSEvent eventWithCGEvent:cgevent];
+        CFRelease(cgevent);
+    }
+
+    auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:item];
+
+    auto dragItemImage = [NSImage imageNamed:NSImageNameMultipleDocuments];
+    NSRect dragItemRect = {(float) point.X, (float) point.Y, [dragItemImage size].width, [dragItemImage size].height};
+    [dragItem setDraggingFrame:dragItemRect contents:dragItemImage];
+
+    int op = 0;
+    int ieffects = (int) effects;
+    if ((ieffects & (int) AvnDragDropEffects::Copy) != 0)
+        op |= NSDragOperationCopy;
+    if ((ieffects & (int) AvnDragDropEffects::Link) != 0)
+        op |= NSDragOperationLink;
+    if ((ieffects & (int) AvnDragDropEffects::Move) != 0)
+        op |= NSDragOperationMove;
+    [View beginDraggingSessionWithItems:@[dragItem] event:nsevent
+                                 source:CreateDraggingSource((NSDragOperation) op, cb, sourceHandle)];
+    return S_OK;
+}
+
+bool WindowBaseImpl::IsDialog() {
+    return false;
+}
+
+NSWindowStyleMask WindowBaseImpl::GetStyle() {
+    return NSWindowStyleMaskBorderless;
+}
+
+void WindowBaseImpl::UpdateStyle() {
+    [Window setStyleMask:GetStyle()];
+}
+
+void WindowBaseImpl::CleanNSWindow() {
+    if(Window != nullptr) {
+        [GetWindowProtocol() disconnectParent];
+        [Window close];
+        Window = nullptr;
+    }
+}
+
+void WindowBaseImpl::CreateNSWindow(bool isDialog) {
+    if (isDialog) {
+        if (![Window isKindOfClass:[AvnPanel class]]) {
+            CleanNSWindow();
+
+            Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
+        }
+    } else {
+        if (![Window isKindOfClass:[AvnWindow class]]) {
+            CleanNSWindow();
+
+            Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
+        }
+    }
+}
+
+void WindowBaseImpl::InitialiseNSWindow() {
+    if(Window != nullptr) {
+        [Window setContentView:StandardContainer];
+        [Window setStyleMask:NSWindowStyleMaskBorderless];
+        [Window setBackingType:NSBackingStoreBuffered];
+
+        [Window setContentSize:lastSize];
+        [Window setContentMinSize:lastMinSize];
+        [Window setContentMaxSize:lastMaxSize];
+
+        [Window setOpaque:false];
+
+        if (lastMenu != nullptr) {
+            [GetWindowProtocol() applyMenu:lastMenu];
+
+            if ([Window isKeyWindow]) {
+                [GetWindowProtocol() showWindowMenuWithAppMenu];
+            }
+        }
+    }
+}
+
+id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
+    if(Window == nullptr)
+    {
+        return nullptr;
+    }
+
+    return static_cast<id <AvnWindowProtocol>>(Window);
+}
+
+extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
+{
+    @autoreleasepool
+    {
+        IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl);
+        return ptr;
+    }
+}

+ 96 - 0
native/Avalonia.Native/src/OSX/WindowImpl.h

@@ -0,0 +1,96 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_WINDOWIMPL_H
+#define AVALONIA_NATIVE_OSX_WINDOWIMPL_H
+
+#import "WindowBaseImpl.h"
+#include "IWindowStateChanged.h"
+
+class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
+{
+private:
+    bool _canResize;
+    bool _fullScreenActive;
+    SystemDecorations _decorations;
+    AvnWindowState _lastWindowState;
+    AvnWindowState _actualWindowState;
+    bool _inSetWindowState;
+    NSRect _preZoomSize;
+    bool _transitioningWindowState;
+    bool _isClientAreaExtended;
+    bool _isDialog;
+    AvnExtendClientAreaChromeHints _extendClientHints;
+
+    FORWARD_IUNKNOWN()
+BEGIN_INTERFACE_MAP()
+        INHERIT_INTERFACE_MAP(WindowBaseImpl)
+        INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow)
+    END_INTERFACE_MAP()
+    virtual ~WindowImpl()
+    {
+    }
+
+    ComPtr<IAvnWindowEvents> WindowEvents;
+
+    WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
+
+    void HideOrShowTrafficLights ();
+
+    virtual HRESULT Show (bool activate, bool isDialog) override;
+
+    virtual HRESULT SetEnabled (bool enable) override;
+
+    virtual HRESULT SetParent (IAvnWindow* parent) override;
+
+    void StartStateTransition () override ;
+
+    void EndStateTransition () override ;
+
+    SystemDecorations Decorations () override ;
+
+    AvnWindowState WindowState () override ;
+
+    void WindowStateChanged () override ;
+
+    bool UndecoratedIsMaximized ();
+
+    bool IsZoomed ();
+
+    void DoZoom();
+
+    virtual HRESULT SetCanResize(bool value) override;
+
+    virtual HRESULT SetDecorations(SystemDecorations value) override;
+
+    virtual HRESULT SetTitle (char* utf8title) override;
+
+    virtual HRESULT SetTitleBarColor(AvnColor color) override;
+
+    virtual HRESULT GetWindowState (AvnWindowState*ret) override;
+
+    virtual HRESULT TakeFocusFromChildren () override;
+
+    virtual HRESULT SetExtendClientArea (bool enable) override;
+
+    virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override;
+
+    virtual HRESULT GetExtendTitleBarHeight (double*ret) override;
+
+    virtual HRESULT SetExtendTitleBarHeight (double value) override;
+
+    void EnterFullScreenMode ();
+
+    void ExitFullScreenMode ();
+
+    virtual HRESULT SetWindowState (AvnWindowState state) override;
+
+    virtual bool IsDialog() override;
+
+protected:
+    virtual NSWindowStyleMask GetStyle() override;
+};
+
+#endif //AVALONIA_NATIVE_OSX_WINDOWIMPL_H

+ 552 - 0
native/Avalonia.Native/src/OSX/WindowImpl.mm

@@ -0,0 +1,552 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import <AppKit/AppKit.h>
+#include "AutoFitContentView.h"
+#include "AvnView.h"
+#include "automation.h"
+#include "WindowProtocol.h"
+
+WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
+    _isClientAreaExtended = false;
+    _extendClientHints = AvnDefaultChrome;
+    _fullScreenActive = false;
+    _canResize = true;
+    _decorations = SystemDecorationsFull;
+    _transitioningWindowState = false;
+    _inSetWindowState = false;
+    _lastWindowState = Normal;
+    _actualWindowState = Normal;
+    WindowEvents = events;
+    [Window disableCursorRects];
+    [Window setTabbingMode:NSWindowTabbingModeDisallowed];
+    [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+}
+
+void WindowImpl::HideOrShowTrafficLights() {
+    if (Window == nil) {
+        return;
+    }
+
+    for (id subview in Window.contentView.superview.subviews) {
+        if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
+            NSView *titlebarView = [subview subviews][0];
+            for (id button in titlebarView.subviews) {
+                if ([button isKindOfClass:[NSButton class]]) {
+                    if (_isClientAreaExtended) {
+                        auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+
+                        [button setHidden:!wantsChrome];
+                    } else {
+                        [button setHidden:(_decorations != SystemDecorationsFull)];
+                    }
+
+                    [button setWantsLayer:true];
+                }
+            }
+        }
+    }
+}
+
+HRESULT WindowImpl::Show(bool activate, bool isDialog) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        _isDialog = isDialog;
+
+        bool created = Window == nullptr;
+
+        WindowBaseImpl::Show(activate, isDialog);
+
+        if(created)
+        {
+            if(_isClientAreaExtended)
+            {
+                [GetWindowProtocol() setIsExtended:true];
+                SetExtendClientArea(true);
+            }
+        }
+
+        HideOrShowTrafficLights();
+
+        return SetWindowState(_lastWindowState);
+    }
+}
+
+HRESULT WindowImpl::SetEnabled(bool enable) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        [GetWindowProtocol() setEnabled:enable];
+        return S_OK;
+    }
+}
+
+HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (parent == nullptr)
+            return E_POINTER;
+
+        auto cparent = dynamic_cast<WindowImpl *>(parent);
+        if (cparent == nullptr)
+            return E_INVALIDARG;
+
+        // If one tries to show a child window with a minimized parent window, then the parent window will be
+        // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
+        // state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
+        if (cparent->WindowState() == Minimized)
+            cparent->SetWindowState(Normal);
+
+        [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
+        [cparent->Window addChildWindow:Window ordered:NSWindowAbove];
+
+        UpdateStyle();
+
+        return S_OK;
+    }
+}
+
+void WindowImpl::StartStateTransition() {
+    _transitioningWindowState = true;
+}
+
+void WindowImpl::EndStateTransition() {
+    _transitioningWindowState = false;
+}
+
+SystemDecorations WindowImpl::Decorations() {
+    return _decorations;
+}
+
+AvnWindowState WindowImpl::WindowState() {
+    return _lastWindowState;
+}
+
+void WindowImpl::WindowStateChanged() {
+    if (_shown && !_inSetWindowState && !_transitioningWindowState) {
+        AvnWindowState state;
+        GetWindowState(&state);
+
+        if (_lastWindowState != state) {
+            if (_isClientAreaExtended) {
+                if (_lastWindowState == FullScreen) {
+                    // we exited fs.
+                    if (_extendClientHints & AvnOSXThickTitleBar) {
+                        Window.toolbar = [NSToolbar new];
+                        Window.toolbar.showsBaselineSeparator = false;
+                    }
+
+                    [Window setTitlebarAppearsTransparent:true];
+
+                    [StandardContainer setFrameSize:StandardContainer.frame.size];
+                } else if (state == FullScreen) {
+                    // we entered fs.
+                    if (_extendClientHints & AvnOSXThickTitleBar) {
+                        Window.toolbar = nullptr;
+                    }
+
+                    [Window setTitlebarAppearsTransparent:false];
+
+                    [StandardContainer setFrameSize:StandardContainer.frame.size];
+                }
+            }
+
+            _lastWindowState = state;
+            _actualWindowState = state;
+            WindowEvents->WindowStateChanged(state);
+        }
+    }
+}
+
+bool WindowImpl::UndecoratedIsMaximized() {
+    auto windowSize = [Window frame];
+    auto available = [Window screen].visibleFrame;
+    return CGRectEqualToRect(windowSize, available);
+}
+
+bool WindowImpl::IsZoomed() {
+    return _decorations == SystemDecorationsFull ? [Window isZoomed] : UndecoratedIsMaximized();
+}
+
+void WindowImpl::DoZoom() {
+    switch (_decorations) {
+        case SystemDecorationsNone:
+        case SystemDecorationsBorderOnly:
+            [Window setFrame:[Window screen].visibleFrame display:true];
+            break;
+
+
+        case SystemDecorationsFull:
+            [Window performZoom:Window];
+            break;
+    }
+}
+
+HRESULT WindowImpl::SetCanResize(bool value) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        _canResize = value;
+        UpdateStyle();
+        return S_OK;
+    }
+}
+
+HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        auto currentWindowState = _lastWindowState;
+        _decorations = value;
+
+        if (_fullScreenActive) {
+            return S_OK;
+        }
+
+        UpdateStyle();
+
+        HideOrShowTrafficLights();
+
+        switch (_decorations) {
+            case SystemDecorationsNone:
+                [Window setHasShadow:NO];
+                [Window setTitleVisibility:NSWindowTitleHidden];
+                [Window setTitlebarAppearsTransparent:YES];
+
+                if (currentWindowState == Maximized) {
+                    if (!UndecoratedIsMaximized()) {
+                        DoZoom();
+                    }
+                }
+                break;
+
+            case SystemDecorationsBorderOnly:
+                [Window setHasShadow:YES];
+                [Window setTitleVisibility:NSWindowTitleHidden];
+                [Window setTitlebarAppearsTransparent:YES];
+
+                if (currentWindowState == Maximized) {
+                    if (!UndecoratedIsMaximized()) {
+                        DoZoom();
+                    }
+                }
+                break;
+
+            case SystemDecorationsFull:
+                [Window setHasShadow:YES];
+                [Window setTitleVisibility:NSWindowTitleVisible];
+                [Window setTitlebarAppearsTransparent:NO];
+                [Window setTitle:_lastTitle];
+
+                if (currentWindowState == Maximized) {
+                    auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
+
+                    [View setFrameSize:newFrame];
+                }
+                break;
+        }
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowImpl::SetTitle(char *utf8title) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        _lastTitle = [NSString stringWithUTF8String:(const char *) utf8title];
+        [Window setTitle:_lastTitle];
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowImpl::SetTitleBarColor(AvnColor color) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        float a = (float) color.Alpha / 255.0f;
+        float r = (float) color.Red / 255.0f;
+        float g = (float) color.Green / 255.0f;
+        float b = (float) color.Blue / 255.0f;
+
+        auto nscolor = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a];
+
+        // Based on the titlebar color we have to choose either light or dark
+        // OSX doesnt let you set a foreground color for titlebar.
+        if ((r * 0.299 + g * 0.587 + b * 0.114) > 186.0f / 255.0f) {
+            [Window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
+        } else {
+            [Window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
+        }
+
+        [Window setTitlebarAppearsTransparent:true];
+        [Window setBackgroundColor:nscolor];
+    }
+
+    return S_OK;
+}
+
+HRESULT WindowImpl::GetWindowState(AvnWindowState *ret) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (ret == nullptr) {
+            return E_POINTER;
+        }
+
+        if (([Window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen) {
+            *ret = FullScreen;
+            return S_OK;
+        }
+
+        if ([Window isMiniaturized]) {
+            *ret = Minimized;
+            return S_OK;
+        }
+
+        if (IsZoomed()) {
+            *ret = Maximized;
+            return S_OK;
+        }
+
+        *ret = Normal;
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowImpl::TakeFocusFromChildren() {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (Window == nil)
+            return S_OK;
+        if ([Window isKeyWindow])
+            [Window makeFirstResponder:View];
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowImpl::SetExtendClientArea(bool enable) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        _isClientAreaExtended = enable;
+
+        if(Window != nullptr) {
+            if (enable) {
+                Window.titleVisibility = NSWindowTitleHidden;
+
+                [Window setTitlebarAppearsTransparent:true];
+
+                auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+
+                if (wantsTitleBar) {
+                    [StandardContainer ShowTitleBar:true];
+                } else {
+                    [StandardContainer ShowTitleBar:false];
+                }
+
+                if (_extendClientHints & AvnOSXThickTitleBar) {
+                    Window.toolbar = [NSToolbar new];
+                    Window.toolbar.showsBaselineSeparator = false;
+                } else {
+                    Window.toolbar = nullptr;
+                }
+            } else {
+                Window.titleVisibility = NSWindowTitleVisible;
+                Window.toolbar = nullptr;
+                [Window setTitlebarAppearsTransparent:false];
+                View.layer.zPosition = 0;
+            }
+
+            [GetWindowProtocol() setIsExtended:enable];
+
+            HideOrShowTrafficLights();
+
+            UpdateStyle();
+        }
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowImpl::SetExtendClientAreaHints(AvnExtendClientAreaChromeHints hints) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        _extendClientHints = hints;
+
+        SetExtendClientArea(_isClientAreaExtended);
+        return S_OK;
+    }
+}
+
+HRESULT WindowImpl::GetExtendTitleBarHeight(double *ret) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (ret == nullptr) {
+            return E_POINTER;
+        }
+
+        *ret = [GetWindowProtocol() getExtendedTitleBarHeight];
+
+        return S_OK;
+    }
+}
+
+HRESULT WindowImpl::SetExtendTitleBarHeight(double value) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        [StandardContainer SetTitleBarHeightHint:value];
+        return S_OK;
+    }
+}
+
+void WindowImpl::EnterFullScreenMode() {
+    _fullScreenActive = true;
+
+    [Window setTitle:_lastTitle];
+    [Window toggleFullScreen:nullptr];
+}
+
+void WindowImpl::ExitFullScreenMode() {
+    [Window toggleFullScreen:nullptr];
+
+    _fullScreenActive = false;
+
+    SetDecorations(_decorations);
+}
+
+HRESULT WindowImpl::SetWindowState(AvnWindowState state) {
+    START_COM_CALL;
+
+    @autoreleasepool {
+        if (Window == nullptr) {
+            return S_OK;
+        }
+
+        if (_actualWindowState == state) {
+            return S_OK;
+        }
+
+        _inSetWindowState = true;
+
+        auto currentState = _actualWindowState;
+        _lastWindowState = state;
+
+        if (currentState == Normal) {
+            _preZoomSize = [Window frame];
+        }
+
+        if (_shown) {
+            switch (state) {
+                case Maximized:
+                    if (currentState == FullScreen) {
+                        ExitFullScreenMode();
+                    }
+
+                    lastPositionSet.X = 0;
+                    lastPositionSet.Y = 0;
+
+                    if ([Window isMiniaturized]) {
+                        [Window deminiaturize:Window];
+                    }
+
+                    if (!IsZoomed()) {
+                        DoZoom();
+                    }
+                    break;
+
+                case Minimized:
+                    if (currentState == FullScreen) {
+                        ExitFullScreenMode();
+                    } else {
+                        [Window miniaturize:Window];
+                    }
+                    break;
+
+                case FullScreen:
+                    if ([Window isMiniaturized]) {
+                        [Window deminiaturize:Window];
+                    }
+
+                    EnterFullScreenMode();
+                    break;
+
+                case Normal:
+                    if ([Window isMiniaturized]) {
+                        [Window deminiaturize:Window];
+                    }
+
+                    if (currentState == FullScreen) {
+                        ExitFullScreenMode();
+                    }
+
+                    if (IsZoomed()) {
+                        if (_decorations == SystemDecorationsFull) {
+                            DoZoom();
+                        } else {
+                            [Window setFrame:_preZoomSize display:true];
+                            auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
+
+                            [View setFrameSize:newFrame];
+                        }
+
+                    }
+                    break;
+            }
+
+            _actualWindowState = _lastWindowState;
+            WindowEvents->WindowStateChanged(_actualWindowState);
+        }
+
+
+        _inSetWindowState = false;
+
+        return S_OK;
+    }
+}
+
+bool WindowImpl::IsDialog() {
+    return _isDialog;
+}
+
+NSWindowStyleMask WindowImpl::GetStyle() {
+    unsigned long s = this->_isDialog ? NSWindowStyleMaskUtilityWindow : NSWindowStyleMaskBorderless;
+
+    switch (_decorations) {
+        case SystemDecorationsNone:
+            s = s | NSWindowStyleMaskFullSizeContentView;
+            break;
+
+        case SystemDecorationsBorderOnly:
+            s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView;
+            break;
+
+        case SystemDecorationsFull:
+            s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless;
+
+            if (_canResize) {
+                s = s | NSWindowStyleMaskResizable;
+            }
+            break;
+    }
+
+    if ([Window parentWindow] == nullptr) {
+        s |= NSWindowStyleMaskMiniaturizable;
+    }
+
+    if (_isClientAreaExtended) {
+        s |= NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskTexturedBackground;
+    }
+    return s;
+}

+ 17 - 0
native/Avalonia.Native/src/OSX/WindowInterfaces.h

@@ -0,0 +1,17 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <AppKit/AppKit.h>
+#include "WindowProtocol.h"
+#include "WindowBaseImpl.h"
+
+@interface AvnWindow : NSWindow <AvnWindowProtocol, NSWindowDelegate>
+-(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
+@end
+
+@interface AvnPanel : NSPanel <AvnWindowProtocol, NSWindowDelegate>
+-(AvnPanel* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
+@end

+ 25 - 0
native/Avalonia.Native/src/OSX/WindowProtocol.h

@@ -0,0 +1,25 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#pragma once
+
+#import <AppKit/AppKit.h>
+
+@class AvnMenu;
+
+@protocol AvnWindowProtocol
+-(void) pollModalSession: (NSModalSession _Nonnull) session;
+-(void) restoreParentWindow;
+-(bool) shouldTryToHandleEvents;
+-(void) setEnabled: (bool) enable;
+-(void) showAppMenuOnly;
+-(void) showWindowMenuWithAppMenu;
+-(void) applyMenu:(AvnMenu* _Nullable)menu;
+
+-(double) getExtendedTitleBarHeight;
+-(void) setIsExtended:(bool)value;
+-(void) disconnectParent;
+-(bool) isDialog;
+@end

+ 0 - 12
native/Avalonia.Native/src/OSX/app.mm

@@ -82,18 +82,6 @@ ComPtr<IAvnApplicationEvents> _events;
         _isHandlingSendEvent = oldHandling;
     }
 }
-
-// This is needed for certain embedded controls
-- (BOOL) isHandlingSendEvent
-{
-    return _isHandlingSendEvent;
-}
-
-- (void)setHandlingSendEvent:(BOOL)handlingSendEvent
-{
-    _isHandlingSendEvent = handlingSendEvent;
-}
-
 @end
 
 extern void InitializeAvnApp(IAvnApplicationEvents* events)

+ 2 - 2
native/Avalonia.Native/src/OSX/automation.h

@@ -1,6 +1,6 @@
-#import <Cocoa/Cocoa.h>
-#include "window.h"
+#pragma once
 
+#import <Cocoa/Cocoa.h>
 NS_ASSUME_NONNULL_BEGIN
 
 class IAvnAutomationPeer;

+ 2 - 1
native/Avalonia.Native/src/OSX/automation.mm

@@ -1,7 +1,8 @@
 #include "common.h"
 #include "automation.h"
 #include "AvnString.h"
-#include "window.h"
+#include "INSWindowHolder.h"
+#include "AvnView.h"
 
 @interface AvnAccessibilityElement (Events)
 - (void) raiseChildrenChanged;

+ 1 - 2
native/Avalonia.Native/src/OSX/common.h

@@ -27,7 +27,7 @@ extern IAvnMenuItem* CreateAppMenuItem();
 extern IAvnMenuItem* CreateAppMenuItemSeparator();
 extern IAvnApplicationCommands* CreateApplicationCommands();
 extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
-extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu);
+extern void SetAppMenu(IAvnMenu *menu);
 extern void SetServicesMenu (IAvnMenu* menu);
 extern IAvnMenu* GetAppMenu ();
 extern NSMenuItem* GetAppMenuItem ();
@@ -38,7 +38,6 @@ extern NSPoint ToNSPoint (AvnPoint p);
 extern NSRect ToNSRect (AvnRect r);
 extern AvnPoint ToAvnPoint (NSPoint p);
 extern AvnPoint ConvertPointY (AvnPoint p);
-extern CGFloat PrimaryDisplayHeight();
 extern NSSize ToNSSize (AvnSize s);
 #ifdef DEBUG
 #define NSDebugLog(...) NSLog(__VA_ARGS__)

+ 0 - 1
native/Avalonia.Native/src/OSX/cursor.mm

@@ -1,6 +1,5 @@
 #include "common.h"
 #include "cursor.h"
-#include <map>
 
 class CursorFactory : public ComSingleObject<IAvnCursorFactory, &IID_IAvnCursorFactory>
 {

+ 1 - 6
native/Avalonia.Native/src/OSX/main.mm

@@ -1,7 +1,6 @@
 //This file will contain actual IID structures
 #define COM_GUIDS_MATERIALIZE
 #include "common.h"
-#include "window.h"
 
 static NSString* s_appTitle = @"Avalonia";
 
@@ -343,7 +342,7 @@ public:
         
         @autoreleasepool
         {
-            ::SetAppMenu(s_appTitle, appMenu);
+            ::SetAppMenu(appMenu);
             return S_OK;
         }
     }
@@ -428,7 +427,3 @@ AvnPoint ConvertPointY (AvnPoint p)
     return p;
 }
 
-CGFloat PrimaryDisplayHeight()
-{
-  return NSMaxY([[[NSScreen screens] firstObject] frame]);
-}

+ 0 - 1
native/Avalonia.Native/src/OSX/menu.h

@@ -31,7 +31,6 @@ private:
     NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem
     IAvnActionCallback* _callback;
     IAvnPredicateCallback* _predicate;
-    bool _isSeparator;
     bool _isCheckable;
     
 public:

+ 2 - 4
native/Avalonia.Native/src/OSX/menu.mm

@@ -1,7 +1,6 @@
 
 #include "common.h"
 #include "menu.h"
-#include "window.h"
 #include "KeyTransform.h"
 #include <CoreFoundation/CoreFoundation.h>
 #include <Carbon/Carbon.h> /* For kVK_ constants, and TIS functions. */
@@ -74,8 +73,7 @@
 AvnAppMenuItem::AvnAppMenuItem(bool isSeparator)
 {
     _isCheckable = false;
-    _isSeparator = isSeparator;
-    
+
     if(isSeparator)
     {
         _native = [NSMenuItem separatorItem];
@@ -460,7 +458,7 @@ extern IAvnMenuItem* CreateAppMenuItemSeparator()
 static IAvnMenu* s_appMenu = nullptr;
 static NSMenuItem* s_appMenuItem = nullptr;
 
-extern void SetAppMenu (NSString* appName, IAvnMenu* menu)
+extern void SetAppMenu(IAvnMenu *menu)
 {
     s_appMenu = menu;
     

+ 0 - 4
native/Avalonia.Native/src/OSX/rendertarget.mm

@@ -1,14 +1,10 @@
 #include "common.h"
 #include "rendertarget.h"
-#import <IOSurface/IOSurface.h>
 #import <IOSurface/IOSurfaceObjC.h>
 #import <QuartzCore/QuartzCore.h>
 
-#include <OpenGL/CGLIOSurface.h>
-#include <OpenGL/OpenGL.h>
 #include <OpenGL/glext.h>
 #include <OpenGL/gl3.h>
-#include <OpenGL/gl3ext.h>
 
 @interface IOSurfaceHolder : NSObject
 @end

+ 0 - 77
native/Avalonia.Native/src/OSX/window.h

@@ -1,77 +0,0 @@
-#ifndef window_h
-#define window_h
-
-class WindowBaseImpl;
-
-@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
--(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
--(NSEvent* _Nonnull) lastMouseDownEvent;
--(AvnPoint) translateLocalPoint:(AvnPoint)pt;
--(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
--(void) onClosed;
--(AvnPixelSize) getPixelSize;
--(AvnPlatformResizeReason) getResizeReason;
--(void) setResizeReason:(AvnPlatformResizeReason)reason;
-+ (AvnPoint)toAvnPoint:(CGPoint)p;
-@end
-
-@interface AutoFitContentView : NSView
--(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;
--(void) ShowTitleBar: (bool) show;
--(void) SetTitleBarHeightHint: (double) height;
--(void) SetContent: (NSView* _Nonnull) content;
--(void) ShowBlur: (bool) show;
-@end
-
-@interface AvnWindow : NSWindow <NSWindowDelegate>
-+(void) closeAll;
--(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
--(void) setCanBecomeKeyAndMain;
--(void) pollModalSession: (NSModalSession _Nonnull) session;
--(void) restoreParentWindow;
--(bool) shouldTryToHandleEvents;
--(void) setEnabled: (bool) enable;
--(void) showAppMenuOnly;
--(void) showWindowMenuWithAppMenu;
--(void) applyMenu:(NSMenu* _Nullable)menu;
--(double) getScaling;
--(double) getExtendedTitleBarHeight;
--(void) setIsExtended:(bool)value;
--(bool) isDialog;
-@end
-
-struct INSWindowHolder
-{
-    virtual AvnWindow* _Nonnull GetNSWindow () = 0;
-    virtual AvnView* _Nonnull GetNSView () = 0;
-};
-
-struct IWindowStateChanged
-{
-    virtual void WindowStateChanged () = 0;
-    virtual void StartStateTransition () = 0;
-    virtual void EndStateTransition () = 0;
-    virtual SystemDecorations Decorations () = 0;
-    virtual AvnWindowState WindowState () = 0;
-};
-
-class ResizeScope
-{
-public:
-    ResizeScope(AvnView* _Nonnull view, AvnPlatformResizeReason reason)
-    {
-        _view = view;
-        _restore = [view getResizeReason];
-        [view setResizeReason:reason];
-    }
-    
-    ~ResizeScope()
-    {
-        [_view setResizeReason:_restore];
-    }
-private:
-    AvnView* _Nonnull _view;
-    AvnPlatformResizeReason _restore;
-};
-
-#endif /* window_h */

+ 0 - 2590
native/Avalonia.Native/src/OSX/window.mm

@@ -1,2590 +0,0 @@
-#include "common.h"
-#include "window.h"
-#include "KeyTransform.h"
-#include "cursor.h"
-#include "menu.h"
-#include <OpenGL/gl.h>
-#include "rendertarget.h"
-#include "AvnString.h"
-#include "automation.h"
-
-class WindowBaseImpl : public virtual ComObject,
-    public virtual IAvnWindowBase,
-    public INSWindowHolder
-{
-private:
-    NSCursor* cursor;
-
-public:
-    FORWARD_IUNKNOWN()
-    BEGIN_INTERFACE_MAP()
-    INTERFACE_MAP_ENTRY(IAvnWindowBase, IID_IAvnWindowBase)
-    END_INTERFACE_MAP()
-
-    virtual ~WindowBaseImpl()
-    {
-        View = NULL;
-        Window = NULL;
-    }
-    AutoFitContentView* StandardContainer;
-    AvnView* View;
-    AvnWindow* Window;
-    ComPtr<IAvnWindowBaseEvents> BaseEvents;
-    ComPtr<IAvnGlContext> _glContext;
-    NSObject<IRenderTarget>* renderTarget;
-    AvnPoint lastPositionSet;
-    NSString* _lastTitle;
-    IAvnMenu* _mainMenu;
-    
-    bool _shown;
-    bool _inResize;
-    
-    WindowBaseImpl(IAvnWindowBaseEvents* events, IAvnGlContext* gl)
-    {
-        _shown = false;
-        _inResize = false;
-        _mainMenu = nullptr;
-        BaseEvents = events;
-        _glContext = gl;
-        renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: gl];
-        View = [[AvnView alloc] initWithParent:this];
-        StandardContainer = [[AutoFitContentView new] initWithContent:View];
-
-        Window = [[AvnWindow alloc] initWithParent:this];
-        [Window setContentView: StandardContainer];
-        
-        lastPositionSet.X = 100;
-        lastPositionSet.Y = 100;
-        _lastTitle = @"";
-        
-        [Window setStyleMask:NSWindowStyleMaskBorderless];
-        [Window setBackingType:NSBackingStoreBuffered];
-        
-        [Window setOpaque:false];
-    }
-    
-    virtual HRESULT ObtainNSWindowHandle(void** ret) override
-    {
-        START_COM_CALL;
-        
-        if (ret == nullptr)
-        {
-            return E_POINTER;
-        }
-        
-        *ret =  (__bridge void*)Window;
-        
-        return S_OK;
-    }
-    
-    virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override
-    {
-        START_COM_CALL;
-        
-        if (ret == nullptr)
-        {
-            return E_POINTER;
-        }
-        
-        *ret =  (__bridge_retained void*)Window;
-        
-        return S_OK;
-    }
-    
-    virtual HRESULT ObtainNSViewHandle(void** ret) override
-    {
-        START_COM_CALL;
-        
-        if (ret == nullptr)
-        {
-            return E_POINTER;
-        }
-        
-        *ret =  (__bridge void*)View;
-        
-        return S_OK;
-    }
-    
-    virtual HRESULT ObtainNSViewHandleRetained(void** ret) override
-    {
-        START_COM_CALL;
-        
-        if (ret == nullptr)
-        {
-            return E_POINTER;
-        }
-        
-        *ret =  (__bridge_retained void*)View;
-        
-        return S_OK;
-    }
-    
-    virtual AvnWindow* GetNSWindow() override
-    {
-        return Window;
-    }
-
-    virtual AvnView* GetNSView() override
-    {
-        return View;
-    }
-
-    virtual HRESULT Show(bool activate, bool isDialog) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            SetPosition(lastPositionSet);
-            UpdateStyle();
-            
-            [Window setTitle:_lastTitle];
-            
-            if(ShouldTakeFocusOnShow() && activate)
-            {
-                [Window orderFront: Window];
-                [Window makeKeyAndOrderFront:Window];
-                [Window makeFirstResponder:View];
-                [NSApp activateIgnoringOtherApps:YES];
-            }
-            else
-            {
-                [Window orderFront: Window];
-            }
-            
-            _shown = true;
-            
-            return S_OK;
-        }
-    }
-    
-    virtual bool ShouldTakeFocusOnShow()
-    {
-        return true;
-    }
-    
-    virtual HRESULT Hide () override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(Window != nullptr)
-            {
-                [Window orderOut:Window];
-                [Window restoreParentWindow];
-            }
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT Activate () override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(Window != nullptr)
-            {
-                [Window makeKeyAndOrderFront:nil];
-                [NSApp activateIgnoringOtherApps:YES];
-            }
-        }
-        
-        return S_OK;
-    }
-    
-    virtual HRESULT SetTopMost (bool value) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            [Window setLevel: value ? NSFloatingWindowLevel : NSNormalWindowLevel];
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT Close() override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if (Window != nullptr)
-            {
-                auto window = Window;
-                Window = nullptr;
-                
-                try{
-                    // Seems to throw sometimes on application exit.
-                    [window close];
-                }
-                catch(NSException*){}
-            }
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT GetClientSize(AvnSize* ret) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(ret == nullptr)
-                return E_POINTER;
-            
-            auto frame = [View frame];
-            ret->Width = frame.size.width;
-            ret->Height = frame.size.height;
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT GetFrameSize(AvnSize* ret) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(ret == nullptr)
-                return E_POINTER;
-            
-            auto frame = [Window frame];
-            ret->Width = frame.size.width;
-            ret->Height = frame.size.height;
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT GetScaling (double* ret) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(ret == nullptr)
-                return E_POINTER;
-            
-            if(Window == nullptr)
-            {
-                *ret = 1;
-                return S_OK;
-            }
-            
-            *ret = [Window backingScaleFactor];
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetMinMaxSize (AvnSize minSize, AvnSize maxSize) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            [Window setMinSize: ToNSSize(minSize)];
-            [Window setMaxSize: ToNSSize(maxSize)];
-        
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
-    {
-        if(_inResize)
-        {
-            return S_OK;
-        }
-        
-        _inResize = true;
-        
-        START_COM_CALL;
-        auto resizeBlock = ResizeScope(View, reason);
-        
-        @autoreleasepool
-        {
-            auto maxSize = [Window maxSize];
-            auto minSize = [Window minSize];
-            
-            if (x < minSize.width)
-            {
-                x = minSize.width;
-            }
-            
-            if (y < minSize.height)
-            {
-                y = minSize.height;
-            }
-            
-            if (x > maxSize.width)
-            {
-                x = maxSize.width;
-            }
-            
-            if (y > maxSize.height)
-            {
-                y = maxSize.height;
-            }
-            
-            @try
-            {
-                if(!_shown)
-                {
-                    BaseEvents->Resized(AvnSize{x,y}, reason);
-                }
-                
-                [Window setContentSize:NSSize{x,y}];
-                [Window invalidateShadow];
-            }
-            @finally
-            {
-                _inResize = false;
-            }
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT Invalidate (AvnRect rect) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            [View setNeedsDisplayInRect:[View frame]];
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetMainMenu(IAvnMenu* menu) override
-    {
-        START_COM_CALL;
-        
-        _mainMenu = menu;
-        
-        auto nativeMenu = dynamic_cast<AvnAppMenu*>(menu);
-        
-        auto nsmenu = nativeMenu->GetNative();
-        
-        [Window applyMenu:nsmenu];
-        
-        if ([Window isKeyWindow])
-        {
-            [Window showWindowMenuWithAppMenu];
-        }
-        
-        return S_OK;
-    }
-    
-    virtual HRESULT BeginMoveDrag () override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            auto lastEvent = [View lastMouseDownEvent];
-            
-            if(lastEvent == nullptr)
-            {
-                return S_OK;
-            }
-            
-            [Window performWindowDragWithEvent:lastEvent];
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT BeginResizeDrag (AvnWindowEdge edge) override
-    {
-        START_COM_CALL;
-        
-        return S_OK;
-    }
-    
-    virtual HRESULT GetPosition (AvnPoint* ret) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(ret == nullptr)
-            {
-                return E_POINTER;
-            }
-            
-            auto frame = [Window frame];
-            
-            ret->X = frame.origin.x;
-            ret->Y = frame.origin.y + frame.size.height;
-            
-            *ret = ConvertPointY(*ret);
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetPosition (AvnPoint point) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            lastPositionSet = point;
-            [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))];
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT PointToClient (AvnPoint point, AvnPoint* ret) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(ret == nullptr)
-            {
-                return E_POINTER;
-            }
-            
-            point = ConvertPointY(point);
-            NSRect convertRect = [Window convertRectToScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
-            auto viewPoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);
-            
-            *ret = [View translateLocalPoint:ToAvnPoint(viewPoint)];
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT PointToScreen (AvnPoint point, AvnPoint* ret) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(ret == nullptr)
-            {
-                return E_POINTER;
-            }
-            
-            auto cocoaViewPoint =  ToNSPoint([View translateLocalPoint:point]);
-            NSRect convertRect = [Window convertRectToScreen:NSMakeRect(cocoaViewPoint.x, cocoaViewPoint.y, 0.0, 0.0)];
-            auto cocoaScreenPoint = NSPointFromCGPoint(NSMakePoint(convertRect.origin.x, convertRect.origin.y));
-            *ret = ConvertPointY(ToAvnPoint(cocoaScreenPoint));
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose) override
-    {
-        START_COM_CALL;
-        
-        [View setSwRenderedFrame: fb dispose: dispose];
-        return S_OK;
-    }
-    
-    virtual HRESULT SetCursor(IAvnCursor* cursor) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            Cursor* avnCursor = dynamic_cast<Cursor*>(cursor);
-            this->cursor = avnCursor->GetNative();
-            UpdateCursor();
-            
-            if(avnCursor->IsHidden())
-            {
-                [NSCursor hide];
-            }
-            else
-            {
-                [NSCursor unhide];
-            }
-            
-            return S_OK;
-        }
-    }
-
-    virtual void UpdateCursor()
-    {
-        if (cursor != nil)
-        {
-            [cursor set];
-        }
-    }
-    
-    virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ppv) override
-    {
-        START_COM_CALL;
-        
-        if(View == NULL)
-            return E_FAIL;
-        *ppv = [renderTarget createSurfaceRenderTarget];
-        return *ppv == nil ? E_FAIL : S_OK;
-    }
-    
-    virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override
-    {
-        START_COM_CALL;
-        
-        if(View == NULL)
-            return E_FAIL;
-        *retOut = ::CreateNativeControlHost(View);
-        return S_OK;
-    }
-    
-    virtual HRESULT SetBlurEnabled (bool enable) override
-    {
-        START_COM_CALL;
-        
-        [StandardContainer ShowBlur:enable];
-        
-        return S_OK;
-    }
-    
-    virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
-                                              IAvnClipboard* clipboard, IAvnDndResultCallback* cb,
-                                              void* sourceHandle) override
-    {
-        START_COM_CALL;
-        
-        auto item = TryGetPasteboardItem(clipboard);
-        [item setString:@"" forType:GetAvnCustomDataType()];
-        if(item == nil)
-            return E_INVALIDARG;
-        if(View == NULL)
-            return E_FAIL;
-        
-        auto nsevent = [NSApp currentEvent];
-        auto nseventType = [nsevent type];
-        
-        // If current event isn't a mouse one (probably due to malfunctioning user app)
-        // attempt to forge a new one
-        if(!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited)
-           || (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged)))
-        {
-            NSRect convertRect = [Window convertRectToScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
-            auto nspoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);            
-            CGPoint cgpoint = NSPointToCGPoint(nspoint);
-            auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft);
-            nsevent = [NSEvent eventWithCGEvent: cgevent];
-            CFRelease(cgevent);
-        }
-        
-        auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: item];
-        
-        auto dragItemImage = [NSImage imageNamed:NSImageNameMultipleDocuments];
-        NSRect dragItemRect = {(float)point.X, (float)point.Y, [dragItemImage size].width, [dragItemImage size].height};
-        [dragItem setDraggingFrame: dragItemRect contents: dragItemImage];
-        
-        int op = 0; int ieffects = (int)effects;
-        if((ieffects & (int)AvnDragDropEffects::Copy) != 0)
-            op |= NSDragOperationCopy;
-        if((ieffects & (int)AvnDragDropEffects::Link) != 0)
-            op |= NSDragOperationLink;
-        if((ieffects & (int)AvnDragDropEffects::Move) != 0)
-            op |= NSDragOperationMove;
-        [View beginDraggingSessionWithItems: @[dragItem] event: nsevent
-                                     source: CreateDraggingSource((NSDragOperation) op, cb, sourceHandle)];
-        return S_OK;
-    }
-
-    virtual bool IsDialog()
-    {
-        return false;
-    }
-    
-protected:
-    virtual NSWindowStyleMask GetStyle()
-    {
-        return NSWindowStyleMaskBorderless;
-    }
-    
-    void UpdateStyle()
-    {
-        [Window setStyleMask: GetStyle()];
-    }
-    
-public:
-    virtual void OnResized ()
-    {
-        
-    }
-};
-
-class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
-{
-private:
-    bool _canResize;
-    bool _fullScreenActive;
-    SystemDecorations _decorations;
-    AvnWindowState _lastWindowState;
-    AvnWindowState _actualWindowState;
-    bool _inSetWindowState;
-    NSRect _preZoomSize;
-    bool _transitioningWindowState;
-    bool _isClientAreaExtended;
-    bool _isDialog;
-    AvnExtendClientAreaChromeHints _extendClientHints;
-    
-    FORWARD_IUNKNOWN()
-    BEGIN_INTERFACE_MAP()
-    INHERIT_INTERFACE_MAP(WindowBaseImpl)
-    INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow)
-    END_INTERFACE_MAP()
-    virtual ~WindowImpl()
-    {
-    }
-    
-    ComPtr<IAvnWindowEvents> WindowEvents;
-    WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
-    {
-        _isClientAreaExtended = false;
-        _extendClientHints = AvnDefaultChrome;
-        _fullScreenActive = false;
-        _canResize = true;
-        _decorations = SystemDecorationsFull;
-        _transitioningWindowState = false;
-        _inSetWindowState = false;
-        _lastWindowState = Normal;
-        _actualWindowState = Normal;
-        WindowEvents = events;
-        [Window setCanBecomeKeyAndMain];
-        [Window disableCursorRects];
-        [Window setTabbingMode:NSWindowTabbingModeDisallowed];
-        [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
-    }
-    
-    void HideOrShowTrafficLights ()
-    {
-        if (Window == nil)
-        {
-            return;
-        }
-        
-        for (id subview in Window.contentView.superview.subviews) {
-            if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
-                NSView *titlebarView = [subview subviews][0];
-                for (id button in titlebarView.subviews) {
-                    if ([button isKindOfClass:[NSButton class]])
-                    {
-                        if(_isClientAreaExtended)
-                        {
-                            auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
-                            
-                            [button setHidden: !wantsChrome];
-                        }
-                        else
-                        {
-                            [button setHidden: (_decorations != SystemDecorationsFull)];
-                        }
-                        
-                        [button setWantsLayer:true];
-                    }
-                }
-            }
-        }
-    }
-    
-    virtual HRESULT Show (bool activate, bool isDialog) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            _isDialog = isDialog;
-            WindowBaseImpl::Show(activate, isDialog);
-            
-            HideOrShowTrafficLights();
-            
-            return SetWindowState(_lastWindowState);
-        }
-    }
-    
-    virtual HRESULT SetEnabled (bool enable) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            [Window setEnabled:enable];
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetParent (IAvnWindow* parent) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(parent == nullptr)
-                return E_POINTER;
-
-            auto cparent = dynamic_cast<WindowImpl*>(parent);
-            if(cparent == nullptr)
-                return E_INVALIDARG;
-            
-            // If one tries to show a child window with a minimized parent window, then the parent window will be
-            // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
-            // state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
-            if (cparent->WindowState() == Minimized)
-                cparent->SetWindowState(Normal);
-            
-            [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
-            [cparent->Window addChildWindow:Window ordered:NSWindowAbove];
-            
-            UpdateStyle();
-            
-            return S_OK;
-        }
-    }
-    
-    void StartStateTransition () override
-    {
-        _transitioningWindowState = true;
-    }
-    
-    void EndStateTransition () override
-    {
-        _transitioningWindowState = false;
-    }
-    
-    SystemDecorations Decorations () override
-    {
-        return _decorations;
-    }
-    
-    AvnWindowState WindowState () override
-    {
-        return _lastWindowState;
-    }
-    
-    void WindowStateChanged () override
-    {
-        if(_shown && !_inSetWindowState && !_transitioningWindowState)
-        {
-            AvnWindowState state;
-            GetWindowState(&state);
-            
-            if(_lastWindowState != state)
-            {
-                if(_isClientAreaExtended)
-                {
-                    if(_lastWindowState == FullScreen)
-                    {
-                        // we exited fs.
-                       if(_extendClientHints & AvnOSXThickTitleBar)
-                       {
-                          Window.toolbar = [NSToolbar new];
-                          Window.toolbar.showsBaselineSeparator = false;
-                       }
-
-                       [Window setTitlebarAppearsTransparent:true];
-
-                       [StandardContainer setFrameSize: StandardContainer.frame.size];
-                    }
-                    else if(state == FullScreen)
-                    {
-                        // we entered fs.
-                        if(_extendClientHints & AvnOSXThickTitleBar)
-                        {
-                            Window.toolbar = nullptr;
-                        }
-                       
-                        [Window setTitlebarAppearsTransparent:false];
-                        
-                        [StandardContainer setFrameSize: StandardContainer.frame.size];
-                    }
-                }
-                
-                _lastWindowState = state;
-                _actualWindowState = state;
-                WindowEvents->WindowStateChanged(state);
-            }
-        }
-    }
-    
-    bool UndecoratedIsMaximized ()
-    {
-        auto windowSize = [Window frame];
-        auto available = [Window screen].visibleFrame;
-        return CGRectEqualToRect(windowSize, available);
-    }
-    
-    bool IsZoomed ()
-    {
-        return _decorations == SystemDecorationsFull ? [Window isZoomed] : UndecoratedIsMaximized();
-    }
-    
-    void DoZoom()
-    {
-        switch (_decorations)
-        {
-            case SystemDecorationsNone:
-            case SystemDecorationsBorderOnly:
-                [Window setFrame:[Window screen].visibleFrame display:true];
-                break;
-
-            
-            case SystemDecorationsFull:
-                [Window performZoom:Window];
-                break;
-        }
-    }
-    
-    virtual HRESULT SetCanResize(bool value) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            _canResize = value;
-            UpdateStyle();
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetDecorations(SystemDecorations value) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            auto currentWindowState = _lastWindowState;
-            _decorations = value;
-            
-            if(_fullScreenActive)
-            {
-                return S_OK;
-            }
-            
-            UpdateStyle();
-            
-            HideOrShowTrafficLights();
-
-            switch (_decorations)
-            {
-                case SystemDecorationsNone:
-                    [Window setHasShadow:NO];
-                    [Window setTitleVisibility:NSWindowTitleHidden];
-                    [Window setTitlebarAppearsTransparent:YES];
-                    
-                    if(currentWindowState == Maximized)
-                    {
-                        if(!UndecoratedIsMaximized())
-                        {
-                            DoZoom();
-                        }
-                    }
-                    break;
-
-                case SystemDecorationsBorderOnly:
-                    [Window setHasShadow:YES];
-                    [Window setTitleVisibility:NSWindowTitleHidden];
-                    [Window setTitlebarAppearsTransparent:YES];
-                    
-                    if(currentWindowState == Maximized)
-                    {
-                        if(!UndecoratedIsMaximized())
-                        {
-                            DoZoom();
-                        }
-                    }
-                    break;
-
-                case SystemDecorationsFull:
-                    [Window setHasShadow:YES];
-                    [Window setTitleVisibility:NSWindowTitleVisible];
-                    [Window setTitlebarAppearsTransparent:NO];
-                    [Window setTitle:_lastTitle];
-                    
-                    if(currentWindowState == Maximized)
-                    {
-                        auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
-                        
-                        [View setFrameSize:newFrame];
-                    }
-                    break;
-            }
-
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetTitle (char* utf8title) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            _lastTitle = [NSString stringWithUTF8String:(const char*)utf8title];
-            [Window setTitle:_lastTitle];
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetTitleBarColor(AvnColor color) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            float a = (float)color.Alpha / 255.0f;
-            float r = (float)color.Red / 255.0f;
-            float g = (float)color.Green / 255.0f;
-            float b = (float)color.Blue / 255.0f;
-            
-            auto nscolor = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a];
-            
-            // Based on the titlebar color we have to choose either light or dark
-            // OSX doesnt let you set a foreground color for titlebar.
-            if ((r*0.299 + g*0.587 + b*0.114) > 186.0f / 255.0f)
-            {
-                [Window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
-            }
-            else
-            {
-                [Window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
-            }
-            
-            [Window setTitlebarAppearsTransparent:true];
-            [Window setBackgroundColor:nscolor];
-        }
-        
-        return S_OK;
-    }
-    
-    virtual HRESULT GetWindowState (AvnWindowState*ret) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(ret == nullptr)
-            {
-                return E_POINTER;
-            }
-            
-            if(([Window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen)
-            {
-                *ret = FullScreen;
-                return S_OK;
-            }
-            
-            if([Window isMiniaturized])
-            {
-                *ret = Minimized;
-                return S_OK;
-            }
-            
-            if(IsZoomed())
-            {
-                *ret = Maximized;
-                return S_OK;
-            }
-            
-            *ret = Normal;
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT TakeFocusFromChildren () override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(Window == nil)
-                return S_OK;
-            if([Window isKeyWindow])
-                [Window makeFirstResponder: View];
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetExtendClientArea (bool enable) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            _isClientAreaExtended = enable;
-            
-            if(enable)
-            {
-                Window.titleVisibility = NSWindowTitleHidden;
-                
-                [Window setTitlebarAppearsTransparent:true];
-                
-                auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
-                
-                if (wantsTitleBar)
-                {
-                    [StandardContainer ShowTitleBar:true];
-                }
-                else
-                {
-                    [StandardContainer ShowTitleBar:false];
-                }
-                
-                if(_extendClientHints & AvnOSXThickTitleBar)
-                {
-                    Window.toolbar = [NSToolbar new];
-                    Window.toolbar.showsBaselineSeparator = false;
-                }
-                else
-                {
-                    Window.toolbar = nullptr;
-                }
-            }
-            else
-            {
-                Window.titleVisibility = NSWindowTitleVisible;
-                Window.toolbar = nullptr;
-                [Window setTitlebarAppearsTransparent:false];
-                View.layer.zPosition = 0;
-            }
-            
-            [Window setIsExtended:enable];
-            
-            HideOrShowTrafficLights();
-            
-            UpdateStyle();
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            _extendClientHints = hints;
-            
-            SetExtendClientArea(_isClientAreaExtended);
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT GetExtendTitleBarHeight (double*ret) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(ret == nullptr)
-            {
-                return E_POINTER;
-            }
-            
-            *ret = [Window getExtendedTitleBarHeight];
-            
-            return S_OK;
-        }
-    }
-    
-    virtual HRESULT SetExtendTitleBarHeight (double value) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            [StandardContainer SetTitleBarHeightHint:value];
-            return S_OK;
-        }
-    }
-    
-    void EnterFullScreenMode ()
-    {
-        _fullScreenActive = true;
-        
-        [Window setTitle:_lastTitle];
-        [Window toggleFullScreen:nullptr];
-    }
-    
-    void ExitFullScreenMode ()
-    {
-        [Window toggleFullScreen:nullptr];
-        
-        _fullScreenActive = false;
-        
-        SetDecorations(_decorations);
-    }
-    
-    virtual HRESULT SetWindowState (AvnWindowState state) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if(Window == nullptr)
-            {
-                return  S_OK;
-            }
-            
-            if(_actualWindowState == state)
-            {
-                return S_OK;
-            }
-            
-            _inSetWindowState = true;
-            
-            auto currentState = _actualWindowState;
-            _lastWindowState = state;
-            
-            if(currentState == Normal)
-            {
-                _preZoomSize = [Window frame];
-            }
-            
-            if(_shown)
-            {
-                switch (state) {
-                    case Maximized:
-                        if(currentState == FullScreen)
-                        {
-                            ExitFullScreenMode();
-                        }
-                        
-                        lastPositionSet.X = 0;
-                        lastPositionSet.Y = 0;
-                        
-                        if([Window isMiniaturized])
-                        {
-                            [Window deminiaturize:Window];
-                        }
-                        
-                        if(!IsZoomed())
-                        {
-                            DoZoom();
-                        }
-                        break;
-                        
-                    case Minimized:
-                        if(currentState == FullScreen)
-                        {
-                            ExitFullScreenMode();
-                        }
-                        else
-                        {
-                            [Window miniaturize:Window];
-                        }
-                        break;
-                        
-                    case FullScreen:
-                        if([Window isMiniaturized])
-                        {
-                            [Window deminiaturize:Window];
-                        }
-                        
-                        EnterFullScreenMode();
-                        break;
-                        
-                    case Normal:
-                        if([Window isMiniaturized])
-                        {
-                            [Window deminiaturize:Window];
-                        }
-                        
-                        if(currentState == FullScreen)
-                        {
-                            ExitFullScreenMode();
-                        }
-                        
-                        if(IsZoomed())
-                        {
-                            if(_decorations == SystemDecorationsFull)
-                            {
-                                DoZoom();
-                            }
-                            else
-                            {
-                                [Window setFrame:_preZoomSize display:true];
-                                auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
-                                
-                                [View setFrameSize:newFrame];
-                            }
-                            
-                        }
-                        break;
-                }
-                
-                _actualWindowState = _lastWindowState;
-                WindowEvents->WindowStateChanged(_actualWindowState);
-            }
-            
-            
-            _inSetWindowState = false;
-            
-            return S_OK;
-        }
-    }
-
-    virtual void OnResized () override
-    {
-        if(_shown && !_inSetWindowState && !_transitioningWindowState)
-        {
-            WindowStateChanged();
-        }
-    }
-    
-    virtual bool IsDialog() override
-    {
-        return _isDialog;
-    }
-    
-protected:
-    virtual NSWindowStyleMask GetStyle() override
-    {
-        unsigned long s = NSWindowStyleMaskBorderless;
-
-        switch (_decorations)
-        {
-            case SystemDecorationsNone:
-                s = s | NSWindowStyleMaskFullSizeContentView;
-                break;
-
-            case SystemDecorationsBorderOnly:
-                s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView;
-                break;
-
-            case SystemDecorationsFull:
-                s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless;
-                
-                if(_canResize)
-                {
-                    s = s | NSWindowStyleMaskResizable;
-                }
-                break;
-        }
-
-        if([Window parentWindow] == nullptr)
-        {
-            s |= NSWindowStyleMaskMiniaturizable;
-        }
-        
-        if(_isClientAreaExtended)
-        {
-            s |= NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskTexturedBackground;
-        }
-        return s;
-    }
-};
-
-NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode, nil];
-
-@implementation AutoFitContentView
-{
-    NSVisualEffectView* _titleBarMaterial;
-    NSBox* _titleBarUnderline;
-    NSView* _content;
-    NSVisualEffectView* _blurBehind;
-    double _titleBarHeightHint;
-    bool _settingSize;
-}
-
--(AutoFitContentView* _Nonnull) initWithContent:(NSView *)content
-{
-    _titleBarHeightHint = -1;
-    _content = content;
-    _settingSize = false;
-
-    [self setAutoresizesSubviews:true];
-    [self setWantsLayer:true];
-    
-    _titleBarMaterial = [NSVisualEffectView new];
-    [_titleBarMaterial setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
-    [_titleBarMaterial setMaterial:NSVisualEffectMaterialTitlebar];
-    [_titleBarMaterial setWantsLayer:true];
-    _titleBarMaterial.hidden = true;
-    
-    _titleBarUnderline = [NSBox new];
-    _titleBarUnderline.boxType = NSBoxSeparator;
-    _titleBarUnderline.fillColor = [NSColor underPageBackgroundColor];
-    _titleBarUnderline.hidden = true;
-    
-    [self addSubview:_titleBarMaterial];
-    [self addSubview:_titleBarUnderline];
-    
-    _blurBehind = [NSVisualEffectView new];
-    [_blurBehind setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
-    [_blurBehind setMaterial:NSVisualEffectMaterialLight];
-    [_blurBehind setWantsLayer:true];
-    _blurBehind.hidden = true;
-    
-    [_blurBehind setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
-    [_content setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
-    
-    [self addSubview:_blurBehind];
-    [self addSubview:_content];
-    
-    [self setWantsLayer:true];
-    return self;
-}
-
--(void) ShowBlur:(bool)show
-{
-    _blurBehind.hidden = !show;
-}
-
--(void) ShowTitleBar: (bool) show
-{
-    _titleBarMaterial.hidden = !show;
-    _titleBarUnderline.hidden = !show;
-}
-
--(void) SetTitleBarHeightHint: (double) height
-{
-    _titleBarHeightHint = height;
-    
-    [self setFrameSize:self.frame.size];
-}
-
--(void)setFrameSize:(NSSize)newSize
-{
-    if(_settingSize)
-    {
-        return;
-    }
-    
-    _settingSize = true;
-    [super setFrameSize:newSize];
-    
-    auto window = objc_cast<AvnWindow>([self window]);
-    
-    // TODO get actual titlebar size
-    
-    double height = _titleBarHeightHint == -1 ? [window getExtendedTitleBarHeight] : _titleBarHeightHint;
-    
-    NSRect tbar;
-    tbar.origin.x = 0;
-    tbar.origin.y = newSize.height - height;
-    tbar.size.width = newSize.width;
-    tbar.size.height = height;
-    
-    [_titleBarMaterial setFrame:tbar];
-    tbar.size.height = height < 1 ? 0 : 1;
-    [_titleBarUnderline setFrame:tbar];
-
-    _settingSize = false;
-}
-
--(void) SetContent: (NSView* _Nonnull) content
-{
-    if(content != nullptr)
-    {
-        [content removeFromSuperview];
-        [self addSubview:content];
-        _content = content;
-    }
-}
-@end
-
-@implementation AvnView
-{
-    ComPtr<WindowBaseImpl> _parent;
-    ComPtr<IUnknown> _swRenderedFrame;
-    AvnFramebuffer _swRenderedFrameBuffer;
-    bool _queuedDisplayFromThread;
-    NSTrackingArea* _area;
-    bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed, _isMouseOver;
-    AvnInputModifiers _modifierState;
-    NSEvent* _lastMouseDownEvent;
-    bool _lastKeyHandled;
-    AvnPixelSize _lastPixelSize;
-    NSObject<IRenderTarget>* _renderTarget;
-    AvnPlatformResizeReason _resizeReason;
-    AvnAccessibilityElement* _accessibilityChild;
-}
-
-- (void)onClosed
-{
-    @synchronized (self)
-    {
-        _parent = nullptr;
-    }
-}
-
--(AvnPixelSize) getPixelSize
-{
-    return _lastPixelSize;
-}
-
-- (NSEvent*) lastMouseDownEvent
-{
-    return _lastMouseDownEvent;
-}
-
-- (void) updateRenderTarget
-{
-    [_renderTarget resize:_lastPixelSize withScale: [[self window] backingScaleFactor]];
-    [self setNeedsDisplayInRect:[self frame]];
-}
-
--(AvnView*)  initWithParent: (WindowBaseImpl*) parent
-{
-    self = [super init];
-    _renderTarget = parent->renderTarget;
-    [self setWantsLayer:YES];
-    [self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize];
-    
-    _parent = parent;
-    _area = nullptr;
-    _lastPixelSize.Height = 100;
-    _lastPixelSize.Width = 100;
-    [self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]];
-    
-    _modifierState = AvnInputModifiersNone;
-    return self;
-}
-
-- (BOOL)isFlipped
-{
-    return YES;
-}
-
-- (BOOL)wantsUpdateLayer
-{
-    return YES;
-}
-
-- (void)setLayer:(CALayer *)layer
-{
-    [_renderTarget setNewLayer: layer];
-    [super setLayer: layer];
-}
-
-- (BOOL)isOpaque
-{
-    return YES;
-}
-
-- (BOOL)acceptsFirstResponder
-{
-    return true;
-}
-
-- (BOOL)acceptsFirstMouse:(NSEvent *)event
-{
-    return true;
-}
-
-- (BOOL)canBecomeKeyView
-{
-    return true;
-}
-
--(void)setFrameSize:(NSSize)newSize
-{
-    [super setFrameSize:newSize];
-    
-    if(_area != nullptr)
-    {
-        [self removeTrackingArea:_area];
-        _area = nullptr;
-    }
-
-    if (_parent == nullptr)
-    {
-        return;
-    }
-
-    NSRect rect = NSZeroRect;
-    rect.size = newSize;
-    
-    NSTrackingAreaOptions options = NSTrackingActiveAlways | NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingEnabledDuringMouseDrag;
-    _area = [[NSTrackingArea alloc] initWithRect:rect options:options owner:self userInfo:nullptr];
-    [self addTrackingArea:_area];
-    
-    _parent->UpdateCursor();
-    
-    auto fsize = [self convertSizeToBacking: [self frame].size];
-    
-    if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
-    {
-        _lastPixelSize.Width = (int)fsize.width;
-        _lastPixelSize.Height = (int)fsize.height;
-        [self updateRenderTarget];
-    
-        auto reason = [self inLiveResize] ? ResizeUser : _resizeReason;
-        _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason);
-    }
-}
-
-- (void)updateLayer
-{
-    AvnInsidePotentialDeadlock deadlock;
-    if (_parent == nullptr)
-    {
-        return;
-    }
-    
-    _parent->BaseEvents->RunRenderPriorityJobs();
-    
-    if (_parent == nullptr)
-    {
-        return;
-    }
-        
-    _parent->BaseEvents->Paint();
-}
-
-- (void)drawRect:(NSRect)dirtyRect
-{
-    return;
-}
-
--(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose
-{
-    @autoreleasepool {
-        [_renderTarget setSwFrame:fb];
-        dispose->Release();
-    }
-}
-
-- (AvnPoint) translateLocalPoint:(AvnPoint)pt
-{
-    pt.Y = [self bounds].size.height - pt.Y;
-    return pt;
-}
-
-+ (AvnPoint)toAvnPoint:(CGPoint)p
-{
-    AvnPoint result;
-    
-    result.X = p.x;
-    result.Y = p.y;
-    
-    return result;
-}
-
-- (void) viewDidChangeBackingProperties
-{
-    auto fsize = [self convertSizeToBacking: [self frame].size];
-    _lastPixelSize.Width = (int)fsize.width;
-    _lastPixelSize.Height = (int)fsize.height;
-    [self updateRenderTarget];
-    
-    if(_parent != nullptr)
-    {
-        _parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
-    }
-    
-    [super viewDidChangeBackingProperties];
-}
-
-- (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
-{
-    auto parentWindow = objc_cast<AvnWindow>([self window]);
-    
-    if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
-    {
-        if(trigerInputWhenDisabled)
-        {
-            auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
-            
-            if(window != nullptr)
-            {
-                window->WindowEvents->GotInputWhenDisabled();
-            }
-        }
-        
-        return TRUE;
-    }
-    
-    return FALSE;
-}
-
-- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
-{
-    bool triggerInputWhenDisabled = type != Move;
-    
-    if([self ignoreUserInput: triggerInputWhenDisabled])
-    {
-        return;
-    }
-    
-    auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
-    auto avnPoint = [AvnView toAvnPoint:localPoint];
-    auto point = [self translateLocalPoint:avnPoint];
-    AvnVector delta;
-    
-    if(type == Wheel)
-    {
-        auto speed = 5;
-        
-        if([event hasPreciseScrollingDeltas])
-        {
-            speed = 50;
-        }
-        
-        delta.X = [event scrollingDeltaX] / speed;
-        delta.Y = [event scrollingDeltaY] / speed;
-        
-        if(delta.X == 0 && delta.Y == 0)
-        {
-            return;
-        }
-    }
-    else if (type == Magnify)
-    {
-        delta.X = delta.Y = [event magnification];
-    }
-    else if (type == Rotate)
-    {
-        delta.X = delta.Y = [event rotation];
-    }
-    else if (type == Swipe)
-    {
-        delta.X = [event deltaX];
-        delta.Y = [event deltaY];
-    }
-    
-    auto timestamp = [event timestamp] * 1000;
-    auto modifiers = [self getModifiers:[event modifierFlags]];
-    
-    if(type != AvnRawMouseEventType::Move ||
-       (
-           [self window] != nil &&
-           (
-                [[self window] firstResponder] == nil
-                || ![[[self window] firstResponder] isKindOfClass: [NSView class]]
-           )
-       )
-    )
-        [self becomeFirstResponder];
-    
-    if(_parent != nullptr)
-    {
-        _parent->BaseEvents->RawMouseEvent(type, timestamp, modifiers, point, delta);
-    }
-    
-    [super mouseMoved:event];
-}
-
-- (BOOL) resignFirstResponder
-{
-    _parent->BaseEvents->LostFocus();
-    return YES;
-}
-
-- (void)mouseMoved:(NSEvent *)event
-{
-    [self mouseEvent:event withType:Move];
-}
-
-- (void)mouseDown:(NSEvent *)event
-{
-    _isLeftPressed = true;
-    _lastMouseDownEvent = event;
-    [self mouseEvent:event withType:LeftButtonDown];
-}
-
-- (void)otherMouseDown:(NSEvent *)event
-{
-    _lastMouseDownEvent = event;
-
-    switch(event.buttonNumber)
-    {
-        case 2:
-        case 3:
-            _isMiddlePressed = true;
-            [self mouseEvent:event withType:MiddleButtonDown];
-            break;
-        case 4:
-            _isXButton1Pressed = true;
-            [self mouseEvent:event withType:XButton1Down];
-            break;
-        case 5:
-            _isXButton2Pressed = true;
-            [self mouseEvent:event withType:XButton2Down];
-            break;
-    }
-}
-
-- (void)rightMouseDown:(NSEvent *)event
-{
-    _isRightPressed = true;
-    _lastMouseDownEvent = event;
-    [self mouseEvent:event withType:RightButtonDown];
-}
-
-- (void)mouseUp:(NSEvent *)event
-{
-    _isLeftPressed = false;
-    [self mouseEvent:event withType:LeftButtonUp];
-}
-
-- (void)otherMouseUp:(NSEvent *)event
-{
-    switch(event.buttonNumber)
-    {
-        case 2:
-        case 3:
-            _isMiddlePressed = false;
-            [self mouseEvent:event withType:MiddleButtonUp];
-            break;
-        case 4:
-            _isXButton1Pressed = false;
-            [self mouseEvent:event withType:XButton1Up];
-            break;
-        case 5:
-            _isXButton2Pressed = false;
-            [self mouseEvent:event withType:XButton2Up];
-            break;
-    }
-}
-
-- (void)rightMouseUp:(NSEvent *)event
-{
-    _isRightPressed = false;
-    [self mouseEvent:event withType:RightButtonUp];
-}
-
-- (void)mouseDragged:(NSEvent *)event
-{
-    [self mouseEvent:event withType:Move];
-    [super mouseDragged:event];
-}
-
-- (void)otherMouseDragged:(NSEvent *)event
-{
-    [self mouseEvent:event withType:Move];
-    [super otherMouseDragged:event];
-}
-
-- (void)rightMouseDragged:(NSEvent *)event
-{
-    [self mouseEvent:event withType:Move];
-    [super rightMouseDragged:event];
-}
-
-- (void)scrollWheel:(NSEvent *)event
-{
-    [self mouseEvent:event withType:Wheel];
-    [super scrollWheel:event];
-}
-
-- (void)magnifyWithEvent:(NSEvent *)event
-{
-    [self mouseEvent:event withType:Magnify];
-    [super magnifyWithEvent:event];
-}
-
-- (void)rotateWithEvent:(NSEvent *)event
-{
-    [self mouseEvent:event withType:Rotate];
-    [super rotateWithEvent:event];
-}
-
-- (void)swipeWithEvent:(NSEvent *)event
-{
-    [self mouseEvent:event withType:Swipe];
-    [super swipeWithEvent:event];
-}
-
-- (void)mouseEntered:(NSEvent *)event
-{
-    _isMouseOver = true;
-    [super mouseEntered:event];
-}
-
-- (void)mouseExited:(NSEvent *)event
-{
-    _isMouseOver = false;
-    [self mouseEvent:event withType:LeaveWindow];
-    [super mouseExited:event];
-} 
-
-- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
-{
-    if([self ignoreUserInput: false])
-    {
-        return;
-    }
-    
-    auto key = s_KeyMap[[event keyCode]];
-    
-    auto timestamp = [event timestamp] * 1000;
-    auto modifiers = [self getModifiers:[event modifierFlags]];
-     
-    if(_parent != nullptr)
-    {
-        _lastKeyHandled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
-    }
-}
-
-- (BOOL)performKeyEquivalent:(NSEvent *)event
-{
-    bool result = _lastKeyHandled;
-    
-    _lastKeyHandled = false;
-    
-    return result;
-}
-
-- (void)flagsChanged:(NSEvent *)event
-{
-    auto newModifierState = [self getModifiers:[event modifierFlags]];
-    
-    bool isAltCurrentlyPressed = (_modifierState & Alt) == Alt;
-    bool isControlCurrentlyPressed = (_modifierState & Control) == Control;
-    bool isShiftCurrentlyPressed = (_modifierState & Shift) == Shift;
-    bool isCommandCurrentlyPressed = (_modifierState & Windows) == Windows;
-    
-    bool isAltPressed = (newModifierState & Alt) == Alt;
-    bool isControlPressed = (newModifierState & Control) == Control;
-    bool isShiftPressed = (newModifierState & Shift) == Shift;
-    bool isCommandPressed = (newModifierState & Windows) == Windows;
-    
-    
-    if (isAltPressed && !isAltCurrentlyPressed)
-    {
-        [self keyboardEvent:event withType:KeyDown];
-    }
-    else if (isAltCurrentlyPressed && !isAltPressed)
-    {
-        [self keyboardEvent:event withType:KeyUp];
-    }
-    
-    if (isControlPressed && !isControlCurrentlyPressed)
-    {
-        [self keyboardEvent:event withType:KeyDown];
-    }
-    else if (isControlCurrentlyPressed && !isControlPressed)
-    {
-        [self keyboardEvent:event withType:KeyUp];
-    }
-    
-    if (isShiftPressed && !isShiftCurrentlyPressed)
-    {
-        [self keyboardEvent:event withType:KeyDown];
-    }
-    else if(isShiftCurrentlyPressed && !isShiftPressed)
-    {
-        [self keyboardEvent:event withType:KeyUp];
-    }
-    
-    if(isCommandPressed && !isCommandCurrentlyPressed)
-    {
-        [self keyboardEvent:event withType:KeyDown];
-    }
-    else if(isCommandCurrentlyPressed && ! isCommandPressed)
-    {
-        [self keyboardEvent:event withType:KeyUp];
-    }
-    
-    _modifierState = newModifierState;
-    
-    [[self inputContext] handleEvent:event];
-    [super flagsChanged:event];
-}
-
-- (void)keyDown:(NSEvent *)event
-{
-    [self keyboardEvent:event withType:KeyDown];
-    [[self inputContext] handleEvent:event];
-    [super keyDown:event];
-}
-
-- (void)keyUp:(NSEvent *)event
-{
-    [self keyboardEvent:event withType:KeyUp];
-    [super keyUp:event];
-}
-
-- (AvnInputModifiers)getModifiers:(NSEventModifierFlags)mod
-{
-    unsigned int rv = 0;
-    
-    if (mod & NSEventModifierFlagControl)
-        rv |= Control;
-    if (mod & NSEventModifierFlagShift)
-        rv |= Shift;
-    if (mod & NSEventModifierFlagOption)
-        rv |= Alt;
-    if (mod & NSEventModifierFlagCommand)
-        rv |= Windows;
-    
-    if (_isLeftPressed)
-        rv |= LeftMouseButton;
-    if (_isMiddlePressed)
-        rv |= MiddleMouseButton;
-    if (_isRightPressed)
-        rv |= RightMouseButton;
-    if (_isXButton1Pressed)
-        rv |= XButton1MouseButton;
-    if (_isXButton2Pressed)
-        rv |= XButton2MouseButton;
-    
-    return (AvnInputModifiers)rv;
-}
-
-- (BOOL)hasMarkedText
-{
-    return _lastKeyHandled;
-}
-
-- (NSRange)markedRange
-{
-    return NSMakeRange(NSNotFound, 0);
-}
-
-- (NSRange)selectedRange
-{
-    return NSMakeRange(NSNotFound, 0);
-}
-
-- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
-{
-    
-}
-
-- (void)unmarkText
-{
-    
-}
-
-- (NSArray<NSString *> *)validAttributesForMarkedText
-{
-    return [NSArray new];
-}
-
-- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
-{
-    return [NSAttributedString new];
-}
-
-- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
-{
-    if(!_lastKeyHandled)
-    {
-        if(_parent != nullptr)
-        {
-            _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(0, [string UTF8String]);
-        }
-    }
-}
-
-- (NSUInteger)characterIndexForPoint:(NSPoint)point
-{
-    return 0;
-}
-
-- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange
-{
-    CGRect result;
-    
-    return result;
-}
-
-- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id <NSDraggingInfo>)info
-{
-    auto localPoint = [self convertPoint:[info draggingLocation] toView:self];
-    auto avnPoint = [AvnView toAvnPoint:localPoint];
-    auto point = [self translateLocalPoint:avnPoint];
-    auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
-    NSDragOperation nsop = [info draggingSourceOperationMask];
-   
-        auto effects = ConvertDragDropEffects(nsop);
-    int reffects = (int)_parent->BaseEvents
-    ->DragEvent(type, point, modifiers, effects,
-                CreateClipboard([info draggingPasteboard], nil),
-                GetAvnDataObjectHandleFromDraggingInfo(info));
-    
-    NSDragOperation ret = 0;
-    
-    // Ensure that the managed part didn't add any new effects
-    reffects = (int)effects & (int)reffects;
-    
-    // OSX requires exactly one operation
-    if((reffects & (int)AvnDragDropEffects::Copy) != 0)
-        ret = NSDragOperationCopy;
-    else if((reffects & (int)AvnDragDropEffects::Move) != 0)
-        ret = NSDragOperationMove;
-    else if((reffects & (int)AvnDragDropEffects::Link) != 0)
-        ret = NSDragOperationLink;
-    if(ret == 0)
-        ret = NSDragOperationNone;
-    return ret;
-}
-
-- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
-{
-    return [self triggerAvnDragEvent: AvnDragEventType::Enter info:sender];
-}
-
-- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
-{
-    return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender];
-}
-
-- (void)draggingExited:(id <NSDraggingInfo>)sender
-{
-    [self triggerAvnDragEvent: AvnDragEventType::Leave info:sender];
-}
-
-- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
-{
-    return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender] != NSDragOperationNone;
-}
-
-- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
-{
-    return [self triggerAvnDragEvent: AvnDragEventType::Drop info:sender] != NSDragOperationNone;
-}
-
-- (void)concludeDragOperation:(nullable id <NSDraggingInfo>)sender
-{
-    
-}
-
-- (AvnPlatformResizeReason)getResizeReason
-{
-    return _resizeReason;
-}
-
-- (void)setResizeReason:(AvnPlatformResizeReason)reason
-{
-    _resizeReason = reason;
-}
-
-- (AvnAccessibilityElement *) accessibilityChild
-{
-    if (_accessibilityChild == nil)
-    {
-        auto peer = _parent->BaseEvents->GetAutomationPeer();
-        
-        if (peer == nil)
-            return nil;
-
-        _accessibilityChild = [AvnAccessibilityElement acquire:peer];
-    }
-    
-    return _accessibilityChild;
-}
-
-- (NSArray *)accessibilityChildren
-{
-    auto child = [self accessibilityChild];
-    return NSAccessibilityUnignoredChildrenForOnlyChild(child);
-}
-
-- (id)accessibilityHitTest:(NSPoint)point
-{
-    return [[self accessibilityChild] accessibilityHitTest:point];
-}
-
-- (id)accessibilityFocusedUIElement
-{
-    return [[self accessibilityChild] accessibilityFocusedUIElement];
-}
-
-@end
-
-
-@implementation AvnWindow
-{
-    ComPtr<WindowBaseImpl> _parent;
-    bool _canBecomeKeyAndMain;
-    bool _closed;
-    bool _isEnabled;
-    bool _isExtended;
-    AvnMenu* _menu;
-    double _lastScaling;
-    IAvnAutomationPeer* _automationPeer;
-    NSMutableArray* _automationChildren;
-}
-
--(void) setIsExtended:(bool)value;
-{
-    _isExtended = value;
-}
-
--(bool) isDialog
-{
-    return _parent->IsDialog();
-}
-
--(double) getScaling
-{
-    return _lastScaling;
-}
-
--(double) getExtendedTitleBarHeight
-{
-    if(_isExtended)
-    {
-        for (id subview in self.contentView.superview.subviews)
-        {
-            if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")])
-            {
-                NSView *titlebarView = [subview subviews][0];
-
-                return (double)titlebarView.frame.size.height;
-            }
-        }
-
-        return -1;
-    }
-    else
-    {
-        return 0;
-    }
-}
-
-+(void)closeAll
-{
-    [[NSApplication sharedApplication] terminate:self];
-}
-
-- (void)performClose:(id)sender
-{
-    if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
-    {
-        if(![[self delegate] windowShouldClose:self]) return;
-    }
-    else if([self respondsToSelector:@selector(windowShouldClose:)])
-    {
-        if(![self windowShouldClose:self]) return;
-    }
-
-    [self close];
-}
-
-- (void)pollModalSession:(nonnull NSModalSession)session
-{
-    auto response = [NSApp runModalSession:session];
-    
-    if(response == NSModalResponseContinue)
-    {
-        dispatch_async(dispatch_get_main_queue(), ^{
-            [self pollModalSession:session];
-        });
-    }
-    else if (!_closed)
-    {
-        [self orderOut:self];
-        [NSApp endModalSession:session];
-    }
-}
-
--(void) showWindowMenuWithAppMenu
-{
-    if(_menu != nullptr)
-    {
-        auto appMenuItem = ::GetAppMenuItem();
-        
-        if(appMenuItem != nullptr)
-        {
-            auto appMenu = [appMenuItem menu];
-            
-            [appMenu removeItem:appMenuItem];
-            
-            [_menu insertItem:appMenuItem atIndex:0];
-            
-            [_menu setHasGlobalMenuItem:true];
-        }
-        
-        [NSApp setMenu:_menu];
-    }
-    else
-    {
-        [self showAppMenuOnly];
-    }
-}
-
--(void) showAppMenuOnly
-{
-    auto appMenuItem = ::GetAppMenuItem();
-    
-    if(appMenuItem != nullptr)
-    {
-        auto appMenu = ::GetAppMenu();
-        
-        auto nativeAppMenu = dynamic_cast<AvnAppMenu*>(appMenu);
-        
-        [[appMenuItem menu] removeItem:appMenuItem];
-        
-        if(_menu != nullptr)
-        {
-            [_menu setHasGlobalMenuItem:false];
-        }
-        
-        [nativeAppMenu->GetNative() addItem:appMenuItem];
-        
-        [NSApp setMenu:nativeAppMenu->GetNative()];
-    }
-    else
-    {
-        [NSApp setMenu:nullptr];
-    }
-}
-
--(void) applyMenu:(AvnMenu *)menu
-{
-    if(menu == nullptr)
-    {
-        menu = [AvnMenu new];
-    }
-    
-    _menu = menu;
-}
-
--(void) setCanBecomeKeyAndMain
-{
-    _canBecomeKeyAndMain = true;
-}
-
--(AvnWindow*)  initWithParent: (WindowBaseImpl*) parent
-{
-    self = [super init];
-    [self setReleasedWhenClosed:false];
-    _parent = parent;
-    [self setDelegate:self];
-    _closed = false;
-    _isEnabled = true;
-    
-    _lastScaling = [self backingScaleFactor];
-    [self setOpaque:NO];
-    [self setBackgroundColor: [NSColor clearColor]];
-    _isExtended = false;
-    return self;
-}
-
-- (BOOL)windowShouldClose:(NSWindow *)sender
-{
-    auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
-    
-    if(window != nullptr)
-    {
-        return !window->WindowEvents->Closing();
-    }
-    
-    return true;
-}
-
-- (void)windowDidChangeBackingProperties:(NSNotification *)notification
-{
-    _lastScaling = [self backingScaleFactor];
-}
-
-- (void)windowWillClose:(NSNotification *)notification
-{
-    _closed = true;
-    if(_parent)
-    {
-        ComPtr<WindowBaseImpl> parent = _parent;
-        _parent = NULL;
-        [self restoreParentWindow];
-        parent->BaseEvents->Closed();
-        [parent->View onClosed];
-    }
-}
-
--(BOOL)canBecomeKeyWindow
-{
-    if (_canBecomeKeyAndMain)
-    {
-        // If the window has a child window being shown as a dialog then don't allow it to become the key window.
-        for(NSWindow* uch in [self childWindows])
-        {
-            auto ch = objc_cast<AvnWindow>(uch);
-            if(ch == nil)
-                continue;
-            if (ch.isDialog)
-                return false;
-        }
-        
-        return true;
-    }
-    
-    return false;
-}
-
--(BOOL)canBecomeMainWindow
-{
-    return _canBecomeKeyAndMain;
-}
-
--(bool)shouldTryToHandleEvents
-{
-    return _isEnabled;
-}
-
--(void) setEnabled:(bool)enable
-{
-    _isEnabled = enable;
-}
-
--(void)becomeKeyWindow
-{
-    [self showWindowMenuWithAppMenu];
-    
-    if(_parent != nullptr)
-    {
-        _parent->BaseEvents->Activated();
-    }
-
-    [super becomeKeyWindow];
-}
-
--(void) restoreParentWindow;
-{
-    auto parent = objc_cast<AvnWindow>([self parentWindow]);
-    if(parent != nil)
-    {
-        [parent removeChildWindow:self];
-    }
-}
-
-- (void)windowDidMiniaturize:(NSNotification *)notification
-{
-    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
-    
-    if(parent != nullptr)
-    {
-        parent->WindowStateChanged();
-    }
-}
-
-- (void)windowDidDeminiaturize:(NSNotification *)notification
-{
-    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
-    
-    if(parent != nullptr)
-    {
-        parent->WindowStateChanged();
-    }
-}
-
-- (void)windowDidResize:(NSNotification *)notification
-{
-    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
-    
-    if(parent != nullptr)
-    {
-        parent->WindowStateChanged();
-    }
-}
-
-- (void)windowWillExitFullScreen:(NSNotification *)notification
-{
-    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
-    
-    if(parent != nullptr)
-    {
-        parent->StartStateTransition();
-    }
-}
-
-- (void)windowDidExitFullScreen:(NSNotification *)notification
-{
-    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
-    
-    if(parent != nullptr)
-    {
-        parent->EndStateTransition();
-        
-        if(parent->Decorations() != SystemDecorationsFull && parent->WindowState() == Maximized)
-        {
-            NSRect screenRect = [[self screen] visibleFrame];
-            [self setFrame:screenRect display:YES];
-        }
-        
-        if(parent->WindowState() == Minimized)
-        {
-            [self miniaturize:nullptr];
-        }
-        
-        parent->WindowStateChanged();
-    }
-}
-
-- (void)windowWillEnterFullScreen:(NSNotification *)notification
-{
-    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
-    
-    if(parent != nullptr)
-    {
-        parent->StartStateTransition();
-    }
-}
-
-- (void)windowDidEnterFullScreen:(NSNotification *)notification
-{
-    auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
-    
-    if(parent != nullptr)
-    {
-        parent->EndStateTransition();
-        parent->WindowStateChanged();
-    }
-}
-
-- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
-{
-    return true;
-}
-
--(void)resignKeyWindow
-{
-    if(_parent)
-        _parent->BaseEvents->Deactivated();
-    
-    [self showAppMenuOnly];
-    
-    [super resignKeyWindow];
-}
-
-- (void)windowDidMove:(NSNotification *)notification
-{
-    AvnPoint position;
-    
-    if(_parent != nullptr)
-    {
-        auto cparent = dynamic_cast<WindowImpl*>(_parent.getRaw());
-        
-        if(cparent != nullptr)
-        {
-            if(cparent->WindowState() == Maximized)
-            {
-                cparent->SetWindowState(Normal);
-            }
-        }
-        
-        _parent->GetPosition(&position);
-        _parent->BaseEvents->PositionChanged(position);
-    }
-}
-
-- (AvnPoint) translateLocalPoint:(AvnPoint)pt
-{
-    pt.Y = [self frame].size.height - pt.Y;
-    return pt;
-}
-
-- (void)sendEvent:(NSEvent *)event
-{
-    [super sendEvent:event];
-    
-    /// This is to detect non-client clicks. This can only be done on Windows... not popups, hence the dynamic_cast.
-    if(_parent != nullptr && dynamic_cast<WindowImpl*>(_parent.getRaw()) != nullptr)
-    {
-        switch(event.type)
-        {
-            case NSEventTypeLeftMouseDown:
-            {
-                AvnView* view = _parent->View;
-                NSPoint windowPoint = [event locationInWindow];
-                NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
-                
-                if (!NSPointInRect(viewPoint, view.bounds))
-                {
-                    auto avnPoint = [AvnView toAvnPoint:windowPoint];
-                    auto point = [self translateLocalPoint:avnPoint];
-                    AvnVector delta;
-                   
-                    _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, [event timestamp] * 1000, AvnInputModifiersNone, point, delta);
-                }
-            }
-            break;
-                
-            case NSEventTypeMouseEntered:
-            {
-                _parent->UpdateCursor();
-            }
-            break;
-                
-            case NSEventTypeMouseExited:
-            {
-                [[NSCursor arrowCursor] set];
-            }
-            break;
-                
-            default:
-                break;
-        }
-    }
-}
-
-@end
-
-class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup
-{
-private:
-    BEGIN_INTERFACE_MAP()
-    INHERIT_INTERFACE_MAP(WindowBaseImpl)
-    INTERFACE_MAP_ENTRY(IAvnPopup, IID_IAvnPopup)
-    END_INTERFACE_MAP()
-    virtual ~PopupImpl(){}
-    ComPtr<IAvnWindowEvents> WindowEvents;
-    PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
-    {
-        WindowEvents = events;
-        [Window setLevel:NSPopUpMenuWindowLevel];
-    }
-protected:
-    virtual NSWindowStyleMask GetStyle() override
-    {
-        return NSWindowStyleMaskBorderless;
-    }
-    
-    virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
-    {
-        START_COM_CALL;
-        
-        @autoreleasepool
-        {
-            if (Window != nullptr)
-            {
-                [Window setContentSize:NSSize{x, y}];
-            
-                [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))];
-            }
-            
-            return S_OK;
-        }
-    }
-public:
-    virtual bool ShouldTakeFocusOnShow() override
-    {
-        return false;
-    }
-};
-
-extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
-{
-    @autoreleasepool
-    {
-        IAvnPopup* ptr = dynamic_cast<IAvnPopup*>(new PopupImpl(events, gl));
-        return ptr;
-    }
-}
-
-extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
-{
-    @autoreleasepool
-    {
-        IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl);
-        return ptr;
-    }
-}

+ 0 - 1
samples/ControlCatalog.NetCore/Program.cs

@@ -117,7 +117,6 @@ namespace ControlCatalog.NetCore
                     EnableWmPointerEvents = true
                 })
                 .UseSkia()
-                .UseManagedSystemDialogs()
                 .AfterSetup(builder =>
                 {
                     builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()

+ 12 - 1
samples/ControlCatalog/App.xaml.cs

@@ -18,6 +18,16 @@ namespace ControlCatalog
             DataContext = new ApplicationViewModel();
         }
 
+        public static readonly StyleInclude ColorPickerFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
+        {
+            Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml")
+        };
+
+        public static readonly StyleInclude ColorPickerDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
+        {
+            Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml")
+        };
+
         public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
         {
             Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
@@ -69,7 +79,8 @@ namespace ControlCatalog
         public override void Initialize()
         {
             Styles.Insert(0, Fluent);
-            Styles.Insert(1, DataGridFluent);
+            Styles.Insert(1, ColorPickerFluent);
+            Styles.Insert(2, DataGridFluent);
             AvaloniaXamlLoader.Load(this);
         }
 

+ 1 - 0
samples/ControlCatalog/ControlCatalog.csproj

@@ -23,6 +23,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
     <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
     <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />

+ 3 - 0
samples/ControlCatalog/MainView.xaml

@@ -43,6 +43,9 @@
       <TabItem Header="Clipboard">
         <pages:ClipboardPage />
       </TabItem>
+      <TabItem Header="ColorPicker">
+        <pages:ColorPickerPage />
+      </TabItem>
       <TabItem Header="ComboBox">
         <pages:ComboBoxPage />
       </TabItem>

+ 8 - 4
samples/ControlCatalog/MainView.xaml.cs

@@ -49,7 +49,8 @@ namespace ControlCatalog
                             App.Fluent.Mode = FluentThemeMode.Light;
                         }
                         Application.Current.Styles[0] = App.Fluent;
-                        Application.Current.Styles[1] = App.DataGridFluent;
+                        Application.Current.Styles[1] = App.ColorPickerFluent;
+                        Application.Current.Styles[2] = App.DataGridFluent;
                     }
                     else if (theme == CatalogTheme.FluentDark)
                     {
@@ -59,19 +60,22 @@ namespace ControlCatalog
                             App.Fluent.Mode = FluentThemeMode.Dark;
                         }
                         Application.Current.Styles[0] = App.Fluent;
-                        Application.Current.Styles[1] = App.DataGridFluent;
+                        Application.Current.Styles[1] = App.ColorPickerFluent;
+                        Application.Current.Styles[2] = App.DataGridFluent;
                     }
                     else if (theme == CatalogTheme.DefaultLight)
                     {
                         App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Light;
                         Application.Current.Styles[0] = App.DefaultLight;
-                        Application.Current.Styles[1] = App.DataGridDefault;
+                        Application.Current.Styles[1] = App.ColorPickerDefault;
+                        Application.Current.Styles[2] = App.DataGridDefault;
                     }
                     else if (theme == CatalogTheme.DefaultDark)
                     {
                         App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Dark;
                         Application.Current.Styles[0] = App.DefaultDark;
-                        Application.Current.Styles[1] = App.DataGridDefault;
+                        Application.Current.Styles[1] = App.ColorPickerDefault;
+                        Application.Current.Styles[2] = App.DataGridDefault;
                     }
                 }
             };

+ 79 - 0
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@@ -0,0 +1,79 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:primitives="clr-namespace:Avalonia.Controls.Primitives;assembly=Avalonia.Controls"
+             xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker"
+             mc:Ignorable="d"
+             d:DesignWidth="800"
+             d:DesignHeight="450"
+             x:Class="ControlCatalog.Pages.ColorPickerPage">
+
+  <UserControl.Resources>
+    <pc:ThirdComponentConverter x:Key="ThirdComponent" />
+  </UserControl.Resources>
+
+  <Grid ColumnDefinitions="Auto,10,Auto">
+    <Grid Grid.Column="0"
+          Grid.Row="0"
+          RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
+      <ColorSpectrum x:Name="ColorSpectrum1"
+                     Grid.Row="0"
+                     Color="Red"
+                     CornerRadius="10"
+                     Height="256"
+                     Width="256" />
+      <ColorSlider Grid.Row="1"
+                   Margin="0,10,0,0"
+                   ColorComponent="Component1"
+                   ColorModel="Hsva"
+                   HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
+      <ColorSlider Grid.Row="2"
+                   ColorComponent="Component2"
+                   ColorModel="Hsva"
+                   HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
+      <ColorSlider Grid.Row="3"
+                   ColorComponent="Component3"
+                   ColorModel="Hsva"
+                   HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
+      <ColorSlider Grid.Row="4"
+                   ColorComponent="Alpha"
+                   ColorModel="Hsva"
+                   HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
+      <ColorPreviewer Grid.Row="5"
+                      ShowAccentColors="True"
+                      HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
+    </Grid>
+    <Grid Grid.Column="2"
+          Grid.Row="0"
+          ColumnDefinitions="Auto,Auto,Auto"
+          RowDefinitions="Auto,Auto">
+      <ColorSlider Grid.Column="0"
+                   Grid.Row="0"
+                   IsAlphaMaxForced="True"
+                   IsSaturationValueMaxForced="False"
+                   ColorComponent="{Binding Components, ElementName=ColorSpectrum2, Converter={StaticResource ThirdComponent}}"
+                   ColorModel="Hsva"
+                   Orientation="Vertical"
+                   HsvColor="{Binding HsvColor, ElementName=ColorSpectrum2}" />
+      <ColorSpectrum x:Name="ColorSpectrum2"
+                     Grid.Column="1"
+                     Grid.Row="0"
+                     Color="Green"
+                     Shape="Ring"
+                     Height="256"
+                     Width="256" />
+      <ColorSlider Grid.Column="2"
+                   Grid.Row="0"
+                   ColorComponent="Alpha"
+                   ColorModel="Hsva"
+                   Orientation="Vertical"
+                   HsvColor="{Binding HsvColor, ElementName=ColorSpectrum2}" />
+      <ColorPreviewer Grid.Column="0"
+                      Grid.ColumnSpan="3"
+                      Grid.Row="1"
+                      ShowAccentColors="True"
+                      HsvColor="{Binding HsvColor, ElementName=ColorSpectrum2}" />
+    </Grid>
+  </Grid>
+</UserControl>

+ 19 - 0
samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs

@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public partial class ColorPickerPage : UserControl
+    {
+        public ColorPickerPage()
+        {
+            InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 10 - 0
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@@ -86,6 +86,16 @@
                         <sys:Exception />
                     </DataValidationErrors.Error>
                 </ComboBox>
+
+                <ComboBox PlaceholderText="Scaled" Width="166" RenderTransformOrigin="0,0">
+                    <ComboBox.RenderTransform>
+                      <ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
+                    </ComboBox.RenderTransform>
+                    <ComboBoxItem>Inline Items</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 2</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 3</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 4</ComboBoxItem>
+                </ComboBox>
             </WrapPanel>
 
             <CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>

+ 2 - 0
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@@ -53,10 +53,12 @@
         <ComboBoxItem>UniformGrid - Horizontal</ComboBoxItem>
       </ComboBox>
       <Button Command="{Binding AddItem}">Add Item</Button>
+      <Button Command="{Binding RemoveItem}">Remove Item</Button>
       <Button Command="{Binding RandomizeHeights}">Randomize Heights</Button>
       <Button Command="{Binding ResetItems}">Reset items</Button>
       <Button x:Name="scrollToLast">Scroll to Last</Button>
       <Button x:Name="scrollToRandom">Scroll to Random</Button>
+      <Button x:Name="scrollToSelected">Scroll to Selected</Button>
     </StackPanel>
     <Border BorderThickness="1" BorderBrush="{DynamicResource SystemControlHighlightBaseMediumLowBrush}" Margin="0 0 0 16">
       <ScrollViewer Name="scroller"

+ 10 - 0
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@@ -15,8 +15,10 @@ namespace ControlCatalog.Pages
         private readonly ItemsRepeaterPageViewModel _viewModel;
         private ItemsRepeater _repeater;
         private ScrollViewer _scroller;
+        private int _selectedIndex;
         private Button _scrollToLast;
         private Button _scrollToRandom;
+        private Button _scrollToSelected;
         private Random _random = new Random(0);
 
         public ItemsRepeaterPage()
@@ -26,10 +28,12 @@ namespace ControlCatalog.Pages
             _scroller = this.FindControl<ScrollViewer>("scroller");
             _scrollToLast = this.FindControl<Button>("scrollToLast");
             _scrollToRandom = this.FindControl<Button>("scrollToRandom");
+            _scrollToSelected = this.FindControl<Button>("scrollToSelected");
             _repeater.PointerPressed += RepeaterClick;
             _repeater.KeyDown += RepeaterOnKeyDown;
             _scrollToLast.Click += scrollToLast_Click;
             _scrollToRandom.Click += scrollToRandom_Click;
+            _scrollToSelected.Click += scrollToSelected_Click;
             DataContext = _viewModel = new ItemsRepeaterPageViewModel();
         }
 
@@ -121,6 +125,7 @@ namespace ControlCatalog.Pages
         {
             var item = (e.Source as TextBlock)?.DataContext as ItemsRepeaterPageViewModel.Item;
             _viewModel.SelectedItem = item;
+            _selectedIndex = _viewModel.Items.IndexOf(item);
         }
 
         private void RepeaterOnKeyDown(object sender, KeyEventArgs e)
@@ -140,5 +145,10 @@ namespace ControlCatalog.Pages
         {
             ScrollTo(_random.Next(_viewModel.Items.Count - 1));
         }
+
+        private void scrollToSelected_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            ScrollTo(_selectedIndex);
+        }
     }
 }

+ 13 - 0
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@@ -31,6 +31,19 @@ namespace ControlCatalog.ViewModels
             Items.Insert(index + 1, new Item(index + 1) { Text = $"New Item {_newItemIndex++}" });
         }
 
+        public void RemoveItem()
+        {
+            if (SelectedItem is not null)
+            {
+                Items.Remove(SelectedItem);
+                SelectedItem = null;
+            }
+            else if (Items.Count > 0)
+            {
+                Items.RemoveAt(Items.Count - 1);
+            }
+        }
+
         public void RandomizeHeights()
         {
             var random = new Random();

+ 1 - 1
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels
         private WindowState _windowState;
         private WindowState[] _windowStates;
         private int _transparencyLevel;
-        private ExtendClientAreaChromeHints _chromeHints;
+        private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
         private bool _extendClientAreaEnabled;
         private bool _systemTitleBarEnabled;        
         private bool _preferSystemChromeEnabled;

+ 2 - 3
samples/SampleControls/HamburgerMenu/HamburgerMenu.cs

@@ -43,14 +43,13 @@ namespace ControlSamples
             _splitView = e.NameScope.Find<SplitView>("PART_NavigationPane");
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             base.OnPropertyChanged(change);
 
             if (change.Property == BoundsProperty && _splitView is not null)
             {
-                var oldBounds = change.OldValue.GetValueOrDefault<Rect>();
-                var newBounds = change.NewValue.GetValueOrDefault<Rect>();
+                var (oldBounds, newBounds) = change.GetOldAndNewValue<Rect>();
                 EnsureSplitViewMode(oldBounds, newBounds);
             }
         }

+ 1 - 0
samples/Sandbox/Sandbox.csproj

@@ -8,6 +8,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
   </ItemGroup>
   

+ 4 - 3
src/Avalonia.Base/Animation/Animatable.cs

@@ -87,12 +87,13 @@ namespace Avalonia.Animation
             }
         }
 
-        protected sealed override void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected sealed override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)
         {
             if (change.Property == TransitionsProperty && change.IsEffectiveValueChange)
             {
-                var oldTransitions = change.OldValue.GetValueOrDefault<Transitions>();
-                var newTransitions = change.NewValue.GetValueOrDefault<Transitions>();
+                var e = (AvaloniaPropertyChangedEventArgs<Transitions?>)change;
+                var oldTransitions = e.OldValue.GetValueOrDefault();
+                var newTransitions = e.NewValue.GetValueOrDefault();
 
                 // When transitions are replaced, we add the new transitions before removing the old
                 // transitions, so that when the old transition being disposed causes the value to

+ 48 - 29
src/Avalonia.Base/AvaloniaObject.cs

@@ -5,6 +5,7 @@ using Avalonia.Data;
 using Avalonia.Diagnostics;
 using Avalonia.Logging;
 using Avalonia.PropertyStore;
+using Avalonia.Reactive;
 using Avalonia.Threading;
 
 namespace Avalonia
@@ -15,13 +16,13 @@ namespace Avalonia
     /// <remarks>
     /// This class is analogous to DependencyObject in WPF.
     /// </remarks>
-    public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IValueSink
+    public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
     {
-        private IAvaloniaObject? _inheritanceParent;
+        private AvaloniaObject? _inheritanceParent;
         private List<IDisposable>? _directBindings;
         private PropertyChangedEventHandler? _inpcChanged;
         private EventHandler<AvaloniaPropertyChangedEventArgs>? _propertyChanged;
-        private List<IAvaloniaObject>? _inheritanceChildren;
+        private List<AvaloniaObject>? _inheritanceChildren;
         private ValueStore? _values;
         private bool _batchUpdate;
 
@@ -58,7 +59,7 @@ namespace Avalonia
         /// <value>
         /// The inheritance parent.
         /// </value>
-        protected IAvaloniaObject? InheritanceParent
+        protected AvaloniaObject? InheritanceParent
         {
             get
             {
@@ -320,14 +321,14 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
         /// <param name="priority">The priority of the value.</param>
-        public void SetValue(
+        public IDisposable? SetValue(
             AvaloniaProperty property,
             object? value,
             BindingPriority priority = BindingPriority.LocalValue)
         {
             property = property ?? throw new ArgumentNullException(nameof(property));
 
-            property.RouteSetValue(this, value, priority);
+            return property.RouteSetValue(this, value, priority);
         }
 
         /// <summary>
@@ -385,6 +386,26 @@ namespace Avalonia
             SetDirectValueUnchecked(property, value);
         }
 
+        /// <summary>
+        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <param name="source">The observable.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>
+        /// A disposable which can be used to terminate the binding.
+        /// </returns>
+        public IDisposable Bind(
+            AvaloniaProperty property,
+            IObservable<object?> source,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            property = property ?? throw new ArgumentNullException(nameof(property));
+            source = source ?? throw new ArgumentNullException(nameof(source));
+
+            return property.RouteBind(this, source.ToBindingValue(), priority);
+        }
+
         /// <summary>
         /// Binds a <see cref="AvaloniaProperty"/> to an observable.
         /// </summary>
@@ -445,9 +466,8 @@ namespace Avalonia
         /// <summary>
         /// Coerces the specified <see cref="AvaloniaProperty"/>.
         /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="property">The property.</param>
-        public void CoerceValue<T>(StyledPropertyBase<T> property)
+        public void CoerceValue(AvaloniaProperty property)
         {
             _values?.CoerceValue(property);
         }
@@ -475,19 +495,19 @@ namespace Avalonia
         }
 
         /// <inheritdoc/>
-        void IAvaloniaObject.AddInheritanceChild(IAvaloniaObject child)
+        internal void AddInheritanceChild(AvaloniaObject child)
         {
-            _inheritanceChildren ??= new List<IAvaloniaObject>();
+            _inheritanceChildren ??= new List<AvaloniaObject>();
             _inheritanceChildren.Add(child);
         }
         
         /// <inheritdoc/>
-        void IAvaloniaObject.RemoveInheritanceChild(IAvaloniaObject child)
+        internal void RemoveInheritanceChild(AvaloniaObject child)
         {
             _inheritanceChildren?.Remove(child);
         }
 
-        void IAvaloniaObject.InheritedPropertyChanged<T>(
+        internal void InheritedPropertyChanged<T>(
             AvaloniaProperty<T> property,
             Optional<T> oldValue,
             Optional<T> newValue)
@@ -504,7 +524,7 @@ namespace Avalonia
             return _propertyChanged?.GetInvocationList();
         }
 
-        void IValueSink.ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        internal void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
             var property = (StyledPropertyBase<T>)change.Property;
 
@@ -543,7 +563,7 @@ namespace Avalonia
             }
         }
 
-        void IValueSink.Completed<T>(
+        internal void Completed<T>(
             StyledPropertyBase<T> property,
             IPriorityValueEntry entry,
             Optional<T> oldValue) 
@@ -554,7 +574,7 @@ namespace Avalonia
                 oldValue,
                 default,
                 BindingPriority.Unset);
-            ((IValueSink)this).ValueChanged(change);
+            ValueChanged(change);
         }
 
         /// <summary>
@@ -565,14 +585,11 @@ namespace Avalonia
         /// <param name="oldParent">The old inheritance parent.</param>
         internal void InheritanceParentChanged<T>(
             StyledPropertyBase<T> property,
-            IAvaloniaObject? oldParent)
+            AvaloniaObject? oldParent)
         {
-            var oldValue = oldParent switch
-            {
-                AvaloniaObject o => o.GetValueOrInheritedOrDefault(property),
-                null => property.GetDefaultValue(GetType()),
-                _ => oldParent.GetValue(property)
-            };
+            var oldValue = oldParent is not null ?
+                oldParent.GetValueOrInheritedOrDefault(property) :
+                property.GetDefaultValue(GetType());
 
             var newValue = GetInheritedOrDefault(property);
 
@@ -629,10 +646,12 @@ namespace Avalonia
         /// enabled.
         /// </summary>
         /// <param name="property">The property.</param>
-        /// <param name="value">The new binding value for the property.</param>
-        protected virtual void UpdateDataValidation<T>(
-            AvaloniaProperty<T> property,
-            BindingValue<T> value)
+        /// <param name="state">The current data binding state.</param>
+        /// <param name="error">The current data binding error, if any.</param>
+        protected virtual void UpdateDataValidation(
+            AvaloniaProperty property,
+            BindingValueType state,
+            Exception? error)
         {
         }
 
@@ -640,7 +659,7 @@ namespace Avalonia
         /// Called when a avalonia property changes on the object.
         /// </summary>
         /// <param name="change">The property change details.</param>
-        protected virtual void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected virtual void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)
         {
             if (change.IsEffectiveValueChange)
             {
@@ -652,7 +671,7 @@ namespace Avalonia
         /// Called when a avalonia property changes on the object.
         /// </summary>
         /// <param name="change">The property change details.</param>
-        protected virtual void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected virtual void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
         }
 
@@ -843,7 +862,7 @@ namespace Avalonia
 
             if (metadata.EnableDataValidation == true)
             {
-                UpdateDataValidation(property, value);
+                UpdateDataValidation(property, value.Type, value.Error);
             }
         }
 

+ 43 - 191
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -25,7 +25,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets an observable for a <see cref="AvaloniaProperty"/>.
+        /// Gets an observable for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <param name="property">The property.</param>
@@ -44,7 +44,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets an observable for a <see cref="AvaloniaProperty"/>.
+        /// Gets an observable for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <typeparam name="T">The property type.</typeparam>
@@ -64,7 +64,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets an observable for a <see cref="AvaloniaProperty"/>.
+        /// Gets an observable for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <param name="property">The property.</param>
@@ -85,7 +85,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets an observable for a <see cref="AvaloniaProperty"/>.
+        /// Gets an observable for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <typeparam name="T">The property type.</typeparam>
@@ -128,7 +128,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets a subject for a <see cref="AvaloniaProperty"/>.
+        /// Gets a subject for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
         /// <param name="property">The property.</param>
@@ -150,7 +150,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Gets a subject for a <see cref="AvaloniaProperty"/>.
+        /// Gets a subject for an <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <typeparam name="T">The property type.</typeparam>
         /// <param name="o">The object.</param>
@@ -230,30 +230,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
-        /// </summary>
-        /// <param name="target">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <param name="source">The observable.</param>
-        /// <param name="priority">The priority of the binding.</param>
-        /// <returns>
-        /// A disposable which can be used to terminate the binding.
-        /// </returns>
-        public static IDisposable Bind(
-            this IAvaloniaObject target,
-            AvaloniaProperty property,
-            IObservable<BindingValue<object?>> source,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
-            source = source ?? throw new ArgumentNullException(nameof(source));
-
-            return property.RouteBind(target, source, priority);
-        }
-
-        /// <summary>
-        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
+        /// Binds an <see cref="AvaloniaProperty"/> to an observable.
         /// </summary>
         /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="target">The object.</param>
@@ -273,42 +250,22 @@ namespace Avalonia
             property = property ?? throw new ArgumentNullException(nameof(property));
             source = source ?? throw new ArgumentNullException(nameof(source));
 
-            return property switch
+            if (target is AvaloniaObject ao)
             {
-                StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
-                DirectPropertyBase<T> direct => target.Bind(direct, source),
-                _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
-            };
-        }
+                return property switch
+                {
+                    StyledPropertyBase<T> styled => ao.Bind(styled, source, priority),
+                    DirectPropertyBase<T> direct => ao.Bind(direct, source),
+                    _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
+                };
+            }
 
-        /// <summary>
-        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
-        /// </summary>
-        /// <param name="target">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <param name="source">The observable.</param>
-        /// <param name="priority">The priority of the binding.</param>
-        /// <returns>
-        /// A disposable which can be used to terminate the binding.
-        /// </returns>
-        public static IDisposable Bind(
-            this IAvaloniaObject target,
-            AvaloniaProperty property,
-            IObservable<object?> source,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
-            source = source ?? throw new ArgumentNullException(nameof(source));
+            throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
 
-            return target.Bind(
-                property,
-                source.ToBindingValue(),
-                priority);
         }
 
         /// <summary>
-        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
+        /// Binds an <see cref="AvaloniaProperty"/> to an observable.
         /// </summary>
         /// <param name="target">The object.</param>
         /// <param name="property">The property.</param>
@@ -334,7 +291,7 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Binds a property on an <see cref="IAvaloniaObject"/> to an <see cref="IBinding"/>.
+        /// Binds a property on an <see cref="AvaloniaObject"/> to an <see cref="IBinding"/>.
         /// </summary>
         /// <param name="target">The object.</param>
         /// <param name="property">The property to bind.</param>
@@ -374,56 +331,6 @@ namespace Avalonia
             }
         }
 
-        /// <summary>
-        /// Clears a <see cref="AvaloniaProperty"/>'s local value.
-        /// </summary>
-        /// <param name="target">The object.</param>
-        /// <param name="property">The property.</param>
-        public static void ClearValue(this IAvaloniaObject target, AvaloniaProperty property)
-        {
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            property.RouteClearValue(target);
-        }
-
-        /// <summary>
-        /// Clears a <see cref="AvaloniaProperty"/>'s local value.
-        /// </summary>
-        /// <param name="target">The object.</param>
-        /// <param name="property">The property.</param>
-        public static void ClearValue<T>(this IAvaloniaObject target, AvaloniaProperty<T> property)
-        {
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            switch (property)
-            {
-                case StyledPropertyBase<T> styled:
-                    target.ClearValue(styled);
-                    break;
-                case DirectPropertyBase<T> direct:
-                    target.ClearValue(direct);
-                    break;
-                default:
-                    throw new NotSupportedException("Unsupported AvaloniaProperty type.");
-            }
-        }
-
-        /// <summary>
-        /// Gets a <see cref="AvaloniaProperty"/> value.
-        /// </summary>
-        /// <param name="target">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <returns>The value.</returns>
-        public static object? GetValue(this IAvaloniaObject target, AvaloniaProperty property)
-        {
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            return property.RouteGetValue(target);
-        }
-
         /// <summary>
         /// Gets a <see cref="AvaloniaProperty"/> value.
         /// </summary>
@@ -436,12 +343,18 @@ namespace Avalonia
             target = target ?? throw new ArgumentNullException(nameof(target));
             property = property ?? throw new ArgumentNullException(nameof(property));
 
-            return property switch
+            if (target is AvaloniaObject ao)
             {
-                StyledPropertyBase<T> styled => target.GetValue(styled),
-                DirectPropertyBase<T> direct => target.GetValue(direct),
-                _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
-            };
+                return property switch
+                {
+                    StyledPropertyBase<T> styled => ao.GetValue(styled),
+                    DirectPropertyBase<T> direct => ao.GetValue(direct),
+                    _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
+                };
+
+            }
+
+            throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
         }
 
         /// <summary>
@@ -456,7 +369,7 @@ namespace Avalonia
         /// <see cref="AvaloniaProperty.UnsetValue"/>. Note that this method does not return
         /// property values that come from inherited or default values.
         /// 
-        /// For direct properties returns <see cref="GetValue(IAvaloniaObject, AvaloniaProperty)"/>.
+        /// For direct properties returns the current value of the property.
         /// </remarks>
         public static object? GetBaseValue(
             this IAvaloniaObject target,
@@ -466,7 +379,9 @@ namespace Avalonia
             target = target ?? throw new ArgumentNullException(nameof(target));
             property = property ?? throw new ArgumentNullException(nameof(property));
 
-            return property.RouteGetBaseValue(target, maxPriority);
+            if (target is AvaloniaObject ao)
+                return property.RouteGetBaseValue(ao, maxPriority);
+            throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
         }
 
         /// <summary>
@@ -481,8 +396,7 @@ namespace Avalonia
         /// <see cref="Optional{T}.Empty"/>. Note that this method does not return property values
         /// that come from inherited or default values.
         /// 
-        /// For direct properties returns
-        /// <see cref="IAvaloniaObject.GetValue{T}(DirectPropertyBase{T})"/>.
+        /// For direct properties returns the current value of the property.
         /// </remarks>
         public static Optional<T> GetBaseValue<T>(
             this IAvaloniaObject target,
@@ -492,69 +406,18 @@ namespace Avalonia
             target = target ?? throw new ArgumentNullException(nameof(target));
             property = property ?? throw new ArgumentNullException(nameof(property));
 
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            return property switch
+            if (target is AvaloniaObject ao)
             {
-                StyledPropertyBase<T> styled => target.GetBaseValue(styled, maxPriority),
-                DirectPropertyBase<T> direct => target.GetValue(direct),
-                _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
-            };
-        }
-
-        /// <summary>
-        /// Sets a <see cref="AvaloniaProperty"/> value.
-        /// </summary>
-        /// <param name="target">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <param name="value">The value.</param>
-        /// <param name="priority">The priority of the value.</param>
-        /// <returns>
-        /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
-        /// </returns>
-        public static IDisposable? SetValue(
-            this IAvaloniaObject target,
-            AvaloniaProperty property,
-            object? value,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            return property.RouteSetValue(target, value, priority);
-        }
-
-        /// <summary>
-        /// Sets a <see cref="AvaloniaProperty"/> value.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="target">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <param name="value">The value.</param>
-        /// <param name="priority">The priority of the value.</param>
-        /// <returns>
-        /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
-        /// </returns>
-        public static IDisposable? SetValue<T>(
-            this IAvaloniaObject target,
-            AvaloniaProperty<T> property,
-            T value,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
+                return property switch
+                {
+                    StyledPropertyBase<T> styled => ao.GetBaseValue(styled, maxPriority),
+                    DirectPropertyBase<T> direct => ao.GetValue(direct),
+                    _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
+                };
 
-            switch (property)
-            {
-                case StyledPropertyBase<T> styled:
-                    return target.SetValue(styled, value, priority);
-                case DirectPropertyBase<T> direct:
-                    target.SetValue(direct, value);
-                    return null;
-                default:
-                    throw new NotSupportedException("Unsupported AvaloniaProperty type.");
             }
+
+            throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
         }
 
         /// <summary>
@@ -622,17 +485,6 @@ namespace Avalonia
             return observable.Subscribe(e => SubscribeAdapter(e, handler));
         }
 
-        /// <summary>
-        /// Gets a description of a property that van be used in observables.
-        /// </summary>
-        /// <param name="o">The object.</param>
-        /// <param name="property">The property</param>
-        /// <returns>The description.</returns>
-        private static string GetDescription(IAvaloniaObject o, AvaloniaProperty property)
-        {
-            return $"{o.GetType().Name}.{property.Name}";
-        }
-
         /// <summary>
         /// Observer method for <see cref="AddClassHandler{TTarget}(IObservable{AvaloniaPropertyChangedEventArgs},
         /// Func{TTarget, Action{AvaloniaPropertyChangedEventArgs}})"/>.

+ 8 - 15
src/Avalonia.Base/AvaloniaProperty.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using Avalonia.Data;
 using Avalonia.Data.Core;
+using Avalonia.Styling;
 using Avalonia.Utilities;
 
 namespace Avalonia
@@ -454,33 +455,24 @@ namespace Avalonia
             return Name;
         }
 
-        /// <summary>
-        /// Uses the visitor pattern to resolve an untyped property to a typed property.
-        /// </summary>
-        /// <typeparam name="TData">The type of user data passed.</typeparam>
-        /// <param name="visitor">The visitor which will accept the typed property.</param>
-        /// <param name="data">The user data to pass.</param>
-        public abstract void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
-            where TData : struct;
-
         /// <summary>
         /// Routes an untyped ClearValue call to a typed call.
         /// </summary>
         /// <param name="o">The object instance.</param>
-        internal abstract void RouteClearValue(IAvaloniaObject o);
+        internal abstract void RouteClearValue(AvaloniaObject o);
 
         /// <summary>
         /// Routes an untyped GetValue call to a typed call.
         /// </summary>
         /// <param name="o">The object instance.</param>
-        internal abstract object? RouteGetValue(IAvaloniaObject o);
+        internal abstract object? RouteGetValue(AvaloniaObject o);
 
         /// <summary>
         /// Routes an untyped GetBaseValue call to a typed call.
         /// </summary>
         /// <param name="o">The object instance.</param>
         /// <param name="maxPriority">The maximum priority for the value.</param>
-        internal abstract object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority);
+        internal abstract object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority);
 
         /// <summary>
         /// Routes an untyped SetValue call to a typed call.
@@ -492,7 +484,7 @@ namespace Avalonia
         /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
         /// </returns>
         internal abstract IDisposable? RouteSetValue(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             object? value,
             BindingPriority priority);
 
@@ -503,11 +495,12 @@ namespace Avalonia
         /// <param name="source">The binding source.</param>
         /// <param name="priority">The priority.</param>
         internal abstract IDisposable RouteBind(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             IObservable<BindingValue<object?>> source,
             BindingPriority priority);
 
-        internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, IAvaloniaObject? oldParent);
+        internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent);
+        internal abstract ISetterInstance CreateSetterInstance(IStyleable target, object? value);
 
         /// <summary>
         /// Overrides the metadata for the property on the specified type.

+ 43 - 0
src/Avalonia.Base/AvaloniaPropertyChangedExtensions.cs

@@ -0,0 +1,43 @@
+namespace Avalonia
+{
+    /// <summary>
+    /// Provides extensions for <see cref="AvaloniaPropertyChangedEventArgs"/>.
+    /// </summary>
+    public static class AvaloniaPropertyChangedExtensions
+    {
+        /// <summary>
+        /// Gets a typed value from <see cref="AvaloniaPropertyChangedEventArgs.OldValue"/>.
+        /// </summary>
+        /// <typeparam name="T">The value type.</typeparam>
+        /// <param name="e">The event args.</param>
+        /// <returns>The value.</returns>
+        public static T GetOldValue<T>(this AvaloniaPropertyChangedEventArgs e)
+        {
+            return ((AvaloniaPropertyChangedEventArgs<T>)e).OldValue.GetValueOrDefault()!;
+        }
+
+        /// <summary>
+        /// Gets a typed value from <see cref="AvaloniaPropertyChangedEventArgs.NewValue"/>.
+        /// </summary>
+        /// <typeparam name="T">The value type.</typeparam>
+        /// <param name="e">The event args.</param>
+        /// <returns>The value.</returns>
+        public static T GetNewValue<T>(this AvaloniaPropertyChangedEventArgs e)
+        {
+            return ((AvaloniaPropertyChangedEventArgs<T>)e).NewValue.GetValueOrDefault()!;
+        }
+
+        /// <summary>
+        /// Gets a typed value from <see cref="AvaloniaPropertyChangedEventArgs.OldValue"/> and
+        /// <see cref="AvaloniaPropertyChangedEventArgs.NewValue"/>.
+        /// </summary>
+        /// <typeparam name="T">The value type.</typeparam>
+        /// <param name="e">The event args.</param>
+        /// <returns>The value.</returns>
+        public static (T oldValue, T newValue) GetOldAndNewValue<T>(this AvaloniaPropertyChangedEventArgs e)
+        {
+            var ev = (AvaloniaPropertyChangedEventArgs<T>)e;
+            return (ev.OldValue.GetValueOrDefault()!, ev.NewValue.GetValueOrDefault()!);
+        }
+    }
+}

+ 6 - 0
src/Avalonia.Base/Collections/AvaloniaList.cs

@@ -394,7 +394,13 @@ namespace Avalonia.Collections
                         } while (en.MoveNext());
 
                         if (notificationItems is not null)
+                        {
                             NotifyAdd(notificationItems, index);
+                        }
+                        else
+                        {
+                            NotifyCountChanged();
+                        }
                     }
                 }
             }

+ 4 - 7
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@@ -40,19 +40,16 @@ namespace Avalonia.Controls
             control = control ?? throw new ArgumentNullException(nameof(control));
             key = key ?? throw new ArgumentNullException(nameof(key));
 
-            IResourceHost? current = control;
+            IResourceNode? current = control;
 
             while (current != null)
             {
-                if (current is IResourceHost host)
+                if (current.TryGetResource(key, out value))
                 {
-                    if (host.TryGetResource(key, out value))
-                    {
-                        return true;
-                    }
+                    return true;
                 }
 
-                current = (current as IStyledElement)?.StylingParent as IResourceHost;
+                current = (current as IStyledElement)?.StylingParent as IResourceNode;
             }
 
             value = null;

+ 12 - 7
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Data.Core.Plugins
             new Dictionary<(Type, string), PropertyInfo?>();
 
         /// <inheritdoc/>
-        public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
+        public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null;
 
         /// <summary>
         /// Starts monitoring the value of a property on an object.
@@ -36,7 +36,7 @@ namespace Avalonia.Data.Core.Plugins
             if (!reference.TryGetTarget(out var instance) || instance is null)
                 return null;
 
-            var p = GetFirstPropertyWithName(instance.GetType(), propertyName);
+            var p = GetFirstPropertyWithName(instance, propertyName);
 
             if (p != null)
             {
@@ -50,8 +50,16 @@ namespace Avalonia.Data.Core.Plugins
             }
         }
 
-        private PropertyInfo? GetFirstPropertyWithName(Type type, string propertyName)
+        private const BindingFlags PropertyBindingFlags =
+            BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
+        
+        private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName)
         {
+            if (instance is IReflectableType reflectableType)
+                return reflectableType.GetTypeInfo().GetProperty(propertyName, PropertyBindingFlags);
+
+            var type = instance.GetType();
+            
             var key = (type, propertyName);
 
             if (!_propertyLookup.TryGetValue(key, out var propertyInfo))
@@ -66,10 +74,7 @@ namespace Avalonia.Data.Core.Plugins
         {
             PropertyInfo? found = null;
 
-            const BindingFlags bindingFlags =
-                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
-
-            var properties = type.GetProperties(bindingFlags);
+            var properties = type.GetProperties(PropertyBindingFlags);
 
             foreach (PropertyInfo propertyInfo in properties)
             {

+ 32 - 12
src/Avalonia.Base/DirectPropertyBase.cs

@@ -1,6 +1,7 @@
 using System;
 using Avalonia.Data;
 using Avalonia.Reactive;
+using Avalonia.Styling;
 using Avalonia.Utilities;
 
 namespace Avalonia
@@ -121,31 +122,25 @@ namespace Avalonia
         }
 
         /// <inheritdoc/>
-        public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
-        {
-            visitor.Visit(this, ref data);
-        }
-
-        /// <inheritdoc/>
-        internal override void RouteClearValue(IAvaloniaObject o)
+        internal override void RouteClearValue(AvaloniaObject o)
         {
             o.ClearValue<TValue>(this);
         }
 
         /// <inheritdoc/>
-        internal override object? RouteGetValue(IAvaloniaObject o)
+        internal override object? RouteGetValue(AvaloniaObject o)
         {
             return o.GetValue<TValue>(this);
         }
 
-        internal override object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
+        internal override object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority)
         {
             return o.GetValue<TValue>(this);
         }
 
         /// <inheritdoc/>
         internal override IDisposable? RouteSetValue(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             object? value,
             BindingPriority priority)
         {
@@ -169,7 +164,7 @@ namespace Avalonia
 
         /// <inheritdoc/>
         internal override IDisposable RouteBind(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             IObservable<BindingValue<object?>> source,
             BindingPriority priority)
         {
@@ -177,9 +172,34 @@ namespace Avalonia
             return o.Bind<TValue>(this, adapter);
         }
 
-        internal override void RouteInheritanceParentChanged(AvaloniaObject o, IAvaloniaObject? oldParent)
+        internal override void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent)
         {
             throw new NotSupportedException("Direct properties do not support inheritance.");
         }
+
+        internal override ISetterInstance CreateSetterInstance(IStyleable target, object? value)
+        {
+            if (value is IBinding binding)
+            {
+                return new PropertySetterBindingInstance<TValue>(
+                    target,
+                    this,
+                    binding);
+            }
+            else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
+            {
+                return new PropertySetterLazyInstance<TValue>(
+                    target,
+                    this,
+                    () => (TValue)template.Build());
+            }
+            else
+            {
+                return new PropertySetterInstance<TValue>(
+                    target,
+                    this,
+                    (TValue)value!);
+            }
+        }
     }
 }

+ 1 - 1
src/Avalonia.Base/GeometryGroup.cs

@@ -68,7 +68,7 @@ namespace Avalonia.Media
             return null;
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             base.OnPropertyChanged(change);
 

+ 9 - 95
src/Avalonia.Base/IAvaloniaObject.cs

@@ -17,42 +17,14 @@ namespace Avalonia
         /// Clears an <see cref="AvaloniaProperty"/>'s local value.
         /// </summary>
         /// <param name="property">The property.</param>
-        void ClearValue<T>(StyledPropertyBase<T> property);
-
-        /// <summary>
-        /// Clears an <see cref="AvaloniaProperty"/>'s local value.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        void ClearValue<T>(DirectPropertyBase<T> property);
-
-        /// <summary>
-        /// Gets a <see cref="AvaloniaProperty"/> value.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <returns>The value.</returns>
-        T GetValue<T>(StyledPropertyBase<T> property);
+        void ClearValue(AvaloniaProperty property);
 
         /// <summary>
         /// Gets a <see cref="AvaloniaProperty"/> value.
         /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="property">The property.</param>
         /// <returns>The value.</returns>
-        T GetValue<T>(DirectPropertyBase<T> property);
-
-        /// <summary>
-        /// Gets an <see cref="AvaloniaProperty"/> base value.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="maxPriority">The maximum priority for the value.</param>
-        /// <remarks>
-        /// Gets the value of the property, if set on this object with a priority equal or lower to
-        /// <paramref name="maxPriority"/>, otherwise <see cref="Optional{T}.Empty"/>. Note that
-        /// this method does not return property values that come from inherited or default values.
-        /// </remarks>
-        Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property, BindingPriority maxPriority);
+        object? GetValue(AvaloniaProperty property);
 
         /// <summary>
         /// Checks whether a <see cref="AvaloniaProperty"/> is animating.
@@ -71,93 +43,35 @@ namespace Avalonia
         /// <summary>
         /// Sets a <see cref="AvaloniaProperty"/> value.
         /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
         /// <param name="priority">The priority of the value.</param>
         /// <returns>
         /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
         /// </returns>
-        IDisposable? SetValue<T>(
-            StyledPropertyBase<T> property,
-            T value,
+        IDisposable? SetValue(
+            AvaloniaProperty property,
+            object? value,
             BindingPriority priority = BindingPriority.LocalValue);
 
-        /// <summary>
-        /// Sets a <see cref="AvaloniaProperty"/> value.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="value">The value.</param>
-        void SetValue<T>(DirectPropertyBase<T> property, T value);
-
         /// <summary>
         /// Binds a <see cref="AvaloniaProperty"/> to an observable.
         /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="property">The property.</param>
         /// <param name="source">The observable.</param>
         /// <param name="priority">The priority of the binding.</param>
         /// <returns>
         /// A disposable which can be used to terminate the binding.
         /// </returns>
-        IDisposable Bind<T>(
-            StyledPropertyBase<T> property,
-            IObservable<BindingValue<T>> source,
+        IDisposable Bind(
+            AvaloniaProperty property,
+            IObservable<object?> source,
             BindingPriority priority = BindingPriority.LocalValue);
 
-        /// <summary>
-        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="source">The observable.</param>
-        /// <returns>
-        /// A disposable which can be used to terminate the binding.
-        /// </returns>
-        IDisposable Bind<T>(
-            DirectPropertyBase<T> property,
-            IObservable<BindingValue<T>> source);
-
         /// <summary>
         /// Coerces the specified <see cref="AvaloniaProperty"/>.
         /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="property">The property.</param>
-        void CoerceValue<T>(StyledPropertyBase<T> property);
-
-        /// <summary>
-        /// Registers an object as an inheritance child.
-        /// </summary>
-        /// <param name="child">The inheritance child.</param>
-        /// <remarks>
-        /// Inheritance children will receive a call to
-        /// <see cref="InheritedPropertyChanged{T}(AvaloniaProperty{T}, Optional{T}, Optional{T})"/>
-        /// when an inheritable property value changes on the parent.
-        /// </remarks>
-        void AddInheritanceChild(IAvaloniaObject child);
-
-        /// <summary>
-        /// Unregisters an object as an inheritance child.
-        /// </summary>
-        /// <param name="child">The inheritance child.</param>
-        /// <remarks>
-        /// Removes an inheritance child that was added by a call to
-        /// <see cref="AddInheritanceChild(IAvaloniaObject)"/>.
-        /// </remarks>
-        void RemoveInheritanceChild(IAvaloniaObject child);
-
-        /// <summary>
-        /// Called when an inheritable property changes on an object registered as an inheritance
-        /// parent.
-        /// </summary>
-        /// <typeparam name="T">The type of the value.</typeparam>
-        /// <param name="property">The property that has changed.</param>
-        /// <param name="oldValue"></param>
-        /// <param name="newValue"></param>
-        void InheritedPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            Optional<T> newValue);
+        void CoerceValue(AvaloniaProperty property);
     }
 }

+ 4 - 4
src/Avalonia.Base/Input/InputElement.cs

@@ -601,21 +601,21 @@ namespace Avalonia.Input
         {
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             base.OnPropertyChanged(change);
 
             if (change.Property == IsFocusedProperty)
             {
-                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>(), null);
+                UpdatePseudoClasses(change.GetNewValue<bool>(), null);
             }
             else if (change.Property == IsPointerOverProperty)
             {
-                UpdatePseudoClasses(null, change.NewValue.GetValueOrDefault<bool>());
+                UpdatePseudoClasses(null, change.GetNewValue<bool>());
             }
             else if (change.Property == IsKeyboardFocusWithinProperty)
             {
-                PseudoClasses.Set(":focus-within", change.NewValue.GetValueOrDefault<bool>());
+                PseudoClasses.Set(":focus-within", change.GetNewValue<bool>());
             }
         }
 

+ 1 - 1
src/Avalonia.Base/Input/KeyboardNavigation.cs

@@ -58,7 +58,7 @@ namespace Avalonia.Input
         /// <returns>The <see cref="KeyboardNavigationMode"/> for the container.</returns>
         public static int GetTabIndex(IInputElement element)
         {
-            return ((IAvaloniaObject)element).GetValue(TabIndexProperty);
+            return ((AvaloniaObject)element).GetValue(TabIndexProperty);
         }
 
         /// <summary>

+ 3 - 2
src/Avalonia.Base/Input/Navigation/TabNavigation.cs

@@ -610,7 +610,7 @@ namespace Avalonia.Input.Navigation
 
         private static IInputElement? GetActiveElement(IInputElement e)
         {
-            return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabOnceActiveElementProperty);
+            return ((AvaloniaObject)e).GetValue(KeyboardNavigation.TabOnceActiveElementProperty);
         }
 
         private static IInputElement GetGroupParent(IInputElement e) => GetGroupParent(e, false);
@@ -655,8 +655,9 @@ namespace Avalonia.Input.Navigation
 
         private static KeyboardNavigationMode GetKeyNavigationMode(IInputElement e)
         {
-            return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabNavigationProperty);
+            return ((AvaloniaObject)e).GetValue(KeyboardNavigation.TabNavigationProperty);
         }
+
         private static bool IsFocusScope(IInputElement e) => FocusManager.GetIsFocusScope(e) || GetParent(e) == null;
         private static bool IsGroup(IInputElement e) => GetKeyNavigationMode(e) != KeyboardNavigationMode.Continue;
 

+ 0 - 24
src/Avalonia.Base/Interactivity/IInteractive.cs

@@ -28,21 +28,6 @@ namespace Avalonia.Interactivity
             RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
             bool handledEventsToo = false);
 
-        /// <summary>
-        /// Adds a handler for the specified routed event.
-        /// </summary>
-        /// <typeparam name="TEventArgs">The type of the event's args.</typeparam>
-        /// <param name="routedEvent">The routed event.</param>
-        /// <param name="handler">The handler.</param>
-        /// <param name="routes">The routing strategies to listen to.</param>
-        /// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
-        /// <returns>A disposable that terminates the event subscription.</returns>
-        void AddHandler<TEventArgs>(
-            RoutedEvent<TEventArgs> routedEvent,
-            EventHandler<TEventArgs> handler,
-            RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
-            bool handledEventsToo = false) where TEventArgs : RoutedEventArgs;
-
         /// <summary>
         /// Removes a handler for the specified routed event.
         /// </summary>
@@ -50,15 +35,6 @@ namespace Avalonia.Interactivity
         /// <param name="handler">The handler.</param>
         void RemoveHandler(RoutedEvent routedEvent, Delegate handler);
 
-        /// <summary>
-        /// Removes a handler for the specified routed event.
-        /// </summary>
-        /// <typeparam name="TEventArgs">The type of the event's args.</typeparam>
-        /// <param name="routedEvent">The routed event.</param>
-        /// <param name="handler">The handler.</param>
-        void RemoveHandler<TEventArgs>(RoutedEvent<TEventArgs> routedEvent, EventHandler<TEventArgs> handler)
-            where TEventArgs : RoutedEventArgs;
-
         /// <summary>
         /// Adds the object's handlers for a routed event to an event route.
         /// </summary>

+ 56 - 3
src/Avalonia.Base/Layout/LayoutHelper.cs

@@ -52,16 +52,44 @@ namespace Avalonia.Layout
 
         public static Size ArrangeChild(ILayoutable? child, Size availableSize, Thickness padding, Thickness borderThickness)
         {
-            return ArrangeChild(child, availableSize, padding + borderThickness);
+            if (IsParentLayoutRounded(child, out double scale))
+            {
+                padding = RoundLayoutThickness(padding, scale, scale);
+                borderThickness = RoundLayoutThickness(borderThickness, scale, scale);
+            }
+
+            return ArrangeChildInternal(child, availableSize, padding + borderThickness);
         }
 
         public static Size ArrangeChild(ILayoutable? child, Size availableSize, Thickness padding)
+        {
+            if(IsParentLayoutRounded(child, out double scale))
+                padding = RoundLayoutThickness(padding, scale, scale);
+
+            return ArrangeChildInternal(child, availableSize, padding);
+        }
+
+        private static Size ArrangeChildInternal(ILayoutable? child, Size availableSize, Thickness padding)
         {
             child?.Arrange(new Rect(availableSize).Deflate(padding));
 
             return availableSize;
         }
 
+        private static bool IsParentLayoutRounded(ILayoutable? child, out double scale)
+        {
+            var layoutableParent = (ILayoutable?)child?.GetVisualParent();
+
+            if (layoutableParent == null || !((Layoutable)layoutableParent).UseLayoutRounding)
+            {
+                scale = 1.0;
+                return false;
+            }
+
+            scale = GetLayoutScale(layoutableParent);
+            return true;
+        }
+
         /// <summary>
         /// Invalidates measure for given control and all visual children recursively.
         /// </summary>
@@ -126,6 +154,32 @@ namespace Avalonia.Layout
             return new Size(RoundLayoutValue(size.Width, dpiScaleX), RoundLayoutValue(size.Height, dpiScaleY));
         }
 
+        /// <summary>
+        /// Rounds a thickness to integer values for layout purposes, compensating for high DPI screen
+        /// coordinates.
+        /// </summary>
+        /// <param name="thickness">Input thickness.</param>
+        /// <param name="dpiScaleX">DPI along x-dimension.</param>
+        /// <param name="dpiScaleY">DPI along y-dimension.</param>
+        /// <returns>Value of thickness that will be rounded under screen DPI.</returns>
+        /// <remarks>
+        /// This is a layout helper method. It takes DPI into account and also does not return
+        /// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper
+        /// associated with the UseLayoutRounding property and should not be used as a general rounding
+        /// utility.
+        /// </remarks>
+        public static Thickness RoundLayoutThickness(Thickness thickness, double dpiScaleX, double dpiScaleY)
+        {
+            return new Thickness(
+                RoundLayoutValue(thickness.Left, dpiScaleX),
+                RoundLayoutValue(thickness.Top, dpiScaleY),
+                RoundLayoutValue(thickness.Right, dpiScaleX),
+                RoundLayoutValue(thickness.Bottom, dpiScaleY)
+            );
+        }
+
+
+
         /// <summary>
         /// Calculates the value to be used for layout rounding at high DPI.
         /// </summary>
@@ -163,8 +217,7 @@ namespace Avalonia.Layout
 
             return newValue;
         }
-
-
+        
         /// <summary>
         /// Calculates the min and max height for a control. Ported from WPF.
         /// </summary>

+ 12 - 2
src/Avalonia.Base/Layout/Layoutable.cs

@@ -643,17 +643,27 @@ namespace Avalonia.Layout
         {
             if (IsVisible)
             {
+                var useLayoutRounding = UseLayoutRounding;
+                var scale = LayoutHelper.GetLayoutScale(this);
+
                 var margin = Margin;
                 var originX = finalRect.X + margin.Left;
                 var originY = finalRect.Y + margin.Top;
+
+                // Margin has to be treated separately because the layout rounding function is not linear
+                // f(a + b) != f(a) + f(b)
+                // If the margin isn't pre-rounded some sizes will be offset by 1 pixel in certain scales.
+                if (useLayoutRounding)
+                {
+                    margin = LayoutHelper.RoundLayoutThickness(margin, scale, scale);
+                }
+
                 var availableSizeMinusMargins = new Size(
                     Math.Max(0, finalRect.Width - margin.Left - margin.Right),
                     Math.Max(0, finalRect.Height - margin.Top - margin.Bottom));
                 var horizontalAlignment = HorizontalAlignment;
                 var verticalAlignment = VerticalAlignment;
                 var size = availableSizeMinusMargins;
-                var scale = LayoutHelper.GetLayoutScale(this);
-                var useLayoutRounding = UseLayoutRounding;
 
                 if (horizontalAlignment != HorizontalAlignment.Stretch)
                 {

+ 2 - 2
src/Avalonia.Base/Layout/StackLayout.cs

@@ -320,11 +320,11 @@ namespace Avalonia.Layout
             InvalidateLayout();
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             if (change.Property == OrientationProperty)
             {
-                var orientation = change.NewValue.GetValueOrDefault<Orientation>();
+                var orientation = change.GetNewValue<Orientation>();
 
                 //Note: For StackLayout Vertical Orientation means we have a Vertical ScrollOrientation.
                 //Horizontal Orientation means we have a Horizontal ScrollOrientation.

+ 10 - 10
src/Avalonia.Base/Layout/UniformGridLayout.cs

@@ -116,7 +116,7 @@ namespace Avalonia.Layout
         /// Defines the <see cref="MaximumRowsOrColumnsProperty"/> property.
         /// </summary>
         public static readonly StyledProperty<int> MaximumRowsOrColumnsProperty =
-            AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MinItemWidth));
+            AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MaximumRowsOrColumns));
 
         /// <summary>
         /// Defines the <see cref="Orientation"/> property.
@@ -471,11 +471,11 @@ namespace Avalonia.Layout
             gridState.ClearElementOnDataSourceChange(context, args);
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             if (change.Property == OrientationProperty)
             {
-                var orientation = change.NewValue.GetValueOrDefault<Orientation>();
+                var orientation = change.GetNewValue<Orientation>();
 
                 //Note: For UniformGridLayout Vertical Orientation means we have a Horizontal ScrollOrientation. Horizontal Orientation means we have a Vertical ScrollOrientation.
                 //i.e. the properties are the inverse of each other.
@@ -484,31 +484,31 @@ namespace Avalonia.Layout
             }
             else if (change.Property == MinColumnSpacingProperty)
             {
-                _minColumnSpacing = change.NewValue.GetValueOrDefault<double>();
+                _minColumnSpacing = change.GetNewValue<double>();
             }
             else if (change.Property == MinRowSpacingProperty)
             {
-                _minRowSpacing = change.NewValue.GetValueOrDefault<double>();
+                _minRowSpacing = change.GetNewValue<double>();
             }
             else if (change.Property == ItemsJustificationProperty)
             {
-                _itemsJustification = change.NewValue.GetValueOrDefault<UniformGridLayoutItemsJustification>();
+                _itemsJustification = change.GetNewValue<UniformGridLayoutItemsJustification>();
             }
             else if (change.Property == ItemsStretchProperty)
             {
-                _itemsStretch = change.NewValue.GetValueOrDefault<UniformGridLayoutItemsStretch>();
+                _itemsStretch = change.GetNewValue<UniformGridLayoutItemsStretch>();
             }
             else if (change.Property == MinItemWidthProperty)
             {
-                _minItemWidth = change.NewValue.GetValueOrDefault<double>();
+                _minItemWidth = change.GetNewValue<double>();
             }
             else if (change.Property == MinItemHeightProperty)
             {
-                _minItemHeight = change.NewValue.GetValueOrDefault<double>();
+                _minItemHeight = change.GetNewValue<double>();
             }
             else if (change.Property == MaximumRowsOrColumnsProperty)
             {
-                _maximumRowsOrColumns = change.NewValue.GetValueOrDefault<int>();
+                _maximumRowsOrColumns = change.GetNewValue<int>();
             }
 
             InvalidateLayout();

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

@@ -322,7 +322,7 @@ namespace Avalonia.Layout
             return finalSize;
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             base.OnPropertyChanged(change);
 

+ 0 - 51
src/Avalonia.Base/Logging/ILogSink.cs

@@ -26,57 +26,6 @@ namespace Avalonia.Logging
             object? source,
             string messageTemplate);
 
-        /// <summary>
-        /// Logs an event.
-        /// </summary>
-        /// <param name="level">The log event level.</param>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValue0">Message property value.</param>
-        void Log<T0>(
-            LogEventLevel level,
-            string area,
-            object? source,
-            string messageTemplate,
-            T0 propertyValue0);
-
-        /// <summary>
-        /// Logs an event.
-        /// </summary>
-        /// <param name="level">The log event level.</param>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValue0">Message property value.</param>
-        /// <param name="propertyValue1">Message property value.</param>
-        void Log<T0, T1>(
-            LogEventLevel level,
-            string area,
-            object? source,
-            string messageTemplate,
-            T0 propertyValue0,
-            T1 propertyValue1);
-
-        /// <summary>
-        /// Logs an event.
-        /// </summary>
-        /// <param name="level">The log event level.</param>
-        /// <param name="area">The area that the event originates.</param>
-        /// <param name="source">The object from which the event originates.</param>
-        /// <param name="messageTemplate">The message template.</param>
-        /// <param name="propertyValue0">Message property value.</param>
-        /// <param name="propertyValue1">Message property value.</param>
-        /// <param name="propertyValue2">Message property value.</param>
-        void Log<T0, T1, T2>(
-            LogEventLevel level,
-            string area,
-            object? source,
-            string messageTemplate,
-            T0 propertyValue0,
-            T1 propertyValue1,
-            T2 propertyValue2);
-
         /// <summary>
         /// Logs a new event.
         /// </summary>

+ 3 - 35
src/Avalonia.Base/Logging/TraceLogSink.cs

@@ -28,31 +28,7 @@ namespace Avalonia.Logging
         {
             if (IsEnabled(level, area))
             {
-                Trace.WriteLine(Format<object, object, object>(area, messageTemplate, source));
-            }
-        }
-
-        public void Log<T0>(LogEventLevel level, string area, object? source, string messageTemplate, T0 propertyValue0)
-        {
-            if (IsEnabled(level, area))
-            {
-                Trace.WriteLine(Format<T0, object, object>(area, messageTemplate, source, propertyValue0));
-            }
-        }
-
-        public void Log<T0, T1>(LogEventLevel level, string area, object? source, string messageTemplate, T0 propertyValue0, T1 propertyValue1)
-        {
-            if (IsEnabled(level, area))
-            {
-                Trace.WriteLine(Format<T0, T1, object>(area, messageTemplate, source, propertyValue0, propertyValue1));
-            }
-        }
-
-        public void Log<T0, T1, T2>(LogEventLevel level, string area, object? source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2)
-        {
-            if (IsEnabled(level, area))
-            {
-                Trace.WriteLine(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2));
+                Trace.WriteLine(Format<object, object, object>(area, messageTemplate, source, null));
             }
         }
 
@@ -68,9 +44,7 @@ namespace Avalonia.Logging
             string area,
             string template,
             object? source,
-            T0? v0 = default,
-            T1? v1 = default,
-            T2? v2 = default)
+            object?[]? values)
         {
             var result = new StringBuilder(template.Length);
             var r = new CharacterReader(template.AsSpan());
@@ -93,13 +67,7 @@ namespace Avalonia.Logging
                     if (r.Peek != '{')
                     {
                         result.Append('\'');
-                        result.Append(i++ switch
-                        {
-                            0 => v0,
-                            1 => v1,
-                            2 => v2,
-                            _ => null
-                        });
+                        result.Append(values?[i++]);
                         result.Append('\'');
                         r.TakeUntil('}');
                         r.Take();

+ 2 - 2
src/Avalonia.Base/Media/ConicGradientBrush.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Media
         /// Defines the <see cref="Center"/> property.
         /// </summary>
         public static readonly StyledProperty<RelativePoint> CenterProperty =
-            AvaloniaProperty.Register<RadialGradientBrush, RelativePoint>(
+            AvaloniaProperty.Register<ConicGradientBrush, RelativePoint>(
                 nameof(Center),
                 RelativePoint.Center);
 
@@ -19,7 +19,7 @@ namespace Avalonia.Media
         /// Defines the <see cref="Angle"/> property.
         /// </summary>
         public static readonly StyledProperty<double> AngleProperty =
-            AvaloniaProperty.Register<RadialGradientBrush, double>(
+            AvaloniaProperty.Register<ConicGradientBrush, double>(
                 nameof(Angle),
                 0);
         

+ 2 - 3
src/Avalonia.Base/Media/DashStyle.cs

@@ -112,14 +112,13 @@ namespace Avalonia.Media
         /// <returns></returns>
         public ImmutableDashStyle ToImmutable() => new ImmutableDashStyle(Dashes, Offset);
 
-        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             base.OnPropertyChanged(change);
 
             if (change.Property == DashesProperty)
             {
-                var oldValue = change.OldValue.GetValueOrDefault<AvaloniaList<double>>();
-                var newValue = change.NewValue.GetValueOrDefault<AvaloniaList<double>>();
+                var (oldValue, newValue) = change.GetOldAndNewValue<AvaloniaList<double>>();
 
                 if (oldValue is object)
                 {

+ 1 - 1
src/Avalonia.Base/Media/DrawingImage.cs

@@ -69,7 +69,7 @@ namespace Avalonia.Media
         }
 
         /// <inheritdoc/>
-        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             base.OnPropertyChanged(change);
 

+ 47 - 32
src/Avalonia.Base/Media/GlyphRun.cs

@@ -28,6 +28,8 @@ namespace Avalonia.Media
         private IReadOnlyList<Vector>? _glyphOffsets;
         private IReadOnlyList<int>? _glyphClusters;
 
+        private int _offsetToFirstCharacter;
+
         /// <summary>
         ///     Initializes a new instance of the <see cref="GlyphRun"/> class by specifying properties of the class.
         /// </summary>
@@ -49,7 +51,7 @@ namespace Avalonia.Media
             IReadOnlyList<int>? glyphClusters = null,
             int biDiLevel = 0)
         {
-            _glyphTypeface = glyphTypeface;  
+            _glyphTypeface = glyphTypeface;
 
             FontRenderingEmSize = fontRenderingEmSize;
 
@@ -203,8 +205,8 @@ namespace Avalonia.Media
         /// </returns>
         public double GetDistanceFromCharacterHit(CharacterHit characterHit)
         {
-            var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
-           
+            var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength - _offsetToFirstCharacter;
+
             var distance = 0.0;
 
             if (IsLeftToRight)
@@ -223,7 +225,7 @@ namespace Avalonia.Media
                 }
 
                 var glyphIndex = FindGlyphIndex(characterIndex);
-                
+
                 if (GlyphClusters != null)
                 {
                     var currentCluster = GlyphClusters[glyphIndex];
@@ -249,7 +251,7 @@ namespace Avalonia.Media
             {
                 //RightToLeft
                 var glyphIndex = FindGlyphIndex(characterIndex);
-                
+
                 if (GlyphClusters != null)
                 {
                     if (characterIndex > GlyphClusters[0])
@@ -284,13 +286,13 @@ namespace Avalonia.Media
         public CharacterHit GetCharacterHitFromDistance(double distance, out bool isInside)
         {
             var characterIndex = 0;
-            
+
             // Before
             if (distance <= 0)
             {
                 isInside = false;
 
-                if(GlyphClusters != null)
+                if (GlyphClusters != null)
                 {
                     characterIndex = GlyphClusters[characterIndex];
                 }
@@ -307,11 +309,11 @@ namespace Avalonia.Media
 
                 characterIndex = GlyphIndices.Count - 1;
 
-                if(GlyphClusters != null)
+                if (GlyphClusters != null)
                 {
                     characterIndex = GlyphClusters[characterIndex];
                 }
-                
+
                 var lastCharacterHit = FindNearestCharacterHit(characterIndex, out _);
 
                 return IsLeftToRight ? lastCharacterHit : new CharacterHit(lastCharacterHit.FirstCharacterIndex);
@@ -327,7 +329,7 @@ namespace Avalonia.Media
                     var advance = GetGlyphAdvance(index, out var cluster);
 
                     characterIndex = cluster;
-                    
+
                     if (distance > currentX && distance <= currentX + advance)
                     {
                         break;
@@ -345,7 +347,7 @@ namespace Avalonia.Media
                     var advance = GetGlyphAdvance(index, out var cluster);
 
                     characterIndex = cluster;
-                    
+
                     if (currentX - advance < distance)
                     {
                         break;
@@ -552,20 +554,20 @@ namespace Avalonia.Media
                 }
 
                 nextCluster = GlyphClusters[currentIndex];
-            }
+            }           
 
             int trailingLength;
 
             if (nextCluster == cluster)
             {
-                trailingLength = Characters.Start + Characters.Length - cluster;
+                trailingLength = Characters.Start + Characters.Length - _offsetToFirstCharacter - cluster;
             }
             else
             {
                 trailingLength = nextCluster - cluster;
             }
 
-            return new CharacterHit(cluster, trailingLength);
+            return new CharacterHit(_offsetToFirstCharacter + cluster, trailingLength);
         }
 
         /// <summary>
@@ -577,7 +579,7 @@ namespace Avalonia.Media
         private double GetGlyphAdvance(int index, out int cluster)
         {
             cluster = GlyphClusters != null ? GlyphClusters[index] : index;
-            
+
             if (GlyphAdvances != null)
             {
                 return GlyphAdvances[index];
@@ -599,11 +601,18 @@ namespace Avalonia.Media
 
         private GlyphRunMetrics CreateGlyphRunMetrics()
         {
+            if (GlyphClusters != null && GlyphClusters.Count > 0)
+            {
+                var firstCluster = GlyphClusters[0];
+
+                _offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster);
+            }
+
             var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
             var widthIncludingTrailingWhitespace = 0d;
 
             var trailingWhitespaceLength = GetTrailingWhitespaceLength(out var newLineLength, out var glyphCount);
-            
+
             for (var index = 0; index < GlyphIndices.Count; index++)
             {
                 var advance = GetGlyphAdvance(index, out _);
@@ -615,7 +624,7 @@ namespace Avalonia.Media
 
             if (IsLeftToRight)
             {
-                for (var index = GlyphIndices.Count - glyphCount; index <GlyphIndices.Count; index++)
+                for (var index = GlyphIndices.Count - glyphCount; index < GlyphIndices.Count; index++)
                 {
                     width -= GetGlyphAdvance(index, out _);
                 }
@@ -670,34 +679,40 @@ namespace Avalonia.Media
             {
                 for (var i = GlyphClusters.Count - 1; i >= 0; i--)
                 {
-                    var cluster = GlyphClusters[i];
+                    var currentCluster = GlyphClusters[i];
+                    var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset);
+                    var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _);
 
-                    var codepointIndex = IsLeftToRight ? cluster - _characters.Start : _characters.End - cluster;
-
-                    if (codepointIndex < 0)
+                    if (!codepoint.IsWhiteSpace)
                     {
-                        trailingWhitespaceLength = _characters.Length;
-                        
-                        glyphCount = GlyphClusters.Count;
-                        
                         break;
                     }
-                    
-                    var codepoint = Codepoint.ReadAt(_characters, codepointIndex, out _);
 
-                    if (!codepoint.IsWhiteSpace)
+                    var clusterLength = 1;
+
+                    while(i - 1 >= 0)
                     {
+                        var nextCluster = GlyphClusters[i - 1];
+
+                        if(currentCluster == nextCluster)
+                        {
+                            clusterLength++;
+                            i--;
+
+                            continue;
+                        }
+
                         break;
                     }
 
                     if (codepoint.IsBreakChar)
                     {
-                        newLineLength++;
+                        newLineLength += clusterLength;
                     }
 
-                    trailingWhitespaceLength++;
-                    
-                    glyphCount++;
+                    trailingWhitespaceLength += clusterLength;
+                   
+                    glyphCount++;                   
                 }
             }
 

+ 17 - 11
src/Avalonia.Base/Media/Pen.cs

@@ -7,7 +7,7 @@ namespace Avalonia.Media
     /// <summary>
     /// Describes how a stroke is drawn.
     /// </summary>
-    public sealed class Pen : AvaloniaObject, IPen, IWeakEventSubscriber<EventArgs>
+    public sealed class Pen : AvaloniaObject, IPen
     {
         /// <summary>
         /// Defines the <see cref="Brush"/> property.
@@ -48,7 +48,8 @@ namespace Avalonia.Media
         private EventHandler? _invalidated;
         private IAffectsRender? _subscribedToBrush;
         private IAffectsRender? _subscribedToDashes;
-        
+        private TargetWeakEventSubscriber<Pen, EventArgs>? _weakSubscriber;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="Pen"/> class.
         /// </summary>
@@ -192,7 +193,7 @@ namespace Avalonia.Media
                 MiterLimit);
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
             _invalidated?.Invoke(this, EventArgs.Empty);
             if(change.Property == BrushProperty)
@@ -207,13 +208,24 @@ namespace Avalonia.Media
         {
             if ((_invalidated == null || field != value) && field != null)
             {
-                InvalidatedWeakEvent.Unsubscribe(field, this);
+                if (_weakSubscriber != null)
+                    InvalidatedWeakEvent.Unsubscribe(field, _weakSubscriber);
                 field = null;
             }
 
             if (_invalidated != null && field != value && value is IAffectsRender affectsRender)
             {
-                InvalidatedWeakEvent.Subscribe(affectsRender, this);
+                if (_weakSubscriber == null)
+                {
+                    _weakSubscriber = new TargetWeakEventSubscriber<Pen, EventArgs>(
+                        this, static (target, _, ev, _) =>
+                        {
+                            if (ev == InvalidatedWeakEvent)
+                                target._invalidated?.Invoke(target, EventArgs.Empty);
+                        });
+                }
+
+                InvalidatedWeakEvent.Subscribe(affectsRender, _weakSubscriber);
                 field = affectsRender;
             }
         }
@@ -223,11 +235,5 @@ namespace Avalonia.Media
             UpdateSubscription(ref _subscribedToBrush, Brush);
             UpdateSubscription(ref _subscribedToDashes, DashStyle);
         }
-        
-        void IWeakEventSubscriber<EventArgs>.OnEvent(object? sender, WeakEvent ev, EventArgs e)
-        {
-            if (ev == InvalidatedWeakEvent) 
-                _invalidated?.Invoke(this, EventArgs.Empty);
-        }
     }
 }

+ 1 - 3
src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs

@@ -1,6 +1,4 @@
-using Avalonia.Media.TextFormatting.Unicode;
-
-namespace Avalonia.Media.TextFormatting
+namespace Avalonia.Media.TextFormatting
 {
     /// <summary>
     /// Represents a base class for text formatting.

+ 47 - 18
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@@ -8,6 +8,8 @@ namespace Avalonia.Media.TextFormatting
 {
     internal class TextFormatterImpl : TextFormatter
     {
+        private static readonly char[] s_empty = { ' ' };
+
         /// <inheritdoc cref="TextFormatter.FormatLine"/>
         public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
             TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null)
@@ -77,14 +79,14 @@ namespace Avalonia.Media.TextFormatting
             {
                 var currentRun = textRuns[i];
 
-                if (currentLength + currentRun.Text.Length < length)
+                if (currentLength + currentRun.TextSourceLength < length)
                 {
                     currentLength += currentRun.TextSourceLength;
 
                     continue;
                 }
 
-                var firstCount = currentRun.Text.Length >= 1 ? i + 1 : i;
+                var firstCount = currentRun.TextSourceLength >= 1 ? i + 1 : i;
 
                 var first = new List<DrawableTextRun>(firstCount);
 
@@ -98,13 +100,13 @@ namespace Avalonia.Media.TextFormatting
 
                 var secondCount = textRuns.Count - firstCount;
 
-                if (currentLength + currentRun.Text.Length == length)
+                if (currentLength + currentRun.TextSourceLength == length)
                 {
                     var second = secondCount > 0 ? new List<DrawableTextRun>(secondCount) : null;
 
                     if (second != null)
                     {
-                        var offset = currentRun.Text.Length >= 1 ? 1 : 0;
+                        var offset = currentRun.TextSourceLength >= 1 ? 1 : 0;
 
                         for (var j = 0; j < secondCount; j++)
                         {
@@ -122,16 +124,14 @@ namespace Avalonia.Media.TextFormatting
 
                     var second = new List<DrawableTextRun>(secondCount);
 
-                    if (currentRun is not ShapedTextCharacters shapedTextCharacters)
+                    if (currentRun is ShapedTextCharacters shapedTextCharacters)
                     {
-                        throw new NotSupportedException("Only shaped runs can be split in between.");
-                    }
-
-                    var split = shapedTextCharacters.Split(length - currentLength);
+                        var split = shapedTextCharacters.Split(length - currentLength);
 
-                    first.Add(split.First);
+                        first.Add(split.First);
 
-                    second.Add(split.Second!);
+                        second.Add(split.Second!);
+                    }                
 
                     for (var j = 1; j < secondCount; j++)
                     {
@@ -267,7 +267,6 @@ namespace Avalonia.Media.TextFormatting
             IReadOnlyList<ShapeableTextCharacters> textRuns, ReadOnlySlice<char> text, TextShaperOptions options)
         {
             var shapedRuns = new List<ShapedTextCharacters>(textRuns.Count);
-            var firstRun = textRuns[0];
 
             var shapedBuffer = TextShaper.Current.ShapeText(text, options);
 
@@ -471,11 +470,10 @@ namespace Avalonia.Media.TextFormatting
             return false;
         }
 
-        private static bool TryMeasureLength(IReadOnlyList<DrawableTextRun> textRuns, int firstTextSourceIndex, double paragraphWidth, out int measuredLength)
+        private static bool TryMeasureLength(IReadOnlyList<DrawableTextRun> textRuns, double paragraphWidth, out int measuredLength)
         {
             measuredLength = 0;
             var currentWidth = 0.0;
-            var lastCluster = firstTextSourceIndex;
 
             foreach (var currentRun in textRuns)
             {
@@ -483,12 +481,17 @@ namespace Avalonia.Media.TextFormatting
                 {
                     case ShapedTextCharacters shapedTextCharacters:
                         {
+                            var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphClusters[0];
+                            var lastCluster = firstCluster;
+
                             for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++)
                             {
                                 var glyphInfo = shapedTextCharacters.ShapedBuffer[i];
 
                                 if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth)
                                 {
+                                    measuredLength += Math.Max(0, lastCluster - firstCluster);
+
                                     goto found;
                                 }
 
@@ -496,6 +499,8 @@ namespace Avalonia.Media.TextFormatting
                                 currentWidth += glyphInfo.GlyphAdvance;
                             }
 
+                            measuredLength += currentRun.TextSourceLength;
+
                             break;
                         }
 
@@ -506,7 +511,7 @@ namespace Avalonia.Media.TextFormatting
                                 goto found;
                             }
 
-                            lastCluster += currentRun.TextSourceLength;
+                            measuredLength += currentRun.TextSourceLength;
                             currentWidth += currentRun.Size.Width;
 
                             break;
@@ -516,11 +521,30 @@ namespace Avalonia.Media.TextFormatting
 
             found:
 
-            measuredLength = Math.Max(0, lastCluster - firstTextSourceIndex + 1);
-
             return measuredLength != 0;
         }
 
+        /// <summary>
+        /// Creates an empty text line.
+        /// </summary>
+        /// <returns>The empty text line.</returns>
+        public static TextLineImpl CreateEmptyTextLine(int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties)
+        {
+            var flowDirection = paragraphProperties.FlowDirection;
+            var properties = paragraphProperties.DefaultTextRunProperties;
+            var glyphTypeface = properties.Typeface.GlyphTypeface;
+            var text = new ReadOnlySlice<char>(s_empty, firstTextSourceIndex, 1);
+            var glyph = glyphTypeface.GetGlyph(s_empty[0]);
+            var glyphInfos = new[] { new GlyphInfo(glyph, firstTextSourceIndex) };
+
+            var shapedBuffer = new ShapedBuffer(text, glyphInfos, glyphTypeface, properties.FontRenderingEmSize,
+                (sbyte)flowDirection);
+
+            var textRuns = new List<DrawableTextRun> { new ShapedTextCharacters(shapedBuffer, properties) };
+
+            return new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection).FinalizeLine();
+        }
+
         /// <summary>
         /// Performs text wrapping returns a list of text lines.
         /// </summary>
@@ -535,7 +559,12 @@ namespace Avalonia.Media.TextFormatting
             double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection flowDirection,
             TextLineBreak? currentLineBreak)
         {
-            if (!TryMeasureLength(textRuns, firstTextSourceIndex, paragraphWidth, out var measuredLength))
+            if(textRuns.Count == 0)
+            {
+                return CreateEmptyTextLine(firstTextSourceIndex,paragraphWidth, paragraphProperties);
+            }
+
+            if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength))
             {
                 measuredLength = 1;
             }

+ 66 - 53
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@@ -10,13 +10,12 @@ namespace Avalonia.Media.TextFormatting
     /// </summary>
     public class TextLayout
     {
-        private static readonly char[] s_empty = { ' ' };
-
-        private readonly ReadOnlySlice<char> _text;
+        private readonly ITextSource _textSource;
         private readonly TextParagraphProperties _paragraphProperties;
-        private readonly IReadOnlyList<ValueSpan<TextRunProperties>>? _textStyleOverrides;
         private readonly TextTrimming _textTrimming;
 
+        private int _textSourceLength;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="TextLayout" /> class.
         /// </summary>
@@ -50,17 +49,49 @@ namespace Avalonia.Media.TextFormatting
             int maxLines = 0,
             IReadOnlyList<ValueSpan<TextRunProperties>>? textStyleOverrides = null)
         {
-            _text = string.IsNullOrEmpty(text) ?
-                new ReadOnlySlice<char>() :
-                new ReadOnlySlice<char>(text.AsMemory());
-
             _paragraphProperties =
                 CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
                     textDecorations, flowDirection, lineHeight);
 
+            _textSource = new FormattedTextSource(text.AsMemory(), _paragraphProperties.DefaultTextRunProperties, textStyleOverrides);
+
             _textTrimming = textTrimming ?? TextTrimming.None;
 
-            _textStyleOverrides = textStyleOverrides;
+            LineHeight = lineHeight;
+
+            MaxWidth = maxWidth;
+
+            MaxHeight = maxHeight;
+
+            MaxLines = maxLines;      
+
+            TextLines = CreateTextLines();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="TextLayout" /> class.
+        /// </summary>
+        /// <param name="textSource">The text source.</param>
+        /// <param name="paragraphProperties">The default text paragraph properties.</param>
+        /// <param name="textTrimming">The text trimming.</param>
+        /// <param name="maxWidth">The maximum width.</param>
+        /// <param name="maxHeight">The maximum height.</param>
+        /// <param name="lineHeight">The height of each line of text.</param>
+        /// <param name="maxLines">The maximum number of text lines.</param>
+        public TextLayout(
+            ITextSource textSource,
+            TextParagraphProperties paragraphProperties, 
+            TextTrimming? textTrimming = null,
+            double maxWidth = double.PositiveInfinity,
+            double maxHeight = double.PositiveInfinity,
+            double lineHeight = double.NaN,
+            int maxLines = 0)
+        {
+            _textSource = textSource;
+
+            _paragraphProperties = paragraphProperties;
+
+            _textTrimming = textTrimming ?? TextTrimming.None;
 
             LineHeight = lineHeight;
 
@@ -147,7 +178,7 @@ namespace Avalonia.Media.TextFormatting
                 return new Rect();
             }
 
-            if (textPosition < 0 || textPosition >= _text.Length)
+            if (textPosition < 0 || textPosition >= _textSourceLength)
             {
                 var lastLine = TextLines[TextLines.Count - 1];
 
@@ -273,7 +304,7 @@ namespace Avalonia.Media.TextFormatting
                 return 0;
             }
 
-            if (charIndex > _text.Length)
+            if (charIndex > _textSourceLength)
             {
                 return TextLines.Count - 1;
             }
@@ -375,32 +406,11 @@ namespace Avalonia.Media.TextFormatting
             height += textLine.Height;
         }
 
-        /// <summary>
-        /// Creates an empty text line.
-        /// </summary>
-        /// <returns>The empty text line.</returns>
-        private TextLine CreateEmptyTextLine(int firstTextSourceIndex)
-        {
-            var flowDirection = _paragraphProperties.FlowDirection;
-            var properties = _paragraphProperties.DefaultTextRunProperties;
-            var glyphTypeface = properties.Typeface.GlyphTypeface;
-            var text = new ReadOnlySlice<char>(s_empty, firstTextSourceIndex, 1);
-            var glyph = glyphTypeface.GetGlyph(s_empty[0]);
-            var glyphInfos = new[] { new GlyphInfo(glyph, firstTextSourceIndex) };
-
-            var shapedBuffer = new ShapedBuffer(text, glyphInfos, glyphTypeface, properties.FontRenderingEmSize,
-                (sbyte)flowDirection);
-
-            var textRuns = new List<DrawableTextRun> { new ShapedTextCharacters(shapedBuffer, properties) };
-
-            return new TextLineImpl(textRuns, firstTextSourceIndex, 1, MaxWidth, _paragraphProperties, flowDirection).FinalizeLine();
-        }
-
         private IReadOnlyList<TextLine> CreateTextLines()
         {
-            if (_text.IsEmpty || MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight))
+            if (MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight))
             {
-                var textLine = CreateEmptyTextLine(0);
+                var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
 
                 Bounds = new Rect(0,0,0, textLine.Height);
 
@@ -411,26 +421,30 @@ namespace Avalonia.Media.TextFormatting
 
             double left = double.PositiveInfinity, width = 0.0, height = 0.0;
 
-            var currentPosition = 0;
-
-            var textSource = new FormattedTextSource(_text,
-                _paragraphProperties.DefaultTextRunProperties, _textStyleOverrides);
+            _textSourceLength = 0;
 
             TextLine? previousLine = null;
 
-            while (currentPosition < _text.Length)
+            while (true)
             {
-                var textLine = TextFormatter.Current.FormatLine(textSource, currentPosition, MaxWidth,
+                var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth,
                     _paragraphProperties, previousLine?.TextLineBreak);
 
-#if DEBUG
-                if (textLine.Length == 0)
+                if(textLine == null || textLine.Length == 0)
                 {
-                    throw new InvalidOperationException($"{nameof(textLine)} should not be empty.");
+                    if(previousLine != null && previousLine.NewLineLength  > 0)
+                    {
+                        var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth, _paragraphProperties);
+
+                        textLines.Add(emptyTextLine);
+
+                        UpdateBounds(emptyTextLine, ref left, ref width, ref height);
+                    }
+
+                    break;
                 }
-#endif
 
-                currentPosition += textLine.Length;
+                _textSourceLength += textLine.Length;
                 
                 //Fulfill max height constraint
                 if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) && height + textLine.Height > MaxHeight)
@@ -464,17 +478,16 @@ namespace Avalonia.Media.TextFormatting
                 {
                     break;
                 }
-                
-                if (currentPosition != _text.Length || textLine.NewLineLength <= 0)
-                {
-                    continue;
-                }
+            }
 
-                var emptyTextLine = CreateEmptyTextLine(currentPosition);
+            //Make sure the TextLayout always contains at least on empty line
+            if(textLines.Count == 0)
+            {
+                var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties);
 
-                textLines.Add(emptyTextLine);
+                textLines.Add(textLine);
 
-                UpdateBounds(emptyTextLine,ref left, ref width, ref height);
+                UpdateBounds(textLine, ref left, ref width, ref height);
             }
 
             Bounds = new Rect(left, 0, width, height);

+ 76 - 103
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@@ -183,6 +183,7 @@ namespace Avalonia.Media.TextFormatting
                     case ShapedTextCharacters shapedRun:
                         {
                             characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
+
                             break;
                         }
                     default:
@@ -251,7 +252,7 @@ namespace Avalonia.Media.TextFormatting
                             //Look at the left and right edge of the current run
                             if (currentRun.IsLeftToRight)
                             {
-                                if (lastRun == null || lastRun.IsLeftToRight)
+                                if (_flowDirection == FlowDirection.LeftToRight && (lastRun == null || lastRun.IsLeftToRight))
                                 {
                                     if (characterIndex <= textRun.Text.Start)
                                     {
@@ -403,7 +404,7 @@ namespace Avalonia.Media.TextFormatting
             var result = new List<TextBounds>(TextRuns.Count);
             var lastDirection = _flowDirection;
             var currentDirection = lastDirection;
-            var currentPosition = 0;
+            var currentPosition = FirstTextSourceIndex;
             var currentRect = Rect.Empty;
             var startX = Start;
 
@@ -426,31 +427,42 @@ namespace Avalonia.Media.TextFormatting
 
                 if (nextRun != null)
                 {
-                    if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End)
+                    switch (nextRun)
                     {
-                        goto skip;
-                    }
+                        case ShapedTextCharacters when currentRun is ShapedTextCharacters:
+                            {
+                                if (nextRun.Text.Start < currentRun.Text.Start && firstTextSourceCharacterIndex + textLength < currentRun.Text.End)
+                                {
+                                    goto skip;
+                                }
 
-                    if (currentRun.Text.Start >= firstTextSourceCharacterIndex + textLength)
-                    {
-                        goto skip;
-                    }
+                                if (currentRun.Text.Start >= firstTextSourceCharacterIndex + textLength)
+                                {
+                                    goto skip;
+                                }
 
-                    if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < firstTextSourceCharacterIndex)
-                    {
-                        goto skip;
-                    }
+                                if (currentRun.Text.Start > nextRun.Text.Start && currentRun.Text.Start < firstTextSourceCharacterIndex)
+                                {
+                                    goto skip;
+                                }
 
-                    if (currentRun.Text.End < firstTextSourceCharacterIndex)
-                    {
-                        goto skip;
-                    }
+                                if (currentRun.Text.End < firstTextSourceCharacterIndex)
+                                {
+                                    goto skip;
+                                }
 
-                    goto noop;
+                                goto noop;
+                            }
+                        default:
+                            {
+                                goto noop;
+                            }
+                    }
 
                 skip:
                     {
                         startX += currentRun.Size.Width;
+                        currentPosition += currentRun.TextSourceLength;
                     }
 
                     continue;
@@ -460,7 +472,6 @@ namespace Avalonia.Media.TextFormatting
                     }
                 }
 
-
                 var endX = startX;
                 var endOffset = 0d;
 
@@ -520,11 +531,18 @@ namespace Avalonia.Media.TextFormatting
                         }
                     default:
                         {
-                            if (firstTextSourceCharacterIndex + textLength >= currentRun.Text.Start + currentRun.Text.Length)
+                            if (currentPosition + currentRun.TextSourceLength <= firstTextSourceCharacterIndex + textLength)
                             {
                                 endX += currentRun.Size.Width;
                             }
 
+                            if (currentPosition < firstTextSourceCharacterIndex)
+                            {
+                                startX += currentRun.Size.Width;
+                            }
+
+                            currentPosition += currentRun.TextSourceLength;
+
                             break;
                         }
                 }
@@ -536,36 +554,31 @@ namespace Avalonia.Media.TextFormatting
 
                 var width = endX - startX;
 
-                if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
+                if (!MathUtilities.IsZero(width))
                 {
-                    var textBounds = new TextBounds(currentRect.WithWidth(currentRect.Width + width), currentDirection);
+                    if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
+                    {
+                        currentRect = currentRect.WithWidth(currentRect.Width + width);
 
-                    result[result.Count - 1] = textBounds;
-                }
-                else
-                {
-                    currentRect = new Rect(startX, 0, width, Height);
+                        var textBounds = new TextBounds(currentRect, currentDirection);
 
-                    result.Add(new TextBounds(currentRect, currentDirection));
+                        result[result.Count - 1] = textBounds;
+                    }
+                    else
+                    {
+
+                        currentRect = new Rect(startX, 0, width, Height);
+
+                        result.Add(new TextBounds(currentRect, currentDirection));
+
+                    }
                 }
 
                 if (currentDirection == FlowDirection.LeftToRight)
                 {
-                    if (nextRun != null)
-                    {
-                        if (nextRun.Text.Start > currentRun.Text.Start && nextRun.Text.Start >= firstTextSourceCharacterIndex + textLength)
-                        {
-                            break;
-                        }
-
-                        currentPosition = nextRun.Text.End;
-                    }
-                    else
+                    if (currentPosition > firstTextSourceCharacterIndex + textLength)
                     {
-                        if (currentPosition >= firstTextSourceCharacterIndex + textLength)
-                        {
-                            break;
-                        }
+                        break;
                     }
                 }
                 else
@@ -575,10 +588,7 @@ namespace Avalonia.Media.TextFormatting
                         break;
                     }
 
-                    if (currentPosition != currentRun.Text.Start)
-                    {
-                        endX += currentRun.Size.Width - endOffset;
-                    }
+                    endX += currentRun.Size.Width - endOffset;
                 }
 
                 lastDirection = currentDirection;
@@ -590,10 +600,10 @@ namespace Avalonia.Media.TextFormatting
 
         public TextLineImpl FinalizeLine()
         {
-            BidiReorder();
-
             _textLineMetrics = CreateLineMetrics();
 
+            BidiReorder();
+
             return this;
         }
 
@@ -1018,31 +1028,21 @@ namespace Avalonia.Media.TextFormatting
 
         private TextLineMetrics CreateLineMetrics()
         {
-            var start = 0d;
-            var height = 0d;
+            var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface;
+            var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
+            var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight;
+
             var width = 0d;
             var widthIncludingWhitespace = 0d;
             var trailingWhitespaceLength = 0;
             var newLineLength = 0;
-            var ascent = 0d;
-            var descent = 0d;
-            var lineGap = 0d;
-            var fontRenderingEmSize = 0d;
+            var ascent = glyphTypeface.Ascent * scale;
+            var descent = glyphTypeface.Descent * scale;
+            var lineGap = glyphTypeface.LineGap * scale;
 
-            var lineHeight = _paragraphProperties.LineHeight;
+            var height = descent - ascent + lineGap;
 
-            if (_textRuns.Count == 0)
-            {
-                var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface;
-                fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
-                var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight;
-                ascent = glyphTypeface.Ascent * scale;
-                height = double.IsNaN(lineHeight) || MathUtilities.IsZero(lineHeight) ?
-                descent - ascent + lineGap :
-                lineHeight;
-
-                return new TextLineMetrics(false, height, 0, start, -ascent, 0, 0, 0);
-            }
+            var lineHeight = _paragraphProperties.LineHeight;
 
             for (var index = 0; index < _textRuns.Count; index++)
             {
@@ -1078,41 +1078,11 @@ namespace Avalonia.Media.TextFormatting
                                 }
                             }
 
-                            switch (_paragraphProperties.FlowDirection)
+                            if (index == _textRuns.Count - 1)
                             {
-                                case FlowDirection.LeftToRight:
-                                    {
-                                        if (index == _textRuns.Count - 1)
-                                        {
-                                            width = widthIncludingWhitespace + textRun.GlyphRun.Metrics.Width;
-                                            trailingWhitespaceLength = textRun.GlyphRun.Metrics.TrailingWhitespaceLength;
-                                            newLineLength = textRun.GlyphRun.Metrics.NewlineLength;
-                                        }
-
-                                        break;
-                                    }
-
-                                case FlowDirection.RightToLeft:
-                                    {
-                                        if (index == _textRuns.Count - 1)
-                                        {
-                                            var firstRun = _textRuns[0];
-
-                                            if (firstRun is ShapedTextCharacters shapedTextCharacters)
-                                            {
-                                                var offset = shapedTextCharacters.GlyphRun.Metrics.WidthIncludingTrailingWhitespace -
-                                                             shapedTextCharacters.GlyphRun.Metrics.Width;
-
-                                                width = widthIncludingWhitespace +
-                                                    textRun.GlyphRun.Metrics.WidthIncludingTrailingWhitespace - offset;
-
-                                                trailingWhitespaceLength = shapedTextCharacters.GlyphRun.Metrics.TrailingWhitespaceLength;
-                                                newLineLength = shapedTextCharacters.GlyphRun.Metrics.NewlineLength;
-                                            }
-                                        }
-
-                                        break;
-                                    }
+                                width = widthIncludingWhitespace + textRun.GlyphRun.Metrics.Width;
+                                trailingWhitespaceLength = textRun.GlyphRun.Metrics.TrailingWhitespaceLength;
+                                newLineLength = textRun.GlyphRun.Metrics.NewlineLength;
                             }
 
                             widthIncludingWhitespace += textRun.GlyphRun.Metrics.WidthIncludingTrailingWhitespace;
@@ -1166,12 +1136,15 @@ namespace Avalonia.Media.TextFormatting
                 }
             }
 
-            start = GetParagraphOffsetX(width, widthIncludingWhitespace, _paragraphWidth,
+            var start = GetParagraphOffsetX(width, widthIncludingWhitespace, _paragraphWidth,
                 _paragraphProperties.TextAlignment, _paragraphProperties.FlowDirection);
 
             if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight))
             {
-                height = lineHeight;
+                if (lineHeight > height)
+                {
+                    height = lineHeight;
+                }
             }
 
             return new TextLineMetrics(widthIncludingWhitespace > _paragraphWidth, height, newLineLength, start,

+ 5 - 0
src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs

@@ -238,6 +238,11 @@ namespace Avalonia.Media.TextFormatting.Unicode
             _levelRuns.Clear();
             _resolvedLevelsBuffer.Clear();
 
+            if (types.IsEmpty)
+            {
+                return;
+            }
+
             // Setup original types and working types
             _originalClasses = types;
             _workingClasses = _workingClassesBuffer.Add(types);

+ 1 - 0
src/Avalonia.Base/Properties/AssemblyInfo.cs

@@ -19,6 +19,7 @@ using Avalonia.Metadata;
 
 [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
 [assembly: InternalsVisibleTo("Avalonia.Controls, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
+[assembly: InternalsVisibleTo("Avalonia.Controls.ColorPicker, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
 [assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
 [assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
 [assembly: InternalsVisibleTo("Avalonia.DesignerSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]

+ 8 - 9
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@@ -18,19 +18,19 @@ namespace Avalonia.PropertyStore
     /// <typeparam name="T">The property type.</typeparam>
     internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>>
     {
-        private readonly IAvaloniaObject _owner;
-        private IValueSink _sink;
+        private readonly AvaloniaObject _owner;
+        private ValueOwner<T> _sink;
         private IDisposable? _subscription;
         private bool _isSubscribed;
         private bool _batchUpdate;
         private Optional<T> _value;
 
         public BindingEntry(
-            IAvaloniaObject owner,
+            AvaloniaObject owner,
             StyledPropertyBase<T> property,
             IObservable<BindingValue<T>> source,
             BindingPriority priority,
-            IValueSink sink)
+            ValueOwner<T> sink)
         {
             _owner = owner;
             Property = property;
@@ -50,7 +50,7 @@ namespace Avalonia.PropertyStore
         {
             _batchUpdate = false;
 
-            if (_sink is ValueStore)
+            if (_sink.IsValueStore)
                 Start();
         }
 
@@ -113,16 +113,15 @@ namespace Avalonia.PropertyStore
             }
         }
 
-        public void Reparent(IValueSink sink) => _sink = sink;
+        public void Reparent(PriorityValue<T> parent) => _sink = new(parent);
 
         public void RaiseValueChanged(
-            IValueSink sink,
-            IAvaloniaObject owner,
+            AvaloniaObject owner,
             AvaloniaProperty property,
             Optional<object?> oldValue,
             Optional<object?> newValue)
         {
-            sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
+            owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                 owner,
                 (AvaloniaProperty<T>)property,
                 oldValue.Cast<T>(),

+ 6 - 7
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@@ -18,14 +18,14 @@ namespace Avalonia.PropertyStore
     /// <typeparam name="T">The property type.</typeparam>
     internal class ConstantValueEntry<T> : IPriorityValueEntry<T>, IConstantValueEntry
     {
-        private IValueSink _sink;
+        private ValueOwner<T> _sink;
         private Optional<T> _value;
 
         public ConstantValueEntry(
             StyledPropertyBase<T> property,
             T value,
             BindingPriority priority,
-            IValueSink sink)
+            ValueOwner<T> sink)
         {
             Property = property;
             _value = value;
@@ -37,7 +37,7 @@ namespace Avalonia.PropertyStore
             StyledPropertyBase<T> property,
             Optional<T> value,
             BindingPriority priority,
-            IValueSink sink)
+            ValueOwner<T> sink)
         {
             Property = property;
             _value = value;
@@ -62,17 +62,16 @@ namespace Avalonia.PropertyStore
             _sink.Completed(Property, this, oldValue);
         }
 
-        public void Reparent(IValueSink sink) => _sink = sink;
+        public void Reparent(PriorityValue<T> sink) => _sink = new(sink);
         public void Start() { }
 
         public void RaiseValueChanged(
-            IValueSink sink,
-            IAvaloniaObject owner,
+            AvaloniaObject owner,
             AvaloniaProperty property,
             Optional<object?> oldValue,
             Optional<object?> newValue)
         {
-            sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
+            owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                 owner,
                 (AvaloniaProperty<T>)property,
                 oldValue.Cast<T>(),

+ 1 - 1
src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs

@@ -5,7 +5,6 @@
     /// </summary>
     internal interface IPriorityValueEntry : IValue
     {
-        void Reparent(IValueSink sink);
     }
 
     /// <summary>
@@ -14,5 +13,6 @@
     /// <typeparam name="T">The property type.</typeparam>
     internal interface IPriorityValueEntry<T> : IPriorityValueEntry, IValue<T>
     {
+        void Reparent(PriorityValue<T> parent);
     }
 }

+ 1 - 2
src/Avalonia.Base/PropertyStore/IValue.cs

@@ -11,8 +11,7 @@ namespace Avalonia.PropertyStore
         Optional<object?> GetValue();
         void Start();
         void RaiseValueChanged(
-            IValueSink sink,
-            IAvaloniaObject owner,
+            AvaloniaObject owner,
             AvaloniaProperty property,
             Optional<object?> oldValue,
             Optional<object?> newValue);

+ 0 - 17
src/Avalonia.Base/PropertyStore/IValueSink.cs

@@ -1,17 +0,0 @@
-using Avalonia.Data;
-
-namespace Avalonia.PropertyStore
-{
-    /// <summary>
-    /// Represents an entity that can receive change notifications in a <see cref="ValueStore"/>.
-    /// </summary>
-    internal interface IValueSink
-    {
-        void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change);
-
-        void Completed<T>(
-            StyledPropertyBase<T> property,
-            IPriorityValueEntry entry,
-            Optional<T> oldValue);
-    }
-}

+ 2 - 3
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@@ -25,13 +25,12 @@ namespace Avalonia.PropertyStore
         public void Start() { }
 
         public void RaiseValueChanged(
-            IValueSink sink,
-            IAvaloniaObject owner,
+            AvaloniaObject owner,
             AvaloniaProperty property,
             Optional<object?> oldValue,
             Optional<object?> newValue)
         {
-            sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
+            owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                 owner,
                 (AvaloniaProperty<T>)property,
                 oldValue.Cast<T>(),

+ 33 - 34
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@@ -1,10 +1,17 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
 using Avalonia.Data;
 
 namespace Avalonia.PropertyStore
 {
+    /// <summary>
+    /// Represents an untyped interface to <see cref="PriorityValue{T}"/>.
+    /// </summary>
+    interface IPriorityValue : IValue
+    {
+        void UpdateEffectiveValue();
+    }
+
     /// <summary>
     /// Stores a set of prioritized values and bindings in a <see cref="ValueStore"/>.
     /// </summary>
@@ -16,10 +23,10 @@ namespace Avalonia.PropertyStore
     /// <see cref="IPriorityValueEntry{T}"/> entries (sorted first by priority and then in the order
     /// they were added) plus a local value.
     /// </remarks>
-    internal class PriorityValue<T> : IValue<T>, IValueSink, IBatchUpdate
+    internal class PriorityValue<T> : IPriorityValue, IValue<T>, IBatchUpdate
     {
-        private readonly IAvaloniaObject _owner;
-        private readonly IValueSink _sink;
+        private readonly AvaloniaObject _owner;
+        private readonly ValueStore _store;
         private readonly List<IPriorityValueEntry<T>> _entries = new List<IPriorityValueEntry<T>>();
         private readonly Func<IAvaloniaObject, T, T>? _coerceValue;
         private Optional<T> _localValue;
@@ -28,13 +35,13 @@ namespace Avalonia.PropertyStore
         private bool _batchUpdate;
 
         public PriorityValue(
-            IAvaloniaObject owner,
+            AvaloniaObject owner,
             StyledPropertyBase<T> property,
-            IValueSink sink)
+            ValueStore store)
         {
             _owner = owner;
             Property = property;
-            _sink = sink;
+            _store = store;
 
             if (property.HasCoercion)
             {
@@ -44,11 +51,11 @@ namespace Avalonia.PropertyStore
         }
 
         public PriorityValue(
-            IAvaloniaObject owner,
+            AvaloniaObject owner,
             StyledPropertyBase<T> property,
-            IValueSink sink,
+            ValueStore store,
             IPriorityValueEntry<T> existing)
-            : this(owner, property, sink)
+            : this(owner, property, store)
         {
             existing.Reparent(this);
             _entries.Add(existing);
@@ -75,9 +82,9 @@ namespace Avalonia.PropertyStore
         }
 
         public PriorityValue(
-            IAvaloniaObject owner,
+            AvaloniaObject owner,
             StyledPropertyBase<T> property,
-            IValueSink sink,
+            ValueStore sink,
             LocalValueEntry<T> existing)
             : this(owner, property, sink)
         {
@@ -148,7 +155,7 @@ namespace Avalonia.PropertyStore
             else
             {
                 var insert = FindInsertPoint(priority);
-                var entry = new ConstantValueEntry<T>(Property, value, priority, this);
+                var entry = new ConstantValueEntry<T>(Property, value, priority, new ValueOwner<T>(this));
                 _entries.Insert(insert, entry);
                 result = entry;
             }
@@ -165,7 +172,7 @@ namespace Avalonia.PropertyStore
 
         public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPriority priority)
         {
-            var binding = new BindingEntry<T>(_owner, Property, source, priority, this);
+            var binding = new BindingEntry<T>(_owner, Property, source, priority, new(this));
             var insert = FindInsertPoint(binding.Priority);
             _entries.Insert(insert, binding);
 
@@ -186,13 +193,12 @@ namespace Avalonia.PropertyStore
         public void Start() => UpdateEffectiveValue(null);
 
         public void RaiseValueChanged(
-            IValueSink sink,
-            IAvaloniaObject owner,
+            AvaloniaObject owner,
             AvaloniaProperty property,
             Optional<object?> oldValue,
             Optional<object?> newValue)
         {
-            sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
+            owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                 owner,
                 (AvaloniaProperty<T>)property,
                 oldValue.Cast<T>(),
@@ -200,7 +206,7 @@ namespace Avalonia.PropertyStore
                 Priority));
         }
 
-        void IValueSink.ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change)
+        public void ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change)
         {
             if (change.Priority == BindingPriority.LocalValue)
             {
@@ -213,22 +219,15 @@ namespace Avalonia.PropertyStore
             }
         }
 
-        void IValueSink.Completed<TValue>(
-            StyledPropertyBase<TValue> property,
-            IPriorityValueEntry entry,
-            Optional<TValue> oldValue)
+        public void Completed(IPriorityValueEntry entry, Optional<T> oldValue)
         {
             _entries.Remove((IPriorityValueEntry<T>)entry);
-
-            if (oldValue is Optional<T> o)
-            {
-                UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
-                    _owner,
-                    Property,
-                    o,
-                    default,
-                    entry.Priority));
-            }
+            UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
+                _owner,
+                Property,
+                oldValue,
+                default,
+                entry.Priority));
         }
 
         private int FindInsertPoint(BindingPriority priority)
@@ -308,7 +307,7 @@ namespace Avalonia.PropertyStore
                 var old = _value;
                 _value = value;
 
-                _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
+                _store.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                     _owner,
                     Property,
                     old,
@@ -319,7 +318,7 @@ namespace Avalonia.PropertyStore
             {
                 change.MarkNonEffectiveValue();
                 change.SetOldValue(default);
-                _sink.ValueChanged(change);
+                _store.ValueChanged(change);
             }
         }
     }

+ 45 - 0
src/Avalonia.Base/PropertyStore/ValueOwner.cs

@@ -0,0 +1,45 @@
+using Avalonia.Data;
+
+namespace Avalonia.PropertyStore
+{
+    /// <summary>
+    /// Represents a union type of <see cref="ValueStore"/> and <see cref="PriorityValue{T}"/>,
+    /// which are the valid owners of a value store <see cref="IValue"/>.
+    /// </summary>
+    /// <typeparam name="T">The value type.</typeparam>
+    internal readonly struct ValueOwner<T>
+    {
+        private readonly ValueStore? _store;
+        private readonly PriorityValue<T>? _priorityValue;
+
+        public ValueOwner(ValueStore o)
+        {
+            _store = o;
+            _priorityValue = null;
+        }
+
+        public ValueOwner(PriorityValue<T> v)
+        {
+            _store = null;
+            _priorityValue = v;
+        }
+
+        public bool IsValueStore => _store is not null;
+
+        public void Completed(StyledPropertyBase<T> property, IPriorityValueEntry entry, Optional<T> oldValue)
+        {
+            if (_store is not null)
+                _store?.Completed(property, entry, oldValue);
+            else
+                _priorityValue!.Completed(entry, oldValue);
+        }
+
+        public void ValueChanged(AvaloniaPropertyChangedEventArgs<T> e)
+        {
+            if (_store is not null)
+                _store?.ValueChanged(e);
+            else
+                _priorityValue!.ValueChanged(e);
+        }
+    }
+}

+ 1 - 1
src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs

@@ -51,7 +51,7 @@ namespace Avalonia.Reactive
             {
                 if (e is AvaloniaPropertyChangedEventArgs<T> typedArgs)
                 {
-                    var newValue = e.Sender.GetValue(typedArgs.Property);
+                    var newValue = e.Sender.GetValue<T>(typedArgs.Property);
 
                     if (!_value.HasValue || !EqualityComparer<T>.Default.Equals(newValue, _value.Value))
                     {

+ 19 - 11
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@@ -49,23 +49,31 @@ namespace Avalonia.Reactive
         {
             if (e.Property == _property)
             {
-                T newValue;
-
-                if (e is AvaloniaPropertyChangedEventArgs<T> typed)
+                if (e.Sender is AvaloniaObject ao)
                 {
-                    newValue = typed.Sender.GetValue(typed.Property);
+                    T newValue;
+
+                    if (e is AvaloniaPropertyChangedEventArgs<T> typed)
+                    {
+                        newValue = AvaloniaObjectExtensions.GetValue(ao, typed.Property);
+                    }
+                    else
+                    {
+                        newValue = (T)e.Sender.GetValue(e.Property)!;
+                    }
+
+                    if (!_value.HasValue ||
+                        !EqualityComparer<T>.Default.Equals(newValue, _value.Value))
+                    {
+                        _value = newValue;
+                        PublishNext(_value.Value!);
+                    }
                 }
                 else
                 {
-                    newValue = (T)e.Sender.GetValue(e.Property)!;
+                    throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
                 }
 
-                if (!_value.HasValue ||
-                    !EqualityComparer<T>.Default.Equals(newValue, _value.Value))
-                {
-                    _value = newValue;
-                    PublishNext(_value.Value!);
-                }
             }
         }
     }

+ 6 - 1
src/Avalonia.Base/StyledElement.cs

@@ -467,7 +467,12 @@ namespace Avalonia
         /// <param name="parent">The parent.</param>
         void ISetInheritanceParent.SetParent(IAvaloniaObject? parent)
         {
-            InheritanceParent = parent;
+            InheritanceParent = parent switch
+            {
+                AvaloniaObject ao => ao,
+                null => null,
+                _ => throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.")
+            };
         }
 
         void IStyleable.StyleApplied(IStyleInstance instance)

+ 32 - 12
src/Avalonia.Base/StyledPropertyBase.cs

@@ -2,6 +2,7 @@ using System;
 using System.Diagnostics;
 using Avalonia.Data;
 using Avalonia.Reactive;
+using Avalonia.Styling;
 using Avalonia.Utilities;
 
 namespace Avalonia
@@ -158,12 +159,6 @@ namespace Avalonia
             base.OverrideMetadata(type, metadata);
         }
 
-        /// <inheritdoc/>
-        public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
-        {
-            visitor.Visit(this, ref data);
-        }
-
         /// <summary>
         /// Gets the string representation of the property.
         /// </summary>
@@ -177,19 +172,19 @@ namespace Avalonia
         object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type);
 
         /// <inheritdoc/>
-        internal override void RouteClearValue(IAvaloniaObject o)
+        internal override void RouteClearValue(AvaloniaObject o)
         {
             o.ClearValue<TValue>(this);
         }
 
         /// <inheritdoc/>
-        internal override object? RouteGetValue(IAvaloniaObject o)
+        internal override object? RouteGetValue(AvaloniaObject o)
         {
             return o.GetValue<TValue>(this);
         }
 
         /// <inheritdoc/>
-        internal override object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
+        internal override object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority)
         {
             var value = o.GetBaseValue<TValue>(this, maxPriority);
             return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue;
@@ -197,7 +192,7 @@ namespace Avalonia
 
         /// <inheritdoc/>
         internal override IDisposable? RouteSetValue(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             object? value,
             BindingPriority priority)
         {
@@ -221,7 +216,7 @@ namespace Avalonia
 
         /// <inheritdoc/>
         internal override IDisposable RouteBind(
-            IAvaloniaObject o,
+            AvaloniaObject o,
             IObservable<BindingValue<object?>> source,
             BindingPriority priority)
         {
@@ -232,11 +227,36 @@ namespace Avalonia
         /// <inheritdoc/>
         internal override void RouteInheritanceParentChanged(
             AvaloniaObject o,
-            IAvaloniaObject? oldParent)
+            AvaloniaObject? oldParent)
         {
             o.InheritanceParentChanged(this, oldParent);
         }
 
+        internal override ISetterInstance CreateSetterInstance(IStyleable target, object? value)
+        {
+            if (value is IBinding binding)
+            {
+                return new PropertySetterBindingInstance<TValue>(
+                    target,
+                    this,
+                    binding);
+            }
+            else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
+            {
+                return new PropertySetterLazyInstance<TValue>(
+                    target,
+                    this,
+                    () => (TValue)template.Build());
+            }
+            else
+            {
+                return new PropertySetterInstance<TValue>(
+                    target,
+                    this,
+                    (TValue)value!);
+            }
+        }
+
         private object? GetDefaultBoxedValue(Type type)
         {
             _ = type ?? throw new ArgumentNullException(nameof(type));

+ 1 - 1
src/Avalonia.Base/Styling/IStyle.cs

@@ -8,7 +8,7 @@ namespace Avalonia.Styling
     /// <summary>
     /// Defines the interface for styles.
     /// </summary>
-    public interface IStyle
+    public interface IStyle : IResourceNode
     {
         /// <summary>
         /// Gets a collection of child styles.

+ 2 - 63
src/Avalonia.Base/Styling/Setter.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Styling
     /// A <see cref="Setter"/> is used to set a <see cref="AvaloniaProperty"/> value on a
     /// <see cref="AvaloniaObject"/> depending on a condition.
     /// </remarks>
-    public class Setter : ISetter, IAnimationSetter, IAvaloniaPropertyVisitor<Setter.SetterVisitorData>
+    public class Setter : ISetter, IAnimationSetter
     {
         private object? _value;
 
@@ -68,68 +68,7 @@ namespace Avalonia.Styling
                 throw new InvalidOperationException("Setter.Property must be set.");
             }
 
-            var data = new SetterVisitorData
-            {
-                target = target,
-                value = Value,
-            };
-
-            Property.Accept(this, ref data);
-            return data.result!;
-        }
-
-        void IAvaloniaPropertyVisitor<SetterVisitorData>.Visit<T>(
-            StyledPropertyBase<T> property,
-            ref SetterVisitorData data)
-        {
-            if (data.value is IBinding binding)
-            {
-                data.result = new PropertySetterBindingInstance<T>(
-                    data.target,
-                    property,
-                    binding);
-            }
-            else if (data.value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(property.PropertyType))
-            {
-                data.result = new PropertySetterLazyInstance<T>(
-                    data.target,
-                    property,
-                    () => (T)template.Build());
-            }
-            else
-            {
-                data.result = new PropertySetterInstance<T>(
-                    data.target,
-                    property,
-                    (T)data.value!);
-            }
-        }
-
-        void IAvaloniaPropertyVisitor<SetterVisitorData>.Visit<T>(
-            DirectPropertyBase<T> property,
-            ref SetterVisitorData data)
-        {
-            if (data.value is IBinding binding)
-            {
-                data.result = new PropertySetterBindingInstance<T>(
-                    data.target,
-                    property,
-                    binding);
-            }
-            else if (data.value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(property.PropertyType))
-            {
-                data.result = new PropertySetterLazyInstance<T>(
-                    data.target,
-                    property,
-                    () => (T)template.Build());
-            }
-            else
-            {
-                data.result = new PropertySetterInstance<T>(
-                    data.target,
-                    property,
-                    (T)data.value!);
-            }
+            return Property.CreateSetterInstance(target, Value);
         }
 
         private struct SetterVisitorData

+ 1 - 1
src/Avalonia.Base/Styling/Styles.cs

@@ -160,7 +160,7 @@ namespace Avalonia.Styling
 
             for (var i = Count - 1; i >= 0; --i)
             {
-                if (this[i] is IResourceProvider p && p.TryGetResource(key, out value))
+                if (this[i].TryGetResource(key, out value))
                 {
                     return true;
                 }

+ 0 - 26
src/Avalonia.Base/Threading/IDispatcher.cs

@@ -26,15 +26,6 @@ namespace Avalonia.Threading
         /// <param name="priority">The priority with which to invoke the method.</param>
         void Post(Action action, DispatcherPriority priority = default);
 
-        /// <summary>
-        /// Posts an action that will be invoked on the dispatcher thread.
-        /// </summary>
-        /// <typeparam name="T">type of argument</typeparam>
-        /// <param name="action">The method to call.</param>
-        /// <param name="arg">The argument of method to call.</param>
-        /// <param name="priority">The priority with which to invoke the method.</param>
-        void Post<T>(Action<T> action, T arg, DispatcherPriority priority = default);
-
         /// <summary>
         /// Invokes a action on the dispatcher thread.
         /// </summary>
@@ -43,14 +34,6 @@ namespace Avalonia.Threading
         /// <returns>A task that can be used to track the method's execution.</returns>
         Task InvokeAsync(Action action, DispatcherPriority priority = default);
 
-        /// <summary>
-        /// Invokes a method on the dispatcher thread.
-        /// </summary>
-        /// <param name="function">The method.</param>
-        /// <param name="priority">The priority with which to invoke the method.</param>
-        /// <returns>A task that can be used to track the method's execution.</returns>
-        Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = default);
-
         /// <summary>
         /// Queues the specified work to run on the dispatcher thread and returns a proxy for the
         /// task returned by <paramref name="function"/>.
@@ -59,14 +42,5 @@ namespace Avalonia.Threading
         /// <param name="priority">The priority with which to invoke the method.</param>
         /// <returns>A task that represents a proxy for the task returned by <paramref name="function"/>.</returns>
         Task InvokeAsync(Func<Task> function, DispatcherPriority priority = default);
-
-        /// <summary>
-        /// Queues the specified work to run on the dispatcher thread and returns a proxy for the
-        /// task returned by <paramref name="function"/>.
-        /// </summary>
-        /// <param name="function">The work to execute asynchronously.</param>
-        /// <param name="priority">The priority with which to invoke the method.</param>
-        /// <returns>A task that represents a proxy for the task returned by <paramref name="function"/>.</returns>
-        Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> function, DispatcherPriority priority = default);
     }
 }

+ 1 - 2
src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs

@@ -5,12 +5,11 @@ namespace Avalonia.Threading
     public class ThreadSafeObjectPool<T> where T : class, new()
     {
         private Stack<T> _stack = new Stack<T>();
-        private object _lock = new object();
         public static ThreadSafeObjectPool<T> Default { get; } = new ThreadSafeObjectPool<T>();
 
         public T Get()
         {
-            lock (_lock)
+            lock (_stack)
             {
                 if(_stack.Count == 0)
                     return new T();

+ 0 - 32
src/Avalonia.Base/Utilities/IAvaloniaPropertyVisitor.cs

@@ -1,32 +0,0 @@
-namespace Avalonia.Utilities
-{
-    /// <summary>
-    /// A visitor to resolve an untyped <see cref="AvaloniaProperty"/> to a typed property.
-    /// </summary>
-    /// <typeparam name="TData">The type of user data passed.</typeparam>
-    /// <remarks>
-    /// Pass an instance that implements this interface to
-    /// <see cref="AvaloniaProperty.Accept{TData}(IAvaloniaPropertyVisitor{TData}, ref TData)"/>
-    /// in order to resolve un untyped <see cref="AvaloniaProperty"/> to a typed
-    /// <see cref="StyledPropertyBase{TValue}"/> or <see cref="DirectPropertyBase{TValue}"/>.
-    /// </remarks>
-    public interface IAvaloniaPropertyVisitor<TData>
-        where TData : struct
-    {
-        /// <summary>
-        /// Called when the property is a styled property.
-        /// </summary>
-        /// <typeparam name="T">The property value type.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="data">The user data.</param>
-        void Visit<T>(StyledPropertyBase<T> property, ref TData data);
-
-        /// <summary>
-        /// Called when the property is a direct property.
-        /// </summary>
-        /// <typeparam name="T">The property value type.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="data">The user data.</param>
-        void Visit<T>(DirectPropertyBase<T> property, ref TData data);
-    }
-}

Некоторые файлы не были показаны из-за большого количества измененных файлов