Browse Source

Initial implementation of Proper WPF embedding™

pull/1016/head
Nikita Tsukanov 9 years ago
parent
commit
67ff5ba53c
  1. 43
      Avalonia.sln
  2. 3
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
  3. 6
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  4. 2
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  5. 101
      src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
  6. 36
      src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs
  7. 38
      src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs
  8. 16
      src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs
  9. 80
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs
  10. 30
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs
  11. 190
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  12. 81
      src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs

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

3
samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml

@ -5,6 +5,7 @@
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>
@ -15,7 +16,7 @@
</StackPanel>
</GroupBox>
<GroupBox Header="Avalonia">
<embedding:WpfAvaloniaControlHost x:Name="Host"/>
<wpf:WpfAvaloniaHost x:Name="Host"/>
</GroupBox>
</DockPanel>
</Window>

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

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

@ -39,6 +39,8 @@ namespace Avalonia.Controls.Embedding
}
}
public Size MeasureBase(Size availableSize) => base.MeasureOverride(availableSize);
protected override Size MeasureOverride(Size availableSize)
{
var cs = PlatformImpl?.ClientSize ?? default(Size);

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

@ -0,0 +1,101 @@
<?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\Helpers.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.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="..\Avalonia.Win32\Avalonia.Win32.csproj">
<Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
<Name>Avalonia.Win32</Name>
</ProjectReference>
</ItemGroup>
<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;
}
}
}

16
src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.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 Helpers
{
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);
}
}

80
src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs

@ -0,0 +1,80 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Avalonia.Win32.Interop.Wpf
{
public class WpfAvaloniaHost : FrameworkElement, IDisposable
{
private WpfTopLevelImpl _impl = new WpfTopLevelImpl();
private readonly SynchronizationContext _sync;
public WpfAvaloniaHost()
{
_sync = SynchronizationContext.Current;
_impl.ControlRoot.Prepare();
_impl.Visibility = Visibility.Visible;
AddLogicalChild(_impl);
AddVisualChild(_impl);
}
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.ControlRoot.MeasureBase(constraint.ToAvaloniaSize()).ToWpfSize();
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);
}
}
}
}

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

190
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -0,0 +1,190 @@
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 WpfTopLevelImpl()
{
PresentationSource.AddSourceChangedHandler(this, OnSourceChanged);
_hook = WndProc;
_ttl = this;
_surfaces = new object[] {new WritableBitmapSurface(this)};
_mouse = new WpfMouseDevice(this);
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
ControlRoot = new EmbeddableControlRoot(this);
SnapsToDevicePixels = true;
Focusable = true;
}
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();
Size ITopLevelImpl.ClientSize => _finalSize;
IMouseDevice ITopLevelImpl.MouseDevice => _mouse;
double ITopLevelImpl.Scaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1;
IEnumerable<object> ITopLevelImpl.Surfaces => _surfaces;
protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
{
_finalSize = finalSize.ToAvaloniaSize();
_ttl.Resized?.Invoke(finalSize.ToAvaloniaSize());
return base.ArrangeOverride(finalSize);
}
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
=> ControlRoot.MeasureBase(availableSize.ToAvaloniaSize()).ToWpfSize();
protected override void OnRender(DrawingContext drawingContext)
{
_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);
}
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);
}
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 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;
}
}

81
src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs

@ -0,0 +1,81 @@
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 = 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;
}
Vector GetScaling()
{
var src = PresentationSource.FromVisual(_impl)?.CompositionTarget;
if (src == null)
return new Vector(1, 1);
return new Vector(src.TransformToDevice.M11, src.TransformToDevice.M22);
}
}
}
Loading…
Cancel
Save