diff --git a/.gitignore b/.gitignore
index c36f64e5de..640725fa26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -162,7 +162,8 @@ $RECYCLE.BIN/
#################
## Cake
#################
-tools/
+tools/*
+!tools/packages.config
.nuget
artifacts/
nuget
diff --git a/Avalonia.sln b/Avalonia.sln
index f12af02236..afaee6a907 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -191,6 +191,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
@@ -2589,6 +2591,46 @@ Global
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.Build.0 = Release|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.ActiveCfg = Release|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2649,5 +2691,6 @@ Global
{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
EndGlobalSection
EndGlobal
diff --git a/appveyor.yml b/appveyor.yml
index a52b2dfa70..8409cac57c 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -13,6 +13,8 @@ environment:
MYGET_API_KEY:
secure: OtVfyN3ErqQrDTnWH2HDfJDlCiu/i4/X4wFmK3ZXXP7HmCiXYPSbTjMPwwdOxRaK
MYGET_API_URL: https://www.myget.org/F/avalonia-ci/api/v2/package
+init:
+- ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")}
install:
- if not exist gtk-sharp-2.12.26.msi appveyor DownloadFile http://download.xamarin.com/GTKforWindows/Windows/gtk-sharp-2.12.26.msi
- if not exist dotnet-1.0.1.exe appveyor DownloadFile https://go.microsoft.com/fwlink/?linkid=843448 -FileName "dotnet-1.0.1.exe"
diff --git a/build.cake b/build.cake
index 021e1e0cc3..801acac324 100644
--- a/build.cake
+++ b/build.cake
@@ -11,7 +11,7 @@
// TOOLS
///////////////////////////////////////////////////////////////////////////////
-#tool "nuget:?package=xunit.runner.console&version=2.1.0"
+#tool "nuget:?package=xunit.runner.console&version=2.2.0"
#tool "nuget:?package=OpenCover"
///////////////////////////////////////////////////////////////////////////////
@@ -98,7 +98,6 @@ Task("Clean")
CleanDirectory(parameters.TestsRoot);
});
-
Task("Restore-NuGet-Packages")
.IsDependentOn("Clean")
.WithCriteria(parameters.IsRunningOnWindows)
@@ -170,23 +169,25 @@ void RunCoreTest(string dir, Parameters parameters, bool net461Only)
continue;
Information("Running for " + fw);
DotNetCoreTest(System.IO.Path.Combine(dir, System.IO.Path.GetFileName(dir)+".csproj"),
- new DotNetCoreTestSettings{Framework = fw});
+ new DotNetCoreTestSettings {
+ Configuration = parameters.Configuration,
+ Framework = fw
+ });
}
}
-
Task("Run-Net-Core-Unit-Tests")
.IsDependentOn("Clean")
.Does(() => {
RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
- RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, true);
- RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, true);
- RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, true);
- RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, true);
- //RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, true);
- //RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, true);
- RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, true);
- RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, true);
+ RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
+ RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false);
+ RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false);
+ RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false);
+ RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false);
+ RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
+ RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
+ RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
});
Task("Run-Unit-Tests")
diff --git a/build/Base.props b/build/Base.props
new file mode 100644
index 0000000000..6689465338
--- /dev/null
+++ b/build/Base.props
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/build/Moq.props b/build/Moq.props
index c8544b8309..55242d922e 100644
--- a/build/Moq.props
+++ b/build/Moq.props
@@ -1,5 +1,5 @@
-
+
diff --git a/build/SharpDX.props b/build/SharpDX.props
index e381bc03e6..0eb910e71e 100644
--- a/build/SharpDX.props
+++ b/build/SharpDX.props
@@ -3,6 +3,7 @@
+
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index 77407f9996..04e8a3ad4f 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,6 +1,6 @@
-
+
diff --git a/build/XUnit.props b/build/XUnit.props
index 58df7e8d3c..27e0afc987 100644
--- a/build/XUnit.props
+++ b/build/XUnit.props
@@ -7,7 +7,9 @@
-
-
+
+
+
+
diff --git a/docs/tutorial/from-wpf.md b/docs/tutorial/from-wpf.md
index aa7a9bc13a..2db40cfd86 100644
--- a/docs/tutorial/from-wpf.md
+++ b/docs/tutorial/from-wpf.md
@@ -33,7 +33,7 @@ placed in a `DataTemplates` collection on each control (and on `Application`):
-
+
diff --git a/packages.cake b/packages.cake
index 1e8e356694..07c77f64fd 100644
--- a/packages.cake
+++ b/packages.cake
@@ -75,22 +75,26 @@ public class Packages
var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
+ var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1;
SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1;
SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1;
var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1;
var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1;
var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1;
+ var SharpDXDirect3D9Version = packageVersions["SharpDX.Direct3D9"].FirstOrDefault().Item1;
var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1;
context.Information("Package: Serilog, version: {0}", SerilogVersion);
context.Information("Package: Splat, version: {0}", SplatVersion);
context.Information("Package: Sprache, version: {0}", SpracheVersion);
context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
+ context.Information("Package: System.ValueTuple, version: {0}", SystemValueTupleVersion);
context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion);
context.Information("Package: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion);
context.Information("Package: SharpDX, version: {0}", SharpDXVersion);
context.Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version);
context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version);
+ context.Information("Package: SharpDX.Direct3D9, version: {0}", SharpDXDirect3D9Version);
context.Information("Package: SharpDX.DXGI, version: {0}", SharpDXDXGIVersion);
var nugetPackagesDir = System.Environment.GetEnvironmentVariable("NUGET_HOME")
@@ -197,6 +201,7 @@ public class Packages
new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
+ new NuSpecDependency() { Id = "System.ValueTuple", Version = SystemValueTupleVersion },
//.NET Core
new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" },
new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp1.0", Version = "1.1.0" },
@@ -204,7 +209,8 @@ public class Packages
new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp1.0", Version = SplatVersion },
new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion },
new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp1.0", Version = SpracheVersion },
- new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion }
+ new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion },
+ new NuSpecDependency() { Id = "System.ValueTuple", TargetFramework = "netcoreapp1.0", Version = SystemValueTupleVersion }
},
Files = coreLibrariesNuSpecContent
.Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
@@ -465,6 +471,22 @@ public class Packages
BasePath = context.Directory("./"),
OutputDirectory = parameters.NugetRoot
},
+ new NuGetPackSettings()
+ {
+ Id = "Avalonia.Win32.Interoperability",
+ Dependencies = new []
+ {
+ new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
+ new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
+ new NuSpecDependency() { Id = "SharpDX.Direct3D9", Version = SharpDXDirect3D9Version },
+ },
+ Files = new []
+ {
+ new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" }
+ },
+ BasePath = context.Directory("./src/Windows"),
+ OutputDirectory = parameters.NugetRoot
+ },
///////////////////////////////////////////////////////////////////////////////
// Avalonia.LinuxFramebuffer
///////////////////////////////////////////////////////////////////////////////
diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml
index 98171f29d6..9c5591c849 100644
--- a/samples/ControlCatalog/Pages/MenuPage.xaml
+++ b/samples/ControlCatalog/Pages/MenuPage.xaml
@@ -31,5 +31,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
index 1115cf5768..1d8dc32a69 100644
--- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
+++ b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
@@ -1,10 +1,12 @@
@@ -14,8 +16,18 @@
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
index e60c9ced0a..c7a23c22fc 100644
--- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
+++ b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
@@ -11,7 +11,9 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
+using Avalonia;
using Avalonia.Controls;
+using Avalonia.VisualTree;
using ControlCatalog;
using Window = System.Windows.Window;
@@ -25,7 +27,18 @@ namespace WindowsInteropTest
public EmbedToWpfDemo()
{
InitializeComponent();
- Host.Content = new MainView();
+ var view = new MainView();
+ view.AttachedToVisualTree += delegate
+ {
+ ((TopLevel) view.GetVisualRoot()).AttachDevTools();
+ };
+ Host.Content = view;
+ var btn = (Avalonia.Controls.Button) RightBtn.Content;
+ btn.Click += delegate
+ {
+ btn.Content += "!";
+ };
+
}
}
}
diff --git a/samples/interop/WindowsInteropTest/Program.cs b/samples/interop/WindowsInteropTest/Program.cs
index 4770688ecf..fac06d74b0 100644
--- a/samples/interop/WindowsInteropTest/Program.cs
+++ b/samples/interop/WindowsInteropTest/Program.cs
@@ -15,7 +15,7 @@ namespace WindowsInteropTest
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
- AppBuilder.Configure().UseWin32().UseSkia().SetupWithoutStarting();
+ AppBuilder.Configure().UseWin32().UseDirect2D1().SetupWithoutStarting();
System.Windows.Forms.Application.Run(new SelectorForm());
}
}
diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
index 3891c2bc5c..84bd709f8c 100644
--- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
+++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
@@ -14,7 +14,7 @@
true
- AnyCPU
+ x86
true
full
false
@@ -164,6 +164,10 @@
{3e908f67-5543-4879-a1dc-08eace79b3cd}
Avalonia.Direct2D1
+
+ {cbc4ff2f-92d4-420b-be21-9fe0b930b04e}
+ Avalonia.Win32.Interop
+
{811a76cf-1cf6-440f-963b-bbe31bd72a82}
Avalonia.Win32
@@ -181,4 +185,5 @@
+
\ No newline at end of file
diff --git a/scripts/ReplaceNugetCache.ps1 b/scripts/ReplaceNugetCache.ps1
new file mode 100644
index 0000000000..854442eb09
--- /dev/null
+++ b/scripts/ReplaceNugetCache.ps1
@@ -0,0 +1,5 @@
+copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp1.0\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard1.1\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard1.1\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.skia.desktop\$args\lib\netstandard1.3\
+copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard1.1\
diff --git a/scripts/ReplaceNugetCache.sh b/scripts/ReplaceNugetCache.sh
new file mode 100755
index 0000000000..2ce3e7648d
--- /dev/null
+++ b/scripts/ReplaceNugetCache.sh
@@ -0,0 +1,7 @@
+ #!/usr/bin/env bash
+
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp1.0/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard1.1/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard1.1/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.skia.desktop/$1/lib/netstandard1.3/
+
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index c779031e6c..e9b4ad0a6d 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -51,7 +51,6 @@ namespace Avalonia.Android
.Bind().ToTransient()
.Bind().ToTransient()
.Bind().ToSingleton()
- .Bind().ToSingleton()
.Bind().ToConstant(Instance)
.Bind().ToConstant(ImmediateRenderer.Factory)
.Bind().ToConstant(new AndroidThreadingInterface())
diff --git a/src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs b/src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
index 9ae79efb48..d52eeb15e4 100644
--- a/src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
+++ b/src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
@@ -4,6 +4,8 @@ namespace Avalonia.Android.Platform.Input
{
public class AndroidMouseDevice : MouseDevice
{
+ public static AndroidMouseDevice Instance { get; } = new AndroidMouseDevice();
+
public AndroidMouseDevice()
{
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
index 64dbeb89cc..051a058363 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
@@ -44,7 +44,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public int Width { get; }
public int Height { get; }
public int RowBytes { get; }
- public Size Dpi { get; } = new Size(96, 96);
+ public Vector Dpi { get; } = new Vector(96, 96);
public PixelFormat Format { get; }
[DllImport("android")]
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 75772be171..0c62eb9060 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -10,6 +10,7 @@ using Avalonia.Platform;
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
+using Avalonia.Android.Platform.Input;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
@@ -65,6 +66,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
}
+ public IMouseDevice MouseDevice => AndroidMouseDevice.Instance;
+
public Action Closed { get; set; }
public Action Input { get; set; }
diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
index 702829b91c..0f90472bd0 100644
--- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
+++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
@@ -71,7 +71,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y)
{
var inputRoot = _getInputRoot();
- var mouseDevice = MouseDevice.Instance;
+ var mouseDevice = Avalonia.Android.Platform.Input.AndroidMouseDevice.Instance;
//in order the controls to work in a predictable way
//we need to generate mouse move before first mouse down event
diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index 95be67c98c..cc458545e2 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -30,6 +30,7 @@
Properties\SharedAssemblyInfo.cs
+
\ No newline at end of file
diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs
index ecaf59e174..125c29b21b 100644
--- a/src/Avalonia.Base/Data/BindingNotification.cs
+++ b/src/Avalonia.Base/Data/BindingNotification.cs
@@ -44,11 +44,7 @@ namespace Avalonia.Data
public static readonly BindingNotification UnsetValue =
new BindingNotification(AvaloniaProperty.UnsetValue);
- // Null cannot be held in WeakReference as it's indistinguishable from an expired value so
- // use this value in its place.
- private static readonly object NullValue = new object();
-
- private WeakReference
true
@@ -42,4 +43,4 @@
-
\ No newline at end of file
+
diff --git a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj
index e2d4522d7e..f0964680a1 100644
--- a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj
+++ b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj
@@ -54,6 +54,9 @@
prompt
MinimumRecommendedRules.ruleset
+
+ true
+
@@ -106,4 +109,4 @@
-
\ No newline at end of file
+
diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs
index e9c241b848..e5e8faec5f 100644
--- a/src/Skia/Avalonia.Skia/BitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs
@@ -131,7 +131,7 @@ namespace Avalonia.Skia
public int Width => _bmp.Width;
public int Height => _bmp.Height;
public int RowBytes => _bmp.RowBytes;
- public Size Dpi { get; } = new Size(96, 96);
+ public Vector Dpi { get; } = new Vector(96, 96);
public PixelFormat Format => _bmp.ColorType.ToPixelFormat();
}
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 4a9f2c6572..3ed0509c0a 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -141,23 +141,17 @@ namespace Avalonia.Skia
var rv = new PaintWrapper(paint);
paint.IsStroke = false;
- // TODO: SkiaSharp does not contain alpha yet!
+
double opacity = brush.Opacity * _currentOpacity;
- //paint.SetAlpha(paint.GetAlpha() * opacity);
paint.IsAntialias = true;
- SKColor color = new SKColor(255, 255, 255, 255);
-
var solid = brush as ISolidColorBrush;
- if (solid != null)
- color = solid.Color.ToSKColor();
-
- paint.Color = (new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * opacity)));
-
if (solid != null)
{
+ paint.Color = new SKColor(solid.Color.R, solid.Color.G, solid.Color.B, (byte) (solid.Color.A * opacity));
return rv;
}
+ paint.Color = (new SKColor(255, 255, 255, (byte)(255 * opacity)));
var gradient = brush as IGradientBrush;
if (gradient != null)
diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
index 8568c80c04..1d224f97d7 100644
--- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
+++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
@@ -42,7 +42,6 @@ namespace Avalonia.Skia
_paint.Typeface = skiaTypeface;
_paint.TextSize = (float)(typeface?.FontSize ?? 12);
_paint.TextAlign = textAlignment.ToSKTextAlign();
- _paint.BlendMode = SKBlendMode.Src;
_wrapping = wrapping;
_constraint = constraint;
@@ -200,66 +199,65 @@ namespace Avalonia.Skia
}
ctx->Canvas->restore();
*/
- SKPaint paint = _paint;
- IDisposable currd = null;
- var currentWrapper = foreground;
-
- try
+ using (var paint = _paint.Clone())
{
- SKPaint currFGPaint = ApplyWrapperTo(ref foreground, ref currd, paint);
- bool hasCusomFGBrushes = _foregroundBrushes.Any();
-
- for (int c = 0; c < _skiaLines.Count; c++)
+ IDisposable currd = null;
+ var currentWrapper = foreground;
+ SKPaint currentPaint = null;
+ try
{
- AvaloniaFormattedTextLine line = _skiaLines[c];
-
- float x = TransformX(origin.X, 0, paint.TextAlign);
+ ApplyWrapperTo(ref currentPaint, foreground, ref currd, paint);
+ bool hasCusomFGBrushes = _foregroundBrushes.Any();
- if (!hasCusomFGBrushes)
- {
- var subString = Text.Substring(line.Start, line.Length);
- canvas.DrawText(subString, x, origin.Y + line.Top + _lineOffset, paint);
- }
- else
+ for (int c = 0; c < _skiaLines.Count; c++)
{
- float currX = x;
- string subStr;
- int len;
+ AvaloniaFormattedTextLine line = _skiaLines[c];
- for (int i = line.Start; i < line.Start + line.Length;)
- {
- var fb = GetNextForegroundBrush(ref line, i, out len);
-
- if (fb != null)
- {
- //TODO: figure out how to get the brush size
- currentWrapper = context.CreatePaint(fb, new Size());
- }
- else
- {
- if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose();
- currentWrapper = foreground;
- }
+ float x = TransformX(origin.X, 0, paint.TextAlign);
- subStr = Text.Substring(i, len);
+ if (!hasCusomFGBrushes)
+ {
+ var subString = Text.Substring(line.Start, line.Length);
+ canvas.DrawText(subString, x, origin.Y + line.Top + _lineOffset, paint);
+ }
+ else
+ {
+ float currX = x;
+ string subStr;
+ int len;
- if (currFGPaint != currentWrapper.Paint)
+ for (int i = line.Start; i < line.Start + line.Length;)
{
- currFGPaint = ApplyWrapperTo(ref currentWrapper, ref currd, paint);
+ var fb = GetNextForegroundBrush(ref line, i, out len);
+
+ if (fb != null)
+ {
+ //TODO: figure out how to get the brush size
+ currentWrapper = context.CreatePaint(fb, new Size());
+ }
+ else
+ {
+ if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose();
+ currentWrapper = foreground;
+ }
+
+ subStr = Text.Substring(i, len);
+
+ ApplyWrapperTo(ref currentPaint, currentWrapper, ref currd, paint);
+
+ canvas.DrawText(subStr, currX, origin.Y + line.Top + _lineOffset, paint);
+
+ i += len;
+ currX += paint.MeasureText(subStr);
}
-
- canvas.DrawText(subStr, currX, origin.Y + line.Top + _lineOffset, paint);
-
- i += len;
- currX += paint.MeasureText(subStr);
}
}
}
- }
- finally
- {
- if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose();
- currd?.Dispose();
+ finally
+ {
+ if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose();
+ currd?.Dispose();
+ }
}
}
@@ -278,12 +276,13 @@ namespace Avalonia.Skia
private Size _size;
private List _skiaLines;
- private static SKPaint ApplyWrapperTo(ref DrawingContextImpl.PaintWrapper wrapper,
+ private static void ApplyWrapperTo(ref SKPaint current, DrawingContextImpl.PaintWrapper wrapper,
ref IDisposable curr, SKPaint paint)
{
+ if (current == wrapper.Paint)
+ return;
curr?.Dispose();
curr = wrapper.ApplyTo(paint);
- return wrapper.Paint;
}
private static bool IsBreakChar(char c)
diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
index 0eacdf41ac..ae8e653e55 100644
--- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
@@ -76,7 +76,7 @@ namespace Avalonia.Skia
canvas.RestoreToCount(0);
canvas.Save();
canvas.ResetMatrix();
- var scale = Matrix.CreateScale(fb.Dpi.Width / 96, fb.Dpi.Height / 96);
+ var scale = Matrix.CreateScale(fb.Dpi.X / 96, fb.Dpi.Y / 96);
return new DrawingContextImpl(canvas, visualBrushRenderer, scale, canvas, surface, shim, fb);
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
index bd2e77d022..38fd7254dc 100644
--- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
+++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
@@ -50,7 +50,9 @@
+
+
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 469c29d626..3176dee7a4 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -135,12 +135,17 @@ namespace Avalonia.Direct2D1
public IRenderTarget CreateRenderTarget(IEnumerable surfaces)
{
- var nativeWindow = surfaces?.OfType().FirstOrDefault();
- if (nativeWindow != null)
+ foreach (var s in surfaces)
{
- if(nativeWindow.HandleDescriptor != "HWND")
- throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " + nativeWindow.HandleDescriptor);
- return new HwndRenderTarget(nativeWindow);
+ if (s is IPlatformHandle nativeWindow)
+ {
+ if (nativeWindow.HandleDescriptor != "HWND")
+ throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " +
+ nativeWindow.HandleDescriptor);
+ return new HwndRenderTarget(nativeWindow);
+ }
+ if (s is IExternalDirect2DRenderTargetSurface external)
+ return new ExternalRenderTarget(external, s_dwfactory);
}
throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces");
}
diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
new file mode 100644
index 0000000000..307048f7b4
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Direct2D1.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using SharpDX;
+using DirectWriteFactory = SharpDX.DirectWrite.Factory;
+
+namespace Avalonia.Direct2D1
+{
+ class ExternalRenderTarget : IRenderTarget
+ {
+ private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider;
+ private readonly DirectWriteFactory _dwFactory;
+ public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
+ DirectWriteFactory dwFactory)
+ {
+ _externalRenderTargetProvider = externalRenderTargetProvider;
+ _dwFactory = dwFactory;
+ }
+
+ public void Dispose()
+ {
+ _externalRenderTargetProvider.DestroyRenderTarget();
+ }
+
+ public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
+ {
+ var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
+ _externalRenderTargetProvider.BeforeDrawing();
+ return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () =>
+ {
+ try
+ {
+ _externalRenderTargetProvider.AfterDrawing();
+ }
+ catch (SharpDXException ex) when ((uint) ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
+ {
+ _externalRenderTargetProvider.DestroyRenderTarget();
+ }
+ });
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs b/src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs
new file mode 100644
index 0000000000..aad51f46d5
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Direct2D1
+{
+ public interface IExternalDirect2DRenderTargetSurface
+ {
+ SharpDX.Direct2D1.RenderTarget GetOrCreateRenderTarget();
+ void DestroyRenderTarget();
+ void BeforeDrawing();
+ void AfterDrawing();
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 535ca900c2..0b46ba1c47 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -23,6 +23,7 @@ namespace Avalonia.Direct2D1.Media
private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly SharpDX.DXGI.SwapChain1 _swapChain;
+ private readonly Action _finishedCallback;
private SharpDX.DirectWrite.Factory _directWriteFactory;
///
@@ -32,15 +33,18 @@ namespace Avalonia.Direct2D1.Media
/// The render target to draw to.
/// The DirectWrite factory.
/// An optional swap chain associated with this drawing context.
+ /// An optional delegate to be called when context is disposed.
public DrawingContextImpl(
IVisualBrushRenderer visualBrushRenderer,
SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory,
- SharpDX.DXGI.SwapChain1 swapChain = null)
+ SharpDX.DXGI.SwapChain1 swapChain = null,
+ Action finishedCallback = null)
{
_visualBrushRenderer = visualBrushRenderer;
_renderTarget = renderTarget;
_swapChain = swapChain;
+ _finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_swapChain = swapChain;
_renderTarget.BeginDraw();
@@ -73,6 +77,7 @@ namespace Avalonia.Direct2D1.Media
_renderTarget.EndDraw();
_swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None);
+ _finishedCallback?.Invoke();
}
catch (SharpDXException ex) when ((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs
index 06eb26b407..5dc07e06c4 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs
@@ -37,7 +37,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
public int Width => _lock.Size.Width;
public int Height => _lock.Size.Height;
public int RowBytes => _lock.Stride;
- public Size Dpi { get; } = new Size(96, 96);
+ public Vector Dpi { get; } = new Vector(96, 96);
public PixelFormat Format => _format;
}
diff --git a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
new file mode 100644
index 0000000000..5f1a065028
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
@@ -0,0 +1,123 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}
+ Library
+ Properties
+ Avalonia.Win32.Interop
+ Avalonia.Win32.Interop
+ v4.5.2
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UnmanagedMethods.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {d211e587-d8bc-45b9-95a4-f297c8fa5200}
+ Avalonia.Animation
+
+
+ {b09b78d8-9b26-48b0-9149-d64a2f120f3f}
+ Avalonia.Base
+
+
+ {d2221c82-4a25-4583-9b43-d791e3f6820c}
+ Avalonia.Controls
+
+
+ {4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}
+ Avalonia.DotNetFrameworkRuntime
+
+
+ {62024b2d-53eb-4638-b26b-85eeaa54866e}
+ Avalonia.Input
+
+
+ {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}
+ Avalonia.Interactivity
+
+
+ {42472427-4774-4c81-8aff-9f27b8e31721}
+ Avalonia.Layout
+
+
+ {f1baa01a-f176-4c6a-b39d-5b40bb1b148f}
+ Avalonia.Styling
+
+
+ {eb582467-6abb-43a1-b052-e981ba910e3a}
+ Avalonia.Visuals
+
+
+ {fb05ac90-89ba-4f2f-a924-f37875fb547c}
+ Avalonia.Cairo
+
+
+ {3e53a01a-b331-47f3-b828-4a5717e77a24}
+ Avalonia.Markup.Xaml
+
+
+ {6417e941-21bc-467b-a771-0de389353ce6}
+ Avalonia.Markup
+
+
+ {3e908f67-5543-4879-a1dc-08eace79b3cd}
+ Avalonia.Direct2D1
+
+
+ {811a76cf-1cf6-440f-963b-bbe31bd72a82}
+ Avalonia.Win32
+
+
+
+ true
+
+
+
+
\ No newline at end of file
diff --git a/src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs b/src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..7c0d638381
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Avalonia.Win32.Interop")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Avalonia.Win32.Interop")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("cbc4ff2f-92d4-420b-be21-9fe0b930b04e")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs
new file mode 100644
index 0000000000..6ae898ae3d
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ static class CursorShim
+ {
+ public static Cursor FromHCursor(IntPtr hcursor)
+ {
+ var field = typeof(Cursor).GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
+ .FirstOrDefault(f => f.FieldType == typeof(SafeHandle));
+ if (field == null)
+ return null;
+ var rv = (Cursor) FormatterServices.GetUninitializedObject(typeof(Cursor));
+ field.SetValue(rv, new SafeHandleShim(hcursor));
+ return rv;
+ }
+
+ class SafeHandleShim : SafeHandle
+ {
+ public SafeHandleShim(IntPtr hcursor) : base(new IntPtr(-1), false)
+ {
+ this.handle = hcursor;
+ }
+
+ protected override bool ReleaseHandle() => true;
+
+ public override bool IsInvalid => false;
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
new file mode 100644
index 0000000000..8fe7275a0f
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Interop;
+using Avalonia.Direct2D1;
+using SharpDX;
+using SharpDX.Direct2D1;
+using SharpDX.Direct3D11;
+using SharpDX.Direct3D9;
+using SharpDX.DXGI;
+using AlphaMode = SharpDX.Direct2D1.AlphaMode;
+using Device = SharpDX.Direct3D11.Device;
+using Format = SharpDX.DXGI.Format;
+using MapFlags = SharpDX.Direct3D11.MapFlags;
+using PresentParameters = SharpDX.DXGI.PresentParameters;
+using Query = SharpDX.Direct3D11.Query;
+using QueryType = SharpDX.Direct3D11.QueryType;
+using RenderTarget = SharpDX.Direct2D1.RenderTarget;
+using Surface = SharpDX.DXGI.Surface;
+using SwapEffect = SharpDX.DXGI.SwapEffect;
+using Usage = SharpDX.Direct3D9.Usage;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ class Direct2DImageSurface : IExternalDirect2DRenderTargetSurface, IDisposable
+ {
+ class SwapBuffer: IDisposable
+ {
+ private readonly Query _event;
+ private readonly SharpDX.Direct3D11.Resource _resource;
+ private readonly SharpDX.Direct3D11.Resource _sharedResource;
+ public SharpDX.Direct3D9.Surface Texture { get; }
+ public RenderTarget Target { get;}
+ public IntSize Size { get; }
+
+ public SwapBuffer(IntSize size, Vector dpi)
+ {
+ int width = (int) size.Width;
+ int height = (int) size.Height;
+ _event = new Query(s_dxDevice, new QueryDescription {Type = QueryType.Event});
+ using (var texture = new Texture2D(s_dxDevice, new Texture2DDescription
+ {
+ Width = width,
+ Height = height,
+ ArraySize = 1,
+ MipLevels = 1,
+ Format = Format.B8G8R8A8_UNorm,
+ Usage = ResourceUsage.Default,
+ SampleDescription = new SampleDescription(2, 0),
+ BindFlags = BindFlags.RenderTarget,
+ }))
+ using (var surface = texture.QueryInterface())
+
+ {
+ _resource = texture.QueryInterface();
+
+ Target = new RenderTarget(AvaloniaLocator.Current.GetService(), surface,
+ new RenderTargetProperties
+ {
+ DpiX = (float) dpi.X,
+ DpiY = (float) dpi.Y,
+ MinLevel = FeatureLevel.Level_10,
+ PixelFormat = new PixelFormat(Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied),
+
+ });
+ }
+ using (var texture = new Texture2D(s_dxDevice, new Texture2DDescription
+ {
+ Width = width,
+ Height = height,
+ ArraySize = 1,
+ MipLevels = 1,
+ Format = Format.B8G8R8A8_UNorm,
+ Usage = ResourceUsage.Default,
+ SampleDescription = new SampleDescription(1, 0),
+ BindFlags = BindFlags.RenderTarget|BindFlags.ShaderResource,
+ OptionFlags = ResourceOptionFlags.Shared,
+ }))
+ using (var resource = texture.QueryInterface())
+ {
+ _sharedResource = texture.QueryInterface();
+ var handle = resource.SharedHandle;
+ using (var texture9 = new Texture(s_d3DDevice, texture.Description.Width,
+ texture.Description.Height, 1,
+ Usage.RenderTarget, SharpDX.Direct3D9.Format.A8R8G8B8, Pool.Default, ref handle))
+ Texture = texture9.GetSurfaceLevel(0);
+ }
+ Size = size;
+ }
+
+ public void Dispose()
+ {
+ Texture?.Dispose();
+ Target?.Dispose();
+ _resource?.Dispose();
+ _sharedResource?.Dispose();
+ _event?.Dispose();
+ }
+
+ public void Flush()
+ {
+ s_dxDevice.ImmediateContext.ResolveSubresource(_resource, 0, _sharedResource, 0, Format.B8G8R8A8_UNorm);
+ s_dxDevice.ImmediateContext.Flush();
+ s_dxDevice.ImmediateContext.End(_event);
+ s_dxDevice.ImmediateContext.GetData(_event).Dispose();
+ }
+ }
+
+ private D3DImage _image;
+ private SwapBuffer _backBuffer;
+ private readonly WpfTopLevelImpl _impl;
+ private static Device s_dxDevice;
+ private static Direct3DEx s_d3DContext;
+ private static DeviceEx s_d3DDevice;
+ private Vector _oldDpi;
+
+
+ [DllImport("user32.dll", SetLastError = false)]
+ private static extern IntPtr GetDesktopWindow();
+ void EnsureDirectX()
+ {
+ if(s_d3DDevice != null)
+ return;
+ s_d3DContext = new Direct3DEx();
+
+ SharpDX.Direct3D9.PresentParameters presentparams = new SharpDX.Direct3D9.PresentParameters
+ {
+ Windowed = true,
+ SwapEffect = SharpDX.Direct3D9.SwapEffect.Discard,
+ DeviceWindowHandle = GetDesktopWindow(),
+ PresentationInterval = PresentInterval.Default
+ };
+ s_dxDevice = s_dxDevice ?? AvaloniaLocator.Current.GetService()
+ .QueryInterface();
+ s_d3DDevice = new DeviceEx(s_d3DContext, 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.FpuPreserve, presentparams);
+
+ }
+
+ public Direct2DImageSurface(WpfTopLevelImpl impl)
+ {
+ _impl = impl;
+ }
+
+ public RenderTarget GetOrCreateRenderTarget()
+ {
+ EnsureDirectX();
+ var scale = _impl.GetScaling();
+ var size = new IntSize(_impl.ActualWidth * scale.X, _impl.ActualHeight * scale.Y);
+ var dpi = scale * 96;
+
+ if (_backBuffer!=null && _backBuffer.Size == size)
+ return _backBuffer.Target;
+
+ if (_image == null || _oldDpi.X != dpi.X || _oldDpi.Y != dpi.Y)
+ {
+ _image = new D3DImage(dpi.X, dpi.Y);
+ }
+ _impl.ImageSource = _image;
+
+ RemoveAndDispose(ref _backBuffer);
+ if (size == default(IntSize))
+ {
+ _image.Lock();
+ _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero);
+ _image.Unlock();
+ return null;
+ }
+ _backBuffer = new SwapBuffer(size, dpi);
+
+ return _backBuffer.Target;
+ }
+
+ void RemoveAndDispose(ref T d) where T : IDisposable
+ {
+ d?.Dispose();
+ d = default(T);
+ }
+
+ void Swap()
+ {
+ _backBuffer.Flush();
+ _image.Lock();
+ _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _backBuffer?.Texture?.NativePointer ?? IntPtr.Zero, true);
+ _image.AddDirtyRect(new Int32Rect(0, 0, _image.PixelWidth, _image.PixelHeight));
+ _image.Unlock();
+ }
+
+ public void DestroyRenderTarget()
+ {
+ RemoveAndDispose(ref _backBuffer);
+ }
+
+ public void BeforeDrawing()
+ {
+
+ }
+
+ public void AfterDrawing() => Swap();
+ public void Dispose()
+ {
+ RemoveAndDispose(ref _backBuffer);
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs
new file mode 100644
index 0000000000..3fdbdedfd9
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ struct IntSize : IEquatable
+ {
+ public bool Equals(IntSize other)
+ {
+ return Width == other.Width && Height == other.Height;
+ }
+
+ public IntSize(int width, int height)
+ {
+ Width = width;
+ Height = height;
+ }
+
+ public IntSize(double width, double height) : this((int) width, (int) height)
+ {
+
+ }
+
+ public static implicit operator IntSize(System.Windows.Size size)
+ {
+ return new IntSize {Width = (int) size.Width, Height = (int) size.Height};
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is IntSize && Equals((IntSize) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Width * 397) ^ Height;
+ }
+ }
+
+ public static bool operator ==(IntSize left, IntSize right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(IntSize left, IntSize right)
+ {
+ return !left.Equals(right);
+ }
+
+ public int Width { get; set; }
+ public int Height { get; set; }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs
new file mode 100644
index 0000000000..6dc9ba9e09
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Markup;
+using System.Windows.Media;
+using Avalonia.Markup.Xaml.Styling;
+using Avalonia.Platform;
+using Avalonia.Styling;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ [ContentProperty("Content")]
+ public class WpfAvaloniaHost : FrameworkElement, IDisposable, IAddChild
+ {
+ private WpfTopLevelImpl _impl;
+ private readonly SynchronizationContext _sync;
+ private bool _hasChildren;
+ public WpfAvaloniaHost()
+ {
+ _sync = SynchronizationContext.Current;
+ _impl = new WpfTopLevelImpl();
+ _impl.ControlRoot.Prepare();
+ _impl.Visibility = Visibility.Visible;
+ SnapsToDevicePixels = true;
+ UseLayoutRounding = true;
+ PresentationSource.AddSourceChangedHandler(this, OnSourceChanged);
+ }
+
+ private void OnSourceChanged(object sender, SourceChangedEventArgs e)
+ {
+ if (e.NewSource != null && !_hasChildren)
+ {
+ AddLogicalChild(_impl);
+ AddVisualChild(_impl);
+ _hasChildren = true;
+ }
+ else
+ {
+ RemoveVisualChild(_impl);
+ RemoveLogicalChild(_impl);
+ _hasChildren = false;
+ }
+ }
+
+ public object Content
+ {
+ get => _impl.ControlRoot.Content;
+ set => _impl.ControlRoot.Content = value;
+ }
+
+ //Separate class is needed to prevent accidential resurrection
+ class Disposer
+ {
+ private readonly WpfTopLevelImpl _impl;
+
+ public Disposer(WpfTopLevelImpl impl)
+ {
+ _impl = impl;
+ }
+
+ public void Callback(object state)
+ {
+ _impl.Dispose();
+ }
+ }
+
+ protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
+ {
+ _impl.InvalidateMeasure();
+ _impl.Measure(constraint);
+ return _impl.DesiredSize;
+ }
+
+ protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
+ {
+ _impl.Arrange(new System.Windows.Rect(arrangeSize));
+ return arrangeSize;
+ }
+
+ protected override int VisualChildrenCount => 1;
+ protected override System.Windows.Media.Visual GetVisualChild(int index) => _impl;
+
+ ~WpfAvaloniaHost()
+ {
+ if (_impl != null)
+ _sync.Post(new Disposer(_impl).Callback, null);
+ }
+
+ public void Dispose()
+ {
+ if (_impl != null)
+ {
+ RemoveVisualChild(_impl);
+ RemoveLogicalChild(_impl);
+ _impl.Dispose();
+ _impl = null;
+ GC.SuppressFinalize(this);
+ }
+ }
+
+ void IAddChild.AddChild(object value)
+ {
+ if (Content == null)
+ Content = value;
+ else
+ throw new InvalidOperationException();
+ }
+
+ void IAddChild.AddText(string text)
+ {
+ //
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs
new file mode 100644
index 0000000000..6433ff05e0
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ static class WpfInteropExtensions
+ {
+ public static System.Windows.Point ToWpfPoint(this Point pt) => new System.Windows.Point(pt.X, pt.Y);
+ public static Point ToAvaloniaPoint(this System.Windows.Point pt) => new Point(pt.X, pt.Y);
+ public static System.Windows.Size ToWpfSize(this Size pt) => new System.Windows.Size(pt.Width, pt.Height);
+ public static Size ToAvaloniaSize(this System.Windows.Size pt) => new Size(pt.Width, pt.Height);
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs
new file mode 100644
index 0000000000..4aad80f8a5
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs
@@ -0,0 +1,30 @@
+using System;
+using Avalonia.Controls.Embedding;
+using Avalonia.Input;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ class WpfMouseDevice : MouseDevice
+ {
+ private readonly WpfTopLevelImpl _impl;
+
+ public WpfMouseDevice(WpfTopLevelImpl impl)
+ {
+ _impl = impl;
+ }
+
+ public override void Capture(IInputElement control)
+ {
+ if (control == null)
+ {
+ System.Windows.Input.Mouse.Capture(null);
+ }
+ else if ((control.GetVisualRoot() as EmbeddableControlRoot)?.PlatformImpl != _impl)
+ throw new ArgumentException("Visual belongs to unknown toplevel");
+ else
+ System.Windows.Input.Mouse.Capture(_impl);
+ base.Capture(control);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
new file mode 100644
index 0000000000..fbed2f621c
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
@@ -0,0 +1,241 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Media;
+using Avalonia.Controls.Embedding;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Layout;
+using Avalonia.Platform;
+using Key = Avalonia.Input.Key;
+using KeyEventArgs = System.Windows.Input.KeyEventArgs;
+using MouseButton = System.Windows.Input.MouseButton;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ class WpfTopLevelImpl : FrameworkElement, IEmbeddableWindowImpl
+ {
+ private HwndSource _currentHwndSource;
+ private readonly HwndSourceHook _hook;
+ private readonly IEmbeddableWindowImpl _ttl;
+ private IInputRoot _inputRoot;
+ private readonly IEnumerable _surfaces;
+ private readonly IMouseDevice _mouse;
+ private readonly IKeyboardDevice _keyboard;
+ private Size _finalSize;
+
+ public EmbeddableControlRoot ControlRoot { get; }
+ internal ImageSource ImageSource { get; set; }
+
+ public class CustomControlRoot : EmbeddableControlRoot, IEmbeddedLayoutRoot
+ {
+ public CustomControlRoot(WpfTopLevelImpl impl) : base(impl)
+ {
+ EnforceClientSize = false;
+ }
+
+ protected override void OnMeasureInvalidated()
+ {
+ ((FrameworkElement)PlatformImpl)?.InvalidateMeasure();
+ }
+
+ protected override void HandleResized(Size clientSize)
+ {
+ ClientSize = clientSize;
+ LayoutManager.Instance.ExecuteLayoutPass();
+ Renderer?.Resized(clientSize);
+ }
+
+ public Size AllocatedSize => ClientSize;
+ }
+
+ public WpfTopLevelImpl()
+ {
+ PresentationSource.AddSourceChangedHandler(this, OnSourceChanged);
+ _hook = WndProc;
+ _ttl = this;
+ _surfaces = new object[] {new WritableBitmapSurface(this), new Direct2DImageSurface(this)};
+ _mouse = new WpfMouseDevice(this);
+ _keyboard = AvaloniaLocator.Current.GetService();
+
+ ControlRoot = new CustomControlRoot(this);
+ SnapsToDevicePixels = true;
+ Focusable = true;
+ DataContextChanged += delegate
+ {
+ ControlRoot.DataContext = DataContext;
+ };
+ }
+
+ private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
+ {
+ if (msg == (int)UnmanagedMethods.WindowsMessage.WM_DPICHANGED)
+ _ttl.ScalingChanged?.Invoke(_ttl.Scaling);
+ return IntPtr.Zero;
+ }
+
+ private void OnSourceChanged(object sender, SourceChangedEventArgs e)
+ {
+ _currentHwndSource?.RemoveHook(_hook);
+ _currentHwndSource = e.NewSource as HwndSource;
+ _currentHwndSource?.AddHook(_hook);
+ _ttl.ScalingChanged?.Invoke(_ttl.Scaling);
+ }
+
+ public void Dispose()
+ {
+ _ttl.Closed?.Invoke();
+ foreach(var d in _surfaces.OfType())
+ d.Dispose();
+ }
+
+ Size ITopLevelImpl.ClientSize => _finalSize;
+ IMouseDevice ITopLevelImpl.MouseDevice => _mouse;
+
+ double ITopLevelImpl.Scaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1;
+
+ IEnumerable ITopLevelImpl.Surfaces => _surfaces;
+
+ private Size _previousSize;
+ protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
+ {
+ _finalSize = finalSize.ToAvaloniaSize();
+ if (_finalSize == _previousSize)
+ return finalSize;
+ _previousSize = _finalSize;
+ _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize());
+ return base.ArrangeOverride(finalSize);
+ }
+
+ protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
+ {
+ ControlRoot.Measure(availableSize.ToAvaloniaSize());
+ return ControlRoot.DesiredSize.ToWpfSize();
+ }
+
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ if(ActualHeight == 0 || ActualWidth == 0)
+ return;
+ _ttl.Paint?.Invoke(new Rect(0, 0, ActualWidth, ActualHeight));
+ if (ImageSource != null)
+ drawingContext.DrawImage(ImageSource, new System.Windows.Rect(0, 0, ActualWidth, ActualHeight));
+ }
+
+ void ITopLevelImpl.Invalidate(Rect rect) => InvalidateVisual();
+
+ void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
+
+ Point ITopLevelImpl.PointToClient(Point point) => PointFromScreen(point.ToWpfPoint()).ToAvaloniaPoint();
+
+ Point ITopLevelImpl.PointToScreen(Point point) => PointToScreen(point.ToWpfPoint()).ToAvaloniaPoint();
+
+ protected override void OnLostFocus(RoutedEventArgs e) => LostFocus?.Invoke();
+
+
+ InputModifiers GetModifiers()
+ {
+ var state = Keyboard.Modifiers;
+ var rv = default(InputModifiers);
+ if (state.HasFlag(ModifierKeys.Windows))
+ rv |= InputModifiers.Windows;
+ if (state.HasFlag(ModifierKeys.Alt))
+ rv |= InputModifiers.Alt;
+ if (state.HasFlag(ModifierKeys.Control))
+ rv |= InputModifiers.Control;
+ if (state.HasFlag(ModifierKeys.Shift))
+ rv |= InputModifiers.Shift;
+ //TODO: mouse modifiers
+
+
+ return rv;
+ }
+
+ void MouseEvent(RawMouseEventType type, MouseEventArgs e)
+ => _ttl.Input?.Invoke(new RawMouseEventArgs(_mouse, (uint)e.Timestamp, _inputRoot, type,
+ e.GetPosition(this).ToAvaloniaPoint(), GetModifiers()));
+
+ protected override void OnMouseDown(MouseButtonEventArgs e)
+ {
+ RawMouseEventType type;
+ if(e.ChangedButton == MouseButton.Left)
+ type = RawMouseEventType.LeftButtonDown;
+ else if (e.ChangedButton == MouseButton.Middle)
+ type = RawMouseEventType.MiddleButtonDown;
+ else if (e.ChangedButton == MouseButton.Right)
+ type = RawMouseEventType.RightButtonDown;
+ else
+ return;
+ MouseEvent(type, e);
+ Focus();
+ }
+
+ protected override void OnMouseUp(MouseButtonEventArgs e)
+ {
+ RawMouseEventType type;
+ if (e.ChangedButton == MouseButton.Left)
+ type = RawMouseEventType.LeftButtonUp;
+ else if (e.ChangedButton == MouseButton.Middle)
+ type = RawMouseEventType.MiddleButtonUp;
+ else if (e.ChangedButton == MouseButton.Right)
+ type = RawMouseEventType.RightButtonUp;
+ else
+ return;
+ MouseEvent(type, e);
+ Focus();
+ }
+
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ MouseEvent(RawMouseEventType.Move, e);
+ }
+
+ protected override void OnMouseWheel(MouseWheelEventArgs e) =>
+ _ttl.Input?.Invoke(new RawMouseWheelEventArgs(_mouse, (uint) e.Timestamp, _inputRoot,
+ e.GetPosition(this).ToAvaloniaPoint(), new Vector(0, e.Delta), GetModifiers()));
+
+ protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawMouseEventType.LeaveWindow, e);
+
+ protected override void OnKeyDown(KeyEventArgs e)
+ => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint) e.Timestamp, RawKeyEventType.KeyDown,
+ (Key) e.Key,
+ GetModifiers()));
+
+ protected override void OnKeyUp(KeyEventArgs e)
+ => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint)e.Timestamp, RawKeyEventType.KeyUp,
+ (Key)e.Key,
+ GetModifiers()));
+
+ protected override void OnTextInput(TextCompositionEventArgs e)
+ => _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, e.Text));
+
+ void ITopLevelImpl.SetCursor(IPlatformHandle cursor)
+ {
+ if (cursor == null)
+ Cursor = Cursors.Arrow;
+ else if (cursor.HandleDescriptor == "HCURSOR")
+ Cursor = CursorShim.FromHCursor(cursor.Handle);
+ }
+
+ Action ITopLevelImpl.Input { get; set; } //TODO
+ Action ITopLevelImpl.Paint { get; set; }
+ Action ITopLevelImpl.Resized { get; set; }
+ Action ITopLevelImpl.ScalingChanged { get; set; }
+ Action ITopLevelImpl.Closed { get; set; }
+ public new event Action LostFocus;
+
+ internal Vector GetScaling()
+ {
+ var src = PresentationSource.FromVisual(this)?.CompositionTarget;
+ if (src == null)
+ return new Vector(1, 1);
+ return new Vector(src.TransformToDevice.M11, src.TransformToDevice.M22);
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs
new file mode 100644
index 0000000000..0f8752fb8d
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+using PixelFormat = Avalonia.Platform.PixelFormat;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ class WritableBitmapSurface : IFramebufferPlatformSurface
+ {
+ private readonly WpfTopLevelImpl _impl;
+ private WriteableBitmap _bitmap;
+ public WritableBitmapSurface(WpfTopLevelImpl impl)
+ {
+ _impl = impl;
+ }
+
+ public ILockedFramebuffer Lock()
+ {
+ var scale = _impl.GetScaling();
+ var size = new Size(_impl.ActualWidth * scale.X, _impl.ActualHeight * scale.Y);
+ var dpi = scale * 96;
+ if (_bitmap == null || _bitmap.PixelWidth != (int) size.Width || _bitmap.PixelHeight != (int) size.Height)
+ {
+ _bitmap = new WriteableBitmap((int) size.Width, (int) size.Height, dpi.X, dpi.Y,
+ PixelFormats.Bgra32, null);
+ }
+ return new LockedFramebuffer(_impl, _bitmap, dpi);
+ }
+
+ internal class LockedFramebuffer : ILockedFramebuffer
+ {
+ private readonly WpfTopLevelImpl _impl;
+ private readonly WriteableBitmap _bitmap;
+
+ public LockedFramebuffer(WpfTopLevelImpl impl, WriteableBitmap bitmap, Vector dpi)
+ {
+ _impl = impl;
+ _bitmap = bitmap;
+ Dpi = dpi;
+ _bitmap.Lock();
+ }
+
+ public void Dispose()
+ {
+ _bitmap.AddDirtyRect(new Int32Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight));
+ _bitmap.Unlock();
+ /*
+ using (var fileStream = new FileStream("c:\\tools\\wat.png", FileMode.Create))
+ {
+ BitmapEncoder encoder = new PngBitmapEncoder();
+ encoder.Frames.Add(BitmapFrame.Create(_bitmap));
+ encoder.Save(fileStream);
+ }*/
+ _impl.ImageSource = _bitmap;
+ }
+
+ public IntPtr Address => _bitmap.BackBuffer;
+ public int Width => _bitmap.PixelWidth;
+ public int Height => _bitmap.PixelHeight;
+ public int RowBytes => _bitmap.BackBufferStride;
+ public Vector Dpi { get; }
+ public PixelFormat Format => PixelFormat.Bgra8888;
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
index 41728ee486..ea742f8911 100644
--- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
+++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
@@ -57,7 +57,6 @@
Component
-
diff --git a/src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs b/src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs
deleted file mode 100644
index 663f6906ed..0000000000
--- a/src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Forms.Integration;
-using System.Windows.Interop;
-using Avalonia.Controls;
-using Avalonia.Win32.Interop;
-
-namespace Avalonia.Win32.Embedding
-{
- public class WpfAvaloniaControlHost : HwndHost
- {
- private WinFormsAvaloniaControlHost _host;
- private Avalonia.Controls.Control _content;
-
- public Avalonia.Controls.Control Content
- {
- get { return _content; }
- set
- {
- if (_host != null)
- _host.Content = value;
- _content = value;
-
- }
- }
-
- void DestroyHost()
- {
- _host?.Dispose();
- _host = null;
- }
-
- protected override HandleRef BuildWindowCore(HandleRef hwndParent)
- {
- DestroyHost();
- _host = new WinFormsAvaloniaControlHost {Content = _content};
- UnmanagedMethods.SetParent(_host.Handle, hwndParent.Handle);
- return new HandleRef(this, _host.Handle);
- }
-
- protected override void DestroyWindowCore(HandleRef hwnd)
- {
- DestroyHost();
- }
- }
-}
diff --git a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs
index a55c808415..3d8d04d6cc 100644
--- a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs
+++ b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Win32.Input
{
class WindowsMouseDevice : MouseDevice
{
- public new static WindowsMouseDevice Instance { get; } = new WindowsMouseDevice();
+ public static WindowsMouseDevice Instance { get; } = new WindowsMouseDevice();
public WindowImpl CurrentWindow
{
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index b6cfb03221..e02c67c0e6 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -66,7 +66,6 @@ namespace Avalonia.Win32
.Bind().ToSingleton()
.Bind().ToConstant(CursorFactory.Instance)
.Bind().ToConstant(WindowsKeyboardDevice.Instance)
- .Bind().ToConstant(WindowsMouseDevice.Instance)
.Bind().ToConstant(s_instance)
.Bind().ToConstant(s_instance)
.Bind().ToConstant(new RenderLoop(60))
diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs
index fe4fe5c668..df238c919e 100644
--- a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs
+++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs
@@ -39,7 +39,7 @@ namespace Avalonia.Win32
public int RowBytes => Width * 4;
public PixelFormat Format => PixelFormat.Bgra8888;
- public Size Dpi
+ public Vector Dpi
{
get
{
@@ -56,10 +56,10 @@ namespace Avalonia.Win32
out dpix,
out dpiy) == 0)
{
- return new Size(dpix, dpiy);
+ return new Vector(dpix, dpiy);
}
}
- return new Size(96, 96);
+ return new Vector(96, 96);
}
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index f2d7e0e043..f1537e53f5 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -133,6 +133,8 @@ namespace Avalonia.Win32
}
}
+ public IMouseDevice MouseDevice => WindowsMouseDevice.Instance;
+
public WindowState WindowState
{
get
diff --git a/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs b/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
index f3fc90a2ab..58cf6edd78 100644
--- a/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
+++ b/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
@@ -24,7 +24,7 @@ namespace Avalonia.iOS
Width = (int) frame.Width * factor;
Height = (int) frame.Height * factor;
RowBytes = Width * 4;
- Dpi = new Size(96, 96) * factor;
+ Dpi = new Vector(96, 96) * factor;
Format = PixelFormat.Rgba8888;
Address = Marshal.AllocHGlobal(Height * RowBytes);
}
@@ -53,7 +53,7 @@ namespace Avalonia.iOS
public int Width { get; }
public int Height { get; }
public int RowBytes { get; }
- public Size Dpi { get; }
+ public Vector Dpi { get; }
public PixelFormat Format { get; }
}
}
diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs
index 7949e331fe..cf4801dbb7 100644
--- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs
+++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs
@@ -61,6 +61,8 @@ namespace Avalonia.iOS
public Size ClientSize => Bounds.Size.ToAvalonia();
+ public IMouseDevice MouseDevice => iOSPlatform.MouseDevice;
+
public override void Draw(CGRect rect)
{
Paint?.Invoke(new Rect(rect.X, rect.Y, rect.Width, rect.Height));
diff --git a/src/iOS/Avalonia.iOS/iOSPlatform.cs b/src/iOS/Avalonia.iOS/iOSPlatform.cs
index 6d6e1fab03..4c4e497cd7 100644
--- a/src/iOS/Avalonia.iOS/iOSPlatform.cs
+++ b/src/iOS/Avalonia.iOS/iOSPlatform.cs
@@ -57,7 +57,6 @@ namespace Avalonia.iOS
//.Bind().ToTransient()
.Bind().ToTransient()
.Bind().ToConstant(KeyboardDevice)
- .Bind().ToConstant(MouseDevice)
.Bind().ToConstant(ImmediateRenderer.Factory)
.Bind().ToSingleton()
.Bind().ToConstant(PlatformThreadingInterface.Instance)
diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
index d55dc8d544..c656801d90 100644
--- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
+++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
@@ -1,6 +1,7 @@
net461;netcoreapp1.1
+ Library
diff --git a/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs b/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs
index 8e5e3a305b..4b93ea8400 100644
--- a/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs
+++ b/tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs
@@ -7,4 +7,4 @@ using Xunit;
[assembly: AssemblyTitle("Avalonia.UnitTests")]
// Don't run tests in parallel.
-[assembly: CollectionBehavior(DisableTestParallelization = true)]
\ No newline at end of file
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
index be6480618b..1d987e2238 100644
--- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
+++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
@@ -49,6 +49,7 @@
+
@@ -100,6 +101,6 @@
-
+
\ No newline at end of file
diff --git a/tests/Avalonia.Benchmarks/Layout/Measure.cs b/tests/Avalonia.Benchmarks/Layout/Measure.cs
new file mode 100644
index 0000000000..d1fdae9971
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/Layout/Measure.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.UnitTests;
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Layout
+{
+ [MemoryDiagnoser]
+ public class Measure : IDisposable
+ {
+ private IDisposable _app;
+ private TestRoot root;
+ private List controls = new List();
+
+ public Measure()
+ {
+ _app = UnitTestApplication.Start(TestServices.RealLayoutManager);
+
+ var panel = new StackPanel();
+ root = new TestRoot { Child = panel };
+ controls.Add(panel);
+ CreateChildren(panel, 3, 5);
+ LayoutManager.Instance.ExecuteInitialLayoutPass(root);
+ }
+
+ public void Dispose()
+ {
+ _app.Dispose();
+ }
+
+ [Benchmark]
+ public void Remeasure_Half()
+ {
+ var random = new Random(1);
+
+ foreach (var control in controls)
+ {
+ if (random.Next(2) == 0)
+ {
+ control.InvalidateMeasure();
+ }
+ }
+
+ LayoutManager.Instance.ExecuteLayoutPass();
+ }
+
+ private void CreateChildren(IPanel parent, int childCount, int iterations)
+ {
+ for (var i = 0; i < childCount; ++i)
+ {
+ var control = new StackPanel();
+ parent.Children.Add(control);
+
+ if (iterations > 0)
+ {
+ CreateChildren(control, childCount, iterations - 1);
+ }
+
+ controls.Add(control);
+ }
+ }
+ }
+}
diff --git a/tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs b/tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
index 0af451efd2..33af55fdf9 100644
--- a/tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
+++ b/tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
@@ -11,6 +11,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Benchmarks.Styling
{
+ [MemoryDiagnoser]
public class ApplyStyling : IDisposable
{
private IDisposable _app;
diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
index f8235f7d68..957cdd7036 100644
--- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
+++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
@@ -1,12 +1,16 @@
net461;netcoreapp1.1
+ Library
+
+
+
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
new file mode 100644
index 0000000000..e32c703409
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
@@ -0,0 +1,291 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Linq;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Templates;
+using Avalonia.LogicalTree;
+using Avalonia.UnitTests;
+using Avalonia.VisualTree;
+using Moq;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests.Presenters
+{
+ ///
+ /// Tests for ContentControls that are hosted in a control template.
+ ///
+ public class ContentPresenterTests_InTemplate
+ {
+ [Fact]
+ public void Should_Register_With_Host_When_TemplatedParent_Set()
+ {
+ var host = new Mock();
+ var target = new ContentPresenter();
+
+ target.SetValue(Control.TemplatedParentProperty, host.Object);
+
+ host.Verify(x => x.RegisterContentPresenter(target));
+ }
+
+ [Fact]
+ public void Setting_Content_To_Control_Should_Set_Child()
+ {
+ var (target, _) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+
+ Assert.Equal(child, target.Child);
+ }
+
+ [Fact]
+ public void Setting_Content_To_Control_Should_Update_Logical_Tree()
+ {
+ var (target, parent) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+
+ Assert.Equal(parent, child.GetLogicalParent());
+ Assert.Equal(new[] { child }, parent.GetLogicalChildren());
+ }
+
+ [Fact]
+ public void Setting_Content_To_Control_Should_Update_Visual_Tree()
+ {
+ var (target, _) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+
+ Assert.Equal(target, child.GetVisualParent());
+ Assert.Equal(new[] { child }, target.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Setting_Content_To_String_Should_Create_TextBlock()
+ {
+ var (target, _) = CreateTarget();
+
+ target.Content = "Foo";
+
+ Assert.IsType(target.Child);
+ Assert.Equal("Foo", ((TextBlock)target.Child).Text);
+ }
+
+ [Fact]
+ public void Setting_Content_To_String_Should_Update_Logical_Tree()
+ {
+ var (target, parent) = CreateTarget();
+
+ target.Content = "Foo";
+
+ var child = target.Child;
+ Assert.Equal(parent, child.GetLogicalParent());
+ Assert.Equal(new[] { child }, parent.GetLogicalChildren());
+ }
+
+ [Fact]
+ public void Setting_Content_To_String_Should_Update_Visual_Tree()
+ {
+ var (target, _) = CreateTarget();
+
+ target.Content = "Foo";
+
+ var child = target.Child;
+ Assert.Equal(target, child.GetVisualParent());
+ Assert.Equal(new[] { child }, target.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Clearing_Control_Content_Should_Update_Logical_Tree()
+ {
+ var (target, _) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+ target.Content = null;
+
+ Assert.Equal(null, child.GetLogicalParent());
+ Assert.Empty(target.GetLogicalChildren());
+ }
+
+ [Fact]
+ public void Clearing_Control_Content_Should_Update_Visual_Tree()
+ {
+ var (target, _) = CreateTarget();
+ var child = new Border();
+
+ target.Content = child;
+ target.Content = null;
+
+ Assert.Equal(null, child.GetVisualParent());
+ Assert.Empty(target.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Control_Content_Should_Not_Be_NameScope()
+ {
+ var (target, _) = CreateTarget();
+
+ target.Content = new TextBlock();
+
+ Assert.IsType(target.Child);
+ Assert.Null(NameScope.GetNameScope((Control)target.Child));
+ }
+
+ [Fact]
+ public void DataTemplate_Created_Control_Should_Be_NameScope()
+ {
+ var (target, _) = CreateTarget();
+
+ target.Content = "Foo";
+
+ Assert.IsType(target.Child);
+ Assert.NotNull(NameScope.GetNameScope((Control)target.Child));
+ }
+
+ [Fact]
+ public void Assigning_Control_To_Content_Should_Not_Set_DataContext()
+ {
+ var (target, _) = CreateTarget();
+ target.Content = new Border();
+
+ Assert.False(target.IsSet(Control.DataContextProperty));
+ }
+
+ [Fact]
+ public void Assigning_NonControl_To_Content_Should_Set_DataContext_On_UpdateChild()
+ {
+ var (target, _) = CreateTarget();
+ target.Content = "foo";
+
+ Assert.Equal("foo", target.DataContext);
+ }
+
+ [Fact]
+ public void Should_Use_ContentTemplate_If_Specified()
+ {
+ var (target, _) = CreateTarget();
+
+ target.ContentTemplate = new FuncDataTemplate(_ => new Canvas());
+ target.Content = "Foo";
+
+ Assert.IsType