From 67ff5ba53c351a9b9198d14ddd37e69b217aceed Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 6 Jun 2017 16:12:02 +0300 Subject: [PATCH 01/32] =?UTF-8?q?Initial=20implementation=20of=20Proper=20?= =?UTF-8?q?WPF=20embedding=E2=84=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Avalonia.sln | 43 ++++ .../WindowsInteropTest/EmbedToWpfDemo.xaml | 3 +- .../WindowsInteropTest.csproj | 6 +- .../Embedding/EmbeddableControlRoot.cs | 2 + .../Avalonia.Win32.Interop.csproj | 101 ++++++++++ .../Properties/AssemblyInfo.cs | 36 ++++ .../Avalonia.Win32.Interop/Wpf/CursorShim.cs | 38 ++++ .../Avalonia.Win32.Interop/Wpf/Helpers.cs | 16 ++ .../Wpf/WpfAvaloniaHost.cs | 80 ++++++++ .../Wpf/WpfMouseDevice.cs | 30 +++ .../Wpf/WpfTopLevelImpl.cs | 190 ++++++++++++++++++ .../Wpf/WritableBitmapSurface.cs | 81 ++++++++ 12 files changed, 624 insertions(+), 2 deletions(-) create mode 100644 src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj create mode 100644 src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs create mode 100644 src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs create mode 100644 src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs create mode 100644 src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs create mode 100644 src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs create mode 100644 src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs create mode 100644 src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs diff --git a/Avalonia.sln b/Avalonia.sln index f12af02236..afaee6a907 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -191,6 +191,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 @@ -2589,6 +2591,46 @@ Global {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.Build.0 = Release|Any CPU {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.ActiveCfg = Release|Any CPU {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.Build.0 = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.ActiveCfg = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.Build.0 = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.ActiveCfg = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.Build.0 = Debug|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.Build.0 = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.ActiveCfg = Release|Any CPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2649,5 +2691,6 @@ Global {4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E} {854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888} EndGlobalSection EndGlobal diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml index 1115cf5768..5a346d4e8d 100644 --- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml +++ b/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"> @@ -15,7 +16,7 @@ - + diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index 28e5e274d0..d0405a3af8 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -14,7 +14,7 @@ true - AnyCPU + x86 true full false @@ -164,6 +164,10 @@ {3e908f67-5543-4879-a1dc-08eace79b3cd} Avalonia.Direct2D1 + + {cbc4ff2f-92d4-420b-be21-9fe0b930b04e} + Avalonia.Win32.Interop + {811a76cf-1cf6-440f-963b-bbe31bd72a82} Avalonia.Win32 diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index b8d54fa67b..45b4f8eaa1 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/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); diff --git a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj new file mode 100644 index 0000000000..ae61fc697f --- /dev/null +++ b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj @@ -0,0 +1,101 @@ + + + + + Debug + AnyCPU + {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} + Library + Properties + Avalonia.Win32.Interop + Avalonia.Win32.Interop + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + UnmanagedMethods.cs + + + + + + + + + + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Avalonia.Animation + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Avalonia.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Avalonia.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Avalonia.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Avalonia.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Avalonia.Layout + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Avalonia.Styling + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Avalonia.Visuals + + + {fb05ac90-89ba-4f2f-a924-f37875fb547c} + Avalonia.Cairo + + + {811a76cf-1cf6-440f-963b-bbe31bd72a82} + Avalonia.Win32 + + + + \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs b/src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..7c0d638381 --- /dev/null +++ b/src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Avalonia.Win32.Interop")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Avalonia.Win32.Interop")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cbc4ff2f-92d4-420b-be21-9fe0b930b04e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs new file mode 100644 index 0000000000..6ae898ae3d --- /dev/null +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Avalonia.Win32.Interop.Wpf +{ + static class CursorShim + { + public static Cursor FromHCursor(IntPtr hcursor) + { + var field = typeof(Cursor).GetFields(BindingFlags.NonPublic | BindingFlags.Instance) + .FirstOrDefault(f => f.FieldType == typeof(SafeHandle)); + if (field == null) + return null; + var rv = (Cursor) FormatterServices.GetUninitializedObject(typeof(Cursor)); + field.SetValue(rv, new SafeHandleShim(hcursor)); + return rv; + } + + class SafeHandleShim : SafeHandle + { + public SafeHandleShim(IntPtr hcursor) : base(new IntPtr(-1), false) + { + this.handle = hcursor; + } + + protected override bool ReleaseHandle() => true; + + public override bool IsInvalid => false; + } + } +} diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs new file mode 100644 index 0000000000..9c1f39e86b --- /dev/null +++ b/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); + } +} diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs new file mode 100644 index 0000000000..2d350fbe30 --- /dev/null +++ b/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); + } + } + } +} diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs new file mode 100644 index 0000000000..4aad80f8a5 --- /dev/null +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs @@ -0,0 +1,30 @@ +using System; +using Avalonia.Controls.Embedding; +using Avalonia.Input; +using Avalonia.VisualTree; + +namespace Avalonia.Win32.Interop.Wpf +{ + class WpfMouseDevice : MouseDevice + { + private readonly WpfTopLevelImpl _impl; + + public WpfMouseDevice(WpfTopLevelImpl impl) + { + _impl = impl; + } + + public override void Capture(IInputElement control) + { + if (control == null) + { + System.Windows.Input.Mouse.Capture(null); + } + else if ((control.GetVisualRoot() as EmbeddableControlRoot)?.PlatformImpl != _impl) + throw new ArgumentException("Visual belongs to unknown toplevel"); + else + System.Windows.Input.Mouse.Capture(_impl); + base.Capture(control); + } + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs new file mode 100644 index 0000000000..56dc26992e --- /dev/null +++ b/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 _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(); + + 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 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 ITopLevelImpl.Input { get; set; } //TODO + Action ITopLevelImpl.Paint { get; set; } + Action ITopLevelImpl.Resized { get; set; } + Action ITopLevelImpl.ScalingChanged { get; set; } + Action ITopLevelImpl.Closed { get; set; } + public new event Action LostFocus; + + } +} diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs new file mode 100644 index 0000000000..1dd1cb983a --- /dev/null +++ b/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); + } + } +} From b4d43be327d639cb5419046e8afc2122c2a43772 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 5 Jun 2017 23:37:42 +0300 Subject: [PATCH 02/32] DevTools now can be attached to any toplevel --- .../WindowsInteropTest/EmbedToWpfDemo.xaml.cs | 9 ++++++++- src/Avalonia.Diagnostics/DevTools.xaml.cs | 18 +++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs index e60c9ced0a..eebd18dece 100644 --- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs +++ b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs @@ -11,6 +11,7 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using Avalonia; using Avalonia.Controls; using ControlCatalog; using Window = System.Windows.Window; @@ -25,7 +26,13 @@ namespace WindowsInteropTest public EmbedToWpfDemo() { InitializeComponent(); - Host.Content = new MainView(); + var view = new MainView(); + Host.Content = view; + view.AttachedToVisualTree += delegate + { + view.AttachDevTools(); + }; + } } } diff --git a/src/Avalonia.Diagnostics/DevTools.xaml.cs b/src/Avalonia.Diagnostics/DevTools.xaml.cs index b735372b59..e48cdf5681 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml.cs +++ b/src/Avalonia.Diagnostics/DevTools.xaml.cs @@ -16,9 +16,9 @@ namespace Avalonia { public static class WindowExtensions { - public static void AttachDevTools(this Window window) + public static void AttachDevTools(this Control control) { - Avalonia.Diagnostics.DevTools.Attach(window); + Avalonia.Diagnostics.DevTools.Attach((TopLevel)control.GetVisualRoot()); } } } @@ -27,7 +27,7 @@ namespace Avalonia.Diagnostics { public class DevTools : UserControl { - private static Dictionary s_open = new Dictionary(); + private static Dictionary s_open = new Dictionary(); 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(); } } From 3126901721ea3f20c4c2c784cbc9f4dcf1e8ceea Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Jun 2017 17:36:01 +0300 Subject: [PATCH 03/32] WPF embedding improvements --- .../WindowsInteropTest/EmbedToWpfDemo.xaml | 11 ++++++ .../WindowsInteropTest/EmbedToWpfDemo.xaml.cs | 5 +++ src/Avalonia.Layout/Layoutable.cs | 2 +- .../Avalonia.Win32.Interop.csproj | 12 +++++++ .../Wpf/WpfAvaloniaHost.cs | 35 +++++++++++++++---- .../Wpf/WpfTopLevelImpl.cs | 11 ++++++ 6 files changed, 69 insertions(+), 7 deletions(-) diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml index 5a346d4e8d..1d8dc32a69 100644 --- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml +++ b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml @@ -1,6 +1,7 @@  + + + + + + + + + + diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs index eebd18dece..636d89dc70 100644 --- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs +++ b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs @@ -32,6 +32,11 @@ namespace WindowsInteropTest { view.AttachDevTools(); }; + var btn = (Avalonia.Controls.Button) RightBtn.Content; + btn.Click += delegate + { + btn.Content += "!"; + }; } } diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 20050058bf..3f6d789877 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -370,7 +370,7 @@ namespace Avalonia.Layout /// /// Invalidates the measurement of the control and queues a new layout pass. /// - public void InvalidateMeasure() + public virtual void InvalidateMeasure() { if (IsMeasureValid) { diff --git a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj index ae61fc697f..e2d764c62c 100644 --- a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj +++ b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj @@ -68,6 +68,10 @@ {d2221c82-4a25-4583-9b43-d791e3f6820c} Avalonia.Controls + + {4a1abb09-9047-4bd5-a4ad-a055e52c5ee0} + Avalonia.DotNetFrameworkRuntime + {62024b2d-53eb-4638-b26b-85eeaa54866e} Avalonia.Input @@ -92,6 +96,14 @@ {fb05ac90-89ba-4f2f-a924-f37875fb547c} Avalonia.Cairo + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Avalonia.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Avalonia.Markup + {811a76cf-1cf6-440f-963b-bbe31bd72a82} Avalonia.Win32 diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs index 2d350fbe30..4f85a326f0 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs @@ -1,29 +1,35 @@ 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 { - public class WpfAvaloniaHost : FrameworkElement, IDisposable + [ContentProperty("Content")] + public class WpfAvaloniaHost : FrameworkElement, IDisposable, IAddChild { - private WpfTopLevelImpl _impl = new WpfTopLevelImpl(); + private WpfTopLevelImpl _impl; private readonly SynchronizationContext _sync; public WpfAvaloniaHost() { _sync = SynchronizationContext.Current; + _impl = new WpfTopLevelImpl(); _impl.ControlRoot.Prepare(); _impl.Visibility = Visibility.Visible; AddLogicalChild(_impl); AddVisualChild(_impl); } - public object Content { @@ -47,9 +53,13 @@ namespace Avalonia.Win32.Interop.Wpf } } - protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint) - => _impl.ControlRoot.MeasureBase(constraint.ToAvaloniaSize()).ToWpfSize(); - + 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)); @@ -76,5 +86,18 @@ namespace Avalonia.Win32.Interop.Wpf GC.SuppressFinalize(this); } } + + void IAddChild.AddChild(object value) + { + if (Content == null) + Content = value; + else + throw new InvalidOperationException(); + } + + void IAddChild.AddText(string text) + { + // + } } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 56dc26992e..c82b71d3a5 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -33,6 +33,15 @@ namespace Avalonia.Win32.Interop.Wpf public EmbeddableControlRoot ControlRoot { get; } internal ImageSource ImageSource { get; set; } + public class CustomControlRoot : EmbeddableControlRoot + { + public override void InvalidateMeasure() + { + base.InvalidateMeasure(); + ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); + } + } + public WpfTopLevelImpl() { PresentationSource.AddSourceChangedHandler(this, OnSourceChanged); @@ -158,6 +167,8 @@ namespace Avalonia.Win32.Interop.Wpf _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, From d06c9e04eda93b0a47793ed667f7d72ad2cb125a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Jun 2017 18:43:47 +0300 Subject: [PATCH 04/32] WPF integration improvements --- .../Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs | 1 + .../Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs index 4f85a326f0..e36b53199a 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs @@ -29,6 +29,7 @@ namespace Avalonia.Win32.Interop.Wpf _impl.Visibility = Visibility.Visible; AddLogicalChild(_impl); AddVisualChild(_impl); + SnapsToDevicePixels = true; } public object Content diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index c82b71d3a5..7be6b7e800 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -54,6 +54,10 @@ namespace Avalonia.Win32.Interop.Wpf ControlRoot = new EmbeddableControlRoot(this); SnapsToDevicePixels = true; Focusable = true; + DataContextChanged += delegate + { + ControlRoot.DataContext = DataContext; + }; } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) @@ -80,15 +84,18 @@ namespace Avalonia.Win32.Interop.Wpf IEnumerable ITopLevelImpl.Surfaces => _surfaces; + private Size _previousSize; protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) { _finalSize = finalSize.ToAvaloniaSize(); + if (_finalSize == _previousSize) + return finalSize; + _previousSize = _finalSize; _ttl.Resized?.Invoke(finalSize.ToAvaloniaSize()); return base.ArrangeOverride(finalSize); } - protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) - => ControlRoot.MeasureBase(availableSize.ToAvaloniaSize()).ToWpfSize(); + protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) => ControlRoot.MeasureBase(availableSize.ToAvaloniaSize()).ToWpfSize(); protected override void OnRender(DrawingContext drawingContext) { From df13ab2ecb1b889a4acc759d085bd17ea810f902 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Jun 2017 19:09:17 +0300 Subject: [PATCH 05/32] [TEMP] Call measure directly --- .../Embedding/EmbeddableControlRoot.cs | 12 ++++++------ .../Wpf/WpfTopLevelImpl.cs | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index 45b4f8eaa1..179dccaf76 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/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,14 +40,12 @@ namespace Avalonia.Controls.Embedding init.EndInit(); } } - - public Size MeasureBase(Size availableSize) => base.MeasureOverride(availableSize); - + 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(); diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 7be6b7e800..b74358ec4d 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -35,10 +35,16 @@ namespace Avalonia.Win32.Interop.Wpf public class CustomControlRoot : EmbeddableControlRoot { + public CustomControlRoot() + { + EnforceClientSize = false; + + } + public override void InvalidateMeasure() { - base.InvalidateMeasure(); ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); + base.InvalidateMeasure(); } } @@ -95,10 +101,16 @@ namespace Avalonia.Win32.Interop.Wpf return base.ArrangeOverride(finalSize); } - protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) => ControlRoot.MeasureBase(availableSize.ToAvaloniaSize()).ToWpfSize(); + 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)); From b84e9e1123943013cf85412b89e8c67911fcb99a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Jun 2017 19:15:42 +0300 Subject: [PATCH 06/32] Use correct control root --- src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index b74358ec4d..927c975399 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -35,15 +35,14 @@ namespace Avalonia.Win32.Interop.Wpf public class CustomControlRoot : EmbeddableControlRoot { - public CustomControlRoot() + public CustomControlRoot(WpfTopLevelImpl impl) : base(impl) { EnforceClientSize = false; - } public override void InvalidateMeasure() { - ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); + ((FrameworkElement) PlatformImpl)?.InvalidateMeasure(); base.InvalidateMeasure(); } } @@ -57,7 +56,7 @@ namespace Avalonia.Win32.Interop.Wpf _mouse = new WpfMouseDevice(this); _keyboard = AvaloniaLocator.Current.GetService(); - ControlRoot = new EmbeddableControlRoot(this); + ControlRoot = new CustomControlRoot(this); SnapsToDevicePixels = true; Focusable = true; DataContextChanged += delegate From 317b0f7034ebfa00e69371a8e2d7d5378b169c3b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Jun 2017 19:34:04 +0300 Subject: [PATCH 07/32] testtesttest --- src/Avalonia.Layout/Layoutable.cs | 2 +- .../Wpf/WpfTopLevelImpl.cs | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 3f6d789877..778a869003 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -399,7 +399,7 @@ namespace Avalonia.Layout } /// - void ILayoutable.ChildDesiredSizeChanged(ILayoutable control) + public virtual void ChildDesiredSizeChanged(ILayoutable control) { if (!_measuring) { diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 927c975399..1aa09853ab 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -40,10 +40,23 @@ namespace Avalonia.Win32.Interop.Wpf EnforceClientSize = false; } - public override void InvalidateMeasure() + public override void ChildDesiredSizeChanged(ILayoutable control) { - ((FrameworkElement) PlatformImpl)?.InvalidateMeasure(); - base.InvalidateMeasure(); + ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); + base.ChildDesiredSizeChanged(control); + } + + protected override void HandleResized(Size clientSize) + { + ClientSize = clientSize; + LayoutManager.Instance.ExecuteLayoutPass(); + Renderer?.Resized(clientSize); + } + + protected override void ArrangeCore(Rect finalRect) + { + base.ArrangeOverride(finalRect.Size); + Bounds = finalRect; } } From 09c9d7b7d516d30192e1fdefe45125023499cac0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Jun 2017 20:03:27 +0300 Subject: [PATCH 08/32] Layout integration seems to be working now --- src/Avalonia.Layout/LayoutManager.cs | 2 +- src/Avalonia.Layout/Layoutable.cs | 2 +- .../Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs | 10 ++-------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index b7b83bf852..93c2227cab 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -151,7 +151,7 @@ namespace Avalonia.Layout if (root != null) { - root.Arrange(new Rect(root.DesiredSize)); + root.Arrange(new Rect(root.ClientSize)); } else if (parent != null) { diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 778a869003..3f6d789877 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -399,7 +399,7 @@ namespace Avalonia.Layout } /// - public virtual void ChildDesiredSizeChanged(ILayoutable control) + void ILayoutable.ChildDesiredSizeChanged(ILayoutable control) { if (!_measuring) { diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 1aa09853ab..c990bb708e 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -40,10 +40,10 @@ namespace Avalonia.Win32.Interop.Wpf EnforceClientSize = false; } - public override void ChildDesiredSizeChanged(ILayoutable control) + public override void InvalidateMeasure() { ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); - base.ChildDesiredSizeChanged(control); + base.InvalidateMeasure(); } protected override void HandleResized(Size clientSize) @@ -52,12 +52,6 @@ namespace Avalonia.Win32.Interop.Wpf LayoutManager.Instance.ExecuteLayoutPass(); Renderer?.Resized(clientSize); } - - protected override void ArrangeCore(Rect finalRect) - { - base.ArrangeOverride(finalRect.Size); - Bounds = finalRect; - } } public WpfTopLevelImpl() From a2c46aceea16a9f9aced2292f0491999fa4fa5ec Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Jun 2017 20:08:39 +0300 Subject: [PATCH 09/32] Removed old wpf control host and added new nuget package --- packages.cake | 15 ++++++ .../Embedding/WpfAvaloniaControlHost.cs | 52 ------------------- 2 files changed, 15 insertions(+), 52 deletions(-) delete mode 100644 src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs diff --git a/packages.cake b/packages.cake index 1e8e356694..bc1aeef416 100644 --- a/packages.cake +++ b/packages.cake @@ -465,6 +465,21 @@ 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 }, + }, + Files = new [] + { + new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" } + }, + BasePath = context.Directory("./src/Windows"), + OutputDirectory = parameters.NugetRoot + }, /////////////////////////////////////////////////////////////////////////////// // Avalonia.LinuxFramebuffer /////////////////////////////////////////////////////////////////////////////// diff --git a/src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs b/src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs deleted file mode 100644 index 663f6906ed..0000000000 --- a/src/Windows/Avalonia.Win32/Embedding/WpfAvaloniaControlHost.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Forms.Integration; -using System.Windows.Interop; -using Avalonia.Controls; -using Avalonia.Win32.Interop; - -namespace Avalonia.Win32.Embedding -{ - public class WpfAvaloniaControlHost : HwndHost - { - private WinFormsAvaloniaControlHost _host; - private Avalonia.Controls.Control _content; - - public Avalonia.Controls.Control Content - { - get { return _content; } - set - { - if (_host != null) - _host.Content = value; - _content = value; - - } - } - - void DestroyHost() - { - _host?.Dispose(); - _host = null; - } - - protected override HandleRef BuildWindowCore(HandleRef hwndParent) - { - DestroyHost(); - _host = new WinFormsAvaloniaControlHost {Content = _content}; - UnmanagedMethods.SetParent(_host.Handle, hwndParent.Handle); - return new HandleRef(this, _host.Handle); - } - - protected override void DestroyWindowCore(HandleRef hwnd) - { - DestroyHost(); - } - } -} From 1d44d3f7afde95c613164b1f56ac6a95b2fa48d0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 8 Jun 2017 22:07:28 +0300 Subject: [PATCH 10/32] Removed reference to the old host --- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index b134f4666e..198bb7ce0d 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -57,7 +57,6 @@ Component - From abf866cf610025a6b8431e5ba2cdc527efcd4a71 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 7 Jul 2017 23:46:58 +0300 Subject: [PATCH 11/32] Fixes to get it working again after merge --- .../interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs | 3 ++- src/Avalonia.Diagnostics/DevTools.xaml.cs | 4 +--- src/Avalonia.Layout/IEmbeddedLayoutRoot.cs | 10 ++++++++++ src/Avalonia.Layout/LayoutManager.cs | 10 ++++------ .../Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs | 4 +++- 5 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 src/Avalonia.Layout/IEmbeddedLayoutRoot.cs diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs index 636d89dc70..5ca2768d9e 100644 --- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs +++ b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs @@ -27,11 +27,12 @@ namespace WindowsInteropTest { InitializeComponent(); var view = new MainView(); - Host.Content = view; + view.AttachedToVisualTree += delegate { view.AttachDevTools(); }; + Host.Content = view; var btn = (Avalonia.Controls.Button) RightBtn.Content; btn.Click += delegate { diff --git a/src/Avalonia.Diagnostics/DevTools.xaml.cs b/src/Avalonia.Diagnostics/DevTools.xaml.cs index e48cdf5681..85c3cfddd8 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml.cs +++ b/src/Avalonia.Diagnostics/DevTools.xaml.cs @@ -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; } diff --git a/src/Avalonia.Layout/IEmbeddedLayoutRoot.cs b/src/Avalonia.Layout/IEmbeddedLayoutRoot.cs new file mode 100644 index 0000000000..24f0ccd82e --- /dev/null +++ b/src/Avalonia.Layout/IEmbeddedLayoutRoot.cs @@ -0,0 +1,10 @@ +namespace Avalonia.Layout +{ + /// + /// A special layout root with enforced size for Arrange pass + /// + public interface IEmbeddedLayoutRoot : ILayoutRoot + { + Size AllocatedSize { get; } + } +} \ No newline at end of file diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 0ff417e791..108922b5b9 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -186,14 +186,12 @@ namespace Avalonia.Layout if (!control.IsArrangeValid && control.IsAttachedToVisualTree) { - if (control is ILayoutRoot root) - { - root.Arrange(new Rect(root.ClientSize)); - } + if (control is IEmbeddedLayoutRoot embeddedRoot) + control.Arrange(new Rect(embeddedRoot.AllocatedSize)); + else if (control is ILayoutRoot root) + control.Arrange(new Rect(root.DesiredSize)); else - { control.Arrange(control.PreviousArrange.Value); - } } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index c990bb708e..229e330a33 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -33,7 +33,7 @@ namespace Avalonia.Win32.Interop.Wpf public EmbeddableControlRoot ControlRoot { get; } internal ImageSource ImageSource { get; set; } - public class CustomControlRoot : EmbeddableControlRoot + public class CustomControlRoot : EmbeddableControlRoot, IEmbeddedLayoutRoot { public CustomControlRoot(WpfTopLevelImpl impl) : base(impl) { @@ -52,6 +52,8 @@ namespace Avalonia.Win32.Interop.Wpf LayoutManager.Instance.ExecuteLayoutPass(); Renderer?.Resized(clientSize); } + + public Size AllocatedSize => ClientSize; } public WpfTopLevelImpl() From cc354da723057dbcf911e26eb66ffa8b9086fee6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 10 Jul 2017 17:13:52 +0300 Subject: [PATCH 12/32] Focus embedded toplevel on click --- src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 229e330a33..094929deda 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -169,6 +169,7 @@ namespace Avalonia.Win32.Interop.Wpf else return; MouseEvent(type, e); + Focus(); } protected override void OnMouseUp(MouseButtonEventArgs e) @@ -183,6 +184,7 @@ namespace Avalonia.Win32.Interop.Wpf else return; MouseEvent(type, e); + Focus(); } protected override void OnMouseMove(MouseEventArgs e) From 35f353c2db2cd3d93bb6dbe325ccdc8556f3d0c9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 12 Jul 2017 19:31:02 +0300 Subject: [PATCH 13/32] Use OnMeasureInvalidated instead of virtual InvalidateMeasure --- src/Avalonia.Layout/Layoutable.cs | 11 ++++++++++- .../Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs | 3 +-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index a08ab77d70..523c720e2f 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -367,10 +367,18 @@ namespace Avalonia.Layout } } + + /// + /// Called by InvalidateMeasure + /// + protected virtual void OnMeasureInvalidated() + { + } + /// /// Invalidates the measurement of the control and queues a new layout pass. /// - public virtual void InvalidateMeasure() + public void InvalidateMeasure() { if (IsMeasureValid) { @@ -384,6 +392,7 @@ namespace Avalonia.Layout LayoutManager.Instance?.InvalidateMeasure(this); InvalidateVisual(); } + OnMeasureInvalidated(); } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 094929deda..0620c6cc57 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -40,10 +40,9 @@ namespace Avalonia.Win32.Interop.Wpf EnforceClientSize = false; } - public override void InvalidateMeasure() + protected override void OnMeasureInvalidated() { ((FrameworkElement)PlatformImpl)?.InvalidateMeasure(); - base.InvalidateMeasure(); } protected override void HandleResized(Size clientSize) From 76bc7aaafb8c76ab7612ef98a2fc3f6fd56b27a5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 12 Jul 2017 19:57:25 +0300 Subject: [PATCH 14/32] Fixed issues from PR comments #1016 --- .../WindowsInteropTest/EmbedToWpfDemo.xaml.cs | 5 +---- src/Avalonia.Diagnostics/DevTools.xaml.cs | 15 ++++++++++++--- .../Avalonia.Win32.Interop.csproj | 2 +- .../Wpf/{Helpers.cs => WpfInteropExtensions.cs} | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) rename src/Windows/Avalonia.Win32.Interop/Wpf/{Helpers.cs => WpfInteropExtensions.cs} (94%) diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs index 5ca2768d9e..1a91d67b49 100644 --- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs +++ b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs @@ -28,10 +28,7 @@ namespace WindowsInteropTest InitializeComponent(); var view = new MainView(); - view.AttachedToVisualTree += delegate - { - view.AttachDevTools(); - }; + view.AttachDevToolsToTopLevelOnVisualTreeAttachment(); Host.Content = view; var btn = (Avalonia.Controls.Button) RightBtn.Content; btn.Click += delegate diff --git a/src/Avalonia.Diagnostics/DevTools.xaml.cs b/src/Avalonia.Diagnostics/DevTools.xaml.cs index 85c3cfddd8..06965ece89 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml.cs +++ b/src/Avalonia.Diagnostics/DevTools.xaml.cs @@ -14,12 +14,21 @@ using Avalonia.VisualTree; namespace Avalonia { - public static class WindowExtensions + public static class DevToolsExtensions { - public static void AttachDevTools(this Control control) + public static void AttachDevTools(this TopLevel control) { - Avalonia.Diagnostics.DevTools.Attach((TopLevel)control.GetVisualRoot()); + Avalonia.Diagnostics.DevTools.Attach(control); } + + public static void AttachDevToolsToTopLevelOnVisualTreeAttachment(this Control control) + { + (control.GetVisualRoot() as TopLevel)?.AttachDevTools(); + control.AttachedToVisualTree += delegate + { + (control.GetVisualRoot() as TopLevel)?.AttachDevTools(); + }; + } } } diff --git a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj index e2d764c62c..c5cd2ab64d 100644 --- a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj +++ b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj @@ -49,7 +49,7 @@ - + diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs similarity index 94% rename from src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs rename to src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs index 9c1f39e86b..6433ff05e0 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Avalonia.Win32.Interop.Wpf { - static class Helpers + 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); From e5289146d99cff3f8be3a96af5f878fc787c0d73 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 14 Jul 2017 11:20:02 +0300 Subject: [PATCH 15/32] Remove AttachDevToolsToTopLevelOnVisualTreeAttachment since @grokys doesn't like it --- .../interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs | 7 +++++-- src/Avalonia.Diagnostics/DevTools.xaml.cs | 9 --------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs index 1a91d67b49..c7a23c22fc 100644 --- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs +++ b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs @@ -13,6 +13,7 @@ using System.Windows.Navigation; using System.Windows.Shapes; using Avalonia; using Avalonia.Controls; +using Avalonia.VisualTree; using ControlCatalog; using Window = System.Windows.Window; @@ -27,8 +28,10 @@ namespace WindowsInteropTest { InitializeComponent(); var view = new MainView(); - - view.AttachDevToolsToTopLevelOnVisualTreeAttachment(); + view.AttachedToVisualTree += delegate + { + ((TopLevel) view.GetVisualRoot()).AttachDevTools(); + }; Host.Content = view; var btn = (Avalonia.Controls.Button) RightBtn.Content; btn.Click += delegate diff --git a/src/Avalonia.Diagnostics/DevTools.xaml.cs b/src/Avalonia.Diagnostics/DevTools.xaml.cs index 06965ece89..6593a8cd42 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml.cs +++ b/src/Avalonia.Diagnostics/DevTools.xaml.cs @@ -20,15 +20,6 @@ namespace Avalonia { Avalonia.Diagnostics.DevTools.Attach(control); } - - public static void AttachDevToolsToTopLevelOnVisualTreeAttachment(this Control control) - { - (control.GetVisualRoot() as TopLevel)?.AttachDevTools(); - control.AttachedToVisualTree += delegate - { - (control.GetVisualRoot() as TopLevel)?.AttachDevTools(); - }; - } } } From d6cca364697d451b07398c4483a33b0d7770ebdc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 14 Jul 2017 09:46:03 +0100 Subject: [PATCH 16/32] add nuget cache replace scripts. --- scripts/ReplaceNugetCache.ps1 | 5 +++++ scripts/ReplaceNugetCache.sh | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 scripts/ReplaceNugetCache.ps1 create mode 100644 scripts/ReplaceNugetCache.sh diff --git a/scripts/ReplaceNugetCache.ps1 b/scripts/ReplaceNugetCache.ps1 new file mode 100644 index 0000000000..854442eb09 --- /dev/null +++ b/scripts/ReplaceNugetCache.ps1 @@ -0,0 +1,5 @@ +copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp1.0\ +copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard1.1\ +copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard1.1\ +copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.skia.desktop\$args\lib\netstandard1.3\ +copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard1.1\ diff --git a/scripts/ReplaceNugetCache.sh b/scripts/ReplaceNugetCache.sh new file mode 100644 index 0000000000..3878e9e138 --- /dev/null +++ b/scripts/ReplaceNugetCache.sh @@ -0,0 +1,7 @@ + #!/usr/bin/env bash + + cp ./bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp1.0/ + cp ./bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard1.1/ + cp ./bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard1.1/ + cp ./bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.skia.desktop/$1/lib/netstandard1.3/ + From ccb400c8512c99bcb3884966ca908984b4dc61f9 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Thu, 13 Jul 2017 23:09:06 +0100 Subject: [PATCH 17/32] fix linux script. --- scripts/ReplaceNugetCache.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/ReplaceNugetCache.sh b/scripts/ReplaceNugetCache.sh index 3878e9e138..2ce3e7648d 100644 --- a/scripts/ReplaceNugetCache.sh +++ b/scripts/ReplaceNugetCache.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash - cp ./bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp1.0/ - cp ./bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard1.1/ - cp ./bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard1.1/ - cp ./bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.skia.desktop/$1/lib/netstandard1.3/ + 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/ From a1b5e43e184c4b685837395cb77fcf0d6a34b0b8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 14 Jul 2017 11:40:24 +0100 Subject: [PATCH 18/32] add a context menu to the control catalog. --- samples/ControlCatalog/Pages/MenuPage.xaml | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index 98171f29d6..9c5591c849 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -31,5 +31,28 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f953e4c3a40ae6552972b6160a7a2f472bb06012 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 14 Jul 2017 15:20:27 +0100 Subject: [PATCH 19/32] chmod linux replace script --- scripts/ReplaceNugetCache.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/ReplaceNugetCache.sh diff --git a/scripts/ReplaceNugetCache.sh b/scripts/ReplaceNugetCache.sh old mode 100644 new mode 100755 From e3992ef4ad46850b98d31f6be20815ab06511f44 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 14 Jul 2017 15:53:56 +0100 Subject: [PATCH 20/32] fix context menu not closing when an item is clicked. --- src/Avalonia.Controls/ContextMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 0a69a5277f..fdb04f4ade 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -19,7 +19,7 @@ namespace Avalonia.Controls { ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); - MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick); + MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); } /// From 76c90df2da048f46327d26ead6f5d0db1a76c53f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Jul 2017 04:12:40 +0300 Subject: [PATCH 21/32] Initial implementation for Direct2D rendering for WPF integration --- build/SharpDX.props | 1 + samples/interop/WindowsInteropTest/Program.cs | 2 +- .../ExternalRenderTarget.cs | 11 +- .../IExternalDirect2DRenderTargetSurface.cs | 3 +- .../Avalonia.Win32.Interop.csproj | 9 + .../Wpf/Direct2DImageSurface.cs | 206 ++++++++++++++++++ .../Wpf/WpfTopLevelImpl.cs | 11 +- .../Wpf/WritableBitmapSurface.cs | 10 +- 8 files changed, 233 insertions(+), 20 deletions(-) create mode 100644 src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs diff --git a/build/SharpDX.props b/build/SharpDX.props index e381bc03e6..0eb910e71e 100644 --- a/build/SharpDX.props +++ b/build/SharpDX.props @@ -3,6 +3,7 @@ + diff --git a/samples/interop/WindowsInteropTest/Program.cs b/samples/interop/WindowsInteropTest/Program.cs index 4770688ecf..fac06d74b0 100644 --- a/samples/interop/WindowsInteropTest/Program.cs +++ b/samples/interop/WindowsInteropTest/Program.cs @@ -15,7 +15,7 @@ namespace WindowsInteropTest { System.Windows.Forms.Application.EnableVisualStyles(); System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); - AppBuilder.Configure().UseWin32().UseSkia().SetupWithoutStarting(); + AppBuilder.Configure().UseWin32().UseDirect2D1().SetupWithoutStarting(); System.Windows.Forms.Application.Run(new SelectorForm()); } } diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index b1c0e7e30a..307048f7b4 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -15,7 +15,6 @@ namespace Avalonia.Direct2D1 { private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; private readonly DirectWriteFactory _dwFactory; - private SharpDX.Direct2D1.RenderTarget _target; public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, DirectWriteFactory dwFactory) { @@ -25,15 +24,14 @@ namespace Avalonia.Direct2D1 public void Dispose() { - _target?.Dispose(); - _target = null; + _externalRenderTargetProvider.DestroyRenderTarget(); } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - _target = _target ?? _externalRenderTargetProvider.CreateRenderTarget(); + var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); _externalRenderTargetProvider.BeforeDrawing(); - return new DrawingContextImpl(visualBrushRenderer, _target, _dwFactory, null, () => + return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () => { try { @@ -41,8 +39,7 @@ namespace Avalonia.Direct2D1 } catch (SharpDXException ex) when ((uint) ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET { - _target?.Dispose(); - _target = null; + _externalRenderTargetProvider.DestroyRenderTarget(); } }); } diff --git a/src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs b/src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs index 0774c25937..aad51f46d5 100644 --- a/src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs +++ b/src/Windows/Avalonia.Direct2D1/IExternalDirect2DRenderTargetSurface.cs @@ -8,7 +8,8 @@ namespace Avalonia.Direct2D1 { public interface IExternalDirect2DRenderTargetSurface { - SharpDX.Direct2D1.RenderTarget CreateRenderTarget(); + SharpDX.Direct2D1.RenderTarget GetOrCreateRenderTarget(); + void DestroyRenderTarget(); void BeforeDrawing(); void AfterDrawing(); } diff --git a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj index c5cd2ab64d..099a7f4074 100644 --- a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj +++ b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj @@ -49,6 +49,7 @@ + @@ -104,10 +105,18 @@ {6417e941-21bc-467b-a771-0de389353ce6} Avalonia.Markup + + {3e908f67-5543-4879-a1dc-08eace79b3cd} + Avalonia.Direct2D1 + {811a76cf-1cf6-440f-963b-bbe31bd72a82} Avalonia.Win32 + + true + + \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs new file mode 100644 index 0000000000..303e0850aa --- /dev/null +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs @@ -0,0 +1,206 @@ +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 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 + { + class Pair: IDisposable + { + public SharpDX.Direct3D9.Surface Texture { get; } + public SharpDX.Direct3D11.Resource D3D11Resource { get; } + public SharpDX.Direct3D11.Resource StagingResource { get; } + public RenderTarget Target { get;} + public Size Size { get; } + + public Pair(Size size, Vector dpi) + { + int width = (int) size.Width; + int height = (int) size.Height; + 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 surface = texture.QueryInterface()) + using (var resource = texture.QueryInterface()) + { + D3D11Resource = texture.QueryInterface(); + var handle = resource.SharedHandle; + using (var texture9 = new Texture(s_d3DDevice, texture.Description.Width, + texture.Description.Height, 1, + Usage.RenderTarget, SharpDX.Direct3D9.Format.A8R8G8B8, Pool.Default, ref handle)) + Texture = texture9.GetSurfaceLevel(0); + Target = new RenderTarget(AvaloniaLocator.Current.GetService(), surface, + new RenderTargetProperties + { + DpiX = (float) dpi.X, + DpiY = (float) dpi.Y, + MinLevel = FeatureLevel.Level_10, + PixelFormat = new PixelFormat(Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied), + + }); + } + using (var texture = new Texture2D(s_dxDevice, new Texture2DDescription + { + Width = Math.Min(width, 16), + Height = Math.Min(height, 16), + ArraySize = 1, + MipLevels = 1, + Format = Format.B8G8R8A8_UNorm, + Usage = ResourceUsage.Staging, + SampleDescription = new SampleDescription(1, 0), + CpuAccessFlags = CpuAccessFlags.Read + })) + StagingResource = texture.QueryInterface(); + Size = size; + } + + public void Dispose() + { + Texture?.Dispose(); + Target?.Dispose(); + D3D11Resource?.Dispose(); + StagingResource?.Dispose(); + } + + public void Flush() + { + + s_dxDevice.ImmediateContext.CopySubresourceRegion(D3D11Resource, 0, + new ResourceRegion(0, 0, 0, 1, 1, 1), StagingResource, 0, 0, 0, 0); + s_dxDevice.ImmediateContext.MapSubresource(StagingResource, 0, MapMode.Read, MapFlags.None); + s_dxDevice.ImmediateContext.UnmapSubresource(StagingResource, 0); + + } + } + + private D3DImage _image; + private Pair _backBuffer; + private Pair _frontBuffer; + private readonly WpfTopLevelImpl _impl; + private static Device s_dxDevice; + private static Direct3DEx s_d3DContext; + private static DeviceEx s_d3DDevice; + + + [DllImport("user32.dll", SetLastError = false)] + private static extern IntPtr GetDesktopWindow(); + void EnsureDirectX() + { + if(s_d3DDevice != null) + return; + s_d3DContext = new Direct3DEx(); + + SharpDX.Direct3D9.PresentParameters presentparams = new SharpDX.Direct3D9.PresentParameters + { + Windowed = true, + SwapEffect = SharpDX.Direct3D9.SwapEffect.Discard, + DeviceWindowHandle = GetDesktopWindow(), + PresentationInterval = PresentInterval.Default + }; + s_dxDevice = s_dxDevice ?? AvaloniaLocator.Current.GetService() + .QueryInterface(); + s_d3DDevice = new DeviceEx(s_d3DContext, 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.FpuPreserve, presentparams); + + } + + public Direct2DImageSurface(WpfTopLevelImpl impl) + { + _impl = impl; + } + + public RenderTarget GetOrCreateRenderTarget() + { + EnsureDirectX(); + var scale = _impl.GetScaling(); + var size = new Size(_impl.ActualWidth * scale.X, _impl.ActualHeight * scale.Y); + var dpi = scale * 96; + + if (_backBuffer!=null && _backBuffer.Size == size) + return _backBuffer.Target; + + if (_image == null) + _image = new DX11Image(); + _impl.ImageSource = _image; + + + + RemoveAndDispose(ref _backBuffer); + if (size == default(Size)) + { + RemoveAndDispose(ref _frontBuffer); + _image.Lock(); + _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero); + _image.Unlock(); + return null; + } + _backBuffer = new Pair(size, dpi); + + return _backBuffer.Target; + } + + void RemoveAndDispose(ref T d) where T : IDisposable + { + d?.Dispose(); + d = default(T); + } + + void DoSwap() + { + + } + + void Swap() + { + var oldFront = _frontBuffer; + _frontBuffer = _backBuffer; + _backBuffer = oldFront; + _frontBuffer.Flush(); + _image.Lock(); + _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _frontBuffer?.Texture?.NativePointer ?? IntPtr.Zero, true); + _image.AddDirtyRect(new Int32Rect(0, 0, _image.PixelWidth, _image.PixelHeight)); + _image.Unlock(); + } + + public void DestroyRenderTarget() + { + //? + } + + public void BeforeDrawing() + { + + } + + public void AfterDrawing() => Swap(); + } +} diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 0620c6cc57..b5715d43d5 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -60,7 +60,7 @@ namespace Avalonia.Win32.Interop.Wpf PresentationSource.AddSourceChangedHandler(this, OnSourceChanged); _hook = WndProc; _ttl = this; - _surfaces = new object[] {new WritableBitmapSurface(this)}; + _surfaces = new object[] {new WritableBitmapSurface(this), new Direct2DImageSurface(this)}; _mouse = new WpfMouseDevice(this); _keyboard = AvaloniaLocator.Current.GetService(); @@ -224,6 +224,13 @@ namespace Avalonia.Win32.Interop.Wpf Action ITopLevelImpl.ScalingChanged { get; set; } Action ITopLevelImpl.Closed { get; set; } public new event Action LostFocus; - + + internal Vector GetScaling() + { + var src = PresentationSource.FromVisual(this)?.CompositionTarget; + if (src == null) + return new Vector(1, 1); + return new Vector(src.TransformToDevice.M11, src.TransformToDevice.M22); + } } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs index 1dd1cb983a..0f8752fb8d 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs @@ -24,7 +24,7 @@ namespace Avalonia.Win32.Interop.Wpf public ILockedFramebuffer Lock() { - var scale = GetScaling(); + 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) @@ -69,13 +69,5 @@ namespace Avalonia.Win32.Interop.Wpf 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); - } } } From 1cc13484c020190c5c03ede643b1c8defc6dade6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Jul 2017 04:32:01 +0300 Subject: [PATCH 22/32] Cleanup --- .../Wpf/Direct2DImageSurface.cs | 51 +++++++++---------- .../Wpf/WpfAvaloniaHost.cs | 20 +++++++- .../Wpf/WpfTopLevelImpl.cs | 7 ++- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs index 303e0850aa..57efe700dd 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs @@ -24,17 +24,18 @@ using Usage = SharpDX.Direct3D9.Usage; namespace Avalonia.Win32.Interop.Wpf { - class Direct2DImageSurface : IExternalDirect2DRenderTargetSurface + class Direct2DImageSurface : IExternalDirect2DRenderTargetSurface, IDisposable { - class Pair: IDisposable + class SwapBuffer: IDisposable { + + private readonly SharpDX.Direct3D11.Resource _resource; + private readonly SharpDX.Direct3D11.Resource _stagingResource; public SharpDX.Direct3D9.Surface Texture { get; } - public SharpDX.Direct3D11.Resource D3D11Resource { get; } - public SharpDX.Direct3D11.Resource StagingResource { get; } public RenderTarget Target { get;} public Size Size { get; } - public Pair(Size size, Vector dpi) + public SwapBuffer(Size size, Vector dpi) { int width = (int) size.Width; int height = (int) size.Height; @@ -53,7 +54,7 @@ namespace Avalonia.Win32.Interop.Wpf using (var surface = texture.QueryInterface()) using (var resource = texture.QueryInterface()) { - D3D11Resource = texture.QueryInterface(); + _resource = texture.QueryInterface(); var handle = resource.SharedHandle; using (var texture9 = new Texture(s_d3DDevice, texture.Description.Width, texture.Description.Height, 1, @@ -80,7 +81,7 @@ namespace Avalonia.Win32.Interop.Wpf SampleDescription = new SampleDescription(1, 0), CpuAccessFlags = CpuAccessFlags.Read })) - StagingResource = texture.QueryInterface(); + _stagingResource = texture.QueryInterface(); Size = size; } @@ -88,24 +89,22 @@ namespace Avalonia.Win32.Interop.Wpf { Texture?.Dispose(); Target?.Dispose(); - D3D11Resource?.Dispose(); - StagingResource?.Dispose(); + _resource?.Dispose(); + _stagingResource?.Dispose(); } public void Flush() { - - s_dxDevice.ImmediateContext.CopySubresourceRegion(D3D11Resource, 0, - new ResourceRegion(0, 0, 0, 1, 1, 1), StagingResource, 0, 0, 0, 0); - s_dxDevice.ImmediateContext.MapSubresource(StagingResource, 0, MapMode.Read, MapFlags.None); - s_dxDevice.ImmediateContext.UnmapSubresource(StagingResource, 0); - + s_dxDevice.ImmediateContext.CopySubresourceRegion(_resource, 0, + new ResourceRegion(0, 0, 0, 1, 1, 1), _stagingResource, 0, 0, 0, 0); + s_dxDevice.ImmediateContext.MapSubresource(_stagingResource, 0, MapMode.Read, MapFlags.None); + s_dxDevice.ImmediateContext.UnmapSubresource(_stagingResource, 0); } } private D3DImage _image; - private Pair _backBuffer; - private Pair _frontBuffer; + private SwapBuffer _backBuffer; + private SwapBuffer _frontBuffer; private readonly WpfTopLevelImpl _impl; private static Device s_dxDevice; private static Direct3DEx s_d3DContext; @@ -149,11 +148,9 @@ namespace Avalonia.Win32.Interop.Wpf return _backBuffer.Target; if (_image == null) - _image = new DX11Image(); + _image = new D3DImage(); _impl.ImageSource = _image; - - RemoveAndDispose(ref _backBuffer); if (size == default(Size)) { @@ -163,7 +160,7 @@ namespace Avalonia.Win32.Interop.Wpf _image.Unlock(); return null; } - _backBuffer = new Pair(size, dpi); + _backBuffer = new SwapBuffer(size, dpi); return _backBuffer.Target; } @@ -174,11 +171,6 @@ namespace Avalonia.Win32.Interop.Wpf d = default(T); } - void DoSwap() - { - - } - void Swap() { var oldFront = _frontBuffer; @@ -193,6 +185,8 @@ namespace Avalonia.Win32.Interop.Wpf public void DestroyRenderTarget() { + RemoveAndDispose(ref _backBuffer); + RemoveAndDispose(ref _frontBuffer); //? } @@ -202,5 +196,10 @@ namespace Avalonia.Win32.Interop.Wpf } public void AfterDrawing() => Swap(); + public void Dispose() + { + RemoveAndDispose(ref _frontBuffer); + RemoveAndDispose(ref _backBuffer); + } } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs index e36b53199a..0a15bc26d2 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs @@ -21,15 +21,31 @@ namespace Avalonia.Win32.Interop.Wpf { 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; - AddLogicalChild(_impl); - AddVisualChild(_impl); SnapsToDevicePixels = 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 diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index b5715d43d5..fbed2f621c 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -88,7 +88,12 @@ namespace Avalonia.Win32.Interop.Wpf _ttl.ScalingChanged?.Invoke(_ttl.Scaling); } - public void Dispose() => _ttl.Closed?.Invoke(); + public void Dispose() + { + _ttl.Closed?.Invoke(); + foreach(var d in _surfaces.OfType()) + d.Dispose(); + } Size ITopLevelImpl.ClientSize => _finalSize; IMouseDevice ITopLevelImpl.MouseDevice => _mouse; From e0f481ae9fcb91aa3d03ef796a87bac4c73e905b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Jul 2017 06:02:23 +0300 Subject: [PATCH 23/32] Move data between two Texture2D instances manually --- .../Wpf/Direct2DImageSurface.cs | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs index 57efe700dd..9bb9c014b7 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs @@ -17,6 +17,8 @@ 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; @@ -28,9 +30,9 @@ namespace Avalonia.Win32.Interop.Wpf { class SwapBuffer: IDisposable { - + private Query _event; private readonly SharpDX.Direct3D11.Resource _resource; - private readonly SharpDX.Direct3D11.Resource _stagingResource; + private readonly SharpDX.Direct3D11.Resource _sharedResource; public SharpDX.Direct3D9.Surface Texture { get; } public RenderTarget Target { get;} public Size Size { get; } @@ -39,6 +41,7 @@ namespace Avalonia.Win32.Interop.Wpf { 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, @@ -47,19 +50,14 @@ namespace Avalonia.Win32.Interop.Wpf MipLevels = 1, Format = Format.B8G8R8A8_UNorm, Usage = ResourceUsage.Default, - SampleDescription = new SampleDescription(1, 0), - BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, - OptionFlags = ResourceOptionFlags.Shared + SampleDescription = new SampleDescription(2, 0), + BindFlags = BindFlags.RenderTarget, })) using (var surface = texture.QueryInterface()) - using (var resource = texture.QueryInterface()) + { _resource = texture.QueryInterface(); - var handle = resource.SharedHandle; - using (var texture9 = new Texture(s_d3DDevice, texture.Description.Width, - texture.Description.Height, 1, - Usage.RenderTarget, SharpDX.Direct3D9.Format.A8R8G8B8, Pool.Default, ref handle)) - Texture = texture9.GetSurfaceLevel(0); + Target = new RenderTarget(AvaloniaLocator.Current.GetService(), surface, new RenderTargetProperties { @@ -72,16 +70,25 @@ namespace Avalonia.Win32.Interop.Wpf } using (var texture = new Texture2D(s_dxDevice, new Texture2DDescription { - Width = Math.Min(width, 16), - Height = Math.Min(height, 16), + Width = width, + Height = height, ArraySize = 1, MipLevels = 1, Format = Format.B8G8R8A8_UNorm, - Usage = ResourceUsage.Staging, + Usage = ResourceUsage.Default, SampleDescription = new SampleDescription(1, 0), - CpuAccessFlags = CpuAccessFlags.Read + BindFlags = BindFlags.RenderTarget|BindFlags.ShaderResource, + OptionFlags = ResourceOptionFlags.Shared, })) - _stagingResource = texture.QueryInterface(); + using (var resource = texture.QueryInterface()) + { + _sharedResource = texture.QueryInterface(); + var handle = resource.SharedHandle; + using (var texture9 = new Texture(s_d3DDevice, texture.Description.Width, + texture.Description.Height, 1, + Usage.RenderTarget, SharpDX.Direct3D9.Format.A8R8G8B8, Pool.Default, ref handle)) + Texture = texture9.GetSurfaceLevel(0); + } Size = size; } @@ -90,21 +97,20 @@ namespace Avalonia.Win32.Interop.Wpf Texture?.Dispose(); Target?.Dispose(); _resource?.Dispose(); - _stagingResource?.Dispose(); + _sharedResource?.Dispose(); } public void Flush() { - s_dxDevice.ImmediateContext.CopySubresourceRegion(_resource, 0, - new ResourceRegion(0, 0, 0, 1, 1, 1), _stagingResource, 0, 0, 0, 0); - s_dxDevice.ImmediateContext.MapSubresource(_stagingResource, 0, MapMode.Read, MapFlags.None); - s_dxDevice.ImmediateContext.UnmapSubresource(_stagingResource, 0); + 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 SwapBuffer _frontBuffer; private readonly WpfTopLevelImpl _impl; private static Device s_dxDevice; private static Direct3DEx s_d3DContext; @@ -154,7 +160,6 @@ namespace Avalonia.Win32.Interop.Wpf RemoveAndDispose(ref _backBuffer); if (size == default(Size)) { - RemoveAndDispose(ref _frontBuffer); _image.Lock(); _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero); _image.Unlock(); @@ -173,12 +178,9 @@ namespace Avalonia.Win32.Interop.Wpf void Swap() { - var oldFront = _frontBuffer; - _frontBuffer = _backBuffer; - _backBuffer = oldFront; - _frontBuffer.Flush(); + _backBuffer.Flush(); _image.Lock(); - _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _frontBuffer?.Texture?.NativePointer ?? IntPtr.Zero, true); + _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _backBuffer?.Texture?.NativePointer ?? IntPtr.Zero, true); _image.AddDirtyRect(new Int32Rect(0, 0, _image.PixelWidth, _image.PixelHeight)); _image.Unlock(); } @@ -186,8 +188,6 @@ namespace Avalonia.Win32.Interop.Wpf public void DestroyRenderTarget() { RemoveAndDispose(ref _backBuffer); - RemoveAndDispose(ref _frontBuffer); - //? } public void BeforeDrawing() @@ -198,7 +198,6 @@ namespace Avalonia.Win32.Interop.Wpf public void AfterDrawing() => Swap(); public void Dispose() { - RemoveAndDispose(ref _frontBuffer); RemoveAndDispose(ref _backBuffer); } } From 714869033c8bcd96932ea32308894a33901a0ec4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Jul 2017 12:06:04 +0300 Subject: [PATCH 24/32] Reference SharpDX.Direct3D9 package --- packages.cake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages.cake b/packages.cake index bc1aeef416..46fa26ee4b 100644 --- a/packages.cake +++ b/packages.cake @@ -80,6 +80,7 @@ public class Packages 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); @@ -91,6 +92,7 @@ public class Packages 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") @@ -472,6 +474,7 @@ public class Packages { 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 [] { From 099a7ae752a1f48e292785a0af4459103e67c735 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Jul 2017 12:23:00 +0300 Subject: [PATCH 25/32] Dispose event --- src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs index 9bb9c014b7..8891a76676 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs @@ -30,7 +30,7 @@ namespace Avalonia.Win32.Interop.Wpf { class SwapBuffer: IDisposable { - private Query _event; + private readonly Query _event; private readonly SharpDX.Direct3D11.Resource _resource; private readonly SharpDX.Direct3D11.Resource _sharedResource; public SharpDX.Direct3D9.Surface Texture { get; } @@ -98,6 +98,7 @@ namespace Avalonia.Win32.Interop.Wpf Target?.Dispose(); _resource?.Dispose(); _sharedResource?.Dispose(); + _event?.Dispose(); } public void Flush() From d5806d1af2bd05130691b4705a38235955e7b09d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Jul 2017 14:18:31 +0300 Subject: [PATCH 26/32] Fixed DPI support --- .../Avalonia.Win32.Interop.csproj | 1 + .../Wpf/Direct2DImageSurface.cs | 17 +++--- .../Avalonia.Win32.Interop/Wpf/IntSize.cs | 59 +++++++++++++++++++ .../Wpf/WpfAvaloniaHost.cs | 1 + 4 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs diff --git a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj index 099a7f4074..5f1a065028 100644 --- a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj +++ b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs index 8891a76676..8fe7275a0f 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs @@ -35,9 +35,9 @@ namespace Avalonia.Win32.Interop.Wpf private readonly SharpDX.Direct3D11.Resource _sharedResource; public SharpDX.Direct3D9.Surface Texture { get; } public RenderTarget Target { get;} - public Size Size { get; } + public IntSize Size { get; } - public SwapBuffer(Size size, Vector dpi) + public SwapBuffer(IntSize size, Vector dpi) { int width = (int) size.Width; int height = (int) size.Height; @@ -116,6 +116,7 @@ namespace Avalonia.Win32.Interop.Wpf private static Device s_dxDevice; private static Direct3DEx s_d3DContext; private static DeviceEx s_d3DDevice; + private Vector _oldDpi; [DllImport("user32.dll", SetLastError = false)] @@ -148,18 +149,20 @@ namespace Avalonia.Win32.Interop.Wpf { EnsureDirectX(); var scale = _impl.GetScaling(); - var size = new Size(_impl.ActualWidth * scale.X, _impl.ActualHeight * scale.Y); + 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) - _image = new D3DImage(); + + 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(Size)) + if (size == default(IntSize)) { _image.Lock(); _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero); diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs new file mode 100644 index 0000000000..3fdbdedfd9 --- /dev/null +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Win32.Interop.Wpf +{ + struct IntSize : IEquatable + { + public bool Equals(IntSize other) + { + return Width == other.Width && Height == other.Height; + } + + public IntSize(int width, int height) + { + Width = width; + Height = height; + } + + public IntSize(double width, double height) : this((int) width, (int) height) + { + + } + + public static implicit operator IntSize(System.Windows.Size size) + { + return new IntSize {Width = (int) size.Width, Height = (int) size.Height}; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is IntSize && Equals((IntSize) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Width * 397) ^ Height; + } + } + + public static bool operator ==(IntSize left, IntSize right) + { + return left.Equals(right); + } + + public static bool operator !=(IntSize left, IntSize right) + { + return !left.Equals(right); + } + + public int Width { get; set; } + public int Height { get; set; } + } +} diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs index 0a15bc26d2..6dc9ba9e09 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs @@ -29,6 +29,7 @@ namespace Avalonia.Win32.Interop.Wpf _impl.ControlRoot.Prepare(); _impl.Visibility = Visibility.Visible; SnapsToDevicePixels = true; + UseLayoutRounding = true; PresentationSource.AddSourceChangedHandler(this, OnSourceChanged); } From 6c4bbdcc4df569190319b754ed3260f511b7e2ed Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Jul 2017 18:22:11 +0300 Subject: [PATCH 27/32] Added System.ValueTuple to dependency list --- build/Base.props | 5 +++++ packages.cake | 4 ++++ src/Avalonia.Base/Avalonia.Base.csproj | 1 + src/Avalonia.Input/Avalonia.Input.csproj | 3 --- 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 build/Base.props diff --git a/build/Base.props b/build/Base.props new file mode 100644 index 0000000000..6689465338 --- /dev/null +++ b/build/Base.props @@ -0,0 +1,5 @@ + + + + + diff --git a/packages.cake b/packages.cake index bc1aeef416..06731beed1 100644 --- a/packages.cake +++ b/packages.cake @@ -75,6 +75,7 @@ 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; @@ -86,6 +87,7 @@ public class Packages 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); @@ -197,6 +199,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" }, @@ -205,6 +208,7 @@ public class Packages 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.ValueTuple", TargetFramework = "netcoreapp1.0", Version = SystemValueTupleVersion } }, Files = coreLibrariesNuSpecContent .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform) diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 95be67c98c..cc458545e2 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -30,6 +30,7 @@ Properties\SharedAssemblyInfo.cs + \ No newline at end of file diff --git a/src/Avalonia.Input/Avalonia.Input.csproj b/src/Avalonia.Input/Avalonia.Input.csproj index 0411cf77a5..e9e74e24fe 100644 --- a/src/Avalonia.Input/Avalonia.Input.csproj +++ b/src/Avalonia.Input/Avalonia.Input.csproj @@ -37,8 +37,5 @@ Properties\SharedAssemblyInfo.cs - - - \ No newline at end of file From 0d77f1701da1b5e6d2bf95e14bda8bf6b5ce96c3 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Jul 2017 18:25:58 +0300 Subject: [PATCH 28/32] Script typo --- packages.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages.cake b/packages.cake index 06731beed1..1a29695d82 100644 --- a/packages.cake +++ b/packages.cake @@ -207,7 +207,7 @@ 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 From 2638f02cd6d7d73cf067fa3c3b6ed8bdf5e23aea Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 18 Jul 2017 09:55:10 +0100 Subject: [PATCH 29/32] fix null reference when focusing textbox in attached to visual tree, but before ontemplate applied. --- src/Avalonia.Controls/TextBox.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index d2e8085d8c..6fc79e5d2c 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -236,6 +236,11 @@ namespace Avalonia.Controls { _presenter = e.NameScope.Get("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(); } } From c37dd6cda923f65da21aa4a9b6a0a35c30bd431a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 18 Jul 2017 10:02:15 +0100 Subject: [PATCH 30/32] null check on hide caret. --- src/Avalonia.Controls/TextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 6fc79e5d2c..92ab12f82e 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -268,7 +268,7 @@ namespace Avalonia.Controls base.OnLostFocus(e); SelectionStart = 0; SelectionEnd = 0; - _presenter.HideCaret(); + _presenter?.HideCaret(); } protected override void OnTextInput(TextInputEventArgs e) From df61044d50beeabe836d063ee2fae22978bf6f06 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 18 Jul 2017 21:20:14 +0300 Subject: [PATCH 31/32] Fixed text opacity for Skia backend --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 12 +-- src/Skia/Avalonia.Skia/FormattedTextImpl.cs | 101 +++++++++---------- 2 files changed, 53 insertions(+), 60 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 4a9f2c6572..3ed0509c0a 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -141,23 +141,17 @@ namespace Avalonia.Skia var rv = new PaintWrapper(paint); paint.IsStroke = false; - // TODO: SkiaSharp does not contain alpha yet! + double opacity = brush.Opacity * _currentOpacity; - //paint.SetAlpha(paint.GetAlpha() * opacity); paint.IsAntialias = true; - SKColor color = new SKColor(255, 255, 255, 255); - var solid = brush as ISolidColorBrush; - if (solid != null) - color = solid.Color.ToSKColor(); - - paint.Color = (new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * opacity))); - if (solid != null) { + paint.Color = new SKColor(solid.Color.R, solid.Color.G, solid.Color.B, (byte) (solid.Color.A * opacity)); return rv; } + paint.Color = (new SKColor(255, 255, 255, (byte)(255 * opacity))); var gradient = brush as IGradientBrush; if (gradient != null) diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 8568c80c04..1d224f97d7 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -42,7 +42,6 @@ namespace Avalonia.Skia _paint.Typeface = skiaTypeface; _paint.TextSize = (float)(typeface?.FontSize ?? 12); _paint.TextAlign = textAlignment.ToSKTextAlign(); - _paint.BlendMode = SKBlendMode.Src; _wrapping = wrapping; _constraint = constraint; @@ -200,66 +199,65 @@ namespace Avalonia.Skia } ctx->Canvas->restore(); */ - SKPaint paint = _paint; - IDisposable currd = null; - var currentWrapper = foreground; - - try + using (var paint = _paint.Clone()) { - SKPaint currFGPaint = ApplyWrapperTo(ref foreground, ref currd, paint); - bool hasCusomFGBrushes = _foregroundBrushes.Any(); - - for (int c = 0; c < _skiaLines.Count; c++) + IDisposable currd = null; + var currentWrapper = foreground; + SKPaint currentPaint = null; + try { - AvaloniaFormattedTextLine line = _skiaLines[c]; - - float x = TransformX(origin.X, 0, paint.TextAlign); + ApplyWrapperTo(ref currentPaint, foreground, ref currd, paint); + bool hasCusomFGBrushes = _foregroundBrushes.Any(); - if (!hasCusomFGBrushes) - { - var subString = Text.Substring(line.Start, line.Length); - canvas.DrawText(subString, x, origin.Y + line.Top + _lineOffset, paint); - } - else + for (int c = 0; c < _skiaLines.Count; c++) { - float currX = x; - string subStr; - int len; + AvaloniaFormattedTextLine line = _skiaLines[c]; - for (int i = line.Start; i < line.Start + line.Length;) - { - var fb = GetNextForegroundBrush(ref line, i, out len); - - if (fb != null) - { - //TODO: figure out how to get the brush size - currentWrapper = context.CreatePaint(fb, new Size()); - } - else - { - if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose(); - currentWrapper = foreground; - } + float x = TransformX(origin.X, 0, paint.TextAlign); - subStr = Text.Substring(i, len); + if (!hasCusomFGBrushes) + { + var subString = Text.Substring(line.Start, line.Length); + canvas.DrawText(subString, x, origin.Y + line.Top + _lineOffset, paint); + } + else + { + float currX = x; + string subStr; + int len; - if (currFGPaint != currentWrapper.Paint) + for (int i = line.Start; i < line.Start + line.Length;) { - currFGPaint = ApplyWrapperTo(ref currentWrapper, ref currd, paint); + var fb = GetNextForegroundBrush(ref line, i, out len); + + if (fb != null) + { + //TODO: figure out how to get the brush size + currentWrapper = context.CreatePaint(fb, new Size()); + } + else + { + if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose(); + currentWrapper = foreground; + } + + subStr = Text.Substring(i, len); + + ApplyWrapperTo(ref currentPaint, currentWrapper, ref currd, paint); + + canvas.DrawText(subStr, currX, origin.Y + line.Top + _lineOffset, paint); + + i += len; + currX += paint.MeasureText(subStr); } - - canvas.DrawText(subStr, currX, origin.Y + line.Top + _lineOffset, paint); - - i += len; - currX += paint.MeasureText(subStr); } } } - } - finally - { - if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose(); - currd?.Dispose(); + finally + { + if (!currentWrapper.Equals(foreground)) currentWrapper.Dispose(); + currd?.Dispose(); + } } } @@ -278,12 +276,13 @@ namespace Avalonia.Skia private Size _size; private List _skiaLines; - private static SKPaint ApplyWrapperTo(ref DrawingContextImpl.PaintWrapper wrapper, + private static void ApplyWrapperTo(ref SKPaint current, DrawingContextImpl.PaintWrapper wrapper, ref IDisposable curr, SKPaint paint) { + if (current == wrapper.Paint) + return; curr?.Dispose(); curr = wrapper.ApplyTo(paint); - return wrapper.Paint; } private static bool IsBreakChar(char c) From 9e35232242f815a68e64d807d92d68d9a3cc2aa8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Jul 2017 12:23:05 +0300 Subject: [PATCH 32/32] Pick nuget api address from environment --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 6b63176a89..529cbbb65f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,6 +15,7 @@ environment: MYGET_API_URL: https://www.myget.org/F/avalonia-ci/api/v2/package init: - ps: (New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/appveyor/ci/master/scripts/xamarin-vs2017-151-fixed.targets', "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Microsoft.Common.Targets\ImportAfter\Xamarin.Common.targets") +- 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"