Browse Source

Merge branch 'master' into RemoveNugetWorkaround

pull/983/head
Nikita Tsukanov 9 years ago
committed by GitHub
parent
commit
bc5caecafb
  1. 3
      .gitignore
  2. 43
      Avalonia.sln
  3. 2
      appveyor.yml
  4. 25
      build.cake
  5. 5
      build/Base.props
  6. 2
      build/Moq.props
  7. 1
      build/SharpDX.props
  8. 2
      build/SkiaSharp.props
  9. 6
      build/XUnit.props
  10. 2
      docs/tutorial/from-wpf.md
  11. 24
      packages.cake
  12. 23
      samples/ControlCatalog/Pages/MenuPage.xaml
  13. 14
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
  14. 15
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
  15. 2
      samples/interop/WindowsInteropTest/Program.cs
  16. 7
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  17. 5
      scripts/ReplaceNugetCache.ps1
  18. 7
      scripts/ReplaceNugetCache.sh
  19. 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  20. 2
      src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
  21. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  22. 3
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  23. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  24. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  25. 38
      src/Avalonia.Base/Data/BindingNotification.cs
  26. 36
      src/Avalonia.Controls/Button.cs
  27. 2
      src/Avalonia.Controls/ContextMenu.cs
  28. 11
      src/Avalonia.Controls/Control.cs
  29. 10
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  30. 2
      src/Avalonia.Controls/Menu.cs
  31. 11
      src/Avalonia.Controls/MenuItem.cs
  32. 7
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  33. 38
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  34. 4
      src/Avalonia.Controls/Primitives/Popup.cs
  35. 11
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  36. 9
      src/Avalonia.Controls/TextBox.cs
  37. 53
      src/Avalonia.Controls/ToolTip.cs
  38. 3
      src/Avalonia.Controls/TopLevel.cs
  39. 22
      src/Avalonia.Controls/TreeView.cs
  40. 9
      src/Avalonia.Controls/WindowBase.cs
  41. 27
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  42. 5
      src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs
  43. 3
      src/Avalonia.HtmlRenderer/HtmlControl.cs
  44. 5
      src/Avalonia.Input/FocusManager.cs
  45. 15
      src/Avalonia.Input/ICustomKeyboardNavigation.cs
  46. 7
      src/Avalonia.Input/IInputDevice.cs
  47. 8
      src/Avalonia.Input/IInputRoot.cs
  48. 1
      src/Avalonia.Input/InputManager.cs
  49. 12
      src/Avalonia.Input/KeyboardDevice.cs
  50. 27
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  51. 31
      src/Avalonia.Input/MouseDevice.cs
  52. 16
      src/Avalonia.Input/Navigation/DirectionalNavigation.cs
  53. 87
      src/Avalonia.Input/Navigation/TabNavigation.cs
  54. 10
      src/Avalonia.Layout/IEmbeddedLayoutRoot.cs
  55. 99
      src/Avalonia.Layout/LayoutManager.cs
  56. 25
      src/Avalonia.Layout/Layoutable.cs
  57. 10
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  58. 4
      src/Avalonia.Styling/Styling/Style.cs
  59. 2
      src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
  60. 13
      src/Avalonia.Visuals/Vector.cs
  61. 1
      src/Gtk/Avalonia.Gtk/GtkPlatform.cs
  62. 2
      src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs
  63. 3
      src/Gtk/Avalonia.Gtk/TopLevelImpl.cs
  64. 1
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  65. 5
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  66. 2
      src/Gtk/Avalonia.Gtk3/Interop/GObject.cs
  67. 18
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  68. 2
      src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs
  69. 3
      src/Gtk/Avalonia.Gtk3/SystemDialogs.cs
  70. 14
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  71. 1
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  72. 6
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs
  73. 1
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  74. 4
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  75. 2
      src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs
  76. 4
      src/Markup/Avalonia.Markup/Data/BindingExpression.cs
  77. 3
      src/Skia/Avalonia.Skia.Desktop.NetStandard/Avalonia.Skia.Desktop.NetStandard.csproj
  78. 5
      src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj
  79. 2
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  80. 12
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  81. 101
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  82. 2
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  83. 2
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  84. 15
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  85. 47
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  86. 16
      src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs
  87. 7
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  88. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs
  89. 123
      src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
  90. 36
      src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs
  91. 38
      src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs
  92. 208
      src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
  93. 59
      src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs
  94. 121
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs
  95. 16
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs
  96. 30
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs
  97. 241
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  98. 73
      src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs
  99. 1
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  100. 52
      src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs

3
.gitignore

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

43
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

2
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"

25
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")

5
build/Base.props

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

2
build/Moq.props

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

1
build/SharpDX.props

@ -3,6 +3,7 @@
<PackageReference Include="SharpDX" Version="3.1.1" />
<PackageReference Include="SharpDX.Direct2D1" Version="3.1.1" />
<PackageReference Include="SharpDX.Direct3D11" Version="3.1.1" />
<PackageReference Include="SharpDX.Direct3D9" Version="3.1.1" Condition="'$(UseDirect3D9)' == 'true'" />
<PackageReference Include="SharpDX.DXGI" Version="3.1.1" />
</ItemGroup>
</Project>

2
build/SkiaSharp.props

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

6
build/XUnit.props

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

2
docs/tutorial/from-wpf.md

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

24
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
///////////////////////////////////////////////////////////////////////////////

23
samples/ControlCatalog/Pages/MenuPage.xaml

@ -31,5 +31,28 @@
</MenuItem>
</Menu>
</StackPanel>
<TextBlock Classes="h2" Text="A context menu (right click)">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Standard _Menu Item"/>
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
</MenuItem>
<MenuItem Header="Menu Item with _Icon">
<MenuItem.Icon>
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
</UserControl>

14
samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml

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

15
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 += "!";
};
}
}
}

2
samples/interop/WindowsInteropTest/Program.cs

@ -15,7 +15,7 @@ namespace WindowsInteropTest
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
AppBuilder.Configure<App>().UseWin32().UseSkia().SetupWithoutStarting();
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().SetupWithoutStarting();
System.Windows.Forms.Application.Run(new SelectorForm());
}
}

7
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

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

5
scripts/ReplaceNugetCache.ps1

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

7
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/

1
src/Android/Avalonia.Android/AndroidPlatform.cs

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

2
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()
{

2
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")]

3
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<RawInputEventArgs> Input { get; set; }

2
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

1
src/Avalonia.Base/Avalonia.Base.csproj

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

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

36
src/Avalonia.Controls/Button.cs

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

2
src/Avalonia.Controls/ContextMenu.cs

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

11
src/Avalonia.Controls/Control.cs

@ -118,6 +118,7 @@ namespace Avalonia.Controls
public Control()
{
_nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
}
/// <summary>
@ -369,6 +370,12 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnAttachedToLogicalTreeCore(e);
}
/// <inheritdoc/>
void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
@ -418,7 +425,7 @@ namespace Avalonia.Controls
if (_isAttachedToLogicalTree)
{
var oldRoot = FindStyleRoot(old);
var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
if (oldRoot == null)
{
@ -436,7 +443,7 @@ namespace Avalonia.Controls
_parent = (IControl)parent;
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true)
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{
var newRoot = FindStyleRoot(this);

10
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@ -22,6 +22,8 @@ namespace Avalonia.Controls.Embedding
[CanBeNull]
public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl;
protected bool EnforceClientSize { get; set; } = true;
public void Prepare()
{
EnsureInitialized();
@ -38,12 +40,12 @@ namespace Avalonia.Controls.Embedding
init.EndInit();
}
}
protected override Size MeasureOverride(Size availableSize)
{
var cs = PlatformImpl?.ClientSize ?? default(Size);
base.MeasureOverride(cs);
return cs;
if (EnforceClientSize)
availableSize = PlatformImpl?.ClientSize ?? default(Size);
return base.MeasureOverride(availableSize);
}
private readonly NameScope _nameScope = new NameScope();

2
src/Avalonia.Controls/Menu.cs

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

11
src/Avalonia.Controls/MenuItem.cs

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

7
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

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

38
src/Avalonia.Controls/Presenters/ContentPresenter.cs

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

4
src/Avalonia.Controls/Primitives/Popup.cs

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

11
src/Avalonia.Controls/Primitives/TemplatedControl.cs

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

9
src/Avalonia.Controls/TextBox.cs

@ -236,6 +236,11 @@ namespace Avalonia.Controls
{
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
_presenter.Cursor = new Cursor(StandardCursorType.Ibeam);
if(IsFocused)
{
_presenter.ShowCaret();
}
}
protected override void OnGotFocus(GotFocusEventArgs e)
@ -254,7 +259,7 @@ namespace Avalonia.Controls
}
else
{
_presenter.ShowCaret();
_presenter?.ShowCaret();
}
}
@ -263,7 +268,7 @@ namespace Avalonia.Controls
base.OnLostFocus(e);
SelectionStart = 0;
SelectionEnd = 0;
_presenter.HideCaret();
_presenter?.HideCaret();
}
protected override void OnTextInput(TextInputEventArgs e)

53
src/Avalonia.Controls/ToolTip.cs

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

3
src/Avalonia.Controls/TopLevel.cs

@ -163,6 +163,9 @@ namespace Avalonia.Controls
set { SetValue(PointerOverElementProperty, value); }
}
/// <inheritdoc/>
IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
/// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>

22
src/Avalonia.Controls/TreeView.cs

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

9
src/Avalonia.Controls/WindowBase.cs

@ -29,6 +29,7 @@ namespace Avalonia.Controls
public static readonly DirectProperty<WindowBase, bool> IsActiveProperty =
AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive);
private bool _hasExecutedInitialLayoutPass;
private bool _isActive;
private bool _ignoreVisibilityChange;
@ -136,7 +137,13 @@ namespace Avalonia.Controls
{
EnsureInitialized();
IsVisible = true;
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
if (!_hasExecutedInitialLayoutPass)
{
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
_hasExecutedInitialLayoutPass = true;
}
PlatformImpl?.Show();
}
finally

27
src/Avalonia.Diagnostics/DevTools.xaml.cs

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

5
src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs

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

3
src/Avalonia.HtmlRenderer/HtmlControl.cs

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

5
src/Avalonia.Input/FocusManager.cs

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

15
src/Avalonia.Input/ICustomKeyboardNavigation.cs

@ -0,0 +1,15 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Input
{
/// <summary>
/// Designates a control as handling its own keyboard navigation.
/// </summary>
public interface ICustomKeyboardNavigation
{
(bool handled, IInputElement next) GetNext(IInputElement element, NavigationDirection direction);
}
}

7
src/Avalonia.Input/IInputDevice.cs

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

8
src/Avalonia.Input/IInputRoot.cs

@ -1,6 +1,8 @@
// 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 JetBrains.Annotations;
namespace Avalonia.Input
{
/// <summary>
@ -27,5 +29,11 @@ namespace Avalonia.Input
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>
bool ShowAccessKeys { get; set; }
/// <summary>
/// Gets associated mouse device
/// </summary>
[CanBeNull]
IMouseDevice MouseDevice { get; }
}
}

1
src/Avalonia.Input/InputManager.cs

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

12
src/Avalonia.Input/KeyboardDevice.cs

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

27
src/Avalonia.Input/KeyboardNavigationHandler.cs

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

31
src/Avalonia.Input/MouseDevice.cs

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

16
src/Avalonia.Input/Navigation/DirectionalNavigation.cs

@ -41,7 +41,7 @@ namespace Avalonia.Input.Navigation
{
case KeyboardNavigationMode.Continue:
return GetNextInContainer(element, container, direction) ??
GetFirstInNextContainer(element, direction);
GetFirstInNextContainer(element, element, direction);
case KeyboardNavigationMode.Cycle:
return GetNextInContainer(element, container, direction) ??
GetFocusableDescendant(container, direction);
@ -173,10 +173,12 @@ namespace Avalonia.Input.Navigation
/// <summary>
/// Gets the first item that should be focused in the next container.
/// </summary>
/// <param name="element">The element being navigated away from.</param>
/// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer(
IInputElement element,
IInputElement container,
NavigationDirection direction)
{
@ -200,6 +202,16 @@ namespace Avalonia.Input.Navigation
if (sibling != null)
{
if (sibling is ICustomKeyboardNavigation custom)
{
var (handled, customNext) = custom.GetNext(element, direction);
if (handled)
{
return customNext;
}
}
if (sibling.CanFocus())
{
next = sibling;
@ -214,7 +226,7 @@ namespace Avalonia.Input.Navigation
if (next == null)
{
next = GetFirstInNextContainer(parent, direction);
next = GetFirstInNextContainer(element, parent, direction);
}
}
else

87
src/Avalonia.Input/Navigation/TabNavigation.cs

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

10
src/Avalonia.Layout/IEmbeddedLayoutRoot.cs

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

99
src/Avalonia.Layout/LayoutManager.cs

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

25
src/Avalonia.Layout/Layoutable.cs

@ -367,6 +367,14 @@ namespace Avalonia.Layout
}
}
/// <summary>
/// Called by InvalidateMeasure
/// </summary>
protected virtual void OnMeasureInvalidated()
{
}
/// <summary>
/// Invalidates the measurement of the control and queues a new layout pass.
/// </summary>
@ -378,8 +386,13 @@ namespace Avalonia.Layout
IsMeasureValid = false;
IsArrangeValid = false;
LayoutManager.Instance?.InvalidateMeasure(this);
InvalidateVisual();
if (((ILayoutable)this).IsAttachedToVisualTree)
{
LayoutManager.Instance?.InvalidateMeasure(this);
InvalidateVisual();
}
OnMeasureInvalidated();
}
}
@ -393,8 +406,12 @@ namespace Avalonia.Layout
Logger.Verbose(LogArea.Layout, this, "Invalidated arrange");
IsArrangeValid = false;
LayoutManager.Instance?.InvalidateArrange(this);
InvalidateVisual();
if (((ILayoutable)this).IsAttachedToVisualTree)
{
LayoutManager.Instance?.InvalidateArrange(this);
InvalidateVisual();
}
}
}

10
src/Avalonia.Styling/LogicalTree/ILogical.cs

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

4
src/Avalonia.Styling/Styling/Style.cs

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

2
src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs

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

13
src/Avalonia.Visuals/Vector.cs

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

1
src/Gtk/Avalonia.Gtk/GtkPlatform.cs

@ -51,7 +51,6 @@ namespace Avalonia.Gtk
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance)
.Bind<IKeyboardDevice>().ToConstant(GtkKeyboardDevice.Instance)
.Bind<IMouseDevice>().ToConstant(GtkMouseDevice.Instance)
.Bind<IPlatformSettings>().ToConstant(s_instance)
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<IRendererFactory>().ToConstant(s_instance)

2
src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs

@ -48,7 +48,7 @@ namespace Avalonia.Gtk
public int Height => _surface.Height;
public int RowBytes => _surface.Stride;
//TODO: Proper DPI detect
public Size Dpi => new Size(96, 96);
public Vector Dpi => new Vector(96, 96);
public PixelFormat Format => PixelFormat.Bgra8888;
}
}

3
src/Gtk/Avalonia.Gtk/TopLevelImpl.cs

@ -75,6 +75,8 @@ namespace Avalonia.Gtk
}
}
public IMouseDevice MouseDevice => GtkMouseDevice.Instance;
public Avalonia.Controls.WindowState WindowState
{
get
@ -114,6 +116,7 @@ namespace Avalonia.Gtk
public Action Closed { get; set; }
public Action Deactivated { get; set; }
public Action<RawInputEventArgs> Input { get; set; }

1
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -34,7 +34,6 @@ namespace Avalonia.Gtk3
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactory())
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IMouseDevice>().ToConstant(Mouse)
.Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IPlatformThreadingInterface>().ToConstant(Instance)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialog>()

5
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@ -52,12 +52,11 @@ namespace Avalonia.Gtk3
public int RowBytes { get; }
public Size Dpi
public Vector Dpi
{
get
{
return new Size(96, 96) * _factor;
return new Vector(96, 96) * _factor;
}
}

2
src/Gtk/Avalonia.Gtk3/Interop/GObject.cs

@ -41,7 +41,7 @@ namespace Avalonia.Gtk3.Interop
class GtkWindow : GtkWidget
{
public static GtkWindow Null { get; } = new GtkWindow();
}
class GtkImContext : GObject

18
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -500,6 +500,24 @@ namespace Avalonia.Gtk3.Interop
public gdouble delta_y;
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct GdkEventCrossing
{
public GdkEventType type;
public IntPtr window;
public gint8 send_event;
public IntPtr subwindow;
public guint32 time;
public gdouble x;
public gdouble y;
public gdouble x_root;
public gdouble y_root;
public int mode;
public int detail;
public bool focus;
public GdkModifierType state;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct GdkEventWindowState
{

2
src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs

@ -11,6 +11,8 @@ namespace Avalonia.Gtk3.Interop
public Utf8Buffer(string s) : base(IntPtr.Zero, true)
{
if (s == null)
return;
_data = Encoding.UTF8.GetBytes(s);
_gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
handle = _gchandle.AddrOfPinnedObject();

3
src/Gtk/Avalonia.Gtk3/SystemDialogs.cs

@ -18,7 +18,8 @@ namespace Avalonia.Gtk3
bool multiselect, string initialFileName)
{
GtkFileChooser dlg;
using (var name = title != null ? new Utf8Buffer(title) : null)
parent = parent ?? GtkWindow.Null;
using (var name = new Utf8Buffer(title))
dlg = Native.GtkFileChooserDialogNew(name, parent, action, IntPtr.Zero);
if (multiselect)
Native.GtkFileChooserSetSelectMultiple(dlg, true);

14
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -45,6 +45,7 @@ namespace Avalonia.Gtk3
ConnectEvent("window-state-event", OnStateChanged);
ConnectEvent("key-press-event", OnKeyEvent);
ConnectEvent("key-release-event", OnKeyEvent);
ConnectEvent("leave-notify-event", OnLeaveNotifyEvent);
Connect<Native.D.signal_generic>("destroy", OnDestroy);
Native.GtkWidgetRealize(gtkWidget);
_lastSize = ClientSize;
@ -194,6 +195,18 @@ namespace Avalonia.Gtk3
return true;
}
private unsafe bool OnLeaveNotifyEvent(IntPtr w, IntPtr pev, IntPtr userData)
{
var evnt = (GdkEventCrossing*) pev;
var position = new Point(evnt->x, evnt->y);
Input(new RawMouseEventArgs(Gtk3Platform.Mouse,
evnt->time,
_inputRoot,
RawMouseEventType.Move,
position, GetModifierKeys(evnt->state)));
return true;
}
private unsafe bool OnCommit(IntPtr gtkwidget, IntPtr utf8string, IntPtr userdata)
{
Input(new RawTextInputEventArgs(Gtk3Platform.Keyboard, _lastKbdEvent, Utf8Buffer.StringFromPtr(utf8string)));
@ -233,6 +246,7 @@ namespace Avalonia.Gtk3
}
}
public IMouseDevice MouseDevice => Gtk3Platform.Mouse;
public double Scaling => (double) 1 / (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);

1
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -56,6 +56,7 @@ namespace Avalonia.LinuxFramebuffer
}
public Size ClientSize => _fb.PixelSize;
public IMouseDevice MouseDevice => LinuxFramebufferPlatform.MouseDevice;
public double Scaling => 1;
public IEnumerable<object> Surfaces => new object[] {_fb};
public Action<RawInputEventArgs> Input { get; set; }

6
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs

@ -13,16 +13,16 @@ namespace Avalonia.LinuxFramebuffer
{
public sealed unsafe class LinuxFramebuffer : IFramebufferPlatformSurface, IDisposable
{
private readonly Size _dpi;
private readonly Vector _dpi;
private int _fd;
private fb_fix_screeninfo _fixedInfo;
private fb_var_screeninfo _varInfo;
private IntPtr _mappedLength;
private IntPtr _mappedAddress;
public LinuxFramebuffer(string fileName = null, Size? dpi = null)
public LinuxFramebuffer(string fileName = null, Vector? dpi = null)
{
_dpi = dpi ?? new Size(96, 96);
_dpi = dpi ?? new Vector(96, 96);
fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
_fd = NativeUnsafeMethods.open(fileName, 2, 0);
if (_fd <= 0)

1
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -33,7 +33,6 @@ namespace Avalonia.LinuxFramebuffer
AvaloniaLocator.CurrentMutable
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IMouseDevice>().ToConstant(MouseDevice)
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)

4
src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs

@ -11,7 +11,7 @@ namespace Avalonia.LinuxFramebuffer
private fb_var_screeninfo _varInfo;
private readonly IntPtr _address;
public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Size dpi)
public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Vector dpi)
{
_fb = fb;
_fixedInfo = fixedInfo;
@ -41,7 +41,7 @@ namespace Avalonia.LinuxFramebuffer
public int Width => (int)_varInfo.xres;
public int Height => (int) _varInfo.yres;
public int RowBytes => (int) _fixedInfo.line_length;
public Size Dpi { get; }
public Vector Dpi { get; }
public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
}
}

2
src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs

@ -102,7 +102,7 @@ namespace Avalonia.Markup.Xaml.Data
private object ConvertValue(IList<object> values, Type targetType)
{
var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentUICulture);
var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentCulture);
if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null)
{

4
src/Markup/Avalonia.Markup/Data/BindingExpression.cs

@ -122,7 +122,7 @@ namespace Avalonia.Markup.Data
value,
type,
ConverterParameter,
CultureInfo.CurrentUICulture);
CultureInfo.CurrentCulture);
if (converted == AvaloniaProperty.UnsetValue)
{
@ -186,7 +186,7 @@ namespace Avalonia.Markup.Data
value,
_targetType,
ConverterParameter,
CultureInfo.CurrentUICulture);
CultureInfo.CurrentCulture);
notification = converted as BindingNotification;

3
src/Skia/Avalonia.Skia.Desktop.NetStandard/Avalonia.Skia.Desktop.NetStandard.csproj

@ -5,6 +5,7 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RootNamespace>Avalonia.Skia.Desktop</RootNamespace>
<AssemblyName>Avalonia.Skia.Desktop</AssemblyName>
<IncludeLinuxSkia>true</IncludeLinuxSkia>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
@ -42,4 +43,4 @@
<Import Project="..\..\..\build\SkiaSharp.props" />
<Import Project="..\Avalonia.Skia\Avalonia.Skia.projitems" Label="Shared" />
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
</Project>
</Project>

5
src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj

@ -54,6 +54,9 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup>
<IncludeLinuxSkia>true</IncludeLinuxSkia>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
@ -106,4 +109,4 @@
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\build\SkiaSharp.props" />
</Project>
</Project>

2
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();
}

12
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)

101
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<AvaloniaFormattedTextLine> _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)

2
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);
}
}

2
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -50,7 +50,9 @@
</Compile>
<Compile Include="Direct2D1Platform.cs" />
<Compile Include="Disposable.cs" />
<Compile Include="ExternalRenderTarget.cs" />
<Compile Include="HwndRenderTarget.cs" />
<Compile Include="IExternalDirect2DRenderTargetSurface.cs" />
<Compile Include="Media\BrushImpl.cs" />
<Compile Include="Media\BrushWrapper.cs" />
<Compile Include="Media\DrawingContextImpl.cs" />

15
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -135,12 +135,17 @@ namespace Avalonia.Direct2D1
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
var nativeWindow = surfaces?.OfType<IPlatformHandle>().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");
}

47
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();
}
});
}
}
}

16
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();
}
}

7
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;
/// <summary>
@ -32,15 +33,18 @@ namespace Avalonia.Direct2D1.Media
/// <param name="renderTarget">The render target to draw to.</param>
/// <param name="directWriteFactory">The DirectWrite factory.</param>
/// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
/// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param>
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
{

2
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;
}

123
src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Avalonia.Win32.Interop</RootNamespace>
<AssemblyName>Avalonia.Win32.Interop</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Win32\Interop\UnmanagedMethods.cs">
<Link>UnmanagedMethods.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Wpf\CursorShim.cs" />
<Compile Include="Wpf\Direct2DImageSurface.cs" />
<Compile Include="Wpf\IntSize.cs" />
<Compile Include="Wpf\WpfInteropExtensions.cs" />
<Compile Include="Wpf\WpfAvaloniaHost.cs" />
<Compile Include="Wpf\WpfMouseDevice.cs" />
<Compile Include="Wpf\WpfTopLevelImpl.cs" />
<Compile Include="Wpf\WritableBitmapSurface.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Avalonia.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Avalonia.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Avalonia.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
<Project>{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}</Project>
<Name>Avalonia.DotNetFrameworkRuntime</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Avalonia.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Avalonia.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Avalonia.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Avalonia.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Avalonia.Visuals</Name>
</ProjectReference>
<ProjectReference Include="..\..\Gtk\Avalonia.Cairo\Avalonia.Cairo.csproj">
<Project>{fb05ac90-89ba-4f2f-a924-f37875fb547c}</Project>
<Name>Avalonia.Cairo</Name>
</ProjectReference>
<ProjectReference Include="..\..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Avalonia.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Avalonia.Markup</Name>
</ProjectReference>
<ProjectReference Include="..\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj">
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
<Name>Avalonia.Direct2D1</Name>
</ProjectReference>
<ProjectReference Include="..\Avalonia.Win32\Avalonia.Win32.csproj">
<Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
<Name>Avalonia.Win32</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<UseDirect3D9>true</UseDirect3D9>
</PropertyGroup>
<Import Project="..\..\..\build\SharpDX.props" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

36
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")]

38
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;
}
}
}

208
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<Surface>())
{
_resource = texture.QueryInterface<SharpDX.Direct3D11.Resource>();
Target = new RenderTarget(AvaloniaLocator.Current.GetService<SharpDX.Direct2D1.Factory>(), 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<SharpDX.DXGI.Resource>())
{
_sharedResource = texture.QueryInterface<SharpDX.Direct3D11.Resource>();
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<SharpDX.DXGI.Device>()
.QueryInterface<SharpDX.Direct3D11.Device>();
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<T>(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);
}
}
}

59
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<IntSize>
{
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; }
}
}

121
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)
{
//
}
}
}

16
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);
}
}

30
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);
}
}
}

241
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<object> _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<IKeyboardDevice>();
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<IDisposable>())
d.Dispose();
}
Size ITopLevelImpl.ClientSize => _finalSize;
IMouseDevice ITopLevelImpl.MouseDevice => _mouse;
double ITopLevelImpl.Scaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1;
IEnumerable<object> 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<RawInputEventArgs> ITopLevelImpl.Input { get; set; } //TODO
Action<Rect> ITopLevelImpl.Paint { get; set; }
Action<Size> ITopLevelImpl.Resized { get; set; }
Action<double> 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);
}
}
}

73
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;
}
}
}

1
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -57,7 +57,6 @@
<Compile Include="Embedding\WinFormsAvaloniaControlHost.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Embedding\WpfAvaloniaControlHost.cs" />
<Compile Include="IconImpl.cs" />
<Compile Include="WinFormsWin32Platform.cs" />
</ItemGroup>

52
src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs

@ -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();
}
}
}

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

Loading…
Cancel
Save