Browse Source

Merge remote-tracking branch 'origin/master' into refactor/pixelsize

Dan Walmsley 7 years ago
parent
commit
af661085d1
100 changed files with 3050 additions and 736 deletions
  1. 2 0
      .travis.yml
  2. 55 32
      Avalonia.sln
  3. 89 0
      azure-pipelines.yml
  4. 18 114
      build.cake
  5. 0 5
      build/MonoMac.props
  6. 1 1
      build/ReactiveUI.props
  7. 2 2
      build/SharedVersion.props
  8. 1 0
      dirs.proj
  9. 21 17
      packages.cake
  10. 8 0
      parameters.cake
  11. 3 3
      readme.md
  12. 7 2
      samples/ControlCatalog/App.xaml
  13. 1 1
      samples/ControlCatalog/Pages/ButtonPage.xaml
  14. 39 22
      samples/ControlCatalog/Pages/MenuPage.xaml
  15. 97 0
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  16. 5 2
      samples/RenderDemo/Pages/AnimationsPage.xaml
  17. 16 0
      samples/RenderDemo/Pages/AnimationsPage.xaml.cs
  18. 6 20
      samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
  19. 1 0
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  20. 3 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  21. 8 20
      src/Avalonia.Animation/Animatable.cs
  22. 5 10
      src/Avalonia.Animation/Animation.cs
  23. 16 45
      src/Avalonia.Animation/AnimationInstance`1.cs
  24. 68 50
      src/Avalonia.Animation/Animator`1.cs
  25. 30 0
      src/Avalonia.Animation/Clock.cs
  26. 72 0
      src/Avalonia.Animation/ClockBase.cs
  27. 65 0
      src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs
  28. 4 1
      src/Avalonia.Animation/DoubleAnimator.cs
  29. 4 1
      src/Avalonia.Animation/FillMode.cs
  30. 7 4
      src/Avalonia.Animation/IAnimation.cs
  31. 3 0
      src/Avalonia.Animation/IAnimationSetter.cs
  32. 5 2
      src/Avalonia.Animation/IAnimator.cs
  33. 11 0
      src/Avalonia.Animation/IClock.cs
  34. 10 0
      src/Avalonia.Animation/IGlobalClock.cs
  35. 1 1
      src/Avalonia.Animation/ITransition.cs
  36. 4 1
      src/Avalonia.Animation/KeyFrame.cs
  37. 3 0
      src/Avalonia.Animation/KeyFramePair`1.cs
  38. 4 1
      src/Avalonia.Animation/PlayState.cs
  39. 4 1
      src/Avalonia.Animation/PlaybackDirection.cs
  40. 0 54
      src/Avalonia.Animation/Timing.cs
  41. 14 13
      src/Avalonia.Animation/TransitionInstance.cs
  42. 2 5
      src/Avalonia.Animation/Transition`1.cs
  43. 1 0
      src/Avalonia.Base/Avalonia.Base.csproj
  44. 18 0
      src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs
  45. 2 2
      src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
  46. 7 0
      src/Avalonia.Base/PriorityBindingEntry.cs
  47. 9 5
      src/Avalonia.Base/PriorityLevel.cs
  48. 1 9
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  49. 2 3
      src/Avalonia.Base/Reactive/ObservableEx.cs
  50. 23 1
      src/Avalonia.Base/Utilities/MathUtilities.cs
  51. 1 1
      src/Avalonia.Controls/AppBuilderBase.cs
  52. 14 2
      src/Avalonia.Controls/Application.cs
  53. 5 1
      src/Avalonia.Controls/Border.cs
  54. 1 1
      src/Avalonia.Controls/DrawingPresenter.cs
  55. 2 2
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  56. 1 0
      src/Avalonia.Controls/Image.cs
  57. 34 4
      src/Avalonia.Controls/MenuItem.cs
  58. 1 0
      src/Avalonia.Controls/Panel.cs
  59. 1 1
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  60. 7 4
      src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
  61. 1 7
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  62. 21 3
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  63. 7 1
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  64. 4 4
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  65. 4 3
      src/Avalonia.Controls/ProgressBar.cs
  66. 20 6
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  67. 161 0
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  68. 3 1
      src/Avalonia.Controls/TextBlock.cs
  69. 221 162
      src/Avalonia.Controls/TextBox.cs
  70. 0 1
      src/Avalonia.Controls/TopLevel.cs
  71. 84 72
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  72. 1 1
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  73. 4 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  74. 9 1
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  75. 2 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  76. 8 0
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  77. 1 0
      src/Avalonia.Diagnostics/DevTools.xaml
  78. 23 0
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  79. 38 0
      src/Avalonia.Diagnostics/Models/EventChainLink.cs
  80. 5 0
      src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
  81. 61 0
      src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs
  82. 98 0
      src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs
  83. 78 0
      src/Avalonia.Diagnostics/ViewModels/EventTreeNodeBase.cs
  84. 60 0
      src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs
  85. 80 0
      src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs
  86. 53 0
      src/Avalonia.Diagnostics/Views/EventsView.xaml
  87. 32 0
      src/Avalonia.Diagnostics/Views/EventsView.xaml.cs
  88. 2 2
      src/Avalonia.DotNetCoreRuntime/AppBuilder.cs
  89. 1 1
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  90. 18 0
      src/Avalonia.Input/Key.cs
  91. 11 0
      src/Avalonia.Input/KeyGesture.cs
  92. 98 0
      src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
  93. 4 0
      src/Avalonia.Native.OSX/.gitignore
  94. 328 0
      src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  95. 7 0
      src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  96. 91 0
      src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme
  97. 12 0
      src/Avalonia.Native.OSX/KeyTransform.h
  98. 241 0
      src/Avalonia.Native.OSX/KeyTransform.mm
  99. 51 0
      src/Avalonia.Native.OSX/Screens.mm
  100. 262 0
      src/Avalonia.Native.OSX/SystemDialogs.mm

+ 2 - 0
.travis.yml

@@ -11,6 +11,8 @@ mono:
   - 5.2.0
 dotnet: 2.1.200
 script:
+  - sudo apt-get update
+  - sudo apt-get install castxml
   - ./build.sh --target "Travis" --configuration "Release"
 notifications:
   email: false

+ 55 - 32
Avalonia.sln

@@ -80,13 +80,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Skia", "Skia", "{3743B0F2-C
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Android", "Android", "{7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Android", "src\Android\Avalonia.Android\Avalonia.Android.csproj", "{7B92AF71-6287-4693-9DCB-BD5B6E927E23}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Android", "src\Android\Avalonia.Android\Avalonia.Android.csproj", "{7B92AF71-6287-4693-9DCB-BD5B6E927E23}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.AndroidTestApplication", "src\Android\Avalonia.AndroidTestApplication\Avalonia.AndroidTestApplication.csproj", "{FF69B927-C545-49AE-8E16-3D14D621AA12}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "iOS", "iOS", "{0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.iOS", "src\iOS\Avalonia.iOS\Avalonia.iOS.csproj", "{4488AD85-1495-4809-9AA4-DDFE0A48527E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.iOS", "src\iOS\Avalonia.iOS\Avalonia.iOS.csproj", "{4488AD85-1495-4809-9AA4-DDFE0A48527E}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.iOSTestApplication", "src\iOS\Avalonia.iOSTestApplication\Avalonia.iOSTestApplication.csproj", "{8C923867-8A8F-4F6B-8B80-47D9E8436166}"
 EndProject
@@ -164,7 +164,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.LinuxFramebuffer",
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.Skia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}"
 EndProject
@@ -178,14 +178,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Designer.HostApp",
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}"
 EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OSX", "OSX", "{A59C4C0A-64DF-4621-B450-2BA00D6F61E2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MonoMac", "src\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj", "{CBFD5788-567D-401B-9DFA-74E4224025A0}"
-EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Designer.HostApp.NetFX", "src\tools\Avalonia.Designer.HostApp.NetFX\Avalonia.Designer.HostApp.NetFX.csproj", "{4ADA61C8-D191-428D-9066-EF4F0D86520F}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", "tests\Avalonia.Skia.UnitTests\Avalonia.Skia.UnitTests.csproj", "{E1240B49-7B4B-4371-A00E-068778C5CF0B}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.OpenGL", "src\Avalonia.OpenGL\Avalonia.OpenGL.csproj", "{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Native", "src\Avalonia.Native\Avalonia.Native.csproj", "{12A91A62-C064-42CA-9A8C-A1272F354388}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -1591,30 +1591,6 @@ Global
 		{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.Build.0 = Release|Any CPU
 		{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Any CPU.Build.0 = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhone.Build.0 = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhone.Build.0 = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Any CPU.Build.0 = Release|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhone.ActiveCfg = Release|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhone.Build.0 = Release|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
-		{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 		{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
 		{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
 		{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@@ -1663,6 +1639,54 @@ Global
 		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhone.Build.0 = Release|Any CPU
 		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhone.Build.0 = Release|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|Any CPU.Build.0 = Release|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhone.Build.0 = Release|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -1713,7 +1737,6 @@ Global
 		{E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
 		{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
-		{CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
 		{4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
 		{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 	EndGlobalSection

+ 89 - 0
azure-pipelines.yml

@@ -0,0 +1,89 @@
+jobs:
+- job: Linux
+  pool:
+    vmImage: 'ubuntu-16.04'
+  steps:
+  - task: CmdLine@2
+    inputs:
+      script: |
+        sudo apt-get update
+        sudo apt-get install castxml
+  - task: CmdLine@2
+    inputs:
+      script: |
+        dotnet tool install -g Cake.Tool --version 0.30.0
+  
+  - script: |
+      export PATH="$PATH:$HOME/.dotnet/tools"
+      dotnet --info
+      printenv
+      dotnet cake build.cake -target="Azure-Linux" -configuration="Release"
+     
+- job: macOS
+  pool:
+    vmImage: 'xcode9-macos10.13'
+  steps:
+  - task: DotNetCoreInstaller@0
+    inputs:
+      version: '2.1.403'
+  - task: Xcode@5
+    inputs:
+      actions: 'build'
+      scheme: ''
+      sdk: 'macosx10.13'
+      configuration: 'Release'
+      xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
+      xcodeVersion: 'default' # Options: 8, 9, default, specifyPath
+      args: '-derivedDataPath ./'
+  - task: CmdLine@2
+    inputs:
+      script: brew install castxml
+  - task: CmdLine@2
+    inputs:
+      script: |
+        dotnet tool install -g Cake.Tool --version 0.30.0
+  - script: |
+      export COREHOST_TRACE=0
+      export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
+      export DOTNET_CLI_TELEMETRY_OPTOUT=1
+      which dotnet
+      dotnet --info
+      export PATH="$PATH:$HOME/.dotnet/tools"
+      dotnet --info
+      printenv
+      dotnet cake build.cake -target="Azure-OSX" -configuration="Release"
+  
+  - task: PublishBuildArtifacts@1
+    inputs:
+      pathToPublish: '$(Build.SourcesDirectory)/Build/Products/Release/'
+      artifactName: 'Avalonia.Native.OSX'
+  - task: PublishBuildArtifacts@1
+    inputs:
+      pathToPublish: '$(Build.SourcesDirectory)/artifacts/bin'
+      artifactName: 'BinariesOSX'
+
+- job: Windows
+  pool:
+    vmImage: 'vs2017-win2016'
+  steps:
+  - task: CmdLine@2
+    inputs:
+      script: |
+        dotnet tool install -g Cake.Tool --version 0.30.0
+  - task: CmdLine@2
+    inputs:
+      script: |
+        set PATH=%PATH%;%USERPROFILE%\.dotnet\tools
+        dotnet cake build.cake -target="Azure-Windows" -configuration="Release"
+  - task: PublishBuildArtifacts@1
+    inputs:
+      pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget'
+      artifactName: 'NuGet'
+  - task: PublishBuildArtifacts@1
+    inputs:
+      pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip'
+      artifactName: 'Samples'
+  - task: PublishBuildArtifacts@1
+    inputs:
+      pathToPublish: '$(Build.SourcesDirectory)/artifacts/bin'
+      artifactName: 'BinariesWindows'

+ 18 - 114
build.cake

@@ -1,15 +1,9 @@
-///////////////////////////////////////////////////////////////////////////////
-// ADDINS
-///////////////////////////////////////////////////////////////////////////////
-
-#addin "nuget:?package=NuGet.Core&version=2.14.0"
-#tool "nuget:?package=NuGet.CommandLine&version=4.3.0"
-#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720"
-
 ///////////////////////////////////////////////////////////////////////////////
 // TOOLS
 ///////////////////////////////////////////////////////////////////////////////
 
+#tool "nuget:?package=NuGet.CommandLine&version=4.7.1"
+#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.3"
 #tool "nuget:?package=xunit.runner.console&version=2.3.1"
 #tool "nuget:?package=JetBrains.dotMemoryUnit&version=3.0.20171219.105559"
 
@@ -21,7 +15,6 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
-using NuGet;
 
 ///////////////////////////////////////////////////////////////////////////////
 // SCRIPTS
@@ -71,6 +64,7 @@ Setup<AvaloniaBuildData>(context =>
     Information("IsRunningOnUnix: " + parameters.IsRunningOnUnix);
     Information("IsRunningOnWindows: " + parameters.IsRunningOnWindows);
     Information("IsRunningOnAppVeyor: " + parameters.IsRunningOnAppVeyor);
+    Information("IsRunnongOnAzure:" + parameters.IsRunningOnAzure);
     Information("IsPullRequest: " + parameters.IsPullRequest);
     Information("IsMainRepo: " + parameters.IsMainRepo);
     Information("IsMasterBranch: " + parameters.IsMasterBranch);
@@ -170,6 +164,7 @@ Task("Run-Unit-Tests-Impl")
     RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false);
     RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false);
     RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", data.Parameters, false);
     if (data.Parameters.IsRunningOnWindows)
     {
         RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, false);
@@ -245,107 +240,6 @@ Task("Create-NuGet-Packages-Impl")
     }
 });
 
-Task("Publish-MyGet-Impl")
-    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
-    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
-    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
-    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMasterBranch)
-    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMyGetRelease)
-    .Does<AvaloniaBuildData>(data =>
-{
-    var apiKey = EnvironmentVariable("MYGET_API_KEY");
-    if(string.IsNullOrEmpty(apiKey)) 
-    {
-        throw new InvalidOperationException("Could not resolve MyGet API key.");
-    }
-
-    var apiUrl = EnvironmentVariable("MYGET_API_URL");
-    if(string.IsNullOrEmpty(apiUrl)) 
-    {
-        throw new InvalidOperationException("Could not resolve MyGet API url.");
-    }
-
-    foreach(var nupkg in data.Packages.NugetPackages)
-    {
-        NuGetPush(nupkg, new NuGetPushSettings {
-            Source = apiUrl,
-            ApiKey = apiKey
-        });
-    }
-})
-.OnError(exception =>
-{
-    Information("Publish-MyGet Task failed, but continuing with next Task...");
-});
-
-Task("Publish-NuGet-Impl")
-    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
-    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
-    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
-    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsNuGetRelease)
-    .Does<AvaloniaBuildData>(data =>
-{
-    var apiKey = EnvironmentVariable("NUGET_API_KEY");
-    if(string.IsNullOrEmpty(apiKey)) 
-    {
-        throw new InvalidOperationException("Could not resolve NuGet API key.");
-    }
-
-    var apiUrl = EnvironmentVariable("NUGET_API_URL");
-    if(string.IsNullOrEmpty(apiUrl)) 
-    {
-        throw new InvalidOperationException("Could not resolve NuGet API url.");
-    }
-
-    foreach(var nupkg in data.Packages.NugetPackages)
-    {
-        NuGetPush(nupkg, new NuGetPushSettings {
-            ApiKey = apiKey,
-            Source = apiUrl
-        });
-    }
-})
-.OnError(exception =>
-{
-    Information("Publish-NuGet Task failed, but continuing with next Task...");
-});
-
-Task("Inspect-Impl")
-    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
-    .Does(() =>
-{
-    var badIssues = new []{"PossibleNullReferenceException"};
-    var whitelist = new []{"tests", "src\\android", "src\\ios",
-        "src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"};
-    Information("Running code inspections");
-    
-    var exitCode = StartProcess(Context.Tools.Resolve("inspectcode.exe"),
-        new ProcessSettings
-        {
-            Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln",
-            RedirectStandardOutput = true
-        });
-
-    Information("Analyzing report");
-    var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml"));
-    var failBuild = false;
-    foreach(var xml in doc.Descendants("Issue"))
-    {
-        var typeId = xml.Attribute("TypeId").Value.ToString();
-        if(badIssues.Contains(typeId))
-        {
-            var file = xml.Attribute("File").Value.ToString().ToLower();
-            if(whitelist.Any(wh => file.StartsWith(wh)))
-                continue;
-            var line = xml.Attribute("Line").Value.ToString();
-            Error(typeId + " - " + file + " on line " + line);
-            failBuild = true;
-        }
-    }
-    if(failBuild)
-        throw new Exception("Issues found");
-});
-
 ///////////////////////////////////////////////////////////////////////////////
 // TARGETS
 ///////////////////////////////////////////////////////////////////////////////
@@ -363,19 +257,29 @@ Task("Run-Tests")
 
 Task("Package")
     .IsDependentOn("Run-Tests")
-    .IsDependentOn("Inspect-Impl")
     .IsDependentOn("Create-NuGet-Packages-Impl");
 
 Task("AppVeyor")
   .IsDependentOn("Package")
   .IsDependentOn("Copy-Files-Impl")
-  .IsDependentOn("Zip-Files-Impl")
-  .IsDependentOn("Publish-MyGet-Impl")
-  .IsDependentOn("Publish-NuGet-Impl");
+  .IsDependentOn("Zip-Files-Impl");
 
 Task("Travis")
   .IsDependentOn("Run-Tests");
 
+Task("Azure-Linux")
+  .IsDependentOn("Run-Tests");
+
+Task("Azure-OSX")
+  .IsDependentOn("Run-Tests")
+  .IsDependentOn("Copy-Files-Impl")
+  .IsDependentOn("Zip-Files-Impl");
+
+Task("Azure-Windows")
+  .IsDependentOn("Package")
+  .IsDependentOn("Copy-Files-Impl")
+  .IsDependentOn("Zip-Files-Impl");
+
 ///////////////////////////////////////////////////////////////////////////////
 // EXECUTE
 ///////////////////////////////////////////////////////////////////////////////

+ 0 - 5
build/MonoMac.props

@@ -1,5 +0,0 @@
-<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ItemGroup>
-    <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
-  </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="8.7.1" />
+    <PackageReference Include="reactiveui" Version="9.0.1" />
   </ItemGroup>
 </Project>

+ 2 - 2
build/SharedVersion.props

@@ -2,8 +2,8 @@
   xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
     <Product>Avalonia</Product>
-    <Version>0.6.2</Version>
-    <Copyright>Copyright 2016 &#169; The AvaloniaUI Project</Copyright>
+    <Version>0.7.0</Version>
+    <Copyright>Copyright 2018 &#169; The AvaloniaUI Project</Copyright>
     <PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
     <PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>
     <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

+ 1 - 0
dirs.proj

@@ -5,6 +5,7 @@
     <ProjectReference Include="tests/**/*.*proj" />
     <ProjectReference Remove="**/*.shproj" />
     <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
+    <ProjectReference Remove="**/*.pbxproj" />
   </ItemGroup>
   <ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">
     <ProjectReference Remove="src/Android/**/*.*proj" />

+ 21 - 17
packages.cake

@@ -150,7 +150,7 @@ public class Packages
             nuspec.Symbols = false;
             nuspec.NoPackageAnalysis = true;
             nuspec.Description = "The Avalonia UI framework";
-            nuspec.Copyright = "Copyright 2015";
+            nuspec.Copyright = "Copyright 2018";
             nuspec.Tags = new [] { "Avalonia" };
         });
 
@@ -167,6 +167,7 @@ public class Packages
             new [] { "./src/", "Avalonia.Logging.Serilog"},
             new [] { "./src/", "Avalonia.Visuals"},
             new [] { "./src/", "Avalonia.Styling"},
+            new [] { "./src/", "Avalonia.OpenGL"},
             new [] { "./src/", "Avalonia.Themes.Default"},
             new [] { "./src/Markup/", "Avalonia.Markup"},
             new [] { "./src/Markup/", "Avalonia.Markup.Xaml"},
@@ -407,20 +408,6 @@ public class Packages
                 BasePath = context.Directory("./src/Skia/Avalonia.Skia/bin/" + parameters.DirSuffix + "/netstandard2.0"),
                 OutputDirectory = parameters.NugetRoot
             },
-            new NuGetPackSettings()
-            {
-                Id = "Avalonia.MonoMac",
-                Dependencies = new DependencyBuilder(this)
-                {
-                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
-                }.Dep("MonoMac.NetStandard").ToArray(),
-                Files = new []
-                {
-                    new NuSpecContent { Source = "netstandard2.0/Avalonia.MonoMac.dll", Target = "lib/netstandard2.0" },
-                },
-                BasePath = context.Directory("./src/OSX/Avalonia.MonoMac/bin/" + parameters.DirSuffix),
-                OutputDirectory = parameters.NugetRoot
-            },
             ///////////////////////////////////////////////////////////////////////////////
             // Avalonia.Desktop
             ///////////////////////////////////////////////////////////////////////////////
@@ -433,7 +420,7 @@ public class Packages
                     new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
                     new NuSpecDependency() { Id = "Avalonia.Skia", Version = parameters.Version },
                     new NuSpecDependency() { Id = "Avalonia.Gtk3", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.MonoMac", Version = parameters.Version }
+                    new NuSpecDependency() { Id = "Avalonia.Native", Version = parameters.Version }
                 },
                 Files = new NuSpecContent[]
                 {
@@ -459,7 +446,24 @@ public class Packages
                 },
                 BasePath = context.Directory("./src/Linux/"),
                 OutputDirectory = parameters.NugetRoot
-            }
+            },
+            ///////////////////////////////////////////////////////////////////////////////
+            // Avalonia.Native
+            ///////////////////////////////////////////////////////////////////////////////
+            new NuGetPackSettings()
+            {
+                Id = "Avalonia.Native",
+                Dependencies = new []
+                {
+                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
+                },
+                Files = new []
+                {
+                    new NuSpecContent { Source = "Avalonia.Native.dll", Target = "lib/netstandard2.0" }
+                },
+                BasePath = context.Directory("./src/Avalonia.Native/bin/" + parameters.DirSuffix + "/netstandard2.0"),
+                OutputDirectory = parameters.NugetRoot
+            },
         };
 
         var nuspecNuGetSettingInterop = new NuGetPackSettings()

+ 8 - 0
parameters.cake

@@ -14,6 +14,7 @@ public class Parameters
     public bool IsRunningOnUnix { get; private set; }
     public bool IsRunningOnWindows { get; private set; }
     public bool IsRunningOnAppVeyor { get; private set; }
+    public bool IsRunningOnAzure { get; private set; }
     public bool IsPullRequest { get; private set; }
     public bool IsMainRepo { get; private set; }
     public bool IsMasterBranch { get; private set; }
@@ -53,6 +54,8 @@ public class Parameters
         IsRunningOnUnix = context.IsRunningOnUnix();
         IsRunningOnWindows = context.IsRunningOnWindows();
         IsRunningOnAppVeyor = buildSystem.AppVeyor.IsRunningOnAppVeyor;
+        IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts";
+        
         IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest;
         IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, buildSystem.AppVeyor.Environment.Repository.Name);
         IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, buildSystem.AppVeyor.Environment.Repository.Branch);
@@ -85,6 +88,11 @@ public class Parameters
                 Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta";
             }
         }
+        else if (IsRunningOnAzure)
+        {
+                // Use AssemblyVersion with Build as version
+                Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta";   
+        }
 
         // DIRECTORIES
         ArtifactsDir = (DirectoryPath)context.Directory("./artifacts");

+ 3 - 3
readme.md

@@ -2,9 +2,9 @@
 
 # Avalonia
 
-| Gitter Chat | Windows Build Status | Linux/Mac Build Status | Open Collective |
-|---|---|---|---|
-|  [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) |
+| Gitter Chat | Build Status | Windows Build Status | Linux/Mac Build Status | Open Collective |
+|---|---|---|---|---|
+|  [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) |
 
 ## About
 

+ 7 - 2
samples/ControlCatalog/App.xaml

@@ -14,6 +14,11 @@
       <Setter Property="FontSize" Value="13"/>
     </Style>
 
-    <StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
+      <Style Selector="TextBlock.h3">
+          <Setter Property="Foreground" Value="#a2a2a2"/>
+          <Setter Property="FontSize" Value="13"/>
+      </Style>
+
+      <StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
   </Application.Styles>
-</Application>
+</Application>

+ 1 - 1
samples/ControlCatalog/Pages/ButtonPage.xaml

@@ -18,7 +18,7 @@
             <Style>
               <Style.Resources>
                 <SolidColorBrush x:Key="ThemeBorderMidBrush">Red</SolidColorBrush>
-                <SolidColorBrush x:Key="ThemeControlDarkBrush">DarkRed</SolidColorBrush>
+                <SolidColorBrush x:Key="ThemeControlHighBrush">DarkRed</SolidColorBrush>
               </Style.Resources>
             </Style>
           </Button.Styles>          

+ 39 - 22
samples/ControlCatalog/Pages/MenuPage.xaml

@@ -7,29 +7,46 @@
               Margin="0,16,0,0"
               HorizontalAlignment="Center"
               Spacing="16">
-            <Menu>
-                <MenuItem Header="_First">
-                    <MenuItem Header="Standard _Menu Item"/>
-                    <Separator/>
-                    <MenuItem Header="Menu with _Submenu">
-                        <MenuItem Header="Submenu _1"/>
-                        <MenuItem Header="Submenu _2"/>
+            <StackPanel>
+                <TextBlock Classes="h3" Margin="4 8">Defined in XAML</TextBlock>
+                <Menu>
+                    <MenuItem Header="_First">
+                        <MenuItem Header="Standard _Menu Item"/>
+                        <Separator/>
+                        <MenuItem Header="Menu with _Submenu">
+                            <MenuItem Header="Submenu _1"/>
+                            <MenuItem Header="Submenu _2"/>
+                        </MenuItem>
+                        <MenuItem Header="Menu Item with _Icon">
+                            <MenuItem.Icon>
+                                <Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
+                            </MenuItem.Icon>
+                        </MenuItem>
+                        <MenuItem Header="Menu Item with _Checkbox">
+                            <MenuItem.Icon>
+                                <CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
+                            </MenuItem.Icon>
+                        </MenuItem>
                     </MenuItem>
-                    <MenuItem Header="Menu Item with _Icon">
-                        <MenuItem.Icon>
-                            <Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
-                        </MenuItem.Icon>
+                    <MenuItem Header="_Second">
+                        <MenuItem Header="Second _Menu Item"/>
                     </MenuItem>
-                    <MenuItem Header="Menu Item with _Checkbox">
-                        <MenuItem.Icon>
-                            <CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
-                        </MenuItem.Icon>
-                    </MenuItem>
-                </MenuItem>
-                <MenuItem Header="_Second">
-                    <MenuItem Header="Second _Menu Item"/>
-                </MenuItem>
-            </Menu>
+                </Menu>
+            </StackPanel>
+
+            <StackPanel>
+                <TextBlock Classes="h3" Margin="4 8">Dyanamically generated</TextBlock>
+                <Menu Items="{Binding MenuItems}">
+                    <Menu.Styles>
+                        <Style Selector="MenuItem">
+                            <Setter Property="Header" Value="{Binding Header}"/>
+                            <Setter Property="Items" Value="{Binding Items}"/>
+                            <Setter Property="Command" Value="{Binding Command}"/>
+                            <Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
+                        </Style>
+                    </Menu.Styles>
+                </Menu>
+            </StackPanel>
         </StackPanel>
     </StackPanel>
-</UserControl>
+</UserControl>

+ 97 - 0
samples/ControlCatalog/Pages/MenuPage.xaml.cs

@@ -1,5 +1,10 @@
+using System.Collections.Generic;
+using System.Reactive;
+using System.Threading.Tasks;
+using System.Windows.Input;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
+using ReactiveUI;
 
 namespace ControlCatalog.Pages
 {
@@ -8,6 +13,51 @@ namespace ControlCatalog.Pages
         public MenuPage()
         {
             this.InitializeComponent();
+            var vm = new MenuPageViewModel();
+
+            vm.MenuItems = new[]
+            {
+                new MenuItemViewModel
+                {
+                    Header = "_File",
+                    Items = new[]
+                    {
+                        new MenuItemViewModel { Header = "_Open...", Command = vm.OpenCommand },
+                        new MenuItemViewModel { Header = "Save", Command = vm.SaveCommand },
+                        new MenuItemViewModel { Header = "-" },
+                        new MenuItemViewModel
+                        {
+                            Header = "Recent",
+                            Items = new[]
+                            {
+                                new MenuItemViewModel
+                                {
+                                    Header = "File1.txt",
+                                    Command = vm.OpenRecentCommand,
+                                    CommandParameter = @"c:\foo\File1.txt"
+                                },
+                                new MenuItemViewModel
+                                {
+                                    Header = "File2.txt",
+                                    Command = vm.OpenRecentCommand,
+                                    CommandParameter = @"c:\foo\File2.txt"
+                                },
+                            }
+                        },
+                    }
+                },
+                new MenuItemViewModel
+                {
+                    Header = "_Edit",
+                    Items = new[]
+                    {
+                        new MenuItemViewModel { Header = "_Copy" },
+                        new MenuItemViewModel { Header = "_Paste" },
+                    }
+                }
+            };
+
+            DataContext = vm;
         }
 
         private void InitializeComponent()
@@ -15,4 +65,51 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
     }
+
+    public class MenuPageViewModel
+    {
+        public MenuPageViewModel()
+        {
+            OpenCommand = ReactiveCommand.CreateFromTask(Open);
+            SaveCommand = ReactiveCommand.Create(Save);
+            OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent);
+        }
+
+        public IReadOnlyList<MenuItemViewModel> MenuItems { get; set; }
+        public ReactiveCommand<Unit, Unit> OpenCommand { get; }
+        public ReactiveCommand<Unit, Unit> SaveCommand { get; }
+        public ReactiveCommand<string, Unit> OpenRecentCommand { get; }
+
+        public async Task Open()
+        {
+            var dialog = new OpenFileDialog();
+            var result = await dialog.ShowAsync();
+
+            if (result != null)
+            {
+                foreach (var path in result)
+                {
+                    System.Diagnostics.Debug.WriteLine($"Opened: {path}");
+                }
+            }
+        }
+
+        public void Save()
+        {
+            System.Diagnostics.Debug.WriteLine("Save");
+        }
+
+        public void OpenRecent(string path)
+        {
+            System.Diagnostics.Debug.WriteLine($"Open recent: {path}");
+        }
+    }
+
+    public class MenuItemViewModel
+    {
+        public string Header { get; set; }
+        public ICommand Command { get; set; }
+        public object CommandParameter { get; set; }
+        public IList<MenuItemViewModel> Items { get; set; }
+    }
 }

+ 5 - 2
samples/RenderDemo/Pages/AnimationsPage.xaml

@@ -107,9 +107,12 @@
   </UserControl.Styles>
   <Grid>
     <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" ClipToBounds="False">
+      <StackPanel.Clock>
+          <Clock />
+      </StackPanel.Clock>
       <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
         <TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock>
-        <Button Content="{Binding PlayStateText}" Command="{Binding ToggleGlobalPlayState}"/>
+        <Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
       </StackPanel>
       <WrapPanel ClipToBounds="False">
         <Border Classes="Test Rect1" Background="DarkRed"/>
@@ -120,4 +123,4 @@
       </WrapPanel>
     </StackPanel>
   </Grid>
-</UserControl>
+</UserControl>

+ 16 - 0
samples/RenderDemo/Pages/AnimationsPage.xaml.cs

@@ -5,6 +5,7 @@ using Avalonia.Controls;
 using Avalonia.Controls.Shapes;
 using Avalonia.Data;
 using Avalonia.Input;
+using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media;
 using RenderDemo.ViewModels;
@@ -23,5 +24,20 @@ namespace RenderDemo.Pages
         {
             AvaloniaXamlLoader.Load(this);
         }
+
+        private void ToggleClock(object sender, RoutedEventArgs args)
+        {
+            var button = sender as Button;
+            var clock = button.Clock;
+
+            if (clock.PlayState == PlayState.Run)
+            {
+                clock.PlayState = PlayState.Pause;
+            }
+            else if (clock.PlayState == PlayState.Pause)
+            {
+                clock.PlayState = PlayState.Run;
+            }
+        }
     }
 }

+ 6 - 20
samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs

@@ -6,27 +6,15 @@ namespace RenderDemo.ViewModels
 {
     public class AnimationsPageViewModel : ReactiveObject
     {
-        private string _playStateText = "Pause all animations";
+        private bool _isPlaying = true;
 
-        public AnimationsPageViewModel()
-        {
-            ToggleGlobalPlayState = ReactiveCommand.Create(() => TogglePlayState());
-        }
+        private string _playStateText = "Pause animations on this page";
 
-        void TogglePlayState()
+        public void TogglePlayState()
         {
-            switch (Animation.GlobalPlayState)
-            {
-                case PlayState.Run:
-                    PlayStateText = "Resume all animations";
-                    Animation.GlobalPlayState = PlayState.Pause;
-                    break;
-
-                case PlayState.Pause:
-                    PlayStateText = "Pause all animations";
-                    Animation.GlobalPlayState = PlayState.Run;
-                    break;
-            }
+            PlayStateText = _isPlaying
+                ? "Resume animations on this page" : "Pause animations on this page";
+            _isPlaying = !_isPlaying;
         }
 
         public string PlayStateText
@@ -34,7 +22,5 @@ namespace RenderDemo.ViewModels
             get { return _playStateText; }
             set { this.RaiseAndSetIfChanged(ref _playStateText, value); }
         }
-
-        public ReactiveCommand ToggleGlobalPlayState { get; }
     }
 }

+ 1 - 0
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@@ -7,6 +7,7 @@ using System.Linq;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Controls.Primitives;
+using ReactiveUI.Legacy;
 using ReactiveUI;
 
 namespace VirtualizationDemo.ViewModels

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

@@ -52,7 +52,9 @@ namespace Avalonia.Android
                 .Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
                 .Bind<IWindowingPlatform>().ToConstant(Instance)
                 .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
-                .Bind<IRenderLoop>().ToConstant(new DefaultRenderLoop(60))
+                .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
+                .Bind<IRenderLoop>().ToConstant(new RenderLoop())
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IAssetLoader>().ToConstant(new AssetLoader(app.GetType().Assembly));
 
             SkiaPlatform.Initialize();

+ 8 - 20
src/Avalonia.Animation/Animatable.cs

@@ -14,26 +14,14 @@ namespace Avalonia.Animation
     /// Base class for all animatable objects.
     /// </summary>
     public class Animatable : AvaloniaObject
-    { 
-        /// <summary>
-        /// Defines the <see cref="PlayState"/> property.
-        /// </summary>
-        public static readonly DirectProperty<Animatable, PlayState> PlayStateProperty =
-            AvaloniaProperty.RegisterDirect<Animatable, PlayState>(
-                nameof(PlayState),
-                o => o.PlayState,
-                (o, v) => o.PlayState = v);
-
-        private PlayState _playState = PlayState.Run;
+    {
+        public static readonly StyledProperty<IClock> ClockProperty =
+            AvaloniaProperty.Register<Animatable, IClock>(nameof(Clock), inherits: true);
 
-        /// <summary>
-        /// Gets or sets the state of the animation for this
-        /// control.
-        /// </summary>
-        public PlayState PlayState
+        public IClock Clock
         {
-            get { return _playState; }
-            set { SetAndRaise(PlayStateProperty, ref _playState, value); }
+            get => GetValue(ClockProperty);
+            set => SetValue(ClockProperty, value);
         }
 
         /// <summary>
@@ -69,9 +57,9 @@ namespace Avalonia.Animation
 
                 if (match != null)
                 {
-                    match.Apply(this, e.OldValue, e.NewValue);
+                    match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue);
                 }
             }
         }
     }
-}
+}

+ 5 - 10
src/Avalonia.Animation/Animation.cs

@@ -17,11 +17,6 @@ namespace Avalonia.Animation
     /// </summary>
     public class Animation : AvaloniaList<KeyFrame>, IAnimation
     {
-        /// <summary>
-        /// Gets or sets the animation play state for all animations
-        /// </summary> 
-        public static PlayState GlobalPlayState { get; set; } = PlayState.Run;
-
         /// <summary>
         /// Gets or sets the active time of this animation.
         /// </summary>
@@ -149,12 +144,12 @@ namespace Avalonia.Animation
         }
 
         /// <inheritdocs/>
-        public IDisposable Apply(Animatable control, IObservable<bool> match, Action onComplete)
+        public IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
         {
             var (animators, subscriptions) = InterpretKeyframes(control);
             if (animators.Count == 1)
             {
-                subscriptions.Add(animators[0].Apply(this, control, match, onComplete));
+                subscriptions.Add(animators[0].Apply(this, control, clock, match, onComplete));
             }
             else
             {
@@ -168,7 +163,7 @@ namespace Avalonia.Animation
                         animatorOnComplete = () => tcs.SetResult(null);
                         completionTasks.Add(tcs.Task);
                     }
-                    subscriptions.Add(animator.Apply(this, control, match, animatorOnComplete));
+                    subscriptions.Add(animator.Apply(this, control, clock, match, animatorOnComplete));
                 }
 
                 if (onComplete != null)
@@ -180,7 +175,7 @@ namespace Avalonia.Animation
         }
 
         /// <inheritdocs/>
-        public Task RunAsync(Animatable control)
+        public Task RunAsync(Animatable control, IClock clock = null)
         {
             var run = new TaskCompletionSource<object>();
 
@@ -188,7 +183,7 @@ namespace Avalonia.Animation
                 run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
 
             IDisposable subscriptions = null;
-            subscriptions = this.Apply(control, Observable.Return(true), () =>
+            subscriptions = this.Apply(control, clock, Observable.Return(true), () =>
             {
                 run.SetResult(null);
                 subscriptions?.Dispose();

+ 16 - 45
src/Avalonia.Animation/AnimationInstance`1.cs

@@ -8,7 +8,7 @@ using Avalonia.Reactive;
 namespace Avalonia.Animation
 {
     /// <summary>
-    /// Handles interpolatoin and time-related functions 
+    /// Handles interpolation and time-related functions 
     /// for keyframe animations.
     /// </summary>
     internal class AnimationInstance<T> : SingleSubscriberObservableBase<T>
@@ -19,7 +19,6 @@ namespace Avalonia.Animation
         private double _currentIteration;
         private bool _isLooping;
         private bool _gotFirstKFValue;
-        private bool _gotFirstFrameCount;
         private bool _iterationDelay;
         private FillMode _fillMode;
         private PlaybackDirection _animationDirection;
@@ -29,15 +28,14 @@ namespace Avalonia.Animation
         private double _speedRatio;
         private TimeSpan _delay;
         private TimeSpan _duration;
-        private TimeSpan _firstFrameCount;
-        private TimeSpan _internalClock;
-        private TimeSpan? _previousClock;
         private Easings.Easing _easeFunc;
         private Action _onCompleteAction;
         private Func<double, T, T> _interpolator;
         private IDisposable _timerSubscription;
+        private readonly IClock _baseClock;
+        private IClock _clock;
 
-        public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, Action OnComplete, Func<double, T, T> Interpolator)
+        public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action OnComplete, Func<double, T, T> Interpolator)
         {
             if (animation.SpeedRatio <= 0)
                 throw new InvalidOperationException("Speed ratio cannot be negative or zero.");
@@ -73,17 +71,19 @@ namespace Avalonia.Animation
             _fillMode = animation.FillMode;
             _onCompleteAction = OnComplete;
             _interpolator = Interpolator;
+            _baseClock = baseClock;
         }
 
         protected override void Unsubscribed()
         {
             _timerSubscription?.Dispose();
+            _clock.PlayState = PlayState.Stop;
         }
 
         protected override void Subscribed()
         {
-            _timerSubscription = Timing.AnimationsTimer
-                                       .Subscribe(p => this.Step(p));
+            _clock = new Clock(_baseClock);
+            _timerSubscription = _clock.Subscribe(Step);
         }
 
         public void Step(TimeSpan frameTick)
@@ -116,46 +116,21 @@ namespace Avalonia.Animation
                     PublishNext(_lastInterpValue);
         }
 
-        private void DoPlayStatesAndTime(TimeSpan systemTime)
+        private void DoPlayStates()
         {
-            if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop)
+            if (_clock.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop)
                 DoComplete();
 
-            if (!_previousClock.HasValue)
-            {
-                _previousClock = systemTime;
-                _internalClock = TimeSpan.Zero;
-            }
-            else
-            {
-                if (Animation.GlobalPlayState == PlayState.Pause || _targetControl.PlayState == PlayState.Pause)
-                {
-                    _previousClock = systemTime;
-                    return;
-                }
-                var delta = systemTime - _previousClock;
-                _internalClock += delta.Value;
-                _previousClock = systemTime;
-            }
-
             if (!_gotFirstKFValue)
             {
                 _firstKFValue = (T)_parent.First().Value;
                 _gotFirstKFValue = true;
             }
-
-            if (!_gotFirstFrameCount)
-            {
-                _firstFrameCount = _internalClock;
-                _gotFirstFrameCount = true;
-            }
         }
 
-        private void InternalStep(TimeSpan systemTime)
+        private void InternalStep(TimeSpan time)
         {
-            DoPlayStatesAndTime(systemTime);
- 
-            var time = _internalClock - _firstFrameCount;
+            DoPlayStates();
             var delayEndpoint = _delay;
             var iterationEndpoint = delayEndpoint + _duration;
 
@@ -176,22 +151,18 @@ namespace Avalonia.Animation
                 }
 
                 //Calculate the current iteration number
-                _currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2;
+                _currentIteration = (int)Math.Floor((double)((double)time.Ticks / iterationEndpoint.Ticks)) + 2;
             }
             else
             {
-                _previousClock = systemTime;
                 return;
             }
 
-            time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks);
+            time = TimeSpan.FromTicks((long)(time.Ticks % iterationEndpoint.Ticks));
 
             if (!_isLooping)
             {
-                if (_currentIteration > _repeatCount)
-                    DoComplete();
-
-                if (time > iterationEndpoint)
+                if ((_currentIteration > _repeatCount) || (time > iterationEndpoint))
                     DoComplete();
             }
 
@@ -225,4 +196,4 @@ namespace Avalonia.Animation
             }
         }
     }
-}
+}

+ 68 - 50
src/Avalonia.Animation/Animator`1.cs

@@ -1,10 +1,14 @@
-using System;
+// 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;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reactive.Linq;
 using Avalonia.Animation.Utils;
 using Avalonia.Collections;
 using Avalonia.Data;
+using Avalonia.Reactive;
 
 namespace Avalonia.Animation
 {
@@ -17,7 +21,7 @@ namespace Avalonia.Animation
         /// List of type-converted keyframes.
         /// </summary>
         private readonly List<AnimatorKeyFrame> _convertedKeyframes = new List<AnimatorKeyFrame>();
- 
+
         private bool _isVerifiedAndConverted;
 
         /// <summary>
@@ -28,21 +32,17 @@ namespace Avalonia.Animation
         public Animator()
         {
             // Invalidate keyframes when changed.
-             this.CollectionChanged += delegate { _isVerifiedAndConverted = false; };
+            this.CollectionChanged += delegate { _isVerifiedAndConverted = false; };
         }
 
         /// <inheritdoc/>
-        public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> match, Action onComplete)
+        public virtual IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
         {
-             if (!_isVerifiedAndConverted)
+            if (!_isVerifiedAndConverted)
                 VerifyConvertKeyFrames();
 
-            return match
-                .Where(p => p)
-                .Subscribe(_ =>
-                {
-                    var timerObs = RunKeyFrames(animation, control, onComplete);
-                });
+            var subject = new DisposeAnimationInstanceSubject<T>(this, animation, control, clock, onComplete);
+            return match.Subscribe(subject);
         }
 
         /// <summary>
@@ -52,58 +52,84 @@ namespace Avalonia.Animation
         /// (i.e., the normalized time between the selected keyframes, relative to the
         /// time parameter).
         /// </summary>
-        /// <param name="t">The time parameter, relative to the total animation time</param>
-        protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t)
+        /// <param name="animationTime">The time parameter, relative to the total animation time</param>
+        protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double animationTime)
         {
-            AnimatorKeyFrame firstCue, lastCue ;
+            AnimatorKeyFrame firstKeyframe, lastKeyframe;
             int kvCount = _convertedKeyframes.Count;
             if (kvCount > 2)
             {
-                if (t <= 0.0)
+                if (animationTime <= 0.0)
                 {
-                    firstCue = _convertedKeyframes[0];
-                    lastCue = _convertedKeyframes[1];
+                    firstKeyframe = _convertedKeyframes[0];
+                    lastKeyframe = _convertedKeyframes[1];
                 }
-                else if (t >= 1.0)
+                else if (animationTime >= 1.0)
                 {
-                    firstCue = _convertedKeyframes[_convertedKeyframes.Count - 2];
-                    lastCue = _convertedKeyframes[_convertedKeyframes.Count - 1];
+                    firstKeyframe = _convertedKeyframes[_convertedKeyframes.Count - 2];
+                    lastKeyframe = _convertedKeyframes[_convertedKeyframes.Count - 1];
                 }
                 else
                 {
-                    (double time, int index) maxval = (0.0d, 0);
-                    for (int i = 0; i < _convertedKeyframes.Count; i++)
-                    {
-                        var comp = _convertedKeyframes[i].Cue.CueValue;
-                        if (t >= comp)
-                        {
-                            maxval = (comp, i);
-                        }
-                    }
-                    firstCue = _convertedKeyframes[maxval.index];
-                    lastCue = _convertedKeyframes[maxval.index + 1];
+                    int index = FindClosestBeforeKeyFrame(animationTime);
+                    firstKeyframe = _convertedKeyframes[index];
+                    lastKeyframe = _convertedKeyframes[index + 1];
                 }
             }
             else
             {
-                firstCue = _convertedKeyframes[0];
-                lastCue = _convertedKeyframes[1];
+                firstKeyframe = _convertedKeyframes[0];
+                lastKeyframe = _convertedKeyframes[1];
             }
 
-            double t0 = firstCue.Cue.CueValue;
-            double t1 = lastCue.Cue.CueValue;
-            var intraframeTime = (t - t0) / (t1 - t0);
-            var firstFrameData = (firstCue.GetTypedValue<T>(), firstCue.isNeutral);
-            var lastFrameData = (lastCue.GetTypedValue<T>(), lastCue.isNeutral);
+            double t0 = firstKeyframe.Cue.CueValue;
+            double t1 = lastKeyframe.Cue.CueValue;
+            var intraframeTime = (animationTime - t0) / (t1 - t0);
+            var firstFrameData = (firstKeyframe.GetTypedValue<T>(), firstKeyframe.isNeutral);
+            var lastFrameData = (lastKeyframe.GetTypedValue<T>(), lastKeyframe.isNeutral);
             return (intraframeTime, new KeyFramePair<T>(firstFrameData, lastFrameData));
         }
 
+        private int FindClosestBeforeKeyFrame(double time)
+        {
+            int FindClosestBeforeKeyFrame(int startIndex, int length)
+            {
+                if (length == 0 || length == 1)
+                {
+                    return startIndex;
+                }
+
+                int middle = startIndex + (length / 2);
+
+                if (_convertedKeyframes[middle].Cue.CueValue < time)
+                {
+                    return FindClosestBeforeKeyFrame(middle, length - middle);
+                }
+                else if (_convertedKeyframes[middle].Cue.CueValue > time)
+                {
+                    return FindClosestBeforeKeyFrame(startIndex, middle - startIndex);
+                }
+                else
+                {
+                    return middle;
+                }
+            }
+
+            return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count);
+        }
+
         /// <summary>
         /// Runs the KeyFrames Animation.
         /// </summary>
-        private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete)
+        internal IDisposable Run(Animation animation, Animatable control, IClock clock, Action onComplete)
         {
-            var instance = new AnimationInstance<T>(animation, control, this, onComplete, DoInterpolation);
+            var instance = new AnimationInstance<T>(
+                animation,
+                control,
+                this,
+                clock ?? control.Clock ?? Clock.GlobalClock,
+                onComplete,
+                DoInterpolation);
             return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
         }
 
@@ -124,14 +150,6 @@ namespace Avalonia.Animation
 
             AddNeutralKeyFramesIfNeeded();
 
-            var copy = _convertedKeyframes.ToList().OrderBy(p => p.Cue.CueValue);
-            _convertedKeyframes.Clear();
-
-            foreach (AnimatorKeyFrame keyframe in copy)
-            {
-                _convertedKeyframes.Add(keyframe);
-            }
-
             _isVerifiedAndConverted = true;
         }
 
@@ -161,7 +179,7 @@ namespace Avalonia.Animation
         {
             if (!hasStartKey)
             {
-                _convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
+                _convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
             }
 
             if (!hasEndKey)
@@ -170,4 +188,4 @@ namespace Avalonia.Animation
             }
         }
     }
-}
+}

+ 30 - 0
src/Avalonia.Animation/Clock.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using System.Text;
+using Avalonia.Reactive;
+
+namespace Avalonia.Animation
+{
+    public class Clock : ClockBase
+    {
+        public static IClock GlobalClock => AvaloniaLocator.Current.GetService<IGlobalClock>();
+
+        private IDisposable _parentSubscription;
+
+        public Clock()
+            :this(GlobalClock)
+        {
+        }
+        
+        public Clock(IClock parent)
+        {
+            _parentSubscription = parent.Subscribe(Pulse);
+        }
+
+        protected override void Stop()
+        {
+            _parentSubscription?.Dispose();
+        }
+    }
+}

+ 72 - 0
src/Avalonia.Animation/ClockBase.cs

@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using System.Text;
+using Avalonia.Reactive;
+
+namespace Avalonia.Animation
+{
+    public class ClockBase : IClock
+    {
+        private ClockObservable _observable;
+
+        private IObservable<TimeSpan> _connectedObservable;
+
+        private TimeSpan? _previousTime;
+        private TimeSpan _internalTime;
+
+        protected ClockBase()
+        {
+            _observable = new ClockObservable();
+            _connectedObservable = _observable.Publish().RefCount();
+        }
+
+        protected bool HasSubscriptions => _observable.HasSubscriptions;
+
+        public PlayState PlayState { get; set; }
+
+        protected void Pulse(TimeSpan systemTime)
+        {
+            if (!_previousTime.HasValue)
+            {
+                _previousTime = systemTime;
+                _internalTime = TimeSpan.Zero;
+            }
+            else
+            {
+                if (PlayState == PlayState.Pause)
+                {
+                    _previousTime = systemTime;
+                    return;
+                }
+                var delta = systemTime - _previousTime;
+                _internalTime += delta.Value;
+                _previousTime = systemTime;
+            }
+
+            _observable.Pulse(_internalTime);
+
+            if (PlayState == PlayState.Stop)
+            {
+                Stop();
+            }
+        }
+
+        protected virtual void Stop()
+        {
+        }
+
+        public IDisposable Subscribe(IObserver<TimeSpan> observer)
+        {
+            return _connectedObservable.Subscribe(observer);
+        }
+
+        private class ClockObservable : LightweightObservableBase<TimeSpan>
+        {
+            public bool HasSubscriptions { get; private set; }
+            public void Pulse(TimeSpan time) => PublishNext(time);
+            protected override void Initialize() => HasSubscriptions = true;
+            protected override void Deinitialize() => HasSubscriptions = false;
+        }
+    }
+}

+ 65 - 0
src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs

@@ -0,0 +1,65 @@
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive.Linq;
+using Avalonia.Animation.Utils;
+using Avalonia.Collections;
+using Avalonia.Data;
+using Avalonia.Reactive;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Manages the lifetime of animation instances as determined by its selector state.
+    /// </summary>
+    internal class DisposeAnimationInstanceSubject<T> : IObserver<bool>, IDisposable
+    {
+        private IDisposable _lastInstance;
+        private bool _lastMatch;
+        private Animator<T> _animator;
+        private Animation _animation;
+        private Animatable _control;
+        private Action _onComplete;
+        private IClock _clock;
+
+        public DisposeAnimationInstanceSubject(Animator<T> animator, Animation animation, Animatable control, IClock clock, Action onComplete)
+        {
+            this._animator = animator;
+            this._animation = animation;
+            this._control = control;
+            this._onComplete = onComplete;
+            this._clock = clock;
+        }
+
+
+        public void Dispose()
+        {
+            _lastInstance?.Dispose();
+        }
+
+        public void OnCompleted()
+        {
+        }
+
+        public void OnError(Exception error)
+        {
+            _lastInstance?.Dispose();
+        }
+
+        void IObserver<bool>.OnNext(bool matchVal)
+        {
+            if (matchVal != _lastMatch)
+            {
+                _lastInstance?.Dispose();
+                if (matchVal)
+                {
+                    _lastInstance = _animator.Run(_animation, _control, _clock, _onComplete);
+                }
+                _lastMatch = matchVal;
+            }
+        }
+    }
+}

+ 4 - 1
src/Avalonia.Animation/DoubleAnimator.cs

@@ -1,4 +1,7 @@
-namespace Avalonia.Animation
+// 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.
+
+namespace Avalonia.Animation
 {
     /// <summary>
     /// Animator that handles <see cref="double"/> properties.

+ 4 - 1
src/Avalonia.Animation/FillMode.cs

@@ -1,4 +1,7 @@
-namespace Avalonia.Animation
+// 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.
+
+namespace Avalonia.Animation
 {
     public enum FillMode
     {

+ 7 - 4
src/Avalonia.Animation/IAnimation.cs

@@ -1,3 +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;
 using System.Threading.Tasks;
 
@@ -9,13 +12,13 @@ namespace Avalonia.Animation
     public interface IAnimation
     {
         /// <summary>
-        /// Apply the animation to the specified control
+        /// Apply the animation to the specified control and run it when <paramref name="match" /> produces <c>true</c>.
         /// </summary>
-        IDisposable Apply(Animatable control, IObservable<bool> match, Action onComplete = null);
+        IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete = null);
 
         /// <summary>
-        /// Run the animation to the specified control
+        /// Run the animation on the specified control.
         /// </summary>
-        Task RunAsync(Animatable control);
+        Task RunAsync(Animatable control, IClock clock);
     }
 }

+ 3 - 0
src/Avalonia.Animation/IAnimationSetter.cs

@@ -1,3 +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.
+
 namespace Avalonia.Animation
 {
     public interface IAnimationSetter

+ 5 - 2
src/Avalonia.Animation/IAnimator.cs

@@ -1,4 +1,7 @@
-using System;
+// 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;
 using System.Collections.Generic;
 
 namespace Avalonia.Animation
@@ -16,6 +19,6 @@ namespace Avalonia.Animation
         /// <summary>
         /// Applies the current KeyFrame group to the specified control.
         /// </summary>
-        IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch, Action onComplete);
+        IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete);
     }
 }

+ 11 - 0
src/Avalonia.Animation/IClock.cs

@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation
+{
+    public interface IClock : IObservable<TimeSpan>
+    {
+        PlayState PlayState { get; set; }
+    }
+}

+ 10 - 0
src/Avalonia.Animation/IGlobalClock.cs

@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation
+{
+    public interface IGlobalClock : IClock
+    {
+    }
+}

+ 1 - 1
src/Avalonia.Animation/ITransition.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Animation
         /// <summary>
         /// Applies the transition to the specified <see cref="Animatable"/>.
         /// </summary>
-        IDisposable Apply(Animatable control, object oldValue, object newValue);
+        IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue);
 
         /// <summary>
         /// Gets the property to be animated.

+ 4 - 1
src/Avalonia.Animation/KeyFrame.cs

@@ -1,4 +1,7 @@
-using System;
+// 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;
 using System.Collections.Generic;
 using Avalonia.Collections;
 

+ 3 - 0
src/Avalonia.Animation/KeyFramePair`1.cs

@@ -1,3 +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.
+
 namespace Avalonia.Animation
 {
     /// <summary>

+ 4 - 1
src/Avalonia.Animation/PlayState.cs

@@ -1,4 +1,7 @@
-namespace Avalonia.Animation
+// 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.
+
+namespace Avalonia.Animation
 {
     /// <summary>
     /// Determines the playback state of an animation.

+ 4 - 1
src/Avalonia.Animation/PlaybackDirection.cs

@@ -1,4 +1,7 @@
-namespace Avalonia.Animation
+// 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.
+
+namespace Avalonia.Animation
 {
     /// <summary>
     /// Determines the playback direction of an animation.

+ 0 - 54
src/Avalonia.Animation/Timing.cs

@@ -1,54 +0,0 @@
-// 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;
-using System.Linq;
-using System.Reactive.Linq;
-using Avalonia.Threading;
-
-namespace Avalonia.Animation
-{
-    /// <summary>
-    /// Provides global timing functions for animations.
-    /// </summary>
-    public static class Timing
-    {
-        /// <summary>
-        /// The number of frames per second.
-        /// </summary>
-        public const int FramesPerSecond = 60;
-
-        /// <summary>
-        /// The time span of each frame.
-        /// </summary>
-        internal static readonly TimeSpan FrameTick = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
-
-        /// <summary>
-        /// Initializes static members of the <see cref="Timing"/> class.
-        /// </summary>
-        static Timing()
-        { 
-            var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance);
-
-            AnimationsTimer = globalTimer
-                .Select(_ => GetTickCount())
-                .Publish()
-                .RefCount();
-        }
-
-        internal static TimeSpan GetTickCount() => TimeSpan.FromMilliseconds(Environment.TickCount);
-
-        /// <summary>
-        /// Gets the animation timer.
-        /// </summary>
-        /// <remarks>
-        /// The animation timer triggers usually at 60 times per second or as
-        /// defined in <see cref="FramesPerSecond"/>.
-        /// The parameter passed to a subsciber is the current playstate of the animation.
-        /// </remarks>
-        internal static IObservable<TimeSpan> AnimationsTimer
-        {
-            get;
-        }
-    }
-}

+ 14 - 13
src/Avalonia.Animation/TransitionInstance.cs

@@ -15,21 +15,22 @@ namespace Avalonia.Animation
     /// </summary>
     internal class TransitionInstance : SingleSubscriberObservableBase<double>
     {
-        private IDisposable timerSubscription;
-        private TimeSpan startTime;
-        private TimeSpan duration;
+        private IDisposable _timerSubscription;
+        private TimeSpan _duration;
+        private readonly IClock _baseClock;
+        private IClock _clock;
 
-        public TransitionInstance(TimeSpan Duration)
+        public TransitionInstance(IClock clock, TimeSpan Duration)
         {
-            duration = Duration;
+            _duration = Duration;
+            _baseClock = clock;
         }
 
         private void TimerTick(TimeSpan t)
         {
-            var interpVal = (double)(t.Ticks - startTime.Ticks) / duration.Ticks;
+            var interpVal = (double)t.Ticks / _duration.Ticks;
 
-            if (interpVal > 1d
-             || interpVal < 0d)
+            if (interpVal > 1d || interpVal < 0d)
             {
                 PublishCompleted();
                 return;
@@ -40,15 +41,15 @@ namespace Avalonia.Animation
 
         protected override void Unsubscribed()
         {
-            timerSubscription?.Dispose();
+            _timerSubscription?.Dispose();
+            _clock.PlayState = PlayState.Stop;
         }
 
         protected override void Subscribed()
         {
-            startTime = Timing.GetTickCount();
-            timerSubscription = Timing.AnimationsTimer
-                                      .Subscribe(t => TimerTick(t));
+            _clock = new Clock(_baseClock);
+            _timerSubscription = _clock.Subscribe(TimerTick);
             PublishNext(0.0d);
         }
     }
-}
+}

+ 2 - 5
src/Avalonia.Animation/Transition`1.cs

@@ -14,7 +14,6 @@ namespace Avalonia.Animation
     public abstract class Transition<T> : AvaloniaObject, ITransition
     {
         private AvaloniaProperty _prop;
-        private Easing _easing;
 
         /// <summary>
         /// Gets the duration of the animation.
@@ -49,12 +48,10 @@ namespace Avalonia.Animation
         public abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue);
 
         /// <inheritdocs/>
-        public virtual IDisposable Apply(Animatable control, object oldValue, object newValue)
+        public virtual IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue)
         {
-            var transition = DoTransition(new TransitionInstance(Duration), (T)oldValue, (T)newValue);
+            var transition = DoTransition(new TransitionInstance(clock, Duration), (T)oldValue, (T)newValue);
             return control.Bind<T>((AvaloniaProperty<T>)Property, transition, Data.BindingPriority.Animation);
         }
-
-
     }
 }

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

@@ -3,6 +3,7 @@
     <TargetFramework>netstandard2.0</TargetFramework>
     <AssemblyName>Avalonia.Base</AssemblyName>
     <RootNamespace>Avalonia</RootNamespace>
+    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
   </PropertyGroup>
   <Import Project="..\..\build\Base.props" />
   <Import Project="..\..\build\Binding.props" />

+ 18 - 0
src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace Avalonia.Platform.Interop
+{
+    public interface IDynamicLibraryLoader
+    {
+        IntPtr LoadLibrary(string dll);
+        IntPtr GetProcAddress(IntPtr dll, string proc, bool optional);
+    }
+
+    public class DynamicLibraryLoaderException : Exception
+    {
+        public DynamicLibraryLoaderException(string message) : base(message)
+        {
+            
+        }
+    }
+}

+ 2 - 2
src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs → src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs

@@ -2,9 +2,9 @@
 using System.Runtime.InteropServices;
 using System.Text;
 
-namespace Avalonia.Gtk3.Interop
+namespace Avalonia.Platform.Interop
 {
-    class Utf8Buffer : SafeHandle
+    public class Utf8Buffer : SafeHandle
     {
         private GCHandle _gchandle;
         private byte[] _data;

+ 7 - 0
src/Avalonia.Base/PriorityBindingEntry.cs

@@ -50,6 +50,11 @@ namespace Avalonia
             get;
         }
 
+        /// <summary>
+        /// Gets a value indicating whether the binding has completed.
+        /// </summary>
+        public bool HasCompleted { get; private set; }
+
         /// <summary>
         /// The current value of the binding.
         /// </summary>
@@ -129,6 +134,8 @@ namespace Avalonia
 
         private void Completed()
         {
+            HasCompleted = true;
+
             if (Dispatcher.UIThread.CheckAccess())
             {
                 _owner.Completed(this);

+ 9 - 5
src/Avalonia.Base/PriorityLevel.cs

@@ -112,12 +112,16 @@ namespace Avalonia
 
             return Disposable.Create(() =>
             {
-                Bindings.Remove(node);
-                entry.Dispose();
-
-                if (entry.Index >= ActiveBindingIndex)
+                if (!entry.HasCompleted)
                 {
-                    ActivateFirstBinding();
+                    Bindings.Remove(node);
+
+                    entry.Dispose();
+
+                    if (entry.Index >= ActiveBindingIndex)
+                    {
+                        ActivateFirstBinding();
+                    }
                 }
             });
         }

+ 1 - 9
src/Avalonia.Base/Reactive/LightweightObservableBase.cs

@@ -82,18 +82,10 @@ namespace Avalonia.Reactive
                         if (observers.Count == 0)
                         {
                             observers.TrimExcess();
+                            Deinitialize();
                         }
-                        else
-                        {
-                            return;
-                        }
-                    } else
-                    {
-                        return;
                     }
                 }
-
-                Deinitialize();
             }
         }
 

+ 2 - 3
src/Avalonia.Base/Reactive/ObservableEx.cs

@@ -21,7 +21,7 @@ namespace Avalonia.Reactive
         {
             return new SingleValueImpl<T>(value);
         }
-
+ 
         private class SingleValueImpl<T> : IObservable<T>
         {
             private T _value;
@@ -30,7 +30,6 @@ namespace Avalonia.Reactive
             {
                 _value = value;
             }
-
             public IDisposable Subscribe(IObserver<T> observer)
             {
                 observer.OnNext(_value);
@@ -38,4 +37,4 @@ namespace Avalonia.Reactive
             }
         }
     }
-}
+}

+ 23 - 1
src/Avalonia.Base/Utilities/MathUtilities.cs

@@ -1,7 +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.
 
-
 namespace Avalonia.Utilities
 {
     /// <summary>
@@ -31,5 +30,28 @@ namespace Avalonia.Utilities
                 return val;
             }
         }
+
+        /// <summary>
+        /// Clamps a value between a minimum and maximum value.
+        /// </summary>
+        /// <param name="val">The value.</param>
+        /// <param name="min">The minimum value.</param>
+        /// <param name="max">The maximum value.</param>
+        /// <returns>The clamped value.</returns>
+        public static int Clamp(int val, int min, int max)
+        {
+            if (val < min)
+            {
+                return min;
+            }
+            else if (val > max)
+            {
+                return max;
+            }
+            else
+            {
+                return val;
+            }
+        }
     }
 }

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

@@ -272,10 +272,10 @@ namespace Avalonia.Controls
 
             s_setupWasAlreadyCalled = true;
 
-            Instance.RegisterServices();
             RuntimePlatformServicesInitializer();
             WindowingSubsystemInitializer();
             RenderingSubsystemInitializer();
+            Instance.RegisterServices();
             Instance.Initialize();
             AfterSetupCallback(Self);
         }

+ 14 - 2
src/Avalonia.Controls/Application.cs

@@ -4,12 +4,14 @@
 using System;
 using System.Reactive.Concurrency;
 using System.Threading;
+using Avalonia.Animation;
 using Avalonia.Controls;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.Input.Raw;
 using Avalonia.Platform;
+using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.Threading;
 
@@ -120,11 +122,11 @@ namespace Avalonia
                 if (_resources != null)
                 {
                     hadResources = _resources.Count > 0;
-                    _resources.ResourcesChanged -= ResourcesChanged;
+                    _resources.ResourcesChanged -= ThisResourcesChanged;
                 }
 
                 _resources = value;
-                _resources.ResourcesChanged += ResourcesChanged;
+                _resources.ResourcesChanged += ThisResourcesChanged;
 
                 if (hadResources || _resources.Count > 0)
                 {
@@ -335,6 +337,16 @@ namespace Avalonia
                 .Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
                 .Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)
                 .Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
+
+            var clock = new RenderLoopClock();
+            AvaloniaLocator.CurrentMutable
+                .Bind<IGlobalClock>().ToConstant(clock)
+                .GetService<IRenderLoop>()?.Add(clock);
+        }
+
+        private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
+        {
+            ResourcesChanged?.Invoke(this, e);
         }
     }
 }

+ 5 - 1
src/Avalonia.Controls/Border.cs

@@ -43,7 +43,11 @@ namespace Avalonia.Controls
         /// </summary>
         static Border()
         {
-            AffectsRender<Border>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
+            AffectsRender<Border>(
+                BackgroundProperty,
+                BorderBrushProperty,
+                BorderThicknessProperty,
+                CornerRadiusProperty);
             AffectsMeasure<Border>(BorderThicknessProperty);
         }
 

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

@@ -49,7 +49,7 @@ namespace Avalonia.Controls
             if (Drawing != null)
             {
                 using (context.PushPreTransform(_transform))
-                using (context.PushClip(Bounds))
+                using (context.PushClip(new Rect(Bounds.Size)))
                 {
                     Drawing.Draw(context);
                 }

+ 2 - 2
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Controls.Generators
     /// </summary>
     public class ItemContainerGenerator : IItemContainerGenerator
     {
-        private Dictionary<int, ItemContainerInfo> _containers = new Dictionary<int, ItemContainerInfo>();
+        private SortedDictionary<int, ItemContainerInfo> _containers = new SortedDictionary<int, ItemContainerInfo>();
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class.
@@ -246,4 +246,4 @@ namespace Avalonia.Controls.Generators
             Recycled?.Invoke(this, e);
         }
     }
-}
+}

+ 1 - 0
src/Avalonia.Controls/Image.cs

@@ -26,6 +26,7 @@ namespace Avalonia.Controls
         static Image()
         {
             AffectsRender<Image>(SourceProperty, StretchProperty);
+            AffectsMeasure<Image>(SourceProperty, StretchProperty);
         }
 
         /// <summary>

+ 34 - 4
src/Avalonia.Controls/MenuItem.cs

@@ -99,6 +99,7 @@ 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);
             ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
@@ -357,10 +358,21 @@ namespace Avalonia.Controls
         {
             base.OnTemplateApplied(e);
 
-            _popup = e.NameScope.Get<Popup>("PART_Popup");
-            _popup.DependencyResolver = DependencyResolver.Instance;
-            _popup.Opened += PopupOpened;
-            _popup.Closed += PopupClosed;
+            if (_popup != null)
+            {
+                _popup.Opened -= PopupOpened;
+                _popup.Closed -= PopupClosed;
+                _popup.DependencyResolver = null;
+            }
+
+            _popup = e.NameScope.Find<Popup>("PART_Popup");
+
+            if (_popup != null)
+            {
+                _popup.DependencyResolver = DependencyResolver.Instance;
+                _popup.Opened += PopupOpened;
+                _popup.Closed += PopupClosed;
+            }
         }
 
         /// <summary>
@@ -408,6 +420,24 @@ namespace Avalonia.Controls
             IsEnabled = Command == null || Command.CanExecute(CommandParameter);
         }
 
+        /// <summary>
+        /// Called when the <see cref="Header"/> property changes.
+        /// </summary>
+        /// <param name="e">The property change event.</param>
+        private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.NewValue is string newValue && newValue == "-")
+            {
+                PseudoClasses.Add(":separator");
+                Focusable = false;
+            }
+            else if (e.OldValue is string oldValue && oldValue == "-")
+            {
+                PseudoClasses.Remove(":separator");
+                Focusable = true;
+            }
+        }
+
         /// <summary>
         /// Called when the <see cref="Icon"/> property changes.
         /// </summary>

+ 1 - 0
src/Avalonia.Controls/Panel.cs

@@ -30,6 +30,7 @@ namespace Avalonia.Controls
         /// </summary>
         static Panel()
         {
+            AffectsRender<Panel>(BackgroundProperty);
             ClipToBoundsProperty.OverrideDefaultValue<Panel>(true);
         }
 

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

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

+ 7 - 4
src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs

@@ -9,12 +9,15 @@ using Avalonia.Threading;
 
 namespace Avalonia.Controls.Platform
 {
-    public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop
+    public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderTimer
     {
         public InternalPlatformThreadingInterface()
         {
             TlsCurrentThreadIsLoopThread = true;
-            StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs()));
+            StartTimer(
+                DispatcherPriority.Render,
+                new TimeSpan(0, 0, 0, 0, 66),
+                () => Tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount)));
         }
 
         private readonly AutoResetEvent _signaled = new AutoResetEvent(false);
@@ -105,7 +108,7 @@ namespace Avalonia.Controls.Platform
 
         public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread;
         public event Action<DispatcherPriority?> Signaled;
-        public event EventHandler<EventArgs> Tick;
+        public event Action<TimeSpan> Tick;
 
     }
-}
+}

+ 1 - 7
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -197,13 +197,6 @@ namespace Avalonia.Controls.Presenters
             }
         }
 
-        /// <inheritdoc/>
-        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
-        {
-            base.OnAttachedToVisualTree(e);
-            _dataTemplate = null;
-        }
-
         /// <summary>
         /// Updates the <see cref="Child"/> control based on the control's <see cref="Content"/>.
         /// </summary>
@@ -268,6 +261,7 @@ namespace Avalonia.Controls.Presenters
         protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
             base.OnAttachedToLogicalTree(e);
+            _dataTemplate = null;
             _createdChild = false;
             InvalidateMeasure();
         }

+ 21 - 3
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -76,9 +76,23 @@ namespace Avalonia.Controls.Presenters
                         var firstIndex = ItemCount - panel.Children.Count;
                         RecycleContainersForMove(firstIndex - FirstIndex);
 
-                        panel.PixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
-                            panel.Children[0].Bounds.Height :
-                            panel.Children[0].Bounds.Width;
+                        double pixelOffset;
+                        var child = panel.Children[0];
+
+                        if (child.IsArrangeValid)
+                        {
+                            pixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
+                                                    child.Bounds.Height :
+                                                    child.Bounds.Width;
+                        }
+                        else
+                        {
+                            pixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
+                                                    child.DesiredSize.Height :
+                                                    child.DesiredSize.Width;
+                        }
+
+                        panel.PixelOffset = pixelOffset;
                     }
                 }
             }
@@ -402,6 +416,10 @@ namespace Avalonia.Controls.Presenters
             var panel = VirtualizingPanel;
             var generator = Owner.ItemContainerGenerator;
             var selector = Owner.MemberSelector;
+
+            //validate delta it should never overflow last index or generate index < 0 
+            delta = MathUtilities.Clamp(delta, -FirstIndex, ItemCount - FirstIndex - panel.Children.Count);
+
             var sign = delta < 0 ? -1 : 1;
             var count = Math.Min(Math.Abs(delta), panel.Children.Count);
             var move = count < panel.Children.Count;

+ 7 - 1
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@@ -6,11 +6,12 @@ using System.Collections.Specialized;
 using System.Linq;
 using Avalonia.VisualTree;
 using Avalonia.Media;
+using Avalonia.Rendering;
 
 namespace Avalonia.Controls.Primitives
 {
     // TODO: Need to track position of adorned elements and move the adorner if they move.
-    public class AdornerLayer : Panel
+    public class AdornerLayer : Panel, ICustomSimpleHitTest
     {
         public static AttachedProperty<Visual> AdornedElementProperty =
             AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Visual>("AdornedElement");
@@ -137,6 +138,11 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        public bool HitTest(Point point)
+        {
+            return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
+        }
+
         private class AdornedElementInfo
         {
             public IDisposable Subscription { get; set; }

+ 4 - 4
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -289,12 +289,12 @@ namespace Avalonia.Controls.Primitives
         /// <inheritdoc/>
         public override void EndInit()
         {
-            base.EndInit();
-
             if (--_updateCount == 0)
             {
                 UpdateFinished();
             }
+
+            base.EndInit();
         }
 
         /// <summary>
@@ -871,8 +871,8 @@ namespace Avalonia.Controls.Primitives
                         RaisePropertyChanged(SelectedItemProperty, oldItem, item, BindingPriority.LocalValue);
                     }
 
-                    added = e.OldItems;
-                    removed = e.NewItems;
+                    added = e.NewItems;
+                    removed = e.OldItems;
                     break;
             }
 

+ 4 - 3
src/Avalonia.Controls/ProgressBar.cs

@@ -37,7 +37,8 @@ namespace Avalonia.Controls
             PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
             PseudoClass<ProgressBar>(IsIndeterminateProperty, ":indeterminate");
 
-            ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
+            ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.UpdateIndicatorWhenPropChanged);
+            IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(x => x.UpdateIndicatorWhenPropChanged);
         }
 
         public bool IsIndeterminate
@@ -114,9 +115,9 @@ namespace Avalonia.Controls
             }
         }
 
-        private void ValueChanged(AvaloniaPropertyChangedEventArgs e)
+        private void UpdateIndicatorWhenPropChanged(AvaloniaPropertyChangedEventArgs e)
         {
             UpdateIndicator(Bounds.Size);
         }
     }
-}
+}

+ 20 - 6
src/Avalonia.Controls/Remote/RemoteWidget.cs

@@ -11,11 +11,19 @@ namespace Avalonia.Controls.Remote
 {
     public class RemoteWidget : Control
     {
+        public enum SizingMode
+        {
+            Local,
+            Remote
+        }
+
         private readonly IAvaloniaRemoteTransportConnection _connection;
         private FrameMessage _lastFrame;
         private WriteableBitmap _bitmap;
         public RemoteWidget(IAvaloniaRemoteTransportConnection connection)
         {
+            Mode = SizingMode.Local;
+
             _connection = connection;
             _connection.OnMessage += (t, msg) => Dispatcher.UIThread.Post(() => OnMessage(msg));
             _connection.Send(new ClientSupportedPixelFormatsMessage
@@ -28,6 +36,8 @@ namespace Avalonia.Controls.Remote
             });
         }
 
+        public SizingMode Mode { get; set; }
+
         private void OnMessage(object msg)
         {
             if (msg is FrameMessage frame)
@@ -44,13 +54,17 @@ namespace Avalonia.Controls.Remote
 
         protected override void ArrangeCore(Rect finalRect)
         {
-            _connection.Send(new ClientViewportAllocatedMessage
+            if (Mode == SizingMode.Local)
             {
-                Width = finalRect.Width,
-                Height = finalRect.Height,
-                DpiX = 96,
-                DpiY = 96 //TODO: Somehow detect the actual DPI
-            });
+                _connection.Send(new ClientViewportAllocatedMessage
+                {
+                    Width = finalRect.Width,
+                    Height = finalRect.Height,
+                    DpiX = 10 * 96,
+                    DpiY = 10 * 96 //TODO: Somehow detect the actual DPI
+                });
+            }
+
             base.ArrangeCore(finalRect);
         }
 

+ 161 - 0
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@@ -4,11 +4,15 @@ using System.Runtime.InteropServices;
 using Avalonia.Controls.Embedding.Offscreen;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Input;
+using Avalonia.Input.Raw;
 using Avalonia.Layout;
 using Avalonia.Platform;
 using Avalonia.Remote.Protocol;
+using Avalonia.Remote.Protocol.Input;
 using Avalonia.Remote.Protocol.Viewport;
 using Avalonia.Threading;
+using InputModifiers = Avalonia.Input.InputModifiers;
+using Key = Avalonia.Input.Key;
 using PixelFormat = Avalonia.Platform.PixelFormat;
 using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
 
@@ -31,6 +35,67 @@ namespace Avalonia.Controls.Remote.Server
         {
             _transport = transport;
             _transport.OnMessage += OnMessage;
+
+            KeyboardDevice = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
+        }
+
+        private static RawMouseEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed)
+        {
+            switch (button)
+            {
+                case Avalonia.Remote.Protocol.Input.MouseButton.Left:
+                    return pressed ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp;
+
+                case Avalonia.Remote.Protocol.Input.MouseButton.Middle:
+                    return pressed ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp;
+
+                case Avalonia.Remote.Protocol.Input.MouseButton.Right:
+                    return pressed ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp;
+
+                default:
+                    return RawMouseEventType.Move;
+            }
+        }
+
+        private static InputModifiers GetAvaloniaInputModifiers (Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
+        {
+            var result = InputModifiers.None;
+
+            foreach(var modifier in modifiers)
+            {
+                switch (modifier)
+                {
+                    case Avalonia.Remote.Protocol.Input.InputModifiers.Control:
+                        result |= InputModifiers.Control;
+                        break;
+
+                    case Avalonia.Remote.Protocol.Input.InputModifiers.Alt:
+                        result |= InputModifiers.Alt;
+                        break;
+
+                    case Avalonia.Remote.Protocol.Input.InputModifiers.Shift:
+                        result |= InputModifiers.Shift;
+                        break;
+
+                    case Avalonia.Remote.Protocol.Input.InputModifiers.Windows:
+                        result |= InputModifiers.Windows;
+                        break;
+
+                    case Avalonia.Remote.Protocol.Input.InputModifiers.LeftMouseButton:
+                        result |= InputModifiers.LeftMouseButton;
+                        break;
+
+                    case Avalonia.Remote.Protocol.Input.InputModifiers.MiddleMouseButton:
+                        result |= InputModifiers.MiddleMouseButton;
+                        break;
+
+                    case Avalonia.Remote.Protocol.Input.InputModifiers.RightMouseButton:
+                        result |= InputModifiers.RightMouseButton;
+                        break;
+                }
+            }
+
+            return result;
         }
 
         protected virtual void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj)
@@ -45,6 +110,16 @@ namespace Avalonia.Controls.Remote.Server
                     }
                     Dispatcher.UIThread.Post(RenderIfNeeded);
                 }
+                if(obj is ClientRenderInfoMessage renderInfo)
+                {
+                    lock(_lock)
+                    {
+                        _dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY);
+                        _invalidated = true;
+                    }
+                    
+                    Dispatcher.UIThread.Post(RenderIfNeeded);
+                }
                 if (obj is ClientSupportedPixelFormatsMessage supportedFormats)
                 {
                     lock (_lock)
@@ -82,6 +157,84 @@ namespace Avalonia.Controls.Remote.Server
                         _pendingAllocation = allocated;
                     }
                 }
+                if(obj is PointerMovedEventMessage pointer)
+                {
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        Input?.Invoke(new RawMouseEventArgs(
+                            MouseDevice, 
+                            0, 
+                            InputRoot, 
+                            RawMouseEventType.Move, 
+                            new Point(pointer.X, pointer.Y), 
+                            GetAvaloniaInputModifiers(pointer.Modifiers)));
+                    }, DispatcherPriority.Input);
+                }
+                if(obj is PointerPressedEventMessage pressed)
+                {
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        Input?.Invoke(new RawMouseEventArgs(
+                            MouseDevice,
+                            0,
+                            InputRoot,
+                            GetAvaloniaEventType(pressed.Button, true),
+                            new Point(pressed.X, pressed.Y),
+                            GetAvaloniaInputModifiers(pressed.Modifiers)));
+                    }, DispatcherPriority.Input);
+                }
+                if (obj is PointerPressedEventMessage released)
+                {
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        Input?.Invoke(new RawMouseEventArgs(
+                            MouseDevice,
+                            0,
+                            InputRoot,
+                            GetAvaloniaEventType(released.Button, false),
+                            new Point(released.X, released.Y),
+                            GetAvaloniaInputModifiers(released.Modifiers)));
+                    }, DispatcherPriority.Input);
+                }
+                if(obj is ScrollEventMessage scroll)
+                {
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        Input?.Invoke(new RawMouseWheelEventArgs(
+                            MouseDevice,
+                            0,
+                            InputRoot,
+                            new Point(scroll.X, scroll.Y),
+                            new Vector(scroll.DeltaX, scroll.DeltaY),
+                            GetAvaloniaInputModifiers(scroll.Modifiers)));
+                    }, DispatcherPriority.Input);
+                }
+                if(obj is KeyEventMessage key)
+                {
+                    Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
+
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        Input?.Invoke(new RawKeyEventArgs(
+                            KeyboardDevice,
+                            0,
+                            key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
+                            (Key)key.Key,
+                            GetAvaloniaInputModifiers(key.Modifiers)));
+                    }, DispatcherPriority.Input);
+                }
+                if(obj is TextInputEventMessage text)
+                {
+                    Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
+
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        Input?.Invoke(new RawTextInputEventArgs(
+                            KeyboardDevice,
+                            0,
+                            text.Text));
+                    }, DispatcherPriority.Input);
+                }
             }
         }
 
@@ -102,6 +255,12 @@ namespace Avalonia.Controls.Remote.Server
         
         FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format)
         {
+            var scalingX = _dpi.X / 96.0;
+            var scalingY = _dpi.Y / 96.0;
+
+            width = (int)(width * scalingX);
+            height = (int)(height * scalingY);
+
             var fmt = format ?? ProtocolPixelFormat.Rgba8888;
             var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4;
             var data = new byte[width * height * bpp];
@@ -169,5 +328,7 @@ namespace Avalonia.Controls.Remote.Server
         }
 
         public override IMouseDevice MouseDevice { get; } = new MouseDevice();
+
+        public IKeyboardDevice KeyboardDevice { get; }
     }
 }

+ 3 - 1
src/Avalonia.Controls/TextBlock.cs

@@ -6,6 +6,7 @@ using System.Reactive;
 using System.Reactive.Linq;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
+using Avalonia.Media.Immutable;
 using Avalonia.Metadata;
 
 namespace Avalonia.Controls
@@ -65,7 +66,7 @@ namespace Avalonia.Controls
         public static readonly AttachedProperty<IBrush> ForegroundProperty =
             AvaloniaProperty.RegisterAttached<TextBlock, Control, IBrush>(
                 nameof(Foreground),
-                new SolidColorBrush(0xff000000),
+                Brushes.Black,
                 inherits: true);
 
         /// <summary>
@@ -100,6 +101,7 @@ namespace Avalonia.Controls
         {
             ClipToBoundsProperty.OverrideDefaultValue<TextBlock>(true);
             AffectsRender<TextBlock>(
+                BackgroundProperty,
                 ForegroundProperty,
                 FontWeightProperty,
                 FontSizeProperty,

+ 221 - 162
src/Avalonia.Controls/TextBox.cs

@@ -212,7 +212,10 @@ namespace Avalonia.Controls
             {
                 if (!_ignoreTextChanges)
                 {
-                    CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0);
+                    var caretIndex = CaretIndex;
+                    SelectionStart = CoerceCaretIndex(SelectionStart, value?.Length ?? 0);
+                    SelectionEnd = CoerceCaretIndex(SelectionEnd, value?.Length ?? 0);
+                    CaretIndex = CoerceCaretIndex(caretIndex, value?.Length ?? 0);
 
                     if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing)
                     {
@@ -365,186 +368,242 @@ namespace Avalonia.Controls
             string text = Text ?? string.Empty;
             int caretIndex = CaretIndex;
             bool movement = false;
+            bool selection = false;
             bool handled = false;
             var modifiers = e.Modifiers;
 
-            switch (e.Key)
-            {
-                case Key.A:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        SelectAll();
-                        handled = true;
-                    }
-                    break;
-                case Key.C:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        if (!IsPasswordBox)
-                        {
-                            Copy();
-                        }
-                        handled = true;
-                    }
-                    break;
+            var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
 
-                case Key.X:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        if (!IsPasswordBox)
-                        {
-                            Copy();
-                            DeleteSelection();
-                        }
-                        handled = true;
-                    }
-                    break;
+            bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
+            bool DetectSelection() => e.Modifiers.HasFlag(keymap.SelectionModifiers);
 
-                case Key.V:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        Paste();
-                        handled = true;
-                    }
+            if (Match(keymap.SelectAll))
+            {
+                SelectAll();
+                handled = true;
+            }
+            else if (Match(keymap.Copy))
+            {
+                if (!IsPasswordBox)
+                {
+                    Copy();
+                }
 
-                    break;
+                handled = true;
+            }
+            else if (Match(keymap.Cut))
+            {
+                if (!IsPasswordBox)
+                {
+                    Copy();
+                    DeleteSelection();
+                }
 
-                case Key.Z:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        try
-                        {
-                            _isUndoingRedoing = true;
-                            _undoRedoHelper.Undo();
-                        }
-                        finally
-                        {
-                            _isUndoingRedoing = false;
-                        }
-                        handled = true;
-                    }
-                    break;
-                case Key.Y:
-                    if (modifiers == InputModifiers.Control)
-                    {
-                        try
-                        {
-                            _isUndoingRedoing = true;
-                            _undoRedoHelper.Redo();
-                        }
-                        finally
-                        {
-                            _isUndoingRedoing = false;
-                        }
-                        handled = true;
-                    }
-                    break;
-                case Key.Left:
-                    MoveHorizontal(-1, modifiers);
-                    movement = true;
-                    break;
+                handled = true;
+            }
+            else if (Match(keymap.Paste))
+            {
 
-                case Key.Right:
-                    MoveHorizontal(1, modifiers);
-                    movement = true;
-                    break;
+                Paste();
+                handled = true;
+            }
+            else if (Match(keymap.Undo))
+            {
 
-                case Key.Up:
-                    movement = MoveVertical(-1, modifiers);
-                    break;
+                try
+                {
+                    _isUndoingRedoing = true;
+                    _undoRedoHelper.Undo();
+                }
+                finally
+                {
+                    _isUndoingRedoing = false;
+                }
 
-                case Key.Down:
-                    movement = MoveVertical(1, modifiers);
-                    break;
+                handled = true;
+            }
+            else if (Match(keymap.Redo))
+            {
+                try
+                {
+                    _isUndoingRedoing = true;
+                    _undoRedoHelper.Redo();
+                }
+                finally
+                {
+                    _isUndoingRedoing = false;
+                }
 
-                case Key.Home:
-                    MoveHome(modifiers);
-                    movement = true;
-                    break;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheStartOfDocument))
+            {
+                MoveHome(true);
+                movement = true;
+                selection = false;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheEndOfDocument))
+            {
+                MoveEnd(true);
+                movement = true;
+                selection = false;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheStartOfLine))
+            {
+                MoveHome(false);
+                movement = true;
+                selection = false;
+                handled = true;
+                
+            }
+            else if (Match(keymap.MoveCursorToTheEndOfLine))
+            {
+                MoveEnd(false);
+                movement = true;
+                selection = false;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheStartOfDocumentWithSelection))
+            {
+                MoveHome(true);
+                movement = true;
+                selection = true;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheEndOfDocumentWithSelection))
+            {
+                MoveEnd(true);
+                movement = true;
+                selection = true;
+                handled = true;
+            }
+            else if (Match(keymap.MoveCursorToTheStartOfLineWithSelection))
+            {
+                MoveHome(false);
+                movement = true;
+                selection = true;
+                handled = true;
+                
+            }
+            else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
+            {
+                MoveEnd(false);
+                movement = true;
+                selection = true;
+                handled = true;
+            }
+            else
+            {
+                bool hasWholeWordModifiers = modifiers.HasFlag(keymap.WholeWordTextActionModifiers);
+                switch (e.Key)
+                {
+                    case Key.Left:
+                        MoveHorizontal(-1, hasWholeWordModifiers);
+                        movement = true;
+                        selection = DetectSelection();
+                        break;
 
-                case Key.End:
-                    MoveEnd(modifiers);
-                    movement = true;
-                    break;
+                    case Key.Right:
+                        MoveHorizontal(1, hasWholeWordModifiers);
+                        movement = true;
+                        selection = DetectSelection();
+                        break;
 
-                case Key.Back:
-                    if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
-                    {
-                        SetSelectionForControlBackspace(modifiers);
-                    }
+                    case Key.Up:
+                        movement = MoveVertical(-1);
+                        selection = DetectSelection();
+                        break;
 
-                    if (!DeleteSelection() && CaretIndex > 0)
-                    {
-                        var removedCharacters = 1;
-                        // handle deleting /r/n
-                        // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if 
-                        // a /r should also be deleted.
-                        if (CaretIndex > 1 &&
-                            text[CaretIndex - 1] == '\n' &&
-                            text[CaretIndex - 2] == '\r')
+                    case Key.Down:
+                        movement = MoveVertical(1);
+                        selection = DetectSelection();
+                        break;
+
+                    case Key.Back:
+                        if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
                         {
-                            removedCharacters = 2;
+                            SetSelectionForControlBackspace();
                         }
 
-                        SetTextInternal(text.Substring(0, caretIndex - removedCharacters) + text.Substring(caretIndex));
-                        CaretIndex -= removedCharacters;
-                        SelectionStart = SelectionEnd = CaretIndex;
-                    }
-                    handled = true;
-                    break;
+                        if (!DeleteSelection() && CaretIndex > 0)
+                        {
+                            var removedCharacters = 1;
+                            // handle deleting /r/n
+                            // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if 
+                            // a /r should also be deleted.
+                            if (CaretIndex > 1 &&
+                                text[CaretIndex - 1] == '\n' &&
+                                text[CaretIndex - 2] == '\r')
+                            {
+                                removedCharacters = 2;
+                            }
 
-                case Key.Delete:
-                    if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
-                    {
-                        SetSelectionForControlDelete(modifiers);
-                    }
+                            SetTextInternal(text.Substring(0, caretIndex - removedCharacters) +
+                                            text.Substring(caretIndex));
+                            CaretIndex -= removedCharacters;
+                            SelectionStart = SelectionEnd = CaretIndex;
+                        }
 
-                    if (!DeleteSelection() && caretIndex < text.Length)
-                    {
-                        var removedCharacters = 1;
-                        // handle deleting /r/n
-                        // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if 
-                        // a /r should also be deleted.
-                        if (CaretIndex < text.Length - 1 &&
-                            text[caretIndex + 1] == '\n' &&
-                            text[caretIndex] == '\r')
+                        handled = true;
+                        break;
+
+                    case Key.Delete:
+                        if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
                         {
-                            removedCharacters = 2;
+                            SetSelectionForControlDelete();
                         }
 
-                        SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters));
-                    }
-                    handled = true;
-                    break;
+                        if (!DeleteSelection() && caretIndex < text.Length)
+                        {
+                            var removedCharacters = 1;
+                            // handle deleting /r/n
+                            // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if 
+                            // a /r should also be deleted.
+                            if (CaretIndex < text.Length - 1 &&
+                                text[caretIndex + 1] == '\n' &&
+                                text[caretIndex] == '\r')
+                            {
+                                removedCharacters = 2;
+                            }
+
+                            SetTextInternal(text.Substring(0, caretIndex) +
+                                            text.Substring(caretIndex + removedCharacters));
+                        }
 
-                case Key.Enter:
-                    if (AcceptsReturn)
-                    {
-                        HandleTextInput(NewLine);
                         handled = true;
-                    }
+                        break;
 
-                    break;
+                    case Key.Enter:
+                        if (AcceptsReturn)
+                        {
+                            HandleTextInput(NewLine);
+                            handled = true;
+                        }
 
-                case Key.Tab:
-                    if (AcceptsTab)
-                    {
-                        HandleTextInput("\t");
-                        handled = true;
-                    }
-                    else
-                    {
-                        base.OnKeyDown(e);
-                    }
+                        break;
 
-                    break;
+                    case Key.Tab:
+                        if (AcceptsTab)
+                        {
+                            HandleTextInput("\t");
+                            handled = true;
+                        }
+                        else
+                        {
+                            base.OnKeyDown(e);
+                        }
 
-                default:
-                    handled = false;
-                    break;
+                        break;
+
+                    default:
+                        handled = false;
+                        break;
+                }
             }
 
-            if (movement && ((modifiers & InputModifiers.Shift) != 0))
+            if (movement && selection)
             {
                 SelectionEnd = CaretIndex;
             }
@@ -663,12 +722,12 @@ namespace Avalonia.Controls
             return result;
         }
 
-        private void MoveHorizontal(int direction, InputModifiers modifiers)
+        private void MoveHorizontal(int direction, bool wholeWord)
         {
             var text = Text ?? string.Empty;
             var caretIndex = CaretIndex;
 
-            if ((modifiers & InputModifiers.Control) == 0)
+            if (!wholeWord)
             {
                 var index = caretIndex + direction;
 
@@ -706,7 +765,7 @@ namespace Avalonia.Controls
             }
         }
 
-        private bool MoveVertical(int count, InputModifiers modifiers)
+        private bool MoveVertical(int count)
         {
             var formattedText = _presenter.FormattedText;
             var lines = formattedText.GetLines().ToList();
@@ -729,12 +788,12 @@ namespace Avalonia.Controls
             }
         }
 
-        private void MoveHome(InputModifiers modifiers)
+        private void MoveHome(bool document)
         {
             var text = Text ?? string.Empty;
             var caretIndex = CaretIndex;
 
-            if ((modifiers & InputModifiers.Control) != 0)
+            if (document)
             {
                 caretIndex = 0;
             }
@@ -759,12 +818,12 @@ namespace Avalonia.Controls
             CaretIndex = caretIndex;
         }
 
-        private void MoveEnd(InputModifiers modifiers)
+        private void MoveEnd(bool document)
         {
             var text = Text ?? string.Empty;
             var caretIndex = CaretIndex;
 
-            if ((modifiers & InputModifiers.Control) != 0)
+            if (document)
             {
                 caretIndex = text.Length;
             }
@@ -879,17 +938,17 @@ namespace Avalonia.Controls
             }
         }
 
-        private void SetSelectionForControlBackspace(InputModifiers modifiers)
+        private void SetSelectionForControlBackspace()
         {
             SelectionStart = CaretIndex;
-            MoveHorizontal(-1, modifiers);
+            MoveHorizontal(-1, true);
             SelectionEnd = CaretIndex;
         }
 
-        private void SetSelectionForControlDelete(InputModifiers modifiers)
+        private void SetSelectionForControlDelete()
         {
             SelectionStart = CaretIndex;
-            MoveHorizontal(1, modifiers);
+            MoveHorizontal(1, true);
             SelectionEnd = CaretIndex;
         }
 

+ 0 - 1
src/Avalonia.Controls/TopLevel.cs

@@ -96,7 +96,6 @@ namespace Avalonia.Controls
             _applicationLifecycle = TryGetService<IApplicationLifecycle>(dependencyResolver);
             _renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
 
-            var renderLoop = TryGetService<IRenderLoop>(dependencyResolver);
             Renderer = impl.CreateRenderer(this);
 
             impl.SetInputRoot(this);

+ 84 - 72
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@@ -88,24 +88,82 @@ namespace Avalonia.Controls.Utils
             }
             else
             {
-                var borderThickness = borders.Left;
-                var cornerRadius = (float)radii.TopLeft;
-                var rect = new Rect(size);
+                var borderThickness = borders.Top;
+                var top = borderThickness * 0.5;
+                var cornerRadius = (float)Math.Max(0, radii.TopLeft - borderThickness - top);
 
                 if (background != null)
                 {
-                    context.FillRectangle(background, rect.Deflate(borders), cornerRadius);
+                    var topLeft = new Point(borders.Left, borders.Top);
+                    var bottomRight = new Point(size.Width - borders.Right, size.Height - borders.Bottom);
+                    var innerRect = new Rect(topLeft, bottomRight);
+                    context.FillRectangle(background, innerRect, cornerRadius);
                 }
 
                 if (borderBrush != null && borderThickness > 0)
                 {
-                    context.DrawRectangle(new Pen(borderBrush, borderThickness), rect.Deflate(borderThickness), cornerRadius);
+                    var topLeft = new Point(top, top);
+                    var bottomRight = new Point(size.Width - top, size.Height - top);
+                    var outerRect = new Rect(topLeft, bottomRight);
+                    context.DrawRectangle(new Pen(borderBrush, borderThickness), outerRect, (float)radii.TopLeft);
                 }
             }
+        }    
+
+        private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderGeometryKeypoints keypoints)
+        {
+            context.BeginFigure(keypoints.TopLeft, true);
+
+            // Top
+            context.LineTo(keypoints.TopRight);
+
+            // TopRight corner
+            var radiusX = boundRect.TopRight.X - keypoints.TopRight.X;
+            var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(keypoints.RightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
+            }
+
+            // Right
+            context.LineTo(keypoints.RightBottom);
+
+            // BottomRight corner
+            radiusX = boundRect.BottomRight.X - keypoints.BottomRight.X;
+            radiusY = boundRect.BottomRight.Y - keypoints.RightBottom.Y;
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(keypoints.BottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+            }
+
+            // Bottom
+            context.LineTo(keypoints.BottomLeft);
+
+            // BottomLeft corner
+            radiusX = keypoints.BottomLeft.X - boundRect.BottomLeft.X;
+            radiusY = boundRect.BottomLeft.Y - keypoints.LeftBottom.Y;
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(keypoints.LeftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+            }
+
+            // Left
+            context.LineTo(keypoints.LeftTop);
+
+            // TopLeft corner
+            radiusX = keypoints.TopLeft.X - boundRect.TopLeft.X;
+            radiusY = keypoints.LeftTop.Y - boundRect.TopLeft.Y;
+
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(keypoints.TopLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+            }
+
+            context.EndFigure(true);
         }
 
         private class BorderGeometryKeypoints
-        {           
+        {
             internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius, bool inner)
             {
                 var left = 0.5 * borderThickness.Left;
@@ -135,25 +193,24 @@ namespace Avalonia.Controls.Utils
                 }
                 else
                 {
-                   
                     leftTopY = cornerRadius.TopLeft + top + boundRect.TopLeft.Y;
-                    topLeftX = cornerRadius.TopLeft + left + boundRect.TopLeft.X;                   
+                    topLeftX = cornerRadius.TopLeft + left + boundRect.TopLeft.X;
                     topRightX = boundRect.Width - (cornerRadius.TopRight + right) + boundRect.TopLeft.X;
-                    rightTopY = cornerRadius.TopRight + top + boundRect.TopLeft.Y;                                   
+                    rightTopY = cornerRadius.TopRight + top + boundRect.TopLeft.Y;
                     rightBottomY = boundRect.Height - (cornerRadius.BottomRight + bottom) + boundRect.TopLeft.Y;
-                    bottomRightX = boundRect.Width - (cornerRadius.BottomRight + right) + boundRect.TopLeft.X;                
-                    bottomLeftX = cornerRadius.BottomLeft + left + boundRect.TopLeft.X;               
+                    bottomRightX = boundRect.Width - (cornerRadius.BottomRight + right) + boundRect.TopLeft.X;
+                    bottomLeftX = cornerRadius.BottomLeft + left + boundRect.TopLeft.X;
                     leftBottomY = boundRect.Height - (cornerRadius.BottomLeft + bottom) + boundRect.TopLeft.Y;
-                }              
+                }
 
-                var leftTopX = boundRect.TopLeft.X;               
-                var topLeftY = boundRect.TopLeft.Y;              
+                var leftTopX = boundRect.TopLeft.X;
+                var topLeftY = boundRect.TopLeft.Y;
                 var topRightY = boundRect.TopLeft.Y;
-                var rightTopX = boundRect.Width + boundRect.TopLeft.X;              
-                var rightBottomX = boundRect.Width + boundRect.TopLeft.X;                             
-                var bottomRightY = boundRect.Height + boundRect.TopLeft.Y;            
+                var rightTopX = boundRect.Width + boundRect.TopLeft.X;
+                var rightBottomX = boundRect.Width + boundRect.TopLeft.X;
+                var bottomRightY = boundRect.Height + boundRect.TopLeft.Y;
                 var bottomLeftY = boundRect.Height + boundRect.TopLeft.Y;
-                var leftBottomX = boundRect.TopLeft.X;           
+                var leftBottomX = boundRect.TopLeft.X;
 
                 LeftTop = new Point(leftTopX, leftTopY);
                 TopLeft = new Point(topLeftX, topLeftY);
@@ -164,7 +221,7 @@ namespace Avalonia.Controls.Utils
                 BottomLeft = new Point(bottomLeftX, bottomLeftY);
                 LeftBottom = new Point(leftBottomX, leftBottomY);
 
-                //Fix overlap
+                // Fix overlap
                 if (TopLeft.X > TopRight.X)
                 {
                     var scaledX = topLeftX / (topLeftX + topRightX) * boundRect.Width;
@@ -194,66 +251,21 @@ namespace Avalonia.Controls.Utils
                 }
             }
 
-            internal Point LeftTop { get; private set; }
-            internal Point TopLeft { get; private set; }
-            internal Point TopRight { get; private set; }
-            internal Point RightTop { get; private set; }
-            internal Point RightBottom { get; private set; }
-            internal Point BottomRight { get; private set; }
-            internal Point BottomLeft { get; private set; }
-            internal Point LeftBottom { get; private set; }
-        }
+            internal Point LeftTop { get; }
 
-        private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderGeometryKeypoints keypoints)
-        {
-            context.BeginFigure(keypoints.TopLeft, true);
+            internal Point TopLeft { get; }
 
-            //Top
-            context.LineTo(keypoints.TopRight);
+            internal Point TopRight { get; }
 
-            //TopRight corner
-            var radiusX = boundRect.TopRight.X - keypoints.TopRight.X;
-            var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
-            if (radiusX != 0 || radiusY != 0)
-            {
-                context.ArcTo(keypoints.RightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
-            }
+            internal Point RightTop { get; }
 
-            //Right
-            context.LineTo(keypoints.RightBottom);
+            internal Point RightBottom { get; }
 
-            //BottomRight corner
-            radiusX = boundRect.BottomRight.X - keypoints.BottomRight.X;
-            radiusY = boundRect.BottomRight.Y - keypoints.RightBottom.Y;
-            if (radiusX != 0 || radiusY != 0)
-            {
-                context.ArcTo(keypoints.BottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
-            }
-
-            //Bottom
-            context.LineTo(keypoints.BottomLeft);
-
-            //BottomLeft corner
-            radiusX = keypoints.BottomLeft.X - boundRect.BottomLeft.X;
-            radiusY = boundRect.BottomLeft.Y - keypoints.LeftBottom.Y;
-            if (radiusX != 0 || radiusY != 0)
-            {
-                context.ArcTo(keypoints.LeftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
-            }
+            internal Point BottomRight { get; }
 
-            //Left
-            context.LineTo(keypoints.LeftTop);
-
-            //TopLeft corner
-            radiusX = keypoints.TopLeft.X - boundRect.TopLeft.X;
-            radiusY = keypoints.LeftTop.Y - boundRect.TopLeft.Y;
-
-            if (radiusX != 0 || radiusY != 0)
-            {
-                context.ArcTo(keypoints.TopLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
-            }
+            internal Point BottomLeft { get; }
 
-            context.EndFigure(true);
+            internal Point LeftBottom { get; }
         }
     }
 }

+ 1 - 1
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@@ -18,7 +18,7 @@ namespace Avalonia.DesignerSupport
             Control control;
             using (PlatformManager.DesignerMode())
             {
-                var loader = new AvaloniaXamlLoader();
+                var loader = new AvaloniaXamlLoader() {IsDesignMode = true};
                 var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml));
 
 

+ 4 - 2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@@ -53,10 +53,12 @@ namespace Avalonia.DesignerSupport.Remote
                 .Bind<IKeyboardDevice>().ToConstant(Keyboard)
                 .Bind<IPlatformSettings>().ToConstant(instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(threading)
-                .Bind<IRenderLoop>().ToConstant(threading)
+                .Bind<IRenderLoop>().ToConstant(new RenderLoop())
+                .Bind<IRenderTimer>().ToConstant(threading)
                 .Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsStub>()
                 .Bind<IWindowingPlatform>().ToConstant(instance)
-                .Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>();
+                .Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
 
         }
 

+ 9 - 1
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@@ -15,6 +15,8 @@ namespace Avalonia.DesignerSupport.Remote
     {
         private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats;
         private static ClientViewportAllocatedMessage s_viewportAllocatedMessage;
+        private static ClientRenderInfoMessage s_renderInfoMessage;
+
         private static IAvaloniaRemoteTransportConnection s_transport;
         class CommandLineArgs
         {
@@ -161,7 +163,8 @@ namespace Avalonia.DesignerSupport.Remote
             PreviewerWindowingPlatform.PreFlightMessages = new List<object>
             {
                 s_supportedPixelFormats,
-                s_viewportAllocatedMessage
+                s_viewportAllocatedMessage,
+                s_renderInfoMessage
             };
         }
 
@@ -173,6 +176,11 @@ namespace Avalonia.DesignerSupport.Remote
                 s_supportedPixelFormats = formats;
                 RebuildPreFlight();
             }
+            if (obj is ClientRenderInfoMessage renderInfo)
+            {
+                s_renderInfoMessage = renderInfo;
+                RebuildPreFlight();
+            }
             if (obj is ClientViewportAllocatedMessage viewport)
             {
                 s_viewportAllocatedMessage = viewport;

+ 2 - 2
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Reactive.Disposables;
@@ -20,7 +20,7 @@ namespace Avalonia.DesignerSupport.Remote
         public IPlatformHandle Handle { get; }
         public Size MaxClientSize { get; }
         public Size ClientSize { get; }
-        public double Scaling { get; }
+        public double Scaling { get; } = 1.0;
         public IEnumerable<object> Surfaces { get; }
         public Action<RawInputEventArgs> Input { get; set; }
         public Action<Rect> Paint { get; set; }

+ 8 - 0
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@@ -2,6 +2,9 @@
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
   </PropertyGroup>
+  <ItemGroup>
+    <None Remove="Views\EventsView.xaml" />
+  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
     <ProjectReference Include="..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
@@ -17,4 +20,9 @@
   </ItemGroup>  
   <Import Project="..\..\build\EmbedXaml.props" />
   <Import Project="..\..\build\Rx.props" />
+  <ItemGroup>
+    <EmbeddedResource Update="Views\EventsView.xaml">
+      <Generator>MSBuild:Compile</Generator>
+    </EmbeddedResource>
+  </ItemGroup>
 </Project>

+ 1 - 0
src/Avalonia.Diagnostics/DevTools.xaml

@@ -3,6 +3,7 @@
     <TabStrip SelectedIndex="{Binding SelectedTab, Mode=TwoWay}">
       <TabStripItem Content="Logical Tree"/>
       <TabStripItem Content="Visual Tree"/>
+      <TabStripItem Content="Events"/>
     </TabStrip>
 
     <ContentControl Content="{Binding Content}" Grid.Row="1"/> 

+ 23 - 0
src/Avalonia.Diagnostics/DevTools.xaml.cs

@@ -10,6 +10,7 @@ using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml;
+using Avalonia.Rendering;
 using Avalonia.VisualTree;
 
 namespace Avalonia
@@ -28,6 +29,7 @@ namespace Avalonia.Diagnostics
 	public class DevTools : UserControl
     {
         private static Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>();
+        private static HashSet<IRenderRoot> s_visualTreeRoots = new HashSet<IRenderRoot>();
         private IDisposable _keySubscription;
 
         public DevTools(IControl root)
@@ -79,6 +81,7 @@ namespace Avalonia.Diagnostics
 
                     devToolsWindow.Closed += devTools.DevToolsClosed;
                     s_open.Add(control, devToolsWindow);
+                    MarkAsDevTool(devToolsWindow);
                     devToolsWindow.Show();
                 }
             }
@@ -89,6 +92,7 @@ namespace Avalonia.Diagnostics
             var devToolsWindow = (Window)sender;
             var devTools = (DevTools)devToolsWindow.Content;
             s_open.Remove((TopLevel)devTools.Root);
+            RemoveDevTool(devToolsWindow);
             _keySubscription.Dispose();
             devToolsWindow.Closed -= DevToolsClosed;
         }
@@ -116,5 +120,24 @@ namespace Avalonia.Diagnostics
                 }
             }
         }
+
+        /// <summary>
+        /// Marks a visual as part of the DevTools, so it can be excluded from event tracking.
+        /// </summary>
+        /// <param name="visual">The visual whose root is to be marked.</param>
+        public static void MarkAsDevTool(IVisual visual)
+        {
+            s_visualTreeRoots.Add(visual.GetVisualRoot());
+        }
+
+        public static void RemoveDevTool(IVisual visual)
+        {
+            s_visualTreeRoots.Remove(visual.GetVisualRoot());
+        }
+
+        public static bool BelongsToDevTool(IVisual visual)
+        {
+            return s_visualTreeRoots.Contains(visual.GetVisualRoot());
+        }
     }
 }

+ 38 - 0
src/Avalonia.Diagnostics/Models/EventChainLink.cs

@@ -0,0 +1,38 @@
+// 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;
+using Avalonia.Interactivity;
+
+namespace Avalonia.Diagnostics.Models
+{
+    internal class EventChainLink
+    {
+        public EventChainLink(object handler, bool handled, RoutingStrategies route)
+        {
+            Contract.Requires<ArgumentNullException>(handler != null);
+
+            this.Handler = handler;
+            this.Handled = handled;
+            this.Route = route;
+        }
+
+        public object Handler { get; }
+
+        public string HandlerName
+        {
+            get
+            {
+                if (Handler is INamed named && !string.IsNullOrEmpty(named.Name))
+                {
+                    return named.Name + " (" + Handler.GetType().Name + ")";
+                }
+                return Handler.GetType().Name;
+            }
+        }
+
+        public bool Handled { get; }
+
+        public RoutingStrategies Route { get; }
+    }
+}

+ 5 - 0
src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs

@@ -14,6 +14,7 @@ namespace Avalonia.Diagnostics.ViewModels
         private int _selectedTab;
         private TreePageViewModel _logicalTree;
         private TreePageViewModel _visualTree;
+        private EventsViewModel _eventsView;
         private string _focusedControl;
         private string _pointerOverElement;
 
@@ -21,6 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels
         {
             _logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
             _visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
+            _eventsView = new EventsViewModel(root);
 
             UpdateFocusedControl();
             KeyboardDevice.Instance.PropertyChanged += (s, e) =>
@@ -57,6 +59,9 @@ namespace Avalonia.Diagnostics.ViewModels
                     case 1:
                         Content = _visualTree;
                         break;
+                    case 2:
+                        Content = _eventsView;
+                        break;
                 }
 
                 RaisePropertyChanged();

+ 61 - 0
src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs

@@ -0,0 +1,61 @@
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+    internal class EventOwnerTreeNode : EventTreeNodeBase
+    {
+        private static readonly RoutedEvent[] s_defaultEvents = new RoutedEvent[]
+        {
+           Button.ClickEvent,
+           InputElement.KeyDownEvent,
+           InputElement.KeyUpEvent,
+           InputElement.TextInputEvent,
+           InputElement.PointerReleasedEvent,
+           InputElement.PointerPressedEvent,
+        };
+
+        public EventOwnerTreeNode(Type type, IEnumerable<RoutedEvent> events, EventsViewModel vm)
+            : base(null, type.Name)
+        {
+            this.Children = new AvaloniaList<EventTreeNodeBase>(events.OrderBy(e => e.Name)
+                .Select(e => new EventTreeNode(this, e, vm) { IsEnabled = s_defaultEvents.Contains(e) }));
+            this.IsExpanded = true;
+        }
+
+        public override bool? IsEnabled
+        {
+            get => base.IsEnabled;
+            set
+            {
+                if (base.IsEnabled != value)
+                {
+                    base.IsEnabled = value;
+                    if (_updateChildren && value != null)
+                    {
+                        foreach (var child in Children)
+                        {
+                            try
+                            {
+                                child._updateParent = false;
+                                child.IsEnabled = value;
+                            }
+                            finally
+                            {
+                                child._updateParent = true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 98 - 0
src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs

@@ -0,0 +1,98 @@
+// 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;
+
+using Avalonia.Diagnostics.Models;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+    internal class EventTreeNode : EventTreeNodeBase
+    {
+        private RoutedEvent _event;
+        private EventsViewModel _parentViewModel;
+        private bool _isRegistered;
+        private FiredEvent _currentEvent;
+
+        public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsViewModel vm)
+            : base(parent, @event.Name)
+        {
+            Contract.Requires<ArgumentNullException>(@event != null);
+            Contract.Requires<ArgumentNullException>(vm != null);
+
+            this._event = @event;
+            this._parentViewModel = vm;
+        }
+
+        public override bool? IsEnabled
+        {
+            get => base.IsEnabled;
+            set
+            {
+                if (base.IsEnabled != value)
+                {
+                    base.IsEnabled = value;
+                    UpdateTracker();
+                    if (Parent != null && _updateParent)
+                    {
+                        try
+                        {
+                            Parent._updateChildren = false;
+                            Parent.UpdateChecked();
+                        }
+                        finally
+                        {
+                            Parent._updateChildren = true;
+                        }
+                    }
+                }
+            }
+        }
+
+        private void UpdateTracker()
+        {
+            if (IsEnabled.GetValueOrDefault() && !_isRegistered)
+            {
+                _event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true);
+                _isRegistered = true;
+            }
+        }
+
+        private void HandleEvent(object sender, RoutedEventArgs e)
+        {
+            if (!_isRegistered || IsEnabled == false)
+                return;
+            if (sender is IVisual v && DevTools.BelongsToDevTool(v))
+                return;
+
+            var s = sender;
+            var handled = e.Handled;
+            var route = e.Route;
+
+            Action handler = delegate
+            {
+                if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e))
+                {
+                    _currentEvent = new FiredEvent(e, new EventChainLink(s, handled, route));
+
+                    _parentViewModel.RecordedEvents.Add(_currentEvent);
+
+                    while (_parentViewModel.RecordedEvents.Count > 100)
+                        _parentViewModel.RecordedEvents.RemoveAt(0);
+                }
+                else
+                {
+                    _currentEvent.AddToChain(new EventChainLink(s, handled, route));
+                }
+            };
+
+            if (!Dispatcher.UIThread.CheckAccess())
+                Dispatcher.UIThread.Post(handler);
+            else
+                handler();
+        }
+    }
+}

+ 78 - 0
src/Avalonia.Diagnostics/ViewModels/EventTreeNodeBase.cs

@@ -0,0 +1,78 @@
+// 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 Avalonia.Collections;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+    internal abstract class EventTreeNodeBase : ViewModelBase
+    {
+        internal bool _updateChildren = true;
+        internal bool _updateParent = true;
+        private bool _isExpanded;
+        private bool? _isEnabled = false;
+
+        public EventTreeNodeBase(EventTreeNodeBase parent, string text)
+        {
+            this.Parent = parent;
+            this.Text = text;
+        }
+
+        public IAvaloniaReadOnlyList<EventTreeNodeBase> Children
+        {
+            get;
+            protected set;
+        }
+
+        public bool IsExpanded
+        {
+            get { return _isExpanded; }
+            set { RaiseAndSetIfChanged(ref _isExpanded, value); }
+        }
+
+        public virtual bool? IsEnabled
+        {
+            get { return _isEnabled; }
+            set { RaiseAndSetIfChanged(ref _isEnabled, value); }
+        }
+
+        public EventTreeNodeBase Parent
+        {
+            get;
+        }
+
+        public string Text
+        {
+            get;
+            private set;
+        }
+
+        internal void UpdateChecked()
+        {
+            IsEnabled = GetValue();
+
+            bool? GetValue()
+            {
+                if (Children == null)
+                    return false;
+                bool? value = false;
+                for (int i = 0; i < Children.Count; i++)
+                {
+                    if (i == 0)
+                    {
+                        value = Children[i].IsEnabled;
+                        continue;
+                    }
+
+                    if (value != Children[i].IsEnabled)
+                    {
+                        value = null;
+                        break;
+                    }
+                }
+
+                return value;
+            }
+        }
+    }
+}

+ 60 - 0
src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs

@@ -0,0 +1,60 @@
+// 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;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Linq;
+using System.Windows.Input;
+
+using Avalonia.Controls;
+using Avalonia.Data.Converters;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+    internal class EventsViewModel : ViewModelBase
+    {
+        private readonly IControl _root;
+        private FiredEvent _selectedEvent;
+
+        public EventsViewModel(IControl root)
+        {
+            this._root = root;
+            this.Nodes = RoutedEventRegistry.Instance.GetAllRegistered()
+                .GroupBy(e => e.OwnerType)
+                .OrderBy(e => e.Key.Name)
+                .Select(g => new EventOwnerTreeNode(g.Key, g, this))
+                .ToArray();
+        }
+
+        public EventTreeNodeBase[] Nodes { get; }
+
+        public ObservableCollection<FiredEvent> RecordedEvents { get; } = new ObservableCollection<FiredEvent>();
+
+        public FiredEvent SelectedEvent
+        {
+            get => _selectedEvent;
+            set => RaiseAndSetIfChanged(ref _selectedEvent, value);
+        }
+
+        private void Clear()
+        {
+            RecordedEvents.Clear();
+        }
+    }
+
+    internal class BoolToBrushConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return (bool)value ? Brushes.LightGreen : Brushes.Transparent;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 80 - 0
src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs

@@ -0,0 +1,80 @@
+// 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;
+using System.Collections.ObjectModel;
+
+using Avalonia.Diagnostics.Models;
+using Avalonia.Interactivity;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+    internal class FiredEvent : ViewModelBase
+    {
+        private RoutedEventArgs _eventArgs;
+        private EventChainLink _handledBy;
+
+        public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator)
+        {
+            Contract.Requires<ArgumentNullException>(eventArgs != null);
+            Contract.Requires<ArgumentNullException>(originator != null);
+
+            this._eventArgs = eventArgs;
+            this.Originator = originator;
+            AddToChain(originator);
+        }
+
+        public bool IsPartOfSameEventChain(RoutedEventArgs e)
+        {
+            return e == _eventArgs;
+        }
+
+        public RoutedEvent Event => _eventArgs.RoutedEvent;
+
+        public bool IsHandled => HandledBy?.Handled == true;
+
+        public ObservableCollection<EventChainLink> EventChain { get; } = new ObservableCollection<EventChainLink>();
+
+        public string DisplayText
+        {
+            get
+            {
+                if (IsHandled)
+                {
+                    return $"{Event.Name} on {Originator.HandlerName};" + Environment.NewLine +
+                        $"strategies: {Event.RoutingStrategies}; handled by: {HandledBy.HandlerName}";
+                }
+                return $"{Event.Name} on {Originator.HandlerName}; strategies: {Event.RoutingStrategies}";
+            }
+        }
+
+        public EventChainLink Originator { get; }
+
+        public EventChainLink HandledBy
+        {
+            get { return _handledBy; }
+            set
+            {
+                if (_handledBy != value)
+                {
+                    _handledBy = value;
+                    RaisePropertyChanged();
+                    RaisePropertyChanged(nameof(IsHandled));
+                    RaisePropertyChanged(nameof(DisplayText));
+                }
+            }
+        }
+
+        public void AddToChain(object handler, bool handled, RoutingStrategies route)
+        {
+            AddToChain(new EventChainLink(handler, handled, route));
+        }
+
+        public void AddToChain(EventChainLink link)
+        {
+            EventChain.Add(link);
+            if (HandledBy == null && link.Handled)
+                HandledBy = link;
+        }
+    }
+}

+ 53 - 0
src/Avalonia.Diagnostics/Views/EventsView.xaml

@@ -0,0 +1,53 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels">
+    <UserControl.Resources>
+        <vm:BoolToBrushConverter x:Key="boolToBrush" />
+    </UserControl.Resources>
+    <Grid ColumnDefinitions="*,4,3*">
+        <TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" Grid.RowSpan="2">
+            <TreeView.DataTemplates>
+                <TreeDataTemplate DataType="vm:EventTreeNodeBase"
+                                  ItemsSource="{Binding Children}">
+                    <CheckBox Content="{Binding Text}" IsChecked="{Binding IsEnabled, Mode=TwoWay}" />
+                </TreeDataTemplate>
+            </TreeView.DataTemplates>
+            <TreeView.Styles>
+                <Style Selector="TreeViewItem">
+                    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
+                </Style>
+            </TreeView.Styles>
+        </TreeView>
+                          
+        <GridSplitter Width="4" Grid.Column="1" />
+        <Grid RowDefinitions="*,4,2*,Auto" Grid.Column="2">
+            <ListBox Name="eventsList" Items="{Binding RecordedEvents}" SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
+                <ListBox.ItemTemplate>
+                    <DataTemplate>
+                        <TextBlock Background="{Binding IsHandled, Converter={StaticResource boolToBrush}}" Text="{Binding DisplayText}" />
+                    </DataTemplate>
+                </ListBox.ItemTemplate>
+            </ListBox>
+            <GridSplitter Height="4" Grid.Row="1" />
+            <DockPanel Grid.Row="2" LastChildFill="True">
+                <TextBlock DockPanel.Dock="Top" FontSize="16" Text="Event chain:" />
+                <ListBox Items="{Binding SelectedEvent.EventChain}">
+                    <ListBox.ItemTemplate>
+                        <DataTemplate>
+                            <StackPanel Orientation="Horizontal" Background="{Binding Handled, Converter={StaticResource boolToBrush}}">
+                                <TextBlock Text="{Binding Route}" />
+                                <TextBlock Text=": " />
+                                <TextBlock Text="{Binding HandlerName}" />
+                                <TextBlock Text=" handled: " />
+                                <TextBlock Text="{Binding Handled}" />
+                            </StackPanel>
+                        </DataTemplate>
+                    </ListBox.ItemTemplate>
+                </ListBox>
+            </DockPanel>
+            <StackPanel Orientation="Horizontal" Grid.Row="3">
+                <Button Content="Clear" Margin="3" Command="{Binding Clear}"  />
+            </StackPanel>
+        </Grid>
+    </Grid>
+</UserControl>

+ 32 - 0
src/Avalonia.Diagnostics/Views/EventsView.xaml.cs

@@ -0,0 +1,32 @@
+// 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.Linq;
+
+using Avalonia.Controls;
+using Avalonia.Diagnostics.ViewModels;
+using Avalonia.Markup.Xaml;
+
+namespace Avalonia.Diagnostics.Views
+{
+    public class EventsView : UserControl
+    {
+        private ListBox _events;
+
+        public EventsView()
+        {
+            this.InitializeComponent();
+            _events = this.FindControl<ListBox>("events");
+        }
+
+        private void RecordedEvents_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+        {
+            _events.ScrollIntoView(_events.Items.OfType<FiredEvent>().LastOrDefault());
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 2 - 2
src/Avalonia.DotNetCoreRuntime/AppBuilder.cs

@@ -43,7 +43,7 @@ namespace Avalonia
             if (os == OperatingSystemType.WinNT)
                 LoadWin32();
             else if(os==OperatingSystemType.OSX)
-                LoadMonoMac();
+                LoadAvaloniaNative();
             else
                 LoadGtk3();
             this.UseSkia();
@@ -51,7 +51,7 @@ namespace Avalonia
             return this;
         }
 
-        void LoadMonoMac() => this.UseMonoMac();
+        void LoadAvaloniaNative() => this.UseAvaloniaNative();
         void LoadWin32() => this.UseWin32();
         void LoadGtk3() => this.UseGtk3();
     }

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

@@ -8,8 +8,8 @@
     <ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
-    <ProjectReference Include="..\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj" />
     <ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
+    <ProjectReference Include="..\Avalonia.Native\Avalonia.Native.csproj" />
     <ProjectReference Include="..\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
     <ProjectReference Include="..\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
   </ItemGroup>  

+ 18 - 0
src/Avalonia.Input/Key.cs

@@ -1020,5 +1020,23 @@ namespace Avalonia.Input
         /// The key is used with another key to create a single combined character.
         /// </summary>
         DeadCharProcessed = 172,
+        
+        
+        /// <summary>
+        /// OSX Platform-specific Fn+Left key
+        /// </summary>
+        FnLeftArrow = 10001,
+        /// <summary>
+        /// OSX Platform-specific Fn+Right key
+        /// </summary>
+        FnRightArrow = 10002,
+        /// <summary>
+        /// OSX Platform-specific Fn+Up key
+        /// </summary>
+        FnUpArrow = 10003,
+        /// <summary>
+        /// OSX Platform-specific Fn+Down key
+        /// </summary>
+        FnDownArrow = 10004,
     }
 }

+ 11 - 0
src/Avalonia.Input/KeyGesture.cs

@@ -6,6 +6,17 @@ namespace Avalonia.Input
 {
     public sealed class KeyGesture : IEquatable<KeyGesture>
     {
+        public KeyGesture()
+        {
+            
+        }
+
+        public KeyGesture(Key key, InputModifiers modifiers = InputModifiers.None)
+        {
+            Key = key;
+            Modifiers = modifiers;
+        }
+        
         public bool Equals(KeyGesture other)
         {
             if (ReferenceEquals(null, other)) return false;

+ 98 - 0
src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs

@@ -0,0 +1,98 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Input.Platform
+{
+    public class PlatformHotkeyConfiguration
+    {
+        public PlatformHotkeyConfiguration() : this(InputModifiers.Control)
+        {
+            
+        }
+        
+        public PlatformHotkeyConfiguration(InputModifiers commandModifiers,
+            InputModifiers selectionModifiers = InputModifiers.Shift,
+            InputModifiers wholeWordTextActionModifiers = InputModifiers.Control)
+        {
+            CommandModifiers = commandModifiers;
+            SelectionModifiers = selectionModifiers;
+            WholeWordTextActionModifiers = wholeWordTextActionModifiers;
+            Copy = new List<KeyGesture>
+            {
+                new KeyGesture(Key.C, commandModifiers)
+            };
+            Cut = new List<KeyGesture>
+            {
+                new KeyGesture(Key.X, commandModifiers)
+            };
+            Paste = new List<KeyGesture>
+            {
+                new KeyGesture(Key.V, commandModifiers)
+            };
+            Undo = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Z, commandModifiers)
+            };
+            Redo = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Y, commandModifiers),
+                new KeyGesture(Key.Z, commandModifiers | selectionModifiers)
+            };
+            SelectAll = new List<KeyGesture>
+            {
+                new KeyGesture(Key.A, commandModifiers)
+            };
+            MoveCursorToTheStartOfLine = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Home)
+            };
+            MoveCursorToTheEndOfLine = new List<KeyGesture>
+            {
+                new KeyGesture(Key.End)
+            };
+            MoveCursorToTheStartOfDocument = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Home, commandModifiers)
+            };
+            MoveCursorToTheEndOfDocument = new List<KeyGesture>
+            {
+                new KeyGesture(Key.End, commandModifiers)
+            };
+            MoveCursorToTheStartOfLineWithSelection = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Home, selectionModifiers)
+            };
+            MoveCursorToTheEndOfLineWithSelection = new List<KeyGesture>
+            {
+                new KeyGesture(Key.End, selectionModifiers)
+            };
+            MoveCursorToTheStartOfDocumentWithSelection = new List<KeyGesture>
+            {
+                new KeyGesture(Key.Home, commandModifiers | selectionModifiers)
+            };
+            MoveCursorToTheEndOfDocumentWithSelection = new List<KeyGesture>
+            {
+                new KeyGesture(Key.End, commandModifiers | selectionModifiers)
+            };
+        }
+        
+        public InputModifiers CommandModifiers { get; set; }
+        public InputModifiers WholeWordTextActionModifiers { get; set; }
+        public InputModifiers SelectionModifiers { get; set; }
+        public List<KeyGesture> Copy { get; set; }
+        public List<KeyGesture> Cut { get; set; }
+        public List<KeyGesture> Paste { get; set; }
+        public List<KeyGesture> Undo { get; set; }
+        public List<KeyGesture> Redo { get; set; }
+        public List<KeyGesture> SelectAll { get; set; }
+        public List<KeyGesture> MoveCursorToTheStartOfLine { get; set; }
+        public List<KeyGesture> MoveCursorToTheEndOfLine { get; set; }
+        public List<KeyGesture> MoveCursorToTheStartOfDocument { get; set; }
+        public List<KeyGesture> MoveCursorToTheEndOfDocument { get; set; }
+        public List<KeyGesture> MoveCursorToTheStartOfLineWithSelection { get; set; }
+        public List<KeyGesture> MoveCursorToTheEndOfLineWithSelection { get; set; }
+        public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
+        public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
+        
+        
+    }
+}

+ 4 - 0
src/Avalonia.Native.OSX/.gitignore

@@ -0,0 +1,4 @@
+build
+
+Avalonia.Native.OSX.xcodeproj/xcuserdata
+Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcuserdata

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

@@ -0,0 +1,328 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
+		37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
+		37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.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 */; };
+		AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
+		AB573DC4217605E400D389A2 /* gl.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB573DC3217605E400D389A2 /* gl.mm */; };
+		AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
+		AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; };
+		AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
+		37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../Avalonia.Native/headers; sourceTree = "<group>"; };
+		37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
+		37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; };
+		37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = "<group>"; };
+		37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.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>"; };
+		AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
+		AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
+		AB573DC3217605E400D389A2 /* gl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = gl.mm; sourceTree = "<group>"; };
+		AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
+		AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = "<group>"; };
+		AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
+		AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libAvalonia.Native.OSX.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
+		AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		AB7A61EC2147C814003C5833 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */,
+				AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		AB661C1C2148230E00291242 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				AB1E522B217613570091CD71 /* OpenGL.framework */,
+				AB661C1D2148230F00291242 /* AppKit.framework */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		AB7A61E62147C814003C5833 = {
+			isa = PBXGroup;
+			children = (
+				37A4E71A2178846A00EACBCD /* headers */,
+				AB573DC3217605E400D389A2 /* gl.mm */,
+				5BF943652167AD1D009CAE35 /* cursor.h */,
+				5B21A981216530F500CEE36E /* cursor.mm */,
+				5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
+				AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */,
+				AB661C212148288600291242 /* common.h */,
+				379860FE214DA0C000CD0246 /* KeyTransform.h */,
+				37E2330E21583241000CB7E2 /* KeyTransform.mm */,
+				AB661C1F2148286E00291242 /* window.mm */,
+				37C09D8A21581EF2006A6758 /* window.h */,
+				AB00E4F62147CA920032A60A /* main.mm */,
+				37A517B22159597E00FBA241 /* Screens.mm */,
+				37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
+				AB7A61F02147C815003C5833 /* Products */,
+				AB661C1C2148230E00291242 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		AB7A61F02147C815003C5833 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		AB7A61ED2147C814003C5833 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		AB7A61EE2147C814003C5833 /* Avalonia.Native.OSX */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = AB7A61F82147C815003C5833 /* Build configuration list for PBXNativeTarget "Avalonia.Native.OSX" */;
+			buildPhases = (
+				AB7A61EB2147C814003C5833 /* Sources */,
+				AB7A61EC2147C814003C5833 /* Frameworks */,
+				AB7A61ED2147C814003C5833 /* Headers */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = Avalonia.Native.OSX;
+			productName = Avalonia.Native.OSX;
+			productReference = AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */;
+			productType = "com.apple.product-type.library.dynamic";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		AB7A61E72147C814003C5833 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 1000;
+				ORGANIZATIONNAME = Avalonia;
+				TargetAttributes = {
+					AB7A61EE2147C814003C5833 = {
+						CreatedOnToolsVersion = 8.3.2;
+						ProvisioningStyle = Automatic;
+					};
+				};
+			};
+			buildConfigurationList = AB7A61EA2147C814003C5833 /* Build configuration list for PBXProject "Avalonia.Native.OSX" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = AB7A61E62147C814003C5833;
+			productRefGroup = AB7A61F02147C815003C5833 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				AB7A61EE2147C814003C5833 /* Avalonia.Native.OSX */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+		AB7A61EB2147C814003C5833 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
+				5B21A982216530F500CEE36E /* cursor.mm in Sources */,
+				AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
+				37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
+				37A517B32159597E00FBA241 /* Screens.mm in Sources */,
+				AB00E4F72147CA920032A60A /* main.mm in Sources */,
+				37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
+				AB573DC4217605E400D389A2 /* gl.mm in Sources */,
+				AB661C202148286E00291242 /* window.mm in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		AB7A61F62147C815003C5833 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		AB7A61F72147C815003C5833 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+		AB7A61F92147C815003C5833 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				EXECUTABLE_PREFIX = lib;
+				HEADER_SEARCH_PATHS = ../Avalonia.Native/headers;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		AB7A61FA2147C815003C5833 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				EXECUTABLE_PREFIX = lib;
+				HEADER_SEARCH_PATHS = ../Avalonia.Native/headers;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		AB7A61EA2147C814003C5833 /* Build configuration list for PBXProject "Avalonia.Native.OSX" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				AB7A61F62147C815003C5833 /* Debug */,
+				AB7A61F72147C815003C5833 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		AB7A61F82147C815003C5833 /* Build configuration list for PBXNativeTarget "Avalonia.Native.OSX" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				AB7A61F92147C815003C5833 /* Debug */,
+				AB7A61FA2147C815003C5833 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = AB7A61E72147C814003C5833 /* Project object */;
+}

+ 7 - 0
src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:Avalonia.Native.OSX.xcodeproj">
+   </FileRef>
+</Workspace>

+ 91 - 0
src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1000"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AB7A61EE2147C814003C5833"
+               BuildableName = "libAvalonia.Native.OSX.dylib"
+               BlueprintName = "Avalonia.Native.OSX"
+               ReferencedContainer = "container:Avalonia.Native.OSX.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "YES"
+      customWorkingDirectory = "$PROJECT_DIR/../../samples/ControlCatalog"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <PathRunnable
+         runnableDebuggingMode = "0"
+         FilePath = "/usr/local/share/dotnet/dotnet">
+      </PathRunnable>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AB7A61EE2147C814003C5833"
+            BuildableName = "libAvalonia.Native.OSX.dylib"
+            BlueprintName = "Avalonia.Native.OSX"
+            ReferencedContainer = "container:Avalonia.Native.OSX.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <CommandLineArguments>
+         <CommandLineArgument
+            argument = "bin/Debug/netcoreapp2.0/ControlCatalog.dll"
+            isEnabled = "YES">
+         </CommandLineArgument>
+      </CommandLineArguments>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AB7A61EE2147C814003C5833"
+            BuildableName = "libAvalonia.Native.OSX.dylib"
+            BlueprintName = "Avalonia.Native.OSX"
+            ReferencedContainer = "container:Avalonia.Native.OSX.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 12 - 0
src/Avalonia.Native.OSX/KeyTransform.h

@@ -0,0 +1,12 @@
+// 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.
+
+#ifndef keytransform_h
+#define keytransform_h
+#include "common.h"
+#include "key.h"
+#include <map>
+
+extern std::map<int, AvnKey> s_KeyMap;
+
+#endif

+ 241 - 0
src/Avalonia.Native.OSX/KeyTransform.mm

@@ -0,0 +1,241 @@
+// 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.
+
+#include "KeyTransform.h"
+
+const int kVK_ANSI_A = 0x00;
+const int kVK_ANSI_S = 0x01;
+const int kVK_ANSI_D = 0x02;
+const int kVK_ANSI_F = 0x03;
+const int kVK_ANSI_H = 0x04;
+const int kVK_ANSI_G = 0x05;
+const int kVK_ANSI_Z = 0x06;
+const int kVK_ANSI_X = 0x07;
+const int kVK_ANSI_C = 0x08;
+const int kVK_ANSI_V = 0x09;
+const int kVK_ANSI_B = 0x0B;
+const int kVK_ANSI_Q = 0x0C;
+const int kVK_ANSI_W = 0x0D;
+const int kVK_ANSI_E = 0x0E;
+const int kVK_ANSI_R = 0x0F;
+const int kVK_ANSI_Y = 0x10;
+const int kVK_ANSI_T = 0x11;
+const int kVK_ANSI_1 = 0x12;
+const int kVK_ANSI_2 = 0x13;
+const int kVK_ANSI_3 = 0x14;
+const int kVK_ANSI_4 = 0x15;
+const int kVK_ANSI_6 = 0x16;
+const int kVK_ANSI_5 = 0x17;
+//const int kVK_ANSI_Equal = 0x18;
+const int kVK_ANSI_9 = 0x19;
+const int kVK_ANSI_7 = 0x1A;
+const int kVK_ANSI_Minus = 0x1B;
+const int kVK_ANSI_8 = 0x1C;
+const int kVK_ANSI_0 = 0x1D;
+const int kVK_ANSI_RightBracket = 0x1E;
+const int kVK_ANSI_O = 0x1F;
+const int kVK_ANSI_U = 0x20;
+const int kVK_ANSI_LeftBracket = 0x21;
+const int kVK_ANSI_I = 0x22;
+const int kVK_ANSI_P = 0x23;
+const int kVK_ANSI_L = 0x25;
+const int kVK_ANSI_J = 0x26;
+const int kVK_ANSI_Quote = 0x27;
+const int kVK_ANSI_K = 0x28;
+const int kVK_ANSI_Semicolon = 0x29;
+const int kVK_ANSI_Backslash = 0x2A;
+const int kVK_ANSI_Comma = 0x2B;
+//const int kVK_ANSI_Slash = 0x2C;
+const int kVK_ANSI_N = 0x2D;
+const int kVK_ANSI_M = 0x2E;
+const int kVK_ANSI_Period = 0x2F;
+//const int kVK_ANSI_Grave = 0x32;
+const int kVK_ANSI_KeypadDecimal = 0x41;
+const int kVK_ANSI_KeypadMultiply = 0x43;
+const int kVK_ANSI_KeypadPlus = 0x45;
+const int kVK_ANSI_KeypadClear = 0x47;
+const int kVK_ANSI_KeypadDivide = 0x4B;
+const int kVK_ANSI_KeypadEnter = 0x4C;
+const int kVK_ANSI_KeypadMinus = 0x4E;
+//const int kVK_ANSI_KeypadEquals = 0x51;
+const int kVK_ANSI_Keypad0 = 0x52;
+const int kVK_ANSI_Keypad1 = 0x53;
+const int kVK_ANSI_Keypad2 = 0x54;
+const int kVK_ANSI_Keypad3 = 0x55;
+const int kVK_ANSI_Keypad4 = 0x56;
+const int kVK_ANSI_Keypad5 = 0x57;
+const int kVK_ANSI_Keypad6 = 0x58;
+const int kVK_ANSI_Keypad7 = 0x59;
+const int kVK_ANSI_Keypad8 = 0x5B;
+const int kVK_ANSI_Keypad9 = 0x5C;
+const int kVK_Return = 0x24;
+const int kVK_Tab = 0x30;
+const int kVK_Space = 0x31;
+const int kVK_Delete = 0x33;
+const int kVK_Escape = 0x35;
+const int kVK_Command = 0x37;
+const int kVK_Shift = 0x38;
+const int kVK_CapsLock = 0x39;
+const int kVK_Option = 0x3A;
+const int kVK_Control = 0x3B;
+const int kVK_RightCommand = 0x36;
+const int kVK_RightShift = 0x3C;
+const int kVK_RightOption = 0x3D;
+const int kVK_RightControl = 0x3E;
+//const int kVK_Function = 0x3F;
+const int kVK_F17 = 0x40;
+const int kVK_VolumeUp = 0x48;
+const int kVK_VolumeDown = 0x49;
+const int kVK_Mute = 0x4A;
+const int kVK_F18 = 0x4F;
+const int kVK_F19 = 0x50;
+const int kVK_F20 = 0x5A;
+const int kVK_F5 = 0x60;
+const int kVK_F6 = 0x61;
+const int kVK_F7 = 0x62;
+const int kVK_F3 = 0x63;
+const int kVK_F8 = 0x64;
+const int kVK_F9 = 0x65;
+const int kVK_F11 = 0x67;
+const int kVK_F13 = 0x69;
+const int kVK_F16 = 0x6A;
+const int kVK_F14 = 0x6B;
+const int kVK_F10 = 0x6D;
+const int kVK_F12 = 0x6F;
+const int kVK_F15 = 0x71;
+const int kVK_Help = 0x72;
+const int kVK_Home = 0x73;
+const int kVK_PageUp = 0x74;
+const int kVK_ForwardDelete = 0x75;
+const int kVK_F4 = 0x76;
+const int kVK_End = 0x77;
+const int kVK_F2 = 0x78;
+const int kVK_PageDown = 0x79;
+const int kVK_F1 = 0x7A;
+const int kVK_LeftArrow = 0x7B;
+const int kVK_RightArrow = 0x7C;
+const int kVK_DownArrow = 0x7D;
+const int kVK_UpArrow = 0x7E;
+//const int kVK_ISO_Section = 0x0A;
+//const int kVK_JIS_Yen = 0x5D;
+//const int kVK_JIS_Underscore = 0x5E;
+//const int kVK_JIS_KeypadComma = 0x5F;
+//const int kVK_JIS_Eisu = 0x66;
+//const int kVK_JIS_Kana = 0x68;
+
+ std::map<int, AvnKey> s_KeyMap =
+ {
+    {kVK_ANSI_A, A},
+    {kVK_ANSI_S, S},
+    {kVK_ANSI_D, D},
+    {kVK_ANSI_F, F},
+    {kVK_ANSI_H, H},
+    {kVK_ANSI_G, G},
+    {kVK_ANSI_Z, Z},
+    {kVK_ANSI_X, X},
+    {kVK_ANSI_C, C},
+    {kVK_ANSI_V, V},
+    {kVK_ANSI_B, B},
+    {kVK_ANSI_Q, Q},
+    {kVK_ANSI_W, W},
+    {kVK_ANSI_E, E},
+    {kVK_ANSI_R, R},
+    {kVK_ANSI_Y, Y},
+    {kVK_ANSI_T, T},
+    {kVK_ANSI_1, D1},
+    {kVK_ANSI_2, D2},
+    {kVK_ANSI_3, D3},
+    {kVK_ANSI_4, D4},
+    {kVK_ANSI_6, D6},
+    {kVK_ANSI_5, D5},
+    //{kVK_ANSI_Equal, ?},
+    {kVK_ANSI_9, D9},
+    {kVK_ANSI_7, D7},
+    {kVK_ANSI_Minus, OemMinus},
+    {kVK_ANSI_8, D8},
+    {kVK_ANSI_0, D0},
+    {kVK_ANSI_RightBracket, OemCloseBrackets},
+    {kVK_ANSI_O, O},
+    {kVK_ANSI_U, U},
+    {kVK_ANSI_LeftBracket, OemOpenBrackets},
+    {kVK_ANSI_I, I},
+    {kVK_ANSI_P, P},
+    {kVK_ANSI_L, L},
+    {kVK_ANSI_J, J},
+    {kVK_ANSI_Quote, OemQuotes},
+    {kVK_ANSI_K, AvnKeyK},
+    {kVK_ANSI_Semicolon, OemSemicolon},
+    {kVK_ANSI_Backslash, OemBackslash},
+    {kVK_ANSI_Comma, OemComma},
+    //{kVK_ANSI_Slash, ?},
+    {kVK_ANSI_N, N},
+    {kVK_ANSI_M, M},
+    {kVK_ANSI_Period, OemPeriod},
+    //{kVK_ANSI_Grave, ?},
+    {kVK_ANSI_KeypadDecimal, Decimal},
+    {kVK_ANSI_KeypadMultiply, Multiply},
+    {kVK_ANSI_KeypadPlus, OemPlus},
+    {kVK_ANSI_KeypadClear, AvnKeyClear},
+    {kVK_ANSI_KeypadDivide, Divide},
+    {kVK_ANSI_KeypadEnter, AvnKeyEnter},
+    {kVK_ANSI_KeypadMinus, OemMinus},
+    //{kVK_ANSI_KeypadEquals, ?},
+    {kVK_ANSI_Keypad0, NumPad0},
+    {kVK_ANSI_Keypad1, NumPad1},
+    {kVK_ANSI_Keypad2, NumPad2},
+    {kVK_ANSI_Keypad3, NumPad3},
+    {kVK_ANSI_Keypad4, NumPad4},
+    {kVK_ANSI_Keypad5, NumPad5},
+    {kVK_ANSI_Keypad6, NumPad6},
+    {kVK_ANSI_Keypad7, NumPad7},
+    {kVK_ANSI_Keypad8, NumPad8},
+    {kVK_ANSI_Keypad9, NumPad9},
+    {kVK_Return, AvnKeyReturn},
+    {kVK_Tab, AvnKeyTab},
+    {kVK_Space, Space},
+    {kVK_Delete, AvnKeyBack},
+    {kVK_Escape, Escape},
+    {kVK_Command, LWin},
+    {kVK_Shift, LeftShift},
+    {kVK_CapsLock, AvnKeyCapsLock},
+    {kVK_Option, LeftAlt},
+    {kVK_Control, LeftCtrl},
+    {kVK_RightCommand, RWin},
+    {kVK_RightShift, RightShift},
+    {kVK_RightOption, RightAlt},
+    {kVK_RightControl, RightCtrl},
+    //{kVK_Function, ?},
+    {kVK_F17, F17},
+    {kVK_VolumeUp, VolumeUp},
+    {kVK_VolumeDown, VolumeDown},
+    {kVK_Mute, VolumeMute},
+    {kVK_F18, F18},
+    {kVK_F19, F19},
+    {kVK_F20, F20},
+    {kVK_F5, F5},
+    {kVK_F6, F6},
+    {kVK_F7, F7},
+    {kVK_F3, F3},
+    {kVK_F8, F8},
+    {kVK_F9, F9},
+    {kVK_F11, F11},
+    {kVK_F13, F13},
+    {kVK_F16, F16},
+    {kVK_F14, F14},
+    {kVK_F10, F10},
+    {kVK_F12, F12},
+    {kVK_F15, F15},
+    {kVK_Help, Help},
+    {kVK_Home, Home},
+    {kVK_PageUp, PageUp},
+    {kVK_ForwardDelete, Delete},
+    {kVK_F4, F4},
+    {kVK_End, End},
+    {kVK_F2, F2},
+    {kVK_PageDown, PageDown},
+    {kVK_F1, F1},
+    {kVK_LeftArrow, Left},
+    {kVK_RightArrow, Right},
+    {kVK_DownArrow, Down},
+    {kVK_UpArrow, Up}
+};

+ 51 - 0
src/Avalonia.Native.OSX/Screens.mm

@@ -0,0 +1,51 @@
+// 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.
+
+#include "common.h"
+
+class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
+{
+    public:
+    FORWARD_IUNKNOWN()
+    virtual HRESULT GetScreenCount (int* ret)
+    {
+        @autoreleasepool
+        {
+            *ret = (int)[NSScreen screens].count;
+            
+            return S_OK;
+        }
+    }
+    
+    virtual HRESULT GetScreen (int index, AvnScreen* ret)
+    {
+        @autoreleasepool
+        {
+            if(index < 0 || index >= [NSScreen screens].count)
+            {
+                return E_INVALIDARG;
+            }
+            
+            auto screen = [[NSScreen screens] objectAtIndex:index];
+            
+            ret->Bounds.X = [screen frame].origin.x;
+            ret->Bounds.Y = [screen frame].origin.y;
+            ret->Bounds.Height = [screen frame].size.height;
+            ret->Bounds.Width = [screen frame].size.width;
+            
+            ret->WorkingArea.X = [screen visibleFrame].origin.x;
+            ret->WorkingArea.Y = [screen visibleFrame].origin.y;
+            ret->WorkingArea.Height = [screen visibleFrame].size.height;
+            ret->WorkingArea.Width = [screen visibleFrame].size.width;
+            
+            ret->Primary = index == 0;
+            
+            return S_OK;
+        }
+    }
+};
+
+extern IAvnScreens* CreateScreens()
+{
+    return new Screens();
+}

+ 262 - 0
src/Avalonia.Native.OSX/SystemDialogs.mm

@@ -0,0 +1,262 @@
+// 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.
+
+#include "common.h"
+#include "window.h"
+
+class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs>
+{
+public:
+    FORWARD_IUNKNOWN()
+    virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
+                                     IAvnSystemDialogEvents* events,
+                                     const char* title,
+                                     const char* initialDirectory)
+    {
+        @autoreleasepool
+        {
+            auto panel = [NSOpenPanel openPanel];
+            
+            panel.canChooseDirectories = true;
+            panel.canCreateDirectories = true;
+            panel.canChooseFiles = false;
+            
+            if(title != nullptr)
+            {
+                panel.title = [NSString stringWithUTF8String:title];
+            }
+            
+            if(initialDirectory != nullptr)
+            {
+                auto directoryString = [NSString stringWithUTF8String:initialDirectory];
+                panel.directoryURL = [NSURL fileURLWithPath:directoryString];
+            }
+            
+            auto handler = ^(NSModalResponse result) {
+                if(result == NSFileHandlingPanelOKButton)
+                {
+                    auto urls = [panel URLs];
+                    
+                    if(urls.count > 0)
+                    {
+                        void* strings[urls.count];
+                        
+                        for(int i = 0; i < urls.count; i++)
+                        {
+                            auto url = [urls objectAtIndex:i];
+                            
+                            auto string = [url absoluteString];
+                            string = [string substringFromIndex:7];
+                            
+                            strings[i] = (void*)[string UTF8String];
+                        }
+                        
+                        events->OnCompleted((int)urls.count, &strings[0]);
+                        
+                        [panel orderOut:panel];
+                        
+                        if(parentWindowHandle != nullptr)
+                        {
+                            auto windowHolder = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
+                            [windowHolder->GetNSWindow() makeKeyAndOrderFront:windowHolder->GetNSWindow()];
+                        }
+                        
+                        return;
+                    }
+                }
+                
+                events->OnCompleted(0, nullptr);
+                
+            };
+            
+            if(parentWindowHandle != nullptr)
+            {
+                auto windowBase = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
+                
+                [panel beginSheetModalForWindow:windowBase->GetNSWindow() completionHandler:handler];
+            }
+            else
+            {
+                [panel beginWithCompletionHandler: handler];
+            }
+        }
+    }
+    
+    virtual void OpenFileDialog (IAvnWindow* parentWindowHandle,
+                                 IAvnSystemDialogEvents* events,
+                                 bool allowMultiple,
+                                 const char* title,
+                                 const char* initialDirectory,
+                                 const char* initialFile,
+                                 const char* filters)
+    {
+        @autoreleasepool
+        {
+            auto panel = [NSOpenPanel openPanel];
+            
+            panel.allowsMultipleSelection = allowMultiple;
+            
+            if(title != nullptr)
+            {
+                panel.title = [NSString stringWithUTF8String:title];
+            }
+            
+            if(initialDirectory != nullptr)
+            {
+                auto directoryString = [NSString stringWithUTF8String:initialDirectory];
+                panel.directoryURL = [NSURL fileURLWithPath:directoryString];
+            }
+            
+            if(initialFile != nullptr)
+            {
+                panel.nameFieldStringValue = [NSString stringWithUTF8String:initialFile];
+            }
+            
+            if(filters != nullptr)
+            {
+                auto filtersString = [NSString stringWithUTF8String:filters];
+                
+                if(filtersString.length > 0)
+                {
+                    auto allowedTypes = [filtersString componentsSeparatedByString:@";"];
+                    
+                    panel.allowedFileTypes = allowedTypes;
+                }
+            }
+            
+            auto handler = ^(NSModalResponse result) {
+                if(result == NSFileHandlingPanelOKButton)
+                {
+                    auto urls = [panel URLs];
+                    
+                    if(urls.count > 0)
+                    {
+                        void* strings[urls.count];
+                        
+                        for(int i = 0; i < urls.count; i++)
+                        {
+                            auto url = [urls objectAtIndex:i];
+                            
+                            auto string = [url absoluteString];
+                            string = [string substringFromIndex:7];
+                            
+                            strings[i] = (void*)[string UTF8String];
+                        }
+                        
+                        events->OnCompleted((int)urls.count, &strings[0]);
+                        
+                        [panel orderOut:panel];
+                        
+                        if(parentWindowHandle != nullptr)
+                        {
+                            auto windowHolder = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
+                            [windowHolder->GetNSWindow() makeKeyAndOrderFront:windowHolder->GetNSWindow()];
+                        }
+                        
+                        return;
+                    }
+                }
+                
+                events->OnCompleted(0, nullptr);
+                
+            };
+            
+            if(parentWindowHandle != nullptr)
+            {
+                auto windowHolder = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
+                
+                [panel beginSheetModalForWindow:windowHolder->GetNSWindow() completionHandler:handler];
+            }
+            else
+            {
+                [panel beginWithCompletionHandler: handler];
+            }
+        }
+    }
+    
+    virtual void SaveFileDialog (IAvnWindow* parentWindowHandle,
+                                 IAvnSystemDialogEvents* events,
+                                 const char* title,
+                                 const char* initialDirectory,
+                                 const char* initialFile,
+                                 const char* filters)
+    {
+        @autoreleasepool
+        {
+            auto panel = [NSSavePanel savePanel];
+            
+            if(title != nullptr)
+            {
+                panel.title = [NSString stringWithUTF8String:title];
+            }
+            
+            if(initialDirectory != nullptr)
+            {
+                auto directoryString = [NSString stringWithUTF8String:initialDirectory];
+                panel.directoryURL = [NSURL fileURLWithPath:directoryString];
+            }
+            
+            if(initialFile != nullptr)
+            {
+                panel.nameFieldStringValue = [NSString stringWithUTF8String:initialFile];
+            }
+            
+            if(filters != nullptr)
+            {
+                auto filtersString = [NSString stringWithUTF8String:filters];
+                
+                if(filtersString.length > 0)
+                {
+                    auto allowedTypes = [filtersString componentsSeparatedByString:@";"];
+                    
+                    panel.allowedFileTypes = allowedTypes;
+                }
+            }
+            
+            auto handler = ^(NSModalResponse result) {
+                if(result == NSFileHandlingPanelOKButton)
+                {
+                    void* strings[1];
+                    
+                    auto url = [panel URL];
+                    
+                    auto string = [url absoluteString];
+                    string = [string substringFromIndex:7];     
+                    strings[0] = (void*)[string UTF8String];
+               
+                    events->OnCompleted(1, &strings[0]);
+                    
+                    [panel orderOut:panel];
+                    
+                    if(parentWindowHandle != nullptr)
+                    {
+                        auto windowHolder = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
+                        [windowHolder->GetNSWindow() makeKeyAndOrderFront:windowHolder->GetNSWindow()];
+                    }
+                    
+                    return;
+                }
+                
+                events->OnCompleted(0, nullptr);
+                
+            };
+            
+            if(parentWindowHandle != nullptr)
+            {
+                auto windowBase = dynamic_cast<INSWindowHolder*>(parentWindowHandle);
+                
+                [panel beginSheetModalForWindow:windowBase->GetNSWindow() completionHandler:handler];
+            }
+            else
+            {
+                [panel beginWithCompletionHandler: handler];
+            }
+        }
+    }
+
+};
+
+extern IAvnSystemDialogs* CreateSystemDialogs()
+{
+    return new SystemDialogs();
+}

Some files were not shown because too many files changed in this diff