1
0
Эх сурвалжийг харах

Merge branch 'master' into readonly-struct

Jeremy Koritzinsky 8 жил өмнө
parent
commit
ee0310e0bf
100 өөрчлөгдсөн 1321 нэмэгдсэн , 522 устгасан
  1. 2 0
      .gitignore
  2. 1 2
      .ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
  3. 28 50
      build.cake
  4. 2 2
      parameters.cake
  5. 1 1
      samples/BindingTest/App.config
  6. 1 1
      samples/BindingTest/BindingTest.csproj
  7. 1 1
      samples/ControlCatalog.Desktop/App.config
  8. 1 1
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  9. 1 1
      samples/RenderTest/App.config
  10. 1 1
      samples/RenderTest/RenderTest.csproj
  11. 1 1
      samples/VirtualizationTest/App.config
  12. 1 1
      samples/VirtualizationTest/VirtualizationTest.csproj
  13. 1 0
      scripts/ReplaceNugetCache.ps1
  14. 2 0
      scripts/ReplaceNugetCache.sh
  15. 2 1
      scripts/ReplaceNugetCacheRelease.ps1
  16. 2 5
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  17. 1 1
      src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
  18. 2 2
      src/Avalonia.Animation/Avalonia.Animation.csproj
  19. 2 2
      src/Avalonia.Base/Avalonia.Base.csproj
  20. 121 30
      src/Avalonia.Base/AvaloniaObject.cs
  21. 4 20
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  22. 7 0
      src/Avalonia.Base/IAvaloniaObject.cs
  23. 32 14
      src/Avalonia.Base/PriorityBindingEntry.cs
  24. 42 21
      src/Avalonia.Base/PriorityValue.cs
  25. 0 16
      src/Avalonia.Base/Reactive/AnonymousSubject`1.cs
  26. 0 49
      src/Avalonia.Base/Reactive/AnonymousSubject`2.cs
  27. 156 0
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  28. 20 0
      src/Avalonia.Controls/AppBuilderBase.cs
  29. 9 0
      src/Avalonia.Controls/Application.cs
  30. 2 2
      src/Avalonia.Controls/Avalonia.Controls.csproj
  31. 1 0
      src/Avalonia.Controls/ContentControl.cs
  32. 9 5
      src/Avalonia.Controls/Control.cs
  33. 2 2
      src/Avalonia.Controls/IPanel.cs
  34. 3 29
      src/Avalonia.Controls/Panel.cs
  35. 4 11
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  36. 20 11
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  37. 3 1
      src/Avalonia.Controls/Primitives/RangeBase.cs
  38. 44 26
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  39. 10 2
      src/Avalonia.Controls/Primitives/Track.cs
  40. 8 0
      src/Avalonia.Controls/TextBox.cs
  41. 2 2
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  42. 1 1
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  43. 2 2
      src/Avalonia.Input/Avalonia.Input.csproj
  44. 16 1
      src/Avalonia.Input/KeyGesture.cs
  45. 2 2
      src/Avalonia.Interactivity/Avalonia.Interactivity.csproj
  46. 2 2
      src/Avalonia.Layout/Avalonia.Layout.csproj
  47. 2 2
      src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj
  48. 2 2
      src/Avalonia.Styling/Avalonia.Styling.csproj
  49. 2 2
      src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
  50. 1 1
      src/Avalonia.Themes.Default/Button.xaml
  51. 1 1
      src/Avalonia.Themes.Default/CalendarButton.xaml
  52. 1 1
      src/Avalonia.Themes.Default/CalendarDayButton.xaml
  53. 1 1
      src/Avalonia.Themes.Default/CheckBox.xaml
  54. 1 1
      src/Avalonia.Themes.Default/ContentControl.xaml
  55. 1 1
      src/Avalonia.Themes.Default/DropDownItem.xaml
  56. 1 1
      src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
  57. 4 4
      src/Avalonia.Themes.Default/Expander.xaml
  58. 1 1
      src/Avalonia.Themes.Default/LayoutTransformControl.xaml
  59. 1 1
      src/Avalonia.Themes.Default/ListBoxItem.xaml
  60. 1 1
      src/Avalonia.Themes.Default/PopupRoot.xaml
  61. 1 1
      src/Avalonia.Themes.Default/RadioButton.xaml
  62. 1 1
      src/Avalonia.Themes.Default/RepeatButton.xaml
  63. 1 1
      src/Avalonia.Themes.Default/TabStripItem.xaml
  64. 1 1
      src/Avalonia.Themes.Default/ToggleButton.xaml
  65. 1 1
      src/Avalonia.Themes.Default/ToolTip.xaml
  66. 1 1
      src/Avalonia.Themes.Default/Window.xaml
  67. 30 20
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  68. 1 1
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  69. 11 6
      src/Avalonia.Visuals/Rendering/RendererBase.cs
  70. 4 2
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  71. 12 3
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  72. 28 2
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  73. 2 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  74. 13 1
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs
  75. 1 1
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  76. 24 0
      src/Markup/Avalonia.Markup.Xaml/XamlLoadException.cs
  77. 2 2
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  78. 2 2
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  79. 4 3
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  80. 5 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  81. 2 2
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  82. 3 3
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  83. 1 1
      tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
  84. 127 12
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  85. 120 9
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  86. 12 0
      tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
  87. 4 4
      tests/Avalonia.Benchmarks/App.config
  88. 2 1
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  89. 43 0
      tests/Avalonia.Benchmarks/Base/Properties.cs
  90. 5 1
      tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs
  91. 1 1
      tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
  92. 58 0
      tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
  93. 17 1
      tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs
  94. 2 2
      tests/Avalonia.Controls.UnitTests/DockPanelTests.cs
  95. 1 1
      tests/Avalonia.Controls.UnitTests/DropDownTests.cs
  96. 42 42
      tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
  97. 1 1
      tests/Avalonia.Controls.UnitTests/GridTests.cs
  98. 70 1
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  99. 0 45
      tests/Avalonia.Controls.UnitTests/PanelTests.cs
  100. 70 1
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

+ 2 - 0
.gitignore

@@ -176,3 +176,5 @@ nuget
 Avalonia.XBuild.sln
 Avalonia.XBuild.sln
 project.lock.json
 project.lock.json
 .idea/*
 .idea/*
+**/obj-Skia/*
+**/obj-Direct2D1/*

+ 1 - 2
.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject

@@ -1,7 +1,6 @@
 <ProjectConfiguration>
 <ProjectConfiguration>
   <Settings>
   <Settings>
-    <DefaultTestTimeout>1000</DefaultTestTimeout>
-    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+    <DefaultTestTimeout>3000</DefaultTestTimeout>
     <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
     <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
   </Settings>
   </Settings>
 </ProjectConfiguration>
 </ProjectConfiguration>

+ 28 - 50
build.cake

@@ -93,7 +93,7 @@ Task("Clean")
     CleanDirectory(parameters.NugetRoot);
     CleanDirectory(parameters.NugetRoot);
     CleanDirectory(parameters.ZipRoot);
     CleanDirectory(parameters.ZipRoot);
     CleanDirectory(parameters.BinRoot);
     CleanDirectory(parameters.BinRoot);
-    CleanDirectory(parameters.TestsRoot);
+    CleanDirectory(parameters.DesignerTestsRoot);
 });
 });
 
 
 Task("Restore-NuGet-Packages")
 Task("Restore-NuGet-Packages")
@@ -124,14 +124,9 @@ Task("Restore-NuGet-Packages")
 
 
 void DotNetCoreBuild()
 void DotNetCoreBuild()
 {
 {
-    DotNetCoreRestore("samples\\ControlCatalog.NetCore");
-    DotNetBuild("samples\\ControlCatalog.NetCore");
+    DotNetCoreBuild("samples\\ControlCatalog.NetCore");
 }
 }
 
 
-Task("DotNetCoreBuild")
-    .IsDependentOn("Clean")
-    .Does(() => DotNetCoreBuild());
-
 Task("Build")
 Task("Build")
     .IsDependentOn("Restore-NuGet-Packages")
     .IsDependentOn("Restore-NuGet-Packages")
     .Does(() =>
     .Does(() =>
@@ -140,11 +135,11 @@ Task("Build")
     {
     {
         MSBuild(parameters.MSBuildSolution, settings => {
         MSBuild(parameters.MSBuildSolution, settings => {
             settings.SetConfiguration(parameters.Configuration);
             settings.SetConfiguration(parameters.Configuration);
+            settings.SetVerbosity(Verbosity.Minimal);
             settings.WithProperty("Platform", "\"" + parameters.Platform + "\"");
             settings.WithProperty("Platform", "\"" + parameters.Platform + "\"");
             settings.WithProperty("UseRoslynPathHack", "true");
             settings.WithProperty("UseRoslynPathHack", "true");
-            settings.SetVerbosity(Verbosity.Minimal);
-            settings.WithProperty("Windows", "True");
             settings.UseToolVersion(MSBuildToolVersion.VS2017);
             settings.UseToolVersion(MSBuildToolVersion.VS2017);
+            settings.WithProperty("Windows", "True");
             settings.SetNodeReuse(false);
             settings.SetNodeReuse(false);
         });
         });
     }
     }
@@ -160,10 +155,9 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
     if(!project.EndsWith(".csproj"))
     if(!project.EndsWith(".csproj"))
         project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj");
         project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj");
     Information("Running tests from " + project);
     Information("Running tests from " + project);
-    DotNetCoreRestore(project);
     var frameworks = new List<string>(){"netcoreapp2.0"};
     var frameworks = new List<string>(){"netcoreapp2.0"};
     if(parameters.IsRunningOnWindows)
     if(parameters.IsRunningOnWindows)
-        frameworks.Add("net461");
+        frameworks.Add("net47");
     foreach(var fw in frameworks)
     foreach(var fw in frameworks)
     {
     {
         if(!fw.StartsWith("netcoreapp") && coreOnly)
         if(!fw.StartsWith("netcoreapp") && coreOnly)
@@ -178,8 +172,11 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
     }
     }
 }
 }
 
 
-Task("Run-Net-Core-Unit-Tests")
-    .IsDependentOn("Clean")
+Task("Run-Unit-Tests")
+    .IsDependentOn("Build")
+    .IsDependentOn("Run-Designer-Unit-Tests")
+    .IsDependentOn("Run-Render-Tests")
+    .WithCriteria(() => !parameters.SkipTests)
     .Does(() => {
     .Does(() => {
         RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
         RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
         RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
         RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
@@ -190,27 +187,25 @@ Task("Run-Net-Core-Unit-Tests")
         RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
         RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
         RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
         RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
         RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
         RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
-        if(parameters.IsRunningOnWindows)
-            RunCoreTest("./tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true);
+        if (parameters.IsRunningOnWindows)
+        {
+            RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", parameters, true);
+        }
     });
     });
 
 
-Task("Run-Unit-Tests")
-    .IsDependentOn("Run-Net-Core-Unit-Tests")
+Task("Run-Render-Tests")
     .IsDependentOn("Build")
     .IsDependentOn("Build")
-    //.IsDependentOn("Run-Leak-Tests")
-    .WithCriteria(() => !parameters.SkipTests)
+    .WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows)
+    .Does(() => {
+        RunCoreTest("./tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true);
+        RunCoreTest("./tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", parameters, true);
+    });
+
+Task("Run-Designer-Unit-Tests")
+    .IsDependentOn("Build")
+    .WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows)
     .Does(() =>
     .Does(() =>
 {
 {
-    if(!parameters.IsRunningOnWindows)
-       return;
-
-    var unitTests = GetDirectories("./tests/Avalonia.*.UnitTests")
-        .Select(dir => System.IO.Path.GetFileName(dir.FullPath))
-        .Where( name => !name.Contains("Skia")) // Run in the Run-Net-Core-Unit-Tests target
-        .Where(name => parameters.IsRunningOnWindows ? true : !name.Contains("Direct2D"))
-        .Select(name => MakeAbsolute(File("./tests/" + name + "/bin/" + parameters.DirSuffix + "/" + name + ".dll")))
-        .ToList();
-
     var toolPath = (parameters.IsPlatformAnyCPU || parameters.IsPlatformX86) ? 
     var toolPath = (parameters.IsPlatformAnyCPU || parameters.IsPlatformX86) ? 
         Context.Tools.Resolve("xunit.console.x86.exe") :
         Context.Tools.Resolve("xunit.console.x86.exe") :
         Context.Tools.Resolve("xunit.console.exe");
         Context.Tools.Resolve("xunit.console.exe");
@@ -219,27 +214,10 @@ Task("Run-Unit-Tests")
     { 
     { 
         ToolPath = toolPath,
         ToolPath = toolPath,
         Parallelism = ParallelismOption.None,
         Parallelism = ParallelismOption.None,
-        ShadowCopy = false
+        ShadowCopy = false,
     };
     };
 
 
-    xUnitSettings.NoAppDomain = !parameters.IsRunningOnWindows;
-
-    foreach(var test in unitTests.Where(testFile => FileExists(testFile)))
-    {
-        CopyDirectory(test.GetDirectory(), parameters.TestsRoot);
-    }
-
-    var testsInDirectoryToRun = new List<FilePath>();
-    if(parameters.IsRunningOnWindows)
-    {
-        testsInDirectoryToRun.AddRange(GetFiles("./artifacts/tests/*Tests.dll"));
-    }
-    else
-    {
-        testsInDirectoryToRun.AddRange(GetFiles("./artifacts/tests/*.UnitTests.dll"));
-    }
-
-    XUnit2(testsInDirectoryToRun, xUnitSettings);
+    XUnit2("./artifacts/designer-tests/Avalonia.DesignerSupport.Tests.dll", xUnitSettings);
 });
 });
 
 
 Task("Copy-Files")
 Task("Copy-Files")
@@ -427,7 +405,7 @@ Task("Default").Does(() =>
     if(parameters.IsRunningOnWindows)
     if(parameters.IsRunningOnWindows)
         RunTarget("Package");
         RunTarget("Package");
     else
     else
-        RunTarget("Run-Net-Core-Unit-Tests");
+        RunTarget("Run-Unit-Tests");
 });
 });
 Task("AppVeyor")
 Task("AppVeyor")
   .IsDependentOn("Zip-Files")
   .IsDependentOn("Zip-Files")
@@ -435,7 +413,7 @@ Task("AppVeyor")
   .IsDependentOn("Publish-NuGet");
   .IsDependentOn("Publish-NuGet");
 
 
 Task("Travis")
 Task("Travis")
-  .IsDependentOn("Run-Net-Core-Unit-Tests");
+  .IsDependentOn("Run-Unit-Tests");
 
 
 ///////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
 // EXECUTE
 // EXECUTE

+ 2 - 2
parameters.cake

@@ -30,7 +30,7 @@ public class Parameters
     public DirectoryPath NugetRoot { get; private set; }
     public DirectoryPath NugetRoot { get; private set; }
     public DirectoryPath ZipRoot { get; private set; }
     public DirectoryPath ZipRoot { get; private set; }
     public DirectoryPath BinRoot { get; private set; }
     public DirectoryPath BinRoot { get; private set; }
-    public DirectoryPath TestsRoot { get; private set; }
+    public DirectoryPath DesignerTestsRoot { get; private set; }
     public string DirSuffix { get; private set; }
     public string DirSuffix { get; private set; }
     public string DirSuffixIOS { get; private set; }
     public string DirSuffixIOS { get; private set; }
     public DirectoryPathCollection BuildDirs { get; private set; }
     public DirectoryPathCollection BuildDirs { get; private set; }
@@ -106,7 +106,7 @@ public class Parameters
         NugetRoot = ArtifactsDir.Combine("nuget");
         NugetRoot = ArtifactsDir.Combine("nuget");
         ZipRoot = ArtifactsDir.Combine("zip");
         ZipRoot = ArtifactsDir.Combine("zip");
         BinRoot = ArtifactsDir.Combine("bin");
         BinRoot = ArtifactsDir.Combine("bin");
-        TestsRoot = ArtifactsDir.Combine("tests");
+        DesignerTestsRoot = ArtifactsDir.Combine("designer-tests");
 
 
         BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
         BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
 
 

+ 1 - 1
samples/BindingTest/App.config

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
 <configuration>
     <startup> 
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
     </startup>
     </startup>
   <runtime>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

+ 1 - 1
samples/BindingTest/BindingTest.csproj

@@ -9,7 +9,7 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>BindingTest</RootNamespace>
     <RootNamespace>BindingTest</RootNamespace>
     <AssemblyName>BindingTest</AssemblyName>
     <AssemblyName>BindingTest</AssemblyName>
-    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <TargetFrameworkProfile />
     <TargetFrameworkProfile />

+ 1 - 1
samples/ControlCatalog.Desktop/App.config

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
 <configuration>
     <startup> 
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
     </startup>
     </startup>
   <runtime>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

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

@@ -9,7 +9,7 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>ControlCatalog.Desktop</RootNamespace>
     <RootNamespace>ControlCatalog.Desktop</RootNamespace>
     <AssemblyName>ControlCatalog.Desktop</AssemblyName>
     <AssemblyName>ControlCatalog.Desktop</AssemblyName>
-    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <TargetFrameworkProfile />
     <TargetFrameworkProfile />

+ 1 - 1
samples/RenderTest/App.config

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
 <configuration>
     <startup> 
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
     </startup>
     </startup>
   <runtime>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

+ 1 - 1
samples/RenderTest/RenderTest.csproj

@@ -9,7 +9,7 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>RenderTest</RootNamespace>
     <RootNamespace>RenderTest</RootNamespace>
     <AssemblyName>RenderTest</AssemblyName>
     <AssemblyName>RenderTest</AssemblyName>
-    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <TargetFrameworkProfile />
     <TargetFrameworkProfile />

+ 1 - 1
samples/VirtualizationTest/App.config

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
 <configuration>
     <startup> 
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
     </startup>
     </startup>
   <runtime>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

+ 1 - 1
samples/VirtualizationTest/VirtualizationTest.csproj

@@ -9,7 +9,7 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>VirtualizationTest</RootNamespace>
     <RootNamespace>VirtualizationTest</RootNamespace>
     <AssemblyName>VirtualizationTest</AssemblyName>
     <AssemblyName>VirtualizationTest</AssemblyName>
-    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <TargetFrameworkProfile />
     <TargetFrameworkProfile />

+ 1 - 0
scripts/ReplaceNugetCache.ps1

@@ -2,3 +2,4 @@ copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\
 copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
 copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
 copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
 copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
 copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
 copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\

+ 2 - 0
scripts/ReplaceNugetCache.sh

@@ -3,4 +3,6 @@
  cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp2.0/
  cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp2.0/
  cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/
  cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/
  cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard2.0/
  cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard2.0/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.skia/$1/lib/netstandard2.0/
+ 
  
  

+ 2 - 1
scripts/ReplaceNugetCacheRelease.ps1

@@ -1,4 +1,5 @@
 copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
 copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
 copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
 copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
 copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
 copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
-copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
+copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
+copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\

+ 2 - 5
src/Android/Avalonia.Android/Resources/Resource.Designer.cs

@@ -40,14 +40,11 @@ namespace Avalonia.Android
 		public partial class String
 		public partial class String
 		{
 		{
 			
 			
-			// aapt resource value: 0x7f020002
-			public static int ApplicationName = 2130837506;
-			
 			// aapt resource value: 0x7f020001
 			// aapt resource value: 0x7f020001
-			public static int Hello = 2130837505;
+			public static int ApplicationName = 2130837505;
 			
 			
 			// aapt resource value: 0x7f020000
 			// aapt resource value: 0x7f020000
-			public static int library_name = 2130837504;
+			public static int Hello = 2130837504;
 			
 			
 			static String()
 			static String()
 			{
 			{

+ 1 - 1
src/Android/Avalonia.AndroidTestApplication/MainActivity.cs

@@ -56,7 +56,7 @@ namespace Avalonia.AndroidTestApplication
                 {
                 {
                     Margin = new Thickness(30),
                     Margin = new Thickness(30),
                     Background = Brushes.Yellow,
                     Background = Brushes.Yellow,
-                    Children = new Avalonia.Controls.Controls
+                    Children =
                     {
                     {
                         new TextBlock
                         new TextBlock
                         {
                         {

+ 2 - 2
src/Avalonia.Animation/Avalonia.Animation.csproj

@@ -11,7 +11,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Animation.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Animation.xml</DocumentationFile>
     <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -21,7 +21,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Animation.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Animation.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>

+ 2 - 2
src/Avalonia.Base/Avalonia.Base.csproj

@@ -12,7 +12,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Base.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Base.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -22,7 +22,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Base.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Base.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>

+ 121 - 30
src/Avalonia.Base/AvaloniaObject.cs

@@ -51,6 +51,21 @@ namespace Avalonia
         /// </summary>
         /// </summary>
         private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
         private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
 
 
+        private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
+
+        /// <summary>
+        /// Delayed setter helper for direct properties. Used to fix #855.
+        /// </summary>
+        private DeferredSetter<AvaloniaProperty, object> DirectPropertyDeferredSetter
+        {
+            get
+            {
+                return _directDeferredSetter ??
+                    (_directDeferredSetter = new DeferredSetter<AvaloniaProperty, object>());
+            }
+        }
+
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="AvaloniaObject"/> class.
         /// Initializes a new instance of the <see cref="AvaloniaObject"/> class.
         /// </summary>
         /// </summary>
@@ -225,6 +240,19 @@ namespace Avalonia
             return (T)GetValue((AvaloniaProperty)property);
             return (T)GetValue((AvaloniaProperty)property);
         }
         }
 
 
+        /// <summary>
+        /// Checks whether a <see cref="AvaloniaProperty"/> is animating.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <returns>True if the property is animating, otherwise false.</returns>
+        public bool IsAnimating(AvaloniaProperty property)
+        {
+            Contract.Requires<ArgumentNullException>(property != null);
+            VerifyAccess();
+
+            return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false;
+        }
+
         /// <summary>
         /// <summary>
         /// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
         /// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
         /// </summary>
         /// </summary>
@@ -311,9 +339,6 @@ namespace Avalonia
 
 
             var description = GetDescription(source);
             var description = GetDescription(source);
 
 
-            var scheduler = AvaloniaLocator.Current.GetService<IScheduler>() ?? ImmediateScheduler.Instance;
-            source = source.ObserveOn(scheduler); 
-
             if (property.IsDirect)
             if (property.IsDirect)
             {
             {
                 if (property.IsReadOnly)
                 if (property.IsReadOnly)
@@ -539,6 +564,45 @@ namespace Avalonia
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// A callback type for encapsulating complex logic for setting direct properties.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="value">The value to which to set the property.</param>
+        /// <param name="field">The backing field for the property.</param>
+        /// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
+        protected delegate void SetAndRaiseCallback<T>(T value, ref T field, Action<Action> notifyWrapper);
+
+        /// <summary>
+        /// Sets the backing field for a direct avalonia property, raising the 
+        /// <see cref="PropertyChanged"/> event if the value has changed.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="field">The backing field.</param>
+        /// <param name="setterCallback">A callback called to actually set the value to the backing field.</param>
+        /// <param name="value">The value.</param>
+        /// <returns>
+        /// True if the value changed, otherwise false.
+        /// </returns>
+        protected bool SetAndRaise<T>(
+            AvaloniaProperty<T> property,
+            ref T field,
+            SetAndRaiseCallback<T> setterCallback,
+            T value)
+        {
+            Contract.Requires<ArgumentNullException>(setterCallback != null);
+            return DirectPropertyDeferredSetter.SetAndNotify(
+                property,
+                ref field,
+                (object val, ref T backing, Action<Action> notify) =>
+                {
+                    setterCallback((T)val, ref backing, notify);
+                    return true;
+                },
+                value);
+        }
+
         /// <summary>
         /// <summary>
         /// Sets the backing field for a direct avalonia property, raising the 
         /// Sets the backing field for a direct avalonia property, raising the 
         /// <see cref="PropertyChanged"/> event if the value has changed.
         /// <see cref="PropertyChanged"/> event if the value has changed.
@@ -553,17 +617,32 @@ namespace Avalonia
         protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value)
         protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value)
         {
         {
             VerifyAccess();
             VerifyAccess();
-            if (!object.Equals(field, value))
-            {
-                var old = field;
-                field = value;
-                RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
-                return true;
-            }
-            else
-            {
-                return false;
-            }
+            return SetAndRaise(
+                property,
+                ref field,
+                (T val, ref T backing, Action<Action> notifyWrapper)
+                    => SetAndRaiseCore(property, ref backing, val, notifyWrapper),
+                value);
+        }
+
+        /// <summary>
+        /// Default assignment logic for SetAndRaise.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="field">The backing field.</param>
+        /// <param name="value">The value.</param>
+        /// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
+        /// <returns>
+        /// True if the value changed, otherwise false.
+        /// </returns>
+        private bool SetAndRaiseCore<T>(AvaloniaProperty property, ref T field, T value, Action<Action> notifyWrapper)
+        {
+            var old = field;
+            field = value;
+
+            notifyWrapper(() => RaisePropertyChanged(property, old, value, BindingPriority.LocalValue));
+            return true;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -661,29 +740,41 @@ namespace Avalonia
         /// <param name="value">The value.</param>
         /// <param name="value">The value.</param>
         private void SetDirectValue(AvaloniaProperty property, object value)
         private void SetDirectValue(AvaloniaProperty property, object value)
         {
         {
-            var notification = value as BindingNotification;
-
-            if (notification != null)
+            void Set()
             {
             {
-                notification.LogIfError(this, property);
-                value = notification.Value;
-            }
+                var notification = value as BindingNotification;
 
 
-            if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue)
-            {
-                var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
-                var accessor = (IDirectPropertyAccessor)GetRegistered(property);
-                var finalValue = value == AvaloniaProperty.UnsetValue ? 
-                    metadata.UnsetValue : value;
+                if (notification != null)
+                {
+                    notification.LogIfError(this, property);
+                    value = notification.Value;
+                }
 
 
-                LogPropertySet(property, value, BindingPriority.LocalValue);
+                if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue)
+                {
+                    var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
+                    var accessor = (IDirectPropertyAccessor)GetRegistered(property);
+                    var finalValue = value == AvaloniaProperty.UnsetValue ?
+                        metadata.UnsetValue : value;
 
 
-                accessor.SetValue(this, finalValue);
+                    LogPropertySet(property, value, BindingPriority.LocalValue);
+
+                    accessor.SetValue(this, finalValue);
+                }
+
+                if (notification != null)
+                {
+                    UpdateDataValidation(property, notification);
+                }
             }
             }
 
 
-            if (notification != null)
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                Set();
+            }
+            else
             {
             {
-                UpdateDataValidation(property, notification);
+                Dispatcher.UIThread.InvokeAsync(Set);
             }
             }
         }
         }
 
 

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

@@ -138,17 +138,9 @@ namespace Avalonia
             AvaloniaProperty property,
             AvaloniaProperty property,
             BindingPriority priority = BindingPriority.LocalValue)
             BindingPriority priority = BindingPriority.LocalValue)
         {
         {
-            // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the 
-            // AnonymousSubject classes and use Subject.Create<T>.
-            var output = new Subject<object>();
-            var result = new AnonymousSubject<object>(
-                Observer.Create<object>(
-                    x => output.OnNext(x),
-                    e => output.OnError(e),
-                    () => output.OnCompleted()),
+            return Subject.Create<object>(
+                Observer.Create<object>(x => o.SetValue(property, x, priority)),
                 o.GetObservable(property));
                 o.GetObservable(property));
-            o.Bind(property, output, priority);
-            return result;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -169,17 +161,9 @@ namespace Avalonia
             AvaloniaProperty<T> property,
             AvaloniaProperty<T> property,
             BindingPriority priority = BindingPriority.LocalValue)
             BindingPriority priority = BindingPriority.LocalValue)
         {
         {
-            // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the 
-            // AnonymousSubject classes from this file and use Subject.Create<T>.
-            var output = new Subject<T>();
-            var result = new AnonymousSubject<T>(
-                Observer.Create<T>(
-                    x => output.OnNext(x),
-                    e => output.OnError(e),
-                    () => output.OnCompleted()),
+            return Subject.Create<T>(
+                Observer.Create<T>(x => o.SetValue(property, x, priority)),
                 o.GetObservable(property));
                 o.GetObservable(property));
-            o.Bind(property, output, priority);
-            return result;
         }
         }
 
 
         /// <summary>
         /// <summary>

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

@@ -31,6 +31,13 @@ namespace Avalonia
         /// <returns>The value.</returns>
         /// <returns>The value.</returns>
         T GetValue<T>(AvaloniaProperty<T> property);
         T GetValue<T>(AvaloniaProperty<T> property);
 
 
+        /// <summary>
+        /// Checks whether a <see cref="AvaloniaProperty"/> is animating.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <returns>True if the property is animating, otherwise false.</returns>
+        bool IsAnimating(AvaloniaProperty property);
+
         /// <summary>
         /// <summary>
         /// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
         /// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
         /// </summary>
         /// </summary>

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

@@ -3,6 +3,7 @@
 
 
 using System;
 using System;
 using Avalonia.Data;
 using Avalonia.Data;
+using Avalonia.Threading;
 
 
 namespace Avalonia
 namespace Avalonia
 {
 {
@@ -92,33 +93,50 @@ namespace Avalonia
 
 
         private void ValueChanged(object value)
         private void ValueChanged(object value)
         {
         {
-            _owner.Owner.Owner?.VerifyAccess();
-
-            var notification = value as BindingNotification;
-
-            if (notification != null)
+            void Signal()
             {
             {
-                if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
+                var notification = value as BindingNotification;
+
+                if (notification != null)
                 {
                 {
-                    Value = notification.Value;
-                    _owner.Changed(this);
+                    if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
+                    {
+                        Value = notification.Value;
+                        _owner.Changed(this);
+                    }
+
+                    if (notification.ErrorType != BindingErrorType.None)
+                    {
+                        _owner.Error(this, notification);
+                    }
                 }
                 }
-
-                if (notification.ErrorType != BindingErrorType.None)
+                else
                 {
                 {
-                    _owner.Error(this, notification);
+                    Value = value;
+                    _owner.Changed(this);
                 }
                 }
             }
             }
+
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                Signal();
+            }
             else
             else
             {
             {
-                Value = value;
-                _owner.Changed(this);
+                Dispatcher.UIThread.InvokeAsync(Signal);
             }
             }
         }
         }
 
 
         private void Completed()
         private void Completed()
         {
         {
-            _owner.Completed(this);
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                _owner.Completed(this);
+            }
+            else
+            {
+                Dispatcher.UIThread.InvokeAsync(() => _owner.Completed(this));
+            }
         }
         }
     }
     }
 }
 }

+ 42 - 21
src/Avalonia.Base/PriorityValue.cs

@@ -28,8 +28,10 @@ namespace Avalonia
     {
     {
         private readonly Type _valueType;
         private readonly Type _valueType;
         private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
         private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
-        private object _value;
+
         private readonly Func<object, object> _validate;
         private readonly Func<object, object> _validate;
+        private static readonly DeferredSetter<PriorityValue, (object value, int priority)> delayedSetter = new DeferredSetter<PriorityValue, (object, int)>();
+        private (object value, int priority) _value;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="PriorityValue"/> class.
         /// Initializes a new instance of the <see cref="PriorityValue"/> class.
@@ -47,11 +49,22 @@ namespace Avalonia
             Owner = owner;
             Owner = owner;
             Property = property;
             Property = property;
             _valueType = valueType;
             _valueType = valueType;
-            _value = AvaloniaProperty.UnsetValue;
-            ValuePriority = int.MaxValue;
+            _value = (AvaloniaProperty.UnsetValue, int.MaxValue);
             _validate = validate;
             _validate = validate;
         }
         }
 
 
+        /// <summary>
+        /// Gets a value indicating whether the property is animating.
+        /// </summary>
+        public bool IsAnimating
+        {
+            get
+            {
+                return ValuePriority <= (int)BindingPriority.Animation && 
+                    GetLevel(ValuePriority).ActiveBindingIndex != -1;
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the owner of the value.
         /// Gets the owner of the value.
         /// </summary>
         /// </summary>
@@ -65,16 +78,12 @@ namespace Avalonia
         /// <summary>
         /// <summary>
         /// Gets the current value.
         /// Gets the current value.
         /// </summary>
         /// </summary>
-        public object Value => _value;
+        public object Value => _value.value;
 
 
         /// <summary>
         /// <summary>
         /// Gets the priority of the binding that is currently active.
         /// Gets the priority of the binding that is currently active.
         /// </summary>
         /// </summary>
-        public int ValuePriority
-        {
-            get;
-            private set;
-        }
+        public int ValuePriority => _value.priority;
 
 
         /// <summary>
         /// <summary>
         /// Adds a new binding.
         /// Adds a new binding.
@@ -234,25 +243,36 @@ namespace Avalonia
         /// <param name="priority">The priority level that the value came from.</param>
         /// <param name="priority">The priority level that the value came from.</param>
         private void UpdateValue(object value, int priority)
         private void UpdateValue(object value, int priority)
         {
         {
-            var notification = value as BindingNotification;
+            delayedSetter.SetAndNotify(this,
+                ref _value,
+                UpdateCore,
+                (value, priority));
+        }
+
+        private bool UpdateCore(
+            (object value, int priority) update,
+            ref (object value, int priority) backing,
+            Action<Action> notify)
+        {
+            var val = update.value;
+            var notification = val as BindingNotification;
             object castValue;
             object castValue;
 
 
             if (notification != null)
             if (notification != null)
             {
             {
-                value = (notification.HasValue) ? notification.Value : null;
+                val = (notification.HasValue) ? notification.Value : null;
             }
             }
 
 
-            if (TypeUtilities.TryConvertImplicit(_valueType, value, out castValue))
+            if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue))
             {
             {
-                var old = _value;
+                var old = backing.value;
 
 
                 if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
                 if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
                 {
                 {
                     castValue = _validate(castValue);
                     castValue = _validate(castValue);
                 }
                 }
 
 
-                ValuePriority = priority;
-                _value = castValue;
+                backing = (castValue, update.priority);
 
 
                 if (notification?.HasValue == true)
                 if (notification?.HasValue == true)
                 {
                 {
@@ -261,7 +281,7 @@ namespace Avalonia
 
 
                 if (notification == null || notification.HasValue)
                 if (notification == null || notification.HasValue)
                 {
                 {
-                    Owner?.Changed(this, old, _value);
+                    notify(() => Owner?.Changed(this, old, Value));
                 }
                 }
 
 
                 if (notification != null)
                 if (notification != null)
@@ -272,14 +292,15 @@ namespace Avalonia
             else
             else
             {
             {
                 Logger.Error(
                 Logger.Error(
-                    LogArea.Binding, 
+                    LogArea.Binding,
                     Owner,
                     Owner,
                     "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
                     "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
-                    Property.Name, 
-                    _valueType, 
-                    value,
-                    value?.GetType());
+                    Property.Name,
+                    _valueType,
+                    val,
+                    val?.GetType());
             }
             }
+            return true;
         }
         }
     }
     }
 }
 }

+ 0 - 16
src/Avalonia.Base/Reactive/AnonymousSubject`1.cs

@@ -1,16 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive.Subjects;
-
-namespace Avalonia.Reactive
-{
-    public class AnonymousSubject<T> : AnonymousSubject<T, T>, ISubject<T>
-    {
-        public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
-            : base(observer, observable)
-        {
-        }
-    }
-}

+ 0 - 49
src/Avalonia.Base/Reactive/AnonymousSubject`2.cs

@@ -1,49 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive.Subjects;
-
-namespace Avalonia.Reactive
-{
-    public class AnonymousSubject<T, U> : ISubject<T, U>
-    {
-        private readonly IObserver<T> _observer;
-        private readonly IObservable<U> _observable;
-
-        public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
-        {
-            _observer = observer;
-            _observable = observable;
-        }
-
-        public void OnCompleted()
-        {
-            _observer.OnCompleted();
-        }
-
-        public void OnError(Exception error)
-        {
-            if (error == null)
-                throw new ArgumentNullException("error");
-
-            _observer.OnError(error);
-        }
-
-        public void OnNext(T value)
-        {
-            _observer.OnNext(value);
-        }
-
-        public IDisposable Subscribe(IObserver<U> observer)
-        {
-            if (observer == null)
-                throw new ArgumentNullException("observer");
-
-            //
-            // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
-            //
-            return _observable.Subscribe/*Unsafe*/(observer);
-        }
-    }
-}

+ 156 - 0
src/Avalonia.Base/Utilities/DeferredSetter.cs

@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Avalonia.Utilities
+{
+    /// <summary>
+    /// A utility class to enable deferring assignment until after property-changed notifications are sent.
+    /// </summary>
+    /// <typeparam name="TProperty">The type of the object that represents the property.</typeparam>
+    /// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
+    class DeferredSetter<TProperty, TSetRecord>
+        where TProperty: class
+    {
+        private struct NotifyDisposable : IDisposable
+        {
+            private readonly SettingStatus status;
+
+            internal NotifyDisposable(SettingStatus status)
+            {
+                this.status = status;
+                status.Notifying = true;
+            }
+
+            public void Dispose()
+            {
+                status.Notifying = false;
+            }
+        }
+
+        /// <summary>
+        /// Information on current setting/notification status of a property.
+        /// </summary>
+        private class SettingStatus
+        {
+            public bool Notifying { get; set; }
+
+            private Queue<TSetRecord> pendingValues;
+            
+            public Queue<TSetRecord> PendingValues
+            {
+                get
+                {
+                    return pendingValues ?? (pendingValues = new Queue<TSetRecord>());
+                }
+            }
+        }
+
+        private readonly ConditionalWeakTable<TProperty, SettingStatus> setRecords = new ConditionalWeakTable<TProperty, SettingStatus>();
+
+        /// <summary>
+        /// Mark the property as currently notifying.
+        /// </summary>
+        /// <param name="property">The property to mark as notifying.</param>
+        /// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns>
+        private NotifyDisposable MarkNotifying(TProperty property)
+        {
+            Contract.Requires<InvalidOperationException>(!IsNotifying(property));
+            
+            return new NotifyDisposable(setRecords.GetOrCreateValue(property));
+        }
+
+        /// <summary>
+        /// Check if the property is currently notifying listeners.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <returns>If the property is currently notifying listeners.</returns>
+        private bool IsNotifying(TProperty property)
+            => setRecords.TryGetValue(property, out var value) && value.Notifying;
+
+        /// <summary>
+        /// Add a pending assignment for the property.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <param name="value">The value to assign.</param>
+        private void AddPendingSet(TProperty property, TSetRecord value)
+        {
+            Contract.Requires<InvalidOperationException>(IsNotifying(property));
+
+            setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value);
+        }
+
+        /// <summary>
+        /// Checks if there are any pending assignments for the property.
+        /// </summary>
+        /// <param name="property">The property to check.</param>
+        /// <returns>If the property has any pending assignments.</returns>
+        private bool HasPendingSet(TProperty property)
+        {
+            return setRecords.TryGetValue(property, out var status) && status.PendingValues.Count != 0;
+        }
+
+        /// <summary>
+        /// Gets the first pending assignment for the property.
+        /// </summary>
+        /// <param name="property">The property to check.</param>
+        /// <returns>The first pending assignment for the property.</returns>
+        private TSetRecord GetFirstPendingSet(TProperty property)
+        {
+            return setRecords.GetOrCreateValue(property).PendingValues.Dequeue();
+        }
+
+        public delegate bool SetterDelegate<TValue>(TSetRecord record, ref TValue backing, Action<Action> notifyCallback);
+
+        /// <summary>
+        /// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824
+        /// </summary>
+        /// <param name="property">The property to set.</param>
+        /// <param name="backing">The backing field for the property</param>
+        /// <param name="setterCallback">
+        /// A callback that actually sets the property.
+        /// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification.
+        /// </param>
+        /// <param name="value">The value to try to set.</param>
+        public bool SetAndNotify<TValue>(
+            TProperty property,
+            ref TValue backing,
+            SetterDelegate<TValue> setterCallback,
+            TSetRecord value)
+        {
+            Contract.Requires<ArgumentNullException>(setterCallback != null);
+            if (!IsNotifying(property))
+            {
+                bool updated = false;
+                if (!object.Equals(value, backing))
+                {
+                    updated = setterCallback(value, ref backing, notification =>
+                    {
+                        using (MarkNotifying(property))
+                        {
+                            notification();
+                        }
+                    });
+                }
+                while (HasPendingSet(property))
+                {
+                    updated |= setterCallback(GetFirstPendingSet(property), ref backing, notification =>
+                    {
+                        using (MarkNotifying(property))
+                        {
+                            notification();
+                        }
+                    });
+                }
+                return updated;
+            }
+            else if(!object.Equals(value, backing))
+            {
+                AddPendingSet(property, value);
+            }
+            return false;
+        }
+    }
+}

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

@@ -14,6 +14,8 @@ namespace Avalonia.Controls
     /// <typeparam name="TAppBuilder">The type of the AppBuilder class itself.</typeparam>
     /// <typeparam name="TAppBuilder">The type of the AppBuilder class itself.</typeparam>
     public abstract class AppBuilderBase<TAppBuilder> where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
     public abstract class AppBuilderBase<TAppBuilder> where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
     {
     {
+        private static bool s_setupWasAlreadyCalled;
+        
         /// <summary>
         /// <summary>
         /// Gets or sets the <see cref="IRuntimePlatform"/> instance.
         /// Gets or sets the <see cref="IRuntimePlatform"/> instance.
         /// </summary>
         /// </summary>
@@ -207,6 +209,17 @@ namespace Avalonia.Controls
 
 
         public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules());
         public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules());
 
 
+        private bool CheckSetup { get; set; } = true;
+
+        /// <summary>
+        /// Set this AppBuilder to ignore the setup check. Used for testing purposes.
+        /// </summary>
+        internal TAppBuilder IgnoreSetupCheck()
+        {
+            CheckSetup = false;
+            return Self;
+        }
+
         private void SetupAvaloniaModules()
         private void SetupAvaloniaModules()
         {
         {
             var moduleInitializers = from assembly in AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies()
             var moduleInitializers = from assembly in AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies()
@@ -252,6 +265,13 @@ namespace Avalonia.Controls
                 throw new InvalidOperationException("No rendering system configured.");
                 throw new InvalidOperationException("No rendering system configured.");
             }
             }
 
 
+            if (s_setupWasAlreadyCalled && CheckSetup)
+            {
+                throw new InvalidOperationException("Setup was already called on one of AppBuilder instances");
+            }
+
+            s_setupWasAlreadyCalled = true;
+
             Instance.RegisterServices();
             Instance.RegisterServices();
             RuntimePlatformServicesInitializer();
             RuntimePlatformServicesInitializer();
             WindowingSubsystemInitializer();
             WindowingSubsystemInitializer();

+ 9 - 0
src/Avalonia.Controls/Application.cs

@@ -175,6 +175,15 @@ namespace Avalonia
             closable.Closed += (s, e) => source.Cancel();
             closable.Closed += (s, e) => source.Cancel();
             Dispatcher.UIThread.MainLoop(source.Token);
             Dispatcher.UIThread.MainLoop(source.Token);
         }
         }
+        
+        /// <summary>
+        /// Runs the application's main loop until the <see cref="CancellationToken"/> is cancelled.
+        /// </summary>
+        /// <param name="token">The token to track</param>
+        public void Run(CancellationToken token)
+        {
+            Dispatcher.UIThread.MainLoop(token);
+        }
 
 
         /// <summary>
         /// <summary>
         /// Exits the application
         /// Exits the application

+ 2 - 2
src/Avalonia.Controls/Avalonia.Controls.csproj

@@ -11,7 +11,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Controls.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Controls.xml</DocumentationFile>
     <NoWarn>CS1591;CS0067</NoWarn>
     <NoWarn>CS1591;CS0067</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -21,7 +21,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Controls.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Controls.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>

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

@@ -51,6 +51,7 @@ namespace Avalonia.Controls
         /// Gets or sets the content to display.
         /// Gets or sets the content to display.
         /// </summary>
         /// </summary>
         [Content]
         [Content]
+        [DependsOn(nameof(ContentTemplate))]
         public object Content
         public object Content
         {
         {
             get { return GetValue(ContentProperty); }
             get { return GetValue(ContentProperty); }

+ 9 - 5
src/Avalonia.Controls/Control.cs

@@ -487,11 +487,6 @@ namespace Avalonia.Controls
         void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
         void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
         {
         {
             ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
             ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
-
-            foreach (var child in LogicalChildren)
-            {
-                child.NotifyResourcesChanged(e);
-            }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
@@ -536,6 +531,15 @@ namespace Avalonia.Controls
                 }
                 }
 
 
                 _parent = (IControl)parent;
                 _parent = (IControl)parent;
+
+                if (old != null)
+                {
+                    old.ResourcesChanged -= ThisResourcesChanged; 
+                }
+                if (_parent != null)
+                {
+                    _parent.ResourcesChanged += ThisResourcesChanged; 
+                }
                 ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
                 ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
 
 
                 if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
                 if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)

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

@@ -9,8 +9,8 @@ namespace Avalonia.Controls
     public interface IPanel : IControl
     public interface IPanel : IControl
     {
     {
         /// <summary>
         /// <summary>
-        /// Gets or sets the children of the <see cref="Panel"/>.
+        /// Gets the children of the <see cref="Panel"/>.
         /// </summary>
         /// </summary>
-        Controls Children { get; set; }
+        Controls Children { get; }
     }
     }
 }
 }

+ 3 - 29
src/Avalonia.Controls/Panel.cs

@@ -25,8 +25,6 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<IBrush> BackgroundProperty =
         public static readonly StyledProperty<IBrush> BackgroundProperty =
             Border.BackgroundProperty.AddOwner<Panel>();
             Border.BackgroundProperty.AddOwner<Panel>();
 
 
-        private readonly Controls _children = new Controls();
-
         /// <summary>
         /// <summary>
         /// Initializes static members of the <see cref="Panel"/> class.
         /// Initializes static members of the <see cref="Panel"/> class.
         /// </summary>
         /// </summary>
@@ -40,38 +38,14 @@ namespace Avalonia.Controls
         /// </summary>
         /// </summary>
         public Panel()
         public Panel()
         {
         {
-            _children.CollectionChanged += ChildrenChanged;
+            Children.CollectionChanged += ChildrenChanged;
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the children of the <see cref="Panel"/>.
+        /// Gets the children of the <see cref="Panel"/>.
         /// </summary>
         /// </summary>
-        /// <remarks>
-        /// Even though this property can be set, the setter is only intended for use in object
-        /// initializers. Assigning to this property does not change the underlying collection,
-        /// it simply clears the existing collection and adds the contents of the assigned
-        /// collection.
-        /// </remarks>
         [Content]
         [Content]
-        public Controls Children
-        {
-            get
-            {
-                return _children;
-            }
-
-            set
-            {
-                Contract.Requires<ArgumentNullException>(value != null);
-
-                if (_children != value)
-                {
-                    VisualChildren.Clear();
-                    _children.Clear();
-                    _children.AddRange(value);
-                }
-            }
-        }
+        public Controls Children { get; } = new Controls();
 
 
         /// <summary>
         /// <summary>
         /// Gets or Sets Panel background brush.
         /// Gets or Sets Panel background brush.

+ 4 - 11
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.Metadata;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 
 
 namespace Avalonia.Controls.Presenters
 namespace Avalonia.Controls.Presenters
@@ -139,6 +140,7 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// <summary>
         /// Gets or sets the content to be displayed by the presenter.
         /// Gets or sets the content to be displayed by the presenter.
         /// </summary>
         /// </summary>
+        [DependsOn(nameof(ContentTemplate))]
         public object Content
         public object Content
         {
         {
             get { return GetValue(ContentProperty); }
             get { return GetValue(ContentProperty); }
@@ -255,18 +257,9 @@ namespace Avalonia.Controls.Presenters
                     LogicalChildren.Remove(oldChild);
                     LogicalChildren.Remove(oldChild);
                 }
                 }
 
 
-                if (newChild.Parent == null)
+                if (newChild.Parent == null && TemplatedParent == null)
                 {
                 {
-                    var templatedLogicalParent = TemplatedParent as ILogical;
-
-                    if (templatedLogicalParent != null)
-                    {
-                        ((ISetLogicalParent)newChild).SetParent(templatedLogicalParent);
-                    }
-                    else
-                    {
-                        LogicalChildren.Add(newChild);
-                    }
+                    LogicalChildren.Add(newChild);
                 }
                 }
 
 
                 VisualChildren.Add(newChild);
                 VisualChildren.Add(newChild);

+ 20 - 11
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -8,6 +8,7 @@ using Avalonia.Interactivity;
 using Avalonia.Layout;
 using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Platform;
+using Avalonia.Styling;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 using JetBrains.Annotations;
 using JetBrains.Annotations;
 
 
@@ -16,7 +17,7 @@ namespace Avalonia.Controls.Primitives
     /// <summary>
     /// <summary>
     /// The root window of a <see cref="Popup"/>.
     /// The root window of a <see cref="Popup"/>.
     /// </summary>
     /// </summary>
-    public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable
+    public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost
     {
     {
         private IDisposable _presenterSubscription;
         private IDisposable _presenterSubscription;
 
 
@@ -66,6 +67,11 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// </summary>
         IVisual IHostedVisualTreeRoot.Host => Parent;
         IVisual IHostedVisualTreeRoot.Host => Parent;
 
 
+        /// <summary>
+        /// Gets the styling parent of the popup root.
+        /// </summary>
+        IStyleHost IStyleHost.StylingParent => Parent;
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void Dispose() => PlatformImpl?.Dispose();
         public void Dispose() => PlatformImpl?.Dispose();
 
 
@@ -90,20 +96,23 @@ namespace Avalonia.Controls.Primitives
 
 
         private void SetTemplatedParentAndApplyChildTemplates(IControl control)
         private void SetTemplatedParentAndApplyChildTemplates(IControl control)
         {
         {
-            var templatedParent = Parent.TemplatedParent;
-
-            if (control.TemplatedParent == null)
+            if (control != null)
             {
             {
-                control.SetValue(TemplatedParentProperty, templatedParent);
-            }
+                var templatedParent = Parent.TemplatedParent;
 
 
-            control.ApplyTemplate();
+                if (control.TemplatedParent == null)
+                {
+                    control.SetValue(TemplatedParentProperty, templatedParent);
+                }
 
 
-            if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
-            {
-                foreach (IControl child in control.GetVisualChildren())
+                control.ApplyTemplate();
+
+                if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
                 {
                 {
-                    SetTemplatedParentAndApplyChildTemplates(child);
+                    foreach (IControl child in control.GetVisualChildren())
+                    {
+                        SetTemplatedParentAndApplyChildTemplates(child);
+                    }
                 }
                 }
             }
             }
         }
         }

+ 3 - 1
src/Avalonia.Controls/Primitives/RangeBase.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 Avalonia.Data;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 
 
 namespace Avalonia.Controls.Primitives
 namespace Avalonia.Controls.Primitives
@@ -36,7 +37,8 @@ namespace Avalonia.Controls.Primitives
             AvaloniaProperty.RegisterDirect<RangeBase, double>(
             AvaloniaProperty.RegisterDirect<RangeBase, double>(
                 nameof(Value),
                 nameof(Value),
                 o => o.Value,
                 o => o.Value,
-                (o, v) => o.Value = v);
+                (o, v) => o.Value = v,
+                defaultBindingMode: BindingMode.TwoWay);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="SmallChange"/> property.
         /// Defines the <see cref="SmallChange"/> property.

+ 44 - 26
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -151,15 +151,23 @@ namespace Avalonia.Controls.Primitives
             {
             {
                 if (_updateCount == 0)
                 if (_updateCount == 0)
                 {
                 {
-                    var old = SelectedIndex;
-                    var effective = (value >= 0 && value < Items?.Cast<object>().Count()) ? value : -1;
-
-                    if (old != effective)
+                    SetAndRaise(SelectedIndexProperty, ref _selectedIndex, (int val, ref int backing, Action<Action> notifyWrapper) =>
                     {
                     {
-                        _selectedIndex = effective;
-                        RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue);
-                        SelectedItem = ElementAt(Items, effective);
-                    }
+                        var old = backing;
+                        var effective = (val >= 0 && val < Items?.Cast<object>().Count()) ? val : -1;
+
+                        if (old != effective)
+                        {
+                            backing = effective;
+                            notifyWrapper(() =>
+                                RaisePropertyChanged(
+                                    SelectedIndexProperty,
+                                    old,
+                                    effective,
+                                    BindingPriority.LocalValue));
+                            SelectedItem = ElementAt(Items, effective);
+                        }
+                    }, value);
                 }
                 }
                 else
                 else
                 {
                 {
@@ -183,31 +191,41 @@ namespace Avalonia.Controls.Primitives
             {
             {
                 if (_updateCount == 0)
                 if (_updateCount == 0)
                 {
                 {
-                    var old = SelectedItem;
-                    var index = IndexOf(Items, value);
-                    var effective = index != -1 ? value : null;
-
-                    if (!object.Equals(effective, old))
+                    SetAndRaise(SelectedItemProperty, ref _selectedItem, (object val, ref object backing, Action<Action> notifyWrapper) =>
                     {
                     {
-                        _selectedItem = effective;
-                        RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue);
-                        SelectedIndex = index;
+                        var old = backing;
+                        var index = IndexOf(Items, val);
+                        var effective = index != -1 ? val : null;
 
 
-                        if (effective != null)
+                        if (!object.Equals(effective, old))
                         {
                         {
-                            if (SelectedItems.Count != 1 || SelectedItems[0] != effective)
+                            backing = effective;
+
+                            notifyWrapper(() =>
+                                RaisePropertyChanged(
+                                    SelectedItemProperty,
+                                    old,
+                                    effective,
+                                    BindingPriority.LocalValue));
+
+                            SelectedIndex = index;
+
+                            if (effective != null)
+                            {
+                                if (SelectedItems.Count != 1 || SelectedItems[0] != effective)
+                                {
+                                    _syncingSelectedItems = true;
+                                    SelectedItems.Clear();
+                                    SelectedItems.Add(effective);
+                                    _syncingSelectedItems = false;
+                                }
+                            }
+                            else if (SelectedItems.Count > 0)
                             {
                             {
-                                _syncingSelectedItems = true;
                                 SelectedItems.Clear();
                                 SelectedItems.Clear();
-                                SelectedItems.Add(effective);
-                                _syncingSelectedItems = false;
                             }
                             }
                         }
                         }
-                        else if (SelectedItems.Count > 0)
-                        {
-                            SelectedItems.Clear();
-                        }
-                    }
+                    }, value);
                 }
                 }
                 else
                 else
                 {
                 {

+ 10 - 2
src/Avalonia.Controls/Primitives/Track.cs

@@ -154,7 +154,11 @@ namespace Avalonia.Controls.Primitives
 
 
                 if (increaseButton != null)
                 if (increaseButton != null)
                 {
                 {
-                    increaseButton.Arrange(new Rect(firstWidth + thumbWidth, 0, remaining - firstWidth, finalSize.Height));
+                    increaseButton.Arrange(new Rect(
+                        firstWidth + thumbWidth,
+                        0,
+                        Math.Max(0, remaining - firstWidth),
+                        finalSize.Height));
                 }
                 }
             }
             }
             else
             else
@@ -185,7 +189,11 @@ namespace Avalonia.Controls.Primitives
 
 
                 if (increaseButton != null)
                 if (increaseButton != null)
                 {
                 {
-                    increaseButton.Arrange(new Rect(0, firstHeight + thumbHeight, finalSize.Width, Math.Max(remaining - firstHeight, 0)));
+                    increaseButton.Arrange(new Rect(
+                        0,
+                        firstHeight + thumbHeight,
+                        finalSize.Width,
+                        Math.Max(remaining - firstHeight, 0)));
                 }
                 }
             }
             }
 
 

+ 8 - 0
src/Avalonia.Controls/TextBox.cs

@@ -178,6 +178,10 @@ namespace Avalonia.Controls
             {
             {
                 value = CoerceCaretIndex(value);
                 value = CoerceCaretIndex(value);
                 SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
                 SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
+                if (SelectionStart == SelectionEnd)
+                {
+                    CaretIndex = SelectionStart;
+                }
             }
             }
         }
         }
 
 
@@ -192,6 +196,10 @@ namespace Avalonia.Controls
             {
             {
                 value = CoerceCaretIndex(value);
                 value = CoerceCaretIndex(value);
                 SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
                 SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
+                if (SelectionStart == SelectionEnd)
+                {
+                    CaretIndex = SelectionEnd;
+                }
             }
             }
         }
         }
 
 

+ 2 - 2
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@@ -11,7 +11,7 @@
     <DefineConstants>TRACE;DEBUG</DefineConstants>
     <DefineConstants>TRACE;DEBUG</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Diagnostics.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Diagnostics.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -21,7 +21,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Diagnostics.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Diagnostics.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>

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

@@ -5,7 +5,7 @@
     <DefineConstants>$(DefineConstants);DOTNETCORE</DefineConstants>
     <DefineConstants>$(DefineConstants);DOTNETCORE</DefineConstants>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup>
   <PropertyGroup>
-    <DocumentationFile>bin\$(Configuration)\Avalonia.DotNetCoreRuntime.XML</DocumentationFile>
+    <DocumentationFile>bin\$(Configuration)\Avalonia.DotNetCoreRuntime.xml</DocumentationFile>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <Compile Include="..\Shared\SharedAssemblyInfo.cs">
     <Compile Include="..\Shared\SharedAssemblyInfo.cs">

+ 2 - 2
src/Avalonia.Input/Avalonia.Input.csproj

@@ -11,7 +11,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Input.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Input.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -21,7 +21,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Input.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Input.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>

+ 16 - 1
src/Avalonia.Input/KeyGesture.cs

@@ -111,6 +111,21 @@ namespace Avalonia.Input
             return string.Join(" + ", parts);
             return string.Join(" + ", parts);
         }
         }
 
 
-        public bool Matches(KeyEventArgs keyEvent) => keyEvent.Key == Key && keyEvent.Modifiers == Modifiers;
+        public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.Modifiers == Modifiers;
+
+        private Key ResolveNumPadOperationKey(Key key)
+        {
+            switch (key)
+            {
+                case Key.Add:
+                    return Key.OemPlus;
+                case Key.Subtract:
+                    return Key.OemMinus;
+                case Key.Decimal:
+                    return Key.OemPeriod;
+                default:
+                    return key;
+            }
+        }
     }
     }
 }
 }

+ 2 - 2
src/Avalonia.Interactivity/Avalonia.Interactivity.csproj

@@ -11,7 +11,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Interactivity.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Interactivity.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -21,7 +21,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Interactivity.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Interactivity.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>

+ 2 - 2
src/Avalonia.Layout/Avalonia.Layout.csproj

@@ -11,7 +11,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Layout.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Layout.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -21,7 +21,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Layout.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Layout.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>

+ 2 - 2
src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj

@@ -11,7 +11,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Logging.Serilog.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Logging.Serilog.xml</DocumentationFile>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
     <DebugType>pdbonly</DebugType>
     <DebugType>pdbonly</DebugType>
@@ -20,7 +20,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Logging.Serilog.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Logging.Serilog.xml</DocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>

+ 2 - 2
src/Avalonia.Styling/Avalonia.Styling.csproj

@@ -12,7 +12,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Styling.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Styling.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -22,7 +22,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Styling.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Styling.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>

+ 2 - 2
src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj

@@ -11,7 +11,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Themes.Default.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Themes.Default.xml</DocumentationFile>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
   <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
     <DebugType>pdbonly</DebugType>
     <DebugType>pdbonly</DebugType>
@@ -20,7 +20,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Themes.Default.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Themes.Default.xml</DocumentationFile>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>

+ 1 - 1
src/Avalonia.Themes.Default/Button.xaml

@@ -13,8 +13,8 @@
                           Background="{TemplateBinding Background}"
                           Background="{TemplateBinding Background}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderThickness="{TemplateBinding BorderThickness}"
                           BorderThickness="{TemplateBinding BorderThickness}"
-                          Content="{TemplateBinding Content}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
+                          Content="{TemplateBinding Content}"
                           Padding="{TemplateBinding Padding}"
                           Padding="{TemplateBinding Padding}"
                           TextBlock.Foreground="{TemplateBinding Foreground}"
                           TextBlock.Foreground="{TemplateBinding Foreground}"
                           HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                           HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

+ 1 - 1
src/Avalonia.Themes.Default/CalendarButton.xaml

@@ -29,8 +29,8 @@
           <!--Focusable="False"-->
           <!--Focusable="False"-->
           <ContentControl Name="Content"
           <ContentControl Name="Content"
                           Foreground="#FF333333"
                           Foreground="#FF333333"
-                          Content="{TemplateBinding Content}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
+                          Content="{TemplateBinding Content}"
                           HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                           HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                           VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                           VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                           FontSize="{TemplateBinding FontSize}"
                           FontSize="{TemplateBinding FontSize}"

+ 1 - 1
src/Avalonia.Themes.Default/CalendarDayButton.xaml

@@ -30,8 +30,8 @@
                      Fill="{TemplateBinding Background}"/>
                      Fill="{TemplateBinding Background}"/>
 
 
           <ContentControl Name="Content"
           <ContentControl Name="Content"
-                          Content="{TemplateBinding Content}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
+                          Content="{TemplateBinding Content}"
                           HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                           HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                           VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                           VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                           FontSize="{TemplateBinding FontSize}"
                           FontSize="{TemplateBinding FontSize}"

+ 1 - 1
src/Avalonia.Themes.Default/CheckBox.xaml

@@ -31,8 +31,8 @@
             </Panel>
             </Panel>
           </Border>
           </Border>
           <ContentPresenter Name="PART_ContentPresenter"
           <ContentPresenter Name="PART_ContentPresenter"
-                            Content="{TemplateBinding Content}"
                             ContentTemplate="{TemplateBinding ContentTemplate}"
                             ContentTemplate="{TemplateBinding ContentTemplate}"
+                            Content="{TemplateBinding Content}"
                             Margin="4,0,0,0"
                             Margin="4,0,0,0"
                             VerticalAlignment="Center"
                             VerticalAlignment="Center"
                             Grid.Column="1"/>
                             Grid.Column="1"/>

+ 1 - 1
src/Avalonia.Themes.Default/ContentControl.xaml

@@ -5,8 +5,8 @@
                         Background="{TemplateBinding Background}"
                         Background="{TemplateBinding Background}"
                         BorderBrush="{TemplateBinding BorderBrush}"
                         BorderBrush="{TemplateBinding BorderBrush}"
                         BorderThickness="{TemplateBinding BorderThickness}"
                         BorderThickness="{TemplateBinding BorderThickness}"
-                        Content="{TemplateBinding Content}"
                         ContentTemplate="{TemplateBinding ContentTemplate}"
                         ContentTemplate="{TemplateBinding ContentTemplate}"
+                        Content="{TemplateBinding Content}"
                         Padding="{TemplateBinding Padding}"/>
                         Padding="{TemplateBinding Padding}"/>
     </ControlTemplate>
     </ControlTemplate>
   </Setter>
   </Setter>

+ 1 - 1
src/Avalonia.Themes.Default/DropDownItem.xaml

@@ -10,8 +10,8 @@
                           Background="{TemplateBinding Background}"
                           Background="{TemplateBinding Background}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderThickness="{TemplateBinding BorderThickness}"
                           BorderThickness="{TemplateBinding BorderThickness}"
-                          Content="{TemplateBinding Content}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
+                          Content="{TemplateBinding Content}"
                           HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                           HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                           VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                           VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                           Padding="{TemplateBinding Padding}"/>
                           Padding="{TemplateBinding Padding}"/>

+ 1 - 1
src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml

@@ -7,8 +7,8 @@
       <Border Background="{TemplateBinding Background}">
       <Border Background="{TemplateBinding Background}">
         <AdornerDecorator>
         <AdornerDecorator>
           <ContentPresenter Name="PART_ContentPresenter" 
           <ContentPresenter Name="PART_ContentPresenter" 
-                            Content="{TemplateBinding Content}" 
                             ContentTemplate="{TemplateBinding ContentTemplate}"
                             ContentTemplate="{TemplateBinding ContentTemplate}"
+                            Content="{TemplateBinding Content}" 
                             Margin="{TemplateBinding Padding}"/>
                             Margin="{TemplateBinding Padding}"/>
         </AdornerDecorator>
         </AdornerDecorator>
       </Border>
       </Border>

+ 4 - 4
src/Avalonia.Themes.Default/Expander.xaml

@@ -16,8 +16,8 @@
             <ContentPresenter Name="PART_ContentPresenter"
             <ContentPresenter Name="PART_ContentPresenter"
                               Grid.Row="1"
                               Grid.Row="1"
                               IsVisible="{TemplateBinding IsExpanded}"
                               IsVisible="{TemplateBinding IsExpanded}"
-                              Content="{TemplateBinding Content}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
+                              Content="{TemplateBinding Content}"
                               HorizontalAlignment="Stretch"
                               HorizontalAlignment="Stretch"
                               VerticalAlignment="Stretch" />
                               VerticalAlignment="Stretch" />
           </Grid>
           </Grid>
@@ -34,8 +34,8 @@
             <ContentPresenter Name="PART_ContentPresenter"
             <ContentPresenter Name="PART_ContentPresenter"
                               Grid.Row="0"
                               Grid.Row="0"
                               IsVisible="{TemplateBinding IsExpanded}"
                               IsVisible="{TemplateBinding IsExpanded}"
-                              Content="{TemplateBinding Content}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
+                              Content="{TemplateBinding Content}"
                               HorizontalAlignment="Stretch"
                               HorizontalAlignment="Stretch"
                               VerticalAlignment="Stretch" />
                               VerticalAlignment="Stretch" />
           </Grid>
           </Grid>
@@ -52,8 +52,8 @@
             <ContentPresenter Name="PART_ContentPresenter"
             <ContentPresenter Name="PART_ContentPresenter"
                               Grid.Column="1"
                               Grid.Column="1"
                               IsVisible="{TemplateBinding IsExpanded}"
                               IsVisible="{TemplateBinding IsExpanded}"
-                              Content="{TemplateBinding Content}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
+                              Content="{TemplateBinding Content}"
                               HorizontalAlignment="Stretch"
                               HorizontalAlignment="Stretch"
                               VerticalAlignment="Stretch" />
                               VerticalAlignment="Stretch" />
           </Grid>
           </Grid>
@@ -70,8 +70,8 @@
             <ContentPresenter Name="PART_ContentPresenter"
             <ContentPresenter Name="PART_ContentPresenter"
                               Grid.Column="0"
                               Grid.Column="0"
                               IsVisible="{TemplateBinding IsExpanded}"
                               IsVisible="{TemplateBinding IsExpanded}"
-                              Content="{TemplateBinding Content}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
+                              Content="{TemplateBinding Content}"
                               HorizontalAlignment="Stretch"
                               HorizontalAlignment="Stretch"
                               VerticalAlignment="Stretch" />
                               VerticalAlignment="Stretch" />
           </Grid>
           </Grid>

+ 1 - 1
src/Avalonia.Themes.Default/LayoutTransformControl.xaml

@@ -5,8 +5,8 @@
                         Background="{TemplateBinding Background}"
                         Background="{TemplateBinding Background}"
                         BorderBrush="{TemplateBinding BorderBrush}"
                         BorderBrush="{TemplateBinding BorderBrush}"
                         BorderThickness="{TemplateBinding BorderThickness}"
                         BorderThickness="{TemplateBinding BorderThickness}"
-                        Content="{TemplateBinding Content}" 
                         ContentTemplate="{TemplateBinding ContentTemplate}"
                         ContentTemplate="{TemplateBinding ContentTemplate}"
+                        Content="{TemplateBinding Content}" 
                         Padding="{TemplateBinding Padding}"/>
                         Padding="{TemplateBinding Padding}"/>
     </ControlTemplate>
     </ControlTemplate>
   </Setter>
   </Setter>

+ 1 - 1
src/Avalonia.Themes.Default/ListBoxItem.xaml

@@ -7,8 +7,8 @@
                           Background="{TemplateBinding Background}"
                           Background="{TemplateBinding Background}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderThickness="{TemplateBinding BorderThickness}"
                           BorderThickness="{TemplateBinding BorderThickness}"
-                          Content="{TemplateBinding Content}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
+                          Content="{TemplateBinding Content}"
                           Padding="{TemplateBinding Padding}"/>
                           Padding="{TemplateBinding Padding}"/>
       </ControlTemplate>
       </ControlTemplate>
     </Setter>
     </Setter>

+ 1 - 1
src/Avalonia.Themes.Default/PopupRoot.xaml

@@ -4,8 +4,8 @@
     <ControlTemplate>
     <ControlTemplate>
       <ContentPresenter Name="PART_ContentPresenter"
       <ContentPresenter Name="PART_ContentPresenter"
                         Background="{TemplateBinding Background}"
                         Background="{TemplateBinding Background}"
-                        Content="{TemplateBinding Content}" 
                         ContentTemplate="{TemplateBinding ContentTemplate}"
                         ContentTemplate="{TemplateBinding ContentTemplate}"
+                        Content="{TemplateBinding Content}" 
                         Padding="{TemplateBinding Padding}"/>
                         Padding="{TemplateBinding Padding}"/>
     </ControlTemplate>
     </ControlTemplate>
   </Setter>
   </Setter>

+ 1 - 1
src/Avalonia.Themes.Default/RadioButton.xaml

@@ -29,8 +29,8 @@
                    HorizontalAlignment="Center"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"/>
                    VerticalAlignment="Center"/>
           <ContentPresenter Name="PART_ContentPresenter"
           <ContentPresenter Name="PART_ContentPresenter"
-                            Content="{TemplateBinding Content}"
                             ContentTemplate="{TemplateBinding ContentTemplate}"
                             ContentTemplate="{TemplateBinding ContentTemplate}"
+                            Content="{TemplateBinding Content}"
                             Margin="4,0,0,0"
                             Margin="4,0,0,0"
                             VerticalAlignment="Center"
                             VerticalAlignment="Center"
                             Grid.Column="1"/>
                             Grid.Column="1"/>

+ 1 - 1
src/Avalonia.Themes.Default/RepeatButton.xaml

@@ -20,8 +20,8 @@
                                   Background="{TemplateBinding Background}"
                                   Background="{TemplateBinding Background}"
                                   BorderBrush="{TemplateBinding BorderBrush}"
                                   BorderBrush="{TemplateBinding BorderBrush}"
                                   BorderThickness="{TemplateBinding BorderThickness}"
                                   BorderThickness="{TemplateBinding BorderThickness}"
-                                  Content="{TemplateBinding Content}"
                                   ContentTemplate="{TemplateBinding ContentTemplate}"
                                   ContentTemplate="{TemplateBinding ContentTemplate}"
+                                  Content="{TemplateBinding Content}"
                                   Padding="{TemplateBinding Padding}"
                                   Padding="{TemplateBinding Padding}"
                                   TextBlock.Foreground="{TemplateBinding Foreground}"
                                   TextBlock.Foreground="{TemplateBinding Foreground}"
                                   HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                   HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

+ 1 - 1
src/Avalonia.Themes.Default/TabStripItem.xaml

@@ -9,8 +9,8 @@
                           Background="{TemplateBinding Background}"
                           Background="{TemplateBinding Background}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderThickness="{TemplateBinding BorderThickness}"
                           BorderThickness="{TemplateBinding BorderThickness}"
-                          Content="{TemplateBinding Content}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
+                          Content="{TemplateBinding Content}"
                           Padding="{TemplateBinding Padding}"/>
                           Padding="{TemplateBinding Padding}"/>
       </ControlTemplate>
       </ControlTemplate>
     </Setter>
     </Setter>

+ 1 - 1
src/Avalonia.Themes.Default/ToggleButton.xaml

@@ -13,8 +13,8 @@
                           Background="{TemplateBinding Background}"
                           Background="{TemplateBinding Background}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderThickness="{TemplateBinding BorderThickness}"
                           BorderThickness="{TemplateBinding BorderThickness}"
-                          Content="{TemplateBinding Content}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
                           ContentTemplate="{TemplateBinding ContentTemplate}"
+                          Content="{TemplateBinding Content}"
                           Padding="{TemplateBinding Padding}"
                           Padding="{TemplateBinding Padding}"
                           TextBlock.Foreground="{TemplateBinding Foreground}"
                           TextBlock.Foreground="{TemplateBinding Foreground}"
                           HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                           HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

+ 1 - 1
src/Avalonia.Themes.Default/ToolTip.xaml

@@ -9,8 +9,8 @@
                         Background="{TemplateBinding Background}"
                         Background="{TemplateBinding Background}"
                         BorderBrush="{TemplateBinding BorderBrush}"
                         BorderBrush="{TemplateBinding BorderBrush}"
                         BorderThickness="{TemplateBinding BorderThickness}"
                         BorderThickness="{TemplateBinding BorderThickness}"
-                        Content="{TemplateBinding Content}" 
                         ContentTemplate="{TemplateBinding ContentTemplate}"
                         ContentTemplate="{TemplateBinding ContentTemplate}"
+                        Content="{TemplateBinding Content}" 
                         Padding="{TemplateBinding Padding}"/>
                         Padding="{TemplateBinding Padding}"/>
     </ControlTemplate>
     </ControlTemplate>
   </Setter>
   </Setter>

+ 1 - 1
src/Avalonia.Themes.Default/Window.xaml

@@ -7,8 +7,8 @@
       <Border Background="{TemplateBinding Background}">
       <Border Background="{TemplateBinding Background}">
         <AdornerDecorator>
         <AdornerDecorator>
           <ContentPresenter Name="PART_ContentPresenter" 
           <ContentPresenter Name="PART_ContentPresenter" 
-                            Content="{TemplateBinding Content}" 
                             ContentTemplate="{TemplateBinding ContentTemplate}"
                             ContentTemplate="{TemplateBinding ContentTemplate}"
+                            Content="{TemplateBinding Content}" 
                             Margin="{TemplateBinding Padding}"/>
                             Margin="{TemplateBinding Padding}"/>
         </AdornerDecorator>
         </AdornerDecorator>
       </Border>
       </Border>

+ 30 - 20
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -25,11 +25,9 @@ namespace Avalonia.Rendering
         private readonly IRenderLoop _renderLoop;
         private readonly IRenderLoop _renderLoop;
         private readonly IVisual _root;
         private readonly IVisual _root;
         private readonly ISceneBuilder _sceneBuilder;
         private readonly ISceneBuilder _sceneBuilder;
-        private readonly RenderLayers _layers;
 
 
         private bool _running;
         private bool _running;
         private Scene _scene;
         private Scene _scene;
-        private IRenderTarget _renderTarget;
         private DirtyVisuals _dirty;
         private DirtyVisuals _dirty;
         private IRenderTargetBitmapImpl _overlay;
         private IRenderTargetBitmapImpl _overlay;
         private bool _updateQueued;
         private bool _updateQueued;
@@ -56,7 +54,7 @@ namespace Avalonia.Rendering
             _dispatcher = dispatcher ?? Dispatcher.UIThread;
             _dispatcher = dispatcher ?? Dispatcher.UIThread;
             _root = root;
             _root = root;
             _sceneBuilder = sceneBuilder ?? new SceneBuilder();
             _sceneBuilder = sceneBuilder ?? new SceneBuilder();
-            _layers = new RenderLayers();
+            Layers = new RenderLayers();
             _renderLoop = renderLoop;
             _renderLoop = renderLoop;
         }
         }
 
 
@@ -78,9 +76,9 @@ namespace Avalonia.Rendering
             Contract.Requires<ArgumentNullException>(renderTarget != null);
             Contract.Requires<ArgumentNullException>(renderTarget != null);
 
 
             _root = root;
             _root = root;
-            _renderTarget = renderTarget;
+            RenderTarget = renderTarget;
             _sceneBuilder = sceneBuilder ?? new SceneBuilder();
             _sceneBuilder = sceneBuilder ?? new SceneBuilder();
-            _layers = new RenderLayers();
+            Layers = new RenderLayers();
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
@@ -94,6 +92,16 @@ namespace Avalonia.Rendering
         /// </summary>
         /// </summary>
         public string DebugFramesPath { get; set; }
         public string DebugFramesPath { get; set; }
 
 
+        /// <summary>
+        /// Gets the render layers.
+        /// </summary>
+        internal RenderLayers Layers { get; }
+
+        /// <summary>
+        /// Gets the current render target.
+        /// </summary>
+        internal IRenderTarget RenderTarget { get; private set; }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void AddDirty(IVisual visual)
         public void AddDirty(IVisual visual)
         {
         {
@@ -173,9 +181,9 @@ namespace Avalonia.Rendering
             bool renderOverlay = DrawDirtyRects || DrawFps;
             bool renderOverlay = DrawDirtyRects || DrawFps;
             bool composite = false;
             bool composite = false;
 
 
-            if (_renderTarget == null)
+            if (RenderTarget == null)
             {
             {
-                _renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
+                RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
             }
             }
 
 
             if (renderOverlay)
             if (renderOverlay)
@@ -191,8 +199,8 @@ namespace Avalonia.Rendering
 
 
                     if (scene.Generation != _lastSceneId)
                     if (scene.Generation != _lastSceneId)
                     {
                     {
-                        context = _renderTarget.CreateDrawingContext(this);
-                        _layers.Update(scene, context);
+                        context = RenderTarget.CreateDrawingContext(this);
+                        Layers.Update(scene, context);
 
 
                         RenderToLayers(scene);
                         RenderToLayers(scene);
 
 
@@ -208,13 +216,13 @@ namespace Avalonia.Rendering
 
 
                     if (renderOverlay)
                     if (renderOverlay)
                     {
                     {
-                        context = context ?? _renderTarget.CreateDrawingContext(this);
+                        context = context ?? RenderTarget.CreateDrawingContext(this);
                         RenderOverlay(scene, context);
                         RenderOverlay(scene, context);
                         RenderComposite(scene, context);
                         RenderComposite(scene, context);
                     }
                     }
                     else if (composite)
                     else if (composite)
                     {
                     {
-                        context = context ?? _renderTarget.CreateDrawingContext(this);
+                        context = context ?? RenderTarget.CreateDrawingContext(this);
                         RenderComposite(scene, context);
                         RenderComposite(scene, context);
                     }
                     }
 
 
@@ -224,8 +232,8 @@ namespace Avalonia.Rendering
             catch (RenderTargetCorruptedException ex)
             catch (RenderTargetCorruptedException ex)
             {
             {
                 Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
                 Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
-                _renderTarget?.Dispose();
-                _renderTarget = null;
+                RenderTarget?.Dispose();
+                RenderTarget = null;
             }
             }
         }
         }
 
 
@@ -235,9 +243,11 @@ namespace Avalonia.Rendering
             {
             {
                 clipBounds = node.ClipBounds.Intersect(clipBounds);
                 clipBounds = node.ClipBounds.Intersect(clipBounds);
 
 
-                if (!clipBounds.IsEmpty)
+                if (!clipBounds.IsEmpty && node.Opacity > 0)
                 {
                 {
-                    node.BeginRender(context);
+                    var isLayerRoot = node.Visual == layer;
+
+                    node.BeginRender(context, isLayerRoot);
 
 
                     foreach (var operation in node.DrawOperations)
                     foreach (var operation in node.DrawOperations)
                     {
                     {
@@ -251,7 +261,7 @@ namespace Avalonia.Rendering
                         Render(context, (VisualNode)child, layer, clipBounds);
                         Render(context, (VisualNode)child, layer, clipBounds);
                     }
                     }
 
 
-                    node.EndRender(context);
+                    node.EndRender(context, isLayerRoot);
                 }
                 }
             }
             }
         }
         }
@@ -262,7 +272,7 @@ namespace Avalonia.Rendering
             {
             {
                 foreach (var layer in scene.Layers)
                 foreach (var layer in scene.Layers)
                 {
                 {
-                    var renderTarget = _layers[layer.LayerRoot].Bitmap;
+                    var renderTarget = Layers[layer.LayerRoot].Bitmap;
                     var node = (VisualNode)scene.FindNode(layer.LayerRoot);
                     var node = (VisualNode)scene.FindNode(layer.LayerRoot);
 
 
                     if (node != null)
                     if (node != null)
@@ -322,7 +332,7 @@ namespace Avalonia.Rendering
 
 
             foreach (var layer in scene.Layers)
             foreach (var layer in scene.Layers)
             {
             {
-                var bitmap = _layers[layer.LayerRoot].Bitmap;
+                var bitmap = Layers[layer.LayerRoot].Bitmap;
                 var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
                 var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
 
 
                 if (layer.GeometryClip != null)
                 if (layer.GeometryClip != null)
@@ -353,7 +363,7 @@ namespace Avalonia.Rendering
 
 
             if (DrawFps)
             if (DrawFps)
             {
             {
-                RenderFps(context, clientRect, true);
+                RenderFps(context, clientRect, scene.Layers.Count);
             }
             }
         }
         }
 
 
@@ -442,7 +452,7 @@ namespace Avalonia.Rendering
         {
         {
             var index = 0;
             var index = 0;
 
 
-            foreach (var layer in _layers)
+            foreach (var layer in Layers)
             {
             {
                 var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png");
                 var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png");
                 layer.Bitmap.Save(fileName);
                 layer.Bitmap.Save(fileName);

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

@@ -69,7 +69,7 @@ namespace Avalonia.Rendering
 
 
                     if (DrawFps)
                     if (DrawFps)
                     {
                     {
-                        RenderFps(context.PlatformImpl, _root.Bounds, true);
+                        RenderFps(context.PlatformImpl, _root.Bounds, null);
                     }
                     }
                 }
                 }
             }
             }

+ 11 - 6
src/Avalonia.Visuals/Rendering/RendererBase.cs

@@ -22,15 +22,12 @@ namespace Avalonia.Rendering
             };
             };
         }
         }
 
 
-        protected void RenderFps(IDrawingContextImpl context, Rect clientRect, bool incrementFrameCount)
+        protected void RenderFps(IDrawingContextImpl context, Rect clientRect, int? layerCount)
         {
         {
             var now = _stopwatch.Elapsed;
             var now = _stopwatch.Elapsed;
             var elapsed = now - _lastFpsUpdate;
             var elapsed = now - _lastFpsUpdate;
 
 
-            if (incrementFrameCount)
-            {
-                ++_framesThisSecond;
-            }
+            ++_framesThisSecond;
 
 
             if (elapsed.TotalSeconds > 1)
             if (elapsed.TotalSeconds > 1)
             {
             {
@@ -39,7 +36,15 @@ namespace Avalonia.Rendering
                 _lastFpsUpdate = now;
                 _lastFpsUpdate = now;
             }
             }
 
 
-            _fpsText.Text = string.Format("FPS: {0:000}", _fps);
+            if (layerCount.HasValue)
+            {
+                _fpsText.Text = string.Format("Layers: {0} FPS: {1:000}", layerCount, _fps);
+            }
+            else
+            {
+                _fpsText.Text = string.Format("FPS: {0:000}", _fps);
+            }
+
             var size = _fpsText.Measure();
             var size = _fpsText.Measure();
             var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height);
             var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height);
 
 

+ 4 - 2
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs

@@ -72,13 +72,15 @@ namespace Avalonia.Rendering.SceneGraph
         /// Sets up the drawing context for rendering the node's geometry.
         /// Sets up the drawing context for rendering the node's geometry.
         /// </summary>
         /// </summary>
         /// <param name="context">The drawing context.</param>
         /// <param name="context">The drawing context.</param>
-        void BeginRender(IDrawingContextImpl context);
+        /// <param name="skipOpacity">Whether to skip pushing the control's opacity.</param>
+        void BeginRender(IDrawingContextImpl context, bool skipOpacity);
 
 
         /// <summary>
         /// <summary>
         /// Resets the drawing context after rendering the node's geometry.
         /// Resets the drawing context after rendering the node's geometry.
         /// </summary>
         /// </summary>
         /// <param name="context">The drawing context.</param>
         /// <param name="context">The drawing context.</param>
-        void EndRender(IDrawingContextImpl context);
+        /// <param name="skipOpacity">Whether to skip popping the control's opacity.</param>
+        void EndRender(IDrawingContextImpl context, bool skipOpacity);
 
 
         /// <summary>
         /// <summary>
         /// Hit test the geometry in this node.
         /// Hit test the geometry in this node.

+ 12 - 3
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@@ -167,7 +167,6 @@ namespace Avalonia.Rendering.SceneGraph
                 using (context.PushPostTransform(m))
                 using (context.PushPostTransform(m))
                 using (context.PushTransformContainer())
                 using (context.PushTransformContainer())
                 {
                 {
-                    var startLayer = opacity < 1 || visual.OpacityMask != null;
                     var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip);
                     var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip);
 
 
                     forceRecurse = forceRecurse ||
                     forceRecurse = forceRecurse ||
@@ -179,9 +178,11 @@ namespace Avalonia.Rendering.SceneGraph
                     node.ClipToBounds = clipToBounds;
                     node.ClipToBounds = clipToBounds;
                     node.GeometryClip = visual.Clip?.PlatformImpl;
                     node.GeometryClip = visual.Clip?.PlatformImpl;
                     node.Opacity = opacity;
                     node.Opacity = opacity;
-                    node.OpacityMask = visual.OpacityMask;
 
 
-                    if (startLayer)
+                    // TODO: Check equality between node.OpacityMask and visual.OpacityMask before assigning.
+                    node.OpacityMask = visual.OpacityMask?.ToImmutable();
+
+                    if (ShouldStartLayer(visual))
                     {
                     {
                         if (node.LayerRoot != visual)
                         if (node.LayerRoot != visual)
                         {
                         {
@@ -366,6 +367,14 @@ namespace Avalonia.Rendering.SceneGraph
             }
             }
         }
         }
 
 
+        private static bool ShouldStartLayer(IVisual visual)
+        {
+            var o = visual as IAvaloniaObject;
+            return visual.VisualChildren.Count > 0 &&
+                o != null &&
+                o.IsAnimating(Visual.OpacityProperty);
+        }
+
         private static IGeometryImpl CreateLayerGeometryClip(VisualNode node)
         private static IGeometryImpl CreateLayerGeometryClip(VisualNode node)
         {
         {
             IGeometryImpl result = null;
             IGeometryImpl result = null;

+ 28 - 2
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@@ -22,6 +22,7 @@ namespace Avalonia.Rendering.SceneGraph
         private List<IVisualNode> _children;
         private List<IVisualNode> _children;
         private List<IDrawOperation> _drawOperations;
         private List<IDrawOperation> _drawOperations;
         private bool _drawOperationsCloned;
         private bool _drawOperationsCloned;
+        private Matrix transformRestore;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="VisualNode"/> class.
         /// Initializes a new instance of the <see cref="VisualNode"/> class.
@@ -218,8 +219,10 @@ namespace Avalonia.Rendering.SceneGraph
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public void BeginRender(IDrawingContextImpl context)
+        public void BeginRender(IDrawingContextImpl context, bool skipOpacity)
         {
         {
+            transformRestore = context.Transform;
+
             if (ClipToBounds)
             if (ClipToBounds)
             {
             {
                 context.Transform = Matrix.Identity;
                 context.Transform = Matrix.Identity;
@@ -228,24 +231,47 @@ namespace Avalonia.Rendering.SceneGraph
 
 
             context.Transform = Transform;
             context.Transform = Transform;
 
 
+            if (Opacity != 1 && !skipOpacity)
+            {
+                context.PushOpacity(Opacity);
+            }
+
             if (GeometryClip != null)
             if (GeometryClip != null)
             {
             {
                 context.PushGeometryClip(GeometryClip);
                 context.PushGeometryClip(GeometryClip);
             }
             }
+
+            if (OpacityMask != null)
+            {
+                context.PushOpacityMask(OpacityMask, ClipBounds);
+            }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public void EndRender(IDrawingContextImpl context)
+        public void EndRender(IDrawingContextImpl context, bool skipOpacity)
         {
         {
+            if (OpacityMask != null)
+            {
+                context.PopOpacityMask();
+            }
+
             if (GeometryClip != null)
             if (GeometryClip != null)
             {
             {
                 context.PopGeometryClip();
                 context.PopGeometryClip();
             }
             }
 
 
+            if (Opacity != 1 && !skipOpacity)
+            {
+                context.PopOpacity();
+            }
+
             if (ClipToBounds)
             if (ClipToBounds)
             {
             {
+                context.Transform = Matrix.Identity;
                 context.PopClip();
                 context.PopClip();
             }
             }
+
+            context.Transform = transformRestore;
         }
         }
 
 
         private Rect CalculateBounds()
         private Rect CalculateBounds()

+ 2 - 1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -22,7 +22,7 @@
     <DefineConstants>NETSTANDARD1_3;PCL;NETSTANDARD</DefineConstants>
     <DefineConstants>NETSTANDARD1_3;PCL;NETSTANDARD</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Markup.Xaml.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Markup.Xaml.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
     <ItemGroup>
     <ItemGroup>
@@ -91,6 +91,7 @@
         <Compile Include="Templates\TemplateContent.cs" />
         <Compile Include="Templates\TemplateContent.cs" />
         <Compile Include="Templates\TemplateLoader.cs" />
         <Compile Include="Templates\TemplateLoader.cs" />
         <Compile Include="Templates\TreeDataTemplate.cs" />
         <Compile Include="Templates\TreeDataTemplate.cs" />
+        <Compile Include="XamlLoadException.cs" />
         <Compile Include="PortableXaml\portable.xaml.github\src\Portable.Xaml\**\*.cs" Exclude="PortableXaml\portable.xaml.github\src\Portable.Xaml\Assembly\**\*.cs" />
         <Compile Include="PortableXaml\portable.xaml.github\src\Portable.Xaml\**\*.cs" Exclude="PortableXaml\portable.xaml.github\src\Portable.Xaml\Assembly\**\*.cs" />
         <Compile Remove="**\UriTypeConverter.cs" />
         <Compile Remove="**\UriTypeConverter.cs" />
     </ItemGroup>
     </ItemGroup>

+ 13 - 1
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs

@@ -127,7 +127,19 @@ namespace Avalonia.Markup.Xaml
 
 
             using (var stream = assetLocator.Open(uri, baseUri))
             using (var stream = assetLocator.Open(uri, baseUri))
             {
             {
-                return Load(stream, rootInstance, uri);
+                try
+                {
+                    return Load(stream, rootInstance, uri);
+                }
+                catch (Exception e)
+                {
+                    var uriString = uri.ToString();
+                    if (!uri.IsAbsoluteUri)
+                    {
+                        uriString = new Uri(baseUri, uri).AbsoluteUri;
+                    }
+                    throw new XamlLoadException("Error loading xaml at " + uriString, e);
+                }
             }
             }
         }
         }
 
 

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@@ -1 +1 @@
-Subproject commit d50ae8335eb50d4b9606de6f5fa1cbbc78bfd72f
+Subproject commit c0664014455392ac221a765e66f9837704339b6f

+ 24 - 0
src/Markup/Avalonia.Markup.Xaml/XamlLoadException.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace Avalonia.Markup.Xaml
+{
+    public class XamlLoadException: Exception
+    {
+        public XamlLoadException()
+        {
+        }
+
+        protected XamlLoadException(SerializationInfo info, StreamingContext context): base(info, context)
+        {
+        }
+
+        public XamlLoadException(string message): base(message)
+        {
+        }
+
+        public XamlLoadException(string message, Exception innerException): base(message, innerException)
+        {
+        }
+    }
+}

+ 2 - 2
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@@ -11,7 +11,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Markup.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Markup.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@@ -21,7 +21,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Markup.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Markup.xml</DocumentationFile>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>

+ 2 - 2
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@@ -450,7 +450,7 @@ namespace Avalonia.Skia
                 {
                 {
                     var match = _foregroundBrushes[bi];
                     var match = _foregroundBrushes[bi];
 
 
-                    len = match.Key.EndIndex - index + 1;
+                    len = match.Key.EndIndex - index;
                     result = match.Value;
                     result = match.Value;
 
 
                     if (len > 0 && len < length)
                     if (len > 0 && len < length)
@@ -641,7 +641,7 @@ namespace Avalonia.Skia
                 Length = length;
                 Length = length;
             }
             }
 
 
-            public int EndIndex => StartIndex + Length - 1;
+            public int EndIndex => StartIndex + Length;
 
 
             public int Length { get; private set; }
             public int Length { get; private set; }
 
 

+ 4 - 3
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Direct2D1.Media;
 using Avalonia.Direct2D1.Media;
@@ -68,9 +69,9 @@ namespace Avalonia.Direct2D1
                         for (var y = 0; y < _target.Height; y++)
                         for (var y = 0; y < _target.Height; y++)
                         {
                         {
                             UnmanagedMethods.CopyMemory(
                             UnmanagedMethods.CopyMemory(
-                                _target.Address + _target.RowBytes * y,
-                                l.Data.DataPointer + l.Stride * y,
-                                (uint) Math.Min(l.Stride, _target.RowBytes));
+                                (_target.Address + _target.RowBytes * y),
+                                (l.Data.DataPointer + l.Stride * y),
+                                (UIntPtr)Math.Min(l.Stride, _target.RowBytes));
                         }
                         }
                     }
                     }
                     Dispose();
                     Dispose();

+ 5 - 2
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@@ -3,6 +3,7 @@
 
 
 using System;
 using System;
 using System.IO;
 using System.IO;
+using System.Runtime.CompilerServices;
 using Avalonia.Win32.Interop;
 using Avalonia.Win32.Interop;
 using SharpDX.WIC;
 using SharpDX.WIC;
 using APixelFormat = Avalonia.Platform.PixelFormat;
 using APixelFormat = Avalonia.Platform.PixelFormat;
@@ -74,8 +75,10 @@ namespace Avalonia.Direct2D1.Media
             {
             {
                 for (var row = 0; row < height; row++)
                 for (var row = 0; row < height; row++)
                 {
                 {
-                    UnmanagedMethods.CopyMemory(new IntPtr(l.Data.DataPointer.ToInt64() + row * l.Stride),
-                        new IntPtr(data.ToInt64() + row * stride), (uint) l.Data.Pitch);
+                    UnmanagedMethods.CopyMemory(
+                        (l.Data.DataPointer + row * l.Stride),
+                        (data + row * stride),
+                        (UIntPtr) l.Data.Pitch);
                 }
                 }
             }
             }
         }
         }

+ 2 - 2
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@@ -22,7 +22,7 @@
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Win32.XML</DocumentationFile>
+    <DocumentationFile>bin\Debug\Avalonia.Win32.xml</DocumentationFile>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
   </PropertyGroup>
   </PropertyGroup>
@@ -33,7 +33,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Win32.XML</DocumentationFile>
+    <DocumentationFile>bin\Release\Avalonia.Win32.xml</DocumentationFile>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <NoWarn>CS1591</NoWarn>
     <NoWarn>CS1591</NoWarn>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

+ 3 - 3
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -948,9 +948,9 @@ namespace Avalonia.Win32.Interop
             uint dwMaximumSizeLow,
             uint dwMaximumSizeLow,
             string lpName);
             string lpName);
 
 
-        [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
-        public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
-
+        [DllImport("msvcrt.dll", EntryPoint="memcpy", SetLastError = false, CallingConvention=CallingConvention.Cdecl)]
+        public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count); 
+        
         public enum MONITOR
         public enum MONITOR
         {
         {
             MONITOR_DEFAULTTONULL = 0x00000000,
             MONITOR_DEFAULTTONULL = 0x00000000,

+ 1 - 1
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
 <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
+    <TargetFrameworks>net47;netcoreapp2.0</TargetFrameworks>
     <OutputType>Library</OutputType>
     <OutputType>Library</OutputType>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="..\..\build\UnitTests.NetCore.targets" />
   <Import Project="..\..\build\UnitTests.NetCore.targets" />

+ 127 - 12
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@@ -2,22 +2,22 @@
 // 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.ComponentModel;
+using System.Reactive.Concurrency;
 using System.Reactive.Linq;
 using System.Reactive.Linq;
 using System.Reactive.Subjects;
 using System.Reactive.Subjects;
-using Microsoft.Reactive.Testing;
+using System.Threading;
+using System.Threading.Tasks;
 using Avalonia.Data;
 using Avalonia.Data;
 using Avalonia.Logging;
 using Avalonia.Logging;
-using Avalonia.UnitTests;
-using Xunit;
-using System.Threading.Tasks;
+using Avalonia.Markup.Xaml.Data;
 using Avalonia.Platform;
 using Avalonia.Platform;
-using System.Threading;
-using Moq;
-using System.Reactive.Disposables;
-using System.Reactive.Concurrency;
 using Avalonia.Threading;
 using Avalonia.Threading;
+using Avalonia.UnitTests;
+using Avalonia.Diagnostics;
+using Microsoft.Reactive.Testing;
+using Moq;
+using Xunit;
 
 
 namespace Avalonia.Base.UnitTests
 namespace Avalonia.Base.UnitTests
 {
 {
@@ -363,7 +363,7 @@ namespace Avalonia.Base.UnitTests
                 Assert.True(called);
                 Assert.True(called);
             }
             }
         }
         }
-        
+
         [Fact]
         [Fact]
         public async Task Bind_With_Scheduler_Executes_On_Scheduler()
         public async Task Bind_With_Scheduler_Executes_On_Scheduler()
         {
         {
@@ -387,6 +387,77 @@ namespace Avalonia.Base.UnitTests
             }
             }
         }
         }
 
 
+        [Fact]
+        public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Value = 50
+            };
+
+            var target = new Class1();
+
+            target.Bind(Class1.DoubleValueProperty,
+                new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
+
+            var child = new Class1();
+
+            child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty];
+
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+
+            // Issues #855 and #824 were causing a StackOverflowException at this point.
+            target.DoubleValue = 51.001;
+
+            Assert.Equal(2, viewModel.SetterInvokedCount);
+
+            double expected = 51;
+
+            Assert.Equal(expected, viewModel.Value);
+            Assert.Equal(expected, target.DoubleValue);
+            Assert.Equal(expected, child.DoubleValue);
+        }
+
+        [Fact]
+        public void IsAnimating_On_Property_With_No_Value_Returns_False()
+        {
+            var target = new Class1();
+
+            Assert.False(target.IsAnimating(Class1.FooProperty));
+        }
+
+        [Fact]
+        public void IsAnimating_On_Property_With_Animation_Value_Returns_False()
+        {
+            var target = new Class1();
+
+            target.SetValue(Class1.FooProperty, "foo", BindingPriority.Animation);
+
+            Assert.False(target.IsAnimating(Class1.FooProperty));
+        }
+
+        [Fact]
+        public void IsAnimating_On_Property_With_Non_Animation_Binding_Returns_False()
+        {
+            var target = new Class1();
+            var source = new Subject<string>();
+
+            target.Bind(Class1.FooProperty, source, BindingPriority.LocalValue);
+
+            Assert.False(target.IsAnimating(Class1.FooProperty));
+        }
+
+        [Fact]
+        public void IsAnimating_On_Property_With_Animation_Binding_Returns_True()
+        {
+            var target = new Class1();
+            var source = new BehaviorSubject<string>("foo");
+
+            target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
+
+            Assert.True(target.IsAnimating(Class1.FooProperty));
+        }
+
         /// <summary>
         /// <summary>
         /// Returns an observable that returns a single value but does not complete.
         /// Returns an observable that returns a single value but does not complete.
         /// </summary>
         /// </summary>
@@ -405,6 +476,15 @@ namespace Avalonia.Base.UnitTests
 
 
             public static readonly StyledProperty<double> QuxProperty =
             public static readonly StyledProperty<double> QuxProperty =
                 AvaloniaProperty.Register<Class1, double>("Qux", 5.6);
                 AvaloniaProperty.Register<Class1, double>("Qux", 5.6);
+
+            public static readonly StyledProperty<double> DoubleValueProperty =
+                        AvaloniaProperty.Register<Class1, double>(nameof(DoubleValue));
+
+            public double DoubleValue
+            {
+                get { return GetValue(DoubleValueProperty); }
+                set { SetValue(DoubleValueProperty, value); }
+            }
         }
         }
 
 
         private class Class2 : Class1
         private class Class2 : Class1
@@ -431,5 +511,40 @@ namespace Avalonia.Base.UnitTests
                 return InstancedBinding.OneTime(_source);
                 return InstancedBinding.OneTime(_source);
             }
             }
         }
         }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private double _value;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public double Value
+            {
+                get { return _value; }
+                set
+                {
+                    if (_value != value)
+                    {
+                        SetterInvokedCount++;
+                        if (SetterInvokedCount < MaxInvokedCount)
+                        {
+                            _value = (int)value;
+                            if (_value > 75) _value = 75;
+                            if (_value < 25) _value = 25;
+                        }
+                        else
+                        {
+                            _value = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+                    }
+                }
+            }
+        }
     }
     }
-}
+}

+ 120 - 9
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@@ -3,11 +3,18 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Reactive.Subjects;
 using System.Reactive.Subjects;
+using System.Threading;
+using System.Threading.Tasks;
 using Avalonia;
 using Avalonia;
 using Avalonia.Data;
 using Avalonia.Data;
 using Avalonia.Logging;
 using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Threading;
+using Avalonia.Markup.Xaml.Data;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
+using Moq;
 using Xunit;
 using Xunit;
 
 
 namespace Avalonia.Base.UnitTests
 namespace Avalonia.Base.UnitTests
@@ -208,7 +215,7 @@ namespace Avalonia.Base.UnitTests
         {
         {
             var target = new Class1();
             var target = new Class1();
 
 
-            Assert.Throws<ArgumentException>(() => 
+            Assert.Throws<ArgumentException>(() =>
                 target.SetValue(Class1.BarProperty, "newvalue"));
                 target.SetValue(Class1.BarProperty, "newvalue"));
         }
         }
 
 
@@ -217,7 +224,7 @@ namespace Avalonia.Base.UnitTests
         {
         {
             var target = new Class1();
             var target = new Class1();
 
 
-            Assert.Throws<ArgumentException>(() => 
+            Assert.Throws<ArgumentException>(() =>
                 target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
                 target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
         }
         }
 
 
@@ -227,7 +234,7 @@ namespace Avalonia.Base.UnitTests
             var target = new Class1();
             var target = new Class1();
             var source = new Subject<string>();
             var source = new Subject<string>();
 
 
-            Assert.Throws<ArgumentException>(() => 
+            Assert.Throws<ArgumentException>(() =>
                 target.Bind(Class1.BarProperty, source));
                 target.Bind(Class1.BarProperty, source));
         }
         }
 
 
@@ -411,6 +418,28 @@ namespace Avalonia.Base.UnitTests
             Assert.True(called);
             Assert.True(called);
         }
         }
 
 
+        [Fact]
+        public async Task Bind_Executes_On_UIThread()
+        {
+            var target = new Class1();
+            var source = new Subject<object>();
+            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
+
+            var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
+            threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
+                .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
+
+            var services = new TestServices(
+                threadingInterface: threadingInterfaceMock.Object);
+
+            using (UnitTestApplication.Start(services))
+            {
+                target.Bind(Class1.FooProperty, source);
+
+                await Task.Run(() => source.OnNext("foobar"));
+            }
+        }
+
         [Fact]
         [Fact]
         public void AddOwner_Should_Inherit_DefaultBindingMode()
         public void AddOwner_Should_Inherit_DefaultBindingMode()
         {
         {
@@ -439,12 +468,46 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().DefaultBindingMode);
             Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().DefaultBindingMode);
         }
         }
 
 
+        [Fact]
+        public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Value = 50
+            };
+
+            var target = new Class1();
+
+            target.Bind(Class1.DoubleValueProperty, new Binding("Value")
+                                                    {
+                                                        Mode = BindingMode.TwoWay,
+                                                        Source = viewModel
+                                                    });
+
+            var child = new Class1();
+
+            child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty];
+
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+
+            // Issues #855 and #824 were causing a StackOverflowException at this point.
+            target.DoubleValue = 51.001;
+
+            Assert.Equal(2, viewModel.SetterInvokedCount);
+
+            double expected = 51;
+
+            Assert.Equal(expected, viewModel.Value);
+            Assert.Equal(expected, target.DoubleValue);
+            Assert.Equal(expected, child.DoubleValue);
+        }
+
         private class Class1 : AvaloniaObject
         private class Class1 : AvaloniaObject
         {
         {
             public static readonly DirectProperty<Class1, string> FooProperty =
             public static readonly DirectProperty<Class1, string> FooProperty =
                 AvaloniaProperty.RegisterDirect<Class1, string>(
                 AvaloniaProperty.RegisterDirect<Class1, string>(
-                    "Foo", 
-                    o => o.Foo, 
+                    "Foo",
+                    o => o.Foo,
                     (o, v) => o.Foo = v,
                     (o, v) => o.Foo = v,
                     unsetValue: "unset");
                     unsetValue: "unset");
 
 
@@ -453,14 +516,21 @@ namespace Avalonia.Base.UnitTests
 
 
             public static readonly DirectProperty<Class1, int> BazProperty =
             public static readonly DirectProperty<Class1, int> BazProperty =
                 AvaloniaProperty.RegisterDirect<Class1, int>(
                 AvaloniaProperty.RegisterDirect<Class1, int>(
-                    "Bar", 
-                    o => o.Baz, 
-                    (o,v) => o.Baz = v,
+                    "Bar",
+                    o => o.Baz,
+                    (o, v) => o.Baz = v,
                     unsetValue: -1);
                     unsetValue: -1);
 
 
+            public static readonly DirectProperty<Class1, double> DoubleValueProperty =
+                AvaloniaProperty.RegisterDirect<Class1, double>(
+                    nameof(DoubleValue),
+                    o => o.DoubleValue,
+                    (o, v) => o.DoubleValue = v);
+
             private string _foo = "initial";
             private string _foo = "initial";
             private readonly string _bar = "bar";
             private readonly string _bar = "bar";
             private int _baz = 5;
             private int _baz = 5;
+            private double _doubleValue;
 
 
             public string Foo
             public string Foo
             {
             {
@@ -478,6 +548,12 @@ namespace Avalonia.Base.UnitTests
                 get { return _baz; }
                 get { return _baz; }
                 set { SetAndRaise(BazProperty, ref _baz, value); }
                 set { SetAndRaise(BazProperty, ref _baz, value); }
             }
             }
+
+            public double DoubleValue
+            {
+                get { return _doubleValue; }
+                set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
+            }
         }
         }
 
 
         private class Class2 : AvaloniaObject
         private class Class2 : AvaloniaObject
@@ -497,5 +573,40 @@ namespace Avalonia.Base.UnitTests
                 set { SetAndRaise(FooProperty, ref _foo, value); }
                 set { SetAndRaise(FooProperty, ref _foo, value); }
             }
             }
         }
         }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private double _value;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public double Value
+            {
+                get { return _value; }
+                set
+                {
+                    if (_value != value)
+                    {
+                        SetterInvokedCount++;
+                        if (SetterInvokedCount < MaxInvokedCount)
+                        {
+                            _value = (int)value;
+                            if (_value > 75) _value = 75;
+                            if (_value < 25) _value = 25;
+                        }
+                        else
+                        {
+                            _value = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+                    }
+                }
+            }
+        }
     }
     }
-}
+}

+ 12 - 0
tests/Avalonia.Base.UnitTests/DirectPropertyTests.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.Reactive.Subjects;
 using Avalonia.Data;
 using Avalonia.Data;
 using Xunit;
 using Xunit;
 
 
@@ -70,6 +71,17 @@ namespace Avalonia.Base.UnitTests
             Assert.Same(p1.Initialized, p2.Initialized);
             Assert.Same(p1.Initialized, p2.Initialized);
         }
         }
 
 
+        [Fact]
+        public void IsAnimating_On_DirectProperty_With_Binding_Returns_False()
+        {
+            var target = new Class1();
+            var source = new BehaviorSubject<string>("foo");
+
+            target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
+
+            Assert.False(target.IsAnimating(Class1.FooProperty));
+        }
+
         private class Class1 : AvaloniaObject
         private class Class1 : AvaloniaObject
         {
         {
             public static readonly DirectProperty<Class1, string> FooProperty =
             public static readonly DirectProperty<Class1, string> FooProperty =

+ 4 - 4
tests/Avalonia.Benchmarks/App.config

@@ -1,13 +1,13 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <configuration>
 <configuration>
     <startup> 
     <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
     </startup>
     </startup>
   <runtime>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
       <dependentAssembly>
-        <assemblyIdentity name="Moq" publicKeyToken="69f491c39445e920" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-4.2.1510.2205" newVersion="4.2.1510.2205" />
+        <assemblyIdentity name="Moq" publicKeyToken="69f491c39445e920" culture="neutral"/>
+        <bindingRedirect oldVersion="0.0.0.0-4.2.1510.2205" newVersion="4.2.1510.2205"/>
       </dependentAssembly>
       </dependentAssembly>
     </assemblyBinding>
     </assemblyBinding>
   </runtime>
   </runtime>

+ 2 - 1
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@@ -9,7 +9,7 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>Avalonia.Benchmarks</RootNamespace>
     <RootNamespace>Avalonia.Benchmarks</RootNamespace>
     <AssemblyName>Avalonia.Benchmarks</AssemblyName>
     <AssemblyName>Avalonia.Benchmarks</AssemblyName>
-    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <TargetFrameworkProfile />
     <TargetFrameworkProfile />
@@ -49,6 +49,7 @@
     <Reference Include="System.Xml" />
     <Reference Include="System.Xml" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
+    <Compile Include="Base\Properties.cs" />
     <Compile Include="Layout\Measure.cs" />
     <Compile Include="Layout\Measure.cs" />
     <Compile Include="Styling\ApplyStyling.cs" />
     <Compile Include="Styling\ApplyStyling.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="Program.cs" />

+ 43 - 0
tests/Avalonia.Benchmarks/Base/Properties.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Reactive.Subjects;
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Base
+{
+    [MemoryDiagnoser]
+    public class AvaloniaObjectBenchmark
+    {
+        private Class1 target = new Class1();
+        private Subject<int> intBinding = new Subject<int>();
+
+        public AvaloniaObjectBenchmark()
+        {
+            target.SetValue(Class1.IntProperty, 123);
+        }
+
+        [Benchmark]
+        public void ClearAndSetIntProperty()
+        {
+            target.ClearValue(Class1.IntProperty);
+            target.SetValue(Class1.IntProperty, 123);
+        }
+
+        [Benchmark]
+        public void BindIntProperty()
+        {
+            using (target.Bind(Class1.IntProperty, intBinding))
+            {
+                for (var i = 0; i < 100; ++i)
+                {
+                    intBinding.OnNext(i);
+                }
+            }
+        }
+
+        class Class1 : AvaloniaObject
+        {
+            public static readonly AvaloniaProperty<int> IntProperty =
+                AvaloniaProperty.Register<Class1, int>("Int");
+        }
+    }
+}

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

@@ -65,6 +65,7 @@ namespace Avalonia.Controls.UnitTests
             {
             {
                 ResetModuleLoadStates();
                 ResetModuleLoadStates();
                 AppBuilder.Configure<App>()
                 AppBuilder.Configure<App>()
+                    .IgnoreSetupCheck()
                     .UseWindowingSubsystem(() => { })
                     .UseWindowingSubsystem(() => { })
                     .UseRenderingSubsystem(() => { })
                     .UseRenderingSubsystem(() => { })
                     .UseAvaloniaModules()
                     .UseAvaloniaModules()
@@ -81,6 +82,7 @@ namespace Avalonia.Controls.UnitTests
             {
             {
                 ResetModuleLoadStates();
                 ResetModuleLoadStates();
                 var builder = AppBuilder.Configure<App>()
                 var builder = AppBuilder.Configure<App>()
+                    .IgnoreSetupCheck()
                     .UseWindowingSubsystem(() => { })
                     .UseWindowingSubsystem(() => { })
                     .UseRenderingSubsystem(() => { }, "Direct2D1");
                     .UseRenderingSubsystem(() => { }, "Direct2D1");
                 builder.UseAvaloniaModules().SetupWithoutStarting();
                 builder.UseAvaloniaModules().SetupWithoutStarting();
@@ -90,6 +92,7 @@ namespace Avalonia.Controls.UnitTests
 
 
                 ResetModuleLoadStates();
                 ResetModuleLoadStates();
                 builder = AppBuilder.Configure<App>()
                 builder = AppBuilder.Configure<App>()
+                    .IgnoreSetupCheck()
                     .UseWindowingSubsystem(() => { })
                     .UseWindowingSubsystem(() => { })
                     .UseRenderingSubsystem(() => { }, "Skia");
                     .UseRenderingSubsystem(() => { }, "Skia");
                 builder.UseAvaloniaModules().SetupWithoutStarting();
                 builder.UseAvaloniaModules().SetupWithoutStarting();
@@ -99,13 +102,14 @@ namespace Avalonia.Controls.UnitTests
             }
             }
         }
         }
 
 
-        [Fact (Skip = "We don't have rendering modules with dependencies right now")]
+        [Fact]
         public void LoadsRenderingModuleWithoutDependenciesWhenNoModuleMatches()
         public void LoadsRenderingModuleWithoutDependenciesWhenNoModuleMatches()
         {
         {
             using (AvaloniaLocator.EnterScope())
             using (AvaloniaLocator.EnterScope())
             {
             {
                 ResetModuleLoadStates();
                 ResetModuleLoadStates();
                 var builder = AppBuilder.Configure<App>()
                 var builder = AppBuilder.Configure<App>()
+                    .IgnoreSetupCheck()
                     .UseWindowingSubsystem(() => { })
                     .UseWindowingSubsystem(() => { })
                     .UseRenderingSubsystem(() => { }, "TBD");
                     .UseRenderingSubsystem(() => { }, "TBD");
                 builder.UseAvaloniaModules().SetupWithoutStarting();
                 builder.UseAvaloniaModules().SetupWithoutStarting();

+ 1 - 1
tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
 <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
+    <TargetFrameworks>net47;netcoreapp2.0</TargetFrameworks>
     <OutputType>Library</OutputType>
     <OutputType>Library</OutputType>
   </PropertyGroup>
   </PropertyGroup>
   <Import Project="..\..\build\UnitTests.NetCore.targets" />
   <Import Project="..\..\build\UnitTests.NetCore.targets" />

+ 58 - 0
tests/Avalonia.Controls.UnitTests/ContentControlTests.cs

@@ -1,6 +1,7 @@
 // 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;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.Linq;
 using System.Linq;
 using Moq;
 using Moq;
@@ -11,6 +12,9 @@ using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 using Xunit;
 using Xunit;
+using Avalonia.Markup.Xaml.Data;
+using Avalonia.Data;
+using System.Collections.Generic;
 
 
 namespace Avalonia.Controls.UnitTests
 namespace Avalonia.Controls.UnitTests
 {
 {
@@ -273,6 +277,60 @@ namespace Avalonia.Controls.UnitTests
             Assert.Null(target.Presenter.Child.DataContext);
             Assert.Null(target.Presenter.Child.DataContext);
         }
         }
 
 
+        [Fact]
+        public void Binding_ContentTemplate_After_Content_Does_Not_Leave_Orpaned_TextBlock()
+        {
+            // Test for #1271.
+            var children = new List<IControl>();
+            var presenter = new ContentPresenter();
+
+            // The content and then the content template property need to be bound with delayed bindings
+            // as they are in Avalonia.Markup.Xaml.
+            DelayedBinding.Add(presenter, ContentPresenter.ContentProperty, new Binding("Content")
+            {
+                Priority = BindingPriority.TemplatedParent,
+                RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
+            });
+
+            DelayedBinding.Add(presenter, ContentPresenter.ContentTemplateProperty, new Binding("ContentTemplate")
+            {
+                Priority = BindingPriority.TemplatedParent,
+                RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
+            });
+
+            presenter.GetObservable(ContentPresenter.ChildProperty).Subscribe(children.Add);
+
+            var target = new ContentControl
+            {
+                Template = new FuncControlTemplate<ContentControl>(_ => presenter),
+                ContentTemplate = new FuncDataTemplate<string>(x => new Canvas()),
+                Content = "foo",
+            };
+                        
+            // The control must be rooted.
+            var root = new TestRoot
+            {
+                Child = target,
+            };
+
+            target.ApplyTemplate();
+
+            // When the template is applied, the Content property is bound before the ContentTemplate
+            // property, causing a TextBlock to be created by the default template before ContentTemplate
+            // is bound.
+            Assert.Collection(
+                children,
+                x => Assert.Null(x),
+                x => Assert.IsType<TextBlock>(x),
+                x => Assert.IsType<Canvas>(x));
+
+            var textBlock = (TextBlock)children[1];
+
+            // The leak in #1271 was caused by the TextBlock's logical parent not being cleared when
+            // it is replaced by the Canvas.
+            Assert.Null(textBlock.GetLogicalParent());
+        }
+
         private FuncControlTemplate GetTemplate()
         private FuncControlTemplate GetTemplate()
         {
         {
             return new FuncControlTemplate<ContentControl>(parent =>
             return new FuncControlTemplate<ContentControl>(parent =>

+ 17 - 1
tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs

@@ -168,7 +168,7 @@ namespace Avalonia.Controls.UnitTests
             target.Resources.Add("foo", "bar");
             target.Resources.Add("foo", "bar");
 
 
             Assert.True(raisedOnTarget);
             Assert.True(raisedOnTarget);
-            Assert.False(raisedOnPresenter);
+            Assert.True(raisedOnPresenter);
             Assert.True(raisedOnChild);
             Assert.True(raisedOnChild);
         }
         }
 
 
@@ -204,6 +204,22 @@ namespace Avalonia.Controls.UnitTests
             Assert.True(raised);
             Assert.True(raised);
         }
         }
 
 
+        [Fact]
+        public void Setting_Logical_Parent_Subscribes_To_Parents_ResourceChanged_Event()
+        {
+            var parent = new ContentControl();
+            var child = new Border();
+
+            ((ISetLogicalParent)child).SetParent(parent);
+            var raisedOnChild = false;
+
+            child.ResourcesChanged += (_, __) => raisedOnChild = true;
+
+            parent.Resources.Add("foo", "bar");
+
+            Assert.True(raisedOnChild);
+        }
+
         private IControlTemplate ContentControlTemplate()
         private IControlTemplate ContentControlTemplate()
         {
         {
             return new FuncControlTemplate<ContentControl>(x =>
             return new FuncControlTemplate<ContentControl>(x =>

+ 2 - 2
tests/Avalonia.Controls.UnitTests/DockPanelTests.cs

@@ -12,7 +12,7 @@ namespace Avalonia.Controls.UnitTests
         {
         {
             var target = new DockPanel
             var target = new DockPanel
             {
             {
-                Children = new Controls
+                Children =
                 {
                 {
                     new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top },
                     new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top },
                     new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom },
                     new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom },
@@ -38,7 +38,7 @@ namespace Avalonia.Controls.UnitTests
         {
         {
             var target = new DockPanel
             var target = new DockPanel
             {
             {
-                Children = new Controls
+                Children =
                 {
                 {
                     new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left },
                     new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left },
                     new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right },
                     new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right },

+ 1 - 1
tests/Avalonia.Controls.UnitTests/DropDownTests.cs

@@ -89,7 +89,7 @@ namespace Avalonia.Controls.UnitTests
                 return new Panel
                 return new Panel
                 {
                 {
                     Name = "container",
                     Name = "container",
-                    Children = new Controls
+                    Children =
                     {
                     {
                         new ContentControl
                         new ContentControl
                         {
                         {

+ 42 - 42
tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs

@@ -22,14 +22,14 @@ namespace Avalonia.Controls.UnitTests
         {
         {
             var grid = new Grid()
             var grid = new Grid()
                        {
                        {
-                           RowDefinitions = new RowDefinitions("*,Auto,*"),
-                           ColumnDefinitions = new ColumnDefinitions("*,*"),
-                           Children = new Controls()
-                                      {
-                                          new Border { [Grid.RowProperty] = 0 },
-                                          new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" },
-                                          new Border { [Grid.RowProperty] = 2 }
-                                      }
+                            RowDefinitions = new RowDefinitions("*,Auto,*"),
+                            ColumnDefinitions = new ColumnDefinitions("*,*"),
+                            Children =
+                            {
+                                new Border { [Grid.RowProperty] = 0 },
+                                new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" },
+                                new Border { [Grid.RowProperty] = 2 }
+                            }
                        };
                        };
 
 
             var root = new TestRoot { Child = grid };
             var root = new TestRoot { Child = grid };
@@ -43,14 +43,14 @@ namespace Avalonia.Controls.UnitTests
         {
         {
             var grid = new Grid()
             var grid = new Grid()
                        {
                        {
-                           ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
-                           RowDefinitions = new RowDefinitions("*,*"),
-                           Children = new Controls()
-                                      {
-                                          new Border { [Grid.ColumnProperty] = 0 },
-                                          new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
-                                          new Border { [Grid.ColumnProperty] = 2 },
-                                      }
+                            ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
+                            RowDefinitions = new RowDefinitions("*,*"),
+                            Children =
+                            {
+                                new Border { [Grid.ColumnProperty] = 0 },
+                                new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
+                                new Border { [Grid.ColumnProperty] = 2 },
+                            }
                        };
                        };
 
 
             var root = new TestRoot { Child = grid };
             var root = new TestRoot { Child = grid };
@@ -64,14 +64,14 @@ namespace Avalonia.Controls.UnitTests
         {
         {
             var grid = new Grid()
             var grid = new Grid()
                        {
                        {
-                           ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
-                           RowDefinitions = new RowDefinitions("Auto,Auto"),
-                           Children = new Controls()
-                                      {
-                                          new Border { [Grid.ColumnProperty] = 0 },
-                                          new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
-                                          new Border { [Grid.ColumnProperty] = 2 },
-                                      }
+                            ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
+                            RowDefinitions = new RowDefinitions("Auto,Auto"),
+                            Children =
+                            {
+                                new Border { [Grid.ColumnProperty] = 0 },
+                                new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
+                                new Border { [Grid.ColumnProperty] = 2 },
+                            }
                        };
                        };
 
 
             var root = new TestRoot { Child = grid };
             var root = new TestRoot { Child = grid };
@@ -99,11 +99,11 @@ namespace Avalonia.Controls.UnitTests
 
 
             var grid = new Grid()
             var grid = new Grid()
                        {
                        {
-                           RowDefinitions = rowDefinitions,
-                           Children = new Controls()
-                                      {
-                                          control1, splitter, control2
-                                      }
+                            RowDefinitions = rowDefinitions,
+                            Children =
+                            {
+                                control1, splitter, control2
+                            }
                        };
                        };
 
 
             var root = new TestRoot { Child = grid };
             var root = new TestRoot { Child = grid };
@@ -131,14 +131,14 @@ namespace Avalonia.Controls.UnitTests
         {
         {
             var grid = new Grid()
             var grid = new Grid()
                        {
                        {
-                           ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
-                           RowDefinitions = new RowDefinitions("*,*"),
-                           Children = new Controls()
-                                      {
-                                          new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" },
-                                          new Border { [Grid.ColumnProperty] = 1 },
-                                          new Border { [Grid.ColumnProperty] = 2 },
-                                      }
+                            ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
+                            RowDefinitions = new RowDefinitions("*,*"),
+                            Children =
+                            {
+                                new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" },
+                                new Border { [Grid.ColumnProperty] = 1 },
+                                new Border { [Grid.ColumnProperty] = 2 },
+                            }
                        };
                        };
 
 
             var root = new TestRoot { Child = grid };
             var root = new TestRoot { Child = grid };
@@ -171,11 +171,11 @@ namespace Avalonia.Controls.UnitTests
 
 
             var grid = new Grid()
             var grid = new Grid()
                        {
                        {
-                           ColumnDefinitions = columnDefinitions,
-                           Children = new Controls()
-                                      {
-                                          control1, splitter, control2
-                                      }
+                            ColumnDefinitions = columnDefinitions,
+                            Children =
+                            {
+                                control1, splitter, control2
+                            }
                        };
                        };
 
 
             var root = new TestRoot { Child = grid };
             var root = new TestRoot { Child = grid };

+ 1 - 1
tests/Avalonia.Controls.UnitTests/GridTests.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Controls.UnitTests
                     new RowDefinition(GridLength.Auto),
                     new RowDefinition(GridLength.Auto),
                     new RowDefinition(GridLength.Auto),
                     new RowDefinition(GridLength.Auto),
                 },
                 },
-                Children = new Controls
+                Children =
                 {
                 {
                     new Border
                     new Border
                     {
                     {

+ 70 - 1
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@@ -1,11 +1,15 @@
 // 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.Collections.Generic;
+using System.ComponentModel;
 using System.Linq;
 using System.Linq;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Templates;
 using Avalonia.Controls.Templates;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Input;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
+using Avalonia.Markup.Xaml.Data;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 using Xunit;
 using Xunit;
@@ -199,6 +203,71 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(1, target.SelectedIndex);
             Assert.Equal(1, target.SelectedIndex);
         }
         }
 
 
+        [Fact]
+        public void SelectedItem_Should_Not_Cause_StackOverflow()
+        {
+            var viewModel = new TestStackOverflowViewModel()
+            {
+                Items = new List<string> { "foo", "bar", "baz" }
+            };
+
+            var target = new ListBox
+            {
+                Template = new FuncControlTemplate(CreateListBoxTemplate),
+                DataContext = viewModel,
+                Items = viewModel.Items
+            };
+
+            target.Bind(ListBox.SelectedItemProperty,
+                new Binding("SelectedItem") { Mode = BindingMode.TwoWay });
+
+            Assert.Equal(0, viewModel.SetterInvokedCount);
+
+            // In Issue #855, a Stackoverflow occured here.
+            target.SelectedItem = viewModel.Items[2];
+
+            Assert.Equal(viewModel.Items[1], target.SelectedItem);
+            Assert.Equal(1, viewModel.SetterInvokedCount);
+        }
+
+        private class TestStackOverflowViewModel : INotifyPropertyChanged
+        {
+            public List<string> Items { get; set; }
+
+            public int SetterInvokedCount { get; private set; }
+
+            public const int MaxInvokedCount = 1000;
+
+            private string _selectedItem;
+
+            public event PropertyChangedEventHandler PropertyChanged;
+
+            public string SelectedItem
+            {
+                get { return _selectedItem; }
+                set
+                {
+                    if (_selectedItem != value)
+                    {
+                        SetterInvokedCount++;
+
+                        int index = Items.IndexOf(value);
+
+                        if (MaxInvokedCount > SetterInvokedCount && index > 0)
+                        {
+                            _selectedItem = Items[index - 1];
+                        }
+                        else
+                        {
+                            _selectedItem = value;
+                        }
+
+                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
+                    }
+                }
+            }
+        }
+
         private Control CreateListBoxTemplate(ITemplatedControl parent)
         private Control CreateListBoxTemplate(ITemplatedControl parent)
         {
         {
             return new ScrollViewer
             return new ScrollViewer
@@ -237,4 +306,4 @@ namespace Avalonia.Controls.UnitTests
             target.Presenter.ApplyTemplate();
             target.Presenter.ApplyTemplate();
         }
         }
     }
     }
-}
+}

+ 0 - 45
tests/Avalonia.Controls.UnitTests/PanelTests.cs

@@ -2,7 +2,6 @@
 // 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.Linq;
 using System.Linq;
-using Avalonia.Collections;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 using Xunit;
 using Xunit;
@@ -24,18 +23,6 @@ namespace Avalonia.Controls.UnitTests
             Assert.Same(child.GetVisualParent(), panel);
             Assert.Same(child.GetVisualParent(), panel);
         }
         }
 
 
-        [Fact]
-        public void Setting_Controls_Should_Set_Child_Controls_Parent()
-        {
-            var panel = new Panel();
-            var child = new Control();
-
-            panel.Children = new Controls { child };
-
-            Assert.Equal(child.Parent, panel);
-            Assert.Equal(child.GetLogicalParent(), panel);
-        }
-
         [Fact]
         [Fact]
         public void Removing_Control_From_Panel_Should_Clear_Child_Controls_Parent()
         public void Removing_Control_From_Panel_Should_Clear_Child_Controls_Parent()
         {
         {
@@ -69,25 +56,6 @@ namespace Avalonia.Controls.UnitTests
             Assert.Null(child2.GetVisualParent());
             Assert.Null(child2.GetVisualParent());
         }
         }
 
 
-        [Fact]
-        public void Resetting_Panel_Children_Should_Clear_Child_Controls_Parent()
-        {
-            var panel = new Panel();
-            var child1 = new Control();
-            var child2 = new Control();
-
-            panel.Children.Add(child1);
-            panel.Children.Add(child2);
-            panel.Children = new Controls();
-
-            Assert.Null(child1.Parent);
-            Assert.Null(child1.GetLogicalParent());
-            Assert.Null(child1.GetVisualParent());
-            Assert.Null(child2.Parent);
-            Assert.Null(child2.GetLogicalParent());
-            Assert.Null(child2.GetVisualParent());
-        }
-
         [Fact]
         [Fact]
         public void Replacing_Panel_Children_Should_Clear_And_Set_Control_Parent()
         public void Replacing_Panel_Children_Should_Clear_And_Set_Control_Parent()
         {
         {
@@ -147,18 +115,5 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren());
             Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren());
             Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren());
             Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren());
         }
         }
-
-        [Fact]
-        public void Setting_Children_Should_Make_Controls_Appear_In_Logical_And_Visual_Children()
-        {
-            var panel = new Panel();
-            var child = new Control();
-
-            panel.Children = new Controls { child };
-
-            Assert.Equal(new[] { child }, panel.Children);
-            Assert.Equal(new[] { child }, panel.GetLogicalChildren());
-            Assert.Equal(new[] { child }, panel.GetVisualChildren());
-        }
     }
     }
 }
 }

+ 70 - 1
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@@ -2,11 +2,14 @@
 // 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.Controls.Presenters;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Controls.Templates;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
+using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
+using Avalonia.VisualTree;
 using Xunit;
 using Xunit;
 
 
 namespace Avalonia.Controls.UnitTests.Primitives
 namespace Avalonia.Controls.UnitTests.Primitives
@@ -35,6 +38,25 @@ namespace Avalonia.Controls.UnitTests.Primitives
             }
             }
         }
         }
 
 
+        [Fact]
+        public void PopupRoot_StylingParent_Is_Popup()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var target = new TemplatedControlWithPopup
+                {
+                    PopupContent = new Canvas(),
+                };
+
+                var root = new TestRoot { Child = target };
+
+                target.ApplyTemplate();
+                target.Popup.Open();
+
+                Assert.Equal(target.Popup, ((IStyleHost)target.Popup.PopupRoot).StylingParent);
+            }
+        }
+
         [Fact]
         [Fact]
         public void Attaching_PopupRoot_To_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree()
         public void Attaching_PopupRoot_To_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree()
         {
         {
@@ -90,14 +112,33 @@ namespace Avalonia.Controls.UnitTests.Primitives
             }
             }
         }
         }
 
 
+        [Fact]
+        public void Clearing_Content_Of_Popup_In_ControlTemplate_Doesnt_Crash()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var target = new TemplatedControlWithPopup
+                {
+                    PopupContent = new Canvas(),
+                };
+
+                var root = new TestRoot { Child = target };
+
+                target.ApplyTemplate();
+                target.Popup.Open();
+                target.PopupContent = null;
+            }
+        }
+
         private PopupRoot CreateTarget()
         private PopupRoot CreateTarget()
         {
         {
             var result = new PopupRoot
             var result = new PopupRoot
             {
             {
-                Template = new FuncControlTemplate<PopupRoot>(_ =>
+                Template = new FuncControlTemplate<PopupRoot>(parent =>
                     new ContentPresenter
                     new ContentPresenter
                     {
                     {
                         Name = "PART_ContentPresenter",
                         Name = "PART_ContentPresenter",
+                        [!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty],
                     }),
                     }),
             };
             };
 
 
@@ -105,5 +146,33 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
 
             return result;
             return result;
         }
         }
+
+        private class TemplatedControlWithPopup : TemplatedControl
+        {
+            public static readonly AvaloniaProperty<Control> PopupContentProperty =
+                AvaloniaProperty.Register<TemplatedControlWithPopup, Control>(nameof(PopupContent));
+
+            public TemplatedControlWithPopup()
+            {
+                Template = new FuncControlTemplate<TemplatedControlWithPopup>(parent =>
+                    new Popup
+                    {
+                        [!Popup.ChildProperty] = parent[!TemplatedControlWithPopup.PopupContentProperty],
+                    });
+            }
+
+            public Popup Popup { get; private set; }
+
+            public Control PopupContent
+            {
+                get => GetValue(PopupContentProperty);
+                set => SetValue(PopupContentProperty, value);
+            }
+
+            protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+            {
+                Popup = (Popup)this.GetVisualChildren().Single();
+            }
+        }
     }
     }
 }
 }

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно