浏览代码

Merge branch 'compiled-bindings' of https://github.com/jkoritzinsky/Avalonia into compiled-bindings

Jeremy Koritzinsky 6 年之前
父节点
当前提交
d415513341
共有 100 个文件被更改,包括 1920 次插入403 次删除
  1. 1 0
      Avalonia.sln.DotSettings
  2. 1 1
      build/Microsoft.Reactive.Testing.props
  3. 1 1
      build/ReactiveUI.props
  4. 1 1
      build/Rx.props
  5. 1 1
      build/System.Memory.props
  6. 2 1
      dirs.proj
  7. 1 1
      global.json
  8. 32 1
      native/Avalonia.Native/inc/avalonia-native.h
  9. 9 0
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  10. 8 0
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  11. 2 0
      native/Avalonia.Native/src/OSX/Screens.mm
  12. 12 0
      native/Avalonia.Native/src/OSX/common.h
  13. 147 4
      native/Avalonia.Native/src/OSX/main.mm
  14. 80 0
      native/Avalonia.Native/src/OSX/menu.h
  15. 305 0
      native/Avalonia.Native/src/OSX/menu.mm
  16. 0 6
      native/Avalonia.Native/src/OSX/platformthreading.mm
  17. 1 0
      native/Avalonia.Native/src/OSX/window.h
  18. 102 26
      native/Avalonia.Native/src/OSX/window.mm
  19. 3 0
      samples/BindingDemo/MainWindow.xaml
  20. 4 3
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  21. 1 1
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  22. 1 1
      samples/ControlCatalog.Android/MainActivity.cs
  23. 5 1
      samples/ControlCatalog.NetCore/Program.cs
  24. 15 3
      samples/ControlCatalog/App.xaml
  25. 13 0
      samples/ControlCatalog/App.xaml.cs
  26. 25 0
      samples/ControlCatalog/DecoratedWindow.xaml
  27. 2 1
      samples/ControlCatalog/MainView.xaml
  28. 30 1
      samples/ControlCatalog/MainWindow.xaml
  29. 14 0
      samples/ControlCatalog/MainWindow.xaml.cs
  30. 8 1
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  31. 1 1
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  32. 1 1
      samples/ControlCatalog/Pages/ListBoxPage.xaml.cs
  33. 4 1
      samples/ControlCatalog/Pages/MenuPage.xaml
  34. 5 2
      samples/ControlCatalog/Pages/ScreenPage.cs
  35. 33 6
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  36. 3 2
      samples/RenderDemo/ViewModels/MainWindowViewModel.cs
  37. 10 10
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  38. 3 3
      src/Android/Avalonia.Android/AndroidPlatform.cs
  39. 1 1
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  40. 4 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  41. 1 1
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  42. 6 1
      src/Avalonia.Animation/Animatable.cs
  43. 8 8
      src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs
  44. 10 13
      src/Avalonia.Base/AvaloniaObject.cs
  45. 4 5
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  46. 1 1
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  47. 7 3
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  48. 1 1
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  49. 5 0
      src/Avalonia.Base/Data/Core/SettableNode.cs
  50. 71 0
      src/Avalonia.Base/Logging/ILogSink.cs
  51. 21 104
      src/Avalonia.Base/Logging/Logger.cs
  52. 174 0
      src/Avalonia.Base/Logging/ParametrizedLogger.cs
  53. 16 7
      src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
  54. 14 9
      src/Avalonia.Base/PriorityBindingEntry.cs
  55. 47 15
      src/Avalonia.Base/PriorityLevel.cs
  56. 1 1
      src/Avalonia.Base/PriorityValue.cs
  57. 1 1
      src/Avalonia.Base/Utilities/SingleOrDictionary.cs
  58. 5 3
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  59. 25 25
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  60. 2 2
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  61. 2 2
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  62. 4 4
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  63. 2 2
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  64. 29 21
      src/Avalonia.Controls/AppBuilderBase.cs
  65. 18 1
      src/Avalonia.Controls/Application.cs
  66. 1 2
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  67. 9 9
      src/Avalonia.Controls/AutoCompleteBox.cs
  68. 81 4
      src/Avalonia.Controls/Button.cs
  69. 12 13
      src/Avalonia.Controls/Calendar/Calendar.cs
  70. 1 1
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  71. 1 1
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  72. 12 10
      src/Avalonia.Controls/Calendar/DatePicker.cs
  73. 0 12
      src/Avalonia.Controls/Carousel.cs
  74. 2 2
      src/Avalonia.Controls/ComboBox.cs
  75. 1 1
      src/Avalonia.Controls/ContentControl.cs
  76. 1 1
      src/Avalonia.Controls/ContextMenu.cs
  77. 1 1
      src/Avalonia.Controls/DataValidationErrors.cs
  78. 1 1
      src/Avalonia.Controls/Decorator.cs
  79. 2 2
      src/Avalonia.Controls/DropDown.cs
  80. 1 1
      src/Avalonia.Controls/Expander.cs
  81. 2 2
      src/Avalonia.Controls/ItemsControl.cs
  82. 5 3
      src/Avalonia.Controls/LayoutTransformControl.cs
  83. 1 1
      src/Avalonia.Controls/MenuBase.cs
  84. 8 8
      src/Avalonia.Controls/MenuItem.cs
  85. 85 0
      src/Avalonia.Controls/NativeMenu.Export.cs
  86. 60 0
      src/Avalonia.Controls/NativeMenu.cs
  87. 36 0
      src/Avalonia.Controls/NativeMenuBar.cs
  88. 162 0
      src/Avalonia.Controls/NativeMenuItem.cs
  89. 23 0
      src/Avalonia.Controls/NativeMenuItemBase.cs
  90. 10 0
      src/Avalonia.Controls/NativeMenuItemSeperator.cs
  91. 1 1
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  92. 18 0
      src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
  93. 4 1
      src/Avalonia.Controls/Platform/Screen.cs
  94. 2 2
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  95. 3 3
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  96. 1 1
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  97. 1 1
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  98. 3 3
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  99. 1 1
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  100. 1 1
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

+ 1 - 0
Avalonia.sln.DotSettings

@@ -3,6 +3,7 @@
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=3E53A01A_002DB331_002D47F3_002DB828_002D4A5717E77A24_002Fd_003Aglass/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=DECLSPEC_005FPROPERTY/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=ENUM/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>

+ 1 - 1
build/Microsoft.Reactive.Testing.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="Microsoft.Reactive.Testing" Version="4.0.0" />
+    <PackageReference Include="Microsoft.Reactive.Testing" Version="4.1.6" />
   </ItemGroup>
 </Project>

+ 1 - 1
build/ReactiveUI.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="reactiveui" Version="9.0.1" />
+    <PackageReference Include="ReactiveUI" Version="10.3.6" />
   </ItemGroup>
 </Project>

+ 1 - 1
build/Rx.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="System.Reactive" Version="4.0.0" />
+    <PackageReference Include="System.Reactive" Version="4.1.6" />
   </ItemGroup>
 </Project>

+ 1 - 1
build/System.Memory.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="System.Memory" Version="4.5.1" />
+    <PackageReference Include="System.Memory" Version="4.5.3" />
   </ItemGroup>
 </Project>

+ 2 - 1
dirs.proj

@@ -8,7 +8,8 @@
     <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
     <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/XamlIl/**/*.*proj" />
   </ItemGroup>
-  <ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">
+  <!--<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">-->
+  <ItemGroup>
     <ProjectReference Remove="src/Android/**/*.*proj" />
     <ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
   </ItemGroup>

+ 1 - 1
global.json

@@ -1,7 +1,7 @@
 {
     "msbuild-sdks": {
         "Microsoft.Build.Traversal": "1.0.43",
-        "MSBuild.Sdk.Extras": "1.6.65",
+        "MSBuild.Sdk.Extras": "2.0.46",
         "AggregatePackage.NuGet.Sdk" : "0.1.12"
     }
 }

+ 32 - 1
native/Avalonia.Native/inc/avalonia-native.h

@@ -22,6 +22,8 @@ struct IAvnGlContext;
 struct IAvnGlDisplay;
 struct IAvnGlSurfaceRenderTarget;
 struct IAvnGlSurfaceRenderingSession;
+struct IAvnAppMenu;
+struct IAvnAppMenuItem;
 
 struct AvnSize
 {
@@ -52,6 +54,7 @@ struct AvnScreen
 {
     AvnRect Bounds;
     AvnRect WorkingArea;
+    float PixelDensity;
     bool Primary;
 };
 
@@ -172,6 +175,11 @@ public:
     virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
     virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
     virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0;
+    virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0;
+    virtual HRESULT SetAppMenu(IAvnAppMenu* menu) = 0;
+    virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0;
+    virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0;
+    virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) = 0;
 };
 
 AVNCOM(IAvnString, 17) : IUnknown
@@ -187,7 +195,6 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
     virtual HRESULT Close() = 0;
     virtual HRESULT Activate () = 0;
     virtual HRESULT GetClientSize(AvnSize*ret) = 0;
-    virtual HRESULT GetMaxClientSize(AvnSize* ret) = 0;
     virtual HRESULT GetScaling(double*ret)=0;
     virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) = 0;
     virtual HRESULT Resize(double width, double height) = 0;
@@ -203,6 +210,8 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
     virtual HRESULT SetCursor(IAvnCursor* cursor) = 0;
     virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0;
     virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0;
+    virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0;
+    virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0;
     virtual bool TryLock() = 0;
     virtual void Unlock() = 0;
 };
@@ -258,6 +267,7 @@ AVNCOM(IAvnWindowEvents, 06) : IAvnWindowBaseEvents
 AVNCOM(IAvnMacOptions, 07) : IUnknown
 {
     virtual HRESULT SetShowInDock(int show) = 0;
+    virtual HRESULT SetApplicationTitle (void* utf8string) = 0;
 };
 
 AVNCOM(IAvnActionCallback, 08) : IUnknown
@@ -367,4 +377,25 @@ AVNCOM(IAvnGlSurfaceRenderingSession, 16) : IUnknown
     virtual HRESULT GetScaling(double* ret) = 0;
 };
 
+AVNCOM(IAvnAppMenu, 17) : IUnknown
+{
+    virtual HRESULT AddItem (IAvnAppMenuItem* item) = 0;
+    virtual HRESULT RemoveItem (IAvnAppMenuItem* item) = 0;
+    virtual HRESULT SetTitle (void* utf8String) = 0;
+    virtual HRESULT Clear () = 0;
+};
+
+AVNCOM(IAvnPredicateCallback, 18) : IUnknown
+{
+    virtual bool Evaluate() = 0;
+};
+
+AVNCOM(IAvnAppMenuItem, 19) : IUnknown
+{
+    virtual HRESULT SetSubMenu (IAvnAppMenu* menu) = 0;
+    virtual HRESULT SetTitle (void* utf8String) = 0;
+    virtual HRESULT SetGesture (void* utf8String, AvnInputModifiers modifiers) = 0;
+    virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0;
+};
+
 extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();

+ 9 - 0
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@@ -8,10 +8,12 @@
 
 /* Begin PBXBuildFile section */
 		1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
+		37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
 		37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
 		37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
 		37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
 		37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
+		520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
 		5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
 		5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
 		AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
@@ -24,6 +26,7 @@
 
 /* Begin PBXFileReference section */
 		1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; };
+		37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; };
 		379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
 		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>"; };
@@ -32,6 +35,7 @@
 		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>"; };
+		520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = "<group>"; };
 		5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
 		5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
 		5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
@@ -85,6 +89,8 @@
 				AB661C1F2148286E00291242 /* window.mm */,
 				37C09D8A21581EF2006A6758 /* window.h */,
 				AB00E4F62147CA920032A60A /* main.mm */,
+				37155CE3233C00EB0034DCE9 /* menu.h */,
+				520624B222973F4100C4DCEF /* menu.mm */,
 				37A517B22159597E00FBA241 /* Screens.mm */,
 				37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
 				AB7A61F02147C815003C5833 /* Products */,
@@ -107,6 +113,7 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				37155CE4233C00EB0034DCE9 /* menu.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -150,6 +157,7 @@
 			developmentRegion = English;
 			hasScannedForEncodings = 0;
 			knownRegions = (
+				English,
 				en,
 			);
 			mainGroup = AB7A61E62147C814003C5833;
@@ -173,6 +181,7 @@
 				37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
 				AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
 				37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
+				520624B322973F4100C4DCEF /* menu.mm in Sources */,
 				37A517B32159597E00FBA241 /* Screens.mm in Sources */,
 				AB00E4F72147CA920032A60A /* main.mm in Sources */,
 				37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,

+ 8 - 0
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 2 - 0
native/Avalonia.Native/src/OSX/Screens.mm

@@ -38,6 +38,8 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
             ret->WorkingArea.Height = [screen visibleFrame].size.height;
             ret->WorkingArea.Width = [screen visibleFrame].size.width;
             
+            ret->PixelDensity = [screen backingScaleFactor];
+            
             ret->Primary = index == 0;
             
             return S_OK;

+ 12 - 0
native/Avalonia.Native/src/OSX/common.h

@@ -19,6 +19,13 @@ extern IAvnClipboard* CreateClipboard();
 extern IAvnCursorFactory* CreateCursorFactory();
 extern IAvnGlFeature* GetGlFeature();
 extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
+extern IAvnAppMenu* CreateAppMenu();
+extern IAvnAppMenuItem* CreateAppMenuItem();
+extern IAvnAppMenuItem* CreateAppMenuItemSeperator();
+extern void SetAppMenu (NSString* appName, IAvnAppMenu* appMenu);
+extern IAvnAppMenu* GetAppMenu ();
+extern NSMenuItem* GetAppMenuItem ();
+
 extern void InitializeAvnApp();
 extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
 extern NSPoint ToNSPoint (AvnPoint p);
@@ -40,4 +47,9 @@ template<typename T> inline T* objc_cast(id from) {
     return nil;
 }
 
+@interface ActionCallback : NSObject
+- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
+- (void) action;
+@end
+
 #endif

+ 147 - 4
native/Avalonia.Native/src/OSX/main.mm

@@ -5,10 +5,121 @@
 #define COM_GUIDS_MATERIALIZE
 #include "common.h"
 
+static NSString* s_appTitle = @"Avalonia";
+
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+void SetProcessName(NSString* appTitle) {
+    s_appTitle = appTitle;
+    
+    CFStringRef process_name = (__bridge CFStringRef)appTitle;
+    
+    if (!process_name || CFStringGetLength(process_name) == 0) {
+        //NOTREACHED() << "SetProcessName given bad name.";
+        return;
+    }
+    
+    if (![NSThread isMainThread]) {
+        //NOTREACHED() << "Should only set process name from main thread.";
+        return;
+    }
+    
+    // Warning: here be dragons! This is SPI reverse-engineered from WebKit's
+    // plugin host, and could break at any time (although realistically it's only
+    // likely to break in a new major release).
+    // When 10.7 is available, check that this still works, and update this
+    // comment for 10.8.
+    
+    // Private CFType used in these LaunchServices calls.
+    typedef CFTypeRef PrivateLSASN;
+    typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
+    typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
+                                                            CFStringRef,
+                                                            CFStringRef,
+                                                            CFDictionaryRef*);
+    
+    static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
+    NULL;
+    static LSSetApplicationInformationItemType
+    ls_set_application_information_item_func = NULL;
+    static CFStringRef ls_display_name_key = NULL;
+    
+    static bool did_symbol_lookup = false;
+    if (!did_symbol_lookup) {
+        did_symbol_lookup = true;
+        CFBundleRef launch_services_bundle =
+        CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
+        if (!launch_services_bundle) {
+            //LOG(ERROR) << "Failed to look up LaunchServices bundle";
+            return;
+        }
+        
+        ls_get_current_application_asn_func =
+        reinterpret_cast<LSGetCurrentApplicationASNType>(
+                                                         CFBundleGetFunctionPointerForName(
+                                                                                           launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
+        if (!ls_get_current_application_asn_func){}
+        //LOG(ERROR) << "Could not find _LSGetCurrentApplicationASN";
+        
+        ls_set_application_information_item_func =
+        reinterpret_cast<LSSetApplicationInformationItemType>(
+                                                              CFBundleGetFunctionPointerForName(
+                                                                                                launch_services_bundle,
+                                                                                                CFSTR("_LSSetApplicationInformationItem")));
+        if (!ls_set_application_information_item_func){}
+        //LOG(ERROR) << "Could not find _LSSetApplicationInformationItem";
+        
+        CFStringRef* key_pointer = reinterpret_cast<CFStringRef*>(
+                                                                  CFBundleGetDataPointerForName(launch_services_bundle,
+                                                                                                CFSTR("_kLSDisplayNameKey")));
+        ls_display_name_key = key_pointer ? *key_pointer : NULL;
+        if (!ls_display_name_key){}
+        //LOG(ERROR) << "Could not find _kLSDisplayNameKey";
+        
+        // Internally, this call relies on the Mach ports that are started up by the
+        // Carbon Process Manager.  In debug builds this usually happens due to how
+        // the logging layers are started up; but in release, it isn't started in as
+        // much of a defined order.  So if the symbols had to be loaded, go ahead
+        // and force a call to make sure the manager has been initialized and hence
+        // the ports are opened.
+        ProcessSerialNumber psn;
+        GetCurrentProcess(&psn);
+    }
+    if (!ls_get_current_application_asn_func ||
+        !ls_set_application_information_item_func ||
+        !ls_display_name_key) {
+        return;
+    }
+    
+    PrivateLSASN asn = ls_get_current_application_asn_func();
+    // Constant used by WebKit; what exactly it means is unknown.
+    const int magic_session_constant = -2;
+    OSErr err =
+    ls_set_application_information_item_func(magic_session_constant, asn,
+                                             ls_display_name_key,
+                                             process_name,
+                                             NULL /* optional out param */);
+    //LOG_IF(ERROR, err) << "Call to set process name failed, err " << err;
+}
+
 class MacOptions : public ComSingleObject<IAvnMacOptions, &IID_IAvnMacOptions>
 {
 public:
     FORWARD_IUNKNOWN()
+    
+    virtual HRESULT SetApplicationTitle(void* utf8String) override
+    {
+        auto appTitle = [NSString stringWithUTF8String:(const char*)utf8String];
+        
+        [[NSProcessInfo processInfo] setProcessName:appTitle];
+        
+        
+        SetProcessName(appTitle);
+        
+        return S_OK;
+    }
+    
     virtual HRESULT SetShowInDock(int show)  override
     {
         AvnDesiredActivationPolicy = show
@@ -17,8 +128,6 @@ public:
     }
 };
 
-
-
 /// See "Using POSIX Threads in a Cocoa Application" section here:
 /// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024
 @interface ThreadingInitializer : NSObject
@@ -43,8 +152,6 @@ public:
     close(_fds[0]);
     close(_fds[1]);
 }
-
-
 @end
 
 
@@ -123,6 +230,42 @@ public:
         *ppv = rv;
         return S_OK;
     }
+    
+    virtual HRESULT CreateMenu (IAvnAppMenu** ppv) override
+    {
+        *ppv = ::CreateAppMenu();
+        return S_OK;
+    }
+    
+    virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) override
+    {
+        *ppv = ::CreateAppMenuItem();
+        return S_OK;
+    }
+    
+    virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) override
+    {
+        *ppv = ::CreateAppMenuItemSeperator();
+        return S_OK;
+    }
+    
+    virtual HRESULT SetAppMenu (IAvnAppMenu* appMenu) override
+    {
+        ::SetAppMenu(s_appTitle, appMenu);
+        return S_OK;
+    }
+    
+    virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) override
+    {
+        if(retOut == nullptr)
+        {
+            return E_POINTER;
+        }
+        
+        *retOut = ::GetAppMenu();
+        
+        return S_OK;
+    }
 };
 
 extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

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

@@ -0,0 +1,80 @@
+//
+//  menu.h
+//  Avalonia.Native.OSX
+//
+//  Created by Dan Walmsley on 01/08/2019.
+//  Copyright © 2019 Avalonia. All rights reserved.
+//
+
+#ifndef menu_h
+#define menu_h
+
+#include "common.h"
+
+class AvnAppMenuItem;
+class AvnAppMenu;
+
+@interface AvnMenu : NSMenu // for some reason it doesnt detect nsmenu here but compiler doesnt complain
+- (void)setMenu:(NSMenu*) menu;
+@end
+
+@interface AvnMenuItem : NSMenuItem
+- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem;
+- (void)didSelectItem:(id)sender;
+@end
+
+class AvnAppMenuItem : public ComSingleObject<IAvnAppMenuItem, &IID_IAvnAppMenuItem>
+{
+private:
+    NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem
+    IAvnActionCallback* _callback;
+    IAvnPredicateCallback* _predicate;
+    bool _isSeperator;
+    
+public:
+    FORWARD_IUNKNOWN()
+    
+    AvnAppMenuItem(bool isSeperator);
+    
+    NSMenuItem* GetNative();
+    
+    virtual HRESULT SetSubMenu (IAvnAppMenu* menu) override;
+    
+    virtual HRESULT SetTitle (void* utf8String) override;
+    
+    virtual HRESULT SetGesture (void* key, AvnInputModifiers modifiers) override;
+    
+    virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) override;
+    
+    bool EvaluateItemEnabled();
+    
+    void RaiseOnClicked();
+};
+
+
+class AvnAppMenu : public ComSingleObject<IAvnAppMenu, &IID_IAvnAppMenu>
+{
+private:
+    AvnMenu* _native;
+    
+public:
+    FORWARD_IUNKNOWN()
+    
+    AvnAppMenu();
+    
+    AvnAppMenu(AvnMenu* native);
+    
+    AvnMenu* GetNative();
+    
+    virtual HRESULT AddItem (IAvnAppMenuItem* item) override;
+    
+    virtual HRESULT RemoveItem (IAvnAppMenuItem* item) override;
+    
+    virtual HRESULT SetTitle (void* utf8String) override;
+    
+    virtual HRESULT Clear () override;
+};
+
+
+#endif
+

+ 305 - 0
native/Avalonia.Native/src/OSX/menu.mm

@@ -0,0 +1,305 @@
+
+#include "common.h"
+#include "menu.h"
+
+@implementation AvnMenu
+@end
+
+@implementation AvnMenuItem
+{
+    AvnAppMenuItem* _item;
+}
+
+- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem
+{
+    if(self != nil)
+    {
+        _item = menuItem;
+        self = [super initWithTitle:@""
+                             action:@selector(didSelectItem:)
+                      keyEquivalent:@""];
+        
+        [self setEnabled:YES];
+        
+        [self setTarget:self];
+    }
+    
+    return self;
+}
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
+{
+    if([self submenu] != nil)
+    {
+        return YES;
+    }
+    
+    return _item->EvaluateItemEnabled();
+}
+
+- (void)didSelectItem:(nullable id)sender
+{
+    _item->RaiseOnClicked();
+}
+@end
+
+AvnAppMenuItem::AvnAppMenuItem(bool isSeperator)
+{
+    _isSeperator = isSeperator;
+    
+    if(isSeperator)
+    {
+        _native = [NSMenuItem separatorItem];
+    }
+    else
+    {
+        _native = [[AvnMenuItem alloc] initWithAvnAppMenuItem: this];
+    }
+    
+    _callback = nullptr;
+}
+
+NSMenuItem* AvnAppMenuItem::GetNative()
+{
+    return _native;
+}
+
+HRESULT AvnAppMenuItem::SetSubMenu (IAvnAppMenu* menu)
+{
+    auto nsMenu = dynamic_cast<AvnAppMenu*>(menu)->GetNative();
+    
+    [_native setSubmenu: nsMenu];
+    
+    return S_OK;
+}
+
+HRESULT AvnAppMenuItem::SetTitle (void* utf8String)
+{
+    if (utf8String != nullptr)
+    {
+        [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
+    }
+    
+    return S_OK;
+}
+
+HRESULT AvnAppMenuItem::SetGesture (void* key, AvnInputModifiers modifiers)
+{
+    NSEventModifierFlags flags = 0;
+    
+    if (modifiers & Control)
+        flags |= NSEventModifierFlagControl;
+    if (modifiers & Shift)
+        flags |= NSEventModifierFlagShift;
+    if (modifiers & Alt)
+        flags |= NSEventModifierFlagOption;
+    if (modifiers & Windows)
+        flags |= NSEventModifierFlagCommand;
+    
+    [_native setKeyEquivalent:[NSString stringWithUTF8String:(const char*)key]];
+    [_native setKeyEquivalentModifierMask:flags];
+    
+    return S_OK;
+}
+
+HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback)
+{
+    _predicate = predicate;
+    _callback = callback;
+    return S_OK;
+}
+
+bool AvnAppMenuItem::EvaluateItemEnabled()
+{
+    if(_predicate != nullptr)
+    {
+        auto result = _predicate->Evaluate ();
+        
+        return result;
+    }
+    
+    return false;
+}
+
+void AvnAppMenuItem::RaiseOnClicked()
+{
+    if(_callback != nullptr)
+    {
+        _callback->Run();
+    }
+}
+
+AvnAppMenu::AvnAppMenu()
+{
+    _native = [AvnMenu new];
+}
+
+AvnAppMenu::AvnAppMenu(AvnMenu* native)
+{
+    _native = native;
+}
+
+AvnMenu* AvnAppMenu::GetNative()
+{
+    return _native;
+}
+
+HRESULT AvnAppMenu::AddItem (IAvnAppMenuItem* item)
+{
+    auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
+    
+    if(avnMenuItem != nullptr)
+    {
+        [_native addItem: avnMenuItem->GetNative()];
+    }
+    
+    return S_OK;
+}
+
+HRESULT AvnAppMenu::RemoveItem (IAvnAppMenuItem* item)
+{
+    auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
+    
+    if(avnMenuItem != nullptr)
+    {
+        [_native removeItem:avnMenuItem->GetNative()];
+    }
+    
+    return S_OK;
+}
+
+HRESULT AvnAppMenu::SetTitle (void* utf8String)
+{
+    if (utf8String != nullptr)
+    {
+        [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
+    }
+    
+    return S_OK;
+}
+
+HRESULT AvnAppMenu::Clear()
+{
+    [_native removeAllItems];
+    return S_OK;
+}
+
+extern IAvnAppMenu* CreateAppMenu()
+{
+    @autoreleasepool
+    {
+        id menuBar = [NSMenu new];
+        return new AvnAppMenu(menuBar);
+    }
+}
+
+extern IAvnAppMenuItem* CreateAppMenuItem()
+{
+    @autoreleasepool
+    {
+        return new AvnAppMenuItem(false);
+    }
+}
+
+extern IAvnAppMenuItem* CreateAppMenuItemSeperator()
+{
+    @autoreleasepool
+    {
+        return new AvnAppMenuItem(true);
+    }
+}
+
+static IAvnAppMenu* s_appMenu = nullptr;
+static NSMenuItem* s_appMenuItem = nullptr;
+
+extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu)
+{
+    s_appMenu = menu;
+    
+    if(s_appMenu != nullptr)
+    {
+        auto nativeMenu = dynamic_cast<AvnAppMenu*>(s_appMenu);
+        
+        auto currentMenu = [s_appMenuItem menu];
+        
+        if (currentMenu != nullptr)
+        {
+            [currentMenu removeItem:s_appMenuItem];
+        }
+        
+        s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0];
+        
+        if (currentMenu == nullptr)
+        {
+            currentMenu = [s_appMenuItem menu];
+        }
+        
+        [[s_appMenuItem menu] removeItem:s_appMenuItem];
+        
+        [currentMenu insertItem:s_appMenuItem atIndex:0];
+        
+        if([s_appMenuItem submenu] == nullptr)
+        {
+            [s_appMenuItem setSubmenu:[NSMenu new]];
+        }
+        
+        auto appMenu  = [s_appMenuItem submenu];
+        
+        [appMenu addItem:[NSMenuItem separatorItem]];
+        
+        // Services item and menu
+        auto servicesItem = [[NSMenuItem alloc] init];
+        servicesItem.title = @"Services";
+        NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"];
+        servicesItem.submenu = servicesMenu;
+        [NSApplication sharedApplication].servicesMenu = servicesMenu;
+        [appMenu addItem:servicesItem];
+        
+        [appMenu addItem:[NSMenuItem separatorItem]];
+        
+        // Hide Application
+        auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"];
+        
+        [appMenu addItem:hideItem];
+        
+        // Hide Others
+        auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
+                                                       action:@selector(hideOtherApplications:)
+                                                keyEquivalent:@"h"];
+        
+        hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption;
+        [appMenu addItem:hideAllOthersItem];
+        
+        // Show All
+        auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All"
+                                                 action:@selector(unhideAllApplications:)
+                                          keyEquivalent:@""];
+        
+        [appMenu addItem:showAllItem];
+        
+        [appMenu addItem:[NSMenuItem separatorItem]];
+        
+        // Quit Application
+        auto quitItem = [[NSMenuItem alloc] init];
+        quitItem.title = [@"Quit " stringByAppendingString:appName];
+        quitItem.keyEquivalent = @"q";
+        quitItem.action = @selector(terminate:);
+        [appMenu addItem:quitItem];
+    }
+    else
+    {
+        s_appMenuItem = nullptr;
+    }
+}
+
+extern IAvnAppMenu* GetAppMenu ()
+{
+    return s_appMenu;
+}
+
+extern NSMenuItem* GetAppMenuItem ()
+{
+    return s_appMenuItem;
+}
+
+

+ 0 - 6
native/Avalonia.Native/src/OSX/platformthreading.mm

@@ -10,12 +10,6 @@ class PlatformThreadingInterface;
 -(Signaler*) init;
 @end
 
-
-@interface ActionCallback : NSObject
-- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
-- (void) action;
-@end
-
 @implementation ActionCallback
 {
     ComPtr<IAvnActionCallback> _callback;

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

@@ -20,6 +20,7 @@ class WindowBaseImpl;
 -(void) pollModalSession: (NSModalSession _Nonnull) session;
 -(void) restoreParentWindow;
 -(bool) shouldTryToHandleEvents;
+-(void) applyMenu:(NSMenu *)menu;
 @end
 
 struct INSWindowHolder

+ 102 - 26
native/Avalonia.Native/src/OSX/window.mm

@@ -5,6 +5,7 @@
 #include "window.h"
 #include "KeyTransform.h"
 #include "cursor.h"
+#include "menu.h"
 #include <OpenGL/gl.h>
 
 class SoftwareDrawingOperation
@@ -54,7 +55,6 @@ public:
     FORWARD_IUNKNOWN()
     virtual ~WindowBaseImpl()
     {
-        NSDebugLog(@"~WindowBaseImpl()");
         View = NULL;
         Window = NULL;
     }
@@ -64,9 +64,11 @@ public:
     SoftwareDrawingOperation CurrentSwDrawingOperation;
     AvnPoint lastPositionSet;
     NSString* _lastTitle;
+    IAvnAppMenu* _mainMenu;
     
     WindowBaseImpl(IAvnWindowBaseEvents* events)
     {
+        _mainMenu = nullptr;
         BaseEvents = events;
         View = [[AvnView alloc] initWithParent:this];
 
@@ -94,6 +96,7 @@ public:
             UpdateStyle();
             
             [Window makeKeyAndOrderFront:Window];
+            [NSApp activateIgnoringOtherApps:YES];
             
             [Window setTitle:_lastTitle];
             [Window setTitleVisibility:NSWindowTitleVisible];
@@ -123,6 +126,7 @@ public:
             if(Window != nullptr)
             {
                 [Window makeKeyWindow];
+                [NSApp activateIgnoringOtherApps:YES];
             }
         }
         
@@ -161,22 +165,6 @@ public:
         }
     }
     
-    virtual HRESULT GetMaxClientSize(AvnSize* ret) override
-    {
-        @autoreleasepool
-        {
-            if(ret == nullptr)
-                return E_POINTER;
-            
-            auto size = [NSScreen.screens objectAtIndex:0].frame.size;
-            
-            ret->Height = size.height;
-            ret->Width = size.width;
-            
-            return S_OK;
-        }
-    }
-    
     virtual HRESULT GetScaling (double* ret) override
     {
         @autoreleasepool
@@ -226,6 +214,31 @@ public:
         }
     }
     
+    virtual HRESULT SetMainMenu(IAvnAppMenu* menu) override
+    {
+        _mainMenu = menu;
+        
+        auto nativeMenu = dynamic_cast<AvnAppMenu*>(menu);
+        
+        auto nsmenu = nativeMenu->GetNative();
+        
+        [Window applyMenu:nsmenu];
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT ObtainMainMenu(IAvnAppMenu** ret) override
+    {
+        if(ret == nullptr)
+        {
+            return E_POINTER;
+        }
+        
+        *ret = _mainMenu;
+        
+        return S_OK;
+    }
+    
     virtual bool TryLock() override
     {
         @autoreleasepool
@@ -369,12 +382,9 @@ public:
 
     virtual void UpdateCursor()
     {
-        [View resetCursorRects];
         if (cursor != nil)
         {
-             auto rect = [Window frame];
-             [View addCursorRect:rect cursor:cursor];
-             [cursor set];
+            [cursor set];
         }
     }
     
@@ -416,8 +426,8 @@ private:
     INHERIT_INTERFACE_MAP(WindowBaseImpl)
     INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow)
     END_INTERFACE_MAP()
-    virtual ~WindowImpl(){
-        NSDebugLog(@"~WindowImpl");
+    virtual ~WindowImpl()
+    {
     }
     
     ComPtr<IAvnWindowEvents> WindowEvents;
@@ -425,6 +435,7 @@ private:
     {
         WindowEvents = events;
         [Window setCanBecomeKeyAndMain];
+        [Window disableCursorRects];
     }
     
     virtual HRESULT Show () override
@@ -664,10 +675,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 
 - (void)dealloc
 {
-    NSDebugLog(@"AvnView dealloc");
 }
 
-
 - (void)onClosed
 {
     _parent = NULL;
@@ -1063,11 +1072,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     ComPtr<WindowBaseImpl> _parent;
     bool _canBecomeKeyAndMain;
     bool _closed;
+    NSMenu* _menu;
+    bool _isAppMenuApplied;
 }
 
 - (void)dealloc
 {
-    NSDebugLog(@"AvnWindow dealloc");
 }
 
 - (void)pollModalSession:(nonnull NSModalSession)session
@@ -1087,6 +1097,32 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     }
 }
 
+-(void) applyMenu:(NSMenu *)menu
+{
+    if(menu == nullptr)
+    {
+        menu = [NSMenu new];
+    }
+    
+    _menu = menu;
+    
+    if ([self isKeyWindow])
+    {
+        auto appMenu = ::GetAppMenuItem();
+        
+        if(appMenu != nullptr)
+        {
+            [[appMenu menu] removeItem:appMenu];
+            
+            [_menu insertItem:appMenu atIndex:0];
+            
+            _isAppMenuApplied = true;
+        }
+        
+        [NSApp setMenu:menu];
+    }
+}
+
 -(void) setCanBecomeKeyAndMain
 {
     _canBecomeKeyAndMain = true;
@@ -1179,6 +1215,24 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 {
     if([self activateAppropriateChild: true])
     {
+        if(_menu == nullptr)
+        {
+            _menu = [NSMenu new];
+        }
+        
+        auto appMenu = ::GetAppMenuItem();
+        
+        if(appMenu != nullptr)
+        {
+            [[appMenu menu] removeItem:appMenu];
+            
+            [_menu insertItem:appMenu atIndex:0];
+            
+            _isAppMenuApplied = true;
+        }
+        
+        [NSApp setMenu:_menu];
+        
         _parent->BaseEvents->Activated();
         [super becomeKeyWindow];
     }
@@ -1223,6 +1277,28 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 {
     if(_parent)
         _parent->BaseEvents->Deactivated();
+    
+    auto appMenuItem = ::GetAppMenuItem();
+    
+    if(appMenuItem != nullptr)
+    {
+        auto appMenu = ::GetAppMenu();
+        
+        auto nativeAppMenu = dynamic_cast<AvnAppMenu*>(appMenu);
+        
+        [[appMenuItem menu] removeItem:appMenuItem];
+        
+        [nativeAppMenu->GetNative() addItem:appMenuItem];
+        
+        [NSApp setMenu:nativeAppMenu->GetNative()];
+    }
+    else
+    {
+        [NSApp setMenu:nullptr];
+    }
+    
+    // remove window menu items from appmenu?
+    
     [super resignKeyWindow];
 }
 

+ 3 - 0
samples/BindingDemo/MainWindow.xaml

@@ -25,6 +25,9 @@
             <TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue}" Name="first"/>
             <TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/>
             <TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/>
+            <!-- Removed due to #2983: reinstate when that's fixed.
+              <TextBox Watermark="One Way to Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>
+            -->
           </StackPanel>
           <StackPanel Margin="18" Spacing="4" Width="200">
             <TextBlock FontSize="16" Text="Collection Bindings"/>

+ 4 - 3
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@@ -1,10 +1,11 @@
 using System;
 using System.Collections.ObjectModel;
 using System.Linq;
-using ReactiveUI;
+using System.Reactive;
 using System.Reactive.Linq;
 using System.Threading.Tasks;
 using System.Threading;
+using ReactiveUI;
 
 namespace BindingDemo.ViewModels
 {
@@ -56,7 +57,7 @@ namespace BindingDemo.ViewModels
 
         public ObservableCollection<TestItem> Items { get; }
         public ObservableCollection<TestItem> SelectedItems { get; }
-        public ReactiveCommand ShuffleItems { get; }
+        public ReactiveCommand<Unit, Unit> ShuffleItems { get; }
 
         public string BooleanString
         {
@@ -89,7 +90,7 @@ namespace BindingDemo.ViewModels
         }
 
         public IObservable<DateTimeOffset> CurrentTimeObservable { get; }
-        public ReactiveCommand StringValueCommand { get; }
+        public ReactiveCommand<object, Unit> StringValueCommand { get; }
 
         public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel();
         public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel();

+ 1 - 1
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@@ -16,7 +16,7 @@
     <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
     <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
     <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
-    <TargetFrameworkVersion>v8.0</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
     <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

+ 1 - 1
samples/ControlCatalog.Android/MainActivity.cs

@@ -20,7 +20,7 @@ namespace ControlCatalog.Android
         {
             if (Avalonia.Application.Current == null)           
             {
-                AppBuilder.Configure(new App())
+                AppBuilder.Configure<App>()
                     .UseAndroid()
                     .SetupWithoutStarting();
                 Content = new MainView();

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

@@ -55,7 +55,11 @@ namespace ControlCatalog.NetCore
         public static AppBuilder BuildAvaloniaApp()
             => AppBuilder.Configure<App>()
                 .UsePlatformDetect()
-                .With(new X11PlatformOptions { EnableMultiTouch = true })
+                .With(new X11PlatformOptions
+                {
+                    EnableMultiTouch = true,
+                    UseDBusMenu = true
+                })
                 .With(new Win32PlatformOptions
                 {
                     EnableMultitouch = true,

+ 15 - 3
samples/ControlCatalog/App.xaml

@@ -2,9 +2,9 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              x:Class="ControlCatalog.App">
   <Application.Styles>
-      <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
-      <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
-      <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
+    <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
+    <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
+    <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
     <Style Selector="TextBlock.h1">
       <Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
       <Setter Property="FontWeight" Value="Medium"/>
@@ -17,4 +17,16 @@
     </Style>
     <StyleInclude Source="/SideBar.xaml"/>
   </Application.Styles>
+
+  <NativeMenu.Menu>
+      <NativeMenu>
+        <NativeMenuItem Header="Open" Clicked="OnOpenClicked"/>
+        <NativeMenuItem Header="Recent">
+          <NativeMenuItem.Menu>
+            <NativeMenu/>
+          </NativeMenuItem.Menu>
+        </NativeMenuItem>
+        <NativeMenuItem Header="Quit Avalonia" Gesture="CMD+Q"/>
+      </NativeMenu>
+  </NativeMenu.Menu>
 </Application>

+ 13 - 0
samples/ControlCatalog/App.xaml.cs

@@ -1,4 +1,6 @@
+using System;
 using Avalonia;
+using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
 
@@ -6,9 +8,20 @@ namespace ControlCatalog
 {
     public class App : Application
     {
+        private NativeMenu _recentMenu;
+
         public override void Initialize()
         {
             AvaloniaXamlLoader.Load(this);
+
+            Name = "Avalonia";
+
+            _recentMenu = (NativeMenu.GetMenu(this).Items[1] as NativeMenuItem).Menu;
+        }
+
+        public void OnOpenClicked(object sender, EventArgs args)
+        {
+            _recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
         }
 
         public override void OnFrameworkInitializationCompleted()

+ 25 - 0
samples/ControlCatalog/DecoratedWindow.xaml

@@ -3,6 +3,31 @@
         x:Class="ControlCatalog.DecoratedWindow"
         Title="Avalonia Control Gallery"
         xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window">
+        <NativeMenu.Menu>
+    <NativeMenu>
+      <NativeMenuItem Header="Decorated">
+        <NativeMenuItem.Menu>
+          <NativeMenu>
+            <NativeMenuItem Header="Open"/>
+            <NativeMenuItem Header="Recent">
+              <NativeMenuItem.Menu>
+                <NativeMenu/>
+              </NativeMenuItem.Menu>
+            </NativeMenuItem>
+            <NativeMenuItem Header="Quit Avalonia" Gesture="CMD+Q"/>
+          </NativeMenu>
+        </NativeMenuItem.Menu>
+      </NativeMenuItem>
+      <NativeMenuItem Header="Edit">
+        <NativeMenuItem.Menu>
+          <NativeMenu>
+            <NativeMenuItem Header="Copy"/>
+            <NativeMenuItem Header="Paste"/>
+          </NativeMenu>
+        </NativeMenuItem.Menu>
+      </NativeMenuItem>
+    </NativeMenu>
+  </NativeMenu.Menu>
     <Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5">
         <DockPanel  Grid.Column="1"  Grid.Row="1" >
             <Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto">

+ 2 - 1
samples/ControlCatalog/MainView.xaml

@@ -34,7 +34,8 @@
       <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
       <TabItem Header="Image"><pages:ImagePage/></TabItem>
       <TabItem Header="ItemsRepeater"
-               ScrollViewer.VerticalScrollBarVisibility="Disabled">
+               ScrollViewer.VerticalScrollBarVisibility="Disabled"
+               ScrollViewer.HorizontalScrollBarVisibility="Disabled">
         <pages:ItemsRepeaterPage/>
       </TabItem>
       <TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>

+ 30 - 1
samples/ControlCatalog/MainWindow.xaml

@@ -8,7 +8,36 @@
         xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
         xmlns:v="clr-namespace:ControlCatalog.Views"
         x:Class="ControlCatalog.MainWindow">
-    <Window.DataTemplates>
+
+  <NativeMenu.Menu>
+    <NativeMenu>
+      <NativeMenuItem Header="File">
+        <NativeMenuItem.Menu>
+          <NativeMenu>
+            <NativeMenuItem Header="Open" Clicked="OnOpenClicked"/>
+            <NativeMenuItemSeperator/>
+            <NativeMenuItem Header="Recent">
+              <NativeMenuItem.Menu>
+                <NativeMenu/>
+              </NativeMenuItem.Menu>
+            </NativeMenuItem>
+            <NativeMenuItemSeperator/>
+            <NativeMenuItem Header="Quit Avalonia" Clicked="OnCloseClicked" Gesture="CMD+Q"/>
+          </NativeMenu>
+        </NativeMenuItem.Menu>
+      </NativeMenuItem>
+      <NativeMenuItem Header="Edit">
+        <NativeMenuItem.Menu>
+          <NativeMenu>
+            <NativeMenuItem Header="Copy"/>
+            <NativeMenuItem Header="Paste"/>
+          </NativeMenu>
+        </NativeMenuItem.Menu>
+      </NativeMenuItem>
+    </NativeMenu>
+  </NativeMenu.Menu>
+
+  <Window.DataTemplates>
         <DataTemplate DataType="vm:NotificationViewModel">
             <v:CustomNotificationView />
         </DataTemplate>

+ 14 - 0
samples/ControlCatalog/MainWindow.xaml.cs

@@ -14,6 +14,7 @@ namespace ControlCatalog
     public class MainWindow : Window
     {
         private WindowNotificationManager _notificationArea;
+        private NativeMenu _recentMenu;
 
         public MainWindow()
         {
@@ -29,8 +30,21 @@ namespace ControlCatalog
             };
 
             DataContext = new MainWindowViewModel(_notificationArea);
+            _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
         }
 
+        public void OnOpenClicked(object sender, EventArgs args)
+        {
+            _recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
+        }
+        
+        public void OnCloseClicked(object sender, EventArgs args)
+        {
+            Close();
+        }
+
+
+
         private void InitializeComponent()
         {
             // TODO: iOS does not support dynamically loading assemblies

+ 8 - 1
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@@ -14,12 +14,19 @@
         <ComboBoxItem>UniformGrid - Horizontal</ComboBoxItem>
       </ComboBox>
       <Button Command="{Binding AddItem}">Add Item</Button>
+      <Button Command="{Binding RandomizeHeights}">Randomize Heights</Button>
     </StackPanel>
     <Border BorderThickness="1" BorderBrush="{DynamicResource ThemeBorderMidBrush}" Margin="0 0 0 16">
       <ScrollViewer Name="scroller"
                     HorizontalScrollBarVisibility="Auto"
                     VerticalScrollBarVisibility="Auto">
-        <ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}"/>
+        <ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}">
+          <ItemsRepeater.ItemTemplate>
+            <DataTemplate>
+              <TextBlock Height="{Binding Height}" Text="{Binding Text}"/>
+            </DataTemplate>
+          </ItemsRepeater.ItemTemplate>
+        </ItemsRepeater>
       </ScrollViewer>
     </Border>
   </DockPanel>

+ 1 - 1
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@@ -74,7 +74,7 @@ namespace ControlCatalog.Pages
 
         private void RepeaterClick(object sender, PointerPressedEventArgs e)
         {
-            var item = (e.Source as TextBlock)?.DataContext as string;
+            var item = (e.Source as TextBlock)?.DataContext as ItemsRepeaterPageViewModel.Item;
             ((ItemsRepeaterPageViewModel)DataContext).SelectedItem = item;
         }
     }

+ 1 - 1
samples/ControlCatalog/Pages/ListBoxPage.xaml.cs

@@ -78,7 +78,7 @@ namespace ControlCatalog.Pages
                 }
             }
 
-            private string GenerateItem() => $"Item {_counter++}";
+            private string GenerateItem() => $"Item {_counter++.ToString()}";
         }
     }
 }

+ 4 - 1
samples/ControlCatalog/Pages/MenuPage.xaml

@@ -3,8 +3,11 @@
              x:Class="ControlCatalog.Pages.MenuPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">Menu</TextBlock>
+    <TextBlock Classes="h2">Exported menu fallback</TextBlock>
+    <TextBlock>(Should be only visible on platforms without desktop-global menu bar)</TextBlock>
+    <NativeMenuBar/>
     <TextBlock Classes="h2">A window menu</TextBlock>
-
+            
         <StackPanel Orientation="Horizontal"
               Margin="0,16,0,0"
               HorizontalAlignment="Center"

+ 5 - 2
samples/ControlCatalog/Pages/ScreenPage.cs

@@ -57,12 +57,15 @@ namespace ControlCatalog.Pages
                     
                     text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
                     context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
+
+                    text.Text = $"Scaling: {screen.PixelDensity * 100}%";
+                    context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
                     
                     text.Text = $"Primary: {screen.Primary}";
-                    context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
+                    context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
                     
                     text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
-                    context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
+                    context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
                 }
 
             context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));

+ 33 - 6
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@@ -1,4 +1,5 @@
-using System.Collections.ObjectModel;
+using System;
+using System.Collections.ObjectModel;
 using System.Linq;
 using ReactiveUI;
 
@@ -10,18 +11,44 @@ namespace ControlCatalog.ViewModels
 
         public ItemsRepeaterPageViewModel()
         {
-            Items = new ObservableCollection<string>(
-                Enumerable.Range(1, 100000).Select(i => $"Item {i}"));
+            Items = new ObservableCollection<Item>(
+                Enumerable.Range(1, 100000).Select(i => new Item
+                {
+                    Text = $"Item {i.ToString()}",
+                }));
         }
 
-        public ObservableCollection<string> Items { get; }
+        public ObservableCollection<Item> Items { get; }
 
-        public string SelectedItem { get; set; }
+        public Item SelectedItem { get; set; }
 
         public void AddItem()
         {
             var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1;
-            Items.Insert(index + 1, $"New Item {newItemIndex++}");
+            Items.Insert(index + 1, new Item { Text = $"New Item {newItemIndex++}" });
+        }
+
+        public void RandomizeHeights()
+        {
+            var random = new Random();
+
+            foreach (var i in Items)
+            {
+                i.Height = random.Next(240) + 10;
+            }
+        }
+
+        public class Item : ReactiveObject
+        {
+            private double _height = double.NaN;
+
+            public string Text { get; set; }
+            
+            public double Height 
+            {
+                get => _height;
+                set => this.RaiseAndSetIfChanged(ref _height, value);
+            }
         }
     }
 }

+ 3 - 2
samples/RenderDemo/ViewModels/MainWindowViewModel.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Reactive;
 using ReactiveUI;
 
 namespace RenderDemo.ViewModels
@@ -26,7 +27,7 @@ namespace RenderDemo.ViewModels
             set { this.RaiseAndSetIfChanged(ref drawFps, value); }
         }
 
-        public ReactiveCommand ToggleDrawDirtyRects { get; }
-        public ReactiveCommand ToggleDrawFps { get; }
+        public ReactiveCommand<Unit, bool> ToggleDrawDirtyRects { get; }
+        public ReactiveCommand<Unit, bool> ToggleDrawFps { get; }
     }
 }

+ 10 - 10
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@@ -4,10 +4,10 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Reactive;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Controls.Primitives;
-using ReactiveUI.Legacy;
 using ReactiveUI;
 using Avalonia.Layout;
 
@@ -18,7 +18,7 @@ namespace VirtualizationDemo.ViewModels
         private int _itemCount = 200;
         private string _newItemString = "New Item";
         private int _newItemIndex;
-        private IReactiveList<ItemViewModel> _items;
+        private AvaloniaList<ItemViewModel> _items;
         private string _prefix = "Item";
         private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
         private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
@@ -54,7 +54,7 @@ namespace VirtualizationDemo.ViewModels
         public AvaloniaList<ItemViewModel> SelectedItems { get; } 
             = new AvaloniaList<ItemViewModel>();
 
-        public IReactiveList<ItemViewModel> Items
+        public AvaloniaList<ItemViewModel> Items
         {
             get { return _items; }
             private set { this.RaiseAndSetIfChanged(ref _items, value); }
@@ -93,11 +93,11 @@ namespace VirtualizationDemo.ViewModels
         public IEnumerable<ItemVirtualizationMode> VirtualizationModes => 
             Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>();
 
-        public ReactiveCommand AddItemCommand { get; private set; }
-        public ReactiveCommand RecreateCommand { get; private set; }
-        public ReactiveCommand RemoveItemCommand { get; private set; }
-        public ReactiveCommand SelectFirstCommand { get; private set; }
-        public ReactiveCommand SelectLastCommand { get; private set; }
+        public ReactiveCommand<Unit, Unit> AddItemCommand { get; private set; }
+        public ReactiveCommand<Unit, Unit> RecreateCommand { get; private set; }
+        public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; private set; }
+        public ReactiveCommand<Unit, Unit> SelectFirstCommand { get; private set; }
+        public ReactiveCommand<Unit, Unit> SelectLastCommand { get; private set; }
 
         public void RandomizeSize()
         {
@@ -123,7 +123,7 @@ namespace VirtualizationDemo.ViewModels
             {
                 var items = Enumerable.Range(0, count)
                     .Select(x => new ItemViewModel(x));
-                Items = new ReactiveList<ItemViewModel>(items);
+                Items = new AvaloniaList<ItemViewModel>(items);
             }
             else if (count > Items.Count)
             {
@@ -162,7 +162,7 @@ namespace VirtualizationDemo.ViewModels
             _prefix = _prefix == "Item" ? "Recreated" : "Item";
             var items = Enumerable.Range(0, _itemCount)
                 .Select(x => new ItemViewModel(x, _prefix));
-            Items = new ReactiveList<ItemViewModel>(items);
+            Items = new AvaloniaList<ItemViewModel>(items);
         }
 
         private void SelectItem(int index)

+ 3 - 3
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -17,7 +17,7 @@ namespace Avalonia
     {
         public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
         {
-            builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.Instance), "Android");
+            builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.ApplicationType), "Android");
             builder.UseSkia();
             return builder;
         }
@@ -41,7 +41,7 @@ namespace Avalonia.Android
             _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;
         }
 
-        public static void Initialize(Avalonia.Application app)
+        public static void Initialize(Type appType)
         {
             AvaloniaLocator.CurrentMutable
                 .Bind<IClipboard>().ToTransient<ClipboardImpl>()
@@ -55,7 +55,7 @@ namespace Avalonia.Android
                 .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
                 .Bind<IRenderLoop>().ToConstant(new RenderLoop())
                 .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
-                .Bind<IAssetLoader>().ToConstant(new AssetLoader(app.GetType().Assembly));
+                .Bind<IAssetLoader>().ToConstant(new AssetLoader(appType.Assembly));
 
             SkiaPlatform.Initialize();
             ((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext)

+ 1 - 1
src/Android/Avalonia.Android/Avalonia.Android.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="MSBuild.Sdk.Extras">
   <PropertyGroup>
-    <TargetFramework>monoandroid80</TargetFramework>
+    <TargetFramework>monoandroid90</TargetFramework>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   <ItemGroup>

+ 4 - 2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -15,10 +15,10 @@ using Avalonia.Rendering;
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
     class TopLevelImpl : IAndroidView, ITopLevelImpl,  IFramebufferPlatformSurface
-
     {
         private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
         private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
+
         private ViewImpl _view;
 
         public TopLevelImpl(Context context, bool placeOnTop = false)
@@ -28,6 +28,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
                 p => GetAvaloniaPointFromEvent(p));
 
+            Surfaces = new object[] { this };
+
             MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
                 _view.Resources.DisplayMetrics.HeightPixels);
         }
@@ -82,7 +84,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
         public IPlatformHandle Handle => _view;
 
-        public IEnumerable<object> Surfaces => new object[] {this};
+        public IEnumerable<object> Surfaces { get; }
 
         public IRenderer CreateRenderer(IRenderRoot root)
         {

+ 1 - 1
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@@ -16,7 +16,7 @@
     <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
     <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
     <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
-    <TargetFrameworkVersion>v8.0</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
     <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

+ 6 - 1
src/Avalonia.Animation/Animatable.cs

@@ -55,6 +55,11 @@ namespace Avalonia.Animation
             }
             set
             {
+                if (value is null)
+                    return;
+
+                if (_previousTransitions is null)
+                    _previousTransitions = new Dictionary<AvaloniaProperty, IDisposable>();
 
                 SetAndRaise(TransitionsProperty, ref _transitions, value);
             }
@@ -70,7 +75,7 @@ namespace Avalonia.Animation
             if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return;
 
             // PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations).
-            foreach (var transition in Transitions)
+            foreach (var transition in _transitions)
             {
                 if (transition.Property == e.Property)
                 {

+ 8 - 8
src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs

@@ -2,14 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reactive.Linq;
 using Avalonia.Animation.Animators;
-using Avalonia.Animation.Utils;
-using Avalonia.Collections;
-using Avalonia.Data;
-using Avalonia.Reactive;
 
 namespace Avalonia.Animation
 {
@@ -46,6 +39,7 @@ namespace Avalonia.Animation
         public void OnError(Exception error)
         {
             _lastInstance?.Dispose();
+            _lastInstance = null;
         }
 
         void IObserver<bool>.OnNext(bool matchVal)
@@ -53,12 +47,18 @@ namespace Avalonia.Animation
             if (matchVal != _lastMatch)
             {
                 _lastInstance?.Dispose();
+
                 if (matchVal)
                 {
                     _lastInstance = _animator.Run(_animation, _control, _clock, _onComplete);
                 }
+                else
+                {
+                    _lastInstance = null;
+                }
+
                 _lastMatch = matchVal;
             }
         }
     }
-}
+}

+ 10 - 13
src/Avalonia.Base/AvaloniaObject.cs

@@ -326,8 +326,6 @@ namespace Avalonia
 
             VerifyAccess();
 
-            var description = GetDescription(source);
-
             if (property.IsDirect)
             {
                 if (property.IsReadOnly)
@@ -335,12 +333,12 @@ namespace Avalonia
                     throw new ArgumentException($"The property {property.Name} is readonly.");
                 }
 
-                Logger.Verbose(
-                    LogArea.Property, 
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(
+                    LogArea.Property,
                     this,
-                    "Bound {Property} to {Binding} with priority LocalValue", 
-                    property, 
-                    description);
+                    "Bound {Property} to {Binding} with priority LocalValue",
+                    property,
+                    GetDescription(source));
 
                 if (_directBindings == null)
                 {
@@ -351,12 +349,12 @@ namespace Avalonia
             }
             else
             {
-                Logger.Verbose(
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(
                     LogArea.Property,
                     this,
                     "Bound {Property} to {Binding} with priority {Priority}",
                     property,
-                    description,
+                    GetDescription(source),
                     priority);
 
                 return Values.AddBinding(property, source, priority);
@@ -406,7 +404,7 @@ namespace Avalonia
             {
                 RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority);
 
-                Logger.Verbose(
+                Logger.TryGet(LogEventLevel.Verbose)?.Log(
                     LogArea.Property,
                     this,
                     "{Property} changed from {$Old} to {$Value} with priority {Priority}",
@@ -458,8 +456,7 @@ namespace Avalonia
         /// <param name="e">The binding error.</param>
         protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e)
         {
-            Logger.Log(
-                LogEventLevel.Warning,
+            Logger.TryGet(LogEventLevel.Warning)?.Log(
                 LogArea.Binding,
                 this,
                 "Error in binding to {Target}.{Property}: {Message}",
@@ -812,7 +809,7 @@ namespace Avalonia
         /// <param name="priority">The priority.</param>
         private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority)
         {
-            Logger.Verbose(
+            Logger.TryGet(LogEventLevel.Verbose)?.Log(
                 LogArea.Property,
                 this,
                 "Set {Property} to {$Value} with priority {Priority}",

+ 4 - 5
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -192,9 +192,9 @@ namespace Avalonia
         {
             return observable.Subscribe(e =>
             {
-                if (e.Sender is TTarget)
+                if (e.Sender is TTarget target)
                 {
-                    action((TTarget)e.Sender, e);
+                    action(target, e);
                 }
             });
         }
@@ -207,6 +207,7 @@ namespace Avalonia
         /// <param name="observable">The property changed observable.</param>
         /// <param name="handler">Given a TTarget, returns the handler.</param>
         /// <returns>A disposable that can be used to terminate the subscription.</returns>
+        [Obsolete("Use overload taking Action<TTarget, AvaloniaPropertyChangedEventArgs>.")]
         public static IDisposable AddClassHandler<TTarget>(
             this IObservable<AvaloniaPropertyChangedEventArgs> observable,
             Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
@@ -238,9 +239,7 @@ namespace Avalonia
             Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
             where TTarget : class
         {
-            var target = e.Sender as TTarget;
-
-            if (target != null)
+            if (e.Sender is TTarget target)
             {
                 handler(target)(e);
             }

+ 1 - 1
src/Avalonia.Base/Data/Core/BindingExpression.cs

@@ -165,7 +165,7 @@ namespace Avalonia.Data.Core
                             }
                             else
                             {
-                                Logger.Error(
+                                Logger.TryGet(LogEventLevel.Error)?.Log(
                                     LogArea.Binding,
                                     this,
                                     "Could not convert FallbackValue {FallbackValue} to {Type}",

+ 7 - 3
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@@ -8,9 +8,13 @@ namespace Avalonia.Data.Core
     public abstract class ExpressionNode
     {
         private static readonly object CacheInvalid = new object();
+
         protected static readonly WeakReference<object> UnsetReference = 
             new WeakReference<object>(AvaloniaProperty.UnsetValue);
 
+        protected static readonly WeakReference<object> NullReference =
+            new WeakReference<object>(null);
+
         private WeakReference<object> _target = UnsetReference;
         private Action<object> _subscriber;
         private bool _listening;
@@ -98,7 +102,7 @@ namespace Avalonia.Data.Core
 
             if (notification == null)
             {
-                LastValue = new WeakReference<object>(value);
+                LastValue = value != null ? new WeakReference<object>(value) : NullReference;
 
                 if (Next != null)
                 {
@@ -111,7 +115,7 @@ namespace Avalonia.Data.Core
             }
             else
             {
-                LastValue = new WeakReference<object>(notification.Value);
+                LastValue = notification.Value != null ? new WeakReference<object>(notification.Value) : NullReference;
 
                 if (Next != null)
                 {
@@ -136,8 +140,8 @@ namespace Avalonia.Data.Core
             }
             else if (target != AvaloniaProperty.UnsetValue)
             {
-                StartListeningCore(_target);
                 _listening = true;
+                StartListeningCore(_target);
             }
             else
             {

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

@@ -103,8 +103,8 @@ namespace Avalonia.Data.Core.Plugins
 
             protected override void SubscribeCore()
             {
-                SendCurrentValue();
                 SubscribeToChanges();
+                SendCurrentValue();
             }
 
             protected override void UnsubscribeCore()

+ 5 - 0
src/Avalonia.Base/Data/Core/SettableNode.cs

@@ -29,6 +29,11 @@ namespace Avalonia.Data.Core
 
             if (!isLastValueAlive)
             {
+                if (value == null && LastValue == NullReference)
+                {
+                    return true;
+                }
+
                 return false;
             }
 

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

@@ -8,6 +8,77 @@ namespace Avalonia.Logging
     /// </summary>
     public interface ILogSink
     {
+        /// <summary>
+        /// Checks if given log level is enabled.
+        /// </summary>
+        /// <param name="level">The log event level.</param>
+        /// <returns><see langword="true"/> if given log level is enabled.</returns>
+        bool IsEnabled(LogEventLevel level);
+
+        /// <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>
+        void Log(
+            LogEventLevel level,
+            string area,
+            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>

+ 21 - 104
src/Avalonia.Base/Logging/Logger.cs

@@ -1,8 +1,6 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
-using System.Runtime.CompilerServices;
-
 namespace Avalonia.Logging
 {
     /// <summary>
@@ -16,124 +14,43 @@ namespace Avalonia.Logging
         public static ILogSink Sink { get; set; }
 
         /// <summary>
-        /// Logs an event.
+        /// Checks if given log level is enabled.
         /// </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="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Log(
-            LogEventLevel level, 
-            string area,
-            object source,
-            string messageTemplate, 
-            params object[] propertyValues)
-        {
-            Sink?.Log(level, area, source, messageTemplate, propertyValues);
-        }
-
-        /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Verbose"/> level.
-        /// </summary>
-        /// <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="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Verbose(
-            string area,
-            object source,
-            string messageTemplate, 
-            params object[] propertyValues)
+        /// <returns><see langword="true"/> if given log level is enabled.</returns>
+        public static bool IsEnabled(LogEventLevel level)
         {
-            Log(LogEventLevel.Verbose, area, source, messageTemplate, propertyValues);
+            return Sink?.IsEnabled(level) == true;
         }
 
         /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Debug"/> level.
+        /// Returns parametrized logging sink if given log level is enabled.
         /// </summary>
-        /// <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="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Debug(
-            string area,
-            object source,
-            string messageTemplate,
-            params object[] propertyValues)
+        /// <param name="level">The log event level.</param>
+        /// <returns>Log sink or <see langword="null"/> if log level is not enabled.</returns>
+        public static ParametrizedLogger? TryGet(LogEventLevel level)
         {
-            Log(LogEventLevel.Debug, area, source, messageTemplate, propertyValues);
-        }
+            if (!IsEnabled(level))
+            {
+                return null;
+            }
 
-        /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Information"/> level.
-        /// </summary>
-        /// <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="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Information(
-            string area,
-            object source,
-            string messageTemplate,
-            params object[] propertyValues)
-        {
-            Log(LogEventLevel.Information, area, source, messageTemplate, propertyValues);
+            return new ParametrizedLogger(Sink, level);
         }
 
         /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Warning"/> level.
+        /// Returns parametrized logging sink if given log level is enabled.
         /// </summary>
-        /// <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="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Warning(
-            string area,
-            object source,
-            string messageTemplate,
-            params object[] propertyValues)
+        /// <param name="level">The log event level.</param>
+        /// <param name="outLogger">Log sink that is valid only if method returns <see langword="true"/>.</param>
+        /// <returns><see langword="true"/> if logger was obtained successfully.</returns>
+        public static bool TryGet(LogEventLevel level, out ParametrizedLogger outLogger)
         {
-            Log(LogEventLevel.Warning, area, source, messageTemplate, propertyValues);
-        }
+            ParametrizedLogger? logger = TryGet(level);
 
-        /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Error"/> level.
-        /// </summary>
-        /// <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="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Error(
-            string area,
-            object source,
-            string messageTemplate, 
-            params object[] propertyValues)
-        {
-            Log(LogEventLevel.Error, area, source, messageTemplate, propertyValues);
-        }
+            outLogger = logger.GetValueOrDefault();
 
-        /// <summary>
-        /// Logs an event with the <see cref="LogEventLevel.Fatal"/> level.
-        /// </summary>
-        /// <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="propertyValues">The message property values.</param>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static void Fatal(
-            string area,
-            object source,
-            string messageTemplate,
-            params object[] propertyValues)
-        {
-            Log(LogEventLevel.Fatal, area, source, messageTemplate, propertyValues);
+            return logger.HasValue;
         }
     }
 }

+ 174 - 0
src/Avalonia.Base/Logging/ParametrizedLogger.cs

@@ -0,0 +1,174 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+namespace Avalonia.Logging
+{
+    /// <summary>
+    /// Logger sink parametrized for given logging level.
+    /// </summary>
+    public readonly struct ParametrizedLogger
+    {
+        private readonly ILogSink _sink;
+        private readonly LogEventLevel _level;
+
+        public ParametrizedLogger(ILogSink sink, LogEventLevel level)
+        {
+            _sink = sink;
+            _level = level;
+        }
+
+        /// <summary>
+        /// Checks if this logger can be used.
+        /// </summary>
+        public bool IsValid => _sink != null;
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <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>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log(
+            string area,
+            object source,
+            string messageTemplate)
+        {
+            _sink.Log(_level, area, source, messageTemplate);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <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>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <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>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <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>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1, T2>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1,
+            T2 propertyValue2)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <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>
+        /// <param name="propertyValue3">Message property value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1, T2, T3>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1,
+            T2 propertyValue2,
+            T3 propertyValue3)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <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>
+        /// <param name="propertyValue3">Message property value.</param>
+        /// <param name="propertyValue4">Message property value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1, T2, T3, T4>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1,
+            T2 propertyValue2,
+            T3 propertyValue3,
+            T4 propertyValue4)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4);
+        }
+
+        /// <summary>
+        /// Logs an event.
+        /// </summary>
+        /// <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>
+        /// <param name="propertyValue3">Message property value.</param>
+        /// <param name="propertyValue4">Message property value.</param>
+        /// <param name="propertyValue5">Message property value.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Log<T0, T1, T2, T3, T4, T5>(
+            string area,
+            object source,
+            string messageTemplate,
+            T0 propertyValue0,
+            T1 propertyValue1,
+            T2 propertyValue2,
+            T3 propertyValue3,
+            T4 propertyValue4,
+            T5 propertyValue5)
+        {
+            _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4, propertyValue5);
+        }
+    }
+}

+ 16 - 7
src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Buffers;
 using System.Runtime.InteropServices;
 using System.Text;
 
@@ -6,7 +7,7 @@ namespace Avalonia.Platform.Interop
 {
     public class Utf8Buffer : SafeHandle
     {
-        private GCHandle _gchandle;
+        private GCHandle _gcHandle;
         private byte[] _data;
             
         public Utf8Buffer(string s) : base(IntPtr.Zero, true)
@@ -14,8 +15,8 @@ namespace Avalonia.Platform.Interop
             if (s == null)
                 return;
             _data = Encoding.UTF8.GetBytes(s);
-            _gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
-            handle = _gchandle.AddrOfPinnedObject();
+            _gcHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
+            handle = _gcHandle.AddrOfPinnedObject();
         }
 
         public int ByteLen => _data.Length;
@@ -26,7 +27,7 @@ namespace Avalonia.Platform.Interop
             {
                 handle = IntPtr.Zero;
                 _data = null;
-                _gchandle.Free();
+                _gcHandle.Free();
             }
             return true;
         }
@@ -40,10 +41,18 @@ namespace Avalonia.Platform.Interop
                 return null;
             int len;
             for (len = 0; pstr[len] != 0; len++) ;
-            var bytes = new byte[len];
-            Marshal.Copy(s, bytes, 0, len);
 
-            return Encoding.UTF8.GetString(bytes, 0, len);
+            var bytes = ArrayPool<byte>.Shared.Rent(len);
+
+            try
+            {
+                Marshal.Copy(s, bytes, 0, len);
+                return Encoding.UTF8.GetString(bytes, 0, len);
+            }
+            finally
+            {
+                ArrayPool<byte>.Shared.Return(bytes);
+            }
         }
     }
 }

+ 14 - 9
src/Avalonia.Base/PriorityBindingEntry.cs

@@ -99,37 +99,42 @@ namespace Avalonia
 
         void IObserver<object>.OnNext(object value)
         {
-            void Signal()
+            void Signal(PriorityBindingEntry instance, object newValue)
             {
-                var notification = value as BindingNotification;
+                var notification = newValue as BindingNotification;
 
                 if (notification != null)
                 {
                     if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
                     {
-                        Value = notification.Value;
-                        _owner.Changed(this);
+                        instance.Value = notification.Value;
+                        instance._owner.Changed(instance);
                     }
 
                     if (notification.ErrorType != BindingErrorType.None)
                     {
-                        _owner.Error(this, notification);
+                        instance._owner.Error(instance, notification);
                     }
                 }
                 else
                 {
-                    Value = value;
-                    _owner.Changed(this);
+                    instance.Value = newValue;
+                    instance._owner.Changed(instance);
                 }
             }
 
             if (Dispatcher.UIThread.CheckAccess())
             {
-                Signal();
+                Signal(this, value);
             }
             else
             {
-                Dispatcher.UIThread.Post(Signal);
+                // To avoid allocating closure in the outer scope we need to capture variables
+                // locally. This allows us to skip most of the allocations when on UI thread.
+                var instance = this;
+                var newValue = value;
+
+                Dispatcher.UIThread.Post(() => Signal(instance, newValue));
             }
         }
 

+ 47 - 15
src/Avalonia.Base/PriorityLevel.cs

@@ -3,7 +3,8 @@
 
 using System;
 using System.Collections.Generic;
-using System.Reactive.Disposables;
+using System.Diagnostics;
+using System.Threading;
 using Avalonia.Data;
 
 namespace Avalonia
@@ -110,20 +111,7 @@ namespace Avalonia
 
             entry.Start(binding);
 
-            return Disposable.Create(() =>
-            {
-                if (!entry.HasCompleted)
-                {
-                    Bindings.Remove(node);
-
-                    entry.Dispose();
-
-                    if (entry.Index >= ActiveBindingIndex)
-                    {
-                        ActivateFirstBinding();
-                    }
-                }
-            });
+            return new RemoveBindingDisposable(node, Bindings, this);
         }
 
         /// <summary>
@@ -191,5 +179,49 @@ namespace Avalonia
             ActiveBindingIndex = -1;
             Owner.LevelValueChanged(this);
         }
+
+        private sealed class RemoveBindingDisposable : IDisposable
+        {
+            private readonly LinkedList<PriorityBindingEntry> _bindings;
+            private readonly PriorityLevel _priorityLevel;
+            private LinkedListNode<PriorityBindingEntry> _binding;
+
+            public RemoveBindingDisposable(
+                LinkedListNode<PriorityBindingEntry> binding,
+                LinkedList<PriorityBindingEntry> bindings,
+                PriorityLevel priorityLevel)
+            {
+                _binding = binding;
+                _bindings = bindings;
+                _priorityLevel = priorityLevel;
+            }
+
+            public void Dispose()
+            {
+                LinkedListNode<PriorityBindingEntry> binding = Interlocked.Exchange(ref _binding, null);
+
+                if (binding == null)
+                {
+                    // Some system is trying to remove binding twice.
+                    Debug.Assert(false);
+
+                    return;
+                }
+
+                PriorityBindingEntry entry = binding.Value;
+
+                if (!entry.HasCompleted)
+                {
+                    _bindings.Remove(binding);
+
+                    entry.Dispose();
+
+                    if (entry.Index >= _priorityLevel.ActiveBindingIndex)
+                    {
+                        _priorityLevel.ActivateFirstBinding();
+                    }
+                }
+            }
+        }
     }
 }

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

@@ -301,7 +301,7 @@ namespace Avalonia
             }
             else
             {
-                Logger.Error(
+                Logger.TryGet(LogEventLevel.Error)?.Log(
                     LogArea.Binding,
                     Owner,
                     "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",

+ 1 - 1
src/Avalonia.Base/Utilities/SingleOrDictionary.cs

@@ -38,7 +38,7 @@ namespace Avalonia.Utilities
         {
             if (dictionary == null)
             {
-                if (!_singleValue.HasValue || !_singleValue.Value.Key.Equals(key))
+                if (!_singleValue.HasValue || !EqualityComparer<TKey>.Default.Equals(_singleValue.Value.Key, key))
                 {
                     value = default(TValue);
                     return false;

+ 5 - 3
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@@ -142,10 +142,12 @@ namespace Avalonia.Utilities
                     _data = ndata;
                 }
 
+                MethodInfo method = s.Method;
+
                 var subscriber = (TSubscriber)s.Target;
-                if (!s_Callers.TryGetValue(s.Method, out var caller))
-                    s_Callers[s.Method] = caller =
-                        (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, s.Method);
+                if (!s_Callers.TryGetValue(method, out var caller))
+                    s_Callers[method] = caller =
+                        (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method);
                 _data[_count] = new Descriptor
                 {
                     Caller = caller,

+ 25 - 25
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -723,29 +723,29 @@ namespace Avalonia.Controls
 
             PseudoClass<DataGrid, bool>(IsValidProperty, x => !x, ":invalid");
 
-            ItemsProperty.Changed.AddClassHandler<DataGrid>(x => x.OnItemsPropertyChanged);
-            CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>(x => x.OnCanUserResizeColumnsChanged);
-            ColumnWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnColumnWidthChanged);
-            RowBackgroundProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowBackgroundChanged);
-            AlternatingRowBackgroundProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowBackgroundChanged);
-            FrozenColumnCountProperty.Changed.AddClassHandler<DataGrid>(x => x.OnFrozenColumnCountChanged);
-            GridLinesVisibilityProperty.Changed.AddClassHandler<DataGrid>(x => x.OnGridLinesVisibilityChanged);
-            HeadersVisibilityProperty.Changed.AddClassHandler<DataGrid>(x => x.OnHeadersVisibilityChanged);
-            HorizontalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>(x => x.OnHorizontalGridLinesBrushChanged);
-            IsReadOnlyProperty.Changed.AddClassHandler<DataGrid>(x => x.OnIsReadOnlyChanged);
-            MaxColumnWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnMaxColumnWidthChanged);
-            MinColumnWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnMinColumnWidthChanged);
-            RowHeightProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowHeightChanged);
-            RowHeaderWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowHeaderWidthChanged);
-            SelectionModeProperty.Changed.AddClassHandler<DataGrid>(x => x.OnSelectionModeChanged);
-            VerticalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>(x => x.OnVerticalGridLinesBrushChanged);
-            SelectedIndexProperty.Changed.AddClassHandler<DataGrid>(x => x.OnSelectedIndexChanged);
-            SelectedItemProperty.Changed.AddClassHandler<DataGrid>(x => x.OnSelectedItemChanged);
-            IsEnabledProperty.Changed.AddClassHandler<DataGrid>(x => x.DataGrid_IsEnabledChanged);
-            AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler<DataGrid>(x => x.OnAreRowGroupHeadersFrozenChanged);
-            RowDetailsTemplateProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowDetailsTemplateChanged);
-            RowDetailsVisibilityModeProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowDetailsVisibilityModeChanged);
-            AutoGenerateColumnsProperty.Changed.AddClassHandler<DataGrid>(x => x.OnAutoGenerateColumnsChanged);
+            ItemsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnItemsPropertyChanged(e));
+            CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnCanUserResizeColumnsChanged(e));
+            ColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnColumnWidthChanged(e));
+            RowBackgroundProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowBackgroundChanged(e));
+            AlternatingRowBackgroundProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowBackgroundChanged(e));
+            FrozenColumnCountProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnFrozenColumnCountChanged(e));
+            GridLinesVisibilityProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnGridLinesVisibilityChanged(e));
+            HeadersVisibilityProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnHeadersVisibilityChanged(e));
+            HorizontalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnHorizontalGridLinesBrushChanged(e));
+            IsReadOnlyProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnIsReadOnlyChanged(e));
+            MaxColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnMaxColumnWidthChanged(e));
+            MinColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnMinColumnWidthChanged(e));
+            RowHeightProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowHeightChanged(e));
+            RowHeaderWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowHeaderWidthChanged(e));
+            SelectionModeProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnSelectionModeChanged(e));
+            VerticalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnVerticalGridLinesBrushChanged(e));
+            SelectedIndexProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnSelectedIndexChanged(e));
+            SelectedItemProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnSelectedItemChanged(e));
+            IsEnabledProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.DataGrid_IsEnabledChanged(e));
+            AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnAreRowGroupHeadersFrozenChanged(e));
+            RowDetailsTemplateProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowDetailsTemplateChanged(e));
+            RowDetailsVisibilityModeProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowDetailsVisibilityModeChanged(e));
+            AutoGenerateColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnAutoGenerateColumnsChanged(e));
         }
 
         /// <summary>
@@ -3533,7 +3533,7 @@ namespace Avalonia.Controls
                 if (AreColumnHeadersVisible &&
                     _vScrollBar != null && _vScrollBar.IsVisible)
                 {
-                    _topRightCornerHeader.IsVisible = true; ;
+                    _topRightCornerHeader.IsVisible = true;
                 }
                 else
                 {
@@ -5594,7 +5594,7 @@ namespace Avalonia.Controls
                     {
                         // This will trigger a call to this method via Cells_SizeChanged for 
                         // which no processing is needed.
-                        _vScrollBar.IsVisible = true; ;
+                        _vScrollBar.IsVisible = true;
                         if (_vScrollBar.DesiredSize.Width == 0)
                         {
                             // We need to know the width for the rest of layout to work correctly so measure it now

+ 2 - 2
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@@ -29,7 +29,7 @@ namespace Avalonia.Controls
         static DataGridCell()
         {
             PointerPressedEvent.AddClassHandler<DataGridCell>(
-                x => x.DataGridCell_PointerPressed, handledEventsToo: true);
+                (x,e) => x.DataGridCell_PointerPressed(e), handledEventsToo: true);
         }
         public DataGridCell()
         { }
@@ -219,4 +219,4 @@ namespace Avalonia.Controls
             }
         }
     }
-}
+}

+ 2 - 2
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@@ -67,7 +67,7 @@ namespace Avalonia.Controls
 
         static DataGridColumnHeader()
         {
-            AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>(x => x.OnAreSeparatorsVisibleChanged);
+            AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>((x,e) => x.OnAreSeparatorsVisibleChanged(e));
         }
 
         /// <summary>
@@ -468,7 +468,7 @@ namespace Avalonia.Controls
 
         private void DataGridColumnHeader_PointerReleased(object sender, PointerReleasedEventArgs e)
         {
-            if (OwningColumn == null || e.Handled || !IsEnabled || e.MouseButton != MouseButton.Left)
+            if (OwningColumn == null || e.Handled || !IsEnabled || e.InitialPressMouseButton != MouseButton.Left)
             {
                 return;
             }

+ 4 - 4
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@@ -116,10 +116,10 @@ namespace Avalonia.Controls
 
         static DataGridRow()
         {
-            HeaderProperty.Changed.AddClassHandler<DataGridRow>(x => x.OnHeaderChanged);
-            DetailsTemplateProperty.Changed.AddClassHandler<DataGridRow>(x => x.OnDetailsTemplateChanged);
-            AreDetailsVisibleProperty.Changed.AddClassHandler<DataGridRow>(x => x.OnAreDetailsVisibleChanged);
-            PointerPressedEvent.AddClassHandler<DataGridRow>(x => x.DataGridRow_PointerPressed, handledEventsToo: true);
+            HeaderProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnHeaderChanged(e));
+            DetailsTemplateProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnDetailsTemplateChanged(e));
+            AreDetailsVisibleProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnAreDetailsVisibleChanged(e));
+            PointerPressedEvent.AddClassHandler<DataGridRow>((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true);
         }
 
         /// <summary>

+ 2 - 2
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@@ -109,7 +109,7 @@ namespace Avalonia.Controls
 
         static DataGridRowGroupHeader()
         {
-            SublevelIndentProperty.Changed.AddClassHandler<DataGridRowGroupHeader>(x => x.OnSublevelIndentChanged);
+            SublevelIndentProperty.Changed.AddClassHandler<DataGridRowGroupHeader>((x,e) => x.OnSublevelIndentChanged(e));
         }
 
         /// <summary>
@@ -446,4 +446,4 @@ namespace Avalonia.Controls
         }
 
     }
-}
+}

+ 29 - 21
src/Avalonia.Controls/AppBuilderBase.cs

@@ -18,7 +18,9 @@ namespace Avalonia.Controls
     {
         private static bool s_setupWasAlreadyCalled;
         private Action _optionsInitializers;
-
+        private Func<Application> _appFactory;
+        private IApplicationLifetime _lifetime;
+        
         /// <summary>
         /// Gets or sets the <see cref="IRuntimePlatform"/> instance.
         /// </summary>
@@ -30,10 +32,15 @@ namespace Avalonia.Controls
         public Action RuntimePlatformServicesInitializer { get; private set; }
 
         /// <summary>
-        /// Gets or sets the <see cref="Application"/> instance being initialized.
+        /// Gets the <see cref="Application"/> instance being initialized.
         /// </summary>
-        public Application Instance { get; protected set; }
-
+        public Application Instance { get; private set; }
+        
+        /// <summary>
+        /// Gets the type of the Instance (even if it's not created yet)
+        /// </summary>
+        public Type ApplicationType { get; private set; }
+        
         /// <summary>
         /// Gets or sets a method to call the initialize the windowing subsystem.
         /// </summary>
@@ -76,20 +83,11 @@ namespace Avalonia.Controls
         public static TAppBuilder Configure<TApp>()
             where TApp : Application, new()
         {
-            return Configure(new TApp());
-        }
-
-        /// <summary>
-        /// Begin configuring an <see cref="Application"/>.
-        /// </summary>
-        /// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
-        public static TAppBuilder Configure(Application app)
-        {
-            AvaloniaLocator.CurrentMutable.BindToSelf(app);
-
             return new TAppBuilder()
             {
-                Instance = app,
+                ApplicationType = typeof(TApp),
+                // Needed for CoreRT compatibility
+                _appFactory = () => new TApp()
             };
         }
 
@@ -157,6 +155,18 @@ namespace Avalonia.Controls
             return Self;
         }
 
+        /// <summary>
+        /// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it.
+        /// </summary>
+        /// <param name="lifetime"></param>
+        /// <returns></returns>
+        public TAppBuilder SetupWithLifetime(IApplicationLifetime lifetime)
+        {
+            _lifetime = lifetime;
+            Setup();
+            return Self;
+        }
+        
         /// <summary>
         /// Specifies a windowing subsystem to use.
         /// </summary>
@@ -254,11 +264,6 @@ namespace Avalonia.Controls
         /// </summary>
         private void Setup()
         {
-            if (Instance == null)
-            {
-                throw new InvalidOperationException("No App instance configured.");
-            }
-
             if (RuntimePlatformServicesInitializer == null)
             {
                 throw new InvalidOperationException("No runtime platform services configured.");
@@ -285,6 +290,9 @@ namespace Avalonia.Controls
             WindowingSubsystemInitializer();
             RenderingSubsystemInitializer();
             AfterPlatformServicesSetupCallback(Self);
+            Instance = _appFactory();
+            Instance.ApplicationLifetime = _lifetime;
+            AvaloniaLocator.CurrentMutable.BindToSelf(Instance);
             Instance.RegisterServices();
             Instance.Initialize();
             AfterSetupCallback(Self);

+ 18 - 1
src/Avalonia.Controls/Application.cs

@@ -32,7 +32,7 @@ namespace Avalonia
     /// method.
     /// - Tracks the lifetime of the application.
     /// </remarks>
-    public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
+    public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
     {
         /// <summary>
         /// The application-global data templates.
@@ -210,5 +210,22 @@ namespace Avalonia
         {
             ResourcesChanged?.Invoke(this, e);
         }
+
+        private string _name;
+        /// <summary>
+        /// Defines Name property
+        /// </summary>
+        public static readonly DirectProperty<Application, string> NameProperty =
+            AvaloniaProperty.RegisterDirect<Application, string>("Name", o => o.Name, (o, v) => o.Name = v);
+
+        /// <summary>
+        /// Application name to be used for various platform-specific purposes
+        /// </summary>
+        public string Name
+        {
+            get => _name;
+            set => SetAndRaise(NameProperty, ref _name, value);
+        }
+
     }
 }

+ 1 - 2
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@@ -125,8 +125,7 @@ namespace Avalonia
             where T : AppBuilderBase<T>, new()
         {
             var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode};
-            builder.Instance.ApplicationLifetime = lifetime;
-            builder.SetupWithoutStarting();
+            builder.SetupWithLifetime(lifetime);
             return lifetime.Start(args);
         }
     }

+ 9 - 9
src/Avalonia.Controls/AutoCompleteBox.cs

@@ -805,15 +805,15 @@ namespace Avalonia.Controls
         {
             FocusableProperty.OverrideDefaultValue<AutoCompleteBox>(true);
 
-            MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnMinimumPopulateDelayChanged);
-            IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnIsDropDownOpenChanged);
-            SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSelectedItemPropertyChanged);
-            TextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnTextPropertyChanged);
-            SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSearchTextPropertyChanged);
-            FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnFilterModePropertyChanged);
-            ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemFilterPropertyChanged);
-            ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemsPropertyChanged);
-            IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnControlIsEnabledChanged);
+            MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnMinimumPopulateDelayChanged(e));
+            IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnIsDropDownOpenChanged(e));
+            SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSelectedItemPropertyChanged(e));
+            TextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnTextPropertyChanged(e));
+            SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSearchTextPropertyChanged(e));
+            FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnFilterModePropertyChanged(e));
+            ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemFilterPropertyChanged(e));
+            ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemsPropertyChanged(e));
+            IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnControlIsEnabledChanged(e));
         }
 
         /// <summary>

+ 81 - 4
src/Avalonia.Controls/Button.cs

@@ -64,6 +64,12 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<bool> IsDefaultProperty =
             AvaloniaProperty.Register<Button, bool>(nameof(IsDefault));
 
+        /// <summary>
+        /// Defines the <see cref="IsCancelProperty"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> IsCancelProperty =
+            AvaloniaProperty.Register<Button, bool>(nameof(IsCancel));
+
         /// <summary>
         /// Defines the <see cref="Click"/> event.
         /// </summary>
@@ -84,6 +90,7 @@ namespace Avalonia.Controls
             FocusableProperty.OverrideDefaultValue(typeof(Button), true);
             CommandProperty.Changed.Subscribe(CommandChanged);
             IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
+            IsCancelProperty.Changed.Subscribe(IsCancelChanged);
             PseudoClass<Button>(IsPressedProperty, ":pressed");
         }
 
@@ -142,6 +149,16 @@ namespace Avalonia.Controls
             set { SetValue(IsDefaultProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether the button is the Cancel button for the
+        /// window.
+        /// </summary>
+        public bool IsCancel
+        {
+            get { return GetValue(IsCancelProperty); }
+            set { SetValue(IsCancelProperty, value); }
+        }
+
         public bool IsPressed
         {
             get { return GetValue(IsPressedProperty); }
@@ -162,6 +179,13 @@ namespace Avalonia.Controls
                     ListenForDefault(inputElement);
                 }
             }
+            if (IsCancel)
+            {
+                if (e.Root is IInputElement inputElement)
+                {
+                    ListenForCancel(inputElement);
+                }
+            }
         }
 
         /// <inheritdoc/>
@@ -270,7 +294,7 @@ namespace Avalonia.Controls
         {
             base.OnPointerReleased(e);
 
-            if (IsPressed && e.MouseButton == MouseButton.Left)
+            if (IsPressed && e.InitialPressMouseButton == MouseButton.Left)
             {
                 IsPressed = false;
                 e.Handled = true;
@@ -351,6 +375,28 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <summary>
+        /// Called when the <see cref="IsCancel"/> property changes.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void IsCancelChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var button = e.Sender as Button;
+            var isCancel = (bool)e.NewValue;
+
+            if (button?.VisualRoot is IInputElement inputRoot)
+            {
+                if (isCancel)
+                {
+                    button.ListenForCancel(inputRoot);
+                }
+                else
+                {
+                    button.StopListeningForCancel(inputRoot);
+                }
+            }
+        }
+
         /// <summary>
         /// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
         /// </summary>
@@ -373,7 +419,16 @@ namespace Avalonia.Controls
         /// <param name="root">The input root.</param>
         private void ListenForDefault(IInputElement root)
         {
-            root.AddHandler(KeyDownEvent, RootKeyDown);
+            root.AddHandler(KeyDownEvent, RootDefaultKeyDown);
+        }
+
+        /// <summary>
+        /// Starts listening for the Escape key when the button <see cref="IsCancel"/>.
+        /// </summary>
+        /// <param name="root">The input root.</param>
+        private void ListenForCancel(IInputElement root)
+        {
+            root.AddHandler(KeyDownEvent, RootCancelKeyDown);
         }
 
         /// <summary>
@@ -382,7 +437,16 @@ namespace Avalonia.Controls
         /// <param name="root">The input root.</param>
         private void StopListeningForDefault(IInputElement root)
         {
-            root.RemoveHandler(KeyDownEvent, RootKeyDown);
+            root.RemoveHandler(KeyDownEvent, RootDefaultKeyDown);
+        }
+
+        /// <summary>
+        /// Stops listening for the Escape key when the button is no longer <see cref="IsCancel"/>.
+        /// </summary>
+        /// <param name="root">The input root.</param>
+        private void StopListeningForCancel(IInputElement root)
+        {
+            root.RemoveHandler(KeyDownEvent, RootCancelKeyDown);
         }
 
         /// <summary>
@@ -390,12 +454,25 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
-        private void RootKeyDown(object sender, KeyEventArgs e)
+        private void RootDefaultKeyDown(object sender, KeyEventArgs e)
         {
             if (e.Key == Key.Enter && IsVisible && IsEnabled)
             {
                 OnClick();
             }
         }
+
+        /// <summary>
+        /// Called when a key is pressed on the input root and the button <see cref="IsCancel"/>.
+        /// </summary>
+        /// <param name="sender">The event sender.</param>
+        /// <param name="e">The event args.</param>
+        private void RootCancelKeyDown(object sender, KeyEventArgs e)
+        {
+            if (e.Key == Key.Escape && IsVisible && IsEnabled)
+            {
+                OnClick();
+            }
+        }
     }
 }

+ 12 - 13
src/Avalonia.Controls/Calendar/Calendar.cs

@@ -1565,7 +1565,7 @@ namespace Avalonia.Controls
         protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
             base.OnPointerReleased(e);
-            if (!HasFocusInternal && e.MouseButton == MouseButton.Left)
+            if (!HasFocusInternal && e.InitialPressMouseButton == MouseButton.Left)
             {
                 FocusManager.Instance.Focus(this);
             }
@@ -2057,18 +2057,17 @@ namespace Avalonia.Controls
 
         static Calendar()
         {
-            IsEnabledProperty.Changed.AddClassHandler<Calendar>(x => x.OnIsEnabledChanged);
-            FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>(x => x.OnFirstDayOfWeekChanged);
-            IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>(x => x.OnIsTodayHighlightedChanged);
-            DisplayModeProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayModePropertyChanged);
-            SelectionModeProperty.Changed.AddClassHandler<Calendar>(x => x.OnSelectionModeChanged);
-            SelectedDateProperty.Changed.AddClassHandler<Calendar>(x => x.OnSelectedDateChanged);
-            DisplayDateProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateChanged);
-            DisplayDateStartProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateStartChanged);
-            DisplayDateEndProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateEndChanged);
-            KeyDownEvent.AddClassHandler<Calendar>(x => x.Calendar_KeyDown);
-            KeyUpEvent.AddClassHandler<Calendar>(x => x.Calendar_KeyUp);
-            
+            IsEnabledProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsEnabledChanged(e));
+            FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnFirstDayOfWeekChanged(e));
+            IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsTodayHighlightedChanged(e));
+            DisplayModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayModePropertyChanged(e));
+            SelectionModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectionModeChanged(e));
+            SelectedDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectedDateChanged(e));
+            DisplayDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateChanged(e));
+            DisplayDateStartProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateStartChanged(e));
+            DisplayDateEndProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateEndChanged(e));
+            KeyDownEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyDown(e));
+            KeyUpEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyUp(e));
         }
 
         /// <summary>

+ 1 - 1
src/Avalonia.Controls/Calendar/CalendarButton.cs

@@ -173,7 +173,7 @@ namespace Avalonia.Controls.Primitives
         protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
             base.OnPointerReleased(e);
-            if (e.MouseButton == MouseButton.Left)
+            if (e.InitialPressMouseButton == MouseButton.Left)
                 CalendarLeftMouseButtonUp?.Invoke(this, e);
         }
     }

+ 1 - 1
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@@ -231,7 +231,7 @@ namespace Avalonia.Controls.Primitives
         {
             base.OnPointerReleased(e);
 
-            if (e.MouseButton == MouseButton.Left)
+            if (e.InitialPressMouseButton == MouseButton.Left)
                 CalendarDayButtonMouseUp?.Invoke(this, e);
         }
     }

+ 12 - 10
src/Avalonia.Controls/Calendar/DatePicker.cs

@@ -393,14 +393,14 @@ namespace Avalonia.Controls
         {
             FocusableProperty.OverrideDefaultValue<DatePicker>(true);
 
-            DisplayDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateChanged);
-            DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateStartChanged);
-            DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateEndChanged);
-            IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>(x => x.OnIsDropDownOpenChanged);
-            SelectedDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateChanged);
-            SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateFormatChanged);
-            CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>(x => x.OnCustomDateFormatStringChanged);
-            TextProperty.Changed.AddClassHandler<DatePicker>(x => x.OnTextChanged);
+            DisplayDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateChanged(e));
+            DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateStartChanged(e));
+            DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateEndChanged(e));
+            IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnIsDropDownOpenChanged(e));
+            SelectedDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateChanged(e));
+            SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateFormatChanged(e));
+            CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnCustomDateFormatStringChanged(e));
+            TextProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnTextChanged(e));
         }
         /// <summary>
         /// Initializes a new instance of the
@@ -1042,7 +1042,8 @@ namespace Avalonia.Controls
                         }
                     }
                     DateTime? d = SetTextBoxValue(s);
-                    if (!SelectedDate.Equals(d))
+                    
+                    if (SelectedDate != d)
                     {
                         SelectedDate = d;
                     }
@@ -1058,7 +1059,8 @@ namespace Avalonia.Controls
             else
             {
                 DateTime? d = SetTextBoxValue(_defaultText);
-                if (!SelectedDate.Equals(d))
+
+                if (SelectedDate != d)
                 {
                     SelectedDate = d;
                 }

+ 0 - 12
src/Avalonia.Controls/Carousel.cs

@@ -84,17 +84,5 @@ namespace Avalonia.Controls
                 --SelectedIndex;
             }
         }
-
-        /// <inheritdoc/>
-        protected override void OnKeyDown(KeyEventArgs e)
-        {
-            // Ignore key presses.
-        }
-
-        /// <inheritdoc/>
-        protected override void OnPointerPressed(PointerPressedEventArgs e)
-        {
-            // Ignore pointer presses.
-        }
     }
 }

+ 2 - 2
src/Avalonia.Controls/ComboBox.cs

@@ -65,8 +65,8 @@ namespace Avalonia.Controls
         {
             ItemsPanelProperty.OverrideDefaultValue<ComboBox>(DefaultPanel);
             FocusableProperty.OverrideDefaultValue<ComboBox>(true);
-            SelectedItemProperty.Changed.AddClassHandler<ComboBox>(x => x.SelectedItemChanged);
-            KeyDownEvent.AddClassHandler<ComboBox>(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel);
+            SelectedItemProperty.Changed.AddClassHandler<ComboBox>((x,e) => x.SelectedItemChanged(e));
+            KeyDownEvent.AddClassHandler<ComboBox>((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel);
         }
 
         /// <summary>

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

@@ -43,7 +43,7 @@ namespace Avalonia.Controls
 
         static ContentControl()
         {
-            ContentProperty.Changed.AddClassHandler<ContentControl>(x => x.ContentChanged);
+            ContentProperty.Changed.AddClassHandler<ContentControl>((x, e) => x.ContentChanged(e));
         }
 
         /// <summary>

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

@@ -192,7 +192,7 @@ namespace Avalonia.Controls
                 e.Handled = true;
             }
 
-            if (e.MouseButton == MouseButton.Right)
+            if (e.InitialPressMouseButton == MouseButton.Right)
             {
                 if (contextMenu.CancelOpening())
                     return;

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

@@ -56,7 +56,7 @@ namespace Avalonia.Controls
         {
             ErrorsProperty.Changed.Subscribe(ErrorsChanged);
             HasErrorsProperty.Changed.Subscribe(HasErrorsChanged);
-            TemplatedParentProperty.Changed.AddClassHandler<DataValidationErrors>(x => x.OnTemplatedParentChange);
+            TemplatedParentProperty.Changed.AddClassHandler<DataValidationErrors>((x, e) => x.OnTemplatedParentChange(e));
         }
 
         private void OnTemplatedParentChange(AvaloniaPropertyChangedEventArgs e)

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

@@ -29,7 +29,7 @@ namespace Avalonia.Controls
         static Decorator()
         {
             AffectsMeasure<Decorator>(ChildProperty, PaddingProperty);
-            ChildProperty.Changed.AddClassHandler<Decorator>(x => x.ChildChanged);
+            ChildProperty.Changed.AddClassHandler<Decorator>((x, e) => x.ChildChanged(e));
         }
 
         /// <summary>

+ 2 - 2
src/Avalonia.Controls/DropDown.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Controls
     {
         public DropDown()
         {
-            Logger.Warning(LogArea.Control, this, "DropDown is deprecated: Use ComboBox");
+            Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDown is deprecated: Use ComboBox");
         }
 
         Type IStyleable.StyleKey => typeof(ComboBox);
@@ -20,7 +20,7 @@ namespace Avalonia.Controls
     {
         public DropDownItem()
         {
-            Logger.Warning(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem");
+            Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem");
         }
 
         Type IStyleable.StyleKey => typeof(ComboBoxItem);

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

@@ -37,7 +37,7 @@ namespace Avalonia.Controls
 
             PseudoClass<Expander>(IsExpandedProperty, ":expanded");
 
-            IsExpandedProperty.Changed.AddClassHandler<Expander>(x => x.OnIsExpandedChanged);
+            IsExpandedProperty.Changed.AddClassHandler<Expander>((x, e) => x.OnIsExpandedChanged(e));
         }
 
         public IPageTransition ContentTransition

+ 2 - 2
src/Avalonia.Controls/ItemsControl.cs

@@ -64,8 +64,8 @@ namespace Avalonia.Controls
         /// </summary>
         static ItemsControl()
         {
-            ItemsProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemsChanged);
-            ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemTemplateChanged);
+            ItemsProperty.Changed.AddClassHandler<ItemsControl>((x, e) => x.ItemsChanged(e));
+            ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>((x, e) => x.ItemTemplateChanged(e));
         }
 
         /// <summary>

+ 5 - 3
src/Avalonia.Controls/LayoutTransformControl.cs

@@ -28,11 +28,13 @@ namespace Avalonia.Controls
             ClipToBoundsProperty.OverrideDefaultValue<LayoutTransformControl>(true);
 
             LayoutTransformProperty.Changed
-                .AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged);
+                .AddClassHandler<LayoutTransformControl>((x, e) => x.OnLayoutTransformChanged(e));
 
             ChildProperty.Changed
-                .AddClassHandler<LayoutTransformControl>(x => x.OnChildChanged);
-            UseRenderTransformProperty.Changed.AddClassHandler<LayoutTransformControl>(x => x.OnUseRenderTransformPropertyChanged);
+                .AddClassHandler<LayoutTransformControl>((x, e) => x.OnChildChanged(e));
+
+            UseRenderTransformProperty.Changed
+                .AddClassHandler<LayoutTransformControl>((x, e) => x.OnUseRenderTransformPropertyChanged(e));
         }
 
         /// <summary>

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

@@ -64,7 +64,7 @@ namespace Avalonia.Controls
         /// </summary>
         static MenuBase()
         {
-            MenuItem.SubmenuOpenedEvent.AddClassHandler<MenuBase>(x => x.OnSubmenuOpened);
+            MenuItem.SubmenuOpenedEvent.AddClassHandler<MenuBase>((x, e) => x.OnSubmenuOpened(e));
         }
 
         /// <summary>

+ 8 - 8
src/Avalonia.Controls/MenuItem.cs

@@ -102,13 +102,13 @@ namespace Avalonia.Controls
             SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
             CommandProperty.Changed.Subscribe(CommandChanged);
             FocusableProperty.OverrideDefaultValue<MenuItem>(true);
-            HeaderProperty.Changed.AddClassHandler<MenuItem>(x => x.HeaderChanged);
-            IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
-            IsSelectedProperty.Changed.AddClassHandler<MenuItem>(x => x.IsSelectedChanged);
+            HeaderProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.HeaderChanged(e));
+            IconProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.IconChanged(e));
+            IsSelectedProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.IsSelectedChanged(e));
             ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
-            ClickEvent.AddClassHandler<MenuItem>(x => x.OnClick);
-            SubmenuOpenedEvent.AddClassHandler<MenuItem>(x => x.OnSubmenuOpened);
-            IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>(x => x.SubMenuOpenChanged);
+            ClickEvent.AddClassHandler<MenuItem>((x, e) => x.OnClick(e));
+            SubmenuOpenedEvent.AddClassHandler<MenuItem>((x, e) => x.OnSubmenuOpened(e));
+            IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.SubMenuOpenChanged(e));
         }
 
         public MenuItem()
@@ -337,7 +337,7 @@ namespace Avalonia.Controls
         {
             base.OnPointerEnter(e);
 
-            var point = e.GetPointerPoint(null);
+            var point = e.GetCurrentPoint(null);
             RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
                 e.Timestamp, point.Properties, e.KeyModifiers));
         }
@@ -347,7 +347,7 @@ namespace Avalonia.Controls
         {
             base.OnPointerLeave(e);
 
-            var point = e.GetPointerPoint(null);
+            var point = e.GetCurrentPoint(null);
             RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
                 e.Timestamp, point.Properties, e.KeyModifiers));
         }

+ 85 - 0
src/Avalonia.Controls/NativeMenu.Export.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls.Platform;
+using Avalonia.Data;
+
+namespace Avalonia.Controls
+{
+    public partial class NativeMenu
+    {
+        public static readonly AttachedProperty<bool> IsNativeMenuExportedProperty =
+            AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, bool>("IsNativeMenuExported");
+
+        public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty);
+        
+        private static readonly AttachedProperty<NativeMenuInfo> s_nativeMenuInfoProperty =
+            AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, NativeMenuInfo>("___NativeMenuInfo");
+        
+        class NativeMenuInfo
+        {
+            public bool ChangingIsExported { get; set; }
+            public ITopLevelNativeMenuExporter Exporter { get; }
+
+            public NativeMenuInfo(TopLevel target)
+            {
+                Exporter = (target.PlatformImpl as ITopLevelImplWithNativeMenuExporter)?.NativeMenuExporter;
+                if (Exporter != null)
+                {
+                    Exporter.OnIsNativeMenuExportedChanged += delegate
+                    {
+                        SetIsNativeMenuExported(target, Exporter.IsNativeMenuExported);
+                    };
+                }
+            }
+        }
+
+        static NativeMenuInfo GetInfo(TopLevel target)
+        {
+            var rv = target.GetValue(s_nativeMenuInfoProperty);
+            if (rv == null)
+            {
+                target.SetValue(s_nativeMenuInfoProperty, rv = new NativeMenuInfo(target));
+                SetIsNativeMenuExported(target, rv.Exporter?.IsNativeMenuExported ?? false);
+            }
+
+            return rv;
+        }
+
+        static void SetIsNativeMenuExported(TopLevel tl, bool value)
+        {
+            GetInfo(tl).ChangingIsExported = true;
+            tl.SetValue(IsNativeMenuExportedProperty, value);
+        }
+
+        public static readonly AttachedProperty<NativeMenu> MenuProperty
+            = AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu", validate:
+                (o, v) =>
+                {
+                    if(!(o is Application || o is TopLevel))
+                        throw new InvalidOperationException("NativeMenu.Menu property isn't valid on "+o.GetType());
+                    return v;
+                });
+
+        public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu);
+        public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
+        
+        static NativeMenu()
+        {
+            // This is needed because of the lack of attached direct properties
+            IsNativeMenuExportedProperty.Changed.Subscribe(args =>
+            {
+                var info = GetInfo((TopLevel)args.Sender);
+                if (!info.ChangingIsExported)
+                    throw new InvalidOperationException("IsNativeMenuExported property is read-only");
+                info.ChangingIsExported = false;
+            });
+            MenuProperty.Changed.Subscribe(args =>
+            {
+                if (args.Sender is TopLevel tl)
+                {
+                    GetInfo(tl).Exporter?.SetNativeMenu((NativeMenu)args.NewValue);
+                }
+            });
+        }
+    }
+}

+ 60 - 0
src/Avalonia.Controls/NativeMenu.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Avalonia.Collections;
+using Avalonia.Data;
+using Avalonia.LogicalTree;
+using Avalonia.Metadata;
+
+namespace Avalonia.Controls
+{
+    public partial class NativeMenu : AvaloniaObject, IEnumerable<NativeMenuItemBase>
+    {
+        private readonly AvaloniaList<NativeMenuItemBase> _items =
+            new AvaloniaList<NativeMenuItemBase> { ResetBehavior = ResetBehavior.Remove };
+        private NativeMenuItem _parent;
+        [Content]
+        public IList<NativeMenuItemBase> Items => _items;
+
+        public NativeMenu()
+        {
+            _items.Validate = Validator;
+            _items.CollectionChanged += ItemsChanged;
+        }
+
+        private void Validator(NativeMenuItemBase obj)
+        {
+            if (obj.Parent != null)
+                throw new InvalidOperationException("NativeMenuItem already has a parent");
+        }
+
+        private void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            if(e.OldItems!=null)
+                foreach (NativeMenuItemBase i in e.OldItems)
+                    i.Parent = null;
+            if(e.NewItems!=null)
+                foreach (NativeMenuItemBase i in e.NewItems)
+                    i.Parent = this;
+        }
+
+        public static readonly DirectProperty<NativeMenu, NativeMenuItem> ParentProperty =
+            AvaloniaProperty.RegisterDirect<NativeMenu, NativeMenuItem>("Parent", o => o.Parent, (o, v) => o.Parent = v);
+
+        public NativeMenuItem Parent
+        {
+            get => _parent;
+            set => SetAndRaise(ParentProperty, ref _parent, value);
+        }
+
+        public void Add(NativeMenuItemBase item) => _items.Add(item);
+        
+        public IEnumerator<NativeMenuItemBase> GetEnumerator() => _items.GetEnumerator();
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+}

+ 36 - 0
src/Avalonia.Controls/NativeMenuBar.cs

@@ -0,0 +1,36 @@
+using System;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+using Avalonia.Styling;
+
+namespace Avalonia.Controls
+{
+    public class NativeMenuBar : TemplatedControl
+    {
+        public static readonly AttachedProperty<bool> EnableMenuItemClickForwardingProperty =
+            AvaloniaProperty.RegisterAttached<NativeMenuBar, MenuItem, Boolean>(
+                "EnableMenuItemClickForwarding");
+
+        static NativeMenuBar()
+        {
+            EnableMenuItemClickForwardingProperty.Changed.Subscribe(args =>
+            {
+                var item = (MenuItem)args.Sender;
+                if (args.NewValue.Equals(true))
+                    item.Click += OnMenuItemClick;
+                else
+                    item.Click -= OnMenuItemClick;
+            });
+        }
+        
+        public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable)
+        {
+            menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable);
+        }
+
+        private static void OnMenuItemClick(object sender, RoutedEventArgs e)
+        {
+            (((MenuItem)sender).DataContext as NativeMenuItem)?.RaiseClick();
+        }
+    }
+}

+ 162 - 0
src/Avalonia.Controls/NativeMenuItem.cs

@@ -0,0 +1,162 @@
+using System;
+using System.Windows.Input;
+using Avalonia.Input;
+using Avalonia.Utilities;
+
+namespace Avalonia.Controls
+{
+    public class NativeMenuItem : NativeMenuItemBase
+    {
+        private string _header;
+        private KeyGesture _gesture;
+        private bool _enabled = true;
+
+        private NativeMenu _menu;
+
+        static NativeMenuItem()
+        {
+            MenuProperty.Changed.Subscribe(args =>
+            {
+                var item = (NativeMenuItem)args.Sender;
+                var value = (NativeMenu)args.NewValue;
+                if (value.Parent != null && value.Parent != item)
+                    throw new InvalidOperationException("NativeMenu already has a parent");
+                value.Parent = item;
+            });
+        }
+
+
+        class CanExecuteChangedSubscriber : IWeakSubscriber<EventArgs>
+        {
+            private readonly NativeMenuItem _parent;
+
+            public CanExecuteChangedSubscriber(NativeMenuItem parent)
+            {
+                _parent = parent;
+            }
+
+            public void OnEvent(object sender, EventArgs e)
+            {
+                _parent.CanExecuteChanged();
+            }
+        }
+
+        private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
+
+
+        public NativeMenuItem()
+        {
+            _canExecuteChangedSubscriber = new CanExecuteChangedSubscriber(this);
+        }
+
+        public NativeMenuItem(string header) : this()
+        {
+            Header = header;
+        }
+
+        public static readonly DirectProperty<NativeMenuItem, NativeMenu> MenuProperty =
+            AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu>(nameof(Menu), o => o._menu,
+                (o, v) =>
+                {
+                    if (v.Parent != null && v.Parent != o)
+                        throw new InvalidOperationException("NativeMenu already has a parent");
+                    o._menu = v;
+                });
+
+        public NativeMenu Menu
+        {
+            get => _menu;
+            set
+            {
+                if (value.Parent != null && value.Parent != this)
+                    throw new InvalidOperationException("NativeMenu already has a parent");
+                SetAndRaise(MenuProperty, ref _menu, value);
+            }
+        }
+
+        public static readonly DirectProperty<NativeMenuItem, string> HeaderProperty =
+            AvaloniaProperty.RegisterDirect<NativeMenuItem, string>(nameof(Header), o => o._header, (o, v) => o._header = v);
+
+        public string Header
+        {
+            get => GetValue(HeaderProperty);
+            set => SetValue(HeaderProperty, value);
+        }
+
+        public static readonly DirectProperty<NativeMenuItem, KeyGesture> GestureProperty =
+            AvaloniaProperty.RegisterDirect<NativeMenuItem, KeyGesture>(nameof(Gesture), o => o._gesture, (o, v) => o._gesture = v);
+
+        public KeyGesture Gesture
+        {
+            get => GetValue(GestureProperty);
+            set => SetValue(GestureProperty, value);
+        }
+
+        private ICommand _command;
+
+        public static readonly DirectProperty<NativeMenuItem, ICommand> CommandProperty =
+           AvaloniaProperty.RegisterDirect<NativeMenuItem, ICommand>(nameof(Command),
+               o => o._command, (o, v) =>
+               {
+                   if (o._command != null)
+                       WeakSubscriptionManager.Unsubscribe(o._command,
+                           nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber);
+                   o._command = v;
+                   if (o._command != null)
+                       WeakSubscriptionManager.Subscribe(o._command,
+                           nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber);
+                   o.CanExecuteChanged();
+               });
+
+        /// <summary>
+        /// Defines the <see cref="CommandParameter"/> property.
+        /// </summary>
+        public static readonly StyledProperty<object> CommandParameterProperty =
+            Button.CommandParameterProperty.AddOwner<MenuItem>();
+
+        public static readonly DirectProperty<NativeMenuItem, bool> EnabledProperty =
+           AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>(nameof(Enabled), o => o._enabled,
+               (o, v) => o._enabled = v, true);
+
+        public bool Enabled
+        {
+            get => GetValue(EnabledProperty);
+            set => SetValue(EnabledProperty, value);
+        }
+
+        void CanExecuteChanged()
+        {
+            Enabled = _command?.CanExecute(null) ?? true;
+        }
+
+        public bool HasClickHandlers => Clicked != null;
+
+        public ICommand Command
+        {
+            get => GetValue(CommandProperty);
+            set => SetValue(CommandProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
+        /// <see cref="NativeMenuItem"/>.
+        /// </summary>
+        public object CommandParameter
+        {
+            get { return GetValue(CommandParameterProperty); }
+            set { SetValue(CommandParameterProperty, value); }
+        }
+
+        public event EventHandler Clicked;
+
+        public void RaiseClick()
+        {
+            Clicked?.Invoke(this, new EventArgs());
+
+            if (Command?.CanExecute(CommandParameter) == true)
+            {
+                Command.Execute(CommandParameter);
+            }
+        }
+    }
+}

+ 23 - 0
src/Avalonia.Controls/NativeMenuItemBase.cs

@@ -0,0 +1,23 @@
+using System;
+
+namespace Avalonia.Controls
+{
+    public class NativeMenuItemBase : AvaloniaObject
+    {
+        private NativeMenu _parent;
+
+        internal NativeMenuItemBase()
+        {
+
+        }
+
+        public static readonly DirectProperty<NativeMenuItem, NativeMenu> ParentProperty =
+            AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu>("Parent", o => o.Parent, (o, v) => o.Parent = v);
+
+        public NativeMenu Parent
+        {
+            get => _parent;
+            set => SetAndRaise(ParentProperty, ref _parent, value);
+        }
+    }
+}

+ 10 - 0
src/Avalonia.Controls/NativeMenuItemSeperator.cs

@@ -0,0 +1,10 @@
+using System;
+
+namespace Avalonia.Controls
+{
+    public class NativeMenuItemSeperator : NativeMenuItemBase
+    {
+        [Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)]
+        public string Header => "-";
+    }
+}

+ 1 - 1
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@@ -356,7 +356,7 @@ namespace Avalonia.Controls.Platform
         {
             var item = GetMenuItem(e.Source as IControl);
 
-            if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == false)
+            if (e.InitialPressMouseButton == MouseButton.Left && item?.HasSubMenu == false)
             {
                 Click(item);
                 e.Handled = true;

+ 18 - 0
src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Platform;
+
+namespace Avalonia.Controls.Platform
+{
+    public interface ITopLevelNativeMenuExporter
+    {
+        bool IsNativeMenuExported { get; }
+        event EventHandler OnIsNativeMenuExportedChanged;
+        void SetNativeMenu(NativeMenu menu);
+    }
+    
+    public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl
+    {
+        ITopLevelNativeMenuExporter NativeMenuExporter { get; }
+    }
+}

+ 4 - 1
src/Avalonia.Controls/Platform/Screen.cs

@@ -2,14 +2,17 @@
 {
     public class Screen
     {
+        public double PixelDensity { get; }
+
         public PixelRect Bounds { get; }
 
         public PixelRect WorkingArea { get; }
 
         public bool Primary { get; }
         
-        public Screen(PixelRect bounds, PixelRect workingArea, bool primary)
+        public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary)
         {
+            this.PixelDensity = pixelDensity;
             this.Bounds = bounds;
             this.WorkingArea = workingArea;
             this.Primary = primary;

+ 2 - 2
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@@ -46,8 +46,8 @@ namespace Avalonia.Controls.Presenters
         /// </summary>
         static CarouselPresenter()
         {
-            IsVirtualizedProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.IsVirtualizedChanged);
-            SelectedIndexProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.SelectedIndexChanged);
+            IsVirtualizedProperty.Changed.AddClassHandler<CarouselPresenter>((x, e) => x.IsVirtualizedChanged(e));
+            SelectedIndexProperty.Changed.AddClassHandler<CarouselPresenter>((x, e) => x.SelectedIndexChanged(e));
         }
 
         /// <summary>

+ 3 - 3
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -94,9 +94,9 @@ namespace Avalonia.Controls.Presenters
         {
             AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
             AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
-            ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
-            ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
-            TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
+            ContentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
+            ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
+            TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.TemplatedParentChanged(e));
         }
 
         /// <summary>

+ 1 - 1
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@@ -35,7 +35,7 @@ namespace Avalonia.Controls.Presenters
                 KeyboardNavigationMode.Once);
 
             VirtualizationModeProperty.Changed
-                .AddClassHandler<ItemsPresenter>(x => x.VirtualizationModeChanged);
+                .AddClassHandler<ItemsPresenter>((x, e) => x.VirtualizationModeChanged(e));
         }
 
         /// <summary>

+ 1 - 1
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@@ -45,7 +45,7 @@ namespace Avalonia.Controls.Presenters
         /// </summary>
         static ItemsPresenterBase()
         {
-            TemplatedParentProperty.Changed.AddClassHandler<ItemsPresenterBase>(x => x.TemplatedParentChanged);
+            TemplatedParentProperty.Changed.AddClassHandler<ItemsPresenterBase>((x,e) => x.TemplatedParentChanged(e));
         }
 
         /// <summary>

+ 3 - 3
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -73,7 +73,7 @@ namespace Avalonia.Controls.Presenters
         static ScrollContentPresenter()
         {
             ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
-            ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>(x => x.ChildChanged);
+            ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>((x, e) => x.ChildChanged(e));
             AffectsArrange<ScrollContentPresenter>(OffsetProperty);
         }
 
@@ -246,7 +246,7 @@ namespace Avalonia.Controls.Presenters
                 if (isLogical)
                     _activeLogicalGestureScrolls?.TryGetValue(e.Id, out delta);
                 delta += e.Delta;
-                
+
                 if (Extent.Height > Viewport.Height)
                 {
                     double dy;
@@ -293,7 +293,7 @@ namespace Avalonia.Controls.Presenters
             }
         }
 
-        private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e) 
+        private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e)
             => _activeLogicalGestureScrolls?.Remove(e.Id);
 
         /// <inheritdoc/>

+ 1 - 1
src/Avalonia.Controls/Presenters/TextPresenter.cs

@@ -244,7 +244,7 @@ namespace Avalonia.Controls.Presenters
                             var rect = FormattedText.HitTestTextPosition(caretIndex);
                             this.BringIntoView(rect);
                         },
-                        DispatcherPriority.Normal);
+                        DispatcherPriority.Render);
                 }
             }
         }

+ 1 - 1
src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

@@ -30,7 +30,7 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         static HeaderedContentControl()
         {
-            ContentProperty.Changed.AddClassHandler<HeaderedContentControl>(x => x.HeaderChanged);
+            ContentProperty.Changed.AddClassHandler<HeaderedContentControl>((x, e) => x.HeaderChanged(e));
         }
 
         /// <summary>

部分文件因为文件数量过多而无法显示