소스 검색

Merge branch 'master' into portablexaml

danwalmsley 8 년 전
부모
커밋
e0dc2ceff7
100개의 변경된 파일1060개의 추가작업 그리고 569개의 파일을 삭제
  1. 2 1
      .gitignore
  2. 43 0
      Avalonia.sln
  3. 1 0
      Avalonia.sln.DotSettings
  4. 1 0
      appveyor.yml
  5. 53 15
      build.cake
  6. 1 1
      build/Base.props
  7. 1 1
      build/Moq.props
  8. 2 1
      build/SkiaSharp.props
  9. 4 2
      build/XUnit.props
  10. 1 1
      docs/index.md
  11. 1 1
      docs/tutorial/from-wpf.md
  12. 1 1
      docs/tutorial/gettingstarted.md
  13. 35 10
      packages.cake
  14. 11 3
      parameters.cake
  15. 1 1
      readme.md
  16. 2 1
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  17. 23 0
      samples/ControlCatalog/Pages/MenuPage.xaml
  18. 1 0
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  19. 1 1
      samples/interop/Direct3DInteropSample/MainWindow.cs
  20. 1 0
      samples/interop/GtkInteropDemo/GtkInteropDemo.csproj
  21. 13 1
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
  22. 14 1
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
  23. 7 1
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  24. 5 0
      scripts/ReplaceNugetCache.ps1
  25. 7 0
      scripts/ReplaceNugetCache.sh
  26. 0 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  27. 2 0
      src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
  28. 1 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  29. 3 0
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  30. 1 1
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  31. 1 0
      src/Avalonia.Base/Avalonia.Base.csproj
  32. 3 8
      src/Avalonia.Base/AvaloniaObject.cs
  33. 9 16
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  34. 33 90
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  35. 8 30
      src/Avalonia.Base/Data/BindingNotification.cs
  36. 1 1
      src/Avalonia.Base/PriorityValue.cs
  37. 20 4
      src/Avalonia.Controls/AppBuilderBase.cs
  38. 24 14
      src/Avalonia.Controls/Button.cs
  39. 1 1
      src/Avalonia.Controls/ContextMenu.cs
  40. 11 4
      src/Avalonia.Controls/Control.cs
  41. 9 7
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  42. 1 1
      src/Avalonia.Controls/ItemsControl.cs
  43. 1 1
      src/Avalonia.Controls/Menu.cs
  44. 10 1
      src/Avalonia.Controls/MenuItem.cs
  45. 7 0
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  46. 24 14
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  47. 1 2
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  48. 2 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  49. 2 2
      src/Avalonia.Controls/Primitives/Popup.cs
  50. 3 4
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  51. 11 0
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  52. 2 2
      src/Avalonia.Controls/Templates/TemplateExtensions.cs
  53. 5 2
      src/Avalonia.Controls/TextBox.cs
  54. 26 15
      src/Avalonia.Controls/ToolTip.cs
  55. 23 17
      src/Avalonia.Controls/TopLevel.cs
  56. 21 1
      src/Avalonia.Controls/TreeView.cs
  57. 30 13
      src/Avalonia.Controls/Window.cs
  58. 28 15
      src/Avalonia.Controls/WindowBase.cs
  59. 3 1
      src/Avalonia.DesignerSupport/DesignerAssist.cs
  60. 0 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  61. 14 16
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  62. 1 2
      src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs
  63. 43 31
      src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
  64. 2 2
      src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs
  65. 4 7
      src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs
  66. 5 5
      src/Avalonia.Diagnostics/ViewModels/TreeNode.cs
  67. 14 10
      src/Avalonia.Diagnostics/ViewModels/TreePageViewModel.cs
  68. 32 0
      src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs
  69. 3 4
      src/Avalonia.Diagnostics/ViewModels/VisualTreeNode.cs
  70. 10 10
      src/Avalonia.Diagnostics/Views/ControlDetailsView.cs
  71. 30 0
      src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs
  72. 7 1
      src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs
  73. 4 1
      src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs
  74. 2 1
      src/Avalonia.HtmlRenderer/HtmlControl.cs
  75. 3 2
      src/Avalonia.Input/FocusManager.cs
  76. 15 0
      src/Avalonia.Input/ICustomKeyboardNavigation.cs
  77. 7 0
      src/Avalonia.Input/IInputDevice.cs
  78. 8 0
      src/Avalonia.Input/IInputRoot.cs
  79. 2 1
      src/Avalonia.Input/IKeyboardDevice.cs
  80. 1 0
      src/Avalonia.Input/InputManager.cs
  81. 3 9
      src/Avalonia.Input/KeyboardDevice.cs
  82. 27 0
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  83. 8 23
      src/Avalonia.Input/MouseDevice.cs
  84. 37 25
      src/Avalonia.Input/Navigation/DirectionalNavigation.cs
  85. 3 3
      src/Avalonia.Input/Navigation/FocusExtensions.cs
  86. 72 35
      src/Avalonia.Input/Navigation/TabNavigation.cs
  87. 10 0
      src/Avalonia.Layout/IEmbeddedLayoutRoot.cs
  88. 66 33
      src/Avalonia.Layout/LayoutManager.cs
  89. 25 9
      src/Avalonia.Layout/Layoutable.cs
  90. 1 1
      src/Avalonia.Styling/Controls/NameScope.cs
  91. 10 0
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  92. 3 3
      src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs
  93. 7 7
      src/Avalonia.Styling/Styling/DescendentSelector.cs
  94. 3 3
      src/Avalonia.Styling/Styling/Selectors.cs
  95. 2 2
      src/Avalonia.Styling/Styling/Style.cs
  96. 1 1
      src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
  97. 1 1
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  98. 1 1
      src/Avalonia.Visuals/Rendering/ZIndexComparer.cs
  99. 11 2
      src/Avalonia.Visuals/Vector.cs
  100. 3 3
      src/Avalonia.Visuals/Visual.cs

+ 2 - 1
.gitignore

@@ -162,7 +162,8 @@ $RECYCLE.BIN/
 #################
 #################
 ## Cake
 ## Cake
 #################
 #################
-tools/
+tools/*
+!tools/packages.config
 .nuget
 .nuget
 artifacts/
 artifacts/
 nuget
 nuget

+ 43 - 0
Avalonia.sln

@@ -193,6 +193,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer",
 EndProject
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
+EndProject
 Global
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
 		src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
@@ -3191,6 +3193,46 @@ Global
 		{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.Build.0 = Release|Any CPU
 		{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.Build.0 = Release|Any CPU
 		{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.ActiveCfg = Release|Any CPU
 		{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.ActiveCfg = Release|Any CPU
 		{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.Build.0 = Release|Any CPU
 		{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.Build.0 = Debug|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.Build.0 = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.ActiveCfg = Release|Any CPU
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
@@ -3252,5 +3294,6 @@ Global
 		{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
 		{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
 		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 		{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
 		{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
+		{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
 	EndGlobalSection
 	EndGlobalSection
 EndGlobal
 EndGlobal

+ 1 - 0
Avalonia.sln.DotSettings

@@ -1,4 +1,5 @@
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=3E53A01A_002DB331_002D47F3_002DB828_002D4A5717E77A24_002Fd_003Aglass/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String>

+ 1 - 0
appveyor.yml

@@ -35,6 +35,7 @@ test: off
 artifacts:
 artifacts:
   - path: artifacts\nuget\*.nupkg
   - path: artifacts\nuget\*.nupkg
   - path: artifacts\zip\*.zip
   - path: artifacts\zip\*.zip
+  - path: artifacts\inspectcode.xml
 cache:
 cache:
   - gtk-sharp-2.12.26.msi
   - gtk-sharp-2.12.26.msi
   - dotnet-1.0.1.exe
   - dotnet-1.0.1.exe

+ 53 - 15
build.cake

@@ -5,12 +5,13 @@
 #addin "nuget:?package=Polly&version=4.2.0"
 #addin "nuget:?package=Polly&version=4.2.0"
 #addin "nuget:?package=NuGet.Core&version=2.12.0"
 #addin "nuget:?package=NuGet.Core&version=2.12.0"
 #tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-preview1-3980&prerelease"
 #tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-preview1-3980&prerelease"
-#tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.1.20150828.125449"
+#tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.3.20160517.113140"
+#tool "JetBrains.ReSharper.CommandLineTools"
 ///////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
 // TOOLS
 // TOOLS
 ///////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
 
 
-#tool "nuget:?package=xunit.runner.console&version=2.1.0"
+#tool "nuget:?package=xunit.runner.console&version=2.2.0"
 #tool "nuget:?package=OpenCover"
 #tool "nuget:?package=OpenCover"
 
 
 ///////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
@@ -97,14 +98,13 @@ Task("Clean")
     CleanDirectory(parameters.TestsRoot);
     CleanDirectory(parameters.TestsRoot);
 });
 });
 
 
-
 Task("Restore-NuGet-Packages")
 Task("Restore-NuGet-Packages")
     .IsDependentOn("Clean")
     .IsDependentOn("Clean")
     .WithCriteria(parameters.IsRunningOnWindows)
     .WithCriteria(parameters.IsRunningOnWindows)
     .Does(() =>
     .Does(() =>
 {
 {
     var maxRetryCount = 5;
     var maxRetryCount = 5;
-    var toolTimeout = 1d;
+    var toolTimeout = 2d;
     Policy
     Policy
         .Handle<Exception>()
         .Handle<Exception>()
         .Retry(maxRetryCount, (exception, retryCount, context) => {
         .Retry(maxRetryCount, (exception, retryCount, context) => {
@@ -170,23 +170,25 @@ void RunCoreTest(string dir, Parameters parameters, bool net461Only)
             continue;
             continue;
         Information("Running for " + fw);
         Information("Running for " + fw);
         DotNetCoreTest(System.IO.Path.Combine(dir, System.IO.Path.GetFileName(dir)+".csproj"),
         DotNetCoreTest(System.IO.Path.Combine(dir, System.IO.Path.GetFileName(dir)+".csproj"),
-            new DotNetCoreTestSettings{Framework = fw});
+            new DotNetCoreTestSettings {
+                Configuration = parameters.Configuration,
+                Framework = fw
+            });
     }
     }
 }
 }
 
 
-
 Task("Run-Net-Core-Unit-Tests")
 Task("Run-Net-Core-Unit-Tests")
     .IsDependentOn("Clean")
     .IsDependentOn("Clean")
     .Does(() => {
     .Does(() => {
         RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
         RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, true);
-        //RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, true);
-        //RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, true);
-        RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, true);
+        RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
+        RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
     });
     });
 
 
 Task("Run-Unit-Tests")
 Task("Run-Unit-Tests")
@@ -279,11 +281,15 @@ Task("Zip-Files")
     Zip(parameters.ZipSourceControlCatalogDesktopDirs, 
     Zip(parameters.ZipSourceControlCatalogDesktopDirs, 
         parameters.ZipTargetControlCatalogDesktopDirs, 
         parameters.ZipTargetControlCatalogDesktopDirs, 
         GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + 
         GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + 
+        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + 
+        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + 
+        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + 
         GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
         GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
 });
 });
 
 
 Task("Create-NuGet-Packages")
 Task("Create-NuGet-Packages")
     .IsDependentOn("Run-Unit-Tests")
     .IsDependentOn("Run-Unit-Tests")
+    .IsDependentOn("Inspect")
     .Does(() =>
     .Does(() =>
 {
 {
     foreach(var nuspec in packages.NuspecNuGetSettings)
     foreach(var nuspec in packages.NuspecNuGetSettings)
@@ -331,7 +337,6 @@ Task("Publish-NuGet")
     .WithCriteria(() => !parameters.IsLocalBuild)
     .WithCriteria(() => !parameters.IsLocalBuild)
     .WithCriteria(() => !parameters.IsPullRequest)
     .WithCriteria(() => !parameters.IsPullRequest)
     .WithCriteria(() => parameters.IsMainRepo)
     .WithCriteria(() => parameters.IsMainRepo)
-    .WithCriteria(() => parameters.IsMasterBranch)
     .WithCriteria(() => parameters.IsNuGetRelease)
     .WithCriteria(() => parameters.IsNuGetRelease)
     .Does(() =>
     .Does(() =>
 {
 {
@@ -360,6 +365,39 @@ Task("Publish-NuGet")
     Information("Publish-NuGet Task failed, but continuing with next Task...");
     Information("Publish-NuGet Task failed, but continuing with next Task...");
 });
 });
 
 
+Task("Inspect")
+    .WithCriteria(parameters.IsRunningOnWindows)
+    .IsDependentOn("Restore-NuGet-Packages")
+    .Does(() =>
+    {
+        var badIssues = new []{"PossibleNullReferenceException"};
+        var whitelist = new []{"tests", "src\\android", "src\\ios",
+            "src\\windows\\avalonia.designer", "src\\avalonia.htmlrenderer\\external"};
+        Information("Running code inspections");
+        
+        
+        StartProcess("tools\\JetBrains.ReSharper.CommandLineTools\\tools\\inspectcode.exe",
+            new ProcessSettings{ Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln" });
+        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
 // TARGETS
 ///////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////

+ 1 - 1
build/SkiaSharp.Desktop.props → build/Base.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Avalonia.Skia.Linux.Natives" Version="1.56.1.3" />
+    <PackageReference Include="System.ValueTuple" Version="4.3.1" />
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 1 - 1
build/Moq.props

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

+ 2 - 1
build/SkiaSharp.props

@@ -1,5 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="SkiaSharp" Version="1.56.1-beta" />
+    <PackageReference Include="SkiaSharp" Version="1.57.1" />
+    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.57.1.3" />
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 4 - 2
build/XUnit.props

@@ -7,7 +7,9 @@
     <PackageReference Include="xunit.extensibility.core" Version="2.2.0" />
     <PackageReference Include="xunit.extensibility.core" Version="2.2.0" />
     <PackageReference Include="xunit.extensibility.execution" Version="2.2.0" />
     <PackageReference Include="xunit.extensibility.execution" Version="2.2.0" />
     <PackageReference Include="xunit.runner.console" Version="2.2.0" />
     <PackageReference Include="xunit.runner.console" Version="2.2.0" />
-    <PackageReference Condition="'$(TargetFramework)' == 'net461'" Include="xunit.runner.visualstudio" Version="2.2.0" />
-    <PackageReference Condition="'$(TargetFramework)' == 'netcoreapp1.1'" Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
+  </ItemGroup>
+  <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp1.1'">
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 1 - 1
docs/index.md

@@ -10,7 +10,7 @@ What does alpha mean? Well, it means that it's now at a stage where you can have
 
 
 ## How do I try it out
 ## How do I try it out
 
 
-The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://visualstudiogallery.msdn.microsoft.com/a4542e8a-b56c-4295-8df1-7e220178b873).
+The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio).
 
 
 This will add a Avalonia project template and a Window template to the standard Visual Studo "Add" dialog (yes, icons still to come :) ):
 This will add a Avalonia project template and a Window template to the standard Visual Studo "Add" dialog (yes, icons still to come :) ):
 
 

+ 1 - 1
docs/tutorial/from-wpf.md

@@ -33,7 +33,7 @@ placed in a `DataTemplates` collection on each control (and on `Application`):
                     <TextBox Text="{Binding Name}"/>
                     <TextBox Text="{Binding Name}"/>
                 </Border>
                 </Border>
             </DataTemplate>
             </DataTemplate>
-        </UserControl.Styles>
+        </UserControl.DataTemplates>
         <!-- Assuming that DataContext.Foo is an object of type
         <!-- Assuming that DataContext.Foo is an object of type
              MyApp.ViewModels.FooViewModel then a red border with a corner
              MyApp.ViewModels.FooViewModel then a red border with a corner
              radius of 8 containing a TextBox will be displayed here -->
              radius of 8 containing a TextBox will be displayed here -->

+ 1 - 1
docs/tutorial/gettingstarted.md

@@ -4,7 +4,7 @@
 
 
 ![](images/add-dialogs.png)
 ![](images/add-dialogs.png)
 
 
-The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://visualstudiogallery.msdn.microsoft.com/e1c6ae1f-6fd9-467d-8f62-1e28b4225213).
+The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio).
 
 
 This will add a Avalonia project template and a Window template to the standard Visual Studo “Add”
 This will add a Avalonia project template and a Window template to the standard Visual Studo “Add”
 dialog (yes, icons still to come :) ):
 dialog (yes, icons still to come :) ):

+ 35 - 10
packages.cake

@@ -7,6 +7,7 @@ public class Packages
     public FilePath[] BinFiles { get; private set; }
     public FilePath[] BinFiles { get; private set; }
     public string NugetPackagesDir {get; private set;}
     public string NugetPackagesDir {get; private set;}
     public string SkiaSharpVersion {get; private set; }
     public string SkiaSharpVersion {get; private set; }
+    public string SkiaSharpLinuxVersion {get; private set; }
     public Packages(ICakeContext context, Parameters parameters)
     public Packages(ICakeContext context, Parameters parameters)
     {
     {
         // NUGET NUSPECS
         // NUGET NUSPECS
@@ -74,7 +75,9 @@ public class Packages
         var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
         var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
         var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
         var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
         var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
         var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
+        var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1;
         SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1;
         SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1;
+		SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1;
         var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1;
         var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1;
         var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1;
         var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1;
         var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1;
         var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1;
@@ -84,7 +87,9 @@ public class Packages
         context.Information("Package: Splat, version: {0}", SplatVersion);
         context.Information("Package: Splat, version: {0}", SplatVersion);
         context.Information("Package: Sprache, version: {0}", SpracheVersion);
         context.Information("Package: Sprache, version: {0}", SpracheVersion);
         context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
         context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
+        context.Information("Package: System.ValueTuple, version: {0}", SystemValueTupleVersion);
         context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion);
         context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion);
+        context.Information("Package: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion);
         context.Information("Package: SharpDX, version: {0}", SharpDXVersion);
         context.Information("Package: SharpDX, version: {0}", SharpDXVersion);
         context.Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version);
         context.Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version);
         context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version);
         context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version);
@@ -194,6 +199,7 @@ public class Packages
                     new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
                     new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
                     new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
                     new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
                     new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
                     new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
+                    new NuSpecDependency() { Id = "System.ValueTuple", Version = SystemValueTupleVersion },
                     //.NET Core
                     //.NET Core
                     new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" },
                     new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" },
                     new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp1.0", Version = "1.1.0" },
                     new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp1.0", Version = "1.1.0" },
@@ -201,7 +207,8 @@ public class Packages
                     new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp1.0", Version = SplatVersion },
                     new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp1.0", Version = SplatVersion },
                     new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion },
                     new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion },
                     new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp1.0", Version = SpracheVersion },
                     new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp1.0", Version = SpracheVersion },
-                    new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion }
+                    new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion },
+                    new NuSpecDependency() { Id = "System.ValueTuple", TargetFramework = "netcoreapp1.0", Version = SystemValueTupleVersion }
                 },
                 },
                 Files = coreLibrariesNuSpecContent
                 Files = coreLibrariesNuSpecContent
                     .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
                     .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
@@ -425,10 +432,7 @@ public class Packages
                 {
                 {
                     new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
                     new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
                     new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion },
                     new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion },
-                    //netstandard1.3
-                    new NuSpecDependency() { Id = "Avalonia", TargetFramework = "netstandard1.3", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "SkiaSharp", TargetFramework = "netstandard1.3", Version = SkiaSharpVersion },
-                    new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netstandard1.3", Version = "1.6.0" }
+                    new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion }
                 },
                 },
                 Files = new []
                 Files = new []
                 {
                 {
@@ -446,11 +450,17 @@ public class Packages
                 Id = "Avalonia.Desktop",
                 Id = "Avalonia.Desktop",
                 Dependencies = new []
                 Dependencies = new []
                 {
                 {
-                    new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.Gtk", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.Cairo", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", Version = parameters.Version }
+                    //Full .NET
+                    new NuSpecDependency() { Id = "Avalonia.Direct2D1", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Gtk", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Cairo", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="net45", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="net45", Version = parameters.Version },
+                    //.NET Core
+                    new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="netcoreapp1.1", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="netcoreapp1.1", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp1.1", Version = parameters.Version }
                 },
                 },
                 Files = new NuSpecContent[]
                 Files = new NuSpecContent[]
                 {
                 {
@@ -459,6 +469,21 @@ public class Packages
                 BasePath = context.Directory("./"),
                 BasePath = context.Directory("./"),
                 OutputDirectory = parameters.NugetRoot
                 OutputDirectory = parameters.NugetRoot
             },
             },
+            new NuGetPackSettings()
+            {
+                Id = "Avalonia.Win32.Interoperability",
+                Dependencies = new []
+                {
+                    new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
+                    new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
+                },
+                Files = new []
+                {
+                    new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" }
+                },
+                BasePath = context.Directory("./src/Windows"),
+                OutputDirectory = parameters.NugetRoot
+            },
             ///////////////////////////////////////////////////////////////////////////////
             ///////////////////////////////////////////////////////////////////////////////
             // Avalonia.LinuxFramebuffer
             // Avalonia.LinuxFramebuffer
             ///////////////////////////////////////////////////////////////////////////////
             ///////////////////////////////////////////////////////////////////////////////

+ 11 - 3
parameters.cake

@@ -75,17 +75,25 @@ public class Parameters
         IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform) 
         IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform) 
                     && StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
                     && StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
         IsMyGetRelease = !IsTagged && IsReleasable;
         IsMyGetRelease = !IsTagged && IsReleasable;
-        IsNuGetRelease = IsTagged && IsReleasable;
+        
 
 
         // VERSION
         // VERSION
         Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion);
         Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion);
 
 
         if (IsRunningOnAppVeyor)
         if (IsRunningOnAppVeyor)
         {
         {
+            string tagVersion = null;
             if (IsTagged)
             if (IsTagged)
             {
             {
-                // Use Tag Name as version
-                Version = buildSystem.AppVeyor.Environment.Repository.Tag.Name;
+                var tag = buildSystem.AppVeyor.Environment.Repository.Tag.Name;
+                var nugetReleasePrefix = "nuget-release-";
+                IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix);
+                if(IsNuGetRelease)
+                    tagVersion = tag.Substring(nugetReleasePrefix.Length);
+            }
+            if(tagVersion != null)
+            {
+                Version = tagVersion;
             }
             }
             else
             else
             {
             {

+ 1 - 1
readme.md

@@ -42,7 +42,7 @@ using Direct2D and other operating systems using Gtk & Cairo.
 
 
 Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you
 Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you
 can have a play and hopefully create simple applications. There's now a [Visual
 can have a play and hopefully create simple applications. There's now a [Visual
-Studio Extension](https://visualstudiogallery.msdn.microsoft.com/e1c6ae1f-6fd9-467d-8f62-1e28b4225213)
+Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio)
 containing project and item templates that will help you get started, and
 containing project and item templates that will help you get started, and
 there's an initial complement of controls. There's still a lot missing, and you
 there's an initial complement of controls. There's still a lot missing, and you
 *will* find bugs, and the API *will* change, but this represents the first time
 *will* find bugs, and the API *will* change, but this represents the first time

+ 2 - 1
samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@@ -26,7 +26,7 @@
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
+    <PlatformTarget>x86</PlatformTarget>
     <DebugType>pdbonly</DebugType>
     <DebugType>pdbonly</DebugType>
     <Optimize>true</Optimize>
     <Optimize>true</Optimize>
     <OutputPath>bin\Release\</OutputPath>
     <OutputPath>bin\Release\</OutputPath>
@@ -142,6 +142,7 @@
   </ItemGroup>
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="..\..\build\Serilog.props" />
   <Import Project="..\..\build\Serilog.props" />
+  <Import Project="..\..\build\SkiaSharp.props" />
   <Import Project="..\..\build\Serilog.Sinks.Trace.props" />
   <Import Project="..\..\build\Serilog.Sinks.Trace.props" />
   <Import Project="$(MSBuildThisFileDirectory)..\..\src\Shared\nuget.workaround.targets" />
   <Import Project="$(MSBuildThisFileDirectory)..\..\src\Shared\nuget.workaround.targets" />
 </Project>
 </Project>

+ 23 - 0
samples/ControlCatalog/Pages/MenuPage.xaml

@@ -31,5 +31,28 @@
         </MenuItem>
         </MenuItem>
       </Menu>
       </Menu>
     </StackPanel>
     </StackPanel>
+
+    <TextBlock Classes="h2" Text="A context menu (right click)">
+      <TextBlock.ContextMenu>
+        <ContextMenu>
+          <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>
+        </ContextMenu>
+      </TextBlock.ContextMenu>
+    </TextBlock>
   </StackPanel>
   </StackPanel>
 </UserControl>
 </UserControl>

+ 1 - 0
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@@ -22,6 +22,7 @@
     <ItemGroup>
     <ItemGroup>
         <ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
         <ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
         <ProjectReference Include="..\..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
         <ProjectReference Include="..\..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
+        <ProjectReference Include="..\..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
         <ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
         <ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
         <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
         <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
         <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
         <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />

+ 1 - 1
samples/interop/Direct3DInteropSample/MainWindow.cs

@@ -58,7 +58,7 @@ namespace Direct3DInteropSample
                    new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height,
                    new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height,
                             new Rational(60, 1), Format.R8G8B8A8_UNorm),
                             new Rational(60, 1), Format.R8G8B8A8_UNorm),
                 IsWindowed = true,
                 IsWindowed = true,
-                OutputHandle = PlatformImpl.Handle.Handle,
+                OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero,
                 SampleDescription = new SampleDescription(1, 0),
                 SampleDescription = new SampleDescription(1, 0),
                 SwapEffect = SwapEffect.Discard,
                 SwapEffect = SwapEffect.Discard,
                 Usage = Usage.RenderTargetOutput
                 Usage = Usage.RenderTargetOutput

+ 1 - 0
samples/interop/GtkInteropDemo/GtkInteropDemo.csproj

@@ -149,6 +149,7 @@
       <Name>ControlCatalog</Name>
       <Name>ControlCatalog</Name>
     </ProjectReference>
     </ProjectReference>
   </ItemGroup>
   </ItemGroup>
+  <Import Project="..\..\..\build\Rx.props" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
   <Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
 </Project>
 </Project>

+ 13 - 1
samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml

@@ -1,10 +1,12 @@
 <Window x:Class="WindowsInteropTest.EmbedToWpfDemo"
 <Window x:Class="WindowsInteropTest.EmbedToWpfDemo"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:av="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
              xmlns:local="clr-namespace:WindowsInteropTest"
              xmlns:local="clr-namespace:WindowsInteropTest"
              xmlns:embedding="clr-namespace:Avalonia.Win32.Embedding;assembly=Avalonia.Win32"
              xmlns:embedding="clr-namespace:Avalonia.Win32.Embedding;assembly=Avalonia.Win32"
+             xmlns:wpf="clr-namespace:Avalonia.Win32.Interop.Wpf;assembly=Avalonia.Win32.Interop"
              mc:Ignorable="d" 
              mc:Ignorable="d" 
              d:DesignHeight="400" d:DesignWidth="400" MinWidth="500" MinHeight="400">
              d:DesignHeight="400" d:DesignWidth="400" MinWidth="500" MinHeight="400">
     <DockPanel>
     <DockPanel>
@@ -14,8 +16,18 @@
                 <Calendar/>
                 <Calendar/>
             </StackPanel>
             </StackPanel>
         </GroupBox>
         </GroupBox>
+        <GroupBox Header="Avalonia button" DockPanel.Dock="Bottom">
+            <wpf:WpfAvaloniaHost >
+                <av:Button Content="Avalonia button"/>
+            </wpf:WpfAvaloniaHost>
+        </GroupBox>
+        <GroupBox Header="AvBtn" DockPanel.Dock="Right">
+            <wpf:WpfAvaloniaHost x:Name="RightBtn">
+                <av:Button Content="Avalonia button 2"/>
+            </wpf:WpfAvaloniaHost>
+        </GroupBox>
         <GroupBox Header="Avalonia">
         <GroupBox Header="Avalonia">
-            <embedding:WpfAvaloniaControlHost x:Name="Host"/>
+            <wpf:WpfAvaloniaHost x:Name="Host"/>
         </GroupBox>
         </GroupBox>
     </DockPanel>
     </DockPanel>
 </Window>
 </Window>

+ 14 - 1
samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs

@@ -11,7 +11,9 @@ using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
 using System.Windows.Navigation;
 using System.Windows.Navigation;
 using System.Windows.Shapes;
 using System.Windows.Shapes;
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
+using Avalonia.VisualTree;
 using ControlCatalog;
 using ControlCatalog;
 using Window = System.Windows.Window;
 using Window = System.Windows.Window;
 
 
@@ -25,7 +27,18 @@ namespace WindowsInteropTest
         public EmbedToWpfDemo()
         public EmbedToWpfDemo()
         {
         {
             InitializeComponent();
             InitializeComponent();
-            Host.Content =  new MainView();
+            var view = new MainView();
+            view.AttachedToVisualTree += delegate
+            {
+                ((TopLevel) view.GetVisualRoot()).AttachDevTools(); 
+            };
+            Host.Content = view;
+            var btn = (Avalonia.Controls.Button) RightBtn.Content;
+            btn.Click += delegate
+            {
+                btn.Content += "!";
+            };
+
         }
         }
     }
     }
 }
 }

+ 7 - 1
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@@ -14,7 +14,7 @@
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
+    <PlatformTarget>x86</PlatformTarget>
     <DebugSymbols>true</DebugSymbols>
     <DebugSymbols>true</DebugSymbols>
     <DebugType>full</DebugType>
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
     <Optimize>false</Optimize>
@@ -164,6 +164,10 @@
       <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
       <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
       <Name>Avalonia.Direct2D1</Name>
       <Name>Avalonia.Direct2D1</Name>
     </ProjectReference>
     </ProjectReference>
+    <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj">
+      <Project>{cbc4ff2f-92d4-420b-be21-9fe0b930b04e}</Project>
+      <Name>Avalonia.Win32.Interop</Name>
+    </ProjectReference>
     <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj">
     <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj">
       <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
       <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
       <Name>Avalonia.Win32</Name>
       <Name>Avalonia.Win32</Name>
@@ -179,6 +183,8 @@
       <Generator>MSBuild:Compile</Generator>
       <Generator>MSBuild:Compile</Generator>
     </Page>
     </Page>
   </ItemGroup>
   </ItemGroup>
+  <Import Project="..\..\..\build\Rx.props" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\..\..\build\SkiaSharp.props" />
   <Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
   <Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
 </Project>
 </Project>

+ 5 - 0
scripts/ReplaceNugetCache.ps1

@@ -0,0 +1,5 @@
+copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp1.0\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard1.1\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard1.1\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.skia.desktop\$args\lib\netstandard1.3\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard1.1\

+ 7 - 0
scripts/ReplaceNugetCache.sh

@@ -0,0 +1,7 @@
+ #!/usr/bin/env bash
+ 
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp1.0/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard1.1/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard1.1/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.skia.desktop/$1/lib/netstandard1.3/
+ 

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

@@ -51,7 +51,6 @@ namespace Avalonia.Android
                 .Bind<IClipboard>().ToTransient<ClipboardImpl>()
                 .Bind<IClipboard>().ToTransient<ClipboardImpl>()
                 .Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
                 .Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
                 .Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
                 .Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
-                .Bind<IMouseDevice>().ToSingleton<AndroidMouseDevice>()
                 .Bind<IPlatformSettings>().ToConstant(Instance)
                 .Bind<IPlatformSettings>().ToConstant(Instance)
                 .Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
                 .Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
                 .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
                 .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())

+ 2 - 0
src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs

@@ -4,6 +4,8 @@ namespace Avalonia.Android.Platform.Input
 {
 {
     public class AndroidMouseDevice : MouseDevice
     public class AndroidMouseDevice : MouseDevice
     {
     {
+        public static AndroidMouseDevice Instance { get; } = new AndroidMouseDevice();
+
         public AndroidMouseDevice()
         public AndroidMouseDevice()
         {
         {
 
 

+ 1 - 1
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@@ -44,7 +44,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         public int Width { get; }
         public int Width { get; }
         public int Height { get; }
         public int Height { get; }
         public int RowBytes { get; }
         public int RowBytes { get; }
-        public Size Dpi { get; } = new Size(96, 96);
+        public Vector Dpi { get; } = new Vector(96, 96);
         public PixelFormat Format { get; }
         public PixelFormat Format { get; }
 
 
         [DllImport("android")]
         [DllImport("android")]

+ 3 - 0
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -10,6 +10,7 @@ using Avalonia.Platform;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Reactive.Disposables;
 using System.Reactive.Disposables;
+using Avalonia.Android.Platform.Input;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Controls.Platform.Surfaces;
 
 
@@ -65,6 +66,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             }
             }
         }
         }
 
 
+        public IMouseDevice MouseDevice => AndroidMouseDevice.Instance;
+
         public Action Closed { get; set; }
         public Action Closed { get; set; }
 
 
         public Action<RawInputEventArgs> Input { get; set; }
         public Action<RawInputEventArgs> Input { get; set; }

+ 1 - 1
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@@ -71,7 +71,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
                 if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y)
                 if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y)
                 {
                 {
                     var inputRoot = _getInputRoot();
                     var inputRoot = _getInputRoot();
-                    var mouseDevice = MouseDevice.Instance;
+                    var mouseDevice = Avalonia.Android.Platform.Input.AndroidMouseDevice.Instance;
 
 
                     //in order the controls to work in a predictable way
                     //in order the controls to work in a predictable way
                     //we need to generate mouse move before first mouse down event
                     //we need to generate mouse move before first mouse down event

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

@@ -30,6 +30,7 @@
       <Link>Properties\SharedAssemblyInfo.cs</Link>
       <Link>Properties\SharedAssemblyInfo.cs</Link>
     </Compile>
     </Compile>
   </ItemGroup>
   </ItemGroup>
+  <Import Project="..\..\build\Base.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\JetBrains.Annotations.props" />
   <Import Project="..\..\build\JetBrains.Annotations.props" />
 </Project>
 </Project>

+ 3 - 8
src/Avalonia.Base/AvaloniaObject.cs

@@ -622,14 +622,9 @@ namespace Avalonia
         /// <returns>The default value.</returns>
         /// <returns>The default value.</returns>
         private object GetDefaultValue(AvaloniaProperty property)
         private object GetDefaultValue(AvaloniaProperty property)
         {
         {
-            if (property.Inherits && _inheritanceParent != null)
-            {
-                return (_inheritanceParent as AvaloniaObject).GetValueInternal(property);
-            }
-            else
-            {
-                return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
-            }
+            if (property.Inherits && _inheritanceParent is AvaloniaObject aobj)
+                return aobj.GetValueInternal(property);
+            return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 9 - 16
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@@ -103,11 +103,9 @@ namespace Avalonia.Collections
 
 
             _inner = new Dictionary<TKey, TValue>();
             _inner = new Dictionary<TKey, TValue>();
 
 
-            if (PropertyChanged != null)
-            {
-                PropertyChanged(this, new PropertyChangedEventArgs("Count"));
-                PropertyChanged(this, new PropertyChangedEventArgs($"Item[]"));
-            }
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[]"));
+            
 
 
             if (CollectionChanged != null)
             if (CollectionChanged != null)
             {
             {
@@ -144,12 +142,9 @@ namespace Avalonia.Collections
 
 
             if (_inner.TryGetValue(key, out value))
             if (_inner.TryGetValue(key, out value))
             {
             {
-                if (PropertyChanged != null)
-                {
-                    PropertyChanged(this, new PropertyChangedEventArgs("Count"));
-                    PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]"));
-                }
-
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
+                
                 if (CollectionChanged != null)
                 if (CollectionChanged != null)
                 {
                 {
                     var e = new NotifyCollectionChangedEventArgs(
                     var e = new NotifyCollectionChangedEventArgs(
@@ -199,11 +194,9 @@ namespace Avalonia.Collections
 
 
         private void NotifyAdd(TKey key, TValue value)
         private void NotifyAdd(TKey key, TValue value)
         {
         {
-            if (PropertyChanged != null)
-            {
-                PropertyChanged(this, new PropertyChangedEventArgs("Count"));
-                PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]"));
-            }
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
+            
 
 
             if (CollectionChanged != null)
             if (CollectionChanged != null)
             {
             {

+ 33 - 90
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.ComponentModel;
@@ -59,7 +60,8 @@ namespace Avalonia.Collections
         /// the index in the collection and the item.
         /// the index in the collection and the item.
         /// </param>
         /// </param>
         /// <param name="reset">
         /// <param name="reset">
-        /// An action called when the collection is reset.
+        /// An action called when the collection is reset. This will be followed by calls to 
+        /// <paramref name="added"/> for each item present in the collection after the reset.
         /// </param>
         /// </param>
         /// <returns>A disposable used to terminate the subscription.</returns>
         /// <returns>A disposable used to terminate the subscription.</returns>
         public static IDisposable ForEachItem<T>(
         public static IDisposable ForEachItem<T>(
@@ -68,112 +70,38 @@ namespace Avalonia.Collections
             Action<int, T> removed,
             Action<int, T> removed,
             Action reset)
             Action reset)
         {
         {
-            int index;
-
-            NotifyCollectionChangedEventHandler handler = (_, e) =>
+            void Add(int index, IList items)
             {
             {
-                switch (e.Action)
+                foreach (T item in items)
                 {
                 {
-                    case NotifyCollectionChangedAction.Add:
-                        index = e.NewStartingIndex;
-
-                        foreach (T item in e.NewItems)
-                        {
-                            added(index++, item);
-                        }
-
-                        break;
-
-                    case NotifyCollectionChangedAction.Replace:
-                        index = e.OldStartingIndex;
-
-                        foreach (T item in e.OldItems)
-                        {
-                            removed(index++, item);
-                        }
-
-                        index = e.NewStartingIndex;
-
-                        foreach (T item in e.NewItems)
-                        {
-                            added(index++, item);
-                        }
-
-                        break;
-
-                    case NotifyCollectionChangedAction.Remove:
-                        index = e.OldStartingIndex;
-
-                        foreach (T item in e.OldItems)
-                        {
-                            removed(index++, item);
-                        }
-
-                        break;
-
-                    case NotifyCollectionChangedAction.Reset:
-                        if (reset == null)
-                        {
-                            throw new InvalidOperationException(
-                                "Reset called on collection without reset handler.");
-                        }
-
-                        reset();
-                        break;
+                    added(index++, item);
                 }
                 }
-            };
+            }
 
 
-            index = 0;
-            foreach (T i in collection)
+            void Remove(int index, IList items)
             {
             {
-                added(index++, i);
+                for (var i = items.Count - 1; i >= 0; --i)
+                {
+                    removed(index + i, (T)items[i]);
+                }
             }
             }
 
 
-            collection.CollectionChanged += handler;
-
-            return Disposable.Create(() => collection.CollectionChanged -= handler);
-        }
-
-        /// <summary>
-        /// Invokes an action for each item in a collection and subsequently each item added or
-        /// removed from the collection.
-        /// </summary>
-        /// <typeparam name="T">The type of the collection items.</typeparam>
-        /// <param name="collection">The collection.</param>
-        /// <param name="added">
-        /// An action called initially with all items in the collection and subsequently with a
-        /// list of items added to the collection. The parameters passed are the index of the
-        /// first item added to the collection and the items added.
-        /// </param>
-        /// <param name="removed">
-        /// An action called with all items removed from the collection. The parameters passed 
-        /// are the index of the first item removed from the collection and the items removed.
-        /// </param>
-        /// <param name="reset">
-        /// An action called when the collection is reset.
-        /// </param>
-        /// <returns>A disposable used to terminate the subscription.</returns>
-        public static IDisposable ForEachItem<T>(
-            this IAvaloniaReadOnlyList<T> collection,
-            Action<int, IEnumerable<T>> added,
-            Action<int, IEnumerable<T>> removed,
-            Action reset)
-        {
             NotifyCollectionChangedEventHandler handler = (_, e) =>
             NotifyCollectionChangedEventHandler handler = (_, e) =>
             {
             {
                 switch (e.Action)
                 switch (e.Action)
                 {
                 {
                     case NotifyCollectionChangedAction.Add:
                     case NotifyCollectionChangedAction.Add:
-                        added(e.NewStartingIndex, e.NewItems.Cast<T>());
+                        Add(e.NewStartingIndex, e.NewItems);
                         break;
                         break;
 
 
+                    case NotifyCollectionChangedAction.Move:
                     case NotifyCollectionChangedAction.Replace:
                     case NotifyCollectionChangedAction.Replace:
-                        removed(e.OldStartingIndex, e.OldItems.Cast<T>());
-                        added(e.NewStartingIndex, e.NewItems.Cast<T>());
+                        Remove(e.OldStartingIndex, e.OldItems);
+                        Add(e.NewStartingIndex, e.NewItems);
                         break;
                         break;
 
 
                     case NotifyCollectionChangedAction.Remove:
                     case NotifyCollectionChangedAction.Remove:
-                        removed(e.OldStartingIndex, e.OldItems.Cast<T>());
+                        Remove(e.OldStartingIndex, e.OldItems);
                         break;
                         break;
 
 
                     case NotifyCollectionChangedAction.Reset:
                     case NotifyCollectionChangedAction.Reset:
@@ -184,16 +112,31 @@ namespace Avalonia.Collections
                         }
                         }
 
 
                         reset();
                         reset();
+                        Add(0, (IList)collection);
                         break;
                         break;
                 }
                 }
             };
             };
 
 
-            added(0, collection);
+            Add(0, (IList)collection);
             collection.CollectionChanged += handler;
             collection.CollectionChanged += handler;
 
 
             return Disposable.Create(() => collection.CollectionChanged -= handler);
             return Disposable.Create(() => collection.CollectionChanged -= handler);
         }
         }
 
 
+        public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
+            this IAvaloniaReadOnlyList<TSource> collection,
+            Func<TSource, TDerived> select)
+        {
+            var result = new AvaloniaList<TDerived>();
+
+            collection.ForEachItem(
+                (i, item) => result.Insert(i, select(item)),
+                (i, item) => result.RemoveAt(i),
+                () => result.Clear());
+
+            return result;
+        }
+
         /// <summary>
         /// <summary>
         /// Listens for property changed events from all items in a collection.
         /// Listens for property changed events from all items in a collection.
         /// </summary>
         /// </summary>

+ 8 - 30
src/Avalonia.Base/Data/BindingNotification.cs

@@ -44,11 +44,7 @@ namespace Avalonia.Data
         public static readonly BindingNotification UnsetValue =
         public static readonly BindingNotification UnsetValue =
             new BindingNotification(AvaloniaProperty.UnsetValue);
             new BindingNotification(AvaloniaProperty.UnsetValue);
 
 
-        // Null cannot be held in WeakReference as it's indistinguishable from an expired value so
-        // use this value in its place.
-        private static readonly object NullValue = new object();
-
-        private WeakReference<object> _value;
+        private object _value;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="BindingNotification"/> class.
         /// Initializes a new instance of the <see cref="BindingNotification"/> class.
@@ -56,7 +52,7 @@ namespace Avalonia.Data
         /// <param name="value">The binding value.</param>
         /// <param name="value">The binding value.</param>
         public BindingNotification(object value)
         public BindingNotification(object value)
         {
         {
-            _value = new WeakReference<object>(value ?? NullValue);
+            _value = value;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -73,6 +69,7 @@ namespace Avalonia.Data
 
 
             Error = error;
             Error = error;
             ErrorType = errorType;
             ErrorType = errorType;
+            _value = AvaloniaProperty.UnsetValue;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -84,7 +81,7 @@ namespace Avalonia.Data
         public BindingNotification(Exception error, BindingErrorType errorType, object fallbackValue)
         public BindingNotification(Exception error, BindingErrorType errorType, object fallbackValue)
             : this(error, errorType)
             : this(error, errorType)
         {
         {
-            _value = new WeakReference<object>(fallbackValue ?? NullValue);
+            _value = fallbackValue;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -95,31 +92,12 @@ namespace Avalonia.Data
         /// If this property is read when <see cref="HasValue"/> is false then it will return
         /// If this property is read when <see cref="HasValue"/> is false then it will return
         /// <see cref="AvaloniaProperty.UnsetValue"/>.
         /// <see cref="AvaloniaProperty.UnsetValue"/>.
         /// </remarks>
         /// </remarks>
-        public object Value
-        {
-            get
-            {
-                if (_value != null)
-                {
-                    object result;
-
-                    if (_value.TryGetTarget(out result))
-                    {
-                        return result == NullValue ? null : result;
-                    }
-                }
-
-                // There's the possibility of a race condition in that HasValue can return true,
-                // and then the value is GC'd before Value is read. We should be ok though as
-                // we return UnsetValue which should be a safe alternative.
-                return AvaloniaProperty.UnsetValue;
-            }
-        }
+        public object Value => _value;
 
 
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether <see cref="Value"/> should be pushed to the target.
         /// Gets a value indicating whether <see cref="Value"/> should be pushed to the target.
         /// </summary>
         /// </summary>
-        public bool HasValue => _value != null;
+        public bool HasValue => _value != AvaloniaProperty.UnsetValue;
 
 
         /// <summary>
         /// <summary>
         /// Gets the error that occurred on the source, if any.
         /// Gets the error that occurred on the source, if any.
@@ -248,7 +226,7 @@ namespace Avalonia.Data
         /// </summary>
         /// </summary>
         public void ClearValue()
         public void ClearValue()
         {
         {
-            _value = null;
+            _value = AvaloniaProperty.UnsetValue;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -256,7 +234,7 @@ namespace Avalonia.Data
         /// </summary>
         /// </summary>
         public void SetValue(object value)
         public void SetValue(object value)
         {
         {
-            _value = new WeakReference<object>(value ?? NullValue);
+            _value = value;
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>

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

@@ -285,7 +285,7 @@ namespace Avalonia
                     Property.Name, 
                     Property.Name, 
                     _valueType, 
                     _valueType, 
                     value,
                     value,
-                    value.GetType());
+                    value?.GetType());
             }
             }
         }
         }
     }
     }

+ 20 - 4
src/Avalonia.Controls/AppBuilderBase.cs

@@ -55,8 +55,7 @@ namespace Avalonia.Controls
         public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { };
         public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { };
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets a method to call before <see cref="Start{TMainWindow}"/> is called on the
-        /// <see cref="Application"/>.
+        /// Gets or sets a method to call before Startis called on the <see cref="Application"/>.
         /// </summary>
         /// </summary>
         public Action<TAppBuilder> BeforeStartCallback { get; private set; } = builder => { };
         public Action<TAppBuilder> BeforeStartCallback { get; private set; } = builder => { };
 
 
@@ -94,8 +93,7 @@ namespace Avalonia.Controls
         protected TAppBuilder Self => (TAppBuilder) this;
         protected TAppBuilder Self => (TAppBuilder) this;
 
 
         /// <summary>
         /// <summary>
-        /// Registers a callback to call before <see cref="Start{TMainWindow}"/> is called on the
-        /// <see cref="Application"/>.
+        /// Registers a callback to call before Start is called on the <see cref="Application"/>.
         /// </summary>
         /// </summary>
         /// <param name="callback">The callback.</param>
         /// <param name="callback">The callback.</param>
         /// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
         /// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
@@ -129,6 +127,24 @@ namespace Avalonia.Controls
             Instance.Run(window);
             Instance.Run(window);
         }
         }
 
 
+        /// <summary>
+        /// Starts the application with the provided instance of <typeparamref name="TMainWindow"/>.
+        /// </summary>
+        /// <typeparam name="TMainWindow">The window type.</typeparam>
+        /// <param name="mainWindow">Instance of type TMainWindow to use when starting the app</param>
+        /// <param name="dataContextProvider">A delegate that will be called to create a data context for the window (optional).</param>
+        public void Start<TMainWindow>(TMainWindow mainWindow, Func<object> dataContextProvider = null)
+            where TMainWindow : Window
+        {
+            Setup();
+            BeforeStartCallback(Self);
+
+            if (dataContextProvider != null)
+                mainWindow.DataContext = dataContextProvider();
+            mainWindow.Show();
+            Instance.Run(mainWindow);
+        }
+
         /// <summary>
         /// <summary>
         /// Sets up the platform-specific services for the application, but does not run it.
         /// Sets up the platform-specific services for the application, but does not run it.
         /// </summary>
         /// </summary>

+ 24 - 14
src/Avalonia.Controls/Button.cs

@@ -207,7 +207,11 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         /// <param name="e">The event args.</param>
         protected virtual void OnClick(RoutedEventArgs e)
         protected virtual void OnClick(RoutedEventArgs e)
         {
         {
-            Command?.Execute(CommandParameter);
+            if (Command != null)
+            {
+                Command.Execute(CommandParameter);
+                e.Handled = true;
+            }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
@@ -215,13 +219,16 @@ namespace Avalonia.Controls
         {
         {
             base.OnPointerPressed(e);
             base.OnPointerPressed(e);
 
 
-            PseudoClasses.Add(":pressed");
-            e.Device.Capture(this);
-            e.Handled = true;
-
-            if (ClickMode == ClickMode.Press)
+            if (e.MouseButton == MouseButton.Left)
             {
             {
-                RaiseClickEvent();
+                PseudoClasses.Add(":pressed");
+                e.Device.Capture(this);
+                e.Handled = true;
+
+                if (ClickMode == ClickMode.Press)
+                {
+                    RaiseClickEvent();
+                }
             }
             }
         }
         }
 
 
@@ -230,13 +237,16 @@ namespace Avalonia.Controls
         {
         {
             base.OnPointerReleased(e);
             base.OnPointerReleased(e);
 
 
-            e.Device.Capture(null);
-            PseudoClasses.Remove(":pressed");
-            e.Handled = true;
-
-            if (ClickMode == ClickMode.Release && Classes.Contains(":pointerover"))
+            if (e.MouseButton == MouseButton.Left)
             {
             {
-                RaiseClickEvent();
+                e.Device.Capture(null);
+                PseudoClasses.Remove(":pressed");
+                e.Handled = true;
+
+                if (ClickMode == ClickMode.Release && new Rect(Bounds.Size).Contains(e.GetPosition(this)))
+                {
+                    RaiseClickEvent();
+                }
             }
             }
         }
         }
 
 
@@ -275,7 +285,7 @@ namespace Avalonia.Controls
         {
         {
             var button = e.Sender as Button;
             var button = e.Sender as Button;
             var isDefault = (bool)e.NewValue;
             var isDefault = (bool)e.NewValue;
-            var inputRoot = button.VisualRoot as IInputElement;
+            var inputRoot = button?.VisualRoot as IInputElement;
 
 
             if (inputRoot != null)
             if (inputRoot != null)
             {
             {

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

@@ -19,7 +19,7 @@ namespace Avalonia.Controls
         {
         {
             ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
             ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
 
 
-            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick);            
+            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);            
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 11 - 4
src/Avalonia.Controls/Control.cs

@@ -118,6 +118,7 @@ namespace Avalonia.Controls
         public Control()
         public Control()
         {
         {
             _nameScope = this as INameScope;
             _nameScope = this as INameScope;
+            _isAttachedToLogicalTree = this is IStyleRoot;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -379,6 +380,12 @@ namespace Avalonia.Controls
             }
             }
         }
         }
 
 
+        /// <inheritdoc/>
+        void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            this.OnAttachedToLogicalTreeCore(e);
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
         {
@@ -428,7 +435,7 @@ namespace Avalonia.Controls
 
 
                 if (_isAttachedToLogicalTree)
                 if (_isAttachedToLogicalTree)
                 {
                 {
-                    var oldRoot = FindStyleRoot(old);
+                    var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
 
 
                     if (oldRoot == null)
                     if (oldRoot == null)
                     {
                     {
@@ -446,7 +453,7 @@ namespace Avalonia.Controls
 
 
                 _parent = (IControl)parent;
                 _parent = (IControl)parent;
 
 
-                if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true)
+                if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
                 {
                 {
                     var newRoot = FindStyleRoot(this);
                     var newRoot = FindStyleRoot(this);
 
 
@@ -479,7 +486,7 @@ namespace Avalonia.Controls
             {
             {
                 if (!IsInitialized)
                 if (!IsInitialized)
                 {
                 {
-                    foreach (var i in this.GetSelfAndVisualDescendents())
+                    foreach (var i in this.GetSelfAndVisualDescendants())
                     {
                     {
                         var c = i as IControl;
                         var c = i as IControl;
 
 
@@ -651,7 +658,7 @@ namespace Avalonia.Controls
 
 
             if (_focusAdorner != null)
             if (_focusAdorner != null)
             {
             {
-                var adornerLayer = _focusAdorner.Parent as Panel;
+                var adornerLayer = (IPanel)_focusAdorner.Parent;
                 adornerLayer.Children.Remove(_focusAdorner);
                 adornerLayer.Children.Remove(_focusAdorner);
                 _focusAdorner = null;
                 _focusAdorner = null;
             }
             }

+ 9 - 7
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@@ -4,6 +4,7 @@ using Avalonia.Input;
 using Avalonia.Layout;
 using Avalonia.Layout;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.Styling;
 using Avalonia.Styling;
+using JetBrains.Annotations;
 
 
 namespace Avalonia.Controls.Embedding
 namespace Avalonia.Controls.Embedding
 {
 {
@@ -18,8 +19,11 @@ namespace Avalonia.Controls.Embedding
         {
         {
         }
         }
 
 
+        [CanBeNull]
         public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl;
         public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl;
 
 
+        protected bool EnforceClientSize { get; set; } = true;
+
         public void Prepare()
         public void Prepare()
         {
         {
             EnsureInitialized();
             EnsureInitialized();
@@ -36,11 +40,12 @@ namespace Avalonia.Controls.Embedding
                 init.EndInit();
                 init.EndInit();
             }
             }
         }
         }
-
+        
         protected override Size MeasureOverride(Size availableSize)
         protected override Size MeasureOverride(Size availableSize)
         {
         {
-            base.MeasureOverride(PlatformImpl.ClientSize);
-            return PlatformImpl.ClientSize;
+            if (EnforceClientSize)
+                availableSize = PlatformImpl?.ClientSize ?? default(Size);
+            return base.MeasureOverride(availableSize);
         }
         }
 
 
         private readonly NameScope _nameScope = new NameScope();
         private readonly NameScope _nameScope = new NameScope();
@@ -63,9 +68,6 @@ namespace Avalonia.Controls.Embedding
         public void Unregister(string name) => _nameScope.Unregister(name);
         public void Unregister(string name) => _nameScope.Unregister(name);
 
 
         Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
         Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
-        public void Dispose()
-        {
-            PlatformImpl.Dispose();
-        }
+        public void Dispose() => PlatformImpl?.Dispose();
     }
     }
 }
 }

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

@@ -354,7 +354,7 @@ namespace Avalonia.Controls
             }
             }
 
 
             var collection = sender as ICollection;
             var collection = sender as ICollection;
-            PseudoClasses.Set(":empty", collection.Count == 0);
+            PseudoClasses.Set(":empty", collection == null || collection.Count == 0);
         }
         }
 
 
         /// <summary>
         /// <summary>

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

@@ -47,7 +47,7 @@ namespace Avalonia.Controls
         static Menu()
         static Menu()
         {
         {
             ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel);
             ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel);
-            MenuItem.ClickEvent.AddClassHandler<Menu>(x => x.OnMenuClick);
+            MenuItem.ClickEvent.AddClassHandler<Menu>(x => x.OnMenuClick, handledEventsToo: true);
             MenuItem.SubmenuOpenedEvent.AddClassHandler<Menu>(x => x.OnSubmenuOpened);
             MenuItem.SubmenuOpenedEvent.AddClassHandler<Menu>(x => x.OnSubmenuOpened);
         }
         }
 
 

+ 10 - 1
src/Avalonia.Controls/MenuItem.cs

@@ -102,6 +102,11 @@ namespace Avalonia.Controls
             AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<MenuItem>(x => x.AccessKeyPressed);
             AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<MenuItem>(x => x.AccessKeyPressed);
         }
         }
 
 
+        public MenuItem()
+        {
+
+        }
+
         /// <summary>
         /// <summary>
         /// Occurs when a <see cref="MenuItem"/> without a submenu is clicked.
         /// Occurs when a <see cref="MenuItem"/> without a submenu is clicked.
         /// </summary>
         /// </summary>
@@ -192,7 +197,11 @@ namespace Avalonia.Controls
         /// <param name="e">The click event args.</param>
         /// <param name="e">The click event args.</param>
         protected virtual void OnClick(RoutedEventArgs e)
         protected virtual void OnClick(RoutedEventArgs e)
         {
         {
-            Command?.Execute(CommandParameter);
+            if (Command != null)
+            {
+                Command.Execute(CommandParameter);
+                e.Handled = true;
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 7 - 0
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Avalonia.Input;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.Input.Raw;
+using JetBrains.Annotations;
 
 
 namespace Avalonia.Platform
 namespace Avalonia.Platform
 {
 {
@@ -93,5 +94,11 @@ namespace Avalonia.Platform
         /// Gets or sets a method called when the underlying implementation is destroyed.
         /// Gets or sets a method called when the underlying implementation is destroyed.
         /// </summary>
         /// </summary>
         Action Closed { get; set; }
         Action Closed { get; set; }
+
+        /// <summary>
+        /// Gets a mouse device associated with toplevel
+        /// </summary>
+        [CanBeNull]
+        IMouseDevice MouseDevice { get; }
     }
     }
 }
 }

+ 24 - 14
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
 using Avalonia.Layout;
 using Avalonia.Layout;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Media;
+using Avalonia.VisualTree;
 
 
 namespace Avalonia.Controls.Presenters
 namespace Avalonia.Controls.Presenters
 {
 {
@@ -88,6 +89,7 @@ namespace Avalonia.Controls.Presenters
         static ContentPresenter()
         static ContentPresenter()
         {
         {
             ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
             ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
+            ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
             TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
             TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
         }
         }
 
 
@@ -313,27 +315,22 @@ namespace Avalonia.Controls.Presenters
 
 
             if (content != null && newChild == null)
             if (content != null && newChild == null)
             {
             {
-                // We have content and it isn't a control, so first try to recycle the existing
-                // child control to display the new data by querying if the template that created
-                // the child can recycle items and that it also matches the new data.
-                if (oldChild != null &&
-                    _dataTemplate != null &&
-                    _dataTemplate.SupportsRecycling &&
-                    _dataTemplate.Match(content))
+                var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
+
+                // We have content and it isn't a control, so if the new data template is the same
+                // as the old data template, try to recycle the existing child control to display
+                // the new data.
+                if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
                 {
                 {
                     newChild = oldChild;
                     newChild = oldChild;
                 }
                 }
                 else
                 else
                 {
                 {
-                    // We couldn't recycle an existing control so find a data template for the data
-                    // and use it to create a control.
-                    _dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
+                    _dataTemplate = dataTemplate;
                     newChild = _dataTemplate.Build(content);
                     newChild = _dataTemplate.Build(content);
 
 
-                    // Try to give the new control its own name scope.
-                    var controlResult = newChild as Control;
-
-                    if (controlResult != null)
+                    // Give the new control its own name scope.
+                    if (newChild is Control controlResult)
                     {
                     {
                         NameScope.SetNameScope(controlResult, new NameScope());
                         NameScope.SetNameScope(controlResult, new NameScope());
                     }
                     }
@@ -424,6 +421,19 @@ namespace Avalonia.Controls.Presenters
         private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
         private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
         {
         {
             _createdChild = false;
             _createdChild = false;
+
+            if (((ILogical)this).IsAttachedToLogicalTree)
+            {
+                UpdateChild();
+            }
+            else if (Child != null)
+            {
+                VisualChildren.Remove(Child);
+                LogicalChildren.Remove(Child);
+                Child = null;
+                _dataTemplate = null;
+            }
+
             InvalidateMeasure();
             InvalidateMeasure();
         }
         }
 
 

+ 1 - 2
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -155,8 +155,7 @@ namespace Avalonia.Controls.Presenters
                     case NotifyCollectionChangedAction.Add:
                     case NotifyCollectionChangedAction.Add:
                         CreateAndRemoveContainers();
                         CreateAndRemoveContainers();
 
 
-                        if (e.NewStartingIndex >= FirstIndex &&
-                            e.NewStartingIndex < NextIndex)
+                        if (e.NewStartingIndex < NextIndex)
                         {
                         {
                             RecycleContainers();
                             RecycleContainers();
                         }
                         }

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

@@ -111,7 +111,7 @@ namespace Avalonia.Controls.Presenters
         /// <param name="target">The target visual.</param>
         /// <param name="target">The target visual.</param>
         /// <param name="targetRect">The portion of the target visual to bring into view.</param>
         /// <param name="targetRect">The portion of the target visual to bring into view.</param>
         /// <returns>True if the scroll offset was changed; otherwise false.</returns>
         /// <returns>True if the scroll offset was changed; otherwise false.</returns>
-        public bool BringDescendentIntoView(IVisual target, Rect targetRect)
+        public bool BringDescendantIntoView(IVisual target, Rect targetRect)
         {
         {
             if (Child == null)
             if (Child == null)
             {
             {
@@ -262,7 +262,7 @@ namespace Avalonia.Controls.Presenters
 
 
         private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e)
         private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e)
         {
         {
-            e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect);
+            e.Handled = BringDescendantIntoView(e.TargetObject, e.TargetRect);
         }
         }
 
 
         private void ChildChanged(AvaloniaPropertyChangedEventArgs e)
         private void ChildChanged(AvaloniaPropertyChangedEventArgs e)

+ 2 - 2
src/Avalonia.Controls/Primitives/Popup.cs

@@ -340,11 +340,11 @@ namespace Avalonia.Controls.Primitives
             switch (mode)
             switch (mode)
             {
             {
                 case PlacementMode.Pointer:
                 case PlacementMode.Pointer:
-                    if (MouseDevice.Instance != null)
+                    if(PopupRoot != null)
                     {
                     {
                         // Scales the Horizontal and Vertical offset to screen co-ordinates.
                         // Scales the Horizontal and Vertical offset to screen co-ordinates.
                         var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling);
                         var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling);
-                        return MouseDevice.Instance.Position + screenOffset;
+                        return (((IInputRoot)PopupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
                     }
                     }
 
 
                     return default(Point);
                     return default(Point);

+ 3 - 4
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -9,6 +9,7 @@ using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
+using JetBrains.Annotations;
 
 
 namespace Avalonia.Controls.Primitives
 namespace Avalonia.Controls.Primitives
 {
 {
@@ -49,6 +50,7 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// <summary>
         /// Gets the platform-specific window implementation.
         /// Gets the platform-specific window implementation.
         /// </summary>
         /// </summary>
+        [CanBeNull]
         public new IPopupImpl PlatformImpl => (IPopupImpl)base.PlatformImpl;
         public new IPopupImpl PlatformImpl => (IPopupImpl)base.PlatformImpl;
 
 
         /// <summary>
         /// <summary>
@@ -65,10 +67,7 @@ namespace Avalonia.Controls.Primitives
         IVisual IHostedVisualTreeRoot.Host => Parent;
         IVisual IHostedVisualTreeRoot.Host => Parent;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public void Dispose()
-        {
-            this.PlatformImpl.Dispose();
-        }
+        public void Dispose() => PlatformImpl?.Dispose();
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)

+ 11 - 0
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -285,6 +285,17 @@ namespace Avalonia.Controls.Primitives
             return this;
             return this;
         }
         }
 
 
+        /// <inheritdoc/>
+        protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            if (VisualChildren.Count > 0)
+            {
+                ((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(e);
+            }
+
+            base.OnAttachedToLogicalTree(e);
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
         {

+ 2 - 2
src/Avalonia.Controls/Templates/TemplateExtensions.cs

@@ -31,9 +31,9 @@ namespace Avalonia.Controls.Templates
 
 
                 if (child.TemplatedParent != null)
                 if (child.TemplatedParent != null)
                 {
                 {
-                    foreach (var descendent in GetTemplateChildren(child, templatedParent))
+                    foreach (var descendant in GetTemplateChildren(child, templatedParent))
                     {
                     {
-                        yield return descendent;
+                        yield return descendant;
                     }
                     }
                 }
                 }
             }
             }

+ 5 - 2
src/Avalonia.Controls/TextBox.cs

@@ -720,7 +720,7 @@ namespace Avalonia.Controls
                         if (pos < text.Length)
                         if (pos < text.Length)
                         {
                         {
                             --pos;
                             --pos;
-                            if (pos > 0 && Text[pos - 1] == '\r' && Text[pos] == '\n')
+                            if (pos > 0 && text[pos - 1] == '\r' && text[pos] == '\n')
                             {
                             {
                                 --pos;
                                 --pos;
                             }
                             }
@@ -771,6 +771,9 @@ namespace Avalonia.Controls
 
 
         private string GetSelection()
         private string GetSelection()
         {
         {
+            var text = Text;
+            if (string.IsNullOrEmpty(text))
+                return "";
             var selectionStart = SelectionStart;
             var selectionStart = SelectionStart;
             var selectionEnd = SelectionEnd;
             var selectionEnd = SelectionEnd;
             var start = Math.Min(selectionStart, selectionEnd);
             var start = Math.Min(selectionStart, selectionEnd);
@@ -779,7 +782,7 @@ namespace Avalonia.Controls
             {
             {
                 return "";
                 return "";
             }
             }
-            return Text.Substring(start, end - start);
+            return text.Substring(start, end - start);
         }
         }
 
 
         private int GetLine(int caretIndex, IList<FormattedTextLine> lines)
         private int GetLine(int caretIndex, IList<FormattedTextLine> lines)

+ 26 - 15
src/Avalonia.Controls/ToolTip.cs

@@ -105,17 +105,21 @@ namespace Avalonia.Controls
         {
         {
             if (control != null && control.IsVisible && control.GetVisualRoot() != null)
             if (control != null && control.IsVisible && control.GetVisualRoot() != null)
             {
             {
-                if (s_popup != null)
+                var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control);
+                var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
+
+                if (s_popup == null)
                 {
                 {
-                    throw new AvaloniaInternalException("Previous ToolTip not disposed.");
+                    s_popup = new PopupRoot();
+                    s_popup.Content = new ToolTip();
+                }
+                else
+                {
+                    ((ISetLogicalParent)s_popup).SetParent(null);
                 }
                 }
 
 
-                var cp = MouseDevice.Instance?.GetPosition(control);
-                var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
-
-                s_popup = new PopupRoot();
                 ((ISetLogicalParent)s_popup).SetParent(control);
                 ((ISetLogicalParent)s_popup).SetParent(control);
-                s_popup.Content = new ToolTip { Content = GetTip(control) };
+                ((ToolTip)s_popup.Content).Content = GetTip(control);
                 s_popup.Position = position;
                 s_popup.Position = position;
                 s_popup.Show();
                 s_popup.Show();
 
 
@@ -147,16 +151,23 @@ namespace Avalonia.Controls
             {
             {
                 if (s_popup != null)
                 if (s_popup != null)
                 {
                 {
-                    // Clear the ToolTip's Content in case it has control content: this will
-                    // reset its visual parent allowing it to be used again.
-                    ((ToolTip)s_popup.Content).Content = null;
-
-                    // Dispose of the popup.
-                    s_popup.Dispose();
-                    s_popup = null;
+                    DisposeTooltip();
+                    s_show.OnNext(null);
                 }
                 }
+            }
+        }
+
+        private static void DisposeTooltip()
+        {
+            if (s_popup != null)
+            {
+                // Clear the ToolTip's Content in case it has control content: this will
+                // reset its visual parent allowing it to be used again.
+                ((ToolTip)s_popup.Content).Content = null;
 
 
-                s_show.OnNext(null);
+                // Dispose of the popup.
+                s_popup.Dispose();
+                s_popup = null;
             }
             }
         }
         }
     }
     }

+ 23 - 17
src/Avalonia.Controls/TopLevel.cs

@@ -14,6 +14,7 @@ using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
+using JetBrains.Annotations;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls
 {
 {
@@ -92,25 +93,25 @@ namespace Avalonia.Controls
             var rendererFactory = TryGetService<IRendererFactory>(dependencyResolver);
             var rendererFactory = TryGetService<IRendererFactory>(dependencyResolver);
             Renderer = rendererFactory?.CreateRenderer(this, renderLoop);
             Renderer = rendererFactory?.CreateRenderer(this, renderLoop);
 
 
-            PlatformImpl.SetInputRoot(this);
+            impl.SetInputRoot(this);
 
 
-            PlatformImpl.Closed = HandleClosed;
-            PlatformImpl.Input = HandleInput;
-            PlatformImpl.Paint = HandlePaint;
-            PlatformImpl.Resized = HandleResized;
-            PlatformImpl.ScalingChanged = HandleScalingChanged;
+            impl.Closed = HandleClosed;
+            impl.Input = HandleInput;
+            impl.Paint = HandlePaint;
+            impl.Resized = HandleResized;
+            impl.ScalingChanged = HandleScalingChanged;
 
 
 
 
             _keyboardNavigationHandler?.SetOwner(this);
             _keyboardNavigationHandler?.SetOwner(this);
             _accessKeyHandler?.SetOwner(this);
             _accessKeyHandler?.SetOwner(this);
             styler?.ApplyStyles(this);
             styler?.ApplyStyles(this);
 
 
-            ClientSize = PlatformImpl.ClientSize;
+            ClientSize = impl.ClientSize;
             
             
             this.GetObservable(PointerOverElementProperty)
             this.GetObservable(PointerOverElementProperty)
                 .Select(
                 .Select(
                     x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
                     x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
-                .Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor));
+                .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor));
 
 
             if (_applicationLifecycle != null)
             if (_applicationLifecycle != null)
             {
             {
@@ -135,10 +136,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Gets the platform-specific window implementation.
         /// Gets the platform-specific window implementation.
         /// </summary>
         /// </summary>
-        public ITopLevelImpl PlatformImpl
-        {
-            get;
-        }
+        [CanBeNull]
+        public ITopLevelImpl PlatformImpl { get; private set; }
         
         
         /// <summary>
         /// <summary>
         /// Gets the renderer for the window.
         /// Gets the renderer for the window.
@@ -164,6 +163,9 @@ namespace Avalonia.Controls
             set { SetValue(PointerOverElementProperty, value); }
             set { SetValue(PointerOverElementProperty, value); }
         }
         }
 
 
+        /// <inheritdoc/>
+        IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
+
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether access keys are shown in the window.
         /// Gets or sets a value indicating whether access keys are shown in the window.
         /// </summary>
         /// </summary>
@@ -177,7 +179,7 @@ namespace Avalonia.Controls
         Size ILayoutRoot.MaxClientSize => Size.Infinity;
         Size ILayoutRoot.MaxClientSize => Size.Infinity;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling;
+        double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1;
 
 
         IStyleHost IStyleHost.StylingParent
         IStyleHost IStyleHost.StylingParent
         {
         {
@@ -189,25 +191,27 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected virtual IRenderTarget CreateRenderTarget()
         protected virtual IRenderTarget CreateRenderTarget()
         {
         {
+            if(PlatformImpl == null)
+                throw new InvalidOperationException("Cann't create render target, PlatformImpl is null (might be already disposed)");
             return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces);
             return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces);
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         void IRenderRoot.Invalidate(Rect rect)
         void IRenderRoot.Invalidate(Rect rect)
         {
         {
-            PlatformImpl.Invalidate(rect);
+            PlatformImpl?.Invalidate(rect);
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         Point IRenderRoot.PointToClient(Point p)
         Point IRenderRoot.PointToClient(Point p)
         {
         {
-            return PlatformImpl.PointToClient(p);
+            return PlatformImpl?.PointToClient(p) ?? default(Point);
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         Point IRenderRoot.PointToScreen(Point p)
         Point IRenderRoot.PointToScreen(Point p)
         {
         {
-            return PlatformImpl.PointToScreen(p);
+            return PlatformImpl?.PointToScreen(p) ?? default(Point);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -224,6 +228,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         protected virtual void HandleClosed()
         protected virtual void HandleClosed()
         {
         {
+            PlatformImpl = null;
+
             Closed?.Invoke(this, EventArgs.Empty);
             Closed?.Invoke(this, EventArgs.Empty);
             Renderer?.Dispose();
             Renderer?.Dispose();
             Renderer = null;
             Renderer = null;
@@ -250,7 +256,7 @@ namespace Avalonia.Controls
         /// <param name="scaling">The window scaling.</param>
         /// <param name="scaling">The window scaling.</param>
         protected virtual void HandleScalingChanged(double scaling)
         protected virtual void HandleScalingChanged(double scaling)
         {
         {
-            foreach (ILayoutable control in this.GetSelfAndVisualDescendents())
+            foreach (ILayoutable control in this.GetSelfAndVisualDescendants())
             {
             {
                 control.InvalidateMeasure();
                 control.InvalidateMeasure();
             }
             }

+ 21 - 1
src/Avalonia.Controls/TreeView.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Controls
     /// <summary>
     /// <summary>
     /// Displays a hierachical tree of data.
     /// Displays a hierachical tree of data.
     /// </summary>
     /// </summary>
-    public class TreeView : ItemsControl
+    public class TreeView : ItemsControl, ICustomKeyboardNavigation
     {
     {
         /// <summary>
         /// <summary>
         /// Defines the <see cref="AutoScrollToSelectedItem"/> property.
         /// Defines the <see cref="AutoScrollToSelectedItem"/> property.
@@ -90,6 +90,26 @@ namespace Avalonia.Controls
             }
             }
         }
         }
 
 
+        (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, NavigationDirection direction)
+        {
+            if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
+            {
+                if (!this.IsVisualAncestorOf(element))
+                {
+                    IControl result = _selectedItem != null ?
+                        ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) :
+                        ItemContainerGenerator.ContainerFromIndex(0);
+                    return (true, result);
+                }
+                else
+                {
+                    return (true, null);
+                }
+            }
+
+            return (false, null);
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override IItemContainerGenerator CreateItemContainerGenerator()
         protected override IItemContainerGenerator CreateItemContainerGenerator()
         {
         {

+ 30 - 13
src/Avalonia.Controls/Window.cs

@@ -12,6 +12,7 @@ using Avalonia.Platform;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using JetBrains.Annotations;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls
 {
 {
@@ -46,12 +47,12 @@ namespace Avalonia.Controls
     /// </summary>
     /// </summary>
     public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope
     public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope
     {
     {
-        private static IList<Window> s_windows = new List<Window>();
+        private static List<Window> s_windows = new List<Window>();
 
 
         /// <summary>
         /// <summary>
         /// Retrieves an enumeration of all Windows in the currently running application.
         /// Retrieves an enumeration of all Windows in the currently running application.
         /// </summary>
         /// </summary>
-        public static IList<Window> OpenWindows => s_windows;
+        public static IReadOnlyList<Window> OpenWindows => s_windows;
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="SizeToContent"/> property.
         /// Defines the <see cref="SizeToContent"/> property.
@@ -87,11 +88,11 @@ namespace Avalonia.Controls
         static Window()
         static Window()
         {
         {
             BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
             BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
-            TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue));
+            TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue));
             HasSystemDecorationsProperty.Changed.AddClassHandler<Window>(
             HasSystemDecorationsProperty.Changed.AddClassHandler<Window>(
-                (s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue));
+                (s, e) => s.PlatformImpl?.SetSystemDecorations((bool) e.NewValue));
 
 
-            IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
+            IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -109,7 +110,7 @@ namespace Avalonia.Controls
         public Window(IWindowImpl impl)
         public Window(IWindowImpl impl)
             : base(impl)
             : base(impl)
         {
         {
-            _maxPlatformClientSize = this.PlatformImpl.MaxClientSize;
+            _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
@@ -129,6 +130,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Gets the platform-specific window implementation.
         /// Gets the platform-specific window implementation.
         /// </summary>
         /// </summary>
+        [CanBeNull]
         public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl;
         public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl;
 
 
         /// <summary>
         /// <summary>
@@ -164,8 +166,12 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public WindowState WindowState
         public WindowState WindowState
         {
         {
-            get { return this.PlatformImpl.WindowState; }
-            set { this.PlatformImpl.WindowState = value; }
+            get { return PlatformImpl?.WindowState ?? WindowState.Normal; }
+            set
+            {
+                if (PlatformImpl != null)
+                    PlatformImpl.WindowState = value;
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -189,7 +195,7 @@ namespace Avalonia.Controls
         public void Close()
         public void Close()
         {
         {
             s_windows.Remove(this);
             s_windows.Remove(this);
-            PlatformImpl.Dispose();
+            PlatformImpl?.Dispose();
             IsVisible = false;
             IsVisible = false;
         }
         }
 
 
@@ -221,7 +227,7 @@ namespace Avalonia.Controls
         {
         {
             using (BeginAutoSizing())
             using (BeginAutoSizing())
             {
             {
-                PlatformImpl.Hide();
+                PlatformImpl?.Hide();
             }
             }
 
 
             IsVisible = false;
             IsVisible = false;
@@ -232,6 +238,11 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public override void Show()
         public override void Show()
         {
         {
+            if (IsVisible)
+            {
+                return;
+            }
+
             s_windows.Add(this);
             s_windows.Add(this);
 
 
             EnsureInitialized();
             EnsureInitialized();
@@ -240,7 +251,7 @@ namespace Avalonia.Controls
 
 
             using (BeginAutoSizing())
             using (BeginAutoSizing())
             {
             {
-                PlatformImpl.Show();
+                PlatformImpl?.Show();
             }
             }
         }
         }
 
 
@@ -266,6 +277,11 @@ namespace Avalonia.Controls
         /// </returns>
         /// </returns>
         public Task<TResult> ShowDialog<TResult>()
         public Task<TResult> ShowDialog<TResult>()
         {
         {
+            if (IsVisible)
+            {
+                throw new InvalidOperationException("The window is already being shown.");
+            }
+
             s_windows.Add(this);
             s_windows.Add(this);
 
 
             EnsureInitialized();
             EnsureInitialized();
@@ -278,7 +294,7 @@ namespace Avalonia.Controls
                 var activated = affectedWindows.Where(w => w.IsActive).FirstOrDefault();
                 var activated = affectedWindows.Where(w => w.IsActive).FirstOrDefault();
                 SetIsEnabled(affectedWindows, false);
                 SetIsEnabled(affectedWindows, false);
 
 
-                var modal = PlatformImpl.ShowDialog();
+                var modal = PlatformImpl?.ShowDialog();
                 var result = new TaskCompletionSource<TResult>();
                 var result = new TaskCompletionSource<TResult>();
 
 
                 Observable.FromEventPattern<EventHandler, EventArgs>(
                 Observable.FromEventPattern<EventHandler, EventArgs>(
@@ -287,7 +303,7 @@ namespace Avalonia.Controls
                     .Take(1)
                     .Take(1)
                     .Subscribe(_ =>
                     .Subscribe(_ =>
                     {
                     {
-                        modal.Dispose();
+                        modal?.Dispose();
                         SetIsEnabled(affectedWindows, true);
                         SetIsEnabled(affectedWindows, true);
                         activated?.Activate();
                         activated?.Activate();
                         result.SetResult((TResult)_dialogResult);
                         result.SetResult((TResult)_dialogResult);
@@ -354,6 +370,7 @@ namespace Avalonia.Controls
         protected override void HandleClosed()
         protected override void HandleClosed()
         {
         {
             IsVisible = false;
             IsVisible = false;
+            s_windows.Remove(this);
             base.HandleClosed();
             base.HandleClosed();
         }
         }
 
 

+ 28 - 15
src/Avalonia.Controls/WindowBase.cs

@@ -9,6 +9,7 @@ using Avalonia.Controls.Primitives;
 using Avalonia.Input;
 using Avalonia.Input;
 using Avalonia.Layout;
 using Avalonia.Layout;
 using Avalonia.Platform;
 using Avalonia.Platform;
+using JetBrains.Annotations;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls
 {
 {
@@ -28,6 +29,7 @@ namespace Avalonia.Controls
         public static readonly DirectProperty<WindowBase, bool> IsActiveProperty =
         public static readonly DirectProperty<WindowBase, bool> IsActiveProperty =
             AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive);
             AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive);
 
 
+        private bool _hasExecutedInitialLayoutPass;
         private bool _isActive;
         private bool _isActive;
         private bool _ignoreVisibilityChange;
         private bool _ignoreVisibilityChange;
 
 
@@ -43,10 +45,10 @@ namespace Avalonia.Controls
 
 
         public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver)
         public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver)
         {
         {
-            PlatformImpl.Activated = HandleActivated;
-            PlatformImpl.Deactivated = HandleDeactivated;
-            PlatformImpl.PositionChanged = HandlePositionChanged;
-            this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.Resize(x));
+            impl.Activated = HandleActivated;
+            impl.Deactivated = HandleDeactivated;
+            impl.PositionChanged = HandlePositionChanged;
+            this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -64,6 +66,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public event EventHandler<PointEventArgs> PositionChanged;
         public event EventHandler<PointEventArgs> PositionChanged;
 
 
+        [CanBeNull]
         public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl;
         public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl;
 
 
         /// <summary>
         /// <summary>
@@ -80,8 +83,12 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public Point Position
         public Point Position
         {
         {
-            get { return PlatformImpl.Position; }
-            set { PlatformImpl.Position = value; }
+            get { return PlatformImpl?.Position ?? default(Point); }
+            set
+            {
+                if (PlatformImpl is IWindowBaseImpl impl)
+                    impl.Position = value;
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -98,7 +105,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public void Activate()
         public void Activate()
         {
         {
-            PlatformImpl.Activate();
+            PlatformImpl?.Activate();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -110,7 +117,7 @@ namespace Avalonia.Controls
 
 
             try
             try
             {
             {
-                PlatformImpl.Hide();
+                PlatformImpl?.Hide();
                 IsVisible = false;
                 IsVisible = false;
             }
             }
             finally
             finally
@@ -130,8 +137,14 @@ namespace Avalonia.Controls
             {
             {
                 EnsureInitialized();
                 EnsureInitialized();
                 IsVisible = true;
                 IsVisible = true;
-                LayoutManager.Instance.ExecuteInitialLayoutPass(this);
-                PlatformImpl.Show();
+
+                if (!_hasExecutedInitialLayoutPass)
+                {
+                    LayoutManager.Instance.ExecuteInitialLayoutPass(this);
+                    _hasExecutedInitialLayoutPass = true;
+                }
+
+                PlatformImpl?.Show();
             }
             }
             finally
             finally
             {
             {
@@ -163,10 +176,10 @@ namespace Avalonia.Controls
         {
         {
             using (BeginAutoSizing())
             using (BeginAutoSizing())
             {
             {
-                PlatformImpl.Resize(finalSize);
+                PlatformImpl?.Resize(finalSize);
             }
             }
 
 
-            return base.ArrangeOverride(PlatformImpl.ClientSize);
+            return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -174,7 +187,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         protected void EnsureInitialized()
         protected void EnsureInitialized()
         {
         {
-            if (!this.IsInitialized)
+            if (!IsInitialized)
             {
             {
                 var init = (ISupportInitialize)this;
                 var init = (ISupportInitialize)this;
                 init.BeginInit();
                 init.BeginInit();
@@ -268,12 +281,12 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Starts moving a window with left button being held. Should be called from left mouse button press event handler
         /// Starts moving a window with left button being held. Should be called from left mouse button press event handler
         /// </summary>
         /// </summary>
-        public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag();
+        public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag();
 
 
         /// <summary>
         /// <summary>
         /// Starts resizing a window. This function is used if an application has window resizing controls. 
         /// Starts resizing a window. This function is used if an application has window resizing controls. 
         /// Should be called from left mouse button press event handler
         /// Should be called from left mouse button press event handler
         /// </summary>
         /// </summary>
-        public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge);
+        public void BeginResizeDrag(WindowEdge edge) => PlatformImpl?.BeginResizeDrag(edge);
     }
     }
 }
 }

+ 3 - 1
src/Avalonia.DesignerSupport/DesignerAssist.cs

@@ -75,7 +75,7 @@ namespace Avalonia.DesignerSupport
         private static void SetScalingFactor(double factor)
         private static void SetScalingFactor(double factor)
         {
         {
             PlatformManager.SetDesignerScalingFactor(factor);
             PlatformManager.SetDesignerScalingFactor(factor);
-            s_currentWindow?.PlatformImpl.Resize(s_currentWindow.ClientSize);
+            s_currentWindow?.PlatformImpl?.Resize(s_currentWindow.ClientSize);
         }
         }
 
 
         static Window s_currentWindow;
         static Window s_currentWindow;
@@ -149,6 +149,8 @@ namespace Avalonia.DesignerSupport
             s_currentWindow = window;
             s_currentWindow = window;
             window.Show();
             window.Show();
             Design.ApplyDesignerProperties(window, control);
             Design.ApplyDesignerProperties(window, control);
+            // ReSharper disable once PossibleNullReferenceException
+            // Always not null at this point
             Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle);
             Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle);
             Api.OnResize?.Invoke();
             Api.OnResize?.Invoke();
         }
         }

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

@@ -34,7 +34,6 @@
     <ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" />
     <ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" />
     <ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
     <ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
     <ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" />
     <ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" />
-    <ProjectReference Include="..\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
     <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
     <ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
     <ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />

+ 14 - 16
src/Avalonia.Diagnostics/DevTools.xaml.cs

@@ -11,15 +11,14 @@ using Avalonia.Input.Raw;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml;
 using Avalonia.Markup.Xaml;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 
 namespace Avalonia
 namespace Avalonia
 {
 {
-	public static class WindowExtensions
+	public static class DevToolsExtensions
 	{
 	{
-		public static void AttachDevTools(this Window window)
+		public static void AttachDevTools(this TopLevel control)
 		{
 		{
-			Avalonia.Diagnostics.DevTools.Attach(window);
+			Avalonia.Diagnostics.DevTools.Attach(control);
 		}
 		}
 	}
 	}
 }
 }
@@ -28,7 +27,7 @@ namespace Avalonia.Diagnostics
 {
 {
 	public class DevTools : UserControl
 	public class DevTools : UserControl
     {
     {
-        private static Dictionary<Window, Window> s_open = new Dictionary<Window, Window>();
+        private static Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>();
         private IDisposable _keySubscription;
         private IDisposable _keySubscription;
 
 
         public DevTools(IControl root)
         public DevTools(IControl root)
@@ -44,9 +43,9 @@ namespace Avalonia.Diagnostics
 
 
         public IControl Root { get; }
         public IControl Root { get; }
 
 
-        public static IDisposable Attach(Window window)
+        public static IDisposable Attach(TopLevel control)
         {
         {
-            return window.AddHandler(
+            return control.AddHandler(
                 KeyDownEvent,
                 KeyDownEvent,
                 WindowPreviewKeyDown,
                 WindowPreviewKeyDown,
                 RoutingStrategies.Tunnel);
                 RoutingStrategies.Tunnel);
@@ -56,16 +55,16 @@ namespace Avalonia.Diagnostics
         {
         {
             if (e.Key == Key.F12)
             if (e.Key == Key.F12)
             {
             {
-                var window = (Window)sender;
+                var control = (TopLevel)sender;
                 var devToolsWindow = default(Window);
                 var devToolsWindow = default(Window);
 
 
-                if (s_open.TryGetValue(window, out devToolsWindow))
+                if (s_open.TryGetValue(control, out devToolsWindow))
                 {
                 {
                     devToolsWindow.Activate();
                     devToolsWindow.Activate();
                 }
                 }
                 else
                 else
                 {
                 {
-                    var devTools = new DevTools(window);
+                    var devTools = new DevTools(control);
 
 
                     devToolsWindow = new Window
                     devToolsWindow = new Window
                     {
                     {
@@ -74,12 +73,12 @@ namespace Avalonia.Diagnostics
                         Content = devTools,
                         Content = devTools,
                         DataTemplates = new DataTemplates
                         DataTemplates = new DataTemplates
                         {
                         {
-                            new ViewLocator<ReactiveObject>(),
+                            new ViewLocator<ViewModelBase>(),
                         }
                         }
                     };
                     };
 
 
                     devToolsWindow.Closed += devTools.DevToolsClosed;
                     devToolsWindow.Closed += devTools.DevToolsClosed;
-                    s_open.Add((Window)sender, devToolsWindow);
+                    s_open.Add(control, devToolsWindow);
                     devToolsWindow.Show();
                     devToolsWindow.Show();
                 }
                 }
             }
             }
@@ -89,9 +88,7 @@ namespace Avalonia.Diagnostics
         {
         {
             var devToolsWindow = (Window)sender;
             var devToolsWindow = (Window)sender;
             var devTools = (DevTools)devToolsWindow.Content;
             var devTools = (DevTools)devToolsWindow.Content;
-            var window = (Window)devTools.Root;
-
-            s_open.Remove(window);
+            s_open.Remove((TopLevel)devTools.Root);
             _keySubscription.Dispose();
             _keySubscription.Dispose();
             devToolsWindow.Closed -= DevToolsClosed;
             devToolsWindow.Closed -= DevToolsClosed;
         }
         }
@@ -107,7 +104,8 @@ namespace Avalonia.Diagnostics
 
 
             if ((e.Modifiers) == modifiers)
             if ((e.Modifiers) == modifiers)
             {
             {
-                var point = MouseDevice.Instance.GetPosition(Root);
+
+                var point = (Root.VisualRoot as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default(Point);
                 var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible))
                 var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible))
                     .FirstOrDefault();
                     .FirstOrDefault();
 
 

+ 1 - 2
src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs

@@ -4,11 +4,10 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 
 namespace Avalonia.Diagnostics.ViewModels
 namespace Avalonia.Diagnostics.ViewModels
 {
 {
-    internal class ControlDetailsViewModel : ReactiveObject
+    internal class ControlDetailsViewModel : ViewModelBase
     {
     {
         public ControlDetailsViewModel(IVisual control)
         public ControlDetailsViewModel(IVisual control)
         {
         {

+ 43 - 31
src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs

@@ -5,32 +5,50 @@ using System;
 using System.Reactive.Linq;
 using System.Reactive.Linq;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Input;
-using ReactiveUI;
 
 
 namespace Avalonia.Diagnostics.ViewModels
 namespace Avalonia.Diagnostics.ViewModels
 {
 {
-    internal class DevToolsViewModel : ReactiveObject
+    internal class DevToolsViewModel : ViewModelBase
     {
     {
-        private ReactiveObject _content;
-
+        private ViewModelBase _content;
         private int _selectedTab;
         private int _selectedTab;
-
         private TreePageViewModel _logicalTree;
         private TreePageViewModel _logicalTree;
-
         private TreePageViewModel _visualTree;
         private TreePageViewModel _visualTree;
-
-        private readonly ObservableAsPropertyHelper<string> _focusedControl;
-
-        private readonly ObservableAsPropertyHelper<string> _pointerOverElement;
+        private string _focusedControl;
+        private string _pointerOverElement;
 
 
         public DevToolsViewModel(IControl root)
         public DevToolsViewModel(IControl root)
         {
         {
             _logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
             _logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
             _visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
             _visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
 
 
-            this.WhenAnyValue(x => x.SelectedTab).Subscribe(index =>
+            UpdateFocusedControl();
+            KeyboardDevice.Instance.PropertyChanged += (s, e) =>
             {
             {
-                switch (index)
+                if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
+                {
+                    UpdateFocusedControl();
+                }
+            };
+
+            root.GetObservable(TopLevel.PointerOverElementProperty)
+                .Subscribe(x => PointerOverElement = x?.GetType().Name);
+        }
+
+        public ViewModelBase Content
+        {
+            get { return _content; }
+            private set { RaiseAndSetIfChanged(ref _content, value); }
+        }
+
+        public int SelectedTab
+        {
+            get { return _selectedTab; }
+            set
+            {
+                _selectedTab = value;
+
+                switch (value)
                 {
                 {
                     case 0:
                     case 0:
                         Content = _logicalTree;
                         Content = _logicalTree;
@@ -39,34 +57,23 @@ namespace Avalonia.Diagnostics.ViewModels
                         Content = _visualTree;
                         Content = _visualTree;
                         break;
                         break;
                 }
                 }
-            });
 
 
-            _focusedControl = KeyboardDevice.Instance
-                .WhenAnyValue(x => x.FocusedElement)
-                .Select(x => x?.GetType().Name)
-                .ToProperty(this, x => x.FocusedControl);
-
-            _pointerOverElement = root.GetObservable(TopLevel.PointerOverElementProperty)
-                .Select(x => x?.GetType().Name)
-                .ToProperty(this, x => x.PointerOverElement);
+                RaisePropertyChanged();
+            }
         }
         }
 
 
-        public ReactiveObject Content
+        public string FocusedControl
         {
         {
-            get { return _content; }
-            private set { this.RaiseAndSetIfChanged(ref _content, value); }
+            get { return _focusedControl; }
+            private set { RaiseAndSetIfChanged(ref _focusedControl, value); }
         }
         }
 
 
-        public int SelectedTab
+        public string PointerOverElement
         {
         {
-            get { return _selectedTab; }
-            set { this.RaiseAndSetIfChanged(ref _selectedTab, value); }
+            get { return _pointerOverElement; }
+            private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
         }
         }
 
 
-        public string FocusedControl => _focusedControl.Value;
-
-        public string PointerOverElement => _pointerOverElement.Value;
-
         public void SelectControl(IControl control)
         public void SelectControl(IControl control)
         {
         {
             var tree = Content as TreePageViewModel;
             var tree = Content as TreePageViewModel;
@@ -76,5 +83,10 @@ namespace Avalonia.Diagnostics.ViewModels
                 tree.SelectControl(control);
                 tree.SelectControl(control);
             }
             }
         }
         }
+
+        private void UpdateFocusedControl()
+        {
+            _focusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
+        }
     }
     }
 }
 }

+ 2 - 2
src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs

@@ -2,9 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
+using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
-using ReactiveUI;
 
 
 namespace Avalonia.Diagnostics.ViewModels
 namespace Avalonia.Diagnostics.ViewModels
 {
 {
@@ -13,7 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public LogicalTreeNode(ILogical logical, TreeNode parent)
         public LogicalTreeNode(ILogical logical, TreeNode parent)
             : base((Control)logical, parent)
             : base((Control)logical, parent)
         {
         {
-            Children = logical.LogicalChildren.CreateDerivedCollection(x => new LogicalTreeNode(x, this));
+            Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this));
         }
         }
 
 
         public static LogicalTreeNode[] Create(object control)
         public static LogicalTreeNode[] Create(object control)

+ 4 - 7
src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs

@@ -3,16 +3,13 @@
 
 
 using System;
 using System;
 using Avalonia.Data;
 using Avalonia.Data;
-using ReactiveUI;
 
 
 namespace Avalonia.Diagnostics.ViewModels
 namespace Avalonia.Diagnostics.ViewModels
 {
 {
-    internal class PropertyDetails : ReactiveObject
+    internal class PropertyDetails : ViewModelBase
     {
     {
         private object _value;
         private object _value;
-
         private string _priority;
         private string _priority;
-
         private string _diagnostic;
         private string _diagnostic;
 
 
         public PropertyDetails(AvaloniaObject o, AvaloniaProperty property)
         public PropertyDetails(AvaloniaObject o, AvaloniaProperty property)
@@ -41,19 +38,19 @@ namespace Avalonia.Diagnostics.ViewModels
         public string Priority
         public string Priority
         {
         {
             get { return _priority; }
             get { return _priority; }
-            private set { this.RaiseAndSetIfChanged(ref _priority, value); }
+            private set { RaiseAndSetIfChanged(ref _priority, value); }
         }
         }
 
 
         public string Diagnostic
         public string Diagnostic
         {
         {
             get { return _diagnostic; }
             get { return _diagnostic; }
-            private set { this.RaiseAndSetIfChanged(ref _diagnostic, value); }
+            private set { RaiseAndSetIfChanged(ref _diagnostic, value); }
         }
         }
 
 
         public object Value
         public object Value
         {
         {
             get { return _value; }
             get { return _value; }
-            private set { this.RaiseAndSetIfChanged(ref _value, value); }
+            private set { RaiseAndSetIfChanged(ref _value, value); }
         }
         }
     }
     }
 }
 }

+ 5 - 5
src/Avalonia.Diagnostics/ViewModels/TreeNode.cs

@@ -5,13 +5,13 @@ using System;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.Reactive;
 using System.Reactive;
 using System.Reactive.Linq;
 using System.Reactive.Linq;
+using Avalonia.Collections;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 
 namespace Avalonia.Diagnostics.ViewModels
 namespace Avalonia.Diagnostics.ViewModels
 {
 {
-    internal class TreeNode : ReactiveObject
+    internal class TreeNode : ViewModelBase
     {
     {
         private string _classes;
         private string _classes;
         private bool _isExpanded;
         private bool _isExpanded;
@@ -47,7 +47,7 @@ namespace Avalonia.Diagnostics.ViewModels
             }
             }
         }
         }
 
 
-        public IReadOnlyReactiveList<TreeNode> Children
+        public IAvaloniaReadOnlyList<TreeNode> Children
         {
         {
             get;
             get;
             protected set;
             protected set;
@@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public string Classes
         public string Classes
         {
         {
             get { return _classes; }
             get { return _classes; }
-            private set { this.RaiseAndSetIfChanged(ref _classes, value); }
+            private set { RaiseAndSetIfChanged(ref _classes, value); }
         }
         }
 
 
         public IVisual Visual
         public IVisual Visual
@@ -67,7 +67,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public bool IsExpanded
         public bool IsExpanded
         {
         {
             get { return _isExpanded; }
             get { return _isExpanded; }
-            set { this.RaiseAndSetIfChanged(ref _isExpanded, value); }
+            set { RaiseAndSetIfChanged(ref _isExpanded, value); }
         }
         }
 
 
         public TreeNode Parent
         public TreeNode Parent

+ 14 - 10
src/Avalonia.Diagnostics/ViewModels/TreePageViewModel.cs

@@ -1,25 +1,19 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // 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.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
-using System.Reactive.Linq;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 
 namespace Avalonia.Diagnostics.ViewModels
 namespace Avalonia.Diagnostics.ViewModels
 {
 {
-    internal class TreePageViewModel : ReactiveObject
+    internal class TreePageViewModel : ViewModelBase
     {
     {
         private TreeNode _selected;
         private TreeNode _selected;
-
-        private readonly ObservableAsPropertyHelper<ControlDetailsViewModel> _details;
+        private ControlDetailsViewModel _details;
 
 
         public TreePageViewModel(TreeNode[] nodes)
         public TreePageViewModel(TreeNode[] nodes)
         {
         {
             Nodes = nodes;
             Nodes = nodes;
-            _details = this.WhenAnyValue(x => x.SelectedNode)
-                .Select(x => x != null ? new ControlDetailsViewModel(x.Visual) : null)
-                .ToProperty(this, x => x.Details);
         }
         }
 
 
         public TreeNode[] Nodes { get; protected set; }
         public TreeNode[] Nodes { get; protected set; }
@@ -27,10 +21,20 @@ namespace Avalonia.Diagnostics.ViewModels
         public TreeNode SelectedNode
         public TreeNode SelectedNode
         {
         {
             get { return _selected; }
             get { return _selected; }
-            set { this.RaiseAndSetIfChanged(ref _selected, value); }
+            set
+            {
+                if (RaiseAndSetIfChanged(ref _selected, value))
+                {
+                    Details = value != null ? new ControlDetailsViewModel(value.Visual) : null;
+                }
+            }
         }
         }
 
 
-        public ControlDetailsViewModel Details => _details.Value;
+        public ControlDetailsViewModel Details
+        {
+            get { return _details; }
+            private set { RaiseAndSetIfChanged(ref _details, value); }
+        }
 
 
         public TreeNode FindNode(IControl control)
         public TreeNode FindNode(IControl control)
         {
         {

+ 32 - 0
src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+    public class ViewModelBase : INotifyPropertyChanged
+    {
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        [NotifyPropertyChangedInvocator]
+        protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
+        {
+            if (!EqualityComparer<T>.Default.Equals(field, value))
+            {
+                field = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+                return true;
+            }
+
+            return false;
+        }
+
+        [NotifyPropertyChangedInvocator]
+        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 3 - 4
src/Avalonia.Diagnostics/ViewModels/VisualTreeNode.cs

@@ -1,10 +1,9 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // 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.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
-using Avalonia.Controls;
+using Avalonia.Collections;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
-using ReactiveUI;
 
 
 namespace Avalonia.Diagnostics.ViewModels
 namespace Avalonia.Diagnostics.ViewModels
 {
 {
@@ -17,11 +16,11 @@ namespace Avalonia.Diagnostics.ViewModels
 
 
             if (host?.Root == null)
             if (host?.Root == null)
             {
             {
-                Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x, this));
+                Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
             }
             }
             else
             else
             {
             {
-                Children = new ReactiveList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
+                Children = new AvaloniaList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
             }
             }
 
 
             if ((Visual is IStyleable styleable))
             if ((Visual is IStyleable styleable))

+ 10 - 10
src/Avalonia.Diagnostics/Views/ControlDetailsView.cs

@@ -8,7 +8,6 @@ using Avalonia.Controls;
 using Avalonia.Diagnostics.ViewModels;
 using Avalonia.Diagnostics.ViewModels;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Styling;
 using Avalonia.Styling;
-using ReactiveUI;
 
 
 namespace Avalonia.Diagnostics.Views
 namespace Avalonia.Diagnostics.Views
 {
 {
@@ -16,6 +15,7 @@ namespace Avalonia.Diagnostics.Views
     {
     {
         private static readonly StyledProperty<ControlDetailsViewModel> ViewModelProperty =
         private static readonly StyledProperty<ControlDetailsViewModel> ViewModelProperty =
             AvaloniaProperty.Register<ControlDetailsView, ControlDetailsViewModel>("ViewModel");
             AvaloniaProperty.Register<ControlDetailsView, ControlDetailsViewModel>("ViewModel");
+        private SimpleGrid _grid;
 
 
         public ControlDetailsView()
         public ControlDetailsView()
         {
         {
@@ -27,7 +27,11 @@ namespace Avalonia.Diagnostics.Views
         public ControlDetailsViewModel ViewModel
         public ControlDetailsViewModel ViewModel
         {
         {
             get { return GetValue(ViewModelProperty); }
             get { return GetValue(ViewModelProperty); }
-            private set { SetValue(ViewModelProperty, value); }
+            private set
+            {
+                SetValue(ViewModelProperty, value);
+                _grid[GridRepeater.ItemsProperty] = value?.Properties;
+            }
         }
         }
 
 
         private void InitializeComponent()
         private void InitializeComponent()
@@ -36,7 +40,7 @@ namespace Avalonia.Diagnostics.Views
 
 
             Content = new ScrollViewer
             Content = new ScrollViewer
             {
             {
-                Content = new SimpleGrid
+                Content = _grid = new SimpleGrid
                 {
                 {
                     Styles = new Styles
                     Styles = new Styles
                     {
                     {
@@ -49,7 +53,6 @@ namespace Avalonia.Diagnostics.Views
                         },
                         },
                     },
                     },
                     [GridRepeater.TemplateProperty] = pt,
                     [GridRepeater.TemplateProperty] = pt,
-                    [!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties).ToBinding(),
                 }
                 }
             };
             };
         }
         }
@@ -62,16 +65,13 @@ namespace Avalonia.Diagnostics.Views
             {
             {
                 Text = property.Name,
                 Text = property.Name,
                 TextWrapping = TextWrapping.NoWrap,
                 TextWrapping = TextWrapping.NoWrap,
-                [!ToolTip.TipProperty] = property
-                    .WhenAnyValue(x => x.Diagnostic)
-                    .ToBinding(),
+                [!ToolTip.TipProperty] = property.GetObservable<string>(nameof(property.Diagnostic)).ToBinding(),
             };
             };
 
 
             yield return new TextBlock
             yield return new TextBlock
             {
             {
                 TextWrapping = TextWrapping.NoWrap,
                 TextWrapping = TextWrapping.NoWrap,
-                [!TextBlock.TextProperty] = property
-                    .WhenAnyValue(v => v.Value)
+                [!TextBlock.TextProperty] = property.GetObservable<object>(nameof(property.Value))
                     .Select(v => v?.ToString())
                     .Select(v => v?.ToString())
                     .ToBinding(),
                     .ToBinding(),
             };
             };
@@ -79,7 +79,7 @@ namespace Avalonia.Diagnostics.Views
             yield return new TextBlock
             yield return new TextBlock
             {
             {
                 TextWrapping = TextWrapping.NoWrap,
                 TextWrapping = TextWrapping.NoWrap,
-                [!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority).ToBinding(),
+                [!TextBlock.TextProperty] = property.GetObservable<string>((nameof(property.Priority))).ToBinding(),
             };
             };
         }
         }
     }
     }

+ 30 - 0
src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs

@@ -0,0 +1,30 @@
+using System;
+using System.ComponentModel;
+using System.Reactive.Linq;
+using System.Reflection;
+
+namespace Avalonia.Diagnostics.Views
+{
+    internal static class PropertyChangedExtenions
+    {
+        public static IObservable<T> GetObservable<T>(this INotifyPropertyChanged source, string propertyName)
+        {
+            Contract.Requires<ArgumentNullException>(source != null);
+            Contract.Requires<ArgumentNullException>(propertyName != null);
+
+            var property = source.GetType().GetTypeInfo().GetDeclaredProperty(propertyName);
+
+            if (property == null)
+            {
+                throw new ArgumentException($"Property '{propertyName}' not found on '{source}.");
+            }
+
+            return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
+                e => source.PropertyChanged += e,
+                e => source.PropertyChanged -= e)
+                    .Where(e => e.EventArgs.PropertyName == propertyName)
+                    .Select(_ => (T)property.GetValue(source))
+                    .StartWith((T)property.GetValue(source));
+        }
+    }
+}

+ 7 - 1
src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs

@@ -82,7 +82,13 @@ namespace Avalonia
 
 
         private void LoadAssembliesInDirectory()
         private void LoadAssembliesInDirectory()
         {
         {
-            foreach (var file in new FileInfo(Assembly.GetEntryAssembly().Location).Directory.EnumerateFiles("*.dll"))
+            var location = Assembly.GetEntryAssembly().Location;
+            if (string.IsNullOrWhiteSpace(location))
+                return;
+            var dir = new FileInfo(location).Directory;
+            if (dir == null)
+                return;
+            foreach (var file in dir.EnumerateFiles("*.dll"))
             {
             {
                 try
                 try
                 {
                 {

+ 4 - 1
src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs

@@ -10,9 +10,11 @@
 // - Sun Tsu,
 // - Sun Tsu,
 // "The Art of War"
 // "The Art of War"
 
 
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Html;
 using Avalonia.Controls.Html;
 using Avalonia.Input;
 using Avalonia.Input;
+using Avalonia.VisualTree;
 using TheArtOfDev.HtmlRenderer.Adapters;
 using TheArtOfDev.HtmlRenderer.Adapters;
 using TheArtOfDev.HtmlRenderer.Adapters.Entities;
 using TheArtOfDev.HtmlRenderer.Adapters.Entities;
 using TheArtOfDev.HtmlRenderer.Core.Utils;
 using TheArtOfDev.HtmlRenderer.Core.Utils;
@@ -54,7 +56,8 @@ namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
         {
         {
             get
             get
             {
             {
-                return Util.Convert(MouseDevice.Instance.GetPosition(_control));
+                var pos = (_control.GetVisualRoot() as IInputRoot)?.MouseDevice?.Position ?? default(Point);
+                return Util.Convert(pos);
             }
             }
         }
         }
 
 

+ 2 - 1
src/Avalonia.HtmlRenderer/HtmlControl.cs

@@ -17,6 +17,7 @@ using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Threading;
 using Avalonia.Threading;
+using Avalonia.VisualTree;
 using TheArtOfDev.HtmlRenderer.Core;
 using TheArtOfDev.HtmlRenderer.Core;
 using TheArtOfDev.HtmlRenderer.Core.Entities;
 using TheArtOfDev.HtmlRenderer.Core.Entities;
 using TheArtOfDev.HtmlRenderer.Avalonia;
 using TheArtOfDev.HtmlRenderer.Avalonia;
@@ -512,7 +513,7 @@ namespace Avalonia.Controls.Html
         protected virtual void InvokeMouseMove()
         protected virtual void InvokeMouseMove()
         {
         {
 
 
-            _htmlContainer.HandleMouseMove(this, MouseDevice.Instance?.GetPosition(this) ?? default(Point));
+            _htmlContainer.HandleMouseMove(this, (this.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(this) ?? default(Point));
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 3 - 2
src/Avalonia.Input/FocusManager.cs

@@ -176,9 +176,10 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         /// <param name="e">The event args.</param>
         private void OnPreviewPointerPressed(object sender, RoutedEventArgs e)
         private void OnPreviewPointerPressed(object sender, RoutedEventArgs e)
         {
         {
-            if (sender == e.Source)
+            var ev = (PointerPressedEventArgs)e;
+
+            if (sender == e.Source && ev.MouseButton == MouseButton.Left)
             {
             {
-                var ev = (PointerPressedEventArgs)e;
                 var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement);
                 var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement);
 
 
                 if (element == null || !CanFocus(element))
                 if (element == null || !CanFocus(element))

+ 15 - 0
src/Avalonia.Input/ICustomKeyboardNavigation.cs

@@ -0,0 +1,15 @@
+// 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;
+
+namespace Avalonia.Input
+{
+    /// <summary>
+    /// Designates a control as handling its own keyboard navigation.
+    /// </summary>
+    public interface ICustomKeyboardNavigation
+    {
+        (bool handled, IInputElement next) GetNext(IInputElement element, NavigationDirection direction);
+    }
+}

+ 7 - 0
src/Avalonia.Input/IInputDevice.cs

@@ -1,9 +1,16 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // 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.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
+using Avalonia.Input.Raw;
+
 namespace Avalonia.Input
 namespace Avalonia.Input
 {
 {
     public interface IInputDevice
     public interface IInputDevice
     {
     {
+        /// <summary>
+        /// Processes raw event. Is called after preprocessing by InputManager
+        /// </summary>
+        /// <param name="ev"></param>
+        void ProcessRawEvent(RawInputEventArgs ev);
     }
     }
 }
 }

+ 8 - 0
src/Avalonia.Input/IInputRoot.cs

@@ -1,6 +1,8 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // 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.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
+using JetBrains.Annotations;
+
 namespace Avalonia.Input
 namespace Avalonia.Input
 {
 {
     /// <summary>
     /// <summary>
@@ -27,5 +29,11 @@ namespace Avalonia.Input
         /// Gets or sets a value indicating whether access keys are shown in the window.
         /// Gets or sets a value indicating whether access keys are shown in the window.
         /// </summary>
         /// </summary>
         bool ShowAccessKeys { get; set; }
         bool ShowAccessKeys { get; set; }
+
+        /// <summary>
+        /// Gets associated mouse device
+        /// </summary>
+        [CanBeNull]
+        IMouseDevice MouseDevice { get; }
     }
     }
 }
 }

+ 2 - 1
src/Avalonia.Input/IKeyboardDevice.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
+using System.ComponentModel;
 
 
 namespace Avalonia.Input
 namespace Avalonia.Input
 {
 {
@@ -26,7 +27,7 @@ namespace Avalonia.Input
         Toggled = 2,
         Toggled = 2,
     }
     }
 
 
-    public interface IKeyboardDevice : IInputDevice
+    public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
     {
     {
         IInputElement FocusedElement { get; }
         IInputElement FocusedElement { get; }
 
 

+ 1 - 0
src/Avalonia.Input/InputManager.cs

@@ -35,6 +35,7 @@ namespace Avalonia.Input
         public void ProcessInput(RawInputEventArgs e)
         public void ProcessInput(RawInputEventArgs e)
         {
         {
             _preProcess.OnNext(e);
             _preProcess.OnNext(e);
+            e.Device?.ProcessRawEvent(e);
             _process.OnNext(e);
             _process.OnNext(e);
             _postProcess.OnNext(e);
             _postProcess.OnNext(e);
         }
         }

+ 3 - 9
src/Avalonia.Input/KeyboardDevice.cs

@@ -16,14 +16,6 @@ namespace Avalonia.Input
     {
     {
         private IInputElement _focusedElement;
         private IInputElement _focusedElement;
 
 
-        public KeyboardDevice()
-        {
-            InputManager.Process
-                .OfType<RawInputEventArgs>()
-                .Where(e => e.Device == this && !e.Handled)
-                .Subscribe(ProcessRawEvent);
-        }
-
         public event PropertyChangedEventHandler PropertyChanged;
         public event PropertyChangedEventHandler PropertyChanged;
 
 
         public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
         public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
@@ -77,8 +69,10 @@ namespace Avalonia.Input
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
         }
 
 
-        private void ProcessRawEvent(RawInputEventArgs e)
+        public void ProcessRawEvent(RawInputEventArgs e)
         {
         {
+            if(e.Handled)
+                return;
             IInputElement element = FocusedElement;
             IInputElement element = FocusedElement;
 
 
             if (element != null)
             if (element != null)

+ 27 - 0
src/Avalonia.Input/KeyboardNavigationHandler.cs

@@ -2,7 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
+using System.Linq;
 using Avalonia.Input.Navigation;
 using Avalonia.Input.Navigation;
+using Avalonia.VisualTree;
 
 
 namespace Avalonia.Input
 namespace Avalonia.Input
 {
 {
@@ -52,6 +54,31 @@ namespace Avalonia.Input
         {
         {
             Contract.Requires<ArgumentNullException>(element != null);
             Contract.Requires<ArgumentNullException>(element != null);
 
 
+            var customHandler = element.GetSelfAndVisualAncestors()
+                .OfType<ICustomKeyboardNavigation>()
+                .FirstOrDefault();
+
+            if (customHandler != null)
+            {
+                var (handled, next) = customHandler.GetNext(element, direction);
+
+                if (handled)
+                {
+                    if (next != null)
+                    {
+                        return next;
+                    }
+                    else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
+                    {
+                        return TabNavigation.GetNextInTabOrder((IInputElement)customHandler, direction, true);
+                    }
+                    else
+                    {
+                        return null;
+                    }
+                }
+            }
+
             if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
             if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
             {
             {
                 return TabNavigation.GetNextInTabOrder(element, direction);
                 return TabNavigation.GetNextInTabOrder(element, direction);

+ 8 - 23
src/Avalonia.Input/MouseDevice.cs

@@ -20,23 +20,7 @@ namespace Avalonia.Input
         private int _clickCount;
         private int _clickCount;
         private Rect _lastClickRect;
         private Rect _lastClickRect;
         private uint _lastClickTime;
         private uint _lastClickTime;
-
-        /// <summary>
-        /// Intializes a new instance of <see cref="MouseDevice"/>.
-        /// </summary>
-        public MouseDevice()
-        {
-            InputManager.Process
-                .OfType<RawMouseEventArgs>()
-                .Where(e => e.Device == this && !e.Handled)
-                .Subscribe(ProcessRawEvent);
-        }
-
-        /// <summary>
-        /// Gets the current mouse device instance.
-        /// </summary>
-        public static IMouseDevice Instance => AvaloniaLocator.Current.GetService<IMouseDevice>();
-
+       
         /// <summary>
         /// <summary>
         /// Gets the control that is currently capturing by the mouse, if any.
         /// Gets the control that is currently capturing by the mouse, if any.
         /// </summary>
         /// </summary>
@@ -50,12 +34,7 @@ namespace Avalonia.Input
             get;
             get;
             protected set;
             protected set;
         }
         }
-
-        /// <summary>
-        /// Gets the application's input manager.
-        /// </summary>
-        public IInputManager InputManager => AvaloniaLocator.Current.GetService<IInputManager>();
-
+        
         /// <summary>
         /// <summary>
         /// Gets the mouse position, in screen coordinates.
         /// Gets the mouse position, in screen coordinates.
         /// </summary>
         /// </summary>
@@ -102,6 +81,12 @@ namespace Avalonia.Input
             return root.PointToClient(Position) - p;
             return root.PointToClient(Position) - p;
         }
         }
 
 
+        public void ProcessRawEvent(RawInputEventArgs e)
+        {
+            if (!e.Handled && e is RawMouseEventArgs margs)
+                ProcessRawEvent(margs);
+        }
+
         private void ProcessRawEvent(RawMouseEventArgs e)
         private void ProcessRawEvent(RawMouseEventArgs e)
         {
         {
             Contract.Requires<ArgumentNullException>(e != null);
             Contract.Requires<ArgumentNullException>(e != null);

+ 37 - 25
src/Avalonia.Input/Navigation/DirectionalNavigation.cs

@@ -41,10 +41,10 @@ namespace Avalonia.Input.Navigation
                 {
                 {
                     case KeyboardNavigationMode.Continue:
                     case KeyboardNavigationMode.Continue:
                         return GetNextInContainer(element, container, direction) ??
                         return GetNextInContainer(element, container, direction) ??
-                               GetFirstInNextContainer(element, direction);
+                               GetFirstInNextContainer(element, element, direction);
                     case KeyboardNavigationMode.Cycle:
                     case KeyboardNavigationMode.Cycle:
                         return GetNextInContainer(element, container, direction) ??
                         return GetNextInContainer(element, container, direction) ??
-                               GetFocusableDescendent(container, direction);
+                               GetFocusableDescendant(container, direction);
                     case KeyboardNavigationMode.Contained:
                     case KeyboardNavigationMode.Contained:
                         return GetNextInContainer(element, container, direction);
                         return GetNextInContainer(element, container, direction);
                     default:
                     default:
@@ -53,7 +53,7 @@ namespace Avalonia.Input.Navigation
             }
             }
             else
             else
             {
             {
-                return GetFocusableDescendents(element).FirstOrDefault();
+                return GetFocusableDescendants(element).FirstOrDefault();
             }
             }
         }
         }
 
 
@@ -71,24 +71,24 @@ namespace Avalonia.Input.Navigation
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the first or last focusable descendent of the specified element.
+        /// Gets the first or last focusable descendant of the specified element.
         /// </summary>
         /// </summary>
         /// <param name="container">The element.</param>
         /// <param name="container">The element.</param>
         /// <param name="direction">The direction to search.</param>
         /// <param name="direction">The direction to search.</param>
         /// <returns>The element or null if not found.##</returns>
         /// <returns>The element or null if not found.##</returns>
-        private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction)
+        private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
         {
         {
             return IsForward(direction) ?
             return IsForward(direction) ?
-                GetFocusableDescendents(container).FirstOrDefault() :
-                GetFocusableDescendents(container).LastOrDefault();
+                GetFocusableDescendants(container).FirstOrDefault() :
+                GetFocusableDescendants(container).LastOrDefault();
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the focusable descendents of the specified element.
+        /// Gets the focusable descendants of the specified element.
         /// </summary>
         /// </summary>
         /// <param name="element">The element.</param>
         /// <param name="element">The element.</param>
-        /// <returns>The element's focusable descendents.</returns>
-        private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element)
+        /// <returns>The element's focusable descendants.</returns>
+        private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element)
         {
         {
             var children = element.GetVisualChildren().OfType<IInputElement>();
             var children = element.GetVisualChildren().OfType<IInputElement>();
 
 
@@ -99,11 +99,11 @@ namespace Avalonia.Input.Navigation
                     yield return child;
                     yield return child;
                 }
                 }
 
 
-                if (child.CanFocusDescendents())
+                if (child.CanFocusDescendants())
                 {
                 {
-                    foreach (var descendent in GetFocusableDescendents(child))
+                    foreach (var descendant in GetFocusableDescendants(child))
                     {
                     {
-                        yield return descendent;
+                        yield return descendant;
                     }
                     }
                 }
                 }
             }
             }
@@ -123,11 +123,11 @@ namespace Avalonia.Input.Navigation
         {
         {
             if (direction == NavigationDirection.Down)
             if (direction == NavigationDirection.Down)
             {
             {
-                var descendent = GetFocusableDescendents(element).FirstOrDefault();
+                var descendant = GetFocusableDescendants(element).FirstOrDefault();
 
 
-                if (descendent != null)
+                if (descendant != null)
                 {
                 {
-                    return descendent;
+                    return descendant;
                 }
                 }
             }
             }
 
 
@@ -156,11 +156,11 @@ namespace Avalonia.Input.Navigation
 
 
                 if (element != null && direction == NavigationDirection.Up)
                 if (element != null && direction == NavigationDirection.Up)
                 {
                 {
-                    var descendent = GetFocusableDescendents(element).LastOrDefault();
+                    var descendant = GetFocusableDescendants(element).LastOrDefault();
 
 
-                    if (descendent != null)
+                    if (descendant != null)
                     {
                     {
-                        return descendent;
+                        return descendant;
                     }
                     }
                 }
                 }
 
 
@@ -173,10 +173,12 @@ namespace Avalonia.Input.Navigation
         /// <summary>
         /// <summary>
         /// Gets the first item that should be focused in the next container.
         /// Gets the first item that should be focused in the next container.
         /// </summary>
         /// </summary>
+        /// <param name="element">The element being navigated away from.</param>
         /// <param name="container">The container.</param>
         /// <param name="container">The container.</param>
         /// <param name="direction">The direction of the search.</param>
         /// <param name="direction">The direction of the search.</param>
         /// <returns>The first element, or null if there are no more elements.</returns>
         /// <returns>The first element, or null if there are no more elements.</returns>
         private static IInputElement GetFirstInNextContainer(
         private static IInputElement GetFirstInNextContainer(
+            IInputElement element,
             IInputElement container,
             IInputElement container,
             NavigationDirection direction)
             NavigationDirection direction)
         {
         {
@@ -193,13 +195,23 @@ namespace Avalonia.Input.Navigation
 
 
                 var siblings = parent.GetVisualChildren()
                 var siblings = parent.GetVisualChildren()
                     .OfType<IInputElement>()
                     .OfType<IInputElement>()
-                    .Where(FocusExtensions.CanFocusDescendents);
+                    .Where(FocusExtensions.CanFocusDescendants);
                 var sibling = isForward ? 
                 var sibling = isForward ? 
                     siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : 
                     siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : 
                     siblings.TakeWhile(x => x != container).LastOrDefault();
                     siblings.TakeWhile(x => x != container).LastOrDefault();
 
 
                 if (sibling != null)
                 if (sibling != null)
                 {
                 {
+                    if (sibling is ICustomKeyboardNavigation custom)
+                    {
+                        var (handled, customNext) = custom.GetNext(element, direction);
+
+                        if (handled)
+                        {
+                            return customNext;
+                        }
+                    }
+
                     if (sibling.CanFocus())
                     if (sibling.CanFocus())
                     {
                     {
                         next = sibling;
                         next = sibling;
@@ -207,21 +219,21 @@ namespace Avalonia.Input.Navigation
                     else
                     else
                     {
                     {
                         next = isForward ?
                         next = isForward ?
-                            GetFocusableDescendents(sibling).FirstOrDefault() :
-                            GetFocusableDescendents(sibling).LastOrDefault();
+                            GetFocusableDescendants(sibling).FirstOrDefault() :
+                            GetFocusableDescendants(sibling).LastOrDefault();
                     }
                     }
                 }
                 }
 
 
                 if (next == null)
                 if (next == null)
                 {
                 {
-                    next = GetFirstInNextContainer(parent, direction);
+                    next = GetFirstInNextContainer(element, parent, direction);
                 }
                 }
             }
             }
             else
             else
             {
             {
                 next = isForward ?
                 next = isForward ?
-                    GetFocusableDescendents(container).FirstOrDefault() :
-                    GetFocusableDescendents(container).LastOrDefault();
+                    GetFocusableDescendants(container).FirstOrDefault() :
+                    GetFocusableDescendants(container).LastOrDefault();
             }
             }
 
 
             return next;
             return next;

+ 3 - 3
src/Avalonia.Input/Navigation/FocusExtensions.cs

@@ -16,10 +16,10 @@ namespace Avalonia.Input.Navigation
         public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible;
         public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible;
 
 
         /// <summary>
         /// <summary>
-        /// Checks if descendents of the specified element can be focused.
+        /// Checks if descendants of the specified element can be focused.
         /// </summary>
         /// </summary>
         /// <param name="e">The element.</param>
         /// <param name="e">The element.</param>
-        /// <returns>True if descendents of the element can be focused.</returns>
-        public static bool CanFocusDescendents(this IInputElement e) => e.IsEnabledCore && e.IsVisible;
+        /// <returns>True if descendants of the element can be focused.</returns>
+        public static bool CanFocusDescendants(this IInputElement e) => e.IsEnabledCore && e.IsVisible;
     }
     }
 }
 }

+ 72 - 35
src/Avalonia.Input/Navigation/TabNavigation.cs

@@ -18,13 +18,17 @@ namespace Avalonia.Input.Navigation
         /// </summary>
         /// </summary>
         /// <param name="element">The element.</param>
         /// <param name="element">The element.</param>
         /// <param name="direction">The tab direction. Must be Next or Previous.</param>
         /// <param name="direction">The tab direction. Must be Next or Previous.</param>
+        /// <param name="outsideElement">
+        /// If true will not descend into <paramref name="element"/> to find next control.
+        /// </param>
         /// <returns>
         /// <returns>
         /// The next element in the specified direction, or null if <paramref name="element"/>
         /// The next element in the specified direction, or null if <paramref name="element"/>
         /// was the last in the requested direction.
         /// was the last in the requested direction.
         /// </returns>
         /// </returns>
         public static IInputElement GetNextInTabOrder(
         public static IInputElement GetNextInTabOrder(
             IInputElement element,
             IInputElement element,
-            NavigationDirection direction)
+            NavigationDirection direction,
+            bool outsideElement = false)
         {
         {
             Contract.Requires<ArgumentNullException>(element != null);
             Contract.Requires<ArgumentNullException>(element != null);
             Contract.Requires<ArgumentException>(
             Contract.Requires<ArgumentException>(
@@ -40,42 +44,43 @@ namespace Avalonia.Input.Navigation
                 switch (mode)
                 switch (mode)
                 {
                 {
                     case KeyboardNavigationMode.Continue:
                     case KeyboardNavigationMode.Continue:
-                        return GetNextInContainer(element, container, direction) ??
-                               GetFirstInNextContainer(element, direction);
+                        return GetNextInContainer(element, container, direction, outsideElement) ??
+                               GetFirstInNextContainer(element, element, direction);
                     case KeyboardNavigationMode.Cycle:
                     case KeyboardNavigationMode.Cycle:
-                        return GetNextInContainer(element, container, direction) ??
-                               GetFocusableDescendent(container, direction);
+                        return GetNextInContainer(element, container, direction, outsideElement) ??
+                               GetFocusableDescendant(container, direction);
                     case KeyboardNavigationMode.Contained:
                     case KeyboardNavigationMode.Contained:
-                        return GetNextInContainer(element, container, direction);
+                        return GetNextInContainer(element, container, direction, outsideElement);
                     default:
                     default:
-                        return GetFirstInNextContainer(container, direction);
+                        return GetFirstInNextContainer(element, container, direction);
                 }
                 }
             }
             }
             else
             else
             {
             {
-                return GetFocusableDescendents(element).FirstOrDefault();
+                return GetFocusableDescendants(element, direction).FirstOrDefault();
             }
             }
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the first or last focusable descendent of the specified element.
+        /// Gets the first or last focusable descendant of the specified element.
         /// </summary>
         /// </summary>
         /// <param name="container">The element.</param>
         /// <param name="container">The element.</param>
         /// <param name="direction">The direction to search.</param>
         /// <param name="direction">The direction to search.</param>
         /// <returns>The element or null if not found.##</returns>
         /// <returns>The element or null if not found.##</returns>
-        private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction)
+        private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
         {
         {
             return direction == NavigationDirection.Next ?
             return direction == NavigationDirection.Next ?
-                GetFocusableDescendents(container).FirstOrDefault() :
-                GetFocusableDescendents(container).LastOrDefault();
+                GetFocusableDescendants(container, direction).FirstOrDefault() :
+                GetFocusableDescendants(container, direction).LastOrDefault();
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the focusable descendents of the specified element.
+        /// Gets the focusable descendants of the specified element.
         /// </summary>
         /// </summary>
         /// <param name="element">The element.</param>
         /// <param name="element">The element.</param>
-        /// <returns>The element's focusable descendents.</returns>
-        private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element)
+        /// <param name="direction">The tab direction. Must be Next or Previous.</param>
+        /// <returns>The element's focusable descendants.</returns>
+        private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element, NavigationDirection direction)
         {
         {
             var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
             var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
 
 
@@ -103,16 +108,25 @@ namespace Avalonia.Input.Navigation
 
 
             foreach (var child in children)
             foreach (var child in children)
             {
             {
-                if (child.CanFocus())
+                var customNext = GetCustomNext(child, direction);
+
+                if (customNext.handled)
                 {
                 {
-                    yield return child;
+                    yield return customNext.next;
                 }
                 }
-
-                if (child.CanFocusDescendents())
+                else
                 {
                 {
-                    foreach (var descendent in GetFocusableDescendents(child))
+                    if (child.CanFocus())
                     {
                     {
-                        yield return descendent;
+                        yield return child;
+                    }
+
+                    if (child.CanFocusDescendants())
+                    {
+                        foreach (var descendant in GetFocusableDescendants(child, direction))
+                        {
+                            yield return descendant;
+                        }
                     }
                     }
                 }
                 }
             }
             }
@@ -124,19 +138,23 @@ namespace Avalonia.Input.Navigation
         /// <param name="element">The starting element/</param>
         /// <param name="element">The starting element/</param>
         /// <param name="container">The container.</param>
         /// <param name="container">The container.</param>
         /// <param name="direction">The direction.</param>
         /// <param name="direction">The direction.</param>
+        /// <param name="outsideElement">
+        /// If true will not descend into <paramref name="element"/> to find next control.
+        /// </param>
         /// <returns>The next element, or null if the element is the last.</returns>
         /// <returns>The next element, or null if the element is the last.</returns>
         private static IInputElement GetNextInContainer(
         private static IInputElement GetNextInContainer(
             IInputElement element,
             IInputElement element,
             IInputElement container,
             IInputElement container,
-            NavigationDirection direction)
+            NavigationDirection direction,
+            bool outsideElement)
         {
         {
-            if (direction == NavigationDirection.Next)
+            if (direction == NavigationDirection.Next && !outsideElement)
             {
             {
-                var descendent = GetFocusableDescendents(element).FirstOrDefault();
+                var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
 
 
-                if (descendent != null)
+                if (descendant != null)
                 {
                 {
-                    return descendent;
+                    return descendant;
                 }
                 }
             }
             }
 
 
@@ -167,11 +185,11 @@ namespace Avalonia.Input.Navigation
 
 
                 if (element != null && direction == NavigationDirection.Previous)
                 if (element != null && direction == NavigationDirection.Previous)
                 {
                 {
-                    var descendent = GetFocusableDescendents(element).LastOrDefault();
+                    var descendant = GetFocusableDescendants(element, direction).LastOrDefault();
 
 
-                    if (descendent != null)
+                    if (descendant != null)
                     {
                     {
-                        return descendent;
+                        return descendant;
                     }
                     }
                 }
                 }
 
 
@@ -184,10 +202,12 @@ namespace Avalonia.Input.Navigation
         /// <summary>
         /// <summary>
         /// Gets the first item that should be focused in the next container.
         /// Gets the first item that should be focused in the next container.
         /// </summary>
         /// </summary>
+        /// <param name="element">The element being navigated away from.</param>
         /// <param name="container">The container.</param>
         /// <param name="container">The container.</param>
         /// <param name="direction">The direction of the search.</param>
         /// <param name="direction">The direction of the search.</param>
         /// <returns>The first element, or null if there are no more elements.</returns>
         /// <returns>The first element, or null if there are no more elements.</returns>
         private static IInputElement GetFirstInNextContainer(
         private static IInputElement GetFirstInNextContainer(
+            IInputElement element,
             IInputElement container,
             IInputElement container,
             NavigationDirection direction)
             NavigationDirection direction)
         {
         {
@@ -203,13 +223,20 @@ namespace Avalonia.Input.Navigation
 
 
                 var siblings = parent.GetVisualChildren()
                 var siblings = parent.GetVisualChildren()
                     .OfType<IInputElement>()
                     .OfType<IInputElement>()
-                    .Where(FocusExtensions.CanFocusDescendents);
+                    .Where(FocusExtensions.CanFocusDescendants);
                 var sibling = direction == NavigationDirection.Next ? 
                 var sibling = direction == NavigationDirection.Next ? 
                     siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : 
                     siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : 
                     siblings.TakeWhile(x => x != container).LastOrDefault();
                     siblings.TakeWhile(x => x != container).LastOrDefault();
 
 
                 if (sibling != null)
                 if (sibling != null)
                 {
                 {
+                    var customNext = GetCustomNext(sibling, direction);
+
+                    if (customNext.handled)
+                    {
+                        return customNext.next;
+                    }
+
                     if (sibling.CanFocus())
                     if (sibling.CanFocus())
                     {
                     {
                         next = sibling;
                         next = sibling;
@@ -217,24 +244,34 @@ namespace Avalonia.Input.Navigation
                     else
                     else
                     {
                     {
                         next = direction == NavigationDirection.Next ?
                         next = direction == NavigationDirection.Next ?
-                            GetFocusableDescendents(sibling).FirstOrDefault() :
-                            GetFocusableDescendents(sibling).LastOrDefault();
+                            GetFocusableDescendants(sibling, direction).FirstOrDefault() :
+                            GetFocusableDescendants(sibling, direction).LastOrDefault();
                     }
                     }
                 }
                 }
 
 
                 if (next == null)
                 if (next == null)
                 {
                 {
-                    next = GetFirstInNextContainer(parent, direction);
+                    next = GetFirstInNextContainer(element, parent, direction);
                 }
                 }
             }
             }
             else
             else
             {
             {
                 next = direction == NavigationDirection.Next ?
                 next = direction == NavigationDirection.Next ?
-                    GetFocusableDescendents(container).FirstOrDefault() :
-                    GetFocusableDescendents(container).LastOrDefault();
+                    GetFocusableDescendants(container, direction).FirstOrDefault() :
+                    GetFocusableDescendants(container, direction).LastOrDefault();
             }
             }
 
 
             return next;
             return next;
         }
         }
+
+        private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, NavigationDirection direction)
+        {
+            if (element is ICustomKeyboardNavigation custom)
+            {
+                return custom.GetNext(element, direction);
+            }
+
+            return (false, null);
+        }
     }
     }
 }
 }

+ 10 - 0
src/Avalonia.Layout/IEmbeddedLayoutRoot.cs

@@ -0,0 +1,10 @@
+namespace Avalonia.Layout
+{
+    /// <summary>
+    /// A special layout root with enforced size for Arrange pass
+    /// </summary>
+    public interface IEmbeddedLayoutRoot : ILayoutRoot
+    {
+        Size AllocatedSize { get; }
+    }
+}

+ 66 - 33
src/Avalonia.Layout/LayoutManager.cs

@@ -14,8 +14,8 @@ namespace Avalonia.Layout
     /// </summary>
     /// </summary>
     public class LayoutManager : ILayoutManager
     public class LayoutManager : ILayoutManager
     {
     {
-        private readonly HashSet<ILayoutable> _toMeasure = new HashSet<ILayoutable>();
-        private readonly HashSet<ILayoutable> _toArrange = new HashSet<ILayoutable>();
+        private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
+        private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
         private bool _queued;
         private bool _queued;
         private bool _running;
         private bool _running;
 
 
@@ -30,8 +30,18 @@ namespace Avalonia.Layout
             Contract.Requires<ArgumentNullException>(control != null);
             Contract.Requires<ArgumentNullException>(control != null);
             Dispatcher.UIThread.VerifyAccess();
             Dispatcher.UIThread.VerifyAccess();
 
 
-            _toMeasure.Add(control);
-            _toArrange.Add(control);
+            if (!control.IsAttachedToVisualTree)
+            {
+#if DEBUG
+                throw new AvaloniaInternalException(
+                    "LayoutManager.InvalidateMeasure called on a control that is detached from the visual tree.");
+#else
+                return;
+#endif
+            }
+
+            _toMeasure.Enqueue(control);
+            _toArrange.Enqueue(control);
             QueueLayoutPass();
             QueueLayoutPass();
         }
         }
 
 
@@ -41,7 +51,17 @@ namespace Avalonia.Layout
             Contract.Requires<ArgumentNullException>(control != null);
             Contract.Requires<ArgumentNullException>(control != null);
             Dispatcher.UIThread.VerifyAccess();
             Dispatcher.UIThread.VerifyAccess();
 
 
-            _toArrange.Add(control);
+            if (!control.IsAttachedToVisualTree)
+            {
+#if DEBUG
+                throw new AvaloniaInternalException(
+                    "LayoutManager.InvalidateArrange called on a control that is detached from the visual tree.");
+#else
+                return;
+#endif
+            }
+
+            _toArrange.Enqueue(control);
             QueueLayoutPass();
             QueueLayoutPass();
         }
         }
 
 
@@ -108,8 +128,12 @@ namespace Avalonia.Layout
         {
         {
             while (_toMeasure.Count > 0)
             while (_toMeasure.Count > 0)
             {
             {
-                var next = _toMeasure.First();
-                Measure(next);
+                var control = _toMeasure.Dequeue();
+
+                if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
+                {
+                    Measure(control);
+                }
             }
             }
         }
         }
 
 
@@ -117,53 +141,62 @@ namespace Avalonia.Layout
         {
         {
             while (_toArrange.Count > 0 && _toMeasure.Count == 0)
             while (_toArrange.Count > 0 && _toMeasure.Count == 0)
             {
             {
-                var next = _toArrange.First();
-                Arrange(next);
+                var control = _toArrange.Dequeue();
+
+                if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
+                {
+                    Arrange(control);
+                }
             }
             }
         }
         }
 
 
         private void Measure(ILayoutable control)
         private void Measure(ILayoutable control)
         {
         {
-            var root = control as ILayoutRoot;
-            var parent = control.VisualParent as ILayoutable;
-
-            if (root != null)
-            {
-                root.Measure(root.MaxClientSize);
-            }
-            else if (parent != null)
+            // Controls closest to the visual root need to be arranged first. We don't try to store
+            // ordered invalidation lists, instead we traverse the tree upwards, measuring the
+            // controls closest to the root first. This has been shown by benchmarks to be the
+            // fastest and most memory-efficent algorithm.
+            if (control.VisualParent is ILayoutable parent)
             {
             {
                 Measure(parent);
                 Measure(parent);
             }
             }
 
 
-            if (!control.IsMeasureValid)
+            // If the control being measured has IsMeasureValid == true here then its measure was
+            // handed by an ancestor and can be ignored. The measure may have also caused the
+            // control to be removed.
+            if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
             {
             {
-                control.Measure(control.PreviousMeasure.Value);
+                if (control is ILayoutRoot root)
+                {
+                    root.Measure(Size.Infinity);
+                }
+                else
+                {
+                    control.Measure(control.PreviousMeasure.Value);
+                }
             }
             }
-
-            _toMeasure.Remove(control);
         }
         }
 
 
         private void Arrange(ILayoutable control)
         private void Arrange(ILayoutable control)
         {
         {
-            var root = control as ILayoutRoot;
-            var parent = control.VisualParent as ILayoutable;
-
-            if (root != null)
-            {
-                root.Arrange(new Rect(root.DesiredSize));
-            }
-            else if (parent != null)
+            if (control.VisualParent is ILayoutable parent)
             {
             {
                 Arrange(parent);
                 Arrange(parent);
             }
             }
 
 
-            if (control.PreviousArrange.HasValue)
+            if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
             {
             {
-                control.Arrange(control.PreviousArrange.Value);
+                if (control is IEmbeddedLayoutRoot embeddedRoot)
+                    control.Arrange(new Rect(embeddedRoot.AllocatedSize));
+                else if (control is ILayoutRoot root)
+                    control.Arrange(new Rect(root.DesiredSize));
+                else if (control.PreviousArrange != null)
+                {
+                    // Has been observed that PreviousArrange sometimes is null, probably a bug somewhere else.
+                    // Condition observed: control.VisualParent is Scrollbar, control is Border.
+                    control.Arrange(control.PreviousArrange.Value);
+                }
             }
             }
-
-            _toArrange.Remove(control);
         }
         }
 
 
         private void QueueLayoutPass()
         private void QueueLayoutPass()

+ 25 - 9
src/Avalonia.Layout/Layoutable.cs

@@ -367,6 +367,14 @@ namespace Avalonia.Layout
             }
             }
         }
         }
 
 
+
+        /// <summary>
+        /// Called by InvalidateMeasure
+        /// </summary>
+        protected virtual void OnMeasureInvalidated()
+        {
+        }
+
         /// <summary>
         /// <summary>
         /// Invalidates the measurement of the control and queues a new layout pass.
         /// Invalidates the measurement of the control and queues a new layout pass.
         /// </summary>
         /// </summary>
@@ -378,8 +386,13 @@ namespace Avalonia.Layout
 
 
                 IsMeasureValid = false;
                 IsMeasureValid = false;
                 IsArrangeValid = false;
                 IsArrangeValid = false;
-                LayoutManager.Instance?.InvalidateMeasure(this);
-                InvalidateVisual();
+
+                if (((ILayoutable)this).IsAttachedToVisualTree)
+                {
+                    LayoutManager.Instance?.InvalidateMeasure(this);
+                    InvalidateVisual();
+                }
+                OnMeasureInvalidated();
             }
             }
         }
         }
 
 
@@ -393,8 +406,12 @@ namespace Avalonia.Layout
                 Logger.Verbose(LogArea.Layout, this, "Invalidated arrange");
                 Logger.Verbose(LogArea.Layout, this, "Invalidated arrange");
 
 
                 IsArrangeValid = false;
                 IsArrangeValid = false;
-                LayoutManager.Instance?.InvalidateArrange(this);
-                InvalidateVisual();
+
+                if (((ILayoutable)this).IsAttachedToVisualTree)
+                {
+                    LayoutManager.Instance?.InvalidateArrange(this);
+                    InvalidateVisual();
+                }
             }
             }
         }
         }
 
 
@@ -456,10 +473,9 @@ namespace Avalonia.Layout
 
 
                 ApplyTemplate();
                 ApplyTemplate();
 
 
-                var constrained = LayoutHelper
-                    .ApplyLayoutConstraints(this, availableSize)
-                    .Deflate(margin);
-
+                var constrained = LayoutHelper.ApplyLayoutConstraints(
+                    this,
+                    availableSize.Deflate(margin));
                 var measured = MeasureOverride(constrained);
                 var measured = MeasureOverride(constrained);
 
 
                 var width = measured.Width;
                 var width = measured.Width;
@@ -613,7 +629,7 @@ namespace Avalonia.Layout
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
         protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
         {
         {
-            foreach (ILayoutable i in this.GetSelfAndVisualDescendents())
+            foreach (ILayoutable i in this.GetSelfAndVisualDescendants())
             {
             {
                 i.InvalidateMeasure();
                 i.InvalidateMeasure();
             }
             }

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

@@ -50,7 +50,7 @@ namespace Avalonia.Controls
                     return result;
                     return result;
                 }
                 }
 
 
-                visual = (visual as ILogical).LogicalParent as Visual;
+                visual = (visual as ILogical)?.LogicalParent as Visual;
             }
             }
 
 
             return null;
             return null;

+ 10 - 0
src/Avalonia.Styling/LogicalTree/ILogical.cs

@@ -36,6 +36,16 @@ namespace Avalonia.LogicalTree
         /// </summary>
         /// </summary>
         IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; }
         IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; }
 
 
+        /// <summary>
+        /// Notifies the control that it is being attached to a rooted logical tree.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        /// <remarks>
+        /// This method will be called automatically by the framework, you should not need to call
+        /// this method yourself.
+        /// </remarks>
+        void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e);
+
         /// <summary>
         /// <summary>
         /// Notifies the control that it is being detached from a rooted logical tree.
         /// Notifies the control that it is being detached from a rooted logical tree.
         /// </summary>
         /// </summary>

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

@@ -37,15 +37,15 @@ namespace Avalonia.LogicalTree
             return logical.LogicalChildren;
             return logical.LogicalChildren;
         }
         }
 
 
-        public static IEnumerable<ILogical> GetLogicalDescendents(this ILogical logical)
+        public static IEnumerable<ILogical> GetLogicalDescendants(this ILogical logical)
         {
         {
             foreach (ILogical child in logical.LogicalChildren)
             foreach (ILogical child in logical.LogicalChildren)
             {
             {
                 yield return child;
                 yield return child;
 
 
-                foreach (ILogical descendent in child.GetLogicalDescendents())
+                foreach (ILogical descendant in child.GetLogicalDescendants())
                 {
                 {
-                    yield return descendent;
+                    yield return descendant;
                 }
                 }
             }
             }
         }
         }

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

@@ -7,16 +7,16 @@ using Avalonia.LogicalTree;
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
-    internal class DescendentSelector : Selector
+    internal class DescendantSelector : Selector
     {
     {
         private readonly Selector _parent;
         private readonly Selector _parent;
         private string _selectorString;
         private string _selectorString;
 
 
-        public DescendentSelector(Selector parent)
+        public DescendantSelector(Selector parent)
         {
         {
             if (parent == null)
             if (parent == null)
             {
             {
-                throw new InvalidOperationException("Descendent selector must be preceeded by a selector.");
+                throw new InvalidOperationException("Descendant selector must be preceeded by a selector.");
             }
             }
 
 
             _parent = parent;
             _parent = parent;
@@ -41,7 +41,7 @@ namespace Avalonia.Styling
         protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
         protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
         {
         {
             ILogical c = (ILogical)control;
             ILogical c = (ILogical)control;
-            List<IObservable<bool>> descendentMatches = new List<IObservable<bool>>();
+            List<IObservable<bool>> descendantMatches = new List<IObservable<bool>>();
 
 
             while (c != null)
             while (c != null)
             {
             {
@@ -60,14 +60,14 @@ namespace Avalonia.Styling
                     }
                     }
                     else
                     else
                     {
                     {
-                        descendentMatches.Add(match.ObservableResult);
+                        descendantMatches.Add(match.ObservableResult);
                     }
                     }
                 }
                 }
             }
             }
 
 
-            if (descendentMatches.Count > 0)
+            if (descendantMatches.Count > 0)
             {
             {
-                return new SelectorMatch(StyleActivator.Or(descendentMatches));
+                return new SelectorMatch(StyleActivator.Or(descendantMatches));
             }
             }
             else
             else
             {
             {

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

@@ -42,13 +42,13 @@ namespace Avalonia.Styling
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Returns a selector which matches a descendent of a previous selector.
+        /// Returns a selector which matches a descendant of a previous selector.
         /// </summary>
         /// </summary>
         /// <param name="previous">The previous selector.</param>
         /// <param name="previous">The previous selector.</param>
         /// <returns>The selector.</returns>
         /// <returns>The selector.</returns>
-        public static Selector Descendent(this Selector previous)
+        public static Selector Descendant(this Selector previous)
         {
         {
-            return new DescendentSelector(previous);
+            return new DescendantSelector(previous);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 2
src/Avalonia.Styling/Styling/Style.cs

@@ -66,12 +66,12 @@ namespace Avalonia.Styling
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets style's selector.
+        /// Gets or sets the style's selector.
         /// </summary>
         /// </summary>
         public Selector Selector { get; set; }
         public Selector Selector { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets style's setters.
+        /// Gets or sets the style's setters.
         /// </summary>
         /// </summary>
         [Content]
         [Content]
         public IList<ISetter> Setters { get; set; } = new List<ISetter>();
         public IList<ISetter> Setters { get; set; } = new List<ISetter>();

+ 1 - 1
src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs

@@ -27,7 +27,7 @@ namespace Avalonia.Platform
         /// <summary>
         /// <summary>
         /// DPI of underling screen
         /// DPI of underling screen
         /// </summary>
         /// </summary>
-        Size Dpi { get; }
+        Vector Dpi { get; }
         
         
         /// <summary>
         /// <summary>
         /// Pixel format
         /// Pixel format

+ 1 - 1
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@@ -164,7 +164,7 @@ namespace Avalonia.Rendering
 
 
         private static void ClearTransformedBounds(IVisual visual)
         private static void ClearTransformedBounds(IVisual visual)
         {
         {
-            foreach (var e in visual.GetSelfAndVisualDescendents())
+            foreach (var e in visual.GetSelfAndVisualDescendants())
             {
             {
                 BoundsTracker.SetTransformedBounds((Visual)visual, null);
                 BoundsTracker.SetTransformedBounds((Visual)visual, null);
             }
             }

+ 1 - 1
src/Avalonia.Visuals/Rendering/ZIndexComparer.cs

@@ -8,6 +8,6 @@ namespace Avalonia.Rendering
     {
     {
         public static readonly ZIndexComparer Instance = new ZIndexComparer();
         public static readonly ZIndexComparer Instance = new ZIndexComparer();
 
 
-        public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex);
+        public int Compare(IVisual x, IVisual y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0);
     }
     }
 }
 }

+ 11 - 2
src/Avalonia.Visuals/Vector.cs

@@ -52,8 +52,6 @@ namespace Avalonia
             return new Point(a._x, a._y);
             return new Point(a._x, a._y);
         }
         }
 
 
-        
-
         /// <summary>
         /// <summary>
         /// Calculates the dot product of two vectors
         /// Calculates the dot product of two vectors
         /// </summary>
         /// </summary>
@@ -65,6 +63,17 @@ namespace Avalonia
             return a.X*b.X + a.Y*b.Y;
             return a.X*b.X + a.Y*b.Y;
         }
         }
 
 
+        /// <summary>
+        /// Scales a vector.
+        /// </summary>
+        /// <param name="vector">The vector</param>
+        /// <param name="scale">The scaling factor.</param>
+        /// <returns>The scaled vector.</returns>
+        public static Vector operator *(Vector vector, double scale)
+        {
+            return new Vector(vector._x * scale, vector._y * scale);
+        }
+
         /// <summary>
         /// <summary>
         /// Length of the vector
         /// Length of the vector
         /// </summary>
         /// </summary>

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

@@ -314,7 +314,7 @@ namespace Avalonia
 
 
         /// <summary>
         /// <summary>
         /// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method 
         /// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method 
-        /// for this control and all of its visual descendents.
+        /// for this control and all of its visual descendants.
         /// </summary>
         /// </summary>
         /// <param name="e">The event args.</param>
         /// <param name="e">The event args.</param>
         protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
         protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
@@ -342,7 +342,7 @@ namespace Avalonia
 
 
         /// <summary>
         /// <summary>
         /// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method 
         /// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method 
-        /// for this control and all of its visual descendents.
+        /// for this control and all of its visual descendants.
         /// </summary>
         /// </summary>
         /// <param name="e">The event args.</param>
         /// <param name="e">The event args.</param>
         protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
         protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
@@ -422,7 +422,7 @@ namespace Avalonia
 
 
                 if (visual == null)
                 if (visual == null)
                 {
                 {
-                    throw new ArgumentException("'visual' is not a descendent of 'ancestor'.");
+                    throw new ArgumentException("'visual' is not a descendant of 'ancestor'.");
                 }
                 }
             }
             }
 
 

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.