From 92880f6d037de3a5e62f770a32bda4a33edc92bc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 Jun 2020 17:32:02 -0300 Subject: [PATCH 001/492] force dx11 --- samples/ControlCatalog.NetCore/Program.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 5df8c1be64..06cf3e6b61 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using Avalonia; using Avalonia.Dialogs; +using Avalonia.OpenGL; using Avalonia.ReactiveUI; namespace ControlCatalog.NetCore @@ -65,6 +66,10 @@ namespace ControlCatalog.NetCore EnableMultitouch = true, AllowEglInitialization = true }) + .With(new AngleOptions + { + AllowedPlatformApis = new System.Collections.Generic.List { AngleOptions.PlatformApi.DirectX11} + }) .UseSkia() .UseReactiveUI() .UseManagedSystemDialogs() From 459e9db2fb04a05abbf830363db5fed73eed64c3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 Jun 2020 17:35:43 -0300 Subject: [PATCH 002/492] add Composition interfaces. --- .../Avalonia.Win32/Avalonia.Win32.csproj | 1 + .../Composition/CompositionHost.cs | 145 ++++++++++++ .../Avalonia.Win32/Composition/D2DEffects.cs | 91 ++++++++ .../GRAPHICS_EFFECT_PROPERTY_MAPPING.cs | 18 ++ .../Composition/GaussianBlurEffect.cs | 79 +++++++ .../Composition/ICompositorDesktopInterop.cs | 14 ++ .../Composition/ICompositorDesktopInterop1.cs | 69 ++++++ .../Composition/ICompositorInterop.cs | 139 +++++++++++ .../Composition/IGraphicsEffectD2D1Interop.cs | 24 ++ .../IGraphicsEffectD2D1Interop1.cs | 217 ++++++++++++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 2 + 11 files changed, 799 insertions(+) create mode 100644 src/Windows/Avalonia.Win32/Composition/CompositionHost.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/D2DEffects.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/GRAPHICS_EFFECT_PROPERTY_MAPPING.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/ICompositorDesktopInterop.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/ICompositorDesktopInterop1.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/ICompositorInterop.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop1.cs diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 49700710e9..34f71daf52 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs new file mode 100644 index 0000000000..223c208829 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -0,0 +1,145 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.OpenGL.Angle; +using Windows.UI.Composition; +using WinRT; + +namespace Avalonia.Win32 +{ + class CompositionHost + { + internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE + { + DQTAT_COM_NONE = 0, + DQTAT_COM_ASTA = 1, + DQTAT_COM_STA = 2 + }; + + internal enum DISPATCHERQUEUE_THREAD_TYPE + { + DQTYPE_THREAD_DEDICATED = 1, + DQTYPE_THREAD_CURRENT = 2, + }; + + [StructLayout(LayoutKind.Sequential)] + internal struct DispatcherQueueOptions + { + public int dwSize; + + [MarshalAs(UnmanagedType.I4)] + public DISPATCHERQUEUE_THREAD_TYPE threadType; + + [MarshalAs(UnmanagedType.I4)] + public DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType; + }; + + [DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)] + internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options, out IntPtr dispatcherQueueController); + + public static CompositionHost Instance { get; } = new CompositionHost(); + + private Compositor _compositor; + private Windows.System.DispatcherQueueController _dispatcherQueueController; + private Windows.UI.Composition.Desktop.DesktopWindowTarget _target; + + private CompositionHost() + { + } + + public void AddElement(float size, float x, float y) + { + if (_target.Root != null) + { + var visuals = _target.Root.As().Children; + + var visual = _compositor.CreateSpriteVisual(); + + var element = _compositor.CreateSpriteVisual(); + var rand = new Random(); + + element.Brush = _compositor.CreateColorBrush(new Windows.UI.Color { A = 255, R = (byte)(rand.NextDouble() * 255), G = (byte)(rand.NextDouble() * 255), B = (byte)(rand.NextDouble() * 255) }); + element.Size = new System.Numerics.Vector2(size, size); + element.Offset = new System.Numerics.Vector3(x, y, 0.0f); + + var animation = _compositor.CreateVector3KeyFrameAnimation(); + var bottom = (float)600 - element.Size.Y; + animation.InsertKeyFrame(1, new System.Numerics.Vector3(element.Offset.X, bottom, 0)); + + animation.Duration = TimeSpan.FromSeconds(2); + animation.DelayTime = TimeSpan.FromSeconds(3); + element.StartAnimation("Offset", animation); + visuals.InsertAtTop(element); + + visuals.InsertAtTop(visual); + } + } + + public void Initialize(IntPtr hwnd) + { + EnsureDispatcherQueue(); + if (_dispatcherQueueController != null) + _compositor = new Windows.UI.Composition.Compositor(); + + CreateDesktopWindowTarget(hwnd); + CreateCompositionRoot(); + + var interop = _compositor.As(); + + var display = Win32GlManager.EglFeature.Display as AngleWin32EglDisplay; + + var gDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); + + gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100,100), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); + + } + + public void CreateBlur() + { + var effect = new GaussianBlurEffect(); + var effectFactory = _compositor.CreateEffectFactory(effect); + var blurBrush = effectFactory.CreateBrush(); + + var backDropBrush = _compositor.CreateBackdropBrush(); + + blurBrush.SetSourceParameter("backdrop", backDropBrush); + + var visual = _compositor.CreateSpriteVisual(); + + visual.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); + visual.Brush = blurBrush; + + _target.Root = visual; + } + + void CreateCompositionRoot() + { + var root = _compositor.CreateContainerVisual(); + root.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); + //root.Offset = new System.Numerics.Vector3(0, 0, 0); + _target.Root = root; + } + + void CreateDesktopWindowTarget(IntPtr window) + { + var interop = _compositor.As(); + + interop.CreateDesktopWindowTarget(window, false, out var windowTarget); + _target = Windows.UI.Composition.Desktop.DesktopWindowTarget.FromAbi(windowTarget); + } + + void EnsureDispatcherQueue() + { + if (_dispatcherQueueController == null) + { + DispatcherQueueOptions options = new DispatcherQueueOptions(); + options.apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_STA; + options.threadType = DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT; + options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions)); + + CreateDispatcherQueueController(options, out var queue); + _dispatcherQueueController = Windows.System.DispatcherQueueController.FromAbi(queue); + } + } + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/D2DEffects.cs b/src/Windows/Avalonia.Win32/Composition/D2DEffects.cs new file mode 100644 index 0000000000..1c761ee082 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/D2DEffects.cs @@ -0,0 +1,91 @@ +using System; + + +namespace Avalonia.Win32 +{ + class D2DEffects + { + public static readonly Guid CLSID_D2D12DAffineTransform = new Guid(0x6AA97485, 0x6354, 0x4CFC, 0x90, 0x8C, 0xE4, 0xA7, 0x4F, 0x62, 0xC9, 0x6C); + + public static readonly Guid CLSID_D2D13DPerspectiveTransform = new Guid(0xC2844D0B, 0x3D86, 0x46E7, 0x85, 0xBA, 0x52, 0x6C, 0x92, 0x40, 0xF3, 0xFB); + + public static readonly Guid CLSID_D2D13DTransform = new Guid(0xE8467B04, 0xEC61, 0x4B8A, 0xB5, 0xDE, 0xD4, 0xD7, 0x3D, 0xEB, 0xEA, 0x5A); + + public static readonly Guid CLSID_D2D1ArithmeticComposite = new Guid(0xFC151437, 0x049A, 0x4784, 0xA2, 0x4A, 0xF1, 0xC4, 0xDA, 0xF2, 0x09, 0x87); + + public static readonly Guid CLSID_D2D1Atlas = new Guid(0x913E2BE4, 0xFDCF, 0x4FE2, 0xA5, 0xF0, 0x24, 0x54, 0xF1, 0x4F, 0xF4, 0x08); + + public static readonly Guid CLSID_D2D1BitmapSource = new Guid(0x5FB6C24D, 0xC6DD, 0x4231, 0x94, 0x4, 0x50, 0xF4, 0xD5, 0xC3, 0x25, 0x2D); + + public static readonly Guid CLSID_D2D1Blend = new Guid(0x81C5B77B, 0x13F8, 0x4CDD, 0xAD, 0x20, 0xC8, 0x90, 0x54, 0x7A, 0xC6, 0x5D); + + public static readonly Guid CLSID_D2D1Border = new Guid(0x2A2D49C0, 0x4ACF, 0x43C7, 0x8C, 0x6A, 0x7C, 0x4A, 0x27, 0x87, 0x4D, 0x27); + + public static readonly Guid CLSID_D2D1Brightness = new Guid(0x8CEA8D1E, 0x77B0, 0x4986, 0xB3, 0xB9, 0x2F, 0x0C, 0x0E, 0xAE, 0x78, 0x87); + + public static readonly Guid CLSID_D2D1ColorManagement = new Guid(0x1A28524C, 0xFDD6, 0x4AA4, 0xAE, 0x8F, 0x83, 0x7E, 0xB8, 0x26, 0x7B, 0x37); + + public static readonly Guid CLSID_D2D1ColorMatrix = new Guid(0x921F03D6, 0x641C, 0x47DF, 0x85, 0x2D, 0xB4, 0xBB, 0x61, 0x53, 0xAE, 0x11); + + public static readonly Guid CLSID_D2D1Composite = new Guid(0x48FC9F51, 0xF6AC, 0x48F1, 0x8B, 0x58, 0x3B, 0x28, 0xAC, 0x46, 0xF7, 0x6D); + + public static readonly Guid CLSID_D2D1ConvolveMatrix = new Guid(0x407F8C08, 0x5533, 0x4331, 0xA3, 0x41, 0x23, 0xCC, 0x38, 0x77, 0x84, 0x3E); + + public static readonly Guid CLSID_D2D1Crop = new Guid(0xE23F7110, 0x0E9A, 0x4324, 0xAF, 0x47, 0x6A, 0x2C, 0x0C, 0x46, 0xF3, 0x5B); + + public static readonly Guid CLSID_D2D1DirectionalBlur = new Guid(0x174319A6, 0x58E9, 0x49B2, 0xBB, 0x63, 0xCA, 0xF2, 0xC8, 0x11, 0xA3, 0xDB); + + public static readonly Guid CLSID_D2D1DiscreteTransfer = new Guid(0x90866FCD, 0x488E, 0x454B, 0xAF, 0x06, 0xE5, 0x04, 0x1B, 0x66, 0xC3, 0x6C); + + public static readonly Guid CLSID_D2D1DisplacementMap = new Guid(0xEDC48364, 0x417, 0x4111, 0x94, 0x50, 0x43, 0x84, 0x5F, 0xA9, 0xF8, 0x90); + + public static readonly Guid CLSID_D2D1DistantDiffuse = new Guid(0x3E7EFD62, 0xA32D, 0x46D4, 0xA8, 0x3C, 0x52, 0x78, 0x88, 0x9A, 0xC9, 0x54); + + public static readonly Guid CLSID_D2D1DistantSpecular = new Guid(0x428C1EE5, 0x77B8, 0x4450, 0x8A, 0xB5, 0x72, 0x21, 0x9C, 0x21, 0xAB, 0xDA); + + public static readonly Guid CLSID_D2D1DpiCompensation = new Guid(0x6C26C5C7, 0x34E0, 0x46FC, 0x9C, 0xFD, 0xE5, 0x82, 0x37, 0x6, 0xE2, 0x28); + + public static readonly Guid CLSID_D2D1Flood = new Guid(0x61C23C20, 0xAE69, 0x4D8E, 0x94, 0xCF, 0x50, 0x07, 0x8D, 0xF6, 0x38, 0xF2); + + public static readonly Guid CLSID_D2D1GammaTransfer = new Guid(0x409444C4, 0xC419, 0x41A0, 0xB0, 0xC1, 0x8C, 0xD0, 0xC0, 0xA1, 0x8E, 0x42); + + public static readonly Guid CLSID_D2D1GaussianBlur = new Guid(0x1FEB6D69, 0x2FE6, 0x4AC9, 0x8C, 0x58, 0x1D, 0x7F, 0x93, 0xE7, 0xA6, 0xA5); + + public static readonly Guid CLSID_D2D1Scale = new Guid(0x9DAF9369, 0x3846, 0x4D0E, 0xA4, 0x4E, 0xC, 0x60, 0x79, 0x34, 0xA5, 0xD7); + + public static readonly Guid CLSID_D2D1Histogram = new Guid(0x881DB7D0, 0xF7EE, 0x4D4D, 0xA6, 0xD2, 0x46, 0x97, 0xAC, 0xC6, 0x6E, 0xE8); + + public static readonly Guid CLSID_D2D1HueRotation = new Guid(0x0F4458EC, 0x4B32, 0x491B, 0x9E, 0x85, 0xBD, 0x73, 0xF4, 0x4D, 0x3E, 0xB6); + + public static readonly Guid CLSID_D2D1LinearTransfer = new Guid(0xAD47C8FD, 0x63EF, 0x4ACC, 0x9B, 0x51, 0x67, 0x97, 0x9C, 0x03, 0x6C, 0x06); + + public static readonly Guid CLSID_D2D1LuminanceToAlpha = new Guid(0x41251AB7, 0x0BEB, 0x46F8, 0x9D, 0xA7, 0x59, 0xE9, 0x3F, 0xCC, 0xE5, 0xDE); + + public static readonly Guid CLSID_D2D1Morphology = new Guid(0xEAE6C40D, 0x626A, 0x4C2D, 0xBF, 0xCB, 0x39, 0x10, 0x01, 0xAB, 0xE2, 0x02); + + public static readonly Guid CLSID_D2D1OpacityMetadata = new Guid(0x6C53006A, 0x4450, 0x4199, 0xAA, 0x5B, 0xAD, 0x16, 0x56, 0xFE, 0xCE, 0x5E); + + public static readonly Guid CLSID_D2D1PointDiffuse = new Guid(0xB9E303C3, 0xC08C, 0x4F91, 0x8B, 0x7B, 0x38, 0x65, 0x6B, 0xC4, 0x8C, 0x20); + + public static readonly Guid CLSID_D2D1PointSpecular = new Guid(0x09C3CA26, 0x3AE2, 0x4F09, 0x9E, 0xBC, 0xED, 0x38, 0x65, 0xD5, 0x3F, 0x22); + + public static readonly Guid CLSID_D2D1Premultiply = new Guid(0x06EAB419, 0xDEED, 0x4018, 0x80, 0xD2, 0x3E, 0x1D, 0x47, 0x1A, 0xDE, 0xB2); + + public static readonly Guid CLSID_D2D1Saturation = new Guid(0x5CB2D9CF, 0x327D, 0x459F, 0xA0, 0xCE, 0x40, 0xC0, 0xB2, 0x08, 0x6B, 0xF7); + + public static readonly Guid CLSID_D2D1Shadow = new Guid(0xC67EA361, 0x1863, 0x4E69, 0x89, 0xDB, 0x69, 0x5D, 0x3E, 0x9A, 0x5B, 0x6B); + + public static readonly Guid CLSID_D2D1SpotDiffuse = new Guid(0x818A1105, 0x7932, 0x44F4, 0xAA, 0x86, 0x08, 0xAE, 0x7B, 0x2F, 0x2C, 0x93); + + public static readonly Guid CLSID_D2D1SpotSpecular = new Guid(0xEDAE421E, 0x7654, 0x4A37, 0x9D, 0xB8, 0x71, 0xAC, 0xC1, 0xBE, 0xB3, 0xC1); + + public static readonly Guid CLSID_D2D1TableTransfer = new Guid(0x5BF818C3, 0x5E43, 0x48CB, 0xB6, 0x31, 0x86, 0x83, 0x96, 0xD6, 0xA1, 0xD4); + + public static readonly Guid CLSID_D2D1Tile = new Guid(0xB0784138, 0x3B76, 0x4BC5, 0xB1, 0x3B, 0x0F, 0xA2, 0xAD, 0x02, 0x65, 0x9F); + + public static readonly Guid CLSID_D2D1Turbulence = new Guid(0xCF2BB6AE, 0x889A, 0x4AD7, 0xBA, 0x29, 0xA2, 0xFD, 0x73, 0x2C, 0x9F, 0xC9); + + public static readonly Guid CLSID_D2D1UnPremultiply = new Guid(0xFB9AC489, 0xAD8D, 0x41ED, 0x99, 0x99, 0xBB, 0x63, 0x47, 0xD1, 0x10, 0xF7); + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/GRAPHICS_EFFECT_PROPERTY_MAPPING.cs b/src/Windows/Avalonia.Win32/Composition/GRAPHICS_EFFECT_PROPERTY_MAPPING.cs new file mode 100644 index 0000000000..f5d5fc4ad3 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/GRAPHICS_EFFECT_PROPERTY_MAPPING.cs @@ -0,0 +1,18 @@ +namespace Windows.Graphics.Effects.Interop +{ + public enum GRAPHICS_EFFECT_PROPERTY_MAPPING + { + GRAPHICS_EFFECT_PROPERTY_MAPPING_UNKNOWN, + GRAPHICS_EFFECT_PROPERTY_MAPPING_DIRECT, + GRAPHICS_EFFECT_PROPERTY_MAPPING_VECTORX, + GRAPHICS_EFFECT_PROPERTY_MAPPING_VECTORY, + GRAPHICS_EFFECT_PROPERTY_MAPPING_VECTORZ, + GRAPHICS_EFFECT_PROPERTY_MAPPING_VECTORW, + GRAPHICS_EFFECT_PROPERTY_MAPPING_RECT_TO_VECTOR4, + GRAPHICS_EFFECT_PROPERTY_MAPPING_RADIANS_TO_DEGREES, + GRAPHICS_EFFECT_PROPERTY_MAPPING_COLORMATRIX_ALPHA_MODE, + GRAPHICS_EFFECT_PROPERTY_MAPPING_COLOR_TO_VECTOR3, + GRAPHICS_EFFECT_PROPERTY_MAPPING_COLOR_TO_VECTOR4 + }; +} + diff --git a/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs b/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs new file mode 100644 index 0000000000..19595e8977 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs @@ -0,0 +1,79 @@ +using System; +using Windows.Graphics.Effects; +using Windows.Graphics.Effects.Interop; +using Windows.UI.Composition; + + +namespace Avalonia.Win32 +{ + class GaussianBlurEffect : IGraphicsEffect, IGraphicsEffectSource, global::Windows.Graphics.Effects.Interop.IGraphicsEffectD2D1Interop + { + enum D2D1_GAUSSIANBLUR_OPTIMIZATION + { + D2D1_GAUSSIANBLUR_OPTIMIZATION_SPEED, + D2D1_GAUSSIANBLUR_OPTIMIZATION_BALANCED, + D2D1_GAUSSIANBLUR_OPTIMIZATION_QUALITY, + D2D1_GAUSSIANBLUR_OPTIMIZATION_FORCE_DWORD + }; + + enum D2D1_BORDER_MODE + { + D2D1_BORDER_MODE_SOFT, + D2D1_BORDER_MODE_HARD, + D2D1_BORDER_MODE_FORCE_DWORD + }; + + enum D2D1GaussianBlurProp + { + D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, + D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION, + D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, + D2D1_GAUSSIANBLUR_PROP_FORCE_DWORD + }; + + public string Name { get; set; } + + public Guid EffectId => D2DEffects.CLSID_D2D1GaussianBlur; + + public uint PropertyCount => 3; + + public uint SourceCount => 1; + + public uint GetNamedPropertyMapping(string name, out GRAPHICS_EFFECT_PROPERTY_MAPPING mapping) + { + throw new NotImplementedException(); + } + + public object GetProperty(uint index) + { + switch ((D2D1GaussianBlurProp)index) + { + case D2D1GaussianBlurProp.D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION: + return 30.0f; + + case D2D1GaussianBlurProp.D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION: + return (UInt32)D2D1_GAUSSIANBLUR_OPTIMIZATION.D2D1_GAUSSIANBLUR_OPTIMIZATION_SPEED; + + case D2D1GaussianBlurProp.D2D1_GAUSSIANBLUR_PROP_BORDER_MODE: + return (UInt32)D2D1_BORDER_MODE.D2D1_BORDER_MODE_HARD; + } + + return null; + } + + private IGraphicsEffectSource _source = new CompositionEffectSourceParameter("backdrop"); + + public IGraphicsEffectSource GetSource(uint index) + { + if (index == 0) + { + return _source; + } + else + { + return null; + } + } + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/ICompositorDesktopInterop.cs b/src/Windows/Avalonia.Win32/Composition/ICompositorDesktopInterop.cs new file mode 100644 index 0000000000..1d4cd3450f --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/ICompositorDesktopInterop.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; +using WinRT; + +namespace Windows.UI.Composition.Desktop +{ + [WindowsRuntimeType] + [Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")] + public interface ICompositorDesktopInterop + { + void CreateDesktopWindowTarget(IntPtr hwndTarget, bool isTopmost, out IntPtr test); + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/ICompositorDesktopInterop1.cs b/src/Windows/Avalonia.Win32/Composition/ICompositorDesktopInterop1.cs new file mode 100644 index 0000000000..1c3f06d679 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/ICompositorDesktopInterop1.cs @@ -0,0 +1,69 @@ +using WinRT; + +namespace ABI.Windows.UI.Composition.Desktop +{ + using global::System; + using global::System.Runtime.InteropServices; + + [Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")] + internal class ICompositorDesktopInterop : global::Windows.UI.Composition.Desktop.ICompositorDesktopInterop + + { + [Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")] + public struct Vftbl + { + public delegate int _CreateDesktopWindowTarget(IntPtr thisPtr, IntPtr hwndTarget, byte isTopMost, out IntPtr desktopWindowTarget); + + internal global::WinRT.Interop.IUnknownVftbl IUnknownVftbl; + public _CreateDesktopWindowTarget CreateDesktopWindowTarget; + + + public static readonly Vftbl AbiToProjectionVftable; + public static readonly IntPtr AbiToProjectionVftablePtr; + + static Vftbl() + { + AbiToProjectionVftable = new Vftbl + { + IUnknownVftbl = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl, + CreateDesktopWindowTarget = Do_Abi_Create_Desktop_Window_Target + }; + AbiToProjectionVftablePtr = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false); + } + + private static int Do_Abi_Create_Desktop_Window_Target(IntPtr thisPtr, IntPtr hwndTarget, byte isTopMost, out IntPtr desktopWindowTarget) + { + try + { + ComWrappersSupport.FindObject(thisPtr).CreateDesktopWindowTarget(hwndTarget, isTopMost != 0, out desktopWindowTarget); + return 0; + } + catch (Exception ex) + { + desktopWindowTarget = IntPtr.Zero; + return Marshal.GetHRForException(ex); + } + } + } + internal static ObjectReference FromAbi(IntPtr thisPtr) => ObjectReference.FromAbi(thisPtr); + + public static implicit operator ICompositorDesktopInterop(IObjectReference obj) => (obj != null) ? new ICompositorDesktopInterop(obj) : null; + protected readonly ObjectReference _obj; + public IObjectReference ObjRef { get => _obj; } + public IntPtr ThisPtr => _obj.ThisPtr; + public ObjectReference AsInterface() => _obj.As(); + public A As() => _obj.AsType(); + public ICompositorDesktopInterop(IObjectReference obj) : this(obj.As()) { } + internal ICompositorDesktopInterop(ObjectReference obj) + { + _obj = obj; + } + + public void CreateDesktopWindowTarget(IntPtr hwndTarget, bool isTopmost, out IntPtr test) + { + Marshal.ThrowExceptionForHR(_obj.Vftbl.CreateDesktopWindowTarget(ThisPtr, hwndTarget, isTopmost ? (byte)1 : (byte)0, out test)); + } + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/ICompositorInterop.cs b/src/Windows/Avalonia.Win32/Composition/ICompositorInterop.cs new file mode 100644 index 0000000000..d9b25e497e --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/ICompositorInterop.cs @@ -0,0 +1,139 @@ +using System; +using System.Runtime.InteropServices; +using WinRT; + +namespace Windows.UI.Composition.Interop +{ + [WindowsRuntimeType] + [Guid("25297D5C-3AD4-4C9C-B5CF-E36A38512330")] + public interface ICompositorInterop + { + ICompositionSurface CreateCompositionSurfaceForHandle(IntPtr swapChain); + + ICompositionSurface CreateCompositionSurfaceForSwapChain(IntPtr swapChain); + + CompositionGraphicsDevice CreateGraphicsDevice(IntPtr renderingDevice); + } +} + +namespace ABI.Windows.UI.Composition.Interop +{ + using global::System; + using global::System.Runtime.InteropServices; + using global::Windows.UI.Composition; + + [Guid("25297D5C-3AD4-4C9C-B5CF-E36A38512330")] + internal class ICompositorInterop : global::Windows.UI.Composition.Interop.ICompositorInterop + + { + [Guid("25297D5C-3AD4-4C9C-B5CF-E36A38512330")] + public struct Vftbl + { + public delegate int _CreateCompositionSurfaceForHandle(IntPtr ThisPtr, IntPtr swapChain, out IntPtr result); + public delegate int _CreateCompositionSurfaceForSwapChain(IntPtr ThisPtr, IntPtr swapChain, out IntPtr result); + public delegate int _CreateGraphicsDevice(IntPtr ThisPtr, IntPtr renderingDevice, out IntPtr result); + + internal global::WinRT.Interop.IUnknownVftbl IUnknownVftbl; + public _CreateCompositionSurfaceForHandle CreateCompositionSurfaceForHandle; + public _CreateCompositionSurfaceForSwapChain CreateCompositionSurfaceForSwapChain; + public _CreateGraphicsDevice CreateGraphicsDevice; + + + public static readonly Vftbl AbiToProjectionVftable; + public static readonly IntPtr AbiToProjectionVftablePtr; + + static Vftbl() + { + AbiToProjectionVftable = new Vftbl + { + IUnknownVftbl = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl, + + CreateCompositionSurfaceForHandle = Do_Abi_Create_Composition_Surface_For_Handle, + CreateCompositionSurfaceForSwapChain = Do_Abi_Create_Composition_Surface_For_SwapChain, + CreateGraphicsDevice= Do_Abi_Create_Graphics_Device + }; + AbiToProjectionVftablePtr = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false); + } + + private static int Do_Abi_Create_Composition_Surface_For_Handle(IntPtr thisPtr, IntPtr swapChain, out IntPtr surface) + { + try + { + surface = IntPtr.Zero; + //surface = ComWrappersSupport.FindObject(thisPtr).CreateCompositionSurfaceForHandle(swapChain); + return 0; + } + catch (Exception ex) + { + surface = IntPtr.Zero; + return Marshal.GetHRForException(ex); + } + } + + private static int Do_Abi_Create_Composition_Surface_For_SwapChain(IntPtr thisPtr, IntPtr swapChain, out IntPtr surface) + { + try + { + surface = IntPtr.Zero; + //surface = ComWrappersSupport.FindObject(thisPtr).CreateCompositionSurfaceForSwapChain(swapChain); + return 0; + } + catch (Exception ex) + { + surface = IntPtr.Zero; + return Marshal.GetHRForException(ex); + } + } + + private static int Do_Abi_Create_Graphics_Device(IntPtr thisPtr, IntPtr renderingDevice, out IntPtr graphicsDevice) + { + try + { + graphicsDevice = ComWrappersSupport.FindObject(thisPtr).CreateGraphicsDevice(renderingDevice).ThisPtr; + return 0; + } + catch (Exception ex) + { + graphicsDevice = IntPtr.Zero; + return Marshal.GetHRForException(ex); + } + } + } + internal static ObjectReference FromAbi(IntPtr thisPtr) => ObjectReference.FromAbi(thisPtr); + + public static implicit operator ICompositorInterop(IObjectReference obj) => (obj != null) ? new ICompositorInterop(obj) : null; + protected readonly ObjectReference _obj; + public IObjectReference ObjRef { get => _obj; } + public IntPtr ThisPtr => _obj.ThisPtr; + public ObjectReference AsInterface() => _obj.As(); + public A As() => _obj.AsType(); + public ICompositorInterop(IObjectReference obj) : this(obj.As()) { } + internal ICompositorInterop(ObjectReference obj) + { + _obj = obj; + } + + public ICompositionSurface CreateCompositionSurfaceForHandle(IntPtr swapChain) + { + Marshal.ThrowExceptionForHR(_obj.Vftbl.CreateCompositionSurfaceForHandle(ThisPtr, swapChain, out var compositionSurface)); + + return null; + } + + public ICompositionSurface CreateCompositionSurfaceForSwapChain(IntPtr swapChain) + { + Marshal.ThrowExceptionForHR(_obj.Vftbl.CreateCompositionSurfaceForSwapChain(ThisPtr, swapChain, out var compositionSurface)); + + return null; + } + + public CompositionGraphicsDevice CreateGraphicsDevice(IntPtr renderingDevice) + { + Marshal.ThrowExceptionForHR(_obj.Vftbl.CreateGraphicsDevice(ThisPtr, renderingDevice, out var graphicsDevice)); + + return CompositionGraphicsDevice.FromAbi(graphicsDevice); + } + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop.cs b/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop.cs new file mode 100644 index 0000000000..74d3939a98 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.InteropServices; +using WinRT; + +namespace Windows.Graphics.Effects.Interop +{ + [WindowsRuntimeType] + [Guid("2FC57384-A068-44D7-A331-30982FCF7177")] + public interface IGraphicsEffectD2D1Interop + { + Guid EffectId { get; } + + uint GetNamedPropertyMapping(string name, out GRAPHICS_EFFECT_PROPERTY_MAPPING mapping); + + object GetProperty(uint index); + + uint PropertyCount { get; } + + IGraphicsEffectSource GetSource(uint index); + + uint SourceCount { get; } + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop1.cs b/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop1.cs new file mode 100644 index 0000000000..9d053c9e22 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop1.cs @@ -0,0 +1,217 @@ +using WinRT; + +namespace ABI.Windows.Graphics.Effects.Interop +{ + using global::System; + using global::System.Runtime.InteropServices; + + [Guid("2FC57384-A068-44D7-A331-30982FCF7177")] + internal class IGraphicsEffectD2D1Interop : global::Windows.Graphics.Effects.Interop.IGraphicsEffectD2D1Interop + + { + [Guid("2FC57384-A068-44D7-A331-30982FCF7177")] + public struct Vftbl + { + public delegate int _GetEffectId(IntPtr thisPtr, out Guid guid); + public delegate int _GetNamedPropertyMapping(IntPtr thisPtr, IntPtr name, IntPtr index, IntPtr mapping); + public delegate int _GetProperty(IntPtr thisPtr, uint index, out IntPtr value); + public unsafe delegate int _GetPropertyCount(IntPtr thisPtr, uint* count); + public delegate int _GetSource(IntPtr thisPtr, uint index, out IntPtr source); + public delegate int _GetSourceCount(IntPtr thisPtr, out uint count); + + internal global::WinRT.Interop.IUnknownVftbl IUnknownVftbl; + public _GetEffectId GetEffectId; + public _GetNamedPropertyMapping GetNamedPropertyMapping; + public _GetPropertyCount GetPropertyCount; + public _GetProperty GetProperty; + public _GetSource GetSource; + public _GetSourceCount GetSourceCount; + + public static readonly Vftbl AbiToProjectionVftable; + public static readonly IntPtr AbiToProjectionVftablePtr; + + unsafe static Vftbl() + { + AbiToProjectionVftable = new Vftbl + { + IUnknownVftbl = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl, + GetEffectId = Do_Abi_Get_Effect_Id, + GetNamedPropertyMapping = Do_Abi_Get_Property_Mapping, + GetPropertyCount = Do_Abi_Get_Property_Count, + GetProperty = Do_Abi_Get_Property, + GetSource = Do_Abi_Get_Source, + GetSourceCount = Do_Abi_Get_Source_Count + + }; + AbiToProjectionVftablePtr = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false); + } + + private static int Do_Abi_Get_Effect_Id(IntPtr thisPtr, out Guid guid) + { + guid = default; + + try + { + guid = ComWrappersSupport.FindObject(thisPtr).EffectId; + } + catch (Exception ex) + { + return Marshal.GetHRForException(ex); + } + + return 0; + } + + private static int Do_Abi_Get_Property_Mapping(IntPtr thisPtr, IntPtr name, IntPtr index, IntPtr mapping) + { + try + { + ComWrappersSupport.FindObject(thisPtr).GetNamedPropertyMapping(MarshalString.FromAbi(name), out var mappingResult); + } + catch (Exception ex) + { + return Marshal.GetHRForException(ex); + } + + return 0; + } + + private static int Do_Abi_Get_Property(IntPtr thisPtr, uint index, out IntPtr value) + { + value = default; + + try + { + value = MarshalInspectable.CreateMarshaler( + ComWrappersSupport.FindObject(thisPtr).GetProperty(index)) + .As(Guid.Parse("4BD682DD-7554-40E9-9A9B-82654EDE7E62")) + .GetRef(); + + } + catch (Exception ex) + { + return Marshal.GetHRForException(ex); + } + + return 0; + } + + unsafe private static int Do_Abi_Get_Property_Count(IntPtr thisPtr, uint* count) + { + + try + { + var res = ComWrappersSupport.FindObject(thisPtr).PropertyCount; + + if (count != null) + { + *count = res; + } + } + catch (Exception ex) + { + return Marshal.GetHRForException(ex); + } + + return 0; + } + + private static int Do_Abi_Get_Source(IntPtr thisPtr, uint index, out IntPtr value) + { + value = default; + + try + { + var source = ComWrappersSupport.FindObject(thisPtr).GetSource(index); + + value = MarshalInterface.FromManaged(source); + } + catch (Exception ex) + { + return Marshal.GetHRForException(ex); + } + + return 0; + } + + private static int Do_Abi_Get_Source_Count(IntPtr thisPtr, out uint count) + { + count = default; + + try + { + count = ComWrappersSupport.FindObject(thisPtr).SourceCount; + } + catch (Exception ex) + { + return Marshal.GetHRForException(ex); + } + + return 0; + } + } + internal static ObjectReference FromAbi(IntPtr thisPtr) => ObjectReference.FromAbi(thisPtr); + + public static implicit operator IGraphicsEffectD2D1Interop(IObjectReference obj) => (obj != null) ? new IGraphicsEffectD2D1Interop(obj) : null; + protected readonly ObjectReference _obj; + public IObjectReference ObjRef { get => _obj; } + public IntPtr ThisPtr => _obj.ThisPtr; + + public ObjectReference AsInterface() => _obj.As(); + public A As() => _obj.AsType(); + public IGraphicsEffectD2D1Interop(IObjectReference obj) : this(obj.As()) { } + internal IGraphicsEffectD2D1Interop(ObjectReference obj) + { + _obj = obj; + } + + public Guid EffectId + { + get + { + Marshal.ThrowExceptionForHR(_obj.Vftbl.GetEffectId(ThisPtr, out Guid guid)); + return guid; + } + } + + public uint PropertyCount + { + get + { + unsafe + { + uint count = default; + Marshal.ThrowExceptionForHR(_obj.Vftbl.GetPropertyCount(ThisPtr, &count)); + return count; + } + } + } + + public uint SourceCount + { + get + { + Marshal.ThrowExceptionForHR(_obj.Vftbl.GetSourceCount(ThisPtr, out uint count)); + return count; + } + } + + public uint GetNamedPropertyMapping(string name, out global::Windows.Graphics.Effects.Interop.GRAPHICS_EFFECT_PROPERTY_MAPPING mapping) + { + throw new NotImplementedException(); + } + + public object GetProperty(uint index) + { + // Marshal.ThrowExceptionForHR(_obj.Vftbl.GetProperty(ThisPtr, index, out IntPtr value)); + throw new NotImplementedException(); + } + + public global::Windows.Graphics.Effects.IGraphicsEffectSource GetSource(uint index) + { + throw new NotImplementedException(); + } + } +} + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 36398eb810..ab07bdebb3 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -599,6 +599,8 @@ namespace Avalonia.Win32 _scaling = dpix / 96.0; } } + + CompositionHost.Instance.Initialize(_hwnd); } private void CreateDropTarget() From bd2b283ad7d4df2e2088916823b174e6d37d9d24 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 Jun 2020 18:30:43 -0300 Subject: [PATCH 003/492] add drawing surface interop.. --- .../Composition/CompositionHost.cs | 21 ++- .../ICompositionDrawingSurfaceInterop.cs | 135 ++++++++++++++++++ .../IGraphicsEffectD2D1Interop1.cs | 2 +- 3 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 223c208829..a94fd82e1a 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -2,6 +2,7 @@ using System.Runtime.InteropServices; using Avalonia.OpenGL.Angle; using Windows.UI.Composition; +using Windows.UI.Composition.Interop; using WinRT; namespace Avalonia.Win32 @@ -89,8 +90,24 @@ namespace Avalonia.Win32 var gDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); - gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100,100), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); - + var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100,100), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); + + var surfaceInterop = surface.As(); + + surfaceInterop.BeginDraw(new Windows.Foundation.Rect(0, 0, 100, 100), Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"), out var texture, new Windows.Foundation.Point(0, 0)); + surfaceInterop.EndDraw(); + + var brush = _compositor.CreateSurfaceBrush(surface); + + var visual = _compositor.CreateSpriteVisual(); + + visual.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); + visual.Brush = brush; + + _target.Root = visual; + + AddElement(100, 200, 200); + } public void CreateBlur() diff --git a/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs b/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs new file mode 100644 index 0000000000..fd9387b59e --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs @@ -0,0 +1,135 @@ +using System; +using System.Runtime.InteropServices; +using Windows.Foundation; +using WinRT; + +namespace Windows.UI.Composition.Interop +{ + [WindowsRuntimeType] + [Guid("FD04E6E3-FE0C-4C3C-AB19-A07601A576EE")] + public interface ICompositionDrawingSurfaceInterop + { + void BeginDraw(Rect updateRect, Guid iid, out IntPtr updateObject, Point point); + + void EndDraw(); + + void Resize(Size sizePixels); + + void ResumeDraw(); + + void Scroll(Rect scrollRect, Rect clipRect, int offsetX, int offsetY); + + void SuspendDraw(); + } +} + +namespace ABI.Windows.UI.Composition.Interop +{ + using global::System; + using global::System.Runtime.InteropServices; + using global::Windows.UI.Composition; + + [Guid("FD04E6E3-FE0C-4C3C-AB19-A07601A576EE")] + internal class ICompositionDrawingSurfaceInterop : global::Windows.UI.Composition.Interop.ICompositionDrawingSurfaceInterop + + { + [Guid("FD04E6E3-FE0C-4C3C-AB19-A07601A576EE")] + public struct Vftbl + { + public delegate int _BeginDraw(IntPtr ThisPtr, Guid iid, out IntPtr updateObject, Point updateOffset); + public delegate int _EndDraw(IntPtr ThisPtr); + public delegate int _Resize(IntPtr ThisPtr, Size sizePixels); + public delegate int _ResumeDraw(IntPtr ThisPtr); + public delegate int _Scroll(IntPtr ThisPtr, Rect scrollRect, Rect clipRect, int offsetX, int offsetY); + public delegate int _SuspendDraw(IntPtr ThisPtr); + + internal global::WinRT.Interop.IUnknownVftbl IUnknownVftbl; + public _BeginDraw BeginDraw; + public _EndDraw EndDraw; + public _Resize Resize; + public _ResumeDraw ResumeDraw; + public _Scroll Scroll; + public _SuspendDraw SuspendDraw; + + public static readonly Vftbl AbiToProjectionVftable; + public static readonly IntPtr AbiToProjectionVftablePtr; + + static Vftbl() + { + AbiToProjectionVftable = new Vftbl + { + IUnknownVftbl = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl, + + BeginDraw = Do_Abi_BeginDraw, + EndDraw = Do_Abi_EndDraw, + Resize = Do_Abi_Resize + + + }; + AbiToProjectionVftablePtr = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false); + } + + private static int Do_Abi_BeginDraw(IntPtr ThisPtr, Guid iid, out IntPtr updateObject, Point updateOffset) + { + updateObject = IntPtr.Zero; + return 0; + } + + private static int Do_Abi_EndDraw(IntPtr ThisPtr) + { + return 0; + } + + private static int Do_Abi_Resize(IntPtr ThisPtr, Size sizePixels) + { + return 0; + } + } + internal static ObjectReference FromAbi(IntPtr thisPtr) => ObjectReference.FromAbi(thisPtr); + + public static implicit operator ICompositionDrawingSurfaceInterop(IObjectReference obj) => (obj != null) ? new ICompositionDrawingSurfaceInterop(obj) : null; + protected readonly ObjectReference _obj; + public IObjectReference ObjRef { get => _obj; } + public IntPtr ThisPtr => _obj.ThisPtr; + public ObjectReference AsInterface() => _obj.As(); + public A As() => _obj.AsType(); + + public ICompositionDrawingSurfaceInterop(IObjectReference obj) : this(obj.As()) { } + internal ICompositionDrawingSurfaceInterop(ObjectReference obj) + { + _obj = obj; + } + + public void BeginDraw(Rect updateRect, Guid iid, out IntPtr updateObject, Point point) + { + Marshal.ThrowExceptionForHR(_obj.Vftbl.BeginDraw(ThisPtr, iid, out updateObject, point)); + } + + public void EndDraw() + { + Marshal.ThrowExceptionForHR(_obj.Vftbl.EndDraw(ThisPtr)); + } + + public void Resize(Size sizePixels) + { + throw new NotImplementedException(); + } + + public void ResumeDraw() + { + throw new NotImplementedException(); + } + + public void Scroll(Rect scrollRect, Rect clipRect, int offsetX, int offsetY) + { + throw new NotImplementedException(); + } + + public void SuspendDraw() + { + throw new NotImplementedException(); + } + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop1.cs b/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop1.cs index 9d053c9e22..8466b05fb5 100644 --- a/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop1.cs +++ b/src/Windows/Avalonia.Win32/Composition/IGraphicsEffectD2D1Interop1.cs @@ -122,7 +122,7 @@ namespace ABI.Windows.Graphics.Effects.Interop value = default; try - { + { var source = ComWrappersSupport.FindObject(thisPtr).GetSource(index); value = MarshalInterface.FromManaged(source); From 016935a0235844b0d0b1fed42735c26f22060b72 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Jun 2020 11:54:23 -0300 Subject: [PATCH 004/492] attached composition gl surface to composition tree. --- .../Composition/CompositionHost.cs | 67 +++++++++++++++++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 11 +-- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index a94fd82e1a..961c3b0319 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Avalonia.OpenGL; using Avalonia.OpenGL.Angle; using Windows.UI.Composition; using Windows.UI.Composition.Interop; @@ -7,6 +8,61 @@ using WinRT; namespace Avalonia.Win32 { + public class CompositionEglGlPlatformSurface : EglGlPlatformSurfaceBase + { + private readonly EglDisplay _display; + private readonly EglContext _context; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + private ICompositionDrawingSurfaceInterop _surfaceInterop; + + public CompositionEglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base() + { + _display = context.Display; + _context = context; + _info = info; + } + + public void AttachToCompositionTree(IntPtr hwnd) + { + _surfaceInterop = CompositionHost.Instance.Initialize(hwnd); + } + + public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + { + var glSurface = _display.CreateWindowSurface(_info.Handle); + return new CompositionRenderTarget(_display, _context, glSurface, _surfaceInterop, _info); + } + + class CompositionRenderTarget : EglPlatformSurfaceRenderTargetBase + { + private readonly EglDisplay _display; + private readonly EglContext _context; + private readonly EglSurface _glSurface; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + private PixelSize _initialSize; + private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; + + public CompositionRenderTarget(EglDisplay display, EglContext context, + EglSurface glSurface, ICompositionDrawingSurfaceInterop interopSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context) + { + _display = display; + _context = context; + _glSurface = glSurface; + _surfaceInterop = interopSurface; + _info = info; + _initialSize = info.Size; + } + + public override void Dispose() => _glSurface.Dispose(); + + public override bool IsCorrupted => _initialSize != _info.Size; + + public override IGlPlatformSurfaceRenderingSession BeginDraw() => base.BeginDraw(_glSurface, _info); + } + } + + + class CompositionHost { internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE @@ -75,7 +131,7 @@ namespace Avalonia.Win32 } } - public void Initialize(IntPtr hwnd) + public ICompositionDrawingSurfaceInterop Initialize(IntPtr hwnd) { EnsureDispatcherQueue(); if (_dispatcherQueueController != null) @@ -90,13 +146,13 @@ namespace Avalonia.Win32 var gDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); - var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100,100), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); + var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100, 100), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); var surfaceInterop = surface.As(); - surfaceInterop.BeginDraw(new Windows.Foundation.Rect(0, 0, 100, 100), Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"), out var texture, new Windows.Foundation.Point(0, 0)); - surfaceInterop.EndDraw(); - + //surfaceInterop.BeginDraw(new Windows.Foundation.Rect(0, 0, 100, 100), Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"), out var texture, new Windows.Foundation.Point(0, 0)); + //surfaceInterop.EndDraw(); + var brush = _compositor.CreateSurfaceBrush(surface); var visual = _compositor.CreateSpriteVisual(); @@ -108,6 +164,7 @@ namespace Avalonia.Win32 AddElement(100, 200, 200); + return surfaceInterop; } public void CreateBlur() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ab07bdebb3..03d2a328a6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -100,7 +100,12 @@ namespace Avalonia.Win32 _framebuffer = new FramebufferManager(_hwnd); if (Win32GlManager.EglFeature != null) - _gl = new EglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); + _gl = new CompositionEglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); + + if (_gl is CompositionEglGlPlatformSurface cgl) + { + cgl.AttachToCompositionTree(_hwnd); + } Screen = new ScreenImpl(); @@ -598,9 +603,7 @@ namespace Avalonia.Win32 { _scaling = dpix / 96.0; } - } - - CompositionHost.Instance.Initialize(_hwnd); + } } private void CreateDropTarget() From 203e346a4d855dba07d64173844434fa31fdd1f1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Jun 2020 12:49:48 -0300 Subject: [PATCH 005/492] make begindraw work. --- .../Composition/CompositionHost.cs | 96 +++++++++++++++---- .../ICompositionDrawingSurfaceInterop.cs | 45 ++++++--- 2 files changed, 110 insertions(+), 31 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 961c3b0319..5f20d18faf 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -28,36 +28,99 @@ namespace Avalonia.Win32 } public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() - { - var glSurface = _display.CreateWindowSurface(_info.Handle); - return new CompositionRenderTarget(_display, _context, glSurface, _surfaceInterop, _info); + { + return new CompositionRenderTarget(_display, _context, _surfaceInterop, _info); } class CompositionRenderTarget : EglPlatformSurfaceRenderTargetBase { private readonly EglDisplay _display; - private readonly EglContext _context; - private readonly EglSurface _glSurface; + private readonly EglContext _context; private readonly IEglWindowGlPlatformSurfaceInfo _info; private PixelSize _initialSize; private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; - public CompositionRenderTarget(EglDisplay display, EglContext context, - EglSurface glSurface, ICompositionDrawingSurfaceInterop interopSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context) + public CompositionRenderTarget(EglDisplay display, EglContext context, ICompositionDrawingSurfaceInterop interopSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context) { _display = display; - _context = context; - _glSurface = glSurface; + _context = context; _surfaceInterop = interopSurface; _info = info; _initialSize = info.Size; } - public override void Dispose() => _glSurface.Dispose(); + public override bool IsCorrupted => _initialSize != _info.Size; + + public override IGlPlatformSurfaceRenderingSession BeginDraw() + { + var l = _context.Lock(); + + var iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); + var updateRect = new RECT { right = _info.Size.Width, bottom = _info.Size.Height }; + var offset = new POINT(); + + _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); + _surfaceInterop.BeginDraw( + ref updateRect, + ref iid, + out IntPtr texture, ref offset); - public override bool IsCorrupted => _initialSize != _info.Size; + var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture); - public override IGlPlatformSurfaceRenderingSession BeginDraw() => base.BeginDraw(_glSurface, _info); + var restoreContext = _context.MakeCurrent(surface); + + return new Session(_display, _context, null, _info, l, restoreContext, () => { }, true); + } + + public override void Dispose() + { + _surfaceInterop.EndDraw(); + base.Dispose(); + } + } + + class Session : IGlPlatformSurfaceRenderingSession + { + private readonly EglContext _context; + private readonly EglSurface _glSurface; + private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; + private readonly EglDisplay _display; + private readonly IDisposable _lock; + private readonly IDisposable _restoreContext; + private readonly Action _onFinish; + + + public Session(EglDisplay display, EglContext context, + EglSurface glSurface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, + IDisposable @lock, IDisposable restoreContext, Action onFinish, bool isYFlipped) + { + IsYFlipped = isYFlipped; + _context = context; + _display = display; + _glSurface = glSurface; + _info = info; + _lock = @lock; + _restoreContext = restoreContext; + _onFinish = onFinish; + } + + public void Dispose() + { + _context.GlInterface.Flush(); + _display.EglInterface.WaitGL(); + _glSurface?.SwapBuffers(); + _display.EglInterface.WaitClient(); + _display.EglInterface.WaitGL(); + _display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); + _restoreContext.Dispose(); + _lock.Dispose(); + _onFinish?.Invoke(); + } + + public IGlContext Context => _context; + public PixelSize Size => _info.Size; + public double Scaling => _info.Scaling; + public bool IsYFlipped { get; } } } @@ -146,12 +209,11 @@ namespace Avalonia.Win32 var gDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); - var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100, 100), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); - - var surfaceInterop = surface.As(); + var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100, 100), + Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, + Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); - //surfaceInterop.BeginDraw(new Windows.Foundation.Rect(0, 0, 100, 100), Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"), out var texture, new Windows.Foundation.Point(0, 0)); - //surfaceInterop.EndDraw(); + var surfaceInterop = surface.As(); var brush = _compositor.CreateSurfaceBrush(surface); diff --git a/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs b/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs index fd9387b59e..8fea5f293b 100644 --- a/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs +++ b/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs @@ -1,23 +1,39 @@ using System; using System.Runtime.InteropServices; -using Windows.Foundation; using WinRT; namespace Windows.UI.Composition.Interop { + public struct POINT + { + public int X; + public int Y; + } + + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + + public int Width => right - left; + public int Height => bottom - top; + } + [WindowsRuntimeType] [Guid("FD04E6E3-FE0C-4C3C-AB19-A07601A576EE")] public interface ICompositionDrawingSurfaceInterop { - void BeginDraw(Rect updateRect, Guid iid, out IntPtr updateObject, Point point); + void BeginDraw(ref RECT updateRect, ref Guid iid, out IntPtr updateObject, ref POINT point); void EndDraw(); - void Resize(Size sizePixels); + void Resize(POINT sizePixels); void ResumeDraw(); - void Scroll(Rect scrollRect, Rect clipRect, int offsetX, int offsetY); + void Scroll(RECT scrollRect, RECT clipRect, int offsetX, int offsetY); void SuspendDraw(); } @@ -28,6 +44,7 @@ namespace ABI.Windows.UI.Composition.Interop using global::System; using global::System.Runtime.InteropServices; using global::Windows.UI.Composition; + using global::Windows.UI.Composition.Interop; [Guid("FD04E6E3-FE0C-4C3C-AB19-A07601A576EE")] internal class ICompositionDrawingSurfaceInterop : global::Windows.UI.Composition.Interop.ICompositionDrawingSurfaceInterop @@ -36,11 +53,11 @@ namespace ABI.Windows.UI.Composition.Interop [Guid("FD04E6E3-FE0C-4C3C-AB19-A07601A576EE")] public struct Vftbl { - public delegate int _BeginDraw(IntPtr ThisPtr, Guid iid, out IntPtr updateObject, Point updateOffset); + public delegate int _BeginDraw(IntPtr ThisPtr, ref RECT updateRect, ref Guid iid, out IntPtr updateObject, ref POINT updateOffset); public delegate int _EndDraw(IntPtr ThisPtr); - public delegate int _Resize(IntPtr ThisPtr, Size sizePixels); + public delegate int _Resize(IntPtr ThisPtr, POINT sizePixels); public delegate int _ResumeDraw(IntPtr ThisPtr); - public delegate int _Scroll(IntPtr ThisPtr, Rect scrollRect, Rect clipRect, int offsetX, int offsetY); + public delegate int _Scroll(IntPtr ThisPtr, RECT scrollRect, RECT clipRect, int offsetX, int offsetY); public delegate int _SuspendDraw(IntPtr ThisPtr); internal global::WinRT.Interop.IUnknownVftbl IUnknownVftbl; @@ -70,7 +87,7 @@ namespace ABI.Windows.UI.Composition.Interop Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false); } - private static int Do_Abi_BeginDraw(IntPtr ThisPtr, Guid iid, out IntPtr updateObject, Point updateOffset) + private static int Do_Abi_BeginDraw(IntPtr ThisPtr, ref RECT updateRect, ref Guid iid, out IntPtr updateObject, ref POINT updateOffset) { updateObject = IntPtr.Zero; return 0; @@ -81,7 +98,7 @@ namespace ABI.Windows.UI.Composition.Interop return 0; } - private static int Do_Abi_Resize(IntPtr ThisPtr, Size sizePixels) + private static int Do_Abi_Resize(IntPtr ThisPtr, POINT sizePixels) { return 0; } @@ -101,9 +118,9 @@ namespace ABI.Windows.UI.Composition.Interop _obj = obj; } - public void BeginDraw(Rect updateRect, Guid iid, out IntPtr updateObject, Point point) + public void BeginDraw(ref RECT updateRect, ref Guid iid, out IntPtr updateObject, ref POINT point) { - Marshal.ThrowExceptionForHR(_obj.Vftbl.BeginDraw(ThisPtr, iid, out updateObject, point)); + Marshal.ThrowExceptionForHR(_obj.Vftbl.BeginDraw(ThisPtr, ref updateRect, ref iid, out updateObject, ref point)); } public void EndDraw() @@ -111,9 +128,9 @@ namespace ABI.Windows.UI.Composition.Interop Marshal.ThrowExceptionForHR(_obj.Vftbl.EndDraw(ThisPtr)); } - public void Resize(Size sizePixels) + public void Resize(POINT sizePixels) { - throw new NotImplementedException(); + Marshal.ThrowExceptionForHR(_obj.Vftbl.Resize(ThisPtr, sizePixels)); } public void ResumeDraw() @@ -121,7 +138,7 @@ namespace ABI.Windows.UI.Composition.Interop throw new NotImplementedException(); } - public void Scroll(Rect scrollRect, Rect clipRect, int offsetX, int offsetY) + public void Scroll(RECT scrollRect, RECT clipRect, int offsetX, int offsetY) { throw new NotImplementedException(); } From 6adb1d062475ec8679ef01048bba24902c2c95b8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Jun 2020 12:51:37 -0300 Subject: [PATCH 006/492] pass in wrapped surface to session. --- src/Windows/Avalonia.Win32/Composition/CompositionHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 5f20d18faf..02291d93ed 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -69,7 +69,7 @@ namespace Avalonia.Win32 var restoreContext = _context.MakeCurrent(surface); - return new Session(_display, _context, null, _info, l, restoreContext, () => { }, true); + return new Session(_display, _context, surface, _info, l, restoreContext, () => { }, true); } public override void Dispose() @@ -108,7 +108,7 @@ namespace Avalonia.Win32 { _context.GlInterface.Flush(); _display.EglInterface.WaitGL(); - _glSurface?.SwapBuffers(); + _glSurface.SwapBuffers(); _display.EglInterface.WaitClient(); _display.EglInterface.WaitGL(); _display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); From f5dd50e8ea855a8e903af1e872648b60c5e7de52 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Jun 2020 12:56:46 -0300 Subject: [PATCH 007/492] call enddraw. --- src/Windows/Avalonia.Win32/Composition/CompositionHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 02291d93ed..b82fbc7d1d 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -69,7 +69,7 @@ namespace Avalonia.Win32 var restoreContext = _context.MakeCurrent(surface); - return new Session(_display, _context, surface, _info, l, restoreContext, () => { }, true); + return new Session(_display, _context, surface, _info, l, restoreContext, () => { _surfaceInterop.EndDraw(); }, true); } public override void Dispose() From f0c2ff7394715407368979f5361f7746a032099e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Jun 2020 13:02:03 -0300 Subject: [PATCH 008/492] call base.BeginDraw we dont need our own session class. --- .../Composition/CompositionHost.cs | 52 +------------------ 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index b82fbc7d1d..3837134643 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -69,58 +69,8 @@ namespace Avalonia.Win32 var restoreContext = _context.MakeCurrent(surface); - return new Session(_display, _context, surface, _info, l, restoreContext, () => { _surfaceInterop.EndDraw(); }, true); + return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); }, true); } - - public override void Dispose() - { - _surfaceInterop.EndDraw(); - base.Dispose(); - } - } - - class Session : IGlPlatformSurfaceRenderingSession - { - private readonly EglContext _context; - private readonly EglSurface _glSurface; - private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; - private readonly EglDisplay _display; - private readonly IDisposable _lock; - private readonly IDisposable _restoreContext; - private readonly Action _onFinish; - - - public Session(EglDisplay display, EglContext context, - EglSurface glSurface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, - IDisposable @lock, IDisposable restoreContext, Action onFinish, bool isYFlipped) - { - IsYFlipped = isYFlipped; - _context = context; - _display = display; - _glSurface = glSurface; - _info = info; - _lock = @lock; - _restoreContext = restoreContext; - _onFinish = onFinish; - } - - public void Dispose() - { - _context.GlInterface.Flush(); - _display.EglInterface.WaitGL(); - _glSurface.SwapBuffers(); - _display.EglInterface.WaitClient(); - _display.EglInterface.WaitGL(); - _display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); - _restoreContext.Dispose(); - _lock.Dispose(); - _onFinish?.Invoke(); - } - - public IGlContext Context => _context; - public PixelSize Size => _info.Size; - public double Scaling => _info.Scaling; - public bool IsYFlipped { get; } } } From 53f3bc6e7fe84fc0fb3bf9e5f11fd327664ff9ca Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Jun 2020 13:25:24 -0300 Subject: [PATCH 009/492] working rendering. --- .../Composition/CompositionHost.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 3837134643..d4bbb67a3b 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -28,14 +28,14 @@ namespace Avalonia.Win32 } public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() - { + { return new CompositionRenderTarget(_display, _context, _surfaceInterop, _info); } class CompositionRenderTarget : EglPlatformSurfaceRenderTargetBase { private readonly EglDisplay _display; - private readonly EglContext _context; + private readonly EglContext _context; private readonly IEglWindowGlPlatformSurfaceInfo _info; private PixelSize _initialSize; private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; @@ -43,7 +43,7 @@ namespace Avalonia.Win32 public CompositionRenderTarget(EglDisplay display, EglContext context, ICompositionDrawingSurfaceInterop interopSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context) { _display = display; - _context = context; + _context = context; _surfaceInterop = interopSurface; _info = info; _initialSize = info.Size; @@ -53,8 +53,6 @@ namespace Avalonia.Win32 public override IGlPlatformSurfaceRenderingSession BeginDraw() { - var l = _context.Lock(); - var iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); var updateRect = new RECT { right = _info.Size.Width, bottom = _info.Size.Height }; var offset = new POINT(); @@ -67,8 +65,6 @@ namespace Avalonia.Win32 var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture); - var restoreContext = _context.MakeCurrent(surface); - return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); }, true); } } @@ -159,11 +155,11 @@ namespace Avalonia.Win32 var gDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); - var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100, 100), - Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, + var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100, 100), + Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); - var surfaceInterop = surface.As(); + var surfaceInterop = surface.As(); var brush = _compositor.CreateSurfaceBrush(surface); @@ -174,7 +170,7 @@ namespace Avalonia.Win32 _target.Root = visual; - AddElement(100, 200, 200); + //CreateBlur(); return surfaceInterop; } From e53dfc0d360cb1c34706ae81b54bda7aed65b4c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Jun 2020 14:02:51 -0300 Subject: [PATCH 010/492] add blur and avalonia surface visual ontop of blur. --- samples/ControlCatalog/MainWindow.xaml | 3 ++- .../Avalonia.Win32/Composition/CompositionHost.cs | 11 +++++++---- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 1 + src/Windows/Avalonia.Win32/WindowImpl.cs | 4 ++++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 76422bc130..c34e392d40 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -7,7 +7,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:v="clr-namespace:ControlCatalog.Views" - x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{DynamicResource SystemControlPageBackgroundAltHighBrush}"> + TransparencyLevelHint="Transparent" + x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{x:Null}"> diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index d4bbb67a3b..874bdaaf69 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -155,7 +155,7 @@ namespace Avalonia.Win32 var gDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); - var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(100, 100), + var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(0, 0), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); @@ -167,10 +167,13 @@ namespace Avalonia.Win32 visual.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); visual.Brush = brush; + //_target.Root = visual; - _target.Root = visual; + CreateBlur(); + + var visuals = _target.Root.As().Children; - //CreateBlur(); + visuals.InsertAtTop(visual); return surfaceInterop; } @@ -196,7 +199,7 @@ namespace Avalonia.Win32 void CreateCompositionRoot() { var root = _compositor.CreateContainerVisual(); - root.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); + root.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); //root.Offset = new System.Numerics.Vector3(0, 0, 0); _target.Root = root; } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index b3b38db1ab..bef65a06d8 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -465,6 +465,7 @@ namespace Avalonia.Win32.Interop WS_VSCROLL = 0x200000, WS_EX_DLGMODALFRAME = 0x00000001, WS_EX_NOPARENTNOTIFY = 0x00000004, + WS_EX_NOREDIRECTIONBITMAP = 0x00200000, WS_EX_TOPMOST = 0x00000008, WS_EX_ACCEPTFILES = 0x00000010, WS_EX_TRANSPARENT = 0x00000020, diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 03d2a328a6..fdde128800 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -106,6 +106,10 @@ namespace Avalonia.Win32 { cgl.AttachToCompositionTree(_hwnd); } + else + { + CompositionHost.Instance.Initialize(_hwnd); + } Screen = new ScreenImpl(); From 1f9e395fabb55234e452cdfe94562774e950e61a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Jun 2020 14:53:05 -0300 Subject: [PATCH 011/492] hack to clear window context, and only call interopsurface.resize when size actually changes. --- .../Composition/CompositionHost.cs | 33 +++++++++++++++++-- .../Composition/GaussianBlurEffect.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 ++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 874bdaaf69..fd76e4a523 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -51,13 +51,42 @@ namespace Avalonia.Win32 public override bool IsCorrupted => _initialSize != _info.Size; + bool _firstRun = true; + POINT lastSize; public override IGlPlatformSurfaceRenderingSession BeginDraw() { + if (_firstRun) + { + _firstRun = false; + var windowSurface = new EglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, _info); + + using (var target = windowSurface.CreateGlRenderTarget()) + { + using (var session = target.BeginDraw()) + { + using (session.Context.MakeCurrent()) + { + var gl = _context.GlInterface; + gl.Viewport(0, 0, _info.Size.Width, _info.Size.Height); + gl.ClearStencil(0); + gl.ClearColor(0, 0, 0, 0); + gl.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_DEPTH_BUFFER_BIT); + gl.Flush(); + } + } + } + } + + var iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); var updateRect = new RECT { right = _info.Size.Width, bottom = _info.Size.Height }; var offset = new POINT(); - _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); + if (lastSize.X != _info.Size.Width || lastSize.Y != _info.Size.Height) + { + lastSize = new POINT { X = _info.Size.Width, Y = _info.Size.Height }; + _surfaceInterop.Resize(lastSize); + } _surfaceInterop.BeginDraw( ref updateRect, ref iid, @@ -65,7 +94,7 @@ namespace Avalonia.Win32 var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture); - return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); }, true); + return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); Marshal.Release(texture); surface.Dispose(); }, true); } } } diff --git a/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs b/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs index 19595e8977..4e1be3af1a 100644 --- a/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs +++ b/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs @@ -52,7 +52,7 @@ namespace Avalonia.Win32 return 30.0f; case D2D1GaussianBlurProp.D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION: - return (UInt32)D2D1_GAUSSIANBLUR_OPTIMIZATION.D2D1_GAUSSIANBLUR_OPTIMIZATION_SPEED; + return (UInt32)D2D1_GAUSSIANBLUR_OPTIMIZATION.D2D1_GAUSSIANBLUR_OPTIMIZATION_BALANCED; case D2D1GaussianBlurProp.D2D1_GAUSSIANBLUR_PROP_BORDER_MODE: return (UInt32)D2D1_BORDER_MODE.D2D1_BORDER_MODE_HARD; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index fdde128800..151a998673 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -100,7 +100,9 @@ namespace Avalonia.Win32 _framebuffer = new FramebufferManager(_hwnd); if (Win32GlManager.EglFeature != null) + { _gl = new CompositionEglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); + } if (_gl is CompositionEglGlPlatformSurface cgl) { From 372f8e3938088ed882f006e32e1d7b083149a145 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 26 Jun 2020 15:29:35 -0300 Subject: [PATCH 012/492] 1 graphics device, 1 surface and visual tree per window. --- .../Composition/CompositionHost.cs | 49 +++++++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index fd76e4a523..1735b22035 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -24,7 +24,7 @@ namespace Avalonia.Win32 public void AttachToCompositionTree(IntPtr hwnd) { - _surfaceInterop = CompositionHost.Instance.Initialize(hwnd); + _surfaceInterop = CompositionHost.Instance.InitialiseWindowCompositionTree(hwnd); } public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() @@ -47,6 +47,8 @@ namespace Avalonia.Win32 _surfaceInterop = interopSurface; _info = info; _initialSize = info.Size; + lastSize = new POINT { X = _info.Size.Width, Y = _info.Size.Height }; + _surfaceInterop.Resize(lastSize); } public override bool IsCorrupted => _initialSize != _info.Size; @@ -85,7 +87,7 @@ namespace Avalonia.Win32 if (lastSize.X != _info.Size.Width || lastSize.Y != _info.Size.Height) { lastSize = new POINT { X = _info.Size.Width, Y = _info.Size.Height }; - _surfaceInterop.Resize(lastSize); + // _surfaceInterop.Resize(lastSize); } _surfaceInterop.BeginDraw( ref updateRect, @@ -135,17 +137,18 @@ namespace Avalonia.Win32 private Compositor _compositor; private Windows.System.DispatcherQueueController _dispatcherQueueController; - private Windows.UI.Composition.Desktop.DesktopWindowTarget _target; + private CompositionGraphicsDevice _graphicsDevice; private CompositionHost() { + Initialize(); } - public void AddElement(float size, float x, float y) + public void AddElement(CompositionTarget target, float size, float x, float y) { - if (_target.Root != null) + if (target.Root != null) { - var visuals = _target.Root.As().Children; + var visuals = target.Root.As().Children; var visual = _compositor.CreateSpriteVisual(); @@ -169,22 +172,24 @@ namespace Avalonia.Win32 } } - public ICompositionDrawingSurfaceInterop Initialize(IntPtr hwnd) + private void Initialize() { EnsureDispatcherQueue(); if (_dispatcherQueueController != null) _compositor = new Windows.UI.Composition.Compositor(); - CreateDesktopWindowTarget(hwnd); - CreateCompositionRoot(); - var interop = _compositor.As(); var display = Win32GlManager.EglFeature.Display as AngleWin32EglDisplay; - var gDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); + _graphicsDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); + } + + public ICompositionDrawingSurfaceInterop InitialiseWindowCompositionTree(IntPtr hwnd) + { + var target = CreateDesktopWindowTarget(hwnd); - var surface = gDevice.CreateDrawingSurface(new Windows.Foundation.Size(0, 0), + var surface = _graphicsDevice.CreateDrawingSurface(new Windows.Foundation.Size(0, 0), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); @@ -198,16 +203,16 @@ namespace Avalonia.Win32 visual.Brush = brush; //_target.Root = visual; - CreateBlur(); + target.Root = CreateBlur(); - var visuals = _target.Root.As().Children; + var visuals = target.Root.As().Children; visuals.InsertAtTop(visual); return surfaceInterop; } - public void CreateBlur() + public SpriteVisual CreateBlur() { var effect = new GaussianBlurEffect(); var effectFactory = _compositor.CreateEffectFactory(effect); @@ -222,23 +227,15 @@ namespace Avalonia.Win32 visual.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); visual.Brush = blurBrush; - _target.Root = visual; - } - - void CreateCompositionRoot() - { - var root = _compositor.CreateContainerVisual(); - root.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); - //root.Offset = new System.Numerics.Vector3(0, 0, 0); - _target.Root = root; + return visual; } - void CreateDesktopWindowTarget(IntPtr window) + CompositionTarget CreateDesktopWindowTarget(IntPtr window) { var interop = _compositor.As(); interop.CreateDesktopWindowTarget(window, false, out var windowTarget); - _target = Windows.UI.Composition.Desktop.DesktopWindowTarget.FromAbi(windowTarget); + return Windows.UI.Composition.Desktop.DesktopWindowTarget.FromAbi(windowTarget); } void EnsureDispatcherQueue() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 151a998673..bcccdf4810 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -110,7 +110,7 @@ namespace Avalonia.Win32 } else { - CompositionHost.Instance.Initialize(_hwnd); + CompositionHost.Instance.InitialiseWindowCompositionTree(_hwnd); } Screen = new ScreenImpl(); From c22b71aa50cc0b74848be1db1723376a736f7b3a Mon Sep 17 00:00:00 2001 From: Yatao Li Date: Thu, 6 Aug 2020 17:41:32 +0800 Subject: [PATCH 013/492] update build.ps1 to put downloaded dotnet in PATH --- build.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.ps1 b/build.ps1 index 57e2f80075..3672e82d3b 100644 --- a/build.ps1 +++ b/build.ps1 @@ -62,6 +62,8 @@ else { } else { ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } } + + $env:PATH="$DotNetDirectory;$env:PATH" } Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" From 7fa2c8495690b0af1699b87ed62cd273fbd91e59 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Fri, 31 Jul 2020 20:28:04 +0300 Subject: [PATCH 014/492] Add handling mouse events in webapp --- .../webapp/src/FramePresenter.tsx | 25 ++++++++++++- .../src/Models/Input/InputEventMessageBase.ts | 11 ++++++ .../webapp/src/Models/Input/InputModifiers.ts | 9 +++++ .../webapp/src/Models/Input/MouseButton.ts | 6 +++ .../src/Models/Input/MouseEventHelpers.ts | 37 +++++++++++++++++++ .../Models/Input/PointerEventMessageBase.ts | 13 +++++++ .../Models/Input/PointerMovedEventMessage.ts | 12 ++++++ .../Input/PointerPressedEventMessage.ts | 17 +++++++++ .../Input/PointerReleasedEventMessage.ts | 17 +++++++++ 9 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts create mode 100644 src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers.ts create mode 100644 src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton.ts create mode 100644 src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts create mode 100644 src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts create mode 100644 src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts create mode 100644 src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts create mode 100644 src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx index 0059cfe683..ce43822dd8 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx @@ -1,5 +1,8 @@ -import {PreviewerFrame, PreviewerServerConnection} from "src/PreviewerServerConnection"; import * as React from "react"; +import {PreviewerFrame, PreviewerServerConnection} from "src/PreviewerServerConnection"; +import {PointerPressedEventMessage} from "src/Models/Input/PointerPressedEventMessage"; +import {PointerReleasedEventMessage} from "src/Models/Input/PointerReleasedEventMessage"; +import {PointerMovedEventMessage} from "src/Models/Input/PointerMovedEventMessage"; interface PreviewerPresenterProps { conn: PreviewerServerConnection; @@ -51,7 +54,25 @@ export class PreviewerPresenter extends React.Component } } + handleMouseDown(e: React.MouseEvent) { + const pointerPressedEventMessage = new PointerPressedEventMessage(e); + // TODO: Send message to server + } + + handleMouseUp(e: React.MouseEvent) { + const pointerReleasedEventMessage = new PointerReleasedEventMessage(e); + // TODO: Send message to server + } + + handleMouseMove(e: React.MouseEvent) { + const pointerMovedEventMessage = new PointerMovedEventMessage(e); + // TODO: Send message to server + } + render() { - return + return } } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts new file mode 100644 index 0000000000..2da7424e2a --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts @@ -0,0 +1,11 @@ +import * as React from "react"; +import {InputModifiers} from "src/Models/Input/InputModifiers"; +import {getModifiers} from "src/Models/Input/MouseEventHelpers"; + +export abstract class InputEventMessageBase { + public readonly modifiers : Array; + + protected constructor(e: React.MouseEvent) { + this.modifiers = getModifiers(e); + } +} diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers.ts new file mode 100644 index 0000000000..483b4c02d0 --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers.ts @@ -0,0 +1,9 @@ +export enum InputModifiers { + Alt, + Control, + Shift, + Windows, + LeftMouseButton, + RightMouseButton, + MiddleMouseButton, +} diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton.ts new file mode 100644 index 0000000000..880f8fb5ce --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton.ts @@ -0,0 +1,6 @@ +export enum MouseButton { + None, + Left, + Right, + Middle, +} diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts new file mode 100644 index 0000000000..783bacad38 --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts @@ -0,0 +1,37 @@ +import * as React from "react"; +import {InputModifiers} from "src/Models/Input/InputModifiers"; +import {MouseButton} from "src/Models/Input/MouseButton"; + +export function getModifiers(e: React.MouseEvent): Array { + + let modifiers : Array = []; + + if (e.altKey) + modifiers.push(InputModifiers.Alt); + if (e.ctrlKey) + modifiers.push(InputModifiers.Control); + if (e.shiftKey) + modifiers.push(InputModifiers.Shift); + if (e.metaKey) + modifiers.push(InputModifiers.Windows); + if ((e.buttons & 1) != 0) + modifiers.push(InputModifiers.LeftMouseButton); + if ((e.buttons & 2) != 0) + modifiers.push(InputModifiers.RightMouseButton); + if ((e.buttons & 4) != 0) + modifiers.push(InputModifiers.MiddleMouseButton); + + return modifiers; +} + +export function getMouseButton(e: React.MouseEvent) : MouseButton { + if (e.button == 1) { + return MouseButton.Left; + } else if (e.button == 2) { + return MouseButton.Right; + } else if (e.button == 4) { + return MouseButton.Middle + } else { + return MouseButton.None; + } +} diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts new file mode 100644 index 0000000000..e43362ac1c --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts @@ -0,0 +1,13 @@ +import * as React from "react"; +import {InputEventMessageBase} from "src/Models/Input/InputEventMessageBase"; + +export abstract class PointerEventMessageBase extends InputEventMessageBase { + public readonly x: number; + public readonly y: number; + + protected constructor(e: React.MouseEvent) { + super(e) + this.x = e.clientX; + this.y = e.clientY; + } +} diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts new file mode 100644 index 0000000000..3ca8e91b8d --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts @@ -0,0 +1,12 @@ +import * as React from "react"; +import {PointerEventMessageBase} from "src/Models/Input/PointerEventMessageBase"; + +export class PointerMovedEventMessage extends PointerEventMessageBase { + constructor(e: React.MouseEvent) { + super(e) + } + + public toString = () : string => { + return `pointer-moved:${this.modifiers}:${this.x}:${this.y}`; + } +} diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts new file mode 100644 index 0000000000..712ec64a14 --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts @@ -0,0 +1,17 @@ +import * as React from "react"; +import {PointerEventMessageBase} from "src/Models/Input/PointerEventMessageBase"; +import {MouseButton} from "src/Models/Input/MouseButton"; +import {getMouseButton} from "src/Models/Input/MouseEventHelpers"; + +export class PointerPressedEventMessage extends PointerEventMessageBase { + public readonly button: MouseButton + + constructor(e: React.MouseEvent) { + super(e) + this.button = getMouseButton(e); + } + + public toString = () : string => { + return `pointer-pressed:${this.modifiers}:${this.button}:${this.x}:${this.y}`; + } +} diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts new file mode 100644 index 0000000000..46234263db --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts @@ -0,0 +1,17 @@ +import * as React from "react"; +import {PointerEventMessageBase} from "src/Models/Input/PointerEventMessageBase"; +import {MouseButton} from "src/Models/Input/MouseButton"; +import {getMouseButton} from "src/Models/Input/MouseEventHelpers"; + +export class PointerReleasedEventMessage extends PointerEventMessageBase { + public readonly button: MouseButton + + constructor(e: React.MouseEvent) { + super(e) + this.button = getMouseButton(e); + } + + public toString = () : string => { + return `pointer-released:${this.modifiers}:${this.button}:${this.x}:${this.y}`; + } +} From 23d29d445c316df9718d598f5439951d2fb993c7 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sun, 9 Aug 2020 19:54:13 +0300 Subject: [PATCH 015/492] Add message sending and server processing --- .../Remote/HtmlTransport/HtmlTransport.cs | 71 +++++++++++++++++-- .../webapp/src/FramePresenter.tsx | 10 ++- .../webapp/src/PreviewerServerConnection.ts | 6 ++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index 55e2df8890..eb64f83d35 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Viewport; +using InputProtocol = Avalonia.Remote.Protocol.Input; namespace Avalonia.DesignerSupport.Remote.HtmlTransport { @@ -117,10 +118,55 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport return; if (msg.IsText) { - var s = Encoding.UTF8.GetString(msg.Data); - var parts = s.Split(':'); - if (parts[0] == "frame-received") - _onMessage?.Invoke(this, new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }); + var parts = msg.AsString().Split(':'); + switch (parts[0]) + { + case "frame-received": + { + _onMessage?.Invoke( + this, + new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }); + break; + } + case "pointer-released": + { + _onMessage?.Invoke( + this, + new InputProtocol.PointerReleasedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + Button = ParseMouseButton(parts[2]), + X = double.Parse(parts[3]), + Y = double.Parse(parts[4]), + }); + break; + } + case "pointer-pressed": + { + _onMessage?.Invoke( + this, + new InputProtocol.PointerPressedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + Button = ParseMouseButton(parts[2]), + X = double.Parse(parts[3]), + Y = double.Parse(parts[4]), + }); + break; + } + case "pointer-moved": + { + _onMessage?.Invoke( + this, + new InputProtocol.PointerMovedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = double.Parse(parts[2]), + Y = double.Parse(parts[3]), + }); + break; + } + } } } } @@ -262,5 +308,22 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport _onException?.Invoke(this, ex); } #endregion + + private static InputProtocol.InputModifiers[] ParseInputModifiers(string modifiersText) + { + var enumTexts = modifiersText.Split(','); + if (string.IsNullOrEmpty(enumTexts[0])) + return new InputProtocol.InputModifiers[0]; + return enumTexts + .Select(x => (InputProtocol.InputModifiers)Enum.Parse( + typeof(InputProtocol.InputModifiers), x)) + .ToArray(); + } + + private static InputProtocol.MouseButton ParseMouseButton(string buttonText) => + string.IsNullOrEmpty(buttonText) + ? InputProtocol.MouseButton.None + : (InputProtocol.MouseButton)Enum.Parse( + typeof(InputProtocol.MouseButton), buttonText); } } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx index ce43822dd8..b35e611c95 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx @@ -18,6 +18,10 @@ export class PreviewerPresenter extends React.Component this.componentDidUpdate({ conn: null! }, this.state); + + this.handleMouseDown = this.handleMouseDown.bind(this); + this.handleMouseUp = this.handleMouseUp.bind(this); + this.handleMouseMove = this.handleMouseMove.bind(this); } componentDidMount(): void { @@ -56,17 +60,17 @@ export class PreviewerPresenter extends React.Component handleMouseDown(e: React.MouseEvent) { const pointerPressedEventMessage = new PointerPressedEventMessage(e); - // TODO: Send message to server + this.props.conn.sendMouseEvent(pointerPressedEventMessage); } handleMouseUp(e: React.MouseEvent) { const pointerReleasedEventMessage = new PointerReleasedEventMessage(e); - // TODO: Send message to server + this.props.conn.sendMouseEvent(pointerReleasedEventMessage); } handleMouseMove(e: React.MouseEvent) { const pointerMovedEventMessage = new PointerMovedEventMessage(e); - // TODO: Send message to server + this.props.conn.sendMouseEvent(pointerMovedEventMessage); } render() { diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts index 891c5094e7..7f1ab84f99 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/PreviewerServerConnection.ts @@ -1,3 +1,5 @@ +import { InputEventMessageBase } from "src/Models/Input/InputEventMessageBase"; + export interface PreviewerFrame { data: ImageData; dpiX: number; @@ -28,6 +30,10 @@ export class PreviewerServerConnection { this.handlers.delete(listener); } + public sendMouseEvent(message: InputEventMessageBase) { + this.conn.send(message.toString()); + } + constructor(uri: string) { this.currentFrame = null; var conn = this.conn = new WebSocket(uri); From 851c03fa70d53675edc2693ed23299ca8c407bef Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Mon, 10 Aug 2020 17:08:53 +0300 Subject: [PATCH 016/492] Add handling wheel --- .../Remote/HtmlTransport/HtmlTransport.cs | 26 ++++++++++++++----- .../webapp/src/FramePresenter.tsx | 11 +++++++- .../Models/Input/PointerEventMessageBase.ts | 2 +- .../Models/Input/PointerMovedEventMessage.ts | 2 +- .../Input/PointerPressedEventMessage.ts | 4 +-- .../Input/PointerReleasedEventMessage.ts | 4 +-- .../src/Models/Input/ScrollEventMessage.ts | 17 ++++++++++++ 7 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index eb64f83d35..c32aefe308 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -135,9 +135,9 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport new InputProtocol.PointerReleasedEventMessage { Modifiers = ParseInputModifiers(parts[1]), - Button = ParseMouseButton(parts[2]), - X = double.Parse(parts[3]), - Y = double.Parse(parts[4]), + X = double.Parse(parts[2]), + Y = double.Parse(parts[3]), + Button = ParseMouseButton(parts[4]), }); break; } @@ -148,9 +148,9 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport new InputProtocol.PointerPressedEventMessage { Modifiers = ParseInputModifiers(parts[1]), - Button = ParseMouseButton(parts[2]), - X = double.Parse(parts[3]), - Y = double.Parse(parts[4]), + X = double.Parse(parts[2]), + Y = double.Parse(parts[3]), + Button = ParseMouseButton(parts[4]), }); break; } @@ -166,6 +166,20 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport }); break; } + case "scroll": + { + _onMessage?.Invoke( + this, + new InputProtocol.ScrollEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = double.Parse(parts[2]), + Y = double.Parse(parts[3]), + DeltaX = double.Parse(parts[4]), + DeltaY = double.Parse(parts[5]), + }); + break; + } } } } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx index b35e611c95..5182a087e7 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx @@ -3,6 +3,7 @@ import {PreviewerFrame, PreviewerServerConnection} from "src/PreviewerServerConn import {PointerPressedEventMessage} from "src/Models/Input/PointerPressedEventMessage"; import {PointerReleasedEventMessage} from "src/Models/Input/PointerReleasedEventMessage"; import {PointerMovedEventMessage} from "src/Models/Input/PointerMovedEventMessage"; +import {ScrollEventMessage} from "src/Models/Input/ScrollEventMessage"; interface PreviewerPresenterProps { conn: PreviewerServerConnection; @@ -22,6 +23,7 @@ export class PreviewerPresenter extends React.Component this.handleMouseDown = this.handleMouseDown.bind(this); this.handleMouseUp = this.handleMouseUp.bind(this); this.handleMouseMove = this.handleMouseMove.bind(this); + this.handleWheel = this.handleWheel.bind(this); } componentDidMount(): void { @@ -73,10 +75,17 @@ export class PreviewerPresenter extends React.Component this.props.conn.sendMouseEvent(pointerMovedEventMessage); } + handleWheel(e: React.WheelEvent) { + e.preventDefault(); + const scrollEventMessage = new ScrollEventMessage(e); + this.props.conn.sendMouseEvent(scrollEventMessage); + } + render() { return + onMouseMove={this.handleMouseMove} + onWheel={this.handleWheel} /> } } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts index e43362ac1c..fc304c2965 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts @@ -6,7 +6,7 @@ export abstract class PointerEventMessageBase extends InputEventMessageBase { public readonly y: number; protected constructor(e: React.MouseEvent) { - super(e) + super(e); this.x = e.clientX; this.y = e.clientY; } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts index 3ca8e91b8d..cd135dfa39 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts @@ -3,7 +3,7 @@ import {PointerEventMessageBase} from "src/Models/Input/PointerEventMessageBase" export class PointerMovedEventMessage extends PointerEventMessageBase { constructor(e: React.MouseEvent) { - super(e) + super(e); } public toString = () : string => { diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts index 712ec64a14..e75551e0b3 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts @@ -7,11 +7,11 @@ export class PointerPressedEventMessage extends PointerEventMessageBase { public readonly button: MouseButton constructor(e: React.MouseEvent) { - super(e) + super(e); this.button = getMouseButton(e); } public toString = () : string => { - return `pointer-pressed:${this.modifiers}:${this.button}:${this.x}:${this.y}`; + return `pointer-pressed:${this.modifiers}:${this.x}:${this.y}:${this.button}`; } } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts index 46234263db..c2bc368323 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts @@ -7,11 +7,11 @@ export class PointerReleasedEventMessage extends PointerEventMessageBase { public readonly button: MouseButton constructor(e: React.MouseEvent) { - super(e) + super(e); this.button = getMouseButton(e); } public toString = () : string => { - return `pointer-released:${this.modifiers}:${this.button}:${this.x}:${this.y}`; + return `pointer-released:${this.modifiers}:${this.x}:${this.y}:${this.button}`; } } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts new file mode 100644 index 0000000000..427ccc5e5e --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts @@ -0,0 +1,17 @@ +import * as React from "react"; +import {PointerEventMessageBase} from "src/Models/Input/PointerEventMessageBase"; + +export class ScrollEventMessage extends PointerEventMessageBase { + public readonly deltaX: number; + public readonly deltaY: number; + + constructor(e: React.WheelEvent) { + super(e); + this.deltaX = -e.deltaX; + this.deltaY = -e.deltaY; + } + + public toString = () : string => { + return `scroll:${this.modifiers}:${this.x}:${this.y}:${this.deltaX}:${this.deltaY}`; + } +} From 27f289e2bffb3ad0ecbc00eb0b327e41c14d520c Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Mon, 10 Aug 2020 23:21:59 +0300 Subject: [PATCH 017/492] Refactor --- .../Remote/HtmlTransport/HtmlTransport.cs | 141 +++++++++--------- .../webapp/src/FramePresenter.tsx | 13 +- 2 files changed, 76 insertions(+), 78 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index c32aefe308..51d266f11a 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -118,69 +118,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport return; if (msg.IsText) { - var parts = msg.AsString().Split(':'); - switch (parts[0]) - { - case "frame-received": - { - _onMessage?.Invoke( - this, - new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }); - break; - } - case "pointer-released": - { - _onMessage?.Invoke( - this, - new InputProtocol.PointerReleasedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = double.Parse(parts[2]), - Y = double.Parse(parts[3]), - Button = ParseMouseButton(parts[4]), - }); - break; - } - case "pointer-pressed": - { - _onMessage?.Invoke( - this, - new InputProtocol.PointerPressedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = double.Parse(parts[2]), - Y = double.Parse(parts[3]), - Button = ParseMouseButton(parts[4]), - }); - break; - } - case "pointer-moved": - { - _onMessage?.Invoke( - this, - new InputProtocol.PointerMovedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = double.Parse(parts[2]), - Y = double.Parse(parts[3]), - }); - break; - } - case "scroll": - { - _onMessage?.Invoke( - this, - new InputProtocol.ScrollEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = double.Parse(parts[2]), - Y = double.Parse(parts[3]), - DeltaX = double.Parse(parts[4]), - DeltaY = double.Parse(parts[5]), - }); - break; - } - } + ProcessingReceiveMessage(msg.AsString()); } } } @@ -323,16 +261,81 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport } #endregion - private static InputProtocol.InputModifiers[] ParseInputModifiers(string modifiersText) + private void ProcessingReceiveMessage(string message) { - var enumTexts = modifiersText.Split(','); - if (string.IsNullOrEmpty(enumTexts[0])) - return new InputProtocol.InputModifiers[0]; - return enumTexts + var parts = message.Split(':'); + switch (parts[0]) + { + case "frame-received": + { + _onMessage?.Invoke( + this, + new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }); + break; + } + case "pointer-released": + { + _onMessage?.Invoke( + this, + new InputProtocol.PointerReleasedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = double.Parse(parts[2]), + Y = double.Parse(parts[3]), + Button = ParseMouseButton(parts[4]), + }); + break; + } + case "pointer-pressed": + { + _onMessage?.Invoke( + this, + new InputProtocol.PointerPressedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = double.Parse(parts[2]), + Y = double.Parse(parts[3]), + Button = ParseMouseButton(parts[4]), + }); + break; + } + case "pointer-moved": + { + _onMessage?.Invoke( + this, + new InputProtocol.PointerMovedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = double.Parse(parts[2]), + Y = double.Parse(parts[3]), + }); + break; + } + case "scroll": + { + _onMessage?.Invoke( + this, + new InputProtocol.ScrollEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = double.Parse(parts[2]), + Y = double.Parse(parts[3]), + DeltaX = double.Parse(parts[4]), + DeltaY = double.Parse(parts[5]), + }); + break; + } + } + } + + private static InputProtocol.InputModifiers[] ParseInputModifiers(string modifiersText) => + string.IsNullOrEmpty(modifiersText) + ? new InputProtocol.InputModifiers[0] + : modifiersText + .Split(',') .Select(x => (InputProtocol.InputModifiers)Enum.Parse( typeof(InputProtocol.InputModifiers), x)) .ToArray(); - } private static InputProtocol.MouseButton ParseMouseButton(string buttonText) => string.IsNullOrEmpty(buttonText) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx index 5182a087e7..ec8a1bab68 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx @@ -19,11 +19,6 @@ export class PreviewerPresenter extends React.Component this.componentDidUpdate({ conn: null! }, this.state); - - this.handleMouseDown = this.handleMouseDown.bind(this); - this.handleMouseUp = this.handleMouseUp.bind(this); - this.handleMouseMove = this.handleMouseMove.bind(this); - this.handleWheel = this.handleWheel.bind(this); } componentDidMount(): void { @@ -83,9 +78,9 @@ export class PreviewerPresenter extends React.Component render() { return + onMouseDown={this.handleMouseDown.bind(this)} + onMouseUp={this.handleMouseUp.bind(this)} + onMouseMove={this.handleMouseMove.bind(this)} + onWheel={this.handleWheel.bind(this)} /> } } From 162d6f8cedf4f06e49846bd6f182e86af5d5f27e Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 11 Aug 2020 00:29:33 +0300 Subject: [PATCH 018/492] Return null instead of empty array --- .../Remote/HtmlTransport/HtmlTransport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index 51d266f11a..361d35c0b7 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -330,7 +330,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport private static InputProtocol.InputModifiers[] ParseInputModifiers(string modifiersText) => string.IsNullOrEmpty(modifiersText) - ? new InputProtocol.InputModifiers[0] + ? null : modifiersText .Split(',') .Select(x => (InputProtocol.InputModifiers)Enum.Parse( From f3718a5d034ab710e8416f9b76883f4f154c2a1b Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 11 Aug 2020 01:52:33 +0300 Subject: [PATCH 019/492] revert bind and preventdefault --- .../HtmlTransport/webapp/src/FramePresenter.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx index ec8a1bab68..96d2f45fdc 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/FramePresenter.tsx @@ -19,6 +19,11 @@ export class PreviewerPresenter extends React.Component this.componentDidUpdate({ conn: null! }, this.state); + + this.handleMouseDown = this.handleMouseDown.bind(this); + this.handleMouseUp = this.handleMouseUp.bind(this); + this.handleMouseMove = this.handleMouseMove.bind(this); + this.handleWheel = this.handleWheel.bind(this); } componentDidMount(): void { @@ -56,16 +61,19 @@ export class PreviewerPresenter extends React.Component } handleMouseDown(e: React.MouseEvent) { + e.preventDefault(); const pointerPressedEventMessage = new PointerPressedEventMessage(e); this.props.conn.sendMouseEvent(pointerPressedEventMessage); } handleMouseUp(e: React.MouseEvent) { + e.preventDefault(); const pointerReleasedEventMessage = new PointerReleasedEventMessage(e); this.props.conn.sendMouseEvent(pointerReleasedEventMessage); } handleMouseMove(e: React.MouseEvent) { + e.preventDefault(); const pointerMovedEventMessage = new PointerMovedEventMessage(e); this.props.conn.sendMouseEvent(pointerMovedEventMessage); } @@ -78,9 +86,9 @@ export class PreviewerPresenter extends React.Component render() { return + onMouseDown={this.handleMouseDown} + onMouseUp={this.handleMouseUp} + onMouseMove={this.handleMouseMove} + onWheel={this.handleWheel} /> } } From 1cfa116cc325461c041c2ed2c17a367a2f759795 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 11 Aug 2020 14:46:54 +0300 Subject: [PATCH 020/492] Fix typo in detect mouse button --- .../webapp/src/Models/Input/MouseEventHelpers.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts index 783bacad38..3f85959d6f 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts @@ -25,12 +25,12 @@ export function getModifiers(e: React.MouseEvent): Array { } export function getMouseButton(e: React.MouseEvent) : MouseButton { - if (e.button == 1) { + if (e.button == 0) { return MouseButton.Left; + } else if (e.button == 1) { + return MouseButton.Middle; } else if (e.button == 2) { return MouseButton.Right; - } else if (e.button == 4) { - return MouseButton.Middle } else { return MouseButton.None; } From 138404de4e0706b9e3dc4ba50b5ce296540745b3 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sat, 29 Aug 2020 14:21:32 +0300 Subject: [PATCH 021/492] Fix double parse with InvariantCulture --- .../Remote/HtmlTransport/HtmlTransport.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index 361d35c0b7..5946e2681c 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; @@ -280,8 +281,8 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport new InputProtocol.PointerReleasedEventMessage { Modifiers = ParseInputModifiers(parts[1]), - X = double.Parse(parts[2]), - Y = double.Parse(parts[3]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), Button = ParseMouseButton(parts[4]), }); break; @@ -293,8 +294,8 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport new InputProtocol.PointerPressedEventMessage { Modifiers = ParseInputModifiers(parts[1]), - X = double.Parse(parts[2]), - Y = double.Parse(parts[3]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), Button = ParseMouseButton(parts[4]), }); break; @@ -306,8 +307,8 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport new InputProtocol.PointerMovedEventMessage { Modifiers = ParseInputModifiers(parts[1]), - X = double.Parse(parts[2]), - Y = double.Parse(parts[3]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), }); break; } @@ -318,10 +319,10 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport new InputProtocol.ScrollEventMessage { Modifiers = ParseInputModifiers(parts[1]), - X = double.Parse(parts[2]), - Y = double.Parse(parts[3]), - DeltaX = double.Parse(parts[4]), - DeltaY = double.Parse(parts[5]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + DeltaX = ParseDouble(parts[4]), + DeltaY = ParseDouble(parts[5]), }); break; } @@ -334,13 +335,16 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport : modifiersText .Split(',') .Select(x => (InputProtocol.InputModifiers)Enum.Parse( - typeof(InputProtocol.InputModifiers), x)) + typeof(InputProtocol.InputModifiers), x, true)) .ToArray(); private static InputProtocol.MouseButton ParseMouseButton(string buttonText) => string.IsNullOrEmpty(buttonText) ? InputProtocol.MouseButton.None : (InputProtocol.MouseButton)Enum.Parse( - typeof(InputProtocol.MouseButton), buttonText); + typeof(InputProtocol.MouseButton), buttonText, true); + + private static double ParseDouble(string text) => + double.Parse(text, CultureInfo.InvariantCulture); } } From 26ae25d28dee5bd278da14c3a51800b8628d6037 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sat, 29 Aug 2020 14:35:05 +0300 Subject: [PATCH 022/492] Refactor parsing message --- .../Remote/HtmlTransport/HtmlTransport.cs | 88 ++++++++----------- 1 file changed, 37 insertions(+), 51 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index 5946e2681c..ded9ad6b11 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -115,11 +115,11 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport while (true) { var msg = await socket.ReceiveMessage().ConfigureAwait(false); - if(msg == null) - return; - if (msg.IsText) + if(msg != null && msg.IsText) { - ProcessingReceiveMessage(msg.AsString()); + var message = ParseMessage(msg.AsString()); + if (message != null) + _onMessage?.Invoke(this, message); } } } @@ -178,7 +178,6 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport _pendingSocket?.Dispose(); _simpleServer.Dispose(); } - public Task Send(object data) { @@ -262,70 +261,57 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport } #endregion - private void ProcessingReceiveMessage(string message) + private static object ParseMessage(string message) { var parts = message.Split(':'); switch (parts[0]) { case "frame-received": { - _onMessage?.Invoke( - this, - new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }); - break; + return new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }; } case "pointer-released": { - _onMessage?.Invoke( - this, - new InputProtocol.PointerReleasedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - Button = ParseMouseButton(parts[4]), - }); - break; + return new InputProtocol.PointerReleasedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + Button = ParseMouseButton(parts[4]), + }; } case "pointer-pressed": { - _onMessage?.Invoke( - this, - new InputProtocol.PointerPressedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - Button = ParseMouseButton(parts[4]), - }); - break; + return new InputProtocol.PointerPressedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + Button = ParseMouseButton(parts[4]), + }; } case "pointer-moved": { - _onMessage?.Invoke( - this, - new InputProtocol.PointerMovedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - }); - break; + return new InputProtocol.PointerMovedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + }; } case "scroll": { - _onMessage?.Invoke( - this, - new InputProtocol.ScrollEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - DeltaX = ParseDouble(parts[4]), - DeltaY = ParseDouble(parts[5]), - }); - break; + return new InputProtocol.ScrollEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + DeltaX = ParseDouble(parts[4]), + DeltaY = ParseDouble(parts[5]), + }; } + default: + return null; } } @@ -345,6 +331,6 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport typeof(InputProtocol.MouseButton), buttonText, true); private static double ParseDouble(string text) => - double.Parse(text, CultureInfo.InvariantCulture); + double.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture); } } From cf42bc3bd0899545149d947127a1fa0bef8c97d9 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 1 Sep 2020 09:12:44 +0300 Subject: [PATCH 023/492] Fix comparing strings with InvariantCultureIgnoreCase --- .../Remote/HtmlTransport/HtmlTransport.cs | 92 +++++++++---------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index ded9ad6b11..1d7fd91756 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -264,55 +264,53 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport private static object ParseMessage(string message) { var parts = message.Split(':'); - switch (parts[0]) + var key = parts[0]; + if (key.Equals("frame-received", StringComparison.InvariantCultureIgnoreCase)) { - case "frame-received": - { - return new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }; - } - case "pointer-released": - { - return new InputProtocol.PointerReleasedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - Button = ParseMouseButton(parts[4]), - }; - } - case "pointer-pressed": - { - return new InputProtocol.PointerPressedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - Button = ParseMouseButton(parts[4]), - }; - } - case "pointer-moved": - { - return new InputProtocol.PointerMovedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - }; - } - case "scroll": - { - return new InputProtocol.ScrollEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - DeltaX = ParseDouble(parts[4]), - DeltaY = ParseDouble(parts[5]), - }; - } - default: - return null; + return new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }; } + else if (key.Equals("pointer-released", StringComparison.InvariantCultureIgnoreCase)) + { + return new InputProtocol.PointerReleasedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + Button = ParseMouseButton(parts[4]), + }; + } + else if (key.Equals("pointer-pressed", StringComparison.InvariantCultureIgnoreCase)) + { + return new InputProtocol.PointerPressedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + Button = ParseMouseButton(parts[4]), + }; + } + else if (key.Equals("pointer-moved", StringComparison.InvariantCultureIgnoreCase)) + { + return new InputProtocol.PointerMovedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + }; + } + else if (key.Equals("scroll", StringComparison.InvariantCultureIgnoreCase)) + { + return new InputProtocol.ScrollEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + DeltaX = ParseDouble(parts[4]), + DeltaY = ParseDouble(parts[5]), + }; + } + + return null; } private static InputProtocol.InputModifiers[] ParseInputModifiers(string modifiersText) => From 4df6d86e0321f712177937ed5f7d82224f91c685 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 1 Sep 2020 09:24:23 +0300 Subject: [PATCH 024/492] Revert "Fix comparing strings with InvariantCultureIgnoreCase" This reverts commit cf42bc3bd0899545149d947127a1fa0bef8c97d9. --- .../Remote/HtmlTransport/HtmlTransport.cs | 92 ++++++++++--------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index 1d7fd91756..ded9ad6b11 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -264,53 +264,55 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport private static object ParseMessage(string message) { var parts = message.Split(':'); - var key = parts[0]; - if (key.Equals("frame-received", StringComparison.InvariantCultureIgnoreCase)) + switch (parts[0]) { - return new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }; - } - else if (key.Equals("pointer-released", StringComparison.InvariantCultureIgnoreCase)) - { - return new InputProtocol.PointerReleasedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - Button = ParseMouseButton(parts[4]), - }; - } - else if (key.Equals("pointer-pressed", StringComparison.InvariantCultureIgnoreCase)) - { - return new InputProtocol.PointerPressedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - Button = ParseMouseButton(parts[4]), - }; - } - else if (key.Equals("pointer-moved", StringComparison.InvariantCultureIgnoreCase)) - { - return new InputProtocol.PointerMovedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - }; - } - else if (key.Equals("scroll", StringComparison.InvariantCultureIgnoreCase)) - { - return new InputProtocol.ScrollEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - DeltaX = ParseDouble(parts[4]), - DeltaY = ParseDouble(parts[5]), - }; + case "frame-received": + { + return new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }; + } + case "pointer-released": + { + return new InputProtocol.PointerReleasedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + Button = ParseMouseButton(parts[4]), + }; + } + case "pointer-pressed": + { + return new InputProtocol.PointerPressedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + Button = ParseMouseButton(parts[4]), + }; + } + case "pointer-moved": + { + return new InputProtocol.PointerMovedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + }; + } + case "scroll": + { + return new InputProtocol.ScrollEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + DeltaX = ParseDouble(parts[4]), + DeltaY = ParseDouble(parts[5]), + }; + } + default: + return null; } - - return null; } private static InputProtocol.InputModifiers[] ParseInputModifiers(string modifiersText) => From c019f3a3f9ca84850aa24de75d111da7d87acf5a Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 1 Sep 2020 09:27:30 +0300 Subject: [PATCH 025/492] Fix API key to lower invariant --- .../Remote/HtmlTransport/HtmlTransport.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index ded9ad6b11..920f155cfd 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -264,7 +264,8 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport private static object ParseMessage(string message) { var parts = message.Split(':'); - switch (parts[0]) + var key = parts[0].ToLowerInvariant(); + switch (key) { case "frame-received": { From fe1a847847df1179201709db1a11d35e09abc54a Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 1 Sep 2020 09:42:50 +0300 Subject: [PATCH 026/492] Revert "Fix API key to lower invariant" This reverts commit c019f3a3f9ca84850aa24de75d111da7d87acf5a. --- .../Remote/HtmlTransport/HtmlTransport.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index 920f155cfd..ded9ad6b11 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -264,8 +264,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport private static object ParseMessage(string message) { var parts = message.Split(':'); - var key = parts[0].ToLowerInvariant(); - switch (key) + switch (parts[0]) { case "frame-received": { From 5f4f52f78d4075731f878526632a30ffbd664c68 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 1 Sep 2020 09:43:43 +0300 Subject: [PATCH 027/492] Fix comparing strings with InvariantCultureIgnoreCase --- .../Remote/HtmlTransport/HtmlTransport.cs | 92 +++++++++---------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index ded9ad6b11..1d7fd91756 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -264,55 +264,53 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport private static object ParseMessage(string message) { var parts = message.Split(':'); - switch (parts[0]) + var key = parts[0]; + if (key.Equals("frame-received", StringComparison.InvariantCultureIgnoreCase)) { - case "frame-received": - { - return new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }; - } - case "pointer-released": - { - return new InputProtocol.PointerReleasedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - Button = ParseMouseButton(parts[4]), - }; - } - case "pointer-pressed": - { - return new InputProtocol.PointerPressedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - Button = ParseMouseButton(parts[4]), - }; - } - case "pointer-moved": - { - return new InputProtocol.PointerMovedEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - }; - } - case "scroll": - { - return new InputProtocol.ScrollEventMessage - { - Modifiers = ParseInputModifiers(parts[1]), - X = ParseDouble(parts[2]), - Y = ParseDouble(parts[3]), - DeltaX = ParseDouble(parts[4]), - DeltaY = ParseDouble(parts[5]), - }; - } - default: - return null; + return new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }; } + else if (key.Equals("pointer-released", StringComparison.InvariantCultureIgnoreCase)) + { + return new InputProtocol.PointerReleasedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + Button = ParseMouseButton(parts[4]), + }; + } + else if (key.Equals("pointer-pressed", StringComparison.InvariantCultureIgnoreCase)) + { + return new InputProtocol.PointerPressedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + Button = ParseMouseButton(parts[4]), + }; + } + else if (key.Equals("pointer-moved", StringComparison.InvariantCultureIgnoreCase)) + { + return new InputProtocol.PointerMovedEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + }; + } + else if (key.Equals("scroll", StringComparison.InvariantCultureIgnoreCase)) + { + return new InputProtocol.ScrollEventMessage + { + Modifiers = ParseInputModifiers(parts[1]), + X = ParseDouble(parts[2]), + Y = ParseDouble(parts[3]), + DeltaX = ParseDouble(parts[4]), + DeltaY = ParseDouble(parts[5]), + }; + } + + return null; } private static InputProtocol.InputModifiers[] ParseInputModifiers(string modifiersText) => From bcf502e1ca158db751519a8c69819a8aa983030a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 03:41:52 -0700 Subject: [PATCH 028/492] texture offsets --- .../ControlCatalog.NetCore/Properties/launchSettings.json | 8 ++++++++ src/Avalonia.OpenGL/Angle/AngleEglInterface.cs | 8 ++++---- src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs | 4 ++-- src/Avalonia.OpenGL/EglConsts.cs | 4 ++++ src/Windows/Avalonia.Win32/Composition/CompositionHost.cs | 2 +- 5 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 samples/ControlCatalog.NetCore/Properties/launchSettings.json diff --git a/samples/ControlCatalog.NetCore/Properties/launchSettings.json b/samples/ControlCatalog.NetCore/Properties/launchSettings.json new file mode 100644 index 0000000000..cd8a2c824b --- /dev/null +++ b/samples/ControlCatalog.NetCore/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "ControlCatalog.NetCore": { + "commandName": "Project", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index 375b93c27c..ef0ddf65b6 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -7,8 +7,8 @@ namespace Avalonia.OpenGL.Angle { public class AngleEglInterface : EglInterface { - [DllImport("libegl.dll", CharSet = CharSet.Ansi)] - static extern IntPtr eglGetProcAddress(string proc); + [DllImport("libglesv2.dll", CharSet = CharSet.Ansi)] + static extern IntPtr EGL_GetProcAddress(string proc); public AngleEglInterface() : base(LoadAngle()) { @@ -20,10 +20,10 @@ namespace Avalonia.OpenGL.Angle if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new PlatformNotSupportedException(); { - var disp = eglGetProcAddress("eglGetPlatformDisplayEXT"); + var disp = EGL_GetProcAddress("eglGetPlatformDisplayEXT"); if (disp == IntPtr.Zero) throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point"); - return eglGetProcAddress; + return EGL_GetProcAddress; } } diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs index 1a42ed90c2..af10211f15 100644 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs @@ -78,11 +78,11 @@ namespace Avalonia.OpenGL.Angle return d3dDeviceHandle; } - public EglSurface WrapDirect3D11Texture(IntPtr handle) + public EglSurface WrapDirect3D11Texture(IntPtr handle, int offsetX, int offsetY) { if (PlatformApi != AngleOptions.PlatformApi.DirectX11) throw new InvalidOperationException("Current platform API is " + PlatformApi); - return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE }); + return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE, EGL_NONE }); } } } diff --git a/src/Avalonia.OpenGL/EglConsts.cs b/src/Avalonia.OpenGL/EglConsts.cs index 8e44004f2d..c644df97bf 100644 --- a/src/Avalonia.OpenGL/EglConsts.cs +++ b/src/Avalonia.OpenGL/EglConsts.cs @@ -205,5 +205,9 @@ namespace Avalonia.OpenGL public const int EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE = 0x3200; public const int EGL_D3D_TEXTURE_ANGLE = 0x33A3; + + + public const int EGL_TEXTURE_OFFSET_X_ANGLE = 0x3490; + public const int EGL_TEXTURE_OFFSET_Y_ANGLE = 0x3491; } } diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 1735b22035..afd5ff4e39 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -94,7 +94,7 @@ namespace Avalonia.Win32 ref iid, out IntPtr texture, ref offset); - var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture); + var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture, offset.X, offset.Y); return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); Marshal.Release(texture); surface.Dispose(); }, true); } From e6cf04d4e1eb6f2955cf495a65c125ceb391111d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 03:58:34 -0700 Subject: [PATCH 029/492] update egl properties for wrapped texture. --- src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs | 4 ++-- src/Avalonia.OpenGL/EglConsts.cs | 2 ++ src/Windows/Avalonia.Win32/Composition/CompositionHost.cs | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs index af10211f15..3b254d401a 100644 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs @@ -78,11 +78,11 @@ namespace Avalonia.OpenGL.Angle return d3dDeviceHandle; } - public EglSurface WrapDirect3D11Texture(IntPtr handle, int offsetX, int offsetY) + public EglSurface WrapDirect3D11Texture(IntPtr handle, int offsetX, int offsetY, int width, int height) { if (PlatformApi != AngleOptions.PlatformApi.DirectX11) throw new InvalidOperationException("Current platform API is " + PlatformApi); - return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE, EGL_NONE }); + return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE }); } } } diff --git a/src/Avalonia.OpenGL/EglConsts.cs b/src/Avalonia.OpenGL/EglConsts.cs index c644df97bf..5910e32ee1 100644 --- a/src/Avalonia.OpenGL/EglConsts.cs +++ b/src/Avalonia.OpenGL/EglConsts.cs @@ -209,5 +209,7 @@ namespace Avalonia.OpenGL public const int EGL_TEXTURE_OFFSET_X_ANGLE = 0x3490; public const int EGL_TEXTURE_OFFSET_Y_ANGLE = 0x3491; + + public const int EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE = 0x33A6; } } diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index afd5ff4e39..427655adf1 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -87,14 +87,14 @@ namespace Avalonia.Win32 if (lastSize.X != _info.Size.Width || lastSize.Y != _info.Size.Height) { lastSize = new POINT { X = _info.Size.Width, Y = _info.Size.Height }; - // _surfaceInterop.Resize(lastSize); + _surfaceInterop.Resize(lastSize); } _surfaceInterop.BeginDraw( ref updateRect, ref iid, out IntPtr texture, ref offset); - var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture, offset.X, offset.Y); + var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture, offset.X, offset.Y, _info.Size.Width, _info.Size.Height); return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); Marshal.Release(texture); surface.Dispose(); }, true); } From b6667530a25ae46916d11c4f76df83a3b0b7ba04 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 06:27:51 -0700 Subject: [PATCH 030/492] force ui thread rendering for now. --- src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs index 3b254d401a..c1f0f376b1 100644 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs @@ -82,7 +82,7 @@ namespace Avalonia.OpenGL.Angle { if (PlatformApi != AngleOptions.PlatformApi.DirectX11) throw new InvalidOperationException("Current platform API is " + PlatformApi); - return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE }); + return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE }); } } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index af6058d197..8840dcacef 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -88,7 +88,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) - .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new UiThreadRenderTimer(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToSingleton() From 3e96718d90e2c8bad28ab4110a4464e4e418ada9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 07:13:55 -0700 Subject: [PATCH 031/492] simplify hack to clear window. --- .../Composition/CompositionHost.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 427655adf1..b810ec45e0 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -64,18 +64,7 @@ namespace Avalonia.Win32 using (var target = windowSurface.CreateGlRenderTarget()) { - using (var session = target.BeginDraw()) - { - using (session.Context.MakeCurrent()) - { - var gl = _context.GlInterface; - gl.Viewport(0, 0, _info.Size.Width, _info.Size.Height); - gl.ClearStencil(0); - gl.ClearColor(0, 0, 0, 0); - gl.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_DEPTH_BUFFER_BIT); - gl.Flush(); - } - } + target.BeginDraw().Dispose(); } } @@ -83,12 +72,7 @@ namespace Avalonia.Win32 var iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); var updateRect = new RECT { right = _info.Size.Width, bottom = _info.Size.Height }; var offset = new POINT(); - - if (lastSize.X != _info.Size.Width || lastSize.Y != _info.Size.Height) - { - lastSize = new POINT { X = _info.Size.Width, Y = _info.Size.Height }; - _surfaceInterop.Resize(lastSize); - } + _surfaceInterop.BeginDraw( ref updateRect, ref iid, From 40f45115fbbe1e6a9dddc075ef5d926829e34768 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 07:14:28 -0700 Subject: [PATCH 032/492] remove unnecessary calls. --- src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 081db5d26a..ce4c6ecb90 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -72,10 +72,6 @@ namespace Avalonia.Skia $"Can't create drawing context for surface with {size} size and {scaling} scaling"); } - gl.Viewport(0, 0, size.Width, size.Height); - gl.ClearStencil(0); - gl.ClearColor(0, 0, 0, 0); - gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); lock (_grContext) { _grContext.ResetContext(); @@ -88,6 +84,7 @@ namespace Avalonia.Skia SKColorType.Rgba8888); success = true; + return new GlGpuSession(_grContext, renderTarget, surface, glSession); } } From 3deae8dbff8e4734e96b1f6aed2791f0eb989ee1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 07:32:53 -0700 Subject: [PATCH 033/492] no redirection bitmap --- .../Avalonia.Win32/Composition/CompositionHost.cs | 13 ------------- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index b810ec45e0..6d293a31eb 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -53,22 +53,9 @@ namespace Avalonia.Win32 public override bool IsCorrupted => _initialSize != _info.Size; - bool _firstRun = true; POINT lastSize; public override IGlPlatformSurfaceRenderingSession BeginDraw() { - if (_firstRun) - { - _firstRun = false; - var windowSurface = new EglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, _info); - - using (var target = windowSurface.CreateGlRenderTarget()) - { - target.BeginDraw().Dispose(); - } - } - - var iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); var updateRect = new RECT { right = _info.Size.Width, bottom = _info.Size.Height }; var offset = new POINT(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 25ae755990..3047f44a71 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -626,7 +626,7 @@ namespace Avalonia.Win32 protected virtual IntPtr CreateWindowOverride(ushort atom) { return CreateWindowEx( - 0, + (int)WindowStyles.WS_EX_NOREDIRECTIONBITMAP, // TODO this is only when using Win UI Comp. atom, null, (int)WindowStyles.WS_OVERLAPPEDWINDOW | (int) WindowStyles.WS_CLIPCHILDREN, From 2981cc470dc2258450c1b6dae80e405f1ab357a9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 08:46:12 -0700 Subject: [PATCH 034/492] use render thread. --- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 8840dcacef..af6058d197 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -88,7 +88,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) - .Bind().ToConstant(new UiThreadRenderTimer(60)) + .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToSingleton() From 62e2a2d2e64426ed69c0b97cf41ffbb6b386b7e6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 16:59:14 +0100 Subject: [PATCH 035/492] remove override directx api. --- samples/ControlCatalog.NetCore/Program.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 9fcf618614..142736a0bb 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -10,8 +10,6 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Headless; using Avalonia.LogicalTree; using Avalonia.Skia; -using Avalonia.Dialogs; -using Avalonia.OpenGL; using Avalonia.ReactiveUI; using Avalonia.Threading; using Avalonia.Dialogs; @@ -120,10 +118,6 @@ namespace ControlCatalog.NetCore EnableMultitouch = true, AllowEglInitialization = true }) - .With(new AngleOptions - { - AllowedPlatformApis = new System.Collections.Generic.List { AngleOptions.PlatformApi.DirectX11} - }) .UseSkia() .UseReactiveUI() .UseManagedSystemDialogs() From 758ddafad888f0791a6a0cd256b11ca97ac24971 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 17:02:38 +0100 Subject: [PATCH 036/492] restore file. --- src/Avalonia.OpenGL/Angle/AngleEglInterface.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index ef0ddf65b6..375b93c27c 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -7,8 +7,8 @@ namespace Avalonia.OpenGL.Angle { public class AngleEglInterface : EglInterface { - [DllImport("libglesv2.dll", CharSet = CharSet.Ansi)] - static extern IntPtr EGL_GetProcAddress(string proc); + [DllImport("libegl.dll", CharSet = CharSet.Ansi)] + static extern IntPtr eglGetProcAddress(string proc); public AngleEglInterface() : base(LoadAngle()) { @@ -20,10 +20,10 @@ namespace Avalonia.OpenGL.Angle if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new PlatformNotSupportedException(); { - var disp = EGL_GetProcAddress("eglGetPlatformDisplayEXT"); + var disp = eglGetProcAddress("eglGetPlatformDisplayEXT"); if (disp == IntPtr.Zero) throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point"); - return EGL_GetProcAddress; + return eglGetProcAddress; } } From e68ce5643e815210d0b8f8b622eed3aae30e74dd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 09:05:24 -0700 Subject: [PATCH 037/492] Revert "restore file." This reverts commit 758ddafad888f0791a6a0cd256b11ca97ac24971. --- src/Avalonia.OpenGL/Angle/AngleEglInterface.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index 375b93c27c..ef0ddf65b6 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -7,8 +7,8 @@ namespace Avalonia.OpenGL.Angle { public class AngleEglInterface : EglInterface { - [DllImport("libegl.dll", CharSet = CharSet.Ansi)] - static extern IntPtr eglGetProcAddress(string proc); + [DllImport("libglesv2.dll", CharSet = CharSet.Ansi)] + static extern IntPtr EGL_GetProcAddress(string proc); public AngleEglInterface() : base(LoadAngle()) { @@ -20,10 +20,10 @@ namespace Avalonia.OpenGL.Angle if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new PlatformNotSupportedException(); { - var disp = eglGetProcAddress("eglGetPlatformDisplayEXT"); + var disp = EGL_GetProcAddress("eglGetPlatformDisplayEXT"); if (disp == IntPtr.Zero) throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point"); - return eglGetProcAddress; + return EGL_GetProcAddress; } } From f068fa02e989e55ac3d24c944a0aa66837480dad Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Sep 2020 09:14:37 -0700 Subject: [PATCH 038/492] restore render timer. --- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index af6058d197..8840dcacef 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -88,7 +88,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) - .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new UiThreadRenderTimer(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToSingleton() From 1025b1240247793e19415e797100882952dc4043 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sat, 12 Sep 2020 00:27:05 +0500 Subject: [PATCH 039/492] Add tests --- .editorconfig | 3 + .../src/Models/Input/InputEventMessageBase.ts | 4 +- .../src/Models/Input/MouseEventHelpers.ts | 18 +- .../Models/Input/PointerEventMessageBase.ts | 2 +- .../Models/Input/PointerMovedEventMessage.ts | 2 +- .../Input/PointerPressedEventMessage.ts | 6 +- .../Input/PointerReleasedEventMessage.ts | 6 +- .../src/Models/Input/ScrollEventMessage.ts | 2 +- .../Remote/HtmlTransport/webapp/.gitignore | 2 + .../webapp/Models/InputEventTests.ts | 73 + .../HtmlTransport/webapp/package-lock.json | 2414 +++++++++++++++++ .../Remote/HtmlTransport/webapp/package.json | 26 + .../Remote/HtmlTransport/webapp/tsconfig.json | 12 + 13 files changed, 2551 insertions(+), 19 deletions(-) create mode 100644 tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore create mode 100644 tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts create mode 100644 tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json create mode 100644 tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json create mode 100644 tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/tsconfig.json diff --git a/.editorconfig b/.editorconfig index f6bce9cb76..c7a381b730 100644 --- a/.editorconfig +++ b/.editorconfig @@ -156,6 +156,9 @@ indent_size = 2 [*.{props,targets,config,nuspec}] indent_size = 2 +[*.json] +indent_size = 2 + # Shell scripts [*.sh] end_of_line = lf diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts index 2da7424e2a..bfe5aad093 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputEventMessageBase.ts @@ -1,6 +1,6 @@ import * as React from "react"; -import {InputModifiers} from "src/Models/Input/InputModifiers"; -import {getModifiers} from "src/Models/Input/MouseEventHelpers"; +import {InputModifiers} from "./InputModifiers"; +import {getModifiers} from "./MouseEventHelpers"; export abstract class InputEventMessageBase { public readonly modifiers : Array; diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts index 3f85959d6f..f2cd1f4d46 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers.ts @@ -1,6 +1,6 @@ import * as React from "react"; -import {InputModifiers} from "src/Models/Input/InputModifiers"; -import {MouseButton} from "src/Models/Input/MouseButton"; +import {InputModifiers} from "./InputModifiers"; +import {MouseButton} from "./MouseButton"; export function getModifiers(e: React.MouseEvent): Array { @@ -14,12 +14,14 @@ export function getModifiers(e: React.MouseEvent): Array { modifiers.push(InputModifiers.Shift); if (e.metaKey) modifiers.push(InputModifiers.Windows); - if ((e.buttons & 1) != 0) - modifiers.push(InputModifiers.LeftMouseButton); - if ((e.buttons & 2) != 0) - modifiers.push(InputModifiers.RightMouseButton); - if ((e.buttons & 4) != 0) - modifiers.push(InputModifiers.MiddleMouseButton); + if (e.buttons != 0) { + if ((e.buttons & 1) != 0) + modifiers.push(InputModifiers.LeftMouseButton); + if ((e.buttons & 2) != 0) + modifiers.push(InputModifiers.RightMouseButton); + if ((e.buttons & 4) != 0) + modifiers.push(InputModifiers.MiddleMouseButton); + } return modifiers; } diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts index fc304c2965..c9de132ff4 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerEventMessageBase.ts @@ -1,5 +1,5 @@ import * as React from "react"; -import {InputEventMessageBase} from "src/Models/Input/InputEventMessageBase"; +import {InputEventMessageBase} from "./InputEventMessageBase"; export abstract class PointerEventMessageBase extends InputEventMessageBase { public readonly x: number; diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts index cd135dfa39..3f782d1d46 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage.ts @@ -1,5 +1,5 @@ import * as React from "react"; -import {PointerEventMessageBase} from "src/Models/Input/PointerEventMessageBase"; +import {PointerEventMessageBase} from "./PointerEventMessageBase"; export class PointerMovedEventMessage extends PointerEventMessageBase { constructor(e: React.MouseEvent) { diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts index e75551e0b3..0ae06cc064 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage.ts @@ -1,7 +1,7 @@ import * as React from "react"; -import {PointerEventMessageBase} from "src/Models/Input/PointerEventMessageBase"; -import {MouseButton} from "src/Models/Input/MouseButton"; -import {getMouseButton} from "src/Models/Input/MouseEventHelpers"; +import {PointerEventMessageBase} from "./PointerEventMessageBase"; +import {MouseButton} from "./MouseButton"; +import {getMouseButton} from "./MouseEventHelpers"; export class PointerPressedEventMessage extends PointerEventMessageBase { public readonly button: MouseButton diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts index c2bc368323..fb1477b554 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage.ts @@ -1,7 +1,7 @@ import * as React from "react"; -import {PointerEventMessageBase} from "src/Models/Input/PointerEventMessageBase"; -import {MouseButton} from "src/Models/Input/MouseButton"; -import {getMouseButton} from "src/Models/Input/MouseEventHelpers"; +import {PointerEventMessageBase} from "./PointerEventMessageBase"; +import {MouseButton} from "./MouseButton"; +import {getMouseButton} from "./MouseEventHelpers"; export class PointerReleasedEventMessage extends PointerEventMessageBase { public readonly button: MouseButton diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts index 427ccc5e5e..b11be19d82 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage.ts @@ -1,5 +1,5 @@ import * as React from "react"; -import {PointerEventMessageBase} from "src/Models/Input/PointerEventMessageBase"; +import {PointerEventMessageBase} from "./PointerEventMessageBase"; export class ScrollEventMessage extends PointerEventMessageBase { public readonly deltaX: number; diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore new file mode 100644 index 0000000000..e3fbd98336 --- /dev/null +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore @@ -0,0 +1,2 @@ +build +node_modules diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts new file mode 100644 index 0000000000..061bb74be7 --- /dev/null +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts @@ -0,0 +1,73 @@ +import {describe} from 'mocha'; +import {expect} from 'chai'; +import {Mock} from "moq.ts"; +import {MouseEvent, WheelEvent} from "react"; +import {InputModifiers} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers"; +import {MouseButton} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton"; +import {PointerMovedEventMessage} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage"; +import {PointerPressedEventMessage} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage"; +import {PointerReleasedEventMessage} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage"; +import {ScrollEventMessage} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage"; + +const x = .3 +const y = .42 +const modifiers = [ + InputModifiers.Alt, + InputModifiers.Control, + InputModifiers.Shift, + InputModifiers.Windows, + InputModifiers.LeftMouseButton, + InputModifiers.RightMouseButton, + InputModifiers.MiddleMouseButton +] + +const button = MouseButton.Left + +const deltaX = -3. +const deltaY = -3. + +const mouseEvent = new Mock() + .setup(x => x.altKey).returns(true) + .setup(x => x.ctrlKey).returns(true) + .setup(x => x.shiftKey).returns(true) + .setup(x => x.metaKey).returns(true) + .setup(x => x.buttons).returns(7) + .setup(x => x.button).returns(0) + .setup(x => x.clientX).returns(x) + .setup(x => x.clientY).returns(y) + .object() + +const wheelEvent = new Mock() + .setup(x => x.altKey).returns(true) + .setup(x => x.ctrlKey).returns(true) + .setup(x => x.shiftKey).returns(true) + .setup(x => x.metaKey).returns(true) + .setup(x => x.buttons).returns(7) + .setup(x => x.clientX).returns(x) + .setup(x => x.clientY).returns(y) + .setup(x => x.deltaX).returns(-deltaX) + .setup(x => x.deltaY).returns(-deltaY) + .object() + +describe("Input event tests", () => { + it("PointerMovedEventMessage", () => { + const message = new PointerMovedEventMessage(mouseEvent) + expect(message.toString()) + .equal(`pointer-moved:${modifiers}:${x}:${y}`) + }) + it("PointerPressedEventMessage", () => { + const message = new PointerPressedEventMessage(mouseEvent) + expect(message.toString()) + .equal(`pointer-pressed:${modifiers}:${x}:${y}:${button}`) + }) + it("PointerReleasedEventMessage", () => { + const message = new PointerReleasedEventMessage(mouseEvent) + expect(message.toString()) + .equal(`pointer-released:${modifiers}:${x}:${y}:${button}`) + }) + it("ScrollEventMessage", () => { + const message = new ScrollEventMessage(wheelEvent) + expect(message.toString()) + .equal(`scroll:${modifiers}:${x}:${y}:${deltaX}:${deltaY}`) + }) +}) diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json new file mode 100644 index 0000000000..eb707cb73f --- /dev/null +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json @@ -0,0 +1,2414 @@ +{ + "name": "simple-test", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/core": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.6", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, + "@types/chai": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", + "integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ==", + "dev": true + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true + }, + "@types/react": { + "version": "16.9.49", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.49.tgz", + "integrity": "sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "csstype": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", + "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, + "fromentries": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.1.tgz", + "integrity": "sha512-Xu2Qh8yqYuDhQGOhD5iJGninErSfI9A3FrriD3tjUgV5VbJFeH8vfgZ9HnC6jWN80QDVNQK5vmxRAmEAp7Mevw==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "hasha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", + "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", + "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", + "dev": true + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dev": true, + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mocha": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.3.tgz", + "integrity": "sha512-ZbaYib4hT4PpF4bdSO2DohooKXIn4lDeiYqB+vTmCdr6l2woW0b6H3pf5x4sM5nwQMru9RvjjHYWVGltR50ZBw==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.2", + "debug": "4.1.1", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "object.assign": "4.1.0", + "promise.allsettled": "1.0.2", + "serialize-javascript": "4.0.0", + "strip-json-comments": "3.0.1", + "supports-color": "7.1.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.0", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.1" + } + }, + "moq.ts": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/moq.ts/-/moq.ts-6.4.0.tgz", + "integrity": "sha512-g9siyKOvYGVhfDCl9mpLptch93egRC3yXIbPLssxukOROl368XBB+6M7YjLTkjVRHbKbpJBD6P7od+Ky/0xWIw==", + "dev": true, + "requires": { + "tslib": "^2.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "dev": true, + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", + "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", + "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "decamelize": "^1.2.0", + "flat": "^4.1.0", + "is-plain-obj": "^1.1.0", + "yargs": "^14.2.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "yargs": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + } + }, + "yargs-parser": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", + "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json new file mode 100644 index 0000000000..dbb12e192a --- /dev/null +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json @@ -0,0 +1,26 @@ +{ + "name": "simple-test", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "mocha -r ts-node/register ./**/*Tests.ts", + "coverage": "nyc -r text -e .ts -x \"./*Tests.ts\" npm run test" + }, + "type": "module", + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": {}, + "devDependencies": { + "@types/chai": "4.2.12", + "@types/mocha": "8.0.3", + "@types/react": "^16.3.14", + "chai": "^4.2.0", + "mocha": "^8.1.3", + "moq.ts": "^6.4.0", + "nyc": "^15.1.0", + "react": "^16.3.2", + "ts-node": "^9.0.0", + "typescript": "^4.0.2" + } +} diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/tsconfig.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/tsconfig.json new file mode 100644 index 0000000000..fdb82879fc --- /dev/null +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "sourceMap": true, + "strict": true, + "esModuleInterop": true + }, + "exclude": [ + "node_modules" + ] +} From a52464ebb9b31c1e03e89b0c39705f5c942e61d7 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sat, 12 Sep 2020 20:19:58 +0500 Subject: [PATCH 040/492] Add html previewer tests to nuke --- build.ps1 | 4 ++-- nukebuild/Build.cs | 16 ++++++++++++++++ nukebuild/BuildParameters.cs | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/build.ps1 b/build.ps1 index 57e2f80075..98a9c9afee 100644 --- a/build.ps1 +++ b/build.ps1 @@ -43,7 +43,7 @@ if (Test-Path $DotNetGlobalFile) { } # If dotnet is installed locally, and expected version is not set or installation matches the expected version -if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -ne $null -and ` +if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` (!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) { $env:DOTNET_EXE = (Get-Command "dotnet").Path } @@ -53,7 +53,7 @@ else { # Download install script $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" - md -force $TempDirectory > $null + mkdir -force $TempDirectory > $null (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) # Install by channel or version diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 24398accbb..c56703f9cc 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -223,6 +223,21 @@ partial class Build : NukeBuild } } + Target RunHtmlPreviewerTests => _ => _ + .DependsOn(CompileHtmlPreviewer) + .OnlyWhenStatic(() => !(Parameters.SkipPreviewer || Parameters.SkipTests)) + .Executes(() => + { + var webappTestDir = RootDirectory / "tests" / "Avalonia.DesignerSupport.Tests" / "Remote" / "HtmlTransport" / "webapp"; + + NpmTasks.NpmInstall(c => c + .SetWorkingDirectory(webappTestDir) + .SetArgumentConfigurator(a => a.Add("--silent"))); + NpmTasks.NpmRun(c => c + .SetWorkingDirectory(webappTestDir) + .SetCommand("test")); + }); + Target RunCoreLibsTests => _ => _ .OnlyWhenStatic(() => !Parameters.SkipTests) .DependsOn(Compile) @@ -323,6 +338,7 @@ partial class Build : NukeBuild .DependsOn(RunCoreLibsTests) .DependsOn(RunRenderTests) .DependsOn(RunDesignerTests) + .DependsOn(RunHtmlPreviewerTests) .DependsOn(RunLeakTests); Target Package => _ => _ diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs index a167e9d892..c76019d9eb 100644 --- a/nukebuild/BuildParameters.cs +++ b/nukebuild/BuildParameters.cs @@ -62,7 +62,7 @@ public partial class Build public AbsolutePath ZipTargetControlCatalogDesktopDir { get; } - public BuildParameters(Build b) + public BuildParameters(Build b) { // ARGUMENTS Configuration = b.Configuration ?? "Release"; From 8fd50dd15aa569b2f41583a64c690d0233d3d237 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Sat, 12 Sep 2020 20:49:27 +0500 Subject: [PATCH 041/492] Add some tests --- .../Remote/HtmlTransport/webapp/.gitignore | 1 + .../webapp/Models/InputEventTests.ts | 151 ++++++++++-------- 2 files changed, 87 insertions(+), 65 deletions(-) diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore index e3fbd98336..dc33b94747 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/.gitignore @@ -1,2 +1,3 @@ build node_modules +.nyc_output \ No newline at end of file diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts index 061bb74be7..e96c4d4731 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/Models/InputEventTests.ts @@ -1,73 +1,94 @@ -import {describe} from 'mocha'; -import {expect} from 'chai'; -import {Mock} from "moq.ts"; -import {MouseEvent, WheelEvent} from "react"; -import {InputModifiers} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers"; -import {MouseButton} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton"; -import {PointerMovedEventMessage} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage"; -import {PointerPressedEventMessage} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage"; -import {PointerReleasedEventMessage} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage"; -import {ScrollEventMessage} from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage"; +import { describe } from 'mocha'; +import { expect } from 'chai'; +import { Mock } from "moq.ts"; +import { MouseEvent, WheelEvent } from "react"; +import { InputModifiers } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/InputModifiers"; +import { MouseButton } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseButton"; +import { PointerMovedEventMessage } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerMovedEventMessage"; +import { PointerPressedEventMessage } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerPressedEventMessage"; +import { PointerReleasedEventMessage } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/PointerReleasedEventMessage"; +import { ScrollEventMessage } from "../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/ScrollEventMessage"; +import { getModifiers, getMouseButton } from '../../../../../../src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/Models/Input/MouseEventHelpers'; -const x = .3 -const y = .42 -const modifiers = [ - InputModifiers.Alt, - InputModifiers.Control, - InputModifiers.Shift, - InputModifiers.Windows, - InputModifiers.LeftMouseButton, - InputModifiers.RightMouseButton, - InputModifiers.MiddleMouseButton -] +describe("Input event tests", () => { + describe("Helpers", () => { + it("getModifiers", () => { + const event = new Mock() + .setup(x => x.altKey).returns(false) + .setup(x => x.ctrlKey).returns(true) + .setup(x => x.shiftKey).returns(false) + .setup(x => x.metaKey).returns(false) + .setup(x => x.buttons).returns(1) + .object() + var actual = getModifiers(event) -const button = MouseButton.Left + expect(actual) + .eql([InputModifiers.Control, InputModifiers.LeftMouseButton]) + }) + it("getMouseButton", () => { + const event = new Mock() + .setup(x => x.button).returns(1) + .object() + var actual = getMouseButton(event) -const deltaX = -3. -const deltaY = -3. + expect(actual) + .equal(MouseButton.Middle) + }) + }) -const mouseEvent = new Mock() - .setup(x => x.altKey).returns(true) - .setup(x => x.ctrlKey).returns(true) - .setup(x => x.shiftKey).returns(true) - .setup(x => x.metaKey).returns(true) - .setup(x => x.buttons).returns(7) - .setup(x => x.button).returns(0) - .setup(x => x.clientX).returns(x) - .setup(x => x.clientY).returns(y) - .object() + describe("Messages", () => { + const x = .3 + const y = .42 + const modifiers = "0,1,2,3,4,5,6" -const wheelEvent = new Mock() - .setup(x => x.altKey).returns(true) - .setup(x => x.ctrlKey).returns(true) - .setup(x => x.shiftKey).returns(true) - .setup(x => x.metaKey).returns(true) - .setup(x => x.buttons).returns(7) - .setup(x => x.clientX).returns(x) - .setup(x => x.clientY).returns(y) - .setup(x => x.deltaX).returns(-deltaX) - .setup(x => x.deltaY).returns(-deltaY) - .object() + const button = "1" -describe("Input event tests", () => { - it("PointerMovedEventMessage", () => { - const message = new PointerMovedEventMessage(mouseEvent) - expect(message.toString()) - .equal(`pointer-moved:${modifiers}:${x}:${y}`) - }) - it("PointerPressedEventMessage", () => { - const message = new PointerPressedEventMessage(mouseEvent) - expect(message.toString()) - .equal(`pointer-pressed:${modifiers}:${x}:${y}:${button}`) - }) - it("PointerReleasedEventMessage", () => { - const message = new PointerReleasedEventMessage(mouseEvent) - expect(message.toString()) - .equal(`pointer-released:${modifiers}:${x}:${y}:${button}`) - }) - it("ScrollEventMessage", () => { - const message = new ScrollEventMessage(wheelEvent) - expect(message.toString()) - .equal(`scroll:${modifiers}:${x}:${y}:${deltaX}:${deltaY}`) + const deltaX = -3. + const deltaY = -3. + + const mouseEvent = new Mock() + .setup(x => x.altKey).returns(true) + .setup(x => x.ctrlKey).returns(true) + .setup(x => x.shiftKey).returns(true) + .setup(x => x.metaKey).returns(true) + .setup(x => x.buttons).returns(7) + .setup(x => x.button).returns(0) + .setup(x => x.clientX).returns(x) + .setup(x => x.clientY).returns(y) + .object() + + it("PointerMovedEventMessage", () => { + const message = new PointerMovedEventMessage(mouseEvent) + expect(message.toString()) + .equal(`pointer-moved:${modifiers}:${x}:${y}`) + }) + it("PointerPressedEventMessage", () => { + const message = new PointerPressedEventMessage(mouseEvent) + expect(message.toString()) + .equal(`pointer-pressed:${modifiers}:${x}:${y}:${button}`) + }) + it("PointerReleasedEventMessage", () => { + const message = new PointerReleasedEventMessage(mouseEvent) + expect(message.toString()) + .equal(`pointer-released:${modifiers}:${x}:${y}:${button}`) + }) + + it("ScrollEventMessage", () => { + const wheelEvent = new Mock() + .setup(x => x.altKey).returns(true) + .setup(x => x.ctrlKey).returns(true) + .setup(x => x.shiftKey).returns(true) + .setup(x => x.metaKey).returns(true) + .setup(x => x.buttons).returns(7) + .setup(x => x.clientX).returns(x) + .setup(x => x.clientY).returns(y) + .setup(x => x.deltaX).returns(-deltaX) + .setup(x => x.deltaY).returns(-deltaY) + .object() + const message = new ScrollEventMessage(wheelEvent) + + expect(message.toString()) + .equal(`scroll:${modifiers}:${x}:${y}:${deltaX}:${deltaY}`) + }) }) }) From 0de7f4d7e0c5141060038d4a7f1f55ab36c10e51 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 12 Sep 2020 11:24:21 -0700 Subject: [PATCH 042/492] pass egl_width / height. --- src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs index c1f0f376b1..3b254d401a 100644 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs @@ -82,7 +82,7 @@ namespace Avalonia.OpenGL.Angle { if (PlatformApi != AngleOptions.PlatformApi.DirectX11) throw new InvalidOperationException("Current platform API is " + PlatformApi); - return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE }); + return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE }); } } } From 75025a15c195cf91197674f78a368e9ee6ac3da0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 12 Sep 2020 11:46:32 -0700 Subject: [PATCH 043/492] avalonia never resizes so use intptr for update rect. --- .../Avalonia.Win32/Composition/CompositionHost.cs | 7 +++---- .../Composition/ICompositionDrawingSurfaceInterop.cs | 12 ++++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 6d293a31eb..5d13109dda 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -39,6 +39,7 @@ namespace Avalonia.Win32 private readonly IEglWindowGlPlatformSurfaceInfo _info; private PixelSize _initialSize; private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; + private static Guid s_Iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); public CompositionRenderTarget(EglDisplay display, EglContext context, ICompositionDrawingSurfaceInterop interopSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context) { @@ -56,13 +57,11 @@ namespace Avalonia.Win32 POINT lastSize; public override IGlPlatformSurfaceRenderingSession BeginDraw() { - var iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); - var updateRect = new RECT { right = _info.Size.Width, bottom = _info.Size.Height }; var offset = new POINT(); _surfaceInterop.BeginDraw( - ref updateRect, - ref iid, + IntPtr.Zero, + ref s_Iid, out IntPtr texture, ref offset); var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture, offset.X, offset.Y, _info.Size.Width, _info.Size.Height); diff --git a/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs b/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs index 8fea5f293b..2eac796376 100644 --- a/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs +++ b/src/Windows/Avalonia.Win32/Composition/ICompositionDrawingSurfaceInterop.cs @@ -25,7 +25,7 @@ namespace Windows.UI.Composition.Interop [Guid("FD04E6E3-FE0C-4C3C-AB19-A07601A576EE")] public interface ICompositionDrawingSurfaceInterop { - void BeginDraw(ref RECT updateRect, ref Guid iid, out IntPtr updateObject, ref POINT point); + void BeginDraw(IntPtr updateRect, ref Guid iid, out IntPtr updateObject, ref POINT point); void EndDraw(); @@ -51,9 +51,9 @@ namespace ABI.Windows.UI.Composition.Interop { [Guid("FD04E6E3-FE0C-4C3C-AB19-A07601A576EE")] - public struct Vftbl + public unsafe struct Vftbl { - public delegate int _BeginDraw(IntPtr ThisPtr, ref RECT updateRect, ref Guid iid, out IntPtr updateObject, ref POINT updateOffset); + public delegate int _BeginDraw(IntPtr ThisPtr, IntPtr updateRect, ref Guid iid, out IntPtr updateObject, ref POINT updateOffset); public delegate int _EndDraw(IntPtr ThisPtr); public delegate int _Resize(IntPtr ThisPtr, POINT sizePixels); public delegate int _ResumeDraw(IntPtr ThisPtr); @@ -87,7 +87,7 @@ namespace ABI.Windows.UI.Composition.Interop Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false); } - private static int Do_Abi_BeginDraw(IntPtr ThisPtr, ref RECT updateRect, ref Guid iid, out IntPtr updateObject, ref POINT updateOffset) + private static int Do_Abi_BeginDraw(IntPtr ThisPtr, IntPtr updateRect, ref Guid iid, out IntPtr updateObject, ref POINT updateOffset) { updateObject = IntPtr.Zero; return 0; @@ -118,9 +118,9 @@ namespace ABI.Windows.UI.Composition.Interop _obj = obj; } - public void BeginDraw(ref RECT updateRect, ref Guid iid, out IntPtr updateObject, ref POINT point) + public void BeginDraw(IntPtr updateRect, ref Guid iid, out IntPtr updateObject, ref POINT point) { - Marshal.ThrowExceptionForHR(_obj.Vftbl.BeginDraw(ThisPtr, ref updateRect, ref iid, out updateObject, ref point)); + Marshal.ThrowExceptionForHR(_obj.Vftbl.BeginDraw(ThisPtr, updateRect, ref iid, out updateObject, ref point)); } public void EndDraw() From 688ba15b6a3c6b1010bb558cf8b224de589936b7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 01:53:54 -0700 Subject: [PATCH 044/492] seperate classes. --- .../CompositionEglGlPlatformSurface.cs | 75 +++++++++++++++++ .../Composition/CompositionHost.cs | 83 ++----------------- 2 files changed, 84 insertions(+), 74 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs new file mode 100644 index 0000000000..c5e99a4b5d --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -0,0 +1,75 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Angle; +using Windows.UI.Composition.Interop; + +namespace Avalonia.Win32 +{ + public class CompositionEglGlPlatformSurface : EglGlPlatformSurfaceBase + { + private readonly EglDisplay _display; + private readonly EglContext _context; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + private ICompositionDrawingSurfaceInterop _surfaceInterop; + private Windows.UI.Composition.Visual _surface; + + public CompositionEglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base() + { + _display = context.Display; + _context = context; + _info = info; + } + + public void AttachToCompositionTree(IntPtr hwnd) + { + _surfaceInterop = CompositionHost.Instance.InitialiseWindowCompositionTree(hwnd, out _surface); + } + + public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + { + return new CompositionRenderTarget(_display, _context, _surface, _surfaceInterop, _info); + } + + class CompositionRenderTarget : EglPlatformSurfaceRenderTargetBase + { + private readonly EglDisplay _display; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + private PixelSize _initialSize; + private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; + private static Guid s_Iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); + private Windows.UI.Composition.Visual _compositionVisual; + + public CompositionRenderTarget(EglDisplay display, + EglContext context, + Windows.UI.Composition.Visual compositionVisual, + ICompositionDrawingSurfaceInterop interopSurface, + IEglWindowGlPlatformSurfaceInfo info) + : base(display, context) + { + _display = display; + _surfaceInterop = interopSurface; + _info = info; + _initialSize = info.Size; + _compositionVisual = compositionVisual; + _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); + } + + public override bool IsCorrupted => _initialSize != _info.Size; + public override IGlPlatformSurfaceRenderingSession BeginDraw() + { + var offset = new POINT(); + + _surfaceInterop.BeginDraw( + IntPtr.Zero, + ref s_Iid, + out IntPtr texture, ref offset); + + var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture, offset.X, offset.Y, _info.Size.Width, _info.Size.Height); + + return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); Marshal.Release(texture); surface.Dispose(); _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); }, true); + } + } + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 5d13109dda..48656e1816 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.InteropServices; -using Avalonia.OpenGL; using Avalonia.OpenGL.Angle; using Windows.UI.Composition; using Windows.UI.Composition.Interop; @@ -8,71 +7,6 @@ using WinRT; namespace Avalonia.Win32 { - public class CompositionEglGlPlatformSurface : EglGlPlatformSurfaceBase - { - private readonly EglDisplay _display; - private readonly EglContext _context; - private readonly IEglWindowGlPlatformSurfaceInfo _info; - private ICompositionDrawingSurfaceInterop _surfaceInterop; - - public CompositionEglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base() - { - _display = context.Display; - _context = context; - _info = info; - } - - public void AttachToCompositionTree(IntPtr hwnd) - { - _surfaceInterop = CompositionHost.Instance.InitialiseWindowCompositionTree(hwnd); - } - - public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() - { - return new CompositionRenderTarget(_display, _context, _surfaceInterop, _info); - } - - class CompositionRenderTarget : EglPlatformSurfaceRenderTargetBase - { - private readonly EglDisplay _display; - private readonly EglContext _context; - private readonly IEglWindowGlPlatformSurfaceInfo _info; - private PixelSize _initialSize; - private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; - private static Guid s_Iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); - - public CompositionRenderTarget(EglDisplay display, EglContext context, ICompositionDrawingSurfaceInterop interopSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context) - { - _display = display; - _context = context; - _surfaceInterop = interopSurface; - _info = info; - _initialSize = info.Size; - lastSize = new POINT { X = _info.Size.Width, Y = _info.Size.Height }; - _surfaceInterop.Resize(lastSize); - } - - public override bool IsCorrupted => _initialSize != _info.Size; - - POINT lastSize; - public override IGlPlatformSurfaceRenderingSession BeginDraw() - { - var offset = new POINT(); - - _surfaceInterop.BeginDraw( - IntPtr.Zero, - ref s_Iid, - out IntPtr texture, ref offset); - - var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture, offset.X, offset.Y, _info.Size.Width, _info.Size.Height); - - return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); Marshal.Release(texture); surface.Dispose(); }, true); - } - } - } - - - class CompositionHost { internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE @@ -155,7 +89,7 @@ namespace Avalonia.Win32 _graphicsDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); } - public ICompositionDrawingSurfaceInterop InitialiseWindowCompositionTree(IntPtr hwnd) + public ICompositionDrawingSurfaceInterop InitialiseWindowCompositionTree(IntPtr hwnd, out Windows.UI.Composition.Visual surfaceVisual) { var target = CreateDesktopWindowTarget(hwnd); @@ -163,22 +97,23 @@ namespace Avalonia.Win32 Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); - var surfaceInterop = surface.As(); + var surfaceInterop = surface.As(); var brush = _compositor.CreateSurfaceBrush(surface); - var visual = _compositor.CreateSpriteVisual(); - - visual.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); + visual.Brush = brush; - //_target.Root = visual; - + visual.Offset = new System.Numerics.Vector3(0, 0, 0); target.Root = CreateBlur(); var visuals = target.Root.As().Children; visuals.InsertAtTop(visual); + visual.CompositeMode = CompositionCompositeMode.SourceOver; + + surfaceVisual = visual; + return surfaceInterop; } @@ -204,7 +139,7 @@ namespace Avalonia.Win32 { var interop = _compositor.As(); - interop.CreateDesktopWindowTarget(window, false, out var windowTarget); + interop.CreateDesktopWindowTarget(window, true, out var windowTarget); return Windows.UI.Composition.Desktop.DesktopWindowTarget.FromAbi(windowTarget); } From b0bc3f4ea8fd47237f946ad72e59da4d399a1568 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 01:56:40 -0700 Subject: [PATCH 045/492] check for windows 10 to decide to use windows.ui.comp tree to render into. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3047f44a71..d964e490c0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -104,17 +104,17 @@ namespace Avalonia.Win32 _framebuffer = new FramebufferManager(_hwnd); if (Win32GlManager.EglFeature != null) - { - _gl = new CompositionEglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); - } - - if (_gl is CompositionEglGlPlatformSurface cgl) - { - cgl.AttachToCompositionTree(_hwnd); - } - else { - CompositionHost.Instance.InitialiseWindowCompositionTree(_hwnd); + if (Win32Platform.WindowsVersion.Major >= 10) + { + var cgl = new CompositionEglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); + cgl.AttachToCompositionTree(_hwnd); + _gl = cgl; + } + else + { + _gl = new EglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); + } } Screen = new ScreenImpl(); From 8913cea89ec85f4b4a24ea82c32ac70cbf603198 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 01:57:00 -0700 Subject: [PATCH 046/492] code tidy. --- .../CompositionEglGlPlatformSurface.cs | 15 +++---- .../Composition/CompositionHost.cs | 42 ++++--------------- 2 files changed, 15 insertions(+), 42 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs index c5e99a4b5d..701070a6b5 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -27,7 +27,7 @@ namespace Avalonia.Win32 } public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() - { + { return new CompositionRenderTarget(_display, _context, _surface, _surfaceInterop, _info); } @@ -40,11 +40,11 @@ namespace Avalonia.Win32 private static Guid s_Iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); private Windows.UI.Composition.Visual _compositionVisual; - public CompositionRenderTarget(EglDisplay display, - EglContext context, - Windows.UI.Composition.Visual compositionVisual, - ICompositionDrawingSurfaceInterop interopSurface, - IEglWindowGlPlatformSurfaceInfo info) + public CompositionRenderTarget(EglDisplay display, + EglContext context, + Windows.UI.Composition.Visual compositionVisual, + ICompositionDrawingSurfaceInterop interopSurface, + IEglWindowGlPlatformSurfaceInfo info) : base(display, context) { _display = display; @@ -53,6 +53,7 @@ namespace Avalonia.Win32 _initialSize = info.Size; _compositionVisual = compositionVisual; _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); + _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); } public override bool IsCorrupted => _initialSize != _info.Size; @@ -67,7 +68,7 @@ namespace Avalonia.Win32 var surface = (_display as AngleWin32EglDisplay).WrapDirect3D11Texture(texture, offset.X, offset.Y, _info.Size.Width, _info.Size.Height); - return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); Marshal.Release(texture); surface.Dispose(); _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); }, true); + return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); Marshal.Release(texture); surface.Dispose(); }, true); } } } diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 48656e1816..8da205fdc6 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -48,41 +48,13 @@ namespace Avalonia.Win32 Initialize(); } - public void AddElement(CompositionTarget target, float size, float x, float y) - { - if (target.Root != null) - { - var visuals = target.Root.As().Children; - - var visual = _compositor.CreateSpriteVisual(); - - var element = _compositor.CreateSpriteVisual(); - var rand = new Random(); - - element.Brush = _compositor.CreateColorBrush(new Windows.UI.Color { A = 255, R = (byte)(rand.NextDouble() * 255), G = (byte)(rand.NextDouble() * 255), B = (byte)(rand.NextDouble() * 255) }); - element.Size = new System.Numerics.Vector2(size, size); - element.Offset = new System.Numerics.Vector3(x, y, 0.0f); - - var animation = _compositor.CreateVector3KeyFrameAnimation(); - var bottom = (float)600 - element.Size.Y; - animation.InsertKeyFrame(1, new System.Numerics.Vector3(element.Offset.X, bottom, 0)); - - animation.Duration = TimeSpan.FromSeconds(2); - animation.DelayTime = TimeSpan.FromSeconds(3); - element.StartAnimation("Offset", animation); - visuals.InsertAtTop(element); - - visuals.InsertAtTop(visual); - } - } - private void Initialize() { EnsureDispatcherQueue(); if (_dispatcherQueueController != null) - _compositor = new Windows.UI.Composition.Compositor(); + _compositor = new Compositor(); - var interop = _compositor.As(); + var interop = _compositor.As(); var display = Win32GlManager.EglFeature.Display as AngleWin32EglDisplay; @@ -91,19 +63,19 @@ namespace Avalonia.Win32 public ICompositionDrawingSurfaceInterop InitialiseWindowCompositionTree(IntPtr hwnd, out Windows.UI.Composition.Visual surfaceVisual) { - var target = CreateDesktopWindowTarget(hwnd); + var target = CreateDesktopWindowTarget(hwnd); var surface = _graphicsDevice.CreateDrawingSurface(new Windows.Foundation.Size(0, 0), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied); - var surfaceInterop = surface.As(); + var surfaceInterop = surface.As(); var brush = _compositor.CreateSurfaceBrush(surface); var visual = _compositor.CreateSpriteVisual(); - + visual.Brush = brush; - visual.Offset = new System.Numerics.Vector3(0, 0, 0); + visual.Offset = new System.Numerics.Vector3(0, 0, 0); target.Root = CreateBlur(); var visuals = target.Root.As().Children; @@ -139,7 +111,7 @@ namespace Avalonia.Win32 { var interop = _compositor.As(); - interop.CreateDesktopWindowTarget(window, true, out var windowTarget); + interop.CreateDesktopWindowTarget(window, false, out var windowTarget); return Windows.UI.Composition.Desktop.DesktopWindowTarget.FromAbi(windowTarget); } From 0140d201a1d3ea1444f9397c35cba495e2a39283 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 02:08:43 -0700 Subject: [PATCH 047/492] use render thread. --- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 8840dcacef..af6058d197 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -88,7 +88,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) - .Bind().ToConstant(new UiThreadRenderTimer(60)) + .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToSingleton() From 75bf2fcb67e7f63b53aec383c89ad5e18bb0c742 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 02:19:29 -0700 Subject: [PATCH 048/492] remove usings. --- src/Avalonia.OpenGL/Angle/AngleEglInterface.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index b73053b52e..07890b260c 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -1,7 +1,5 @@ using System; using System.Runtime.InteropServices; -using Avalonia.Platform; -using Avalonia.Platform.Interop; namespace Avalonia.OpenGL.Angle { From 0b7f211b3a9a98449fc0e6a6c6efd4a911645739 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 02:22:17 -0700 Subject: [PATCH 049/492] fix build error. --- src/Avalonia.OpenGL/Angle/AngleEglInterface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index 07890b260c..0067124c0b 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -24,7 +24,7 @@ namespace Avalonia.OpenGL.Angle throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point"); } - return eglGetProcAddress; + return EGL_GetProcAddress; } throw new PlatformNotSupportedException(); From 7dc21fa2164b376f064e12d8b00219c3f4c78791 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 02:29:30 -0700 Subject: [PATCH 050/492] only apply no-redirection bitmap if we are on windows 10. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index d964e490c0..a96a662a71 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -45,6 +45,7 @@ namespace Avalonia.Win32 private Thickness _extendedMargins; private Thickness _offScreenMargin; private double _extendTitleBarHint = -1; + private bool _isUsingComposition; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; @@ -110,6 +111,7 @@ namespace Avalonia.Win32 var cgl = new CompositionEglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); cgl.AttachToCompositionTree(_hwnd); _gl = cgl; + _isUsingComposition = true; } else { @@ -626,7 +628,7 @@ namespace Avalonia.Win32 protected virtual IntPtr CreateWindowOverride(ushort atom) { return CreateWindowEx( - (int)WindowStyles.WS_EX_NOREDIRECTIONBITMAP, // TODO this is only when using Win UI Comp. + _isUsingComposition ? (int)WindowStyles.WS_EX_NOREDIRECTIONBITMAP : 0, atom, null, (int)WindowStyles.WS_OVERLAPPEDWINDOW | (int) WindowStyles.WS_CLIPCHILDREN, From 094e605af9000e257eff19b0657b4db217527bf0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 02:36:46 -0700 Subject: [PATCH 051/492] ensure dx11 api for win.ui.comp. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index a96a662a71..fb9f7783b1 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.OpenGL; +using Avalonia.OpenGL.Angle; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Input; @@ -106,11 +107,15 @@ namespace Avalonia.Win32 if (Win32GlManager.EglFeature != null) { - if (Win32Platform.WindowsVersion.Major >= 10) + if (Win32Platform.WindowsVersion.Major >= 10 && + Win32GlManager.EglFeature.Display is AngleWin32EglDisplay angleDisplay && + angleDisplay.PlatformApi == AngleOptions.PlatformApi.DirectX11) { var cgl = new CompositionEglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); cgl.AttachToCompositionTree(_hwnd); + _gl = cgl; + _isUsingComposition = true; } else From 874ac50de24753be252f42a237250137fff14447 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 02:54:31 -0700 Subject: [PATCH 052/492] add a texture wrapping method without requiring offset, etc. --- src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs index 3b254d401a..9bb0dd8351 100644 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs @@ -78,6 +78,13 @@ namespace Avalonia.OpenGL.Angle return d3dDeviceHandle; } + public EglSurface WrapDirect3D11Texture(IntPtr handle) + { + if (PlatformApi != AngleOptions.PlatformApi.DirectX11) + throw new InvalidOperationException("Current platform API is " + PlatformApi); + return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_NONE }); + } + public EglSurface WrapDirect3D11Texture(IntPtr handle, int offsetX, int offsetY, int width, int height) { if (PlatformApi != AngleOptions.PlatformApi.DirectX11) From fdb68480463776f8d5cd125ce4eb867a5a9ebc75 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 02:55:13 -0700 Subject: [PATCH 053/492] check windows 10 build number and set composition flag before create window is called. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index fb9f7783b1..c100b3308d 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -101,15 +101,16 @@ namespace Avalonia.Win32 }; _rendererLock = new ManagedDeferredRendererLock(); + _isUsingComposition = Win32Platform.WindowsVersion.Major >= 10 && Win32Platform.WindowsVersion.Build >= 14393 && + Win32GlManager.EglFeature.Display is AngleWin32EglDisplay angleDisplay && + angleDisplay.PlatformApi == AngleOptions.PlatformApi.DirectX11; CreateWindow(); _framebuffer = new FramebufferManager(_hwnd); if (Win32GlManager.EglFeature != null) { - if (Win32Platform.WindowsVersion.Major >= 10 && - Win32GlManager.EglFeature.Display is AngleWin32EglDisplay angleDisplay && - angleDisplay.PlatformApi == AngleOptions.PlatformApi.DirectX11) + if (_isUsingComposition) { var cgl = new CompositionEglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); cgl.AttachToCompositionTree(_hwnd); From d25d88cdfac7ab224b3fb6f5342699c262a5fabd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 14 Sep 2020 04:20:17 -0700 Subject: [PATCH 054/492] increase min windows version. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index c100b3308d..7222b66beb 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -101,7 +101,7 @@ namespace Avalonia.Win32 }; _rendererLock = new ManagedDeferredRendererLock(); - _isUsingComposition = Win32Platform.WindowsVersion.Major >= 10 && Win32Platform.WindowsVersion.Build >= 14393 && + _isUsingComposition = Win32Platform.WindowsVersion.Major >= 10 && Win32Platform.WindowsVersion.Build >= 16299 && Win32GlManager.EglFeature.Display is AngleWin32EglDisplay angleDisplay && angleDisplay.PlatformApi == AngleOptions.PlatformApi.DirectX11; From 974c73ca46c414899535635ffab73930e53b5924 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 15 Sep 2020 02:36:27 -0700 Subject: [PATCH 055/492] opengl es 3 --- src/Avalonia.OpenGL/EglDisplay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 7b194e4346..a3d11f2311 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -67,12 +67,13 @@ namespace Avalonia.OpenGL { Attributes = new[] { - EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_CONTEXT_MAJOR_VERSION, 3, + EGL_CONTEXT_MINOR_VERSION, 0, EGL_NONE }, Api = EGL_OPENGL_ES_API, - RenderableTypeBit = EGL_OPENGL_ES2_BIT, - Version = new GlVersion(GlProfileType.OpenGLES, 2, 0) + RenderableTypeBit = EGL_OPENGL_ES3_BIT, + Version = new GlVersion(GlProfileType.OpenGLES, 3, 0) } }) { From 1d1275059883ba603db9727422b5c10471776d8b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 16 Sep 2020 03:25:47 -0700 Subject: [PATCH 056/492] allow enabling of windows.ui.composition with win32 options. --- src/Windows/Avalonia.Win32/Win32Platform.cs | 9 +++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index af6058d197..7f1ac443ad 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -39,6 +39,15 @@ namespace Avalonia public bool AllowEglInitialization { get; set; } = true; public bool? EnableMultitouch { get; set; } public bool OverlayPopups { get; set; } + + /// + /// Render Avalonia to a Texture inside the Windows.UI.Composition tree. + /// + /// + /// Supported on Windows 10 build 16299 and above. Ignored on other versions. + /// This is recommended if you need to use AcrylicBlur or acrylic in your applications. + /// + public bool UseWindowsUIComposition { get; set; } } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7222b66beb..a2bd7f57b9 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -101,7 +101,8 @@ namespace Avalonia.Win32 }; _rendererLock = new ManagedDeferredRendererLock(); - _isUsingComposition = Win32Platform.WindowsVersion.Major >= 10 && Win32Platform.WindowsVersion.Build >= 16299 && + _isUsingComposition = Win32Platform.Options.UseWindowsUIComposition && + Win32Platform.WindowsVersion.Major >= 10 && Win32Platform.WindowsVersion.Build >= 16299 && Win32GlManager.EglFeature.Display is AngleWin32EglDisplay angleDisplay && angleDisplay.PlatformApi == AngleOptions.PlatformApi.DirectX11; From 8470a399b6214ecdd82774cbbc7a1620751db787 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Sep 2020 14:52:02 -0700 Subject: [PATCH 057/492] point angle interface to avalonias custom built optimised angle. --- samples/ControlCatalog.NetCore/Program.cs | 4 +++- src/Avalonia.OpenGL/Angle/AngleEglInterface.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 142736a0bb..9b62c2c98e 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -13,6 +13,7 @@ using Avalonia.Skia; using Avalonia.ReactiveUI; using Avalonia.Threading; using Avalonia.Dialogs; +using Avalonia.OpenGL; namespace ControlCatalog.NetCore { @@ -116,7 +117,8 @@ namespace ControlCatalog.NetCore .With(new Win32PlatformOptions { EnableMultitouch = true, - AllowEglInitialization = true + AllowEglInitialization = true, + UseWindowsUIComposition = true }) .UseSkia() .UseReactiveUI() diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index 0067124c0b..a6f5f33968 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -5,7 +5,7 @@ namespace Avalonia.OpenGL.Angle { public class AngleEglInterface : EglInterface { - [DllImport("avangle.dll", CharSet = CharSet.Ansi)] + [DllImport("av_libGLESv2.dll", CharSet = CharSet.Ansi)] static extern IntPtr EGL_GetProcAddress(string proc); public AngleEglInterface() : base(LoadAngle()) From a114c59fe7930c523f4503c0bd401ee7e0f8bac6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Sep 2020 16:09:59 -0700 Subject: [PATCH 058/492] update angle binaries. --- samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 28b0257eda..6b3e3a5f61 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -13,7 +13,7 @@ - + From d3700adb560d295908c87231dc1b77ff973c4d7f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Sep 2020 16:23:40 -0700 Subject: [PATCH 059/492] setup the acrylic blur in a way it can be show or hidden. --- .../Avalonia.Win32/Composition/CompositionHost.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 8da205fdc6..958959bc93 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -76,7 +76,15 @@ namespace Avalonia.Win32 visual.Brush = brush; visual.Offset = new System.Numerics.Vector3(0, 0, 0); - target.Root = CreateBlur(); + + var container = _compositor.CreateContainerVisual(); + container.RelativeSizeAdjustment = new System.Numerics.Vector2(1, 1); + + target.Root = container; + + var blur = CreateBlur(); + + container.Children.InsertAtTop(blur); var visuals = target.Root.As().Children; From e00aa6e5ded72e32e356793c38bb68daeb2f127c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 17 Sep 2020 17:30:13 -0700 Subject: [PATCH 060/492] iscorrupted no longer useful. --- .../Composition/CompositionEglGlPlatformSurface.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs index 701070a6b5..96646b9590 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -35,7 +35,7 @@ namespace Avalonia.Win32 { private readonly EglDisplay _display; private readonly IEglWindowGlPlatformSurfaceInfo _info; - private PixelSize _initialSize; + private readonly PixelSize _initialSize; private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; private static Guid s_Iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); private Windows.UI.Composition.Visual _compositionVisual; @@ -56,7 +56,7 @@ namespace Avalonia.Win32 _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); } - public override bool IsCorrupted => _initialSize != _info.Size; + public override bool IsCorrupted => false; public override IGlPlatformSurfaceRenderingSession BeginDraw() { var offset = new POINT(); From 6bcf36e23fc61353406ee1dcb6009fe8cba6aa65 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 01:36:17 -0700 Subject: [PATCH 061/492] windows.ui.composition compatible with acrylic blur setting. --- .../CompositionEglGlPlatformSurface.cs | 8 +- .../Composition/CompositionHost.cs | 24 +++++- src/Windows/Avalonia.Win32/WindowImpl.cs | 84 +++++++++++-------- 3 files changed, 75 insertions(+), 41 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs index 96646b9590..38ea545957 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -6,7 +6,7 @@ using Windows.UI.Composition.Interop; namespace Avalonia.Win32 { - public class CompositionEglGlPlatformSurface : EglGlPlatformSurfaceBase + internal class CompositionEglGlPlatformSurface : EglGlPlatformSurfaceBase { private readonly EglDisplay _display; private readonly EglContext _context; @@ -21,9 +21,11 @@ namespace Avalonia.Win32 _info = info; } - public void AttachToCompositionTree(IntPtr hwnd) + public IBlurHost AttachToCompositionTree(IntPtr hwnd) { - _surfaceInterop = CompositionHost.Instance.InitialiseWindowCompositionTree(hwnd, out _surface); + _surfaceInterop = CompositionHost.Instance.InitialiseWindowCompositionTree(hwnd, out _surface, out var blurHost); + + return blurHost; } public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 958959bc93..dfcf6e1d7f 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -7,6 +7,26 @@ using WinRT; namespace Avalonia.Win32 { + internal interface IBlurHost + { + void SetBlur(bool enabled); + } + + internal class CompositionBlurHost : IBlurHost + { + Windows.UI.Composition.Visual _blurVisual; + + public CompositionBlurHost(Windows.UI.Composition.Visual blurVisual) + { + _blurVisual = blurVisual; + } + + public void SetBlur(bool enabled) + { + _blurVisual.IsVisible = enabled; + } + } + class CompositionHost { internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE @@ -61,7 +81,7 @@ namespace Avalonia.Win32 _graphicsDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); } - public ICompositionDrawingSurfaceInterop InitialiseWindowCompositionTree(IntPtr hwnd, out Windows.UI.Composition.Visual surfaceVisual) + public ICompositionDrawingSurfaceInterop InitialiseWindowCompositionTree(IntPtr hwnd, out Windows.UI.Composition.Visual surfaceVisual, out IBlurHost blurHost) { var target = CreateDesktopWindowTarget(hwnd); @@ -84,6 +104,8 @@ namespace Avalonia.Win32 var blur = CreateBlur(); + blurHost = new CompositionBlurHost(blur); + container.Children.InsertAtTop(blur); var visuals = target.Root.As().Children; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index a2bd7f57b9..46c18dea68 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -47,6 +47,7 @@ namespace Avalonia.Win32 private Thickness _offScreenMargin; private double _extendTitleBarHint = -1; private bool _isUsingComposition; + private IBlurHost _blurHost; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; @@ -114,7 +115,7 @@ namespace Avalonia.Win32 if (_isUsingComposition) { var cgl = new CompositionEglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); - cgl.AttachToCompositionTree(_hwnd); + _blurHost = cgl.AttachToCompositionTree(_hwnd); _gl = cgl; @@ -341,54 +342,63 @@ namespace Avalonia.Win32 private WindowTransparencyLevel Win10EnableBlur(WindowTransparencyLevel transparencyLevel) { - bool canUseAcrylic = Win32Platform.WindowsVersion.Major > 10 || Win32Platform.WindowsVersion.Build >= 19628; - - var accent = new AccentPolicy(); - var accentStructSize = Marshal.SizeOf(accent); - - if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur && !canUseAcrylic) + if (_isUsingComposition) { - transparencyLevel = WindowTransparencyLevel.Blur; - } + _blurHost?.SetBlur(transparencyLevel >= WindowTransparencyLevel.Blur); - switch (transparencyLevel) + return transparencyLevel; + } + else { - default: - case WindowTransparencyLevel.None: - accent.AccentState = AccentState.ACCENT_DISABLED; - break; + bool canUseAcrylic = Win32Platform.WindowsVersion.Major > 10 || Win32Platform.WindowsVersion.Build >= 19628; - case WindowTransparencyLevel.Transparent: - accent.AccentState = AccentState.ACCENT_ENABLE_TRANSPARENTGRADIENT; - break; + var accent = new AccentPolicy(); + var accentStructSize = Marshal.SizeOf(accent); - case WindowTransparencyLevel.Blur: - accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; - break; + if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur && !canUseAcrylic) + { + transparencyLevel = WindowTransparencyLevel.Blur; + } - case WindowTransparencyLevel.AcrylicBlur: - case (WindowTransparencyLevel.AcrylicBlur + 1): // hack-force acrylic. - accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC; - transparencyLevel = WindowTransparencyLevel.AcrylicBlur; - break; - } + switch (transparencyLevel) + { + default: + case WindowTransparencyLevel.None: + accent.AccentState = AccentState.ACCENT_DISABLED; + break; + + case WindowTransparencyLevel.Transparent: + accent.AccentState = AccentState.ACCENT_ENABLE_TRANSPARENTGRADIENT; + break; + + case WindowTransparencyLevel.Blur: + accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; + break; + + case WindowTransparencyLevel.AcrylicBlur: + case (WindowTransparencyLevel.AcrylicBlur + 1): // hack-force acrylic. + accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC; + transparencyLevel = WindowTransparencyLevel.AcrylicBlur; + break; + } - accent.AccentFlags = 2; - accent.GradientColor = 0x01000000; + accent.AccentFlags = 2; + accent.GradientColor = 0x01000000; - var accentPtr = Marshal.AllocHGlobal(accentStructSize); - Marshal.StructureToPtr(accent, accentPtr, false); + var accentPtr = Marshal.AllocHGlobal(accentStructSize); + Marshal.StructureToPtr(accent, accentPtr, false); - var data = new WindowCompositionAttributeData(); - data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY; - data.SizeOfData = accentStructSize; - data.Data = accentPtr; + var data = new WindowCompositionAttributeData(); + data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY; + data.SizeOfData = accentStructSize; + data.Data = accentPtr; - SetWindowCompositionAttribute(_hwnd, ref data); + SetWindowCompositionAttribute(_hwnd, ref data); - Marshal.FreeHGlobal(accentPtr); + Marshal.FreeHGlobal(accentPtr); - return transparencyLevel; + return transparencyLevel; + } } public IEnumerable Surfaces => new object[] { Handle, _gl, _framebuffer }; From 8fa906281fa2b175247bd39db8b8b33acab8ee1f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 01:39:49 -0700 Subject: [PATCH 062/492] zero bitmap memory, not transparent white. --- src/Avalonia.Visuals/Rendering/RenderLayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index ddf5f4e5cf..668e9167dc 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs @@ -35,7 +35,7 @@ namespace Avalonia.Rendering using (var context = resized.Item.CreateDrawingContext(null)) { - context.Clear(Colors.Transparent); + context.Clear(default); Bitmap = resized; Scaling = scaling; From 274e76fec9211b016d53750cc202835549adbf1c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 01:49:48 -0700 Subject: [PATCH 063/492] dispose current bitmap whilst context is current. --- src/Avalonia.Visuals/Rendering/RenderLayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index 668e9167dc..1fbd8c02c0 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs @@ -30,11 +30,11 @@ namespace Avalonia.Rendering { if (Size != size || Scaling != scaling) { - Bitmap.Dispose(); var resized = RefCountable.Create(drawingContext.CreateLayer(size)); using (var context = resized.Item.CreateDrawingContext(null)) { + Bitmap.Dispose(); context.Clear(default); Bitmap = resized; From 7f9b675efbe154439a956504464883fffae7bbd5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 03:11:42 -0700 Subject: [PATCH 064/492] refactor effects. --- .../Composition/CompositionHost.cs | 18 ++++++-- .../Avalonia.Win32/Composition/EffectBase.cs | 45 +++++++++++++++++++ .../Composition/GaussianBlurEffect.cs | 42 +++++------------ .../Composition/SaturationEffect.cs | 35 +++++++++++++++ 4 files changed, 104 insertions(+), 36 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/Composition/EffectBase.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/SaturationEffect.cs diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index dfcf6e1d7f..f1cf5af0e8 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -121,18 +121,28 @@ namespace Avalonia.Win32 public SpriteVisual CreateBlur() { - var effect = new GaussianBlurEffect(); - var effectFactory = _compositor.CreateEffectFactory(effect); - var blurBrush = effectFactory.CreateBrush(); + var blurEffect = new GaussianBlurEffect(new CompositionEffectSourceParameter ("backdrop")); + var blurEffectFactory = _compositor.CreateEffectFactory(blurEffect); + var blurBrush = blurEffectFactory.CreateBrush(); var backDropBrush = _compositor.CreateBackdropBrush(); blurBrush.SetSourceParameter("backdrop", backDropBrush); + + var saturateEffect = new SaturationEffect(blurEffect); + var satEffectFactory = _compositor.CreateEffectFactory(saturateEffect); + + + var satBrush = satEffectFactory.CreateBrush(); + satBrush.SetSourceParameter("backdrop", backDropBrush); + + + var visual = _compositor.CreateSpriteVisual(); visual.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); - visual.Brush = blurBrush; + visual.Brush = satBrush; return visual; } diff --git a/src/Windows/Avalonia.Win32/Composition/EffectBase.cs b/src/Windows/Avalonia.Win32/Composition/EffectBase.cs new file mode 100644 index 0000000000..ca5b15971e --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/EffectBase.cs @@ -0,0 +1,45 @@ +using System; +using Windows.Graphics.Effects; +using Windows.Graphics.Effects.Interop; + + +namespace Avalonia.Win32 +{ + abstract class EffectBase : IGraphicsEffect, IGraphicsEffectSource, global::Windows.Graphics.Effects.Interop.IGraphicsEffectD2D1Interop + { + private IGraphicsEffectSource[] _sources; + + public EffectBase(params IGraphicsEffectSource[] sources) + { + _sources = sources; + } + + private IGraphicsEffectSource _source; + + public virtual string Name { get; set; } + + public abstract Guid EffectId { get; } + + public abstract uint PropertyCount { get; } + + public uint SourceCount => (uint)_sources.Length; + + public IGraphicsEffectSource GetSource(uint index) + { + if(index < _sources.Length) + { + return _sources[index]; + } + + return null; + } + + public uint GetNamedPropertyMapping(string name, out GRAPHICS_EFFECT_PROPERTY_MAPPING mapping) + { + throw new NotImplementedException(); + } + + public abstract object GetProperty(uint index); + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs b/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs index 4e1be3af1a..342e68eeb4 100644 --- a/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs +++ b/src/Windows/Avalonia.Win32/Composition/GaussianBlurEffect.cs @@ -1,13 +1,14 @@ using System; using Windows.Graphics.Effects; -using Windows.Graphics.Effects.Interop; -using Windows.UI.Composition; - namespace Avalonia.Win32 { - class GaussianBlurEffect : IGraphicsEffect, IGraphicsEffectSource, global::Windows.Graphics.Effects.Interop.IGraphicsEffectD2D1Interop + class GaussianBlurEffect : EffectBase { + public GaussianBlurEffect(IGraphicsEffectSource source) : base(source) + { + } + enum D2D1_GAUSSIANBLUR_OPTIMIZATION { D2D1_GAUSSIANBLUR_OPTIMIZATION_SPEED, @@ -31,20 +32,11 @@ namespace Avalonia.Win32 D2D1_GAUSSIANBLUR_PROP_FORCE_DWORD }; - public string Name { get; set; } - - public Guid EffectId => D2DEffects.CLSID_D2D1GaussianBlur; + public override Guid EffectId => D2DEffects.CLSID_D2D1GaussianBlur; - public uint PropertyCount => 3; - - public uint SourceCount => 1; - - public uint GetNamedPropertyMapping(string name, out GRAPHICS_EFFECT_PROPERTY_MAPPING mapping) - { - throw new NotImplementedException(); - } + public override uint PropertyCount => 3; - public object GetProperty(uint index) + public override object GetProperty(uint index) { switch ((D2D1GaussianBlurProp)index) { @@ -52,28 +44,14 @@ namespace Avalonia.Win32 return 30.0f; case D2D1GaussianBlurProp.D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION: - return (UInt32)D2D1_GAUSSIANBLUR_OPTIMIZATION.D2D1_GAUSSIANBLUR_OPTIMIZATION_BALANCED; + return (uint)D2D1_GAUSSIANBLUR_OPTIMIZATION.D2D1_GAUSSIANBLUR_OPTIMIZATION_BALANCED; case D2D1GaussianBlurProp.D2D1_GAUSSIANBLUR_PROP_BORDER_MODE: - return (UInt32)D2D1_BORDER_MODE.D2D1_BORDER_MODE_HARD; + return (uint)D2D1_BORDER_MODE.D2D1_BORDER_MODE_HARD; } return null; } - - private IGraphicsEffectSource _source = new CompositionEffectSourceParameter("backdrop"); - - public IGraphicsEffectSource GetSource(uint index) - { - if (index == 0) - { - return _source; - } - else - { - return null; - } - } } } diff --git a/src/Windows/Avalonia.Win32/Composition/SaturationEffect.cs b/src/Windows/Avalonia.Win32/Composition/SaturationEffect.cs new file mode 100644 index 0000000000..90eca22d8e --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/SaturationEffect.cs @@ -0,0 +1,35 @@ +using System; +using Windows.Graphics.Effects; + + +namespace Avalonia.Win32 +{ + class SaturationEffect : EffectBase + { + public SaturationEffect(IGraphicsEffect source) : base(source) + { + } + + enum D2D1_SATURATION_PROP + { + D2D1_SATURATION_PROP_SATURATION, + D2D1_SATURATION_PROP_FORCE_DWORD + }; + + public override Guid EffectId => D2DEffects.CLSID_D2D1Saturation; + + public override uint PropertyCount => 1; + + public override object GetProperty(uint index) + { + switch ((D2D1_SATURATION_PROP)index) + { + case D2D1_SATURATION_PROP.D2D1_SATURATION_PROP_SATURATION: + return 2.0f; + } + + return null; + } + } +} + From 57b3f0b10574a167a448ad4401b6de88c1563354 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 03:21:04 -0700 Subject: [PATCH 065/492] revert changed code. --- .../ControlCatalog.NetCore/Properties/launchSettings.json | 8 -------- samples/ControlCatalog/MainWindow.xaml | 8 ++++++-- 2 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 samples/ControlCatalog.NetCore/Properties/launchSettings.json diff --git a/samples/ControlCatalog.NetCore/Properties/launchSettings.json b/samples/ControlCatalog.NetCore/Properties/launchSettings.json deleted file mode 100644 index cd8a2c824b..0000000000 --- a/samples/ControlCatalog.NetCore/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "ControlCatalog.NetCore": { - "commandName": "Project", - "nativeDebugging": true - } - } -} \ No newline at end of file diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index eebc804a64..97bd88f5e4 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -7,8 +7,12 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:v="clr-namespace:ControlCatalog.Views" - TransparencyLevelHint="Transparent" - x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{x:Null}"> + ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}" + ExtendClientAreaChromeHints="{Binding ChromeHints}" + ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}" + TransparencyLevelHint="{Binding TransparencyLevel}" + x:Name="MainWindow" + x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}"> From 4d0c27dc8f8da04be98b690a8406df2d86a9f49a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 03:32:43 -0700 Subject: [PATCH 066/492] whitespace. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 46c18dea68..9c002038cb 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -717,7 +717,7 @@ namespace Avalonia.Win32 { _scaling = dpix / 96.0; } - } + } } private void CreateDropTarget() From 28168d571be4912cb85d87629fba798ae5fd3b09 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 10:06:19 -0700 Subject: [PATCH 067/492] make managed deferredrenderer lock global. --- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 6 +++--- src/Windows/Avalonia.Win32/WindowImpl.cs | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 25a34561fc..0bab0d1657 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -339,7 +339,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_PAINT: { - using (_rendererLock.Lock()) + using (s_rendererLock.Lock()) { if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) { @@ -356,7 +356,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_SIZE: { - using (_rendererLock.Lock()) + using (s_rendererLock.Lock()) { // Do nothing here, just block until the pending frame render is completed on the render thread } @@ -464,7 +464,7 @@ namespace Avalonia.Win32 } } - using (_rendererLock.Lock()) + using (s_rendererLock.Lock()) { return DefWindowProc(hWnd, msg, wParam, lParam); } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9c002038cb..7b8b6d3e83 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -56,7 +56,7 @@ namespace Avalonia.Win32 private const WindowStyles WindowStateMask = (WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE); private readonly TouchDevice _touchDevice; private readonly MouseDevice _mouseDevice; - private readonly ManagedDeferredRendererLock _rendererLock; + private static readonly ManagedDeferredRendererLock s_rendererLock = new ManagedDeferredRendererLock(); private readonly FramebufferManager _framebuffer; private readonly IGlPlatformSurface _gl; @@ -100,7 +100,6 @@ namespace Avalonia.Win32 IsResizable = true, Decorations = SystemDecorations.Full }; - _rendererLock = new ManagedDeferredRendererLock(); _isUsingComposition = Win32Platform.Options.UseWindowsUIComposition && Win32Platform.WindowsVersion.Major >= 10 && Win32Platform.WindowsVersion.Build >= 16299 && @@ -443,7 +442,7 @@ namespace Avalonia.Win32 return customRendererFactory.Create(root, loop); return Win32Platform.UseDeferredRendering ? - (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock) : + (IRenderer)new DeferredRenderer(root, loop, rendererLock: s_rendererLock) : new ImmediateRenderer(root); } From 79a53846c8bb7dbb789a3d08fe8179e8ada37b72 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Fri, 18 Sep 2020 22:35:59 +0500 Subject: [PATCH 068/492] InvariantCultureIgnoreCase -> OrdinalIgnoreCase --- .../Remote/HtmlTransport/HtmlTransport.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index de7da4985c..0996431b1f 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -267,11 +267,11 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport { var parts = message.Split(':'); var key = parts[0]; - if (key.Equals("frame-received", StringComparison.InvariantCultureIgnoreCase)) + if (key.Equals("frame-received", StringComparison.OrdinalIgnoreCase)) { return new FrameReceivedMessage { SequenceId = long.Parse(parts[1]) }; } - else if (key.Equals("pointer-released", StringComparison.InvariantCultureIgnoreCase)) + else if (key.Equals("pointer-released", StringComparison.OrdinalIgnoreCase)) { return new InputProtocol.PointerReleasedEventMessage { @@ -281,7 +281,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport Button = ParseMouseButton(parts[4]), }; } - else if (key.Equals("pointer-pressed", StringComparison.InvariantCultureIgnoreCase)) + else if (key.Equals("pointer-pressed", StringComparison.OrdinalIgnoreCase)) { return new InputProtocol.PointerPressedEventMessage { @@ -291,7 +291,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport Button = ParseMouseButton(parts[4]), }; } - else if (key.Equals("pointer-moved", StringComparison.InvariantCultureIgnoreCase)) + else if (key.Equals("pointer-moved", StringComparison.OrdinalIgnoreCase)) { return new InputProtocol.PointerMovedEventMessage { @@ -300,7 +300,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport Y = ParseDouble(parts[3]), }; } - else if (key.Equals("scroll", StringComparison.InvariantCultureIgnoreCase)) + else if (key.Equals("scroll", StringComparison.OrdinalIgnoreCase)) { return new InputProtocol.ScrollEventMessage { @@ -316,7 +316,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport } private static InputProtocol.InputModifiers[] ParseInputModifiers(string modifiersText) => - string.IsNullOrEmpty(modifiersText) + string.IsNullOrWhiteSpace(modifiersText) ? null : modifiersText .Split(',') From 9ce5a98277e7c33f8ec3e298a1669dd27485e1fd Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Fri, 18 Sep 2020 22:39:22 +0500 Subject: [PATCH 069/492] IsNullOrEmpty -> IsNullOrWhiteSpace --- .../Remote/HtmlTransport/HtmlTransport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs index 0996431b1f..804bb09510 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/HtmlTransport.cs @@ -325,7 +325,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport .ToArray(); private static InputProtocol.MouseButton ParseMouseButton(string buttonText) => - string.IsNullOrEmpty(buttonText) + string.IsNullOrWhiteSpace(buttonText) ? InputProtocol.MouseButton.None : (InputProtocol.MouseButton)Enum.Parse( typeof(InputProtocol.MouseButton), buttonText, true); From 7720d8f9062c220e05b2c40183694defccccb616 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 10:42:39 -0700 Subject: [PATCH 070/492] fix visual tree so that content resizes with the window. --- .../Composition/CompositionEglGlPlatformSurface.cs | 3 ++- .../Avalonia.Win32/Composition/CompositionHost.cs | 11 ++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs index 38ea545957..4e6dc79673 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -58,7 +58,8 @@ namespace Avalonia.Win32 _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); } - public override bool IsCorrupted => false; + public override bool IsCorrupted => _info.Size != _initialSize; + public override IGlPlatformSurfaceRenderingSession BeginDraw() { var offset = new POINT(); diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index f1cf5af0e8..615b5d303a 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -95,11 +95,10 @@ namespace Avalonia.Win32 var visual = _compositor.CreateSpriteVisual(); visual.Brush = brush; - visual.Offset = new System.Numerics.Vector3(0, 0, 0); + visual.RelativeSizeAdjustment = new System.Numerics.Vector2(1, 1); var container = _compositor.CreateContainerVisual(); - container.RelativeSizeAdjustment = new System.Numerics.Vector2(1, 1); - + target.Root = container; var blur = CreateBlur(); @@ -108,13 +107,11 @@ namespace Avalonia.Win32 container.Children.InsertAtTop(blur); - var visuals = target.Root.As().Children; - - visuals.InsertAtTop(visual); + container.Children.InsertAtTop(visual); visual.CompositeMode = CompositionCompositeMode.SourceOver; - surfaceVisual = visual; + surfaceVisual = container; return surfaceInterop; } From a004366e70c0e8b7dead53f092bc261246597a4d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 10:50:27 -0700 Subject: [PATCH 071/492] make blur not visible at startup. --- src/Windows/Avalonia.Win32/Composition/CompositionHost.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 615b5d303a..516ad44071 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -126,17 +126,14 @@ namespace Avalonia.Win32 blurBrush.SetSourceParameter("backdrop", backDropBrush); - var saturateEffect = new SaturationEffect(blurEffect); var satEffectFactory = _compositor.CreateEffectFactory(saturateEffect); - var satBrush = satEffectFactory.CreateBrush(); satBrush.SetSourceParameter("backdrop", backDropBrush); - - var visual = _compositor.CreateSpriteVisual(); + visual.IsVisible = false; visual.RelativeSizeAdjustment = new System.Numerics.Vector2(1.0f, 1.0f); visual.Brush = satBrush; From 099909d4e286a9820e8a34b9b793979c4b827e77 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 10:55:07 -0700 Subject: [PATCH 072/492] make win.ui.comp the default when running on win10 and compatible gpu is used. --- samples/ControlCatalog.NetCore/Program.cs | 3 +-- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 9b62c2c98e..d967fde34c 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -117,8 +117,7 @@ namespace ControlCatalog.NetCore .With(new Win32PlatformOptions { EnableMultitouch = true, - AllowEglInitialization = true, - UseWindowsUIComposition = true + AllowEglInitialization = true }) .UseSkia() .UseReactiveUI() diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 7f1ac443ad..e9d34d7d56 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -47,7 +47,7 @@ namespace Avalonia /// Supported on Windows 10 build 16299 and above. Ignored on other versions. /// This is recommended if you need to use AcrylicBlur or acrylic in your applications. /// - public bool UseWindowsUIComposition { get; set; } + public bool UseWindowsUIComposition { get; set; } = true; } } From 59d68a30370694203a4647399cddc81542c09c92 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 10:57:45 -0700 Subject: [PATCH 073/492] remove usings. --- samples/ControlCatalog.NetCore/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index d967fde34c..3851cbf9a6 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -7,13 +7,11 @@ using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Dialogs; using Avalonia.Headless; using Avalonia.LogicalTree; -using Avalonia.Skia; using Avalonia.ReactiveUI; using Avalonia.Threading; -using Avalonia.Dialogs; -using Avalonia.OpenGL; namespace ControlCatalog.NetCore { From 02f33d561c21439849838cce4587f27b5560122e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 10:59:16 -0700 Subject: [PATCH 074/492] 1 file per class. --- .../Composition/CompositionBlurHost.cs | 18 +++++++++++++++++ .../Composition/CompositionHost.cs | 20 ------------------- .../Avalonia.Win32/Composition/IBlurHost.cs | 8 ++++++++ 3 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/Composition/CompositionBlurHost.cs create mode 100644 src/Windows/Avalonia.Win32/Composition/IBlurHost.cs diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionBlurHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionBlurHost.cs new file mode 100644 index 0000000000..4c090e797c --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/CompositionBlurHost.cs @@ -0,0 +1,18 @@ +namespace Avalonia.Win32 +{ + internal class CompositionBlurHost : IBlurHost + { + Windows.UI.Composition.Visual _blurVisual; + + public CompositionBlurHost(Windows.UI.Composition.Visual blurVisual) + { + _blurVisual = blurVisual; + } + + public void SetBlur(bool enabled) + { + _blurVisual.IsVisible = enabled; + } + } +} + diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs index 516ad44071..07a0170810 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs @@ -7,26 +7,6 @@ using WinRT; namespace Avalonia.Win32 { - internal interface IBlurHost - { - void SetBlur(bool enabled); - } - - internal class CompositionBlurHost : IBlurHost - { - Windows.UI.Composition.Visual _blurVisual; - - public CompositionBlurHost(Windows.UI.Composition.Visual blurVisual) - { - _blurVisual = blurVisual; - } - - public void SetBlur(bool enabled) - { - _blurVisual.IsVisible = enabled; - } - } - class CompositionHost { internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE diff --git a/src/Windows/Avalonia.Win32/Composition/IBlurHost.cs b/src/Windows/Avalonia.Win32/Composition/IBlurHost.cs new file mode 100644 index 0000000000..6ab470d81c --- /dev/null +++ b/src/Windows/Avalonia.Win32/Composition/IBlurHost.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Win32 +{ + internal interface IBlurHost + { + void SetBlur(bool enabled); + } +} + From 3541e70c3b4c822ca8a082c76c45efaae752d37d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 11:50:45 -0700 Subject: [PATCH 075/492] fix hostapp for designer. --- .../Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 51d18e55d1..152581de27 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -23,5 +23,8 @@ + + + From 8c667faf64f9adf0829929a2a70a9e9247a3d931 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Sep 2020 13:37:16 -0700 Subject: [PATCH 076/492] use stable Angle. --- samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj | 2 +- samples/interop/NativeEmbedSample/NativeEmbedSample.csproj | 2 +- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 2 +- .../Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 6b3e3a5f61..d4d50163d5 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -13,7 +13,7 @@ - + diff --git a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj index cc831ef8ae..abdcac3b77 100644 --- a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj +++ b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj @@ -13,7 +13,7 @@ - + Designer diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index c6af049868..5889d919df 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 152581de27..a2d27fd579 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -24,7 +24,7 @@ - + From a2c736de46b9fb93b1d6e0482d21c0b8e0310902 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 27 Sep 2020 14:39:32 +0300 Subject: [PATCH 077/492] Manually setup FBOs for layers --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 12 +- src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs | 14 ++ .../Gpu/OpenGl/FboSkiaSurface.cs | 124 ++++++++++++++++++ .../Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs | 5 + .../Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs | 7 +- .../Avalonia.Skia/PlatformRenderInterface.cs | 2 +- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 43 ++++-- 7 files changed, 194 insertions(+), 13 deletions(-) create mode 100644 src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index a155fd863b..669da8d03c 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -31,6 +31,7 @@ namespace Avalonia.Skia private bool _disposed; private GRContext _grContext; public GRContext GrContext => _grContext; + private ISkiaGpu _gpu; private readonly SKPaint _strokePaint = new SKPaint(); private readonly SKPaint _fillPaint = new SKPaint(); private readonly SKPaint _boxShadowPaint = new SKPaint(); @@ -65,6 +66,11 @@ namespace Avalonia.Skia /// GPU-accelerated context (optional) /// public GRContext GrContext; + + /// + /// Skia GPU provider context (optional) + /// + public ISkiaGpu Gpu; } /// @@ -79,6 +85,7 @@ namespace Avalonia.Skia _disposables = disposables; _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering; _grContext = createInfo.GrContext; + _gpu = createInfo.Gpu; if (_grContext != null) Monitor.Enter(_grContext); Surface = createInfo.Surface; @@ -417,7 +424,7 @@ namespace Avalonia.Skia /// public IRenderTargetBitmapImpl CreateLayer(Size size) { - return CreateRenderTarget(size); + return CreateRenderTarget( size); } /// @@ -925,7 +932,8 @@ namespace Avalonia.Skia Dpi = _dpi, Format = format, DisableTextLcdRendering = !_canTextUseLcdRendering, - GrContext = _grContext + GrContext = _grContext, + Gpu = _gpu }; return new SurfaceRenderTarget(createInfo); diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index 1a7a9b75cf..ebb0cb3a1b 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Avalonia.OpenGL.Imaging; using SkiaSharp; @@ -15,6 +16,19 @@ namespace Avalonia.Skia /// Surfaces. /// Created render target or if it fails. ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable surfaces); + + /// + /// Creates an offscreen render target surface + /// + /// size in pixels + ISkiaSurface TryCreateSurface(PixelSize size); + } + + public interface ISkiaSurface : IDisposable + { + SKSurface WriteSurface { get; } + SKSurface ReadSurface { get; } + } public interface IOpenGlAwareSkiaGpu : ISkiaGpu diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs new file mode 100644 index 0000000000..93afd14286 --- /dev/null +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -0,0 +1,124 @@ +using System; +using Avalonia.OpenGL; +using SkiaSharp; +using static Avalonia.OpenGL.GlConsts; +namespace Avalonia.Skia +{ + public class FboSkiaSurface : ISkiaSurface + { + private readonly GRContext _grContext; + private readonly IGlContext _glContext; + private readonly PixelSize _pixelSize; + private int _fbo; + private int _depthStencil; + private int _texture; + + private static readonly bool[] TrueFalse = new[] { true, false }; + public FboSkiaSurface(GRContext grContext, IGlContext glContext, PixelSize pixelSize) + { + _grContext = grContext; + _glContext = glContext; + _pixelSize = pixelSize; + var InternalFormat = glContext.Version.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8; + var gl = glContext.GlInterface; + + // Save old bindings + gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFbo); + gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderbuffer); + gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture); + + var arr = new int[2]; + + // Generate FBO + gl.GenFramebuffers(1, arr); + _fbo = arr[0]; + gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo); + + // Create a texture to render into + gl.GenTextures(1, arr); + _texture = arr[0]; + gl.BindTexture(GL_TEXTURE_2D, _texture); + gl.TexImage2D(GL_TEXTURE_2D, 0, + InternalFormat, pixelSize.Width, pixelSize.Height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero); + gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + + var success = false; + foreach (var useStencil8 in TrueFalse) + { + gl.GenRenderbuffers(1, arr); + _depthStencil = arr[0]; + gl.BindRenderbuffer(GL_RENDERBUFFER, _depthStencil); + + if (useStencil8) + { + gl.RenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, pixelSize.Width, pixelSize.Height); + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthStencil); + } + else + { + gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, pixelSize.Width, pixelSize.Height); + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil); + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthStencil); + } + + var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + if (status == GL_FRAMEBUFFER_COMPLETE) + { + success = true; + break; + } + else + { + gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer); + gl.DeleteRenderbuffers(1, arr); + } + } + + gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer); + gl.BindTexture(GL_TEXTURE_2D, oldTexture); + gl.BindFramebuffer(GL_FRAMEBUFFER, oldFbo); + + if (!success) + { + arr[0] = _fbo; + gl.DeleteFramebuffers(1, arr); + arr[0] = _texture; + gl.DeleteTextures(1, arr); + throw new OpenGlException("Unable to create FBO with stencil"); + } + + ReadSurface = SKSurface.Create(_grContext, new GRBackendTexture(pixelSize.Width, pixelSize.Height, false, + new GRGlTextureInfo(GL_TEXTURE_2D, (uint)_texture, (uint)InternalFormat)), SKColorType.Rgba8888); + + var target = new GRBackendRenderTarget(pixelSize.Width, pixelSize.Height, 0, 0, + new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); + WriteSurface = SKSurface.Create(_grContext, target, + GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); + } + + public void Dispose() + { + using (_glContext.EnsureCurrent()) + { + WriteSurface.Dispose(); + WriteSurface = null; + ReadSurface.Dispose(); + ReadSurface = null; + var gl = _glContext.GlInterface; + if (_fbo != 0) + { + gl.DeleteFramebuffers(1, new[] { _fbo }); + gl.DeleteTextures(1, new[] { _texture }); + gl.DeleteRenderbuffers(1, new[] { _depthStencil }); + _fbo = _texture = _depthStencil = 0; + } + } + } + + public SKSurface WriteSurface { get; private set; } + public SKSurface ReadSurface { get; private set; } + } +} diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index 46d42dfdab..f606929b76 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -43,6 +43,11 @@ namespace Avalonia.Skia return null; } + public ISkiaSurface TryCreateSurface(PixelSize size) + { + return new FboSkiaSurface(_grContext, _glContext, size); + } + public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi); } } diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs index ef5da5eb08..9992c9ba8c 100644 --- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs @@ -8,10 +8,12 @@ namespace Avalonia.Skia /// internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo { + private readonly ISkiaGpu _skiaGpu; private readonly ISkiaGpuRenderTarget _renderTarget; - public SkiaGpuRenderTarget(ISkiaGpuRenderTarget renderTarget) + public SkiaGpuRenderTarget(ISkiaGpu skiaGpu, ISkiaGpuRenderTarget renderTarget) { + _skiaGpu = skiaGpu; _renderTarget = renderTarget; } @@ -30,7 +32,8 @@ namespace Avalonia.Skia Surface = session.SkSurface, Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor, VisualBrushRenderer = visualBrushRenderer, - DisableTextLcdRendering = true + DisableTextLcdRendering = true, + Gpu = _skiaGpu }; return new DrawingContextImpl(nfo, session); diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index b9c1cbc673..d6f76a2c20 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -138,7 +138,7 @@ namespace Avalonia.Skia var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces); if (gpuRenderTarget != null) { - return new SkiaGpuRenderTarget(gpuRenderTarget); + return new SkiaGpuRenderTarget(_skiaGpu, gpuRenderTarget); } foreach (var surface in surfaces) diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 428087ac56..d6ea229089 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -13,10 +13,29 @@ namespace Avalonia.Skia /// internal class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl { - private readonly SKSurface _surface; + private readonly ISkiaSurface _surface; private readonly SKCanvas _canvas; private readonly bool _disableLcdRendering; private readonly GRContext _grContext; + private readonly ISkiaGpu _gpu; + + class SkiaSurfaceWrapper : ISkiaSurface + { + public SKSurface WriteSurface { get; private set; } + public SKSurface ReadSurface { get; private set; } + + public SkiaSurfaceWrapper(SKSurface surface) + { + WriteSurface = ReadSurface = surface; + } + + public void Dispose() + { + WriteSurface?.Dispose(); + WriteSurface = null; + ReadSurface = null; + } + } /// /// Create new surface render target. @@ -29,9 +48,14 @@ namespace Avalonia.Skia _disableLcdRendering = createInfo.DisableTextLcdRendering; _grContext = createInfo.GrContext; - _surface = CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, createInfo.Format); + _gpu = createInfo.Gpu; - _canvas = _surface?.Canvas; + _surface = _gpu?.TryCreateSurface(PixelSize); + if (_surface == null) + _surface = new SkiaSurfaceWrapper(CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, + createInfo.Format)); + + _canvas = _surface?.WriteSurface.Canvas; if (_surface == null || _canvas == null) { @@ -70,11 +94,12 @@ namespace Avalonia.Skia var createInfo = new DrawingContextImpl.CreateInfo { - Surface = _surface, + Surface = _surface.WriteSurface, Dpi = Dpi, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = _disableLcdRendering, - GrContext = _grContext + GrContext = _grContext, + Gpu = _gpu }; return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++)); @@ -111,8 +136,8 @@ namespace Avalonia.Skia { if (sourceRect.Left == 0 && sourceRect.Top == 0 && sourceRect.Size == destRect.Size) { - _surface.Canvas.Flush(); - _surface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); + _surface.ReadSurface.Canvas.Flush(); + _surface.ReadSurface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); } else using (var image = SnapshotImage()) @@ -127,7 +152,7 @@ namespace Avalonia.Skia /// Image snapshot. public SKImage SnapshotImage() { - return _surface.Snapshot(); + return _surface.ReadSurface.Snapshot(); } /// @@ -178,6 +203,8 @@ namespace Avalonia.Skia /// GPU-accelerated context (optional) /// public GRContext GrContext; + + public ISkiaGpu Gpu; } } } From 445290ecec998d5a0d345429c9c8a781322c5a04 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 27 Sep 2020 15:01:24 +0300 Subject: [PATCH 078/492] Do the manual blit if possible --- src/Avalonia.OpenGL/GlInterface.cs | 13 +++++++++++++ src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs | 4 +++- .../Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs | 15 +++++++++++++-- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 10 ++++++++-- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index ea2fe0a99c..28b62136da 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -117,6 +117,19 @@ namespace Avalonia.OpenGL public delegate int GlCheckFramebufferStatus(int target); [GlEntryPoint("glCheckFramebufferStatus")] public GlCheckFramebufferStatus CheckFramebufferStatus { get; } + + public delegate void GlBlitFramebuffer(int srcX0, + int srcY0, + int srcX1, + int srcY1, + int dstX0, + int dstY0, + int dstX1, + int dstY1, + int mask, + int filter); + [GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0)] + public GlBlitFramebuffer BlitFramebuffer { get; } public delegate void GlGenRenderbuffers(int count, int[] res); [GlEntryPoint("glGenRenderbuffers")] diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index ebb0cb3a1b..f394fe7ac1 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -28,7 +28,9 @@ namespace Avalonia.Skia { SKSurface WriteSurface { get; } SKSurface ReadSurface { get; } - + bool CanBlit { get; } + void Blit(); + } public interface IOpenGlAwareSkiaGpu : ISkiaGpu diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs index 93afd14286..45572ca40b 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -97,15 +97,16 @@ namespace Avalonia.Skia new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); WriteSurface = SKSurface.Create(_grContext, target, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); + CanBlit = gl.BlitFramebuffer != null; } public void Dispose() { using (_glContext.EnsureCurrent()) { - WriteSurface.Dispose(); + WriteSurface?.Dispose(); WriteSurface = null; - ReadSurface.Dispose(); + ReadSurface?.Dispose(); ReadSurface = null; var gl = _glContext.GlInterface; if (_fbo != 0) @@ -120,5 +121,15 @@ namespace Avalonia.Skia public SKSurface WriteSurface { get; private set; } public SKSurface ReadSurface { get; private set; } + public bool CanBlit { get; } + public void Blit() + { + var gl = _glContext.GlInterface; + gl.GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, out var oldRead); + gl.BindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); + gl.BlitFramebuffer(0, 0, _pixelSize.Width, _pixelSize.Height, 0, 0, _pixelSize.Width, _pixelSize.Height, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + gl.BindFramebuffer(GL_READ_FRAMEBUFFER, oldRead); + } } } diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index d6ea229089..9cf0642e99 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -23,6 +23,8 @@ namespace Avalonia.Skia { public SKSurface WriteSurface { get; private set; } public SKSurface ReadSurface { get; private set; } + public bool CanBlit => false; + public void Blit() => throw new NotSupportedException(); public SkiaSurfaceWrapper(SKSurface surface) { @@ -136,8 +138,12 @@ namespace Avalonia.Skia { if (sourceRect.Left == 0 && sourceRect.Top == 0 && sourceRect.Size == destRect.Size) { - _surface.ReadSurface.Canvas.Flush(); - _surface.ReadSurface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); + _surface.WriteSurface.Canvas.Flush(); + if (context.Canvas.TotalMatrix.IsIdentity && _surface.CanBlit && destRect.Top == 0 && + destRect.Left == 0) + _surface.Blit(); + else + _surface.ReadSurface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); } else using (var image = SnapshotImage()) From 75395bc8f548354a4c50dd2b809aaf826e6390a5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 26 Sep 2020 09:25:07 +0200 Subject: [PATCH 079/492] Don't create extra read surface --- src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs | 3 +-- .../Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs | 14 ++++---------- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 18 +++++++++--------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index f394fe7ac1..c06b4af52f 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -26,8 +26,7 @@ namespace Avalonia.Skia public interface ISkiaSurface : IDisposable { - SKSurface WriteSurface { get; } - SKSurface ReadSurface { get; } + SKSurface Surface { get; } bool CanBlit { get; } void Blit(); diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs index 45572ca40b..40347e2cd1 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -90,12 +90,9 @@ namespace Avalonia.Skia throw new OpenGlException("Unable to create FBO with stencil"); } - ReadSurface = SKSurface.Create(_grContext, new GRBackendTexture(pixelSize.Width, pixelSize.Height, false, - new GRGlTextureInfo(GL_TEXTURE_2D, (uint)_texture, (uint)InternalFormat)), SKColorType.Rgba8888); - var target = new GRBackendRenderTarget(pixelSize.Width, pixelSize.Height, 0, 0, new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); - WriteSurface = SKSurface.Create(_grContext, target, + Surface = SKSurface.Create(_grContext, target, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); CanBlit = gl.BlitFramebuffer != null; } @@ -104,10 +101,8 @@ namespace Avalonia.Skia { using (_glContext.EnsureCurrent()) { - WriteSurface?.Dispose(); - WriteSurface = null; - ReadSurface?.Dispose(); - ReadSurface = null; + Surface?.Dispose(); + Surface = null; var gl = _glContext.GlInterface; if (_fbo != 0) { @@ -119,8 +114,7 @@ namespace Avalonia.Skia } } - public SKSurface WriteSurface { get; private set; } - public SKSurface ReadSurface { get; private set; } + public SKSurface Surface { get; private set; } public bool CanBlit { get; } public void Blit() { diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 9cf0642e99..d0bd8ff150 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -21,20 +21,20 @@ namespace Avalonia.Skia class SkiaSurfaceWrapper : ISkiaSurface { - public SKSurface WriteSurface { get; private set; } + public SKSurface Surface { get; private set; } public SKSurface ReadSurface { get; private set; } public bool CanBlit => false; public void Blit() => throw new NotSupportedException(); public SkiaSurfaceWrapper(SKSurface surface) { - WriteSurface = ReadSurface = surface; + Surface = ReadSurface = surface; } public void Dispose() { - WriteSurface?.Dispose(); - WriteSurface = null; + Surface?.Dispose(); + Surface = null; ReadSurface = null; } } @@ -57,7 +57,7 @@ namespace Avalonia.Skia _surface = new SkiaSurfaceWrapper(CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, createInfo.Format)); - _canvas = _surface?.WriteSurface.Canvas; + _canvas = _surface?.Surface.Canvas; if (_surface == null || _canvas == null) { @@ -96,7 +96,7 @@ namespace Avalonia.Skia var createInfo = new DrawingContextImpl.CreateInfo { - Surface = _surface.WriteSurface, + Surface = _surface.Surface, Dpi = Dpi, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = _disableLcdRendering, @@ -138,12 +138,12 @@ namespace Avalonia.Skia { if (sourceRect.Left == 0 && sourceRect.Top == 0 && sourceRect.Size == destRect.Size) { - _surface.WriteSurface.Canvas.Flush(); + _surface.Surface.Canvas.Flush(); if (context.Canvas.TotalMatrix.IsIdentity && _surface.CanBlit && destRect.Top == 0 && destRect.Left == 0) _surface.Blit(); else - _surface.ReadSurface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); + _surface.Surface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); } else using (var image = SnapshotImage()) @@ -158,7 +158,7 @@ namespace Avalonia.Skia /// Image snapshot. public SKImage SnapshotImage() { - return _surface.ReadSurface.Snapshot(); + return _surface.Surface.Snapshot(); } /// From 8555fed86b7070218a07f329a90da4501b2adb42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Sat, 26 Sep 2020 16:13:03 +0200 Subject: [PATCH 080/492] Add failing tests for #4733 and fix property accessor. --- .../Plugins/InpcPropertyAccessorPlugin.cs | 44 ++++++++++++++++--- .../Core/ExpressionObserverTests_Property.cs | 29 ++++++++++++ 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 8fc2a7b77c..3bf6842cd6 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Reflection; using Avalonia.Utilities; @@ -11,8 +12,11 @@ namespace Avalonia.Data.Core.Plugins /// public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin { + private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup = + new Dictionary<(Type, string), PropertyInfo>(); + /// - public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null; + public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null; /// /// Starts monitoring the value of a property on an object. @@ -30,7 +34,7 @@ namespace Avalonia.Data.Core.Plugins reference.TryGetTarget(out object instance); - var p = GetPropertyWithName(instance.GetType(), propertyName); + var p = GetFirstPropertyWithName(instance.GetType(), propertyName); if (p != null) { @@ -44,12 +48,40 @@ namespace Avalonia.Data.Core.Plugins } } - private static PropertyInfo GetPropertyWithName(Type type, string propertyName) + private PropertyInfo GetFirstPropertyWithName(Type type, string propertyName) { - const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | - BindingFlags.Static | BindingFlags.Instance; + var key = (type, propertyName); + + if (!_propertyLookup.TryGetValue(key, out PropertyInfo propertyInfo)) + { + propertyInfo = TryFindAndCacheProperty(type, propertyName); + } + + return propertyInfo; + } + + private PropertyInfo TryFindAndCacheProperty(Type type, string propertyName) + { + PropertyInfo found = null; + + const BindingFlags bindingFlags = + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + var properties = type.GetProperties(bindingFlags); + + foreach (PropertyInfo propertyInfo in properties) + { + if (propertyInfo.Name == propertyName) + { + found = propertyInfo; + + break; + } + } + + _propertyLookup.Add((type, propertyName), found); - return type.GetProperty(propertyName, bindingFlags); + return found; } private class Accessor : PropertyAccessorBase, IWeakSubscriber diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index 784046ac0b..cf98293d6d 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -600,6 +600,24 @@ namespace Avalonia.Base.UnitTests.Data.Core result); } + [Fact] + public void Should_Not_Throw_Exception_On_Duplicate_Properties() + { + // Repro of https://github.com/AvaloniaUI/Avalonia/issues/4733. + var source = new MyViewModel(); + var target = new PropertyAccessorNode("Name", false); + + target.Target = new WeakReference(source); + + var result = new List(); + + target.Subscribe(x => result.Add(x)); + } + + public class MyViewModelBase { public object Name => "Name"; } + + public class MyViewModel : MyViewModelBase { public new string Name => "NewName"; } + private interface INext { int PropertyChangedSubscriptionCount { get; } @@ -664,6 +682,17 @@ namespace Avalonia.Base.UnitTests.Data.Core } } + private class WithNewBar : Class1 + { + private string _bar; + + public new string Bar + { + get { return _bar; } + set { _bar = value; } + } + } + private class Class3 : Class1 { } From 00dbf12cee54609d0b94d0449d2affdeab93ba18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Sat, 26 Sep 2020 16:26:39 +0200 Subject: [PATCH 081/492] Remove not needed code. --- .../Data/Core/ExpressionObserverTests_Property.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index cf98293d6d..32cdd21e04 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -682,17 +682,6 @@ namespace Avalonia.Base.UnitTests.Data.Core } } - private class WithNewBar : Class1 - { - private string _bar; - - public new string Bar - { - get { return _bar; } - set { _bar = value; } - } - } - private class Class3 : Class1 { } From 38a6ea99acd0f9bdfdce92944b67b907ec53e254 Mon Sep 17 00:00:00 2001 From: "oliver.holliday@lontra.co.uk" Date: Sat, 26 Sep 2020 23:33:38 +0100 Subject: [PATCH 082/492] Add new tests for RadialGradient with GradientOffset handling. Fix implementation of Skia renderer to match Direct2d version. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 10 +- .../Media/RadialGradientBrushTests.cs | 108 +++++++++++++++++- 2 files changed, 112 insertions(+), 6 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index a155fd863b..b794c5e00a 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -583,13 +583,13 @@ namespace Avalonia.Skia var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint(); var radius = (float)(radialGradient.Radius * targetSize.Width); - // TODO: There is no SetAlpha in SkiaSharp - //paint.setAlpha(128); + var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint(); // would be nice to cache these shaders possibly? - using (var shader = - SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode)) - { + using (var shader = SKShader.CreateCompose( + SKShader.CreateColor(stopColors.Last()), + SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, stopColors.Reverse().ToArray(), stopOffsets, tileMode)) + ) { paintWrapper.Paint.Shader = shader; } diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs index 9beb860bee..63d4d9992d 100644 --- a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs @@ -18,7 +18,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media public RadialGradientBrushTests() : base(@"Media\RadialGradientBrush") { } - + [Fact] public async Task RadialGradientBrush_RedBlue() { @@ -43,5 +43,111 @@ namespace Avalonia.Direct2D1.RenderTests.Media await RenderToFile(target); CompareImages(); } + + [Fact] + public async Task RadialGradientBrush_RedBlue_Offset_Inside() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new RadialGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + }, + GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative) + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task RadialGradientBrush_RedBlue_Offset_Outside() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new RadialGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + }, + GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative) + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task RadialGradientBrush_RedGreenBlue_Offset_Inside() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new RadialGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Green, Offset = 0.5 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + }, + GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative) + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task RadialGradientBrush_RedGreenBlue_Offset_Outside() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new RadialGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Green, Offset = 0.5 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + }, + GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative) + } + } + }; + + await RenderToFile(target); + CompareImages(); + } } } From 0d9905c0515c666ba7ede4c2583df3d229931d6e Mon Sep 17 00:00:00 2001 From: "oliver.holliday@lontra.co.uk" Date: Sat, 26 Sep 2020 23:50:22 +0100 Subject: [PATCH 083/492] Add expected test files --- ...ientBrush_RedBlue_Offset_Inside.expected.png | Bin 0 -> 9768 bytes ...entBrush_RedBlue_Offset_Outside.expected.png | Bin 0 -> 9840 bytes ...rush_RedGreenBlue_Offset_Inside.expected.png | Bin 0 -> 15084 bytes ...ush_RedGreenBlue_Offset_Outside.expected.png | Bin 0 -> 15227 bytes ...ientBrush_RedBlue_Offset_Inside.expected.png | Bin 0 -> 9768 bytes ...entBrush_RedBlue_Offset_Outside.expected.png | Bin 0 -> 9840 bytes ...rush_RedGreenBlue_Offset_Inside.expected.png | Bin 0 -> 15084 bytes ...ush_RedGreenBlue_Offset_Outside.expected.png | Bin 0 -> 15227 bytes 8 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png create mode 100644 tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png create mode 100644 tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png create mode 100644 tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png create mode 100644 tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4d690abee5fe85b6f61b36f0d754bf590ab6a444 GIT binary patch literal 9768 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..667aa9151e8d97b711ce1dad1f05c7f5f6f83b89 GIT binary patch literal 9840 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..1d660f79d1aa8d0f6bf42fd80bc8546e03a43327 GIT binary patch literal 15084 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�#q)yoM}zR(et5z0jK7Mc$&~6V zXPk+(%IgvVLDf99Xu! zqqk?Zx8OH@4^-!4+)pma>o@CiUwKX5k z)bMpsLn^sZD8hwt8Ah>(aS>P+J{^QCG;s=f;MN(;h7rqCHK}0|O3HFP6BdN4Ccu`_ zc^i7IRdpL4-vgmF;EPuQ@3jY-dwxuMDf*UvwrsHMgo2n66|RYBNqIC5bk%`*Kq_rV zC0cEinXSeGr}+S})smF0g(+PoS2z54;DFpQ$y}z_waZiSea|9#cQ*rmI0+Cuku|d2 z{5Dyc)PG;$O2J(VVOLROvh;mfL$PyXU^Y7fg-V8+vFy*4MS#G=jkHPuzLf+GgW7kz zaBSwVW`WX&sOmnt-PMfS%HKTChhu^^l@xkRY5f<=d~TrwZo3!4?HLESiwwI!T>K}6 zI?y9b$$S?yryZrf&p7pvLlHFMlz$LqfuMh}qGm9??YKIthq>p-VX~U9z3Yi$MaBgD z_{o{xb7J^yQ1UFcrHQnMM6*eExTJF%z3HK~EihEjYdFP!^9iAfzET5%TSPQjA& z>VHj={O^2+V&(V_in_^DQSgtGTl3m2xTvs-15maweI(58<%D&c41GAcp{~aqZLgfF zjtX(7t8Ni@#4Ylxybh@U4zZ!Sc4%3#I!W7D@|i>Cq%)Od_s6SsO|$Kw0L!-3pBP>zM4I}3JNpJD7BCP0a0O&1u(x5NxLisTqfKpz?k6X3VWsjY$uYf)gi5~{WB;kTeJj@@pT}tMPNo%1%kB~wXXA8< zzHM2WdLdO zHIuB|Eacg03Tp8Oi;Y~fCgb<()|fjT6j&CAB!A}7OC5Ff()9Ln%hAuMUznPYsUpa| zL76MSX2MlqEve7i84Ut!zI)>bCb#Z!|KX2M^OaTJ)Dzg6YUnTpcg+iQv+ICyi+CaN zbBq{znEibxO&`D4@DoKQ@;x*S@W^{}~fX z9C0m>BW(;kA>UhQ>0j#i&-l1icxvWWe^UEgMuXQ(g;>~%`gnB>1j7gA1etT-x>oS; zO}IZuWhvUr6yp+E@&l|vBQWmAR6QW>qWTZ#Sy~uXKXHmx??1zWPUZ}uZ~efsMc38( zAD7wvfkig1`jZj(SYL4Kr*CB4?Ylu7xuRhX>|hFC4t~ELPHT!77n<_TM%Ndhfh$uS z^{H13?JS)H1#^_>)q0&IP%$-W`1A0Sv)p_p1OJCWWTIW=ho99+d<1PJ?0vjsjHxSd zVSw2Nm%V~kvkHmOfw~RoqzMmDXk}@^7X4q3SK5Mx7vMPizBw9k=l>#4fi2HunU^2b zU%QPQ%`dTa?k$wXdY@rlw!B}jwo{s^L(#c9fLb+2K|h2b-|2UyxN5X_wdXj)ThMB% z0}9hG`5PJXwy^E=KCEMY)Z#YKyz!`0A$9g|kUYw!EK(-uZCoas`pp*h8{p$l#hy}L zefhR?u|&nDy3IPeog#pwYO1t07j2$4?<);h6i)CX{4R&`9>yX35?q8O$5*@Q7D85S zEK?F3s!>rQWAJ5ykM|$)Y9*9nK|RB0gL_#hgZuh_J9|Yl6$FWlMkNJfHx^AKG|hY7 z`Htb8fQQ7HZc}a=bsZ^P-P_9a-tydgtG{JaDhS;D5uJJRk0m~teW&B%I^t0qR%zf9x$ zGd{UhqWfFWiwQc73_L1}L^rZM9%Dw7Y9ozA`*5ID5yHcry<(}fHt{ETNDeoJFG2

4gnSxr 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 Date: Sun, 27 Sep 2020 09:36:41 +0100 Subject: [PATCH 084/492] Fix stop-offsets being relative to the wrong point after reversal. Copy arrays manually instead of using Linq to resolve PR comment. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 43 +++++++++++++++--- .../Media/RadialGradientBrushTests.cs | 22 +++++++-- ...h_RedGreenBlue_Offset_Outside.expected.png | Bin 15227 -> 12528 bytes ...h_RedGreenBlue_Offset_Outside.expected.png | Bin 15227 -> 12528 bytes 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index b794c5e00a..65989610b8 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -585,12 +585,43 @@ namespace Avalonia.Skia var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint(); - // would be nice to cache these shaders possibly? - using (var shader = SKShader.CreateCompose( - SKShader.CreateColor(stopColors.Last()), - SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, stopColors.Reverse().ToArray(), stopOffsets, tileMode)) - ) { - paintWrapper.Paint.Shader = shader; + if (origin.Equals(center)) + { + // when the origin is the same as the center the Skia RadialGradient acts the same as D2D + using (var shader = + SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode)) + { + paintWrapper.Paint.Shader = shader; + } + } + else + { + // when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D + + // reverse the order of the stops to match D2D + var reversedColors = new SKColor[stopColors.Length]; + Array.Copy(stopColors, reversedColors, stopColors.Length); + Array.Reverse(reversedColors); + + // and then reverse the reference point of the stops + var reversedStops = new float[stopOffsets.Length]; + for (var i = 0; i < stopOffsets.Length; i++) + { + reversedStops[i] = stopOffsets[i]; + if (reversedStops[i] > 0 && reversedStops[i] < 1) + { + reversedStops[i] = Math.Abs(1 - stopOffsets[i]); + } + } + + // compose with a background colour of the final stop to match D2D's behaviour of filling with the final color + using (var shader = SKShader.CreateCompose( + SKShader.CreateColor(reversedColors[0]), + SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode) + )) + { + paintWrapper.Paint.Shader = shader; + } } break; diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs index 63d4d9992d..2941b8dc34 100644 --- a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs @@ -44,6 +44,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media CompareImages(); } + ///

+ /// Tests using a GradientOrigin that falls inside of the circle described by Center/Radius. + /// [Fact] public async Task RadialGradientBrush_RedBlue_Offset_Inside() { @@ -70,6 +73,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media CompareImages(); } + /// + /// Tests using a GradientOrigin that falls outside of the circle described by Center/Radius. + /// [Fact] public async Task RadialGradientBrush_RedBlue_Offset_Outside() { @@ -96,6 +102,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media CompareImages(); } + /// + /// Tests using a GradientOrigin that falls inside of the circle described by Center/Radius. + /// [Fact] public async Task RadialGradientBrush_RedGreenBlue_Offset_Inside() { @@ -114,7 +123,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media new GradientStop { Color = Colors.Green, Offset = 0.5 }, new GradientStop { Color = Colors.Blue, Offset = 1 } }, - GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative) + GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative), + Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative), + Radius = 0.5 } } }; @@ -123,6 +134,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media CompareImages(); } + /// + /// Tests using a GradientOrigin that falls outside of the circle described by Center/Radius. + /// [Fact] public async Task RadialGradientBrush_RedGreenBlue_Offset_Outside() { @@ -138,10 +152,12 @@ namespace Avalonia.Direct2D1.RenderTests.Media GradientStops = { new GradientStop { Color = Colors.Red, Offset = 0 }, - new GradientStop { Color = Colors.Green, Offset = 0.5 }, + new GradientStop { Color = Colors.Green, Offset = 0.25 }, new GradientStop { Color = Colors.Blue, Offset = 1 } }, - GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative) + GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative), + Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative), + Radius = 0.5 } } }; diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png index 91068ca1cfeed8ec85d62d35d8747eea08673478..ea1009b54a6f41f71c850149bf6b1c0d37726eda 100644 GIT binary patch literal 12528 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 Date: Sun, 27 Sep 2020 21:26:25 +0100 Subject: [PATCH 085/492] Revert "make managed deferredrenderer lock global." This reverts commit 28168d571be4912cb85d87629fba798ae5fd3b09. --- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 6 +++--- src/Windows/Avalonia.Win32/WindowImpl.cs | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 0bab0d1657..25a34561fc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -339,7 +339,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_PAINT: { - using (s_rendererLock.Lock()) + using (_rendererLock.Lock()) { if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) { @@ -356,7 +356,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_SIZE: { - using (s_rendererLock.Lock()) + using (_rendererLock.Lock()) { // Do nothing here, just block until the pending frame render is completed on the render thread } @@ -464,7 +464,7 @@ namespace Avalonia.Win32 } } - using (s_rendererLock.Lock()) + using (_rendererLock.Lock()) { return DefWindowProc(hWnd, msg, wParam, lParam); } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 6ca12e571f..848635fe23 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -58,7 +58,7 @@ namespace Avalonia.Win32 private const WindowStyles WindowStateMask = (WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE); private readonly TouchDevice _touchDevice; private readonly MouseDevice _mouseDevice; - private static readonly ManagedDeferredRendererLock s_rendererLock = new ManagedDeferredRendererLock(); + private readonly ManagedDeferredRendererLock _rendererLock; private readonly FramebufferManager _framebuffer; private readonly IGlPlatformSurface _gl; @@ -102,6 +102,7 @@ namespace Avalonia.Win32 IsResizable = true, Decorations = SystemDecorations.Full }; + _rendererLock = new ManagedDeferredRendererLock(); _isUsingComposition = Win32Platform.Options.UseWindowsUIComposition && Win32Platform.WindowsVersion.Major >= 10 && Win32Platform.WindowsVersion.Build >= 16299 && @@ -445,7 +446,7 @@ namespace Avalonia.Win32 return customRendererFactory.Create(root, loop); return Win32Platform.UseDeferredRendering ? - (IRenderer)new DeferredRenderer(root, loop, rendererLock: s_rendererLock) : + (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock) : new ImmediateRenderer(root); } From c02d97cf84f2f2516114540f5aa8f4d281558094 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 29 Sep 2020 14:42:38 +0300 Subject: [PATCH 086/492] allow to create Skia drawing context from existing canvas --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 98528a128a..fd6ccd2eca 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -41,6 +41,11 @@ namespace Avalonia.Skia /// public struct CreateInfo { + /// + /// Canvas to draw to. + /// + public SKCanvas Canvas; + /// /// Surface to draw to. /// @@ -82,7 +87,7 @@ namespace Avalonia.Skia if (_grContext != null) Monitor.Enter(_grContext); Surface = createInfo.Surface; - Canvas = createInfo.Surface.Canvas; + Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas; if (Canvas == null) { From 05f7ee83af016d4f6f10b9294e270299295596e9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 29 Sep 2020 18:23:19 +0100 Subject: [PATCH 087/492] fix resizing of composition surface. --- .../Composition/CompositionEglGlPlatformSurface.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs index c034d8f7f7..2e4b69c0b5 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -39,7 +39,7 @@ namespace Avalonia.Win32 { private readonly EglPlatformOpenGlInterface _egl; private readonly IEglWindowGlPlatformSurfaceInfo _info; - private readonly PixelSize _initialSize; + private PixelSize _currentSize; private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; private static Guid s_Iid = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); private Windows.UI.Composition.Visual _compositionVisual; @@ -53,7 +53,7 @@ namespace Avalonia.Win32 _egl = egl; _surfaceInterop = interopSurface; _info = info; - _initialSize = info.Size; + _currentSize = info.Size; _compositionVisual = compositionVisual; _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); @@ -61,6 +61,13 @@ namespace Avalonia.Win32 public override IGlPlatformSurfaceRenderingSession BeginDraw() { + if (_info.Size != _currentSize) + { + _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); + _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); + _currentSize = _info.Size; + } + var offset = new POINT(); _surfaceInterop.BeginDraw( From f97d43e76507f35871d4d1ed0b6064b7a5ef6f39 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 30 Sep 2020 23:51:00 +0100 Subject: [PATCH 088/492] add failing unit test. --- .../TreeViewTests.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 7022fbf4c1..f1a346e6aa 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Presenters; @@ -13,6 +14,7 @@ using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; +using ReactiveUI; using Xunit; namespace Avalonia.Controls.UnitTests @@ -454,6 +456,43 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Bound_SelectedItem_Should_Not_Be_Cleared_when_Changing_Selection() + { + using (Application()) + { + var dataContext = new TestDataContext(); + + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + DataContext = dataContext + }; + + target.Bind(TreeView.ItemsProperty, new Binding("Items")); + target.Bind(TreeView.SelectedItemProperty, new Binding("SelectedItem")); + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var children = target.GetLogicalChildren().ToList(); + + var selectedValues = new List(); + + dataContext.WhenAnyValue(x => x.SelectedItem) + .Subscribe(x => selectedValues.Add(x)); + + _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Left); + _mouse.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Left); + + Assert.Equal(4, selectedValues.Count); + Assert.Equal(new[] { null, "Item 0", "Item 2" }, selectedValues.ToArray()); + } + } + [Fact] public void LogicalChildren_Should_Be_Set() { @@ -1288,5 +1327,29 @@ namespace Avalonia.Controls.UnitTests private class DerivedTreeView : TreeView { } + + private class TestDataContext : ReactiveObject + { + private int _counter; + private string _selectedItem; + + public TestDataContext() + { + Items = new ObservableCollection(Enumerable.Range(1, 5).Select(i => GenerateItem())); + } + + private string GenerateItem() => $"Item {_counter++.ToString()}"; + + public ObservableCollection Items { get; } + + public string SelectedItem + { + get { return _selectedItem; } + set + { + this.RaiseAndSetIfChanged(ref _selectedItem, value); + } + } + } } } From 0bb91da4959e46a0a35988dc0583185143f1b1f6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 1 Oct 2020 10:45:02 +0100 Subject: [PATCH 089/492] fix test. --- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index f1a346e6aa..9e22c44f44 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -488,7 +488,7 @@ namespace Avalonia.Controls.UnitTests _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Left); _mouse.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Left); - Assert.Equal(4, selectedValues.Count); + Assert.Equal(3, selectedValues.Count); Assert.Equal(new[] { null, "Item 0", "Item 2" }, selectedValues.ToArray()); } } From ec7ae1fefb8cbe3fae2d228981cec7132dddc0e0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 1 Oct 2020 10:45:33 +0100 Subject: [PATCH 090/492] [TreeView] dont raise change events on the clear stage of setting a single selection. --- src/Avalonia.Controls/TreeView.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index b4c30e0149..b9e30620bc 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -219,7 +219,9 @@ namespace Avalonia.Controls private void SelectSingleItem(object item) { + _syncingSelectedItems = true; SelectedItems.Clear(); + _syncingSelectedItems = false; SelectedItems.Add(item); } @@ -353,7 +355,7 @@ namespace Avalonia.Controls MarkItemSelected(item, true); } - if (SelectedItem == null && !_syncingSelectedItems) + if (!_syncingSelectedItems) { SetAndRaise(SelectedItemProperty, ref _selectedItem, items[0]); } From 0416b234b74469f9cde05ae478b16a9a11bca41c Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Thu, 1 Oct 2020 12:52:50 +0300 Subject: [PATCH 091/492] fix #4786 invalidatevisual when foreground changes in TextPresenter --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index f5115a2f7c..8efbe1719e 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -77,7 +77,7 @@ namespace Avalonia.Controls.Presenters static TextPresenter() { - AffectsRender(SelectionBrushProperty); + AffectsRender(SelectionBrushProperty, TextBlock.ForegroundProperty); AffectsMeasure(TextProperty, PasswordCharProperty, RevealPasswordProperty, TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty, TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty); From 4ca07974af61aa0e363aef8570433a9f7124eafc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 1 Oct 2020 11:06:32 +0100 Subject: [PATCH 092/492] remove line that doesnt do anything. --- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 9e22c44f44..d5b3e6350c 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -478,8 +478,6 @@ namespace Avalonia.Controls.UnitTests CreateNodeDataTemplate(target); ApplyTemplates(target); - var children = target.GetLogicalChildren().ToList(); - var selectedValues = new List(); dataContext.WhenAnyValue(x => x.SelectedItem) From 5d43f8f69907191f4ec2a109f43d70dd32267479 Mon Sep 17 00:00:00 2001 From: artyom Date: Thu, 1 Oct 2020 20:23:43 +0300 Subject: [PATCH 093/492] Add a CompositeTransition --- .../Animation/CompositeTransition.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Avalonia.Visuals/Animation/CompositeTransition.cs diff --git a/src/Avalonia.Visuals/Animation/CompositeTransition.cs b/src/Avalonia.Visuals/Animation/CompositeTransition.cs new file mode 100644 index 0000000000..62ed9d5bc1 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/CompositeTransition.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Avalonia.Metadata; + +namespace Avalonia.Animation +{ + /// + /// Defines a composite transition that can be used to combine multiple transitions into one. + /// + /// + /// + /// Instantiate the in XAML and initialize the + /// property in order to have many animations triggered at once. + /// For example, you can combine and . + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// ]]> + /// + /// + /// + public class CompositeTransition : IPageTransition + { + /// + /// Gets or sets the transitions to be executed. Can be defined from XAML. + /// + [Content] + public List Transitions { get; set; } = new List(); + + /// + /// Starts the animation. + /// + /// + /// The control that is being transitioned away from. May be null. + /// + /// + /// The control that is being transitioned to. May be null. + /// + /// + /// Defines the direction of the transition. + /// + /// + /// A that tracks the progress of the animation. + /// + public Task Start(Visual from, Visual to, bool forward) + { + var transitionTasks = Transitions + .Select(transition => transition.Start(from, to, forward)) + .ToList(); + return Task.WhenAll(transitionTasks); + } + } +} From c3693a01cc1d6aa7cb692e92b46ed7298fcdfaae Mon Sep 17 00:00:00 2001 From: artyom Date: Fri, 2 Oct 2020 00:28:13 +0300 Subject: [PATCH 094/492] Ability to set easings --- ...ansition.cs => CompositePageTransition.cs} | 10 ++--- src/Avalonia.Visuals/Animation/CrossFade.cs | 29 ++++++++---- src/Avalonia.Visuals/Animation/PageSlide.cs | 44 +++++++++---------- 3 files changed, 47 insertions(+), 36 deletions(-) rename src/Avalonia.Visuals/Animation/{CompositeTransition.cs => CompositePageTransition.cs} (85%) diff --git a/src/Avalonia.Visuals/Animation/CompositeTransition.cs b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs similarity index 85% rename from src/Avalonia.Visuals/Animation/CompositeTransition.cs rename to src/Avalonia.Visuals/Animation/CompositePageTransition.cs index 62ed9d5bc1..46bbb0bed2 100644 --- a/src/Avalonia.Visuals/Animation/CompositeTransition.cs +++ b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs @@ -6,28 +6,28 @@ using Avalonia.Metadata; namespace Avalonia.Animation { /// - /// Defines a composite transition that can be used to combine multiple transitions into one. + /// Defines a composite page transition that can be used to combine multiple transitions. /// /// /// - /// Instantiate the in XAML and initialize the + /// Instantiate the in XAML and initialize the /// property in order to have many animations triggered at once. /// For example, you can combine and . /// /// /// - /// + /// /// /// - /// + /// /// /// /// ]]> /// /// /// - public class CompositeTransition : IPageTransition + public class CompositePageTransition : IPageTransition { /// /// Gets or sets the transitions to be executed. Can be defined from XAML. diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs index 640f401418..2ba9f6aeba 100644 --- a/src/Avalonia.Visuals/Animation/CrossFade.cs +++ b/src/Avalonia.Visuals/Animation/CrossFade.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Avalonia.Animation.Easings; using Avalonia.Styling; using Avalonia.VisualTree; @@ -74,14 +75,26 @@ namespace Avalonia.Animation /// public TimeSpan Duration { - get - { - return _fadeOutAnimation.Duration; - } - set - { - _fadeOutAnimation.Duration = _fadeInAnimation.Duration = value; - } + get => _fadeOutAnimation.Duration; + set => _fadeOutAnimation.Duration = _fadeInAnimation.Duration = value; + } + + /// + /// Gets or sets element entrance easing. + /// + public Easing EntranceEasing + { + get => _fadeInAnimation.Easing; + set => _fadeInAnimation.Easing = value; + } + + /// + /// Gets or sets element exit easing. + /// + public Easing ExitEasing + { + get => _fadeOutAnimation.Easing; + set => _fadeOutAnimation.Easing = value; } /// diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs index 501c8c0ba4..b545385ad6 100644 --- a/src/Avalonia.Visuals/Animation/PageSlide.cs +++ b/src/Avalonia.Visuals/Animation/PageSlide.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Avalonia.Animation.Easings; using Avalonia.Media; using Avalonia.Styling; using Avalonia.VisualTree; @@ -48,6 +49,16 @@ namespace Avalonia.Animation /// Gets the duration of the animation. /// public SlideAxis Orientation { get; set; } + + /// + /// Gets or sets element entrance easing. + /// + public Easing EntranceEasing { get; set; } = new LinearEasing(); + + /// + /// Gets or sets element exit easing. + /// + public Easing ExitEasing { get; set; } = new LinearEasing(); /// /// Starts the animation. @@ -75,18 +86,12 @@ namespace Avalonia.Animation { var animation = new Animation { - Children = + Easing = ExitEasing, + Children = { new KeyFrame { - Setters = - { - new Setter - { - Property = translateProperty, - Value = 0d - } - }, + Setters = { new Setter { Property = translateProperty, Value = 0d } }, Cue = new Cue(0d) }, new KeyFrame @@ -100,10 +105,10 @@ namespace Avalonia.Animation } }, Cue = new Cue(1d) - } - } + } + }, + Duration = Duration }; - animation.Duration = Duration; tasks.Add(animation.RunAsync(from)); } @@ -112,9 +117,9 @@ namespace Avalonia.Animation to.IsVisible = true; var animation = new Animation { + Easing = EntranceEasing, Children = { - new KeyFrame { Setters = @@ -129,19 +134,12 @@ namespace Avalonia.Animation }, new KeyFrame { - Setters = - { - new Setter - { - Property = translateProperty, - Value = 0d - } - }, + Setters = { new Setter { Property = translateProperty, Value = 0d } }, Cue = new Cue(1d) } - } + }, + Duration = Duration }; - animation.Duration = Duration; tasks.Add(animation.RunAsync(to)); } From 4aecb6b99ae29c926bc715cfec84370cb4b0949c Mon Sep 17 00:00:00 2001 From: artyom Date: Fri, 2 Oct 2020 00:29:06 +0300 Subject: [PATCH 095/492] Better naming for easings --- src/Avalonia.Visuals/Animation/CrossFade.cs | 4 ++-- src/Avalonia.Visuals/Animation/PageSlide.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs index 2ba9f6aeba..0615b854da 100644 --- a/src/Avalonia.Visuals/Animation/CrossFade.cs +++ b/src/Avalonia.Visuals/Animation/CrossFade.cs @@ -82,7 +82,7 @@ namespace Avalonia.Animation /// /// Gets or sets element entrance easing. /// - public Easing EntranceEasing + public Easing FadeInEasing { get => _fadeInAnimation.Easing; set => _fadeInAnimation.Easing = value; @@ -91,7 +91,7 @@ namespace Avalonia.Animation /// /// Gets or sets element exit easing. /// - public Easing ExitEasing + public Easing FadeOutEasing { get => _fadeOutAnimation.Easing; set => _fadeOutAnimation.Easing = value; diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs index b545385ad6..dd5d598e12 100644 --- a/src/Avalonia.Visuals/Animation/PageSlide.cs +++ b/src/Avalonia.Visuals/Animation/PageSlide.cs @@ -53,12 +53,12 @@ namespace Avalonia.Animation /// /// Gets or sets element entrance easing. /// - public Easing EntranceEasing { get; set; } = new LinearEasing(); + public Easing SlideInEasing { get; set; } = new LinearEasing(); /// /// Gets or sets element exit easing. /// - public Easing ExitEasing { get; set; } = new LinearEasing(); + public Easing SlideOutEasing { get; set; } = new LinearEasing(); /// /// Starts the animation. @@ -86,7 +86,7 @@ namespace Avalonia.Animation { var animation = new Animation { - Easing = ExitEasing, + Easing = SlideOutEasing, Children = { new KeyFrame @@ -117,7 +117,7 @@ namespace Avalonia.Animation to.IsVisible = true; var animation = new Animation { - Easing = EntranceEasing, + Easing = SlideInEasing, Children = { new KeyFrame From 06fc7dbc6c21fd0530061ab56e40108ab7261fa3 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 2 Oct 2020 10:48:50 +0300 Subject: [PATCH 096/492] add SelectionForegroundBrush, CaretBrush as AffectRenderer to TextPresenter --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 8efbe1719e..6a6d37605d 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -77,7 +77,8 @@ namespace Avalonia.Controls.Presenters static TextPresenter() { - AffectsRender(SelectionBrushProperty, TextBlock.ForegroundProperty); + AffectsRender(SelectionBrushProperty, TextBlock.ForegroundProperty, + SelectionForegroundBrushProperty, CaretBrushProperty); AffectsMeasure(TextProperty, PasswordCharProperty, RevealPasswordProperty, TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty, TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty); From 3fd7892cc231e488163f10709d230ae266097dde Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 2 Oct 2020 09:33:18 +0100 Subject: [PATCH 097/492] use suggested implementation. --- src/Avalonia.Controls/TreeView.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index b9e30620bc..560cd9e810 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -117,10 +117,8 @@ namespace Avalonia.Controls if (value != null) { if (selectedItems.Count != 1 || selectedItems[0] != value) - { - _syncingSelectedItems = true; - SelectSingleItem(value); - _syncingSelectedItems = false; + { + SelectSingleItem(value); } } else if (SelectedItems.Count > 0) @@ -219,10 +217,13 @@ namespace Avalonia.Controls private void SelectSingleItem(object item) { + var oldValue = SelectedItem; _syncingSelectedItems = true; - SelectedItems.Clear(); - _syncingSelectedItems = false; + SelectedItems.Clear(); SelectedItems.Add(item); + _syncingSelectedItems = false; + + this.RaisePropertyChanged(SelectedItemProperty, oldValue, item); } /// @@ -355,7 +356,7 @@ namespace Avalonia.Controls MarkItemSelected(item, true); } - if (!_syncingSelectedItems) + if (SelectedItem == null && !_syncingSelectedItems) { SetAndRaise(SelectedItemProperty, ref _selectedItem, items[0]); } From 09069e28b22350e2338d5065848e89a8366a36f6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 2 Oct 2020 10:00:38 +0100 Subject: [PATCH 098/492] fix implementation. --- src/Avalonia.Controls/TreeView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 560cd9e810..f0e00e1d7e 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -223,7 +223,7 @@ namespace Avalonia.Controls SelectedItems.Add(item); _syncingSelectedItems = false; - this.RaisePropertyChanged(SelectedItemProperty, oldValue, item); + SetAndRaise(SelectedItemProperty, ref _selectedItem, item); } /// From e893705823862945c4cfa0316e77f8293817d59c Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 2 Oct 2020 16:07:38 +0300 Subject: [PATCH 099/492] add a way to render to canvas with DrawingContext with public api --- .../ExternalCanvasRenderTarget.cs | 32 +++++++++++++++++++ .../Avalonia.Skia/IExternalCanvasSurface.cs | 11 +++++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 2 ++ 3 files changed, 45 insertions(+) create mode 100644 src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs create mode 100644 src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs diff --git a/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs b/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs new file mode 100644 index 0000000000..c9e779797b --- /dev/null +++ b/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs @@ -0,0 +1,32 @@ +using Avalonia.Platform; +using Avalonia.Rendering; + +namespace Avalonia.Skia +{ + internal class ExternalCanvasRenderTarget : IRenderTarget + { + private IExternalCanvasSurface _surface; + + public ExternalCanvasRenderTarget(IExternalCanvasSurface canvas) + { + _surface = canvas; + } + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + var createInfo = new DrawingContextImpl.CreateInfo + { + Canvas = _surface.Canvas, + Dpi = _surface.Dpi, + VisualBrushRenderer = visualBrushRenderer, + DisableTextLcdRendering = true, + }; + + return new DrawingContextImpl(createInfo); + } + + public void Dispose() + { + } + } +} diff --git a/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs b/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs new file mode 100644 index 0000000000..38347f2caa --- /dev/null +++ b/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs @@ -0,0 +1,11 @@ +using SkiaSharp; + +namespace Avalonia.Skia +{ + public interface IExternalCanvasSurface + { + SKCanvas Canvas { get; } + + Vector Dpi { get; } + } +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index b9c1cbc673..397d358ba7 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -145,6 +145,8 @@ namespace Avalonia.Skia { if (surface is IFramebufferPlatformSurface framebufferSurface) return new FramebufferRenderTarget(framebufferSurface); + if (surface is IExternalCanvasSurface canvas) + return new ExternalCanvasRenderTarget(canvas); } throw new NotSupportedException( From 7c250bba7f76f13cc4b3b5f9b6dc93bfdc28a82a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 2 Oct 2020 18:58:00 +0300 Subject: [PATCH 100/492] Added optimized Blit method for render layers --- .../HeadlessPlatformRenderInterface.cs | 9 ++++- src/Avalonia.Visuals/ApiCompatBaseline.txt | 6 ++- .../Platform/IDrawingContextImpl.cs | 11 +++++- .../Rendering/DeferredRenderer.cs | 8 +++- src/Avalonia.Visuals/Rendering/RenderLayer.cs | 2 +- .../SceneGraph/DeferredDrawingContextImpl.cs | 2 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 2 +- .../Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs | 20 +++++++++- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 37 ++++++++++++------- .../ExternalRenderTarget.cs | 2 +- .../Avalonia.Direct2D1/ILayerFactory.cs | 2 +- .../Media/DrawingContextImpl.cs | 4 +- .../Imaging/D2DRenderTargetBitmapImpl.cs | 11 +++++- .../Avalonia.Direct2D1/RenderTarget.cs | 2 +- .../SwapChainRenderTarget.cs | 2 +- tests/Avalonia.UnitTests/TestRoot.cs | 2 +- 16 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 3ae6c8c30e..246610b897 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -238,7 +238,7 @@ namespace Avalonia.Headless } } - class HeadlessBitmapStub : IBitmapImpl, IRenderTargetBitmapImpl, IWriteableBitmapImpl + class HeadlessBitmapStub : IBitmapImpl, IDrawingContextLayerImpl, IWriteableBitmapImpl { public Size Size { get; } @@ -267,6 +267,11 @@ namespace Avalonia.Headless return new HeadlessDrawingContextStub(); } + public void Blit(IDrawingContextImpl context) + { + + } + public Vector Dpi { get; } public PixelSize PixelSize { get; } public int Version { get; set; } @@ -307,7 +312,7 @@ namespace Avalonia.Headless } - public IRenderTargetBitmapImpl CreateLayer(Size size) + public IDrawingContextLayerImpl CreateLayer(Size size) { return new HeadlessBitmapStub(size, new Vector(96, 96)); } diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 5aa497861d..39cf5ee4dd 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -9,7 +9,11 @@ TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' does not exist in the imple CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IDrawingContextLayerImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation. MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract. -Total Issues: 13 +MembersMustExist : Member 'public Avalonia.Utilities.IRef Avalonia.Rendering.RenderLayer.Bitmap.get()' does not exist in the implementation but it does exist in the contract. +Total Issues: 17 diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index c87946b3ea..9eaef0c616 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -99,7 +99,7 @@ namespace Avalonia.Platform /// has to do a format conversion each time a standard render target bitmap is rendered, /// but a layer created via this method has no such overhead. /// - IRenderTargetBitmapImpl CreateLayer(Size size); + IDrawingContextLayerImpl CreateLayer(Size size); /// /// Pushes a clip rectangle. @@ -156,4 +156,13 @@ namespace Avalonia.Platform /// Custom draw operation void Custom(ICustomDrawOperation custom); } + + public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl + { + /// + /// Does optimized blit with Src blend mode + /// + /// + void Blit(IDrawingContextImpl context); + } } diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 8c020fc073..71b9553256 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -489,6 +489,7 @@ namespace Avalonia.Rendering var clientRect = new Rect(scene.Size); + var firstLayer = true; foreach (var layer in scene.Layers) { var bitmap = Layers[layer.LayerRoot].Bitmap; @@ -501,7 +502,10 @@ namespace Avalonia.Rendering if (layer.OpacityMask == null) { - context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect); + if (firstLayer) + bitmap.Item.Blit(context); + else + context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect); } else { @@ -512,6 +516,8 @@ namespace Avalonia.Rendering { context.PopGeometryClip(); } + + firstLayer = false; } if (_overlay != null) diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index ddf5f4e5cf..7a79e45716 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs @@ -20,7 +20,7 @@ namespace Avalonia.Rendering IsEmpty = true; } - public IRef Bitmap { get; private set; } + public IRef Bitmap { get; private set; } public bool IsEmpty { get; set; } public double Scaling { get; private set; } public Size Size { get; private set; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 4a364998fd..e5d57966e4 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -218,7 +218,7 @@ namespace Avalonia.Rendering.SceneGraph ++_drawOperationindex; } } - public IRenderTargetBitmapImpl CreateLayer(Size size) + public IDrawingContextLayerImpl CreateLayer(Size size) { throw new NotSupportedException("Creating layers on a deferred drawing context not supported"); } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 669da8d03c..231f0ab328 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -422,7 +422,7 @@ namespace Avalonia.Skia } /// - public IRenderTargetBitmapImpl CreateLayer(Size size) + public IDrawingContextLayerImpl CreateLayer(Size size) { return CreateRenderTarget( size); } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index f606929b76..961b6bb284 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using Avalonia.Logging; using Avalonia.OpenGL; using Avalonia.OpenGL.Imaging; using Avalonia.OpenGL.Surfaces; @@ -10,6 +12,7 @@ namespace Avalonia.Skia { private GRContext _grContext; private IGlContext _glContext; + private bool? _canCreateSurfaces; public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes) { @@ -45,7 +48,22 @@ namespace Avalonia.Skia public ISkiaSurface TryCreateSurface(PixelSize size) { - return new FboSkiaSurface(_grContext, _glContext, size); + size = new PixelSize(Math.Max(size.Width, 1), Math.Max(size.Height, 1)); + if (_canCreateSurfaces == false) + return null; + try + { + var surface = new FboSkiaSurface(_grContext, _glContext, size); + _canCreateSurfaces = true; + return surface; + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL") + ?.Log(this, "Unable to create a Skia-compatible FBO manually"); + _canCreateSurfaces ??= false; + return null; + } } public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi); diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index d0bd8ff150..314a3604e9 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Reactive.Disposables; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Skia.Helpers; @@ -11,7 +12,7 @@ namespace Avalonia.Skia /// /// Skia render target that writes to a surface. /// - internal class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl + internal class SurfaceRenderTarget : IDrawingContextLayerImpl, IDrawableBitmapImpl { private readonly ISkiaSurface _surface; private readonly SKCanvas _canvas; @@ -133,23 +134,33 @@ namespace Avalonia.Skia } } - /// - public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) + public void Blit(IDrawingContextImpl contextImpl) { - if (sourceRect.Left == 0 && sourceRect.Top == 0 && sourceRect.Size == destRect.Size) + var context = (DrawingContextImpl)contextImpl; + + if (_surface.CanBlit) { _surface.Surface.Canvas.Flush(); - if (context.Canvas.TotalMatrix.IsIdentity && _surface.CanBlit && destRect.Top == 0 && - destRect.Left == 0) - _surface.Blit(); - else - _surface.Surface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); + + // This should set the render target as the current FBO + context.Canvas.Clear(); + context.Canvas.Flush(); + _surface.Blit(); } else - using (var image = SnapshotImage()) - { - context.Canvas.DrawImage(image, sourceRect, destRect, paint); - } + { + var oldMatrix = context.Canvas.TotalMatrix; + context.Canvas.ResetMatrix(); + _surface.Surface.Draw(context.Canvas, 0, 0, null); + context.Canvas.SetMatrix(oldMatrix); + } + } + + /// + public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) + { + using var image = SnapshotImage(); + context.Canvas.DrawImage(image, sourceRect, destRect, paint); } /// diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index aad50331d2..2c0adcac32 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -38,7 +38,7 @@ namespace Avalonia.Direct2D1 }); } - public IRenderTargetBitmapImpl CreateLayer(Size size) + public IDrawingContextLayerImpl CreateLayer(Size size) { var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget(); return D2DRenderTargetBitmapImpl.CreateCompatible(renderTarget, size); diff --git a/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs b/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs index 08273f1bb2..a15bc0056a 100644 --- a/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs +++ b/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs @@ -4,6 +4,6 @@ namespace Avalonia.Direct2D1 { public interface ILayerFactory { - IRenderTargetBitmapImpl CreateLayer(Size size); + IDrawingContextLayerImpl CreateLayer(Size size); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index e0de40525f..cf47668a63 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -334,7 +334,7 @@ namespace Avalonia.Direct2D1.Media } } - public IRenderTargetBitmapImpl CreateLayer(Size size) + public IDrawingContextLayerImpl CreateLayer(Size size) { if (_layerFactory != null) { @@ -345,7 +345,7 @@ namespace Avalonia.Direct2D1.Media var platform = AvaloniaLocator.Current.GetService(); var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); var pixelSize = PixelSize.FromSizeWithDpi(size, dpi); - return platform.CreateRenderTargetBitmap(pixelSize, dpi); + return (IDrawingContextLayerImpl)platform.CreateRenderTargetBitmap(pixelSize, dpi); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 3a3f9a9f7d..fa56ffb4a7 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -8,7 +8,7 @@ using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media.Imaging { - public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory + public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IDrawingContextLayerImpl, ILayerFactory { private readonly BitmapRenderTarget _renderTarget; @@ -34,7 +34,14 @@ namespace Avalonia.Direct2D1.Media.Imaging return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, null, () => Version++); } - public IRenderTargetBitmapImpl CreateLayer(Size size) + public void Blit(IDrawingContextImpl context) + { + var rc = new Rect(0, 0, PixelSize.Width, PixelSize.Height); + context.DrawBitmap(RefCountable.CreateUnownedNotClonable(this), + 1, rc, rc); + } + + public IDrawingContextLayerImpl CreateLayer(Size size) { return CreateCompatible(_renderTarget, size); } diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index 4433c8d50a..d04c616bd9 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1 return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget); } - public IRenderTargetBitmapImpl CreateLayer(Size size) + public IDrawingContextLayerImpl CreateLayer(Size size) { return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size); } diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 5f4d06dab0..f319cfae03 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -35,7 +35,7 @@ namespace Avalonia.Direct2D1 return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain); } - public IRenderTargetBitmapImpl CreateLayer(Size size) + public IDrawingContextLayerImpl CreateLayer(Size size) { if (_deviceContext == null) { diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index b6f3a020e8..b69bf990d9 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -72,7 +72,7 @@ namespace Avalonia.UnitTests dc.Setup(x => x.CreateLayer(It.IsAny())).Returns(() => { var layerDc = new Mock(); - var layer = new Mock(); + var layer = new Mock(); layer.Setup(x => x.CreateDrawingContext(It.IsAny())).Returns(layerDc.Object); return layer.Object; }); From 858b78b59e92abb2809cf7f80b4fa2e4a1db050d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Fri, 2 Oct 2020 18:18:13 +0200 Subject: [PATCH 101/492] Menu interaction handler fixes. --- src/Avalonia.Controls/MenuItem.cs | 4 ++-- .../Platform/DefaultMenuInteractionHandler.cs | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 3d8ab3ae48..7d4fef009d 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -101,7 +101,7 @@ namespace Avalonia.Controls private ICommand? _command; private bool _commandCanExecute = true; - private Popup _popup; + private Popup? _popup; /// /// Initializes static members of the class. @@ -145,7 +145,7 @@ namespace Avalonia.Controls { var parent = x as Control; return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ?? - Observable.Return(null); + Observable.Return(null); }); this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope); diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 6d6398bcda..a54d1ce308 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -148,6 +148,7 @@ namespace Avalonia.Controls.Platform { case Key.Up: case Key.Down: + { if (item?.IsTopLevel == true) { if (item.HasSubMenu && !item.IsSubMenuOpen) @@ -161,8 +162,10 @@ namespace Avalonia.Controls.Platform goto default; } break; + } case Key.Left: + { if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen) { parent.Close(); @@ -174,8 +177,10 @@ namespace Avalonia.Controls.Platform goto default; } break; + } case Key.Right: + { if (item != null && !item.IsTopLevel && item.HasSubMenu) { Open(item, true); @@ -186,8 +191,10 @@ namespace Avalonia.Controls.Platform goto default; } break; + } case Key.Enter: + { if (item != null) { if (!item.HasSubMenu) @@ -202,12 +209,14 @@ namespace Avalonia.Controls.Platform e.Handled = true; } break; + } case Key.Escape: - if (item?.Parent != null) + { + if (item?.Parent is IMenuElement parent) { - item.Parent.Close(); - item.Parent.Focus(); + parent.Close(); + parent.Focus(); } else { @@ -216,8 +225,10 @@ namespace Avalonia.Controls.Platform e.Handled = true; break; + } default: + { var direction = e.Key.ToNavigationDirection(); if (direction.HasValue) @@ -246,6 +257,7 @@ namespace Avalonia.Controls.Platform } break; + } } if (!e.Handled && item?.Parent is IMenuItem parentItem) From 05a2fe3d7d468b454ce5f9232e23ca70ca9dd76f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 2 Oct 2020 21:44:45 +0300 Subject: [PATCH 102/492] Fixed FBO stencil info --- src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs index 40347e2cd1..a1fbba3b2e 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -90,7 +90,7 @@ namespace Avalonia.Skia throw new OpenGlException("Unable to create FBO with stencil"); } - var target = new GRBackendRenderTarget(pixelSize.Width, pixelSize.Height, 0, 0, + var target = new GRBackendRenderTarget(pixelSize.Width, pixelSize.Height, 0, 8, new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); Surface = SKSurface.Create(_grContext, target, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); From b5c5cc7192b8298c6136f331e3256a58a778256b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 2 Oct 2020 20:27:21 +0100 Subject: [PATCH 103/492] protect d3d calls. --- .../CompositionEglGlPlatformSurface.cs | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs index 2e4b69c0b5..b30043fedd 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -25,9 +25,11 @@ namespace Avalonia.Win32 public IBlurHost AttachToCompositionTree(IntPtr hwnd) { - _surfaceInterop = CompositionHost.Instance.InitialiseWindowCompositionTree(hwnd, out _surface, out var blurHost); - - return blurHost; + using (_egl.PrimaryContext.MakeCurrent()) + { + _surfaceInterop = CompositionHost.Instance.InitialiseWindowCompositionTree(hwnd, out _surface, out var blurHost); + return blurHost; + } } public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() @@ -55,27 +57,38 @@ namespace Avalonia.Win32 _info = info; _currentSize = info.Size; _compositionVisual = compositionVisual; - _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); + + using (_egl.PrimaryContext.MakeCurrent()) + { + _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); + } + _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); } public override IGlPlatformSurfaceRenderingSession BeginDraw() { - if (_info.Size != _currentSize) + IntPtr texture; + EglSurface surface; + + using (_egl.PrimaryEglContext.EnsureCurrent()) { - _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); - _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); - _currentSize = _info.Size; - } + if (_info.Size != _currentSize) + { + _surfaceInterop.Resize(new POINT { X = _info.Size.Width, Y = _info.Size.Height }); + _compositionVisual.Size = new System.Numerics.Vector2(_info.Size.Width, _info.Size.Height); + _currentSize = _info.Size; + } - var offset = new POINT(); + var offset = new POINT(); - _surfaceInterop.BeginDraw( - IntPtr.Zero, - ref s_Iid, - out IntPtr texture, ref offset); + _surfaceInterop.BeginDraw( + IntPtr.Zero, + ref s_Iid, + out texture, ref offset); - var surface = (_egl.Display as AngleWin32EglDisplay).WrapDirect3D11Texture(_egl, texture, offset.X, offset.Y, _info.Size.Width, _info.Size.Height); + surface = (_egl.Display as AngleWin32EglDisplay).WrapDirect3D11Texture(_egl, texture, offset.X, offset.Y, _info.Size.Width, _info.Size.Height); + } return base.BeginDraw(surface, _info, () => { _surfaceInterop.EndDraw(); Marshal.Release(texture); surface.Dispose(); }, true); } From a4e4d6469d686e58fc52bea1c1462bd927a0c2a8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 2 Oct 2020 22:30:19 +0100 Subject: [PATCH 104/492] fix nit in test --- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index d5b3e6350c..b4b14ba409 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1333,11 +1333,9 @@ namespace Avalonia.Controls.UnitTests public TestDataContext() { - Items = new ObservableCollection(Enumerable.Range(1, 5).Select(i => GenerateItem())); + Items = new ObservableCollection(Enumerable.Range(1, 5).Select(i => $"Item {i}")); } - private string GenerateItem() => $"Item {_counter++.ToString()}"; - public ObservableCollection Items { get; } public string SelectedItem From 5a675a073fbeb471bdec1c67e1ea99cc580cfb7f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 2 Oct 2020 23:36:46 +0100 Subject: [PATCH 105/492] fix test. --- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index b4b14ba409..b90b74d794 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1333,7 +1333,7 @@ namespace Avalonia.Controls.UnitTests public TestDataContext() { - Items = new ObservableCollection(Enumerable.Range(1, 5).Select(i => $"Item {i}")); + Items = new ObservableCollection(Enumerable.Range(0, 5).Select(i => $"Item {i}")); } public ObservableCollection Items { get; } From 754da3470ed5bb5d8e5084be88d5877503596672 Mon Sep 17 00:00:00 2001 From: Murdo R Ergeaux Date: Sat, 3 Oct 2020 08:35:19 +0100 Subject: [PATCH 106/492] Hardcode DatePicker month/day to prevent unrepresentable dates --- src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs index daee7968eb..a0e8c03195 100644 --- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs +++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs @@ -486,9 +486,9 @@ namespace Avalonia.Controls.Primitives switch (panelType) { case DateTimePickerPanelType.Year: - return new DateTime(value, FormatDate.Month, FormatDate.Day).ToString(ItemFormat); + return new DateTime(value, 1, 1).ToString(ItemFormat); case DateTimePickerPanelType.Month: - return new DateTime(FormatDate.Year, value, FormatDate.Day).ToString(ItemFormat); + return new DateTime(FormatDate.Year, value, 1).ToString(ItemFormat); case DateTimePickerPanelType.Day: return new DateTime(FormatDate.Year, FormatDate.Month, value).ToString(ItemFormat); case DateTimePickerPanelType.Hour: From 731974b04146d4ffcb1a972cfd623f6f6bb57a0b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 3 Oct 2020 18:42:03 +0300 Subject: [PATCH 107/492] Only do manual layer fbo management on Windows --- .../HeadlessPlatformRenderInterface.cs | 2 ++ .../Platform/IDrawingContextImpl.cs | 5 +++++ .../Rendering/DeferredRenderer.cs | 2 +- src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs | 3 +-- .../Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs | 7 ++++++- src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs | 9 +++++++++ src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 16 ++++++---------- .../Media/Imaging/D2DRenderTargetBitmapImpl.cs | 12 +++++------- 8 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 246610b897..f88310a390 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -272,6 +272,8 @@ namespace Avalonia.Headless } + public bool CanBlit => false; + public Vector Dpi { get; } public PixelSize PixelSize { get; } public int Version { get; set; } diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 9eaef0c616..17571cb1dd 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -164,5 +164,10 @@ namespace Avalonia.Platform /// /// void Blit(IDrawingContextImpl context); + + /// + /// Returns true if layer supports optimized blit + /// + bool CanBlit { get; } } } diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 71b9553256..15e14935ca 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -502,7 +502,7 @@ namespace Avalonia.Rendering if (layer.OpacityMask == null) { - if (firstLayer) + if (firstLayer && bitmap.Item.CanBlit) bitmap.Item.Blit(context); else context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect); diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index c06b4af52f..869c261f1b 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -28,8 +28,7 @@ namespace Avalonia.Skia { SKSurface Surface { get; } bool CanBlit { get; } - void Blit(); - + void Blit(SKCanvas canvas); } public interface IOpenGlAwareSkiaGpu : ISkiaGpu diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs index a1fbba3b2e..9ee8d698ba 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -116,8 +116,13 @@ namespace Avalonia.Skia public SKSurface Surface { get; private set; } public bool CanBlit { get; } - public void Blit() + public void Blit(SKCanvas canvas) { + // This should set the render target as the current FBO + // which is definitely not the best method, but it works + canvas.Clear(); + canvas.Flush(); + var gl = _glContext.GlInterface; gl.GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, out var oldRead); gl.BindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index 961b6bb284..c02d813e24 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using Avalonia.Logging; using Avalonia.OpenGL; using Avalonia.OpenGL.Imaging; @@ -48,6 +49,14 @@ namespace Avalonia.Skia public ISkiaSurface TryCreateSurface(PixelSize size) { + // Only windows platform needs our FBO trickery + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return null; + + // Blit feature requires glBlitFramebuffer + if (_glContext.GlInterface.BlitFramebuffer == null) + return null; + size = new PixelSize(Math.Max(size.Width, 1), Math.Max(size.Height, 1)); if (_canCreateSurfaces == false) return null; diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 314a3604e9..61b599a731 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -23,20 +23,18 @@ namespace Avalonia.Skia class SkiaSurfaceWrapper : ISkiaSurface { public SKSurface Surface { get; private set; } - public SKSurface ReadSurface { get; private set; } public bool CanBlit => false; - public void Blit() => throw new NotSupportedException(); + public void Blit(SKCanvas canvas) => throw new NotSupportedException(); public SkiaSurfaceWrapper(SKSurface surface) { - Surface = ReadSurface = surface; + Surface = surface; } public void Dispose() { Surface?.Dispose(); Surface = null; - ReadSurface = null; } } @@ -141,11 +139,7 @@ namespace Avalonia.Skia if (_surface.CanBlit) { _surface.Surface.Canvas.Flush(); - - // This should set the render target as the current FBO - context.Canvas.Clear(); - context.Canvas.Flush(); - _surface.Blit(); + _surface.Blit(context.Canvas); } else { @@ -155,7 +149,9 @@ namespace Avalonia.Skia context.Canvas.SetMatrix(oldMatrix); } } - + + public bool CanBlit => true; + /// public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index fa56ffb4a7..9a0e2ec00c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Utilities; @@ -34,12 +35,9 @@ namespace Avalonia.Direct2D1.Media.Imaging return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, null, () => Version++); } - public void Blit(IDrawingContextImpl context) - { - var rc = new Rect(0, 0, PixelSize.Width, PixelSize.Height); - context.DrawBitmap(RefCountable.CreateUnownedNotClonable(this), - 1, rc, rc); - } + public void Blit(IDrawingContextImpl context) => throw new NotSupportedException(); + + public bool CanBlit => false; public IDrawingContextLayerImpl CreateLayer(Size size) { From c4bbe3ac5e0becdd7d7ff25eaebb2429b9bba0f3 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 3 Oct 2020 23:15:16 +0300 Subject: [PATCH 108/492] Added WGL support --- samples/RenderDemo/App.xaml.cs | 4 + src/Avalonia.Base/AvaloniaLocator.cs | 17 ++ .../Interop/UnmanagedMethods.cs | 92 +++++++++ .../Avalonia.Win32/OpenGl/WglConsts.cs | 62 ++++++ .../Avalonia.Win32/OpenGl/WglContext.cs | 85 ++++++++ .../Avalonia.Win32/OpenGl/WglDisplay.cs | 183 ++++++++++++++++++ .../OpenGl/WglGlPlatformSurface.cs | 85 ++++++++ .../OpenGl/WglPlatformOpenGlInterface.cs | 39 ++++ .../OpenGl/WglRestoreContext.cs | 39 ++++ src/Windows/Avalonia.Win32/Win32GlManager.cs | 18 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 10 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 13 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 9 +- 13 files changed, 635 insertions(+), 21 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs create mode 100644 src/Windows/Avalonia.Win32/OpenGl/WglContext.cs create mode 100644 src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs create mode 100644 src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs create mode 100644 src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs create mode 100644 src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index 233160b025..340ccdae19 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -18,6 +18,10 @@ namespace RenderDemo // App configuration, used by the entry point and previewer static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() + .With(new Win32PlatformOptions + { + OverlayPopups = true, + }) .UsePlatformDetect() .UseReactiveUI() .LogToDebug(); diff --git a/src/Avalonia.Base/AvaloniaLocator.cs b/src/Avalonia.Base/AvaloniaLocator.cs index f9bbe38bec..3163d15c1b 100644 --- a/src/Avalonia.Base/AvaloniaLocator.cs +++ b/src/Avalonia.Base/AvaloniaLocator.cs @@ -54,6 +54,23 @@ namespace Avalonia return _locator; } + public AvaloniaLocator ToLazy(Func func) where TImlp : TService + { + var constructed = false; + TImlp instance = default; + _locator._registry[typeof (TService)] = () => + { + if (!constructed) + { + instance = func(); + constructed = true; + } + + return instance; + }; + return _locator; + } + public AvaloniaLocator ToSingleton() where TImpl : class, TService, new() { TImpl instance = null; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index b7c68c4b95..5feb6c9e46 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1011,6 +1011,10 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool InvalidateRect(IntPtr hWnd, RECT* lpRect, bool bErase); + + + [DllImport("user32.dll")] + public static extern bool ValidateRect(IntPtr hWnd, IntPtr lpRect); [DllImport("user32.dll")] public static extern bool IsWindowEnabled(IntPtr hWnd); @@ -1292,6 +1296,41 @@ namespace Avalonia.Win32.Interop [DllImport("gdi32.dll")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject); + [DllImport("gdi32.dll")] + public static extern int ChoosePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd); + + [DllImport("gdi32.dll")] + public static extern int DescribePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd); + + [DllImport("gdi32.dll")] + public static extern int SetPixelFormat(IntPtr hdc, int iPixelFormat, ref PixelFormatDescriptor pfd); + + + [DllImport("gdi32.dll")] + public static extern int DescribePixelFormat(IntPtr hdc, int iPixelFormat, int bytes, ref PixelFormatDescriptor pfd); + + [DllImport("gdi32.dll")] + public static extern bool SwapBuffers(IntPtr hdc); + + [DllImport("opengl32.dll")] + public static extern IntPtr wglCreateContext(IntPtr hdc); + + [DllImport("opengl32.dll")] + public static extern bool wglDeleteContext(IntPtr context); + + + [DllImport("opengl32.dll")] + public static extern bool wglMakeCurrent(IntPtr hdc, IntPtr context); + + [DllImport("opengl32.dll")] + public static extern IntPtr wglGetCurrentContext(); + + [DllImport("opengl32.dll")] + public static extern IntPtr wglGetCurrentDC(); + + [DllImport("opengl32.dll", CharSet = CharSet.Ansi)] + public static extern IntPtr wglGetProcAddress(string name); + [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr CreateFileMapping(IntPtr hFile, IntPtr lpFileMappingAttributes, @@ -2121,4 +2160,57 @@ namespace Avalonia.Win32.Interop public bool fNC; public bool fWide; } + + [Flags] + internal enum PixelFormatDescriptorFlags : uint + { + PFD_DOUBLEBUFFER = 0x00000001, + PFD_STEREO = 0x00000002, + PFD_DRAW_TO_WINDOW = 0x00000004, + PFD_DRAW_TO_BITMAP = 0x00000008, + PFD_SUPPORT_GDI = 0x00000010, + PFD_SUPPORT_OPENGL = 0x00000020, + PFD_GENERIC_FORMAT = 0x00000040, + PFD_NEED_PALETTE = 0x00000080, + PFD_NEED_SYSTEM_PALETTE = 0x00000100, + PFD_SWAP_EXCHANGE = 0x00000200, + PFD_SWAP_COPY = 0x00000400, + PFD_SWAP_LAYER_BUFFERS = 0x00000800, + PFD_GENERIC_ACCELERATED = 0x00001000, + PFD_SUPPORT_DIRECTDRAW = 0x00002000, + PFD_DEPTH_DONTCARE = 0x20000000, + PFD_DOUBLEBUFFER_DONTCARE = 0x40000000, + PFD_STEREO_DONTCARE = 0x80000000, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PixelFormatDescriptor + { + public ushort Size; + public ushort Version; + public PixelFormatDescriptorFlags Flags; + public byte PixelType; + public byte ColorBits; + public byte RedBits; + public byte RedShift; + public byte GreenBits; + public byte GreenShift; + public byte BlueBits; + public byte BlueShift; + public byte AlphaBits; + public byte AlphaShift; + public byte AccumBits; + public byte AccumRedBits; + public byte AccumGreenBits; + public byte AccumBlueBits; + public byte AccumAlphaBits; + public byte DepthBits; + public byte StencilBits; + public byte AuxBuffers; + public byte LayerType; + private byte Reserved; + public uint LayerMask; + public uint VisibleMask; + public uint DamageMask; + } } diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs b/src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs new file mode 100644 index 0000000000..3cb3dc25bb --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/WglConsts.cs @@ -0,0 +1,62 @@ +namespace Avalonia.Win32.OpenGl +{ + internal class WglConsts + { + public const int WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; + public const int WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; + public const int WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; + public const int WGL_CONTEXT_FLAGS_ARB = 0x2094; + public const int WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; + public const int WGL_NUMBER_PIXEL_FORMATS_ARB = 0x2000; + + public const int WGL_DRAW_TO_WINDOW_ARB = 0x2001; + public const int WGL_DRAW_TO_BITMAP_ARB = 0x2002; + public const int WGL_ACCELERATION_ARB = 0x2003; + public const int WGL_NEED_PALETTE_ARB = 0x2004; + public const int WGL_NEED_SYSTEM_PALETTE_ARB = 0x2005; + public const int WGL_SWAP_LAYER_BUFFERS_ARB = 0x2006; + public const int WGL_SWAP_METHOD_ARB = 0x2007; + public const int WGL_NUMBER_OVERLAYS_ARB = 0x2008; + public const int WGL_NUMBER_UNDERLAYS_ARB = 0x2009; + public const int WGL_TRANSPARENT_ARB = 0x200A; + public const int WGL_TRANSPARENT_RED_VALUE_ARB = 0x2037; + public const int WGL_TRANSPARENT_GREEN_VALUE_ARB = 0x2038; + public const int WGL_TRANSPARENT_BLUE_VALUE_ARB = 0x2039; + public const int WGL_TRANSPARENT_ALPHA_VALUE_ARB = 0x203A; + public const int WGL_TRANSPARENT_INDEX_VALUE_ARB = 0x203B; + public const int WGL_SHARE_DEPTH_ARB = 0x200C; + public const int WGL_SHARE_STENCIL_ARB = 0x200D; + public const int WGL_SHARE_ACCUM_ARB = 0x200E; + public const int WGL_SUPPORT_GDI_ARB = 0x200F; + public const int WGL_SUPPORT_OPENGL_ARB = 0x2010; + public const int WGL_DOUBLE_BUFFER_ARB = 0x2011; + public const int WGL_STEREO_ARB = 0x2012; + public const int WGL_PIXEL_TYPE_ARB = 0x2013; + public const int WGL_COLOR_BITS_ARB = 0x2014; + public const int WGL_RED_BITS_ARB = 0x2015; + public const int WGL_RED_SHIFT_ARB = 0x2016; + public const int WGL_GREEN_BITS_ARB = 0x2017; + public const int WGL_GREEN_SHIFT_ARB = 0x2018; + public const int WGL_BLUE_BITS_ARB = 0x2019; + public const int WGL_BLUE_SHIFT_ARB = 0x201A; + public const int WGL_ALPHA_BITS_ARB = 0x201B; + public const int WGL_ALPHA_SHIFT_ARB = 0x201C; + public const int WGL_ACCUM_BITS_ARB = 0x201D; + public const int WGL_ACCUM_RED_BITS_ARB = 0x201E; + public const int WGL_ACCUM_GREEN_BITS_ARB = 0x201F; + public const int WGL_ACCUM_BLUE_BITS_ARB = 0x2020; + public const int WGL_ACCUM_ALPHA_BITS_ARB = 0x2021; + public const int WGL_DEPTH_BITS_ARB = 0x2022; + public const int WGL_STENCIL_BITS_ARB = 0x2023; + public const int WGL_AUX_BUFFERS_ARB = 0x2024; + public const int WGL_NO_ACCELERATION_ARB = 0x2025; + public const int WGL_GENERIC_ACCELERATION_ARB = 0x2026; + public const int WGL_FULL_ACCELERATION_ARB = 0x2027; + public const int WGL_SWAP_EXCHANGE_ARB = 0x2028; + public const int WGL_SWAP_COPY_ARB = 0x2029; + public const int WGL_SWAP_UNDEFINED_ARB = 0x202A; + public const int WGL_TYPE_RGBA_ARB = 0x202B; + public const int WGL_TYPE_COLORINDEX_ARB = 0x202C; + + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs new file mode 100644 index 0000000000..d6633ddb61 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs @@ -0,0 +1,85 @@ +using System; +using System.Reactive.Disposables; +using Avalonia.OpenGL; +using Avalonia.Win32.Interop; +using static Avalonia.Win32.Interop.UnmanagedMethods; +using static Avalonia.Win32.OpenGl.WglConsts; + +namespace Avalonia.Win32.OpenGl +{ + class WglContext : IGlContext + { + private object _lock = new object(); + private readonly WglContext _sharedWith; + private readonly IntPtr _context; + private readonly IntPtr _hWnd; + private readonly IntPtr _dc; + private readonly int _pixelFormat; + private readonly PixelFormatDescriptor _formatDescriptor; + public IntPtr Handle => _context; + + public WglContext(WglContext sharedWith, GlVersion version, IntPtr context, IntPtr hWnd, IntPtr dc, int pixelFormat, + PixelFormatDescriptor formatDescriptor) + { + Version = version; + _sharedWith = sharedWith; + _context = context; + _hWnd = hWnd; + _dc = dc; + _pixelFormat = pixelFormat; + _formatDescriptor = formatDescriptor; + StencilSize = formatDescriptor.StencilBits; + using (MakeCurrent()) + GlInterface = new GlInterface(version, proc => + { + var ext = wglGetProcAddress(proc); + if (ext != IntPtr.Zero) + return ext; + return GetProcAddress(WglDisplay.OpenGl32Handle, proc); + }); + + } + + public void Dispose() + { + wglDeleteContext(_context); + ReleaseDC(_hWnd, _dc); + DestroyWindow(_hWnd); + } + + public GlVersion Version { get; } + public GlInterface GlInterface { get; } + public int SampleCount { get; } + public int StencilSize { get; } + + private bool IsCurrent => wglGetCurrentContext() == _context && wglGetCurrentDC() == _dc; + public IDisposable MakeCurrent() + { + if(IsCurrent) + return Disposable.Empty; + return new WglRestoreContext(_dc, _context, _lock); + } + + public IDisposable EnsureCurrent() => MakeCurrent(); + + + public IntPtr CreateConfiguredDeviceContext(IntPtr hWnd) + { + var dc = GetDC(hWnd); + var fmt = _formatDescriptor; + SetPixelFormat(dc, _pixelFormat, ref fmt); + return dc; + } + + public IDisposable MakeCurrent(IntPtr hdc) => new WglRestoreContext(hdc, _context, _lock); + + public bool IsSharedWith(IGlContext context) + { + var c = (WglContext)context; + return c == this + || c._sharedWith == this + || _sharedWith == context + || _sharedWith != null && _sharedWith == c._sharedWith; + } + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs new file mode 100644 index 0000000000..0d387bdb2c --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs @@ -0,0 +1,183 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.OpenGL; +using Avalonia.Win32.Interop; +using static Avalonia.Win32.Interop.UnmanagedMethods; +using static Avalonia.Win32.OpenGl.WglConsts; +namespace Avalonia.Win32.OpenGl +{ + internal class WglDisplay + { + private static bool? _initialized; + private static ushort _windowClass; + private static readonly WndProc _wndProcDelegate = WndProc; + private static readonly DebugCallbackDelegate _debugCallback = DebugCallback; + + private static IntPtr _bootstrapContext; + private static IntPtr _bootstrapWindow; + private static IntPtr _bootstrapDc; + private static PixelFormatDescriptor _defaultPfd; + private static int _defaultPixelFormat; + public static IntPtr OpenGl32Handle = LoadLibrary("opengl32"); + + private delegate bool WglChoosePixelFormatARBDelegate(IntPtr hdc, int[] piAttribIList, float[] pfAttribFList, + int nMaxFormats, int[] piFormats, out int nNumFormats); + + private static WglChoosePixelFormatARBDelegate WglChoosePixelFormatArb; + + private delegate IntPtr WglCreateContextAttribsARBDelegate(IntPtr hDC, IntPtr hShareContext, int[] attribList); + + private static WglCreateContextAttribsARBDelegate WglCreateContextAttribsArb; + + private delegate void GlDebugMessageCallbackDelegate(IntPtr callback, IntPtr userParam); + + private static GlDebugMessageCallbackDelegate GlDebugMessageCallback; + + private delegate void DebugCallbackDelegate(int source, int type, int id, int severity, int len, IntPtr message, + IntPtr userParam); + + static bool Initialize() + { + if (!_initialized.HasValue) + _initialized = InitializeCore(); + return _initialized.Value; + } + static bool InitializeCore() + { + var wndClassEx = new WNDCLASSEX + { + cbSize = Marshal.SizeOf(), + hInstance = GetModuleHandle(null), + lpfnWndProc = _wndProcDelegate, + lpszClassName = "AvaloniaGlWindow-" + Guid.NewGuid(), + style = (int)ClassStyles.CS_OWNDC + }; + + _windowClass = RegisterClassEx(ref wndClassEx); + _bootstrapWindow = CreateOffscreenWindow(); + _bootstrapDc = GetDC(_bootstrapWindow); + _defaultPfd = new PixelFormatDescriptor + { + Size = (ushort)Marshal.SizeOf(), + Version = 1, + Flags = PixelFormatDescriptorFlags.PFD_DRAW_TO_WINDOW | + PixelFormatDescriptorFlags.PFD_SUPPORT_OPENGL | PixelFormatDescriptorFlags.PFD_DOUBLEBUFFER, + DepthBits = 24, + StencilBits = 8, + ColorBits = 32 + }; + _defaultPixelFormat = ChoosePixelFormat(_bootstrapDc, ref _defaultPfd); + SetPixelFormat(_bootstrapDc, _defaultPixelFormat, ref _defaultPfd); + + _bootstrapContext = wglCreateContext(_bootstrapDc); + if (_bootstrapContext == IntPtr.Zero) + return false; + + wglMakeCurrent(_bootstrapDc, _bootstrapContext); + WglCreateContextAttribsArb = Marshal.GetDelegateForFunctionPointer( + wglGetProcAddress("wglCreateContextAttribsARB")); + + WglChoosePixelFormatArb = + Marshal.GetDelegateForFunctionPointer( + wglGetProcAddress("wglChoosePixelFormatARB")); + + GlDebugMessageCallback = + Marshal.GetDelegateForFunctionPointer( + wglGetProcAddress("glDebugMessageCallback")); + + + var formats = new int[1]; + WglChoosePixelFormatArb(_bootstrapDc, new int[] + { + WGL_DRAW_TO_WINDOW_ARB, 1, + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_SUPPORT_OPENGL_ARB, 1, + WGL_DOUBLE_BUFFER_ARB, 1, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_COLOR_BITS_ARB, 32, + WGL_DEPTH_BITS_ARB, 0, + WGL_STENCIL_BITS_ARB, 0, + 0, // End + }, null, 1, formats, out int numFormats); + if (numFormats != 0) + { + DescribePixelFormat(_bootstrapDc, formats[0], Marshal.SizeOf(), ref _defaultPfd); + _defaultPixelFormat = formats[0]; + } + + + wglMakeCurrent(IntPtr.Zero, IntPtr.Zero); + return true; + } + + static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + return DefWindowProc(hWnd, msg, wParam, lParam); + } + + private static void DebugCallback(int source, int type, int id, int severity, int len, IntPtr message, IntPtr userparam) + { + var err = Marshal.PtrToStringAnsi(message, len); + Console.Error.WriteLine(err); + } + + public static WglContext CreateContext(GlVersion[] versions, IGlContext share) + { + if (!Initialize()) + return null; + + var shareContext = (WglContext)share; + + using (new WglRestoreContext(_bootstrapDc, _bootstrapContext, null)) + { + var window = CreateOffscreenWindow(); + var dc = GetDC(window); + SetPixelFormat(dc, _defaultPixelFormat, ref _defaultPfd); + foreach (var version in versions) + { + if(version.Type != GlProfileType.OpenGL) + continue; + var context = WglCreateContextAttribsArb(dc, shareContext?.Handle ?? IntPtr.Zero, + new[] + { + // major + WGL_CONTEXT_MAJOR_VERSION_ARB, version.Major, + // minor + WGL_CONTEXT_MINOR_VERSION_ARB, version.Minor, + // core profile + WGL_CONTEXT_PROFILE_MASK_ARB, 1, + // debug + WGL_CONTEXT_FLAGS_ARB, 1, + // end + 0, 0 + }); + using(new WglRestoreContext(dc, context, null)) + GlDebugMessageCallback(Marshal.GetFunctionPointerForDelegate(_debugCallback), IntPtr.Zero); + if (context != IntPtr.Zero) + return new WglContext(shareContext, version, context, window, dc, + _defaultPixelFormat, _defaultPfd); + } + + ReleaseDC(window, dc); + DestroyWindow(window); + return null; + } + } + + + static IntPtr CreateOffscreenWindow() => + CreateWindowEx( + 0, + _windowClass, + null, + (int)WindowStyles.WS_OVERLAPPEDWINDOW, + 0, + 0, + 640, + 480, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero); + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs new file mode 100644 index 0000000000..72bcffd447 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs @@ -0,0 +1,85 @@ +using System; +using System.Diagnostics; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; +using Avalonia.Win32.Interop; +using static Avalonia.OpenGL.GlConsts; +using static Avalonia.Win32.Interop.UnmanagedMethods; +namespace Avalonia.Win32.OpenGl +{ + class WglGlPlatformSurface: IGlPlatformSurface + { + + private readonly WglContext _context; + private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; + + public WglGlPlatformSurface(WglContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) + { + _context = context; + _info = info; + } + + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + { + return new RenderTarget(_context, _info); + } + + class RenderTarget : IGlPlatformSurfaceRenderTarget + { + private readonly WglContext _context; + private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; + private IntPtr _hdc; + public RenderTarget(WglContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) + { + _context = context; + _info = info; + _hdc = context.CreateConfiguredDeviceContext(info.Handle); + } + + public void Dispose() + { + UnmanagedMethods.ReleaseDC(_hdc, _info.Handle); + } + + public IGlPlatformSurfaceRenderingSession BeginDraw() + { + var oldContext = _context.MakeCurrent(_hdc); + + // Reset to default FBO first + _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, 0); + + return new Session(_context, _hdc, _info, oldContext); + } + + class Session : IGlPlatformSurfaceRenderingSession + { + private readonly WglContext _context; + private readonly IntPtr _hdc; + private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; + private readonly IDisposable _clearContext; + public IGlContext Context => _context; + + public Session(WglContext context, IntPtr hdc, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info, + IDisposable clearContext) + { + _context = context; + _hdc = hdc; + _info = info; + _clearContext = clearContext; + } + + public void Dispose() + { + _context.GlInterface.Flush(); + UnmanagedMethods.SwapBuffers(_hdc); + _clearContext.Dispose(); + } + + public PixelSize Size => _info.Size; + public double Scaling => _info.Scaling; + public bool IsYFlipped { get; } + } + } + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs new file mode 100644 index 0000000000..b948495b99 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using Avalonia.Logging; +using Avalonia.OpenGL; + +namespace Avalonia.Win32.OpenGl +{ + class WglPlatformOpenGlInterface : IPlatformOpenGlInterface + { + public WglContext PrimaryContext { get; } + IGlContext IPlatformOpenGlInterface.PrimaryContext => PrimaryContext; + public IGlContext CreateSharedContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, PrimaryContext); + + public bool CanShareContexts => true; + public bool CanCreateContexts => true; + public IGlContext CreateContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, null); + + private WglPlatformOpenGlInterface(WglContext primary) + { + PrimaryContext = primary; + } + + public static WglPlatformOpenGlInterface TryCreate() + { + try + { + var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions(); + var primary = WglDisplay.CreateContext(opts.WglProfiles.ToArray(), null); + return new WglPlatformOpenGlInterface(primary); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("WGL", "Unable to initialize WGL: " + e); + } + + return null; + } + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs new file mode 100644 index 0000000000..265f078a5c --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading; +using Avalonia.OpenGL; +using static Avalonia.Win32.Interop.UnmanagedMethods; + +namespace Avalonia.Win32.OpenGl +{ + internal class WglRestoreContext : IDisposable + { + private readonly object _monitor; + private readonly IntPtr _oldDc; + private readonly IntPtr _oldContext; + + public WglRestoreContext(IntPtr gc, IntPtr context, object monitor, bool takeMonitor = true) + { + _monitor = monitor; + _oldDc = wglGetCurrentDC(); + _oldContext = wglGetCurrentContext(); + + if (monitor != null && takeMonitor) + Monitor.Enter(monitor); + + if (!wglMakeCurrent(gc, context)) + { + if(monitor != null && takeMonitor) + Monitor.Exit(monitor); + throw new OpenGlException("Unable to make the context current"); + } + } + + public void Dispose() + { + if (!wglMakeCurrent(_oldDc, _oldContext)) + wglMakeCurrent(IntPtr.Zero, IntPtr.Zero); + if (_monitor != null) + Monitor.Exit(_monitor); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index fbc56e7703..523a059e0e 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -1,27 +1,29 @@ using Avalonia.OpenGL; using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; +using Avalonia.Win32.OpenGl; namespace Avalonia.Win32 { static class Win32GlManager { - /// This property is initialized if drawing platform requests OpenGL support - public static EglPlatformOpenGlInterface EglPlatformInterface { get; private set; } - private static bool s_attemptedToInitialize; public static void Initialize() { - AvaloniaLocator.CurrentMutable.Bind().ToFunc(() => + AvaloniaLocator.CurrentMutable.Bind().ToLazy(() => { - if (!s_attemptedToInitialize) + var opts = AvaloniaLocator.Current.GetService(); + if (opts?.UseWgl == true) { - EglPlatformInterface = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); - s_attemptedToInitialize = true; + var wgl = WglPlatformOpenGlInterface.TryCreate(); + return wgl; } + + if (opts?.AllowEglInitialization == true) + return EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); - return EglPlatformInterface; + return null; }); } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index af6058d197..61c1a4f45e 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -11,6 +11,7 @@ using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; @@ -39,6 +40,12 @@ namespace Avalonia public bool AllowEglInitialization { get; set; } = true; public bool? EnableMultitouch { get; set; } public bool OverlayPopups { get; set; } + public bool UseWgl { get; set; } + public IList WglProfiles { get; set; } = new List + { + new GlVersion(GlProfileType.OpenGL, 4, 0), + new GlVersion(GlProfileType.OpenGL, 3, 2), + }; } } @@ -96,8 +103,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(new NonPumpingWaitProvider()) .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()); - if (options.AllowEglInitialization) - Win32GlManager.Initialize(); + Win32GlManager.Initialize(); _uiThread = Thread.CurrentThread; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 25a34561fc..655dfa5c48 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -338,19 +338,14 @@ namespace Avalonia.Win32 } case WindowsMessage.WM_PAINT: - { + { using (_rendererLock.Lock()) { - if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) - { - var f = RenderScaling; - var r = ps.rcPaint; - Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, - (r.bottom - r.top) / f)); - EndPaint(_hwnd, ref ps); - } + Paint?.Invoke(default); } + ValidateRect(hWnd, IntPtr.Zero); + return IntPtr.Zero; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index cb85e14e5a..7079a0120c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -13,6 +13,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; +using Avalonia.Win32.OpenGl; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 @@ -105,8 +106,12 @@ namespace Avalonia.Win32 CreateWindow(); _framebuffer = new FramebufferManager(_hwnd); - if (Win32GlManager.EglPlatformInterface != null) - _gl = new EglGlPlatformSurface(Win32GlManager.EglPlatformInterface, this); + var glPlatform = AvaloniaLocator.Current.GetService(); + + if(glPlatform is EglPlatformOpenGlInterface egl) + _gl = new EglGlPlatformSurface(egl, this); + else if (glPlatform is WglPlatformOpenGlInterface wgl) + _gl = new WglGlPlatformSurface(wgl.PrimaryContext, this); Screen = new ScreenImpl(); From d8ae3fda43ab392aa0caf7bd062e5ec3958324a2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 3 Oct 2020 23:25:17 +0300 Subject: [PATCH 109/492] Finally fixed D2D tests --- .../Media/Imaging/WicRenderTargetBitmapImpl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index e8e27f3f5d..1265a7bdf0 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -5,7 +5,7 @@ using SharpDX.Direct2D1; namespace Avalonia.Direct2D1.Media { - public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl + public class WicRenderTargetBitmapImpl : WicBitmapImpl, IDrawingContextLayerImpl { private readonly WicRenderTarget _renderTarget; @@ -45,5 +45,8 @@ namespace Avalonia.Direct2D1.Media finishedCallback?.Invoke(); }); } + + public void Blit(IDrawingContextImpl context) => throw new NotSupportedException(); + public bool CanBlit => false; } } From abc540b5a6e5e813ed0611916387bed1df7f31ae Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 4 Oct 2020 11:58:49 +0300 Subject: [PATCH 110/492] Disable debug mode for wgl context --- src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs index 0d387bdb2c..dc3c16b4a3 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs @@ -147,7 +147,7 @@ namespace Avalonia.Win32.OpenGl // core profile WGL_CONTEXT_PROFILE_MASK_ARB, 1, // debug - WGL_CONTEXT_FLAGS_ARB, 1, + // WGL_CONTEXT_FLAGS_ARB, 1, // end 0, 0 }); From 40941043f37d34145576bf405f90ec190b44730a Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 5 Oct 2020 12:14:10 +0300 Subject: [PATCH 111/492] Revert "add a way to render to canvas with DrawingContext with public api" This reverts commit e893705823862945c4cfa0316e77f8293817d59c. --- .../ExternalCanvasRenderTarget.cs | 32 ------------------- .../Avalonia.Skia/IExternalCanvasSurface.cs | 11 ------- .../Avalonia.Skia/PlatformRenderInterface.cs | 2 -- 3 files changed, 45 deletions(-) delete mode 100644 src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs delete mode 100644 src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs diff --git a/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs b/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs deleted file mode 100644 index c9e779797b..0000000000 --- a/src/Skia/Avalonia.Skia/ExternalCanvasRenderTarget.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Avalonia.Platform; -using Avalonia.Rendering; - -namespace Avalonia.Skia -{ - internal class ExternalCanvasRenderTarget : IRenderTarget - { - private IExternalCanvasSurface _surface; - - public ExternalCanvasRenderTarget(IExternalCanvasSurface canvas) - { - _surface = canvas; - } - - public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) - { - var createInfo = new DrawingContextImpl.CreateInfo - { - Canvas = _surface.Canvas, - Dpi = _surface.Dpi, - VisualBrushRenderer = visualBrushRenderer, - DisableTextLcdRendering = true, - }; - - return new DrawingContextImpl(createInfo); - } - - public void Dispose() - { - } - } -} diff --git a/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs b/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs deleted file mode 100644 index 38347f2caa..0000000000 --- a/src/Skia/Avalonia.Skia/IExternalCanvasSurface.cs +++ /dev/null @@ -1,11 +0,0 @@ -using SkiaSharp; - -namespace Avalonia.Skia -{ - public interface IExternalCanvasSurface - { - SKCanvas Canvas { get; } - - Vector Dpi { get; } - } -} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 397d358ba7..b9c1cbc673 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -145,8 +145,6 @@ namespace Avalonia.Skia { if (surface is IFramebufferPlatformSurface framebufferSurface) return new FramebufferRenderTarget(framebufferSurface); - if (surface is IExternalCanvasSurface canvas) - return new ExternalCanvasRenderTarget(canvas); } throw new NotSupportedException( From 9c1e31aeced7791e6a7aba251dbc9ad9ff2cf076 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 5 Oct 2020 12:22:31 +0300 Subject: [PATCH 112/492] add public api to create skia drawing context from canvas --- .../Helpers/DrawingContextHelper.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs new file mode 100644 index 0000000000..72438609d5 --- /dev/null +++ b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs @@ -0,0 +1,31 @@ +using Avalonia.Platform; +using Avalonia.Rendering; +using SkiaSharp; + +namespace Avalonia.Skia.Helpers +{ + public class DrawingContextHelper + { + /// + /// Wrap Skia canvas in drawing context so we can use Avalonia api to render to external skia canvas + /// this is useful in scenarios where canvas is not controlled by application, but received from another non avalonia api + /// like: SKCanvas canvas = SKDocument.BeginPage(...); + /// + /// + /// + /// + /// DrawingContext + public static IDrawingContextImpl WrapSkiaCanvas(SKCanvas canvas, Vector dpi, IVisualBrushRenderer visualBrushRenderer = null) + { + var createInfo = new DrawingContextImpl.CreateInfo + { + Canvas = canvas, + Dpi = dpi, + VisualBrushRenderer = visualBrushRenderer, + DisableTextLcdRendering = true, + }; + + return new DrawingContextImpl(createInfo); + } + } +} From 773055c78bb0bb19d0bb15ef330e51895ce3af78 Mon Sep 17 00:00:00 2001 From: "Artyom V. Gorchakov" Date: Mon, 5 Oct 2020 13:25:31 +0300 Subject: [PATCH 113/492] Transitions -> PageTransitions --- src/Avalonia.Visuals/Animation/CompositePageTransition.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/CompositePageTransition.cs b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs index 46bbb0bed2..9489914c97 100644 --- a/src/Avalonia.Visuals/Animation/CompositePageTransition.cs +++ b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs @@ -33,7 +33,7 @@ namespace Avalonia.Animation /// Gets or sets the transitions to be executed. Can be defined from XAML. /// [Content] - public List Transitions { get; set; } = new List(); + public List PageTransitions { get; set; } = new List(); /// /// Starts the animation. @@ -52,7 +52,7 @@ namespace Avalonia.Animation /// public Task Start(Visual from, Visual to, bool forward) { - var transitionTasks = Transitions + var transitionTasks = PageTransitions .Select(transition => transition.Start(from, to, forward)) .ToList(); return Task.WhenAll(transitionTasks); From 12d8daefaf3e55267ffed30bf4bf606cd940ed45 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 5 Oct 2020 18:29:07 +0200 Subject: [PATCH 114/492] Added nullable annotations. To `ScrollContentPresenter` and `IScrollAnchorProvider`. --- .../IScrollAnchorProvider.cs | 6 ++-- .../Presenters/ScrollContentPresenter.cs | 31 ++++++++----------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Controls/IScrollAnchorProvider.cs b/src/Avalonia.Controls/IScrollAnchorProvider.cs index 93f3a0abb8..7ba02e99ea 100644 --- a/src/Avalonia.Controls/IScrollAnchorProvider.cs +++ b/src/Avalonia.Controls/IScrollAnchorProvider.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Controls +#nullable enable + +namespace Avalonia.Controls { /// /// Specifies a contract for a scrolling control that supports scroll anchoring. @@ -8,7 +10,7 @@ /// /// The currently chosen anchor element to use for scroll anchoring. /// - IControl CurrentAnchor { get; } + IControl? CurrentAnchor { get; } /// /// Registers a control as a potential scroll anchor candidate. diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 5fcb14c858..2a04558fc8 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -7,6 +7,8 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.VisualTree; +#nullable enable + namespace Avalonia.Controls.Presenters { /// @@ -64,11 +66,11 @@ namespace Avalonia.Controls.Presenters private bool _arranging; private Size _extent; private Vector _offset; - private IDisposable _logicalScrollSubscription; + private IDisposable? _logicalScrollSubscription; private Size _viewport; - private Dictionary _activeLogicalGestureScrolls; - private List _anchorCandidates; - private (IControl control, Rect bounds) _anchor; + private Dictionary? _activeLogicalGestureScrolls; + private List? _anchorCandidates; + private (IControl? control, Rect bounds) _anchor; /// /// Initializes static members of the class. @@ -90,8 +92,6 @@ namespace Avalonia.Controls.Presenters this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription); } - internal event EventHandler PreArrange; - /// /// Gets or sets a value indicating whether the content can be scrolled horizontally. /// @@ -138,7 +138,7 @@ namespace Avalonia.Controls.Presenters } /// - IControl IScrollAnchorProvider.CurrentAnchor => _anchor.control; + IControl? IScrollAnchorProvider.CurrentAnchor => _anchor.control; /// /// Attempts to bring a portion of the target visual into view by scrolling the content. @@ -247,11 +247,6 @@ namespace Avalonia.Controls.Presenters /// protected override Size ArrangeOverride(Size finalSize) { - PreArrange?.Invoke(this, new VectorEventArgs - { - Vector = new Vector(finalSize.Width, finalSize.Height), - }); - if (_logicalScrollSubscription != null || Child == null) { return base.ArrangeOverride(finalSize); @@ -350,7 +345,7 @@ namespace Avalonia.Controls.Presenters { var logicalUnits = delta.Y / LogicalScrollItemSize; delta = delta.WithY(delta.Y - logicalUnits * LogicalScrollItemSize); - dy = logicalUnits * scrollable.ScrollSize.Height; + dy = logicalUnits * scrollable!.ScrollSize.Height; } else dy = delta.Y; @@ -368,7 +363,7 @@ namespace Avalonia.Controls.Presenters { var logicalUnits = delta.X / LogicalScrollItemSize; delta = delta.WithX(delta.X - logicalUnits * LogicalScrollItemSize); - dx = logicalUnits * scrollable.ScrollSize.Width; + dx = logicalUnits * scrollable!.ScrollSize.Width; } else dx = delta.X; @@ -405,7 +400,7 @@ namespace Avalonia.Controls.Presenters if (Extent.Height > Viewport.Height) { - double height = isLogical ? scrollable.ScrollSize.Height : 50; + double height = isLogical ? scrollable!.ScrollSize.Height : 50; y += -e.Delta.Y * height; y = Math.Max(y, 0); y = Math.Min(y, Extent.Height - Viewport.Height); @@ -413,7 +408,7 @@ namespace Avalonia.Controls.Presenters if (Extent.Width > Viewport.Width) { - double width = isLogical ? scrollable.ScrollSize.Width : 50; + double width = isLogical ? scrollable!.ScrollSize.Width : 50; x += -e.Delta.X * width; x = Math.Max(x, 0); x = Math.Min(x, Extent.Width - Viewport.Width); @@ -441,7 +436,7 @@ namespace Avalonia.Controls.Presenters private void ChildChanged(AvaloniaPropertyChangedEventArgs e) { - UpdateScrollableSubscription((IControl)e.NewValue); + UpdateScrollableSubscription((IControl?)e.NewValue); if (e.OldValue != null) { @@ -449,7 +444,7 @@ namespace Avalonia.Controls.Presenters } } - private void UpdateScrollableSubscription(IControl child) + private void UpdateScrollableSubscription(IControl? child) { var scrollable = child as ILogicalScrollable; From 07b48075cec97c0c40fc05d326776f869d6dc885 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 5 Oct 2020 20:11:32 +0300 Subject: [PATCH 115/492] add failing test for #4806 (skip it for now as it's deadlock) --- .../Media/TextFormatting/TextLayoutTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index f3e1c37705..4785f84133 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -575,6 +575,24 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact(Skip = "Infinity loop issue #4806")] + public void Should_Wrap_Min_OneCharacter_EveryLine() + { + using (Start()) + { + var layout = new TextLayout( + s_singleLineText, + Typeface.Default, + 12, + Brushes.Black, + textWrapping: TextWrapping.Wrap, + maxWidth: 3); + + //every character should be new line as there not enough space for even one character + Assert.Equal(s_singleLineText.Length, layout.TextLines.Count); + } + } + private static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface From 0f81b0fc9302e846d59845787e0bb23833ece5e1 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 5 Oct 2020 20:14:55 +0300 Subject: [PATCH 116/492] add failing test for formatter for #4806 --- .../TextFormatting/TextFormatterTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index adcc79e029..7f9713930a 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -293,6 +293,34 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void Wrap_Should_Not_Produce_Empty_Lines() + { + using (Start()) + { + const string text = "012345"; + + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.Wrap); + var textSource = new SingleBufferTextSource(text, defaultProperties); + var formatter = new TextFormatterImpl(); + + var textSourceIndex = 0; + + while (textSourceIndex < text.Length) + { + var textLine = + formatter.FormatLine(textSource, textSourceIndex, 3, paragraphProperties); + + Assert.NotEqual(0, textLine.TextRange.Length); + + textSourceIndex += textLine.TextRange.Length; + } + + Assert.Equal(text.Length, textSourceIndex); + } + } + public static IDisposable Start() { var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface From 37c2d0afb9b4b878aa2383b0c3f5d6ccdc56ed4d Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 5 Oct 2020 20:15:27 +0300 Subject: [PATCH 117/492] possible fix for #4806 for text formatting with wrap --- .../Media/TextFormatting/TextFormatterImpl.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 3e85f0f6f0..7e31d7e6ee 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -391,6 +391,10 @@ namespace Avalonia.Media.TextFormatting measuredLength = currentBreakPosition + lineBreaker.Current.PositionWrap; } } + else if (measuredLength == 0 && currentLength == 0) + { + measuredLength = 1; + } } currentLength += measuredLength; From 14b26187987493b5ac048dac826e5453fcd79d9a Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 5 Oct 2020 20:17:37 +0300 Subject: [PATCH 118/492] enable back the test for text Wrapping --- .../Media/TextFormatting/TextLayoutTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 4785f84133..26e8ce4797 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -575,7 +575,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } - [Fact(Skip = "Infinity loop issue #4806")] + [Fact] public void Should_Wrap_Min_OneCharacter_EveryLine() { using (Start()) From c941a3d1b99839ad517178f6e88235d2ec4d0cd3 Mon Sep 17 00:00:00 2001 From: amwx Date: Mon, 5 Oct 2020 15:18:26 -0500 Subject: [PATCH 119/492] Return focus to PlacementTarget on close --- src/Avalonia.Controls/Primitives/Popup.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 1e5e80d144..69fa2dc709 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -586,6 +586,19 @@ namespace Avalonia.Controls.Primitives } Closed?.Invoke(this, EventArgs.Empty); + + if(PlacementTarget != null) + { + FocusManager.Instance?.Focus(PlacementTarget); + } + else + { + var anc = this.GetLogicalAncestors().OfType().FirstOrDefault(); + if (anc != null) + { + FocusManager.Instance?.Focus(anc); + } + } } private void ListenForNonClientClick(RawInputEventArgs e) From cd6fa1bf08db5b9a6a7742572121f675e0fa4145 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 6 Oct 2020 11:29:15 +0300 Subject: [PATCH 120/492] [Win32] Drop mouse capture on WM_CANCELMODE --- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 25a34561fc..a03e1ffc22 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -200,6 +200,10 @@ namespace Avalonia.Win32 DipFromLParam(lParam), GetMouseModifiers(wParam)); break; } + // Mouse capture is lost + case WindowsMessage.WM_CANCELMODE: + _mouseDevice.Capture(null); + break; case WindowsMessage.WM_MOUSEMOVE: { From 103a72cfd3d24f0be6eed8fe705c3d772a65fbeb Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 5 Oct 2020 15:27:26 +0300 Subject: [PATCH 121/492] Removed mouse capture shenanigans from the calendar --- .../Calendar/CalendarItem.cs | 79 +------------------ 1 file changed, 4 insertions(+), 75 deletions(-) diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index e9ea942142..e830717b95 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -36,11 +36,7 @@ namespace Avalonia.Controls.Primitives private Button _headerButton; private Button _nextButton; private Button _previousButton; - private Grid _monthView; - private Grid _yearView; private ITemplate _dayTitleTemplate; - private CalendarButton _lastCalendarButton; - private CalendarDayButton _lastCalendarDayButton; private DateTime _currentMonth; private bool _isMouseLeftButtonDown = false; @@ -160,38 +156,12 @@ namespace Avalonia.Controls.Primitives /// /// Gets the Grid that hosts the content when in month mode. /// - internal Grid MonthView - { - get { return _monthView; } - private set - { - if (_monthView != null) - _monthView.PointerLeave -= MonthView_MouseLeave; - - _monthView = value; - - if (_monthView != null) - _monthView.PointerLeave += MonthView_MouseLeave; - } - } + internal Grid MonthView { get; set; } /// /// Gets the Grid that hosts the content when in year or decade mode. /// - internal Grid YearView - { - get { return _yearView; } - private set - { - if (_yearView != null) - _yearView.PointerLeave -= YearView_MouseLeave; - - _yearView = value; - - if (_yearView != null) - _yearView.PointerLeave += YearView_MouseLeave; - } - } - + internal Grid YearView { get; set; } + private void PopulateGrids() { if (MonthView != null) @@ -226,7 +196,6 @@ namespace Avalonia.Controls.Primitives cell.CalendarDayButtonMouseDown += Cell_MouseLeftButtonDown; cell.CalendarDayButtonMouseUp += Cell_MouseLeftButtonUp; cell.PointerEnter += Cell_MouseEnter; - cell.PointerLeave += Cell_MouseLeave; cell.Click += Cell_Click; children.Add(cell); } @@ -256,7 +225,6 @@ namespace Avalonia.Controls.Primitives month.CalendarLeftMouseButtonDown += Month_CalendarButtonMouseDown; month.CalendarLeftMouseButtonUp += Month_CalendarButtonMouseUp; month.PointerEnter += Month_MouseEnter; - month.PointerLeave += Month_MouseLeave; children.Add(month); } } @@ -937,17 +905,7 @@ namespace Avalonia.Controls.Primitives } } } - internal void Cell_MouseLeave(object sender, PointerEventArgs e) - { - if (_isMouseLeftButtonDown) - { - CalendarDayButton b = (CalendarDayButton)sender; - // The button is in Pressed state. Change the state to normal. - if (e.Pointer.Captured == b) - e.Pointer.Capture(null); - _lastCalendarDayButton = b; - } - } + internal void Cell_MouseLeftButtonDown(object sender, PointerPressedEventArgs e) { if (Owner != null) @@ -1207,35 +1165,6 @@ namespace Avalonia.Controls.Primitives } } - private void Month_MouseLeave(object sender, PointerEventArgs e) - { - if (_isMouseLeftButtonDownYearView) - { - CalendarButton b = (CalendarButton)sender; - // The button is in Pressed state. Change the state to normal. - if (e.Pointer.Captured == b) - e.Pointer.Capture(null); - //b.ReleaseMouseCapture(); - - _lastCalendarButton = b; - } - } - private void MonthView_MouseLeave(object sender, PointerEventArgs e) - { - if (_lastCalendarDayButton != null) - { - e.Pointer.Capture(_lastCalendarDayButton); - } - } - - private void YearView_MouseLeave(object sender, PointerEventArgs e) - { - if (_lastCalendarButton != null) - { - e.Pointer.Capture(_lastCalendarButton); - } - } - internal void UpdateDisabled(bool isEnabled) { PseudoClasses.Set(":calendardisabled", !isEnabled); From ce3c8316b5cd90db1e293e7cb4bf4a97cae8c823 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2020 10:46:15 +0200 Subject: [PATCH 122/492] Don't invalidate ItemsRepeater measure on children change. --- src/Avalonia.Controls/Panel.cs | 5 +++++ src/Avalonia.Controls/Repeater/ItemsRepeater.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index ccb92dc497..b7eeb065da 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -137,6 +137,11 @@ namespace Avalonia.Controls throw new NotSupportedException(); } + InvalidateMeasureOnChildrenChanged(); + } + + private protected virtual void InvalidateMeasureOnChildrenChanged() + { InvalidateMeasure(); } diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 40f1b8dbb9..9ed09df7db 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -267,6 +267,11 @@ namespace Avalonia.Controls return result; } + private protected override void InvalidateMeasureOnChildrenChanged() + { + // Don't invalidate measure when children change. + } + protected override Size MeasureOverride(Size availableSize) { if (_isLayoutInProgress) From e9c990d9082bd9b43d014b61612ba1baf3ab9b52 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2020 11:23:21 +0200 Subject: [PATCH 123/492] Tweak ScrollContentPresenter anchoring. Using WinUI's `ScrollPresenter` implementation to guide us. --- .../Presenters/ScrollContentPresenter.cs | 110 +++++++++++------- 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 2a04558fc8..b0b52812b9 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -16,6 +16,8 @@ namespace Avalonia.Controls.Presenters /// public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable, IScrollAnchorProvider { + private const double EdgeDetectionTolerance = 0.1; + /// /// Defines the property. /// @@ -70,7 +72,9 @@ namespace Avalonia.Controls.Presenters private Size _viewport; private Dictionary? _activeLogicalGestureScrolls; private List? _anchorCandidates; - private (IControl? control, Rect bounds) _anchor; + private IControl? _anchorElement; + private Rect _anchorElementBounds; + private bool _isAnchorElementDirty; /// /// Initializes static members of the class. @@ -138,7 +142,14 @@ namespace Avalonia.Controls.Presenters } /// - IControl? IScrollAnchorProvider.CurrentAnchor => _anchor.control; + IControl? IScrollAnchorProvider.CurrentAnchor + { + get + { + EnsureAnchorElementSelection(); + return _anchorElement; + } + } /// /// Attempts to bring a portion of the target visual into view by scrolling the content. @@ -215,16 +226,18 @@ namespace Avalonia.Controls.Presenters _anchorCandidates ??= new List(); _anchorCandidates.Add(element); + _isAnchorElementDirty = true; } /// void IScrollAnchorProvider.UnregisterAnchorCandidate(IControl element) { _anchorCandidates?.Remove(element); + _isAnchorElementDirty = true; - if (_anchor.control == element) + if (_anchorElement == element) { - _anchor = default; + _anchorElement = null; } } @@ -266,59 +279,69 @@ namespace Avalonia.Controls.Presenters // If we have an anchor and its position relative to Child has changed during the // arrange then that change wasn't just due to scrolling (as scrolling doesn't adjust // relative positions within Child). - if (_anchor.control != null && - TranslateBounds(_anchor.control, Child, out var updatedBounds) && - updatedBounds.Position != _anchor.bounds.Position) + if (_anchorElement != null && + TranslateBounds(_anchorElement, Child, out var updatedBounds) && + updatedBounds.Position != _anchorElementBounds.Position) { - var offset = updatedBounds.Position - _anchor.bounds.Position; + var offset = updatedBounds.Position - _anchorElementBounds.Position; return offset; } return default; } - // Calculate the new anchor element. - _anchor = CalculateCurrentAnchor(); + var isAnchoring = Offset.X >= EdgeDetectionTolerance || Offset.Y >= EdgeDetectionTolerance; - // Do the arrange. - ArrangeOverrideImpl(size, -Offset); + if (isAnchoring) + { + // Calculate the new anchor element if necessary. + EnsureAnchorElementSelection(); - // If the anchor moved during the arrange, we need to adjust the offset and do another arrange. - var anchorShift = TrackAnchor(); + // Do the arrange. + ArrangeOverrideImpl(size, -Offset); - if (anchorShift != default) - { - var newOffset = Offset + anchorShift; - var newExtent = Extent; - var maxOffset = new Vector(Extent.Width - Viewport.Width, Extent.Height - Viewport.Height); + // If the anchor moved during the arrange, we need to adjust the offset and do another arrange. + var anchorShift = TrackAnchor(); - if (newOffset.X > maxOffset.X) + if (anchorShift != default) { - newExtent = newExtent.WithWidth(newOffset.X + Viewport.Width); - } + var newOffset = Offset + anchorShift; + var newExtent = Extent; + var maxOffset = new Vector(Extent.Width - Viewport.Width, Extent.Height - Viewport.Height); - if (newOffset.Y > maxOffset.Y) - { - newExtent = newExtent.WithHeight(newOffset.Y + Viewport.Height); - } + if (newOffset.X > maxOffset.X) + { + newExtent = newExtent.WithWidth(newOffset.X + Viewport.Width); + } - Extent = newExtent; + if (newOffset.Y > maxOffset.Y) + { + newExtent = newExtent.WithHeight(newOffset.Y + Viewport.Height); + } - try - { - _arranging = true; - Offset = newOffset; - } - finally - { - _arranging = false; + Extent = newExtent; + + try + { + _arranging = true; + Offset = newOffset; + } + finally + { + _arranging = false; + } + + ArrangeOverrideImpl(size, -Offset); } - + } + else + { ArrangeOverrideImpl(size, -Offset); } Viewport = finalSize; Extent = Child.Bounds.Size.Inflate(Child.Margin); + _isAnchorElementDirty = true; return finalSize; } @@ -493,13 +516,17 @@ namespace Avalonia.Controls.Presenters } } - private (IControl, Rect) CalculateCurrentAnchor() + private void EnsureAnchorElementSelection() { - if (_anchorCandidates == null) + if (!_isAnchorElementDirty || _anchorCandidates is null) { - return default; + return; } + _anchorElement = null; + _anchorElementBounds = default; + _isAnchorElementDirty = false; + var bestCandidate = default(IControl); var bestCandidateDistance = double.MaxValue; @@ -526,10 +553,9 @@ namespace Avalonia.Controls.Presenters // bounds aren't relative to the ScrollContentPresenter itself, if they change // then we know it wasn't just due to scrolling. var unscrolledBounds = TranslateBounds(bestCandidate, Child); - return (bestCandidate, unscrolledBounds); + _anchorElement = bestCandidate; + _anchorElementBounds = unscrolledBounds; } - - return default; } private bool GetViewportBounds(IControl element, out Rect bounds) From d5224d4bb0a98f55c1da1cae9eaab6a57d4b3697 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2020 11:43:56 +0200 Subject: [PATCH 124/492] Ensure valid index before calculating offset. --- src/Avalonia.Layout/StackLayout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs index 909c7bc7eb..4a93c8344f 100644 --- a/src/Avalonia.Layout/StackLayout.cs +++ b/src/Avalonia.Layout/StackLayout.cs @@ -249,8 +249,8 @@ namespace Avalonia.Layout realizationWindowOffsetInExtent + _orientation.MajorSize(realizationRect) >= 0 && realizationWindowOffsetInExtent <= majorSize) { anchorIndex = (int) (realizationWindowOffsetInExtent / averageElementSize); - offset = anchorIndex* averageElementSize + _orientation.MajorStart(lastExtent); anchorIndex = Math.Max(0, Math.Min(itemsCount - 1, anchorIndex)); + offset = anchorIndex* averageElementSize + _orientation.MajorStart(lastExtent); } } From 4a0f517ecc2632d6f9768de4577d3e374c478b5f Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 6 Oct 2020 16:16:39 +0300 Subject: [PATCH 125/492] Revert "possible fix for #4806 for text formatting with wrap" This reverts commit 37c2d0afb9b4b878aa2383b0c3f5d6ccdc56ed4d. --- .../Media/TextFormatting/TextFormatterImpl.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 7e31d7e6ee..3e85f0f6f0 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -391,10 +391,6 @@ namespace Avalonia.Media.TextFormatting measuredLength = currentBreakPosition + lineBreaker.Current.PositionWrap; } } - else if (measuredLength == 0 && currentLength == 0) - { - measuredLength = 1; - } } currentLength += measuredLength; From c2d58f5a0b1402741a23552c6e9a0da467ca434e Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 6 Oct 2020 16:26:30 +0300 Subject: [PATCH 126/492] fix #4806 ensure at least one character is returned on the first text run measure --- .../Media/TextFormatting/TextFormatterImpl.cs | 12 ++++++++++-- .../Media/TextFormatting/TextLineImpl.cs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 3e85f0f6f0..e65f96bf61 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -47,8 +47,9 @@ namespace Avalonia.Media.TextFormatting /// /// The text run. /// The available width. + /// Index of the textCharacters parent TextRun /// - internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth) + internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth, int textRunIndex) { var glyphRun = textCharacters.GlyphRun; @@ -73,6 +74,13 @@ namespace Avalonia.Media.TextFormatting if (currentWidth + advance > availableWidth) { + if(glyphCount == 0 && textRunIndex == 0) + { + //we need to return at least one characted on the first run + //or we risk to get a infinity loop when width is less than one character width + //issue #4806 + glyphCount = 1; + } break; } @@ -350,7 +358,7 @@ namespace Avalonia.Media.TextFormatting if (currentWidth + currentRun.Size.Width > availableWidth) { - var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth); + var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth, runIndex); var breakFound = false; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index f5e87d097b..aa7759fc16 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -75,7 +75,7 @@ namespace Avalonia.Media.TextFormatting if (currentWidth > availableWidth) { - var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth); + var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth, runIndex); var currentBreakPosition = 0; From bba2cafcc1efe44af5e7cf286a2bedaa389cdc20 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2020 16:13:21 +0200 Subject: [PATCH 127/492] Don't register anchor candidates until they're laid out. --- src/Avalonia.Controls/Repeater/ItemsRepeater.cs | 13 +++++++++++-- src/Avalonia.Controls/Repeater/ViewManager.cs | 2 +- src/Avalonia.Controls/Repeater/ViewportManager.cs | 14 ++++++++++++-- .../Repeater/VirtualizationInfo.cs | 1 + 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 9ed09df7db..fb2da09e73 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -369,6 +369,12 @@ namespace Avalonia.Controls { var newBounds = element.Bounds; virtInfo.ArrangeBounds = newBounds; + + if (!virtInfo.IsRegisteredAsAnchorCandidate) + { + _viewportManager.RegisterScrollAnchorCandidate(element); + virtInfo.IsRegisteredAsAnchorCandidate = true; + } } } @@ -520,11 +526,14 @@ namespace Avalonia.Controls return element; } - internal void OnElementPrepared(IControl element, int index) + internal void OnElementPrepared(IControl element, VirtualizationInfo virtInfo) { - _viewportManager.OnElementPrepared(element); + _viewportManager.OnElementPrepared(element, virtInfo); + if (ElementPrepared != null) { + var index = virtInfo.Index; + if (_elementPreparedArgs == null) { _elementPreparedArgs = new ItemsRepeaterElementPreparedEventArgs(element, index); diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs index 416b1e2824..cf2066b373 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewManager.cs @@ -661,7 +661,7 @@ namespace Avalonia.Controls children.Add(element); } - repeater.OnElementPrepared(element, index); + repeater.OnElementPrepared(element, virtInfo); // Update realized indices _firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index); diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index bdb0fa3270..6e24408aa9 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -240,9 +240,14 @@ namespace Avalonia.Controls } } - public void OnElementPrepared(IControl element) + public void OnElementPrepared(IControl element, VirtualizationInfo virtInfo) { - _scroller?.RegisterAnchorCandidate(element); + // WinUI registers the element as an anchor candidate here, but I feel that's in error: + // at this point the element has not yet been positioned by the arrange pass so it will + // have its previous position, meaning that when the arrange pass moves it into its new + // position, an incorrect scroll anchoring will occur. Instead signal that it's not yet + // registered as a scroll anchor candidate. + virtInfo.IsRegisteredAsAnchorCandidate = false; } public void OnElementCleared(IControl element) @@ -373,6 +378,11 @@ namespace Avalonia.Controls } } + public void RegisterScrollAnchorCandidate(IControl element) + { + _scroller?.RegisterAnchorCandidate(element); + } + private IControl GetImmediateChildOfRepeater(IControl descendant) { var targetChild = descendant; diff --git a/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs b/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs index 7a639419c1..f8cfde609e 100644 --- a/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs +++ b/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs @@ -38,6 +38,7 @@ namespace Avalonia.Controls public bool IsInUniqueIdResetPool => Owner == ElementOwner.UniqueIdResetPool; public bool MustClearDataContext { get; set; } public bool KeepAlive { get; set; } + public bool IsRegisteredAsAnchorCandidate { get; set; } public ElementOwner Owner { get; private set; } = ElementOwner.ElementFactory; public string UniqueId { get; private set; } From a19d327629ab93ddbacb64ab191af9e8ecef745c Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 6 Oct 2020 23:03:54 +0300 Subject: [PATCH 128/492] Revert "fix #4806 ensure at least one character is returned on the first text run measure" This reverts commit c2d58f5a0b1402741a23552c6e9a0da467ca434e. --- .../Media/TextFormatting/TextFormatterImpl.cs | 12 ++---------- .../Media/TextFormatting/TextLineImpl.cs | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index e65f96bf61..3e85f0f6f0 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -47,9 +47,8 @@ namespace Avalonia.Media.TextFormatting /// /// The text run. /// The available width. - /// Index of the textCharacters parent TextRun /// - internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth, int textRunIndex) + internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth) { var glyphRun = textCharacters.GlyphRun; @@ -74,13 +73,6 @@ namespace Avalonia.Media.TextFormatting if (currentWidth + advance > availableWidth) { - if(glyphCount == 0 && textRunIndex == 0) - { - //we need to return at least one characted on the first run - //or we risk to get a infinity loop when width is less than one character width - //issue #4806 - glyphCount = 1; - } break; } @@ -358,7 +350,7 @@ namespace Avalonia.Media.TextFormatting if (currentWidth + currentRun.Size.Width > availableWidth) { - var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth, runIndex); + var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth); var breakFound = false; diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index aa7759fc16..f5e87d097b 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -75,7 +75,7 @@ namespace Avalonia.Media.TextFormatting if (currentWidth > availableWidth) { - var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth, runIndex); + var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth); var currentBreakPosition = 0; From 40d74cf682b3ad6e88ff99ace2fbbae2ac2e3fe3 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 6 Oct 2020 19:00:53 +0200 Subject: [PATCH 129/492] Fix baseline alignment of multiple runs Make sure we always wrap at least one character --- .../ControlCatalog/Pages/TextBlockPage.xaml | 4 +- src/Avalonia.Visuals/Media/GlyphRun.cs | 17 +---- .../TextFormatting/ShapedTextCharacters.cs | 2 +- .../Media/TextFormatting/TextFormatterImpl.cs | 67 +++++++++++++------ .../Media/TextFormatting/TextLineImpl.cs | 44 ++++++------ 5 files changed, 74 insertions(+), 60 deletions(-) diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml index 4a1c196917..d4f72f161a 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -18,8 +18,8 @@ - - + + diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index 14ab083b4f..155339b985 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/src/Avalonia.Visuals/Media/GlyphRun.cs @@ -18,7 +18,7 @@ namespace Avalonia.Media private double _fontRenderingEmSize; private Size? _size; private int _biDiLevel; - private Point? _baselineOrigin; + private Point _baselineOrigin; private ReadOnlySlice _glyphIndices; private ReadOnlySlice _glyphAdvances; @@ -97,9 +97,7 @@ namespace Avalonia.Media { get { - _baselineOrigin ??= CalculateBaselineOrigin(); - - return _baselineOrigin.Value; + return _baselineOrigin; } set => Set(ref _baselineOrigin, value); } @@ -540,15 +538,6 @@ namespace Avalonia.Media return GlyphAdvances[index]; } - /// - /// Calculates the default baseline origin of the . - /// - /// The baseline origin. - private Point CalculateBaselineOrigin() - { - return new Point(0, -GlyphTypeface.Ascent * Scale); - } - /// /// Calculates the size of the . /// @@ -611,8 +600,6 @@ namespace Avalonia.Media throw new InvalidOperationException(); } - _baselineOrigin = new Point(0, -GlyphTypeface.Ascent * Scale); - var platformRenderInterface = AvaloniaLocator.Current.GetService(); _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width); diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs index 09ecc0a026..9f6f2b2f43 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs @@ -52,7 +52,7 @@ namespace Avalonia.Media.TextFormatting return; } - if (Properties.Typeface == null) + if (Properties.Typeface == default) { return; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 3e85f0f6f0..4a7282af27 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -43,18 +43,23 @@ namespace Avalonia.Media.TextFormatting } /// - /// Measures the number of characters that fits into available width. + /// Measures the number of characters that fit into available width. /// /// The text run. /// The available width. - /// - internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth) + /// The count of fitting characters. + /// + /// true if characters fit into the available width; otherwise, false. + /// + internal static bool TryMeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth, out int count) { var glyphRun = textCharacters.GlyphRun; if (glyphRun.Size.Width < availableWidth) { - return glyphRun.Characters.Length; + count = glyphRun.Characters.Length; + + return true; } var glyphCount = 0; @@ -96,21 +101,34 @@ namespace Avalonia.Media.TextFormatting } } + if (glyphCount == 0) + { + count = 0; + + return false; + } + if (glyphCount == glyphRun.GlyphIndices.Length) { - return glyphRun.Characters.Length; + count = glyphRun.Characters.Length; + + return true; } if (glyphRun.GlyphClusters.IsEmpty) { - return glyphCount; + count = glyphCount; + + return true; } var firstCluster = glyphRun.GlyphClusters[0]; var lastCluster = glyphRun.GlyphClusters[glyphCount]; - return lastCluster - firstCluster; + count = lastCluster - firstCluster; + + return count > 0; } /// @@ -350,29 +368,38 @@ namespace Avalonia.Media.TextFormatting if (currentWidth + currentRun.Size.Width > availableWidth) { - var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth); - var breakFound = false; var currentBreakPosition = 0; - if (measuredLength < currentRun.Text.Length) + if (TryMeasureCharacters(currentRun, paragraphWidth - currentWidth, out var measuredLength)) { - var lineBreaker = new LineBreakEnumerator(currentRun.Text); - - while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) + if (measuredLength < currentRun.Text.Length) { - var nextBreakPosition = lineBreaker.Current.PositionWrap; + var lineBreaker = new LineBreakEnumerator(currentRun.Text); - if (nextBreakPosition == 0 || nextBreakPosition > measuredLength) + while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) { - break; - } + var nextBreakPosition = lineBreaker.Current.PositionWrap; + + if (nextBreakPosition == 0 || nextBreakPosition > measuredLength) + { + break; + } - breakFound = lineBreaker.Current.Required || - lineBreaker.Current.PositionWrap != currentRun.Text.Length; + breakFound = lineBreaker.Current.Required || + lineBreaker.Current.PositionWrap != currentRun.Text.Length; - currentBreakPosition = nextBreakPosition; + currentBreakPosition = nextBreakPosition; + } + } + } + else + { + // Make sure we wrap at least one character. + if (currentLength == 0) + { + measuredLength = 1; } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index f5e87d097b..d13b4836ea 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -39,7 +39,9 @@ namespace Avalonia.Media.TextFormatting foreach (var textRun in _textRuns) { - using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, 0))) + var offsetY = LineMetrics.TextBaseline; + + using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY))) { textRun.Draw(drawingContext); } @@ -75,37 +77,35 @@ namespace Avalonia.Media.TextFormatting if (currentWidth > availableWidth) { - var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth); - - var currentBreakPosition = 0; - - if (measuredLength < textRange.End) + if (TextFormatterImpl.TryMeasureCharacters(currentRun, availableWidth, out var measuredLength)) { - var lineBreaker = new LineBreakEnumerator(currentRun.Text); - - while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) + if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord && measuredLength < textRange.End) { - var nextBreakPosition = lineBreaker.Current.PositionWrap; + var currentBreakPosition = 0; - if (nextBreakPosition == 0) - { - break; - } + var lineBreaker = new LineBreakEnumerator(currentRun.Text); - if (nextBreakPosition > measuredLength) + while (currentBreakPosition < measuredLength && lineBreaker.MoveNext()) { - break; + var nextBreakPosition = lineBreaker.Current.PositionWrap; + + if (nextBreakPosition == 0) + { + break; + } + + if (nextBreakPosition > measuredLength) + { + break; + } + + currentBreakPosition = nextBreakPosition; } - currentBreakPosition = nextBreakPosition; + measuredLength = currentBreakPosition; } } - if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord) - { - measuredLength = currentBreakPosition; - } - collapsedLength += measuredLength; var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength); From 273f296b47d0a0e8dd39a6ccf922fa8e075f4dbd Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 6 Oct 2020 15:39:21 -0500 Subject: [PATCH 130/492] Small fix --- src/Avalonia.Controls/Primitives/Popup.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 69fa2dc709..5d911d7727 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -358,7 +358,7 @@ namespace Avalonia.Controls.Primitives return; } - var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType().FirstOrDefault(); + var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType(); if (placementTarget == null) { @@ -587,13 +587,13 @@ namespace Avalonia.Controls.Primitives Closed?.Invoke(this, EventArgs.Empty); - if(PlacementTarget != null) + if (PlacementTarget != null) { FocusManager.Instance?.Focus(PlacementTarget); } else { - var anc = this.GetLogicalAncestors().OfType().FirstOrDefault(); + var anc = this.FindLogicalAncestorOfType(); if (anc != null) { FocusManager.Instance?.Focus(anc); From 047d734e94adfd22e9df45e5cf9e20a358b10965 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 6 Oct 2020 21:16:41 -0400 Subject: [PATCH 131/492] Add :empty pseudoclass for ContentPresenter --- .../Presenters/ContentPresenter.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 8837901816..3fd927afa3 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -1,9 +1,9 @@ using System; + +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Controls.Utils; -using Avalonia.Data; -using Avalonia.Input; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; @@ -14,6 +14,7 @@ namespace Avalonia.Controls.Presenters /// /// Presents a single item of data inside a template. /// + [PseudoClasses(":empty")] public class ContentPresenter : Control, IContentPresenter { /// @@ -102,6 +103,11 @@ namespace Avalonia.Controls.Presenters TemplatedParentProperty.Changed.AddClassHandler((x, e) => x.TemplatedParentChanged(e)); } + public ContentPresenter() + { + UpdatePseudoClasses(); + } + /// /// Gets or sets a brush with which to paint the background. /// @@ -424,9 +430,15 @@ namespace Avalonia.Controls.Presenters _recyclingDataTemplate = null; } + UpdatePseudoClasses(); InvalidateMeasure(); } + private void UpdatePseudoClasses() + { + PseudoClasses.Set(":empty", Content is null); + } + private double GetLayoutScale() { var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; From 8ef4b2f7922611f6568a0fd1aaa5c91433365b22 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 6 Oct 2020 21:19:13 -0400 Subject: [PATCH 132/492] ToggleSwitch: add margin only when Content header is set --- src/Avalonia.Themes.Default/ToggleSwitch.xaml | 8 +++++++- src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml index 9ce4da0873..9d1c024eb9 100644 --- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml @@ -87,7 +87,6 @@ Grid.Row="0" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" - Margin="{DynamicResource ToggleSwitchTopHeaderMargin}" VerticalAlignment="Top"/> + + + + + + - - - - - - + + + + + + + From 7f7db22004fd45f136bf7307c93ce2d52f0542d3 Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 6 Oct 2020 22:23:03 -0500 Subject: [PATCH 138/492] Only move focus if Popup had it --- src/Avalonia.Controls/Primitives/Popup.cs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 5d911d7727..becb489557 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -587,16 +587,23 @@ namespace Avalonia.Controls.Primitives Closed?.Invoke(this, EventArgs.Empty); - if (PlacementTarget != null) - { - FocusManager.Instance?.Focus(PlacementTarget); - } - else + var focusCheck = FocusManager.Instance?.Current; + + // Focus is set to null as part of popup closing, so we only want to + // set focus to PlacementTarget if this is the case + if (focusCheck == null) { - var anc = this.FindLogicalAncestorOfType(); - if (anc != null) + if (PlacementTarget != null) { - FocusManager.Instance?.Focus(anc); + FocusManager.Instance?.Focus(PlacementTarget); + } + else + { + var anc = this.FindLogicalAncestorOfType(); + if (anc != null) + { + FocusManager.Instance?.Focus(anc); + } } } } From b58691957f9cd974743b9d2c343a7896a3a264e9 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 7 Oct 2020 03:35:23 -0400 Subject: [PATCH 139/492] Fluent non-transparent notification background --- .../Accents/FluentControlResourcesDark.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 4d21068492..abc02a6371 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -439,7 +439,7 @@ - + From feed5103abd8034568ece7d12c59ba60ef2455b3 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Wed, 7 Oct 2020 12:45:44 +0300 Subject: [PATCH 140/492] Removed redundant method --- .../Primitives/SelectingItemsControl.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index e34b3b145f..4317d795f1 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -848,21 +848,6 @@ namespace Avalonia.Controls.Primitives } } - /// - /// Sets an item container's 'selected' class or . - /// - /// The index of the item. - /// Whether the item should be selected or deselected. - private void MarkItemSelected(int index, bool selected) - { - var container = ItemContainerGenerator?.ContainerFromIndex(index); - - if (container != null) - { - MarkContainerSelected(container, selected); - } - } - private void UpdateContainerSelection() { if (Presenter?.Panel is IPanel panel) From 8bb8f3c79fed45ec03398b56bb4202a06b94745a Mon Sep 17 00:00:00 2001 From: "oliver.holliday@lontra.co.uk" Date: Wed, 7 Oct 2020 20:03:08 +0100 Subject: [PATCH 141/492] Add Skia support for Conic Gradients. Add a place holder implementation for Direct2D using SolidColorBrushImpl. --- .../Media/ConicGradientBrush.cs | 55 ++++++ .../Media/IConicGradientBrush.cs | 19 ++ .../Immutable/ImmutableConicGradientBrush.cs | 47 +++++ src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 17 ++ .../Media/DrawingContextImpl.cs | 6 + .../Media/SolidColorBrushImpl.cs | 17 ++ .../Media/ConicGradientBrushTests.cs | 178 ++++++++++++++++++ .../ConicGradientBrush_RedBlue.expected.png | Bin 0 -> 601 bytes ...cGradientBrush_RedBlue_Center.expected.png | Bin 0 -> 601 bytes ...h_RedBlue_Center_and_Rotation.expected.png | Bin 0 -> 601 bytes ...radientBrush_RedBlue_Rotation.expected.png | Bin 0 -> 601 bytes ...radientBrush_RedBlue_SoftEdge.expected.png | Bin 0 -> 601 bytes .../ConicGradientBrush_Umbrella.expected.png | Bin 0 -> 601 bytes .../ConicGradientBrush_RedBlue.expected.png | Bin 0 -> 7249 bytes ...cGradientBrush_RedBlue_Center.expected.png | Bin 0 -> 6797 bytes ...h_RedBlue_Center_and_Rotation.expected.png | Bin 0 -> 6792 bytes ...radientBrush_RedBlue_Rotation.expected.png | Bin 0 -> 7232 bytes ...radientBrush_RedBlue_SoftEdge.expected.png | Bin 0 -> 8805 bytes .../ConicGradientBrush_Umbrella.expected.png | Bin 0 -> 11408 bytes 19 files changed, 339 insertions(+) create mode 100644 src/Avalonia.Visuals/Media/ConicGradientBrush.cs create mode 100644 src/Avalonia.Visuals/Media/IConicGradientBrush.cs create mode 100644 src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs create mode 100644 tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs create mode 100644 tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png create mode 100644 tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png create mode 100644 tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png create mode 100644 tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png create mode 100644 tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png create mode 100644 tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png create mode 100644 tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png diff --git a/src/Avalonia.Visuals/Media/ConicGradientBrush.cs b/src/Avalonia.Visuals/Media/ConicGradientBrush.cs new file mode 100644 index 0000000000..7c1266fa17 --- /dev/null +++ b/src/Avalonia.Visuals/Media/ConicGradientBrush.cs @@ -0,0 +1,55 @@ +using Avalonia.Media.Immutable; + +namespace Avalonia.Media +{ + /// + /// Paints an area with a swept circular gradient. + /// + public sealed class ConicGradientBrush : GradientBrush, IConicGradientBrush + { + /// + /// Defines the property. + /// + public static readonly StyledProperty CenterProperty = + AvaloniaProperty.Register( + nameof(Center), + RelativePoint.Center); + + /// + /// Defines the property. + /// + public static readonly StyledProperty AngleProperty = + AvaloniaProperty.Register( + nameof(Angle), + 0); + + static ConicGradientBrush() + { + AffectsRender(CenterProperty, AngleProperty); + } + + /// + /// Gets or sets the center point of the gradient. + /// + public RelativePoint Center + { + get { return GetValue(CenterProperty); } + set { SetValue(CenterProperty, value); } + } + + /// + /// Gets or sets the angle of the start and end of the sweep, measured from above the center point. + /// + public double Angle + { + get { return GetValue(AngleProperty); } + set { SetValue(AngleProperty, value); } + } + + /// + public override IBrush ToImmutable() + { + return new ImmutableConicGradientBrush(this); + } + } +} diff --git a/src/Avalonia.Visuals/Media/IConicGradientBrush.cs b/src/Avalonia.Visuals/Media/IConicGradientBrush.cs new file mode 100644 index 0000000000..5368dd1851 --- /dev/null +++ b/src/Avalonia.Visuals/Media/IConicGradientBrush.cs @@ -0,0 +1,19 @@ +namespace Avalonia.Media +{ + /// + /// Paints an area with a conic gradient. + /// + public interface IConicGradientBrush : IGradientBrush + { + /// + /// Gets the center point for the gradient. + /// + RelativePoint Center { get; } + + /// + /// Gets the starting angle for the gradient in degrees, measured from + /// the point above the center point. + /// + double Angle { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs new file mode 100644 index 0000000000..d3c80dfcad --- /dev/null +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; + +namespace Avalonia.Media.Immutable +{ + /// + /// A brush that draws with a sweep gradient. + /// + public class ImmutableConicGradientBrush : ImmutableGradientBrush, IConicGradientBrush + { + /// + /// Initializes a new instance of the class. + /// + /// The gradient stops. + /// The opacity of the brush. + /// The spread method. + /// The center point for the gradient. + /// The starting angle for the gradient. + public ImmutableConicGradientBrush( + IReadOnlyList gradientStops, + double opacity = 1, + GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, + RelativePoint? center = null, + double angle = 0) + : base(gradientStops, opacity, spreadMethod) + { + Center = center ?? RelativePoint.Center; + Angle = angle; + } + + /// + /// Initializes a new instance of the class. + /// + /// The brush from which this brush's properties should be copied. + public ImmutableConicGradientBrush(ConicGradientBrush source) + : base(source) + { + Center = source.Center; + Angle = source.Angle; + } + + /// + public RelativePoint Center { get; } + + /// + public double Angle { get; } + } +} diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 05a8daaec8..b8c81b3409 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -624,6 +624,23 @@ namespace Avalonia.Skia } } + break; + } + case IConicGradientBrush conicGradient: + { + var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint(); + + // Skia's default is that angle 0 is from the right hand side of the center point + // but we are matching CSS where the vertical point above the center is 0. + var angle = (float)(conicGradient.Angle - 90); + var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y); + + using (var shader = + SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation)) + { + paintWrapper.Paint.Shader = shader; + } + break; } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 258a51db5a..5d20bff1e9 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -424,6 +424,7 @@ namespace Avalonia.Direct2D1.Media var solidColorBrush = brush as ISolidColorBrush; var linearGradientBrush = brush as ILinearGradientBrush; var radialGradientBrush = brush as IRadialGradientBrush; + var conicGradientBrush = brush as IConicGradientBrush; var imageBrush = brush as IImageBrush; var visualBrush = brush as IVisualBrush; @@ -439,6 +440,11 @@ namespace Avalonia.Direct2D1.Media { return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize); } + else if (conicGradientBrush != null) + { + // there is no Direct2D implementation of Conic Gradients so use Radial as a stand-in + return new SolidColorBrushImpl(conicGradientBrush, _deviceContext); + } else if (imageBrush?.Source != null) { return new ImageBrushImpl( diff --git a/src/Windows/Avalonia.Direct2D1/Media/SolidColorBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/SolidColorBrushImpl.cs index f93b4a2e08..fea1ca9157 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/SolidColorBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/SolidColorBrushImpl.cs @@ -16,5 +16,22 @@ namespace Avalonia.Direct2D1.Media } ); } + + /// + /// Direct2D has no ConicGradient implementation so fall back to a solid colour brush based on + /// the first gradient stop. + /// + public SolidColorBrushImpl(IConicGradientBrush brush, SharpDX.Direct2D1.DeviceContext target) + { + PlatformBrush = new SharpDX.Direct2D1.SolidColorBrush( + target, + brush?.GradientStops[0].Color.ToDirect2D() ?? new SharpDX.Mathematics.Interop.RawColor4(), + new SharpDX.Direct2D1.BrushProperties + { + Opacity = brush != null ? (float)brush.Opacity : 1.0f, + Transform = target.Transform + } + ); + } } } diff --git a/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs new file mode 100644 index 0000000000..db69a0a028 --- /dev/null +++ b/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs @@ -0,0 +1,178 @@ +using Avalonia.Controls; +using Avalonia.Media; +using System.Threading.Tasks; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Media +#endif +{ + public class ConicGradientBrushTests : TestBase + { + public ConicGradientBrushTests() : base(@"Media\ConicGradientBrush") + { + } + + [Fact] + public async Task ConicGradientBrush_RedBlue() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new ConicGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + } + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task ConicGradientBrush_RedBlue_Rotation() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new ConicGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + }, + Angle = 90 + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task ConicGradientBrush_RedBlue_Center() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new ConicGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + }, + Center = new RelativePoint(0.25, 0.25, RelativeUnit.Relative) + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task ConicGradientBrush_RedBlue_Center_and_Rotation() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new ConicGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + }, + Center = new RelativePoint(0.25, 0.25, RelativeUnit.Relative), + Angle = 90 + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task ConicGradientBrush_RedBlue_SoftEdge() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new ConicGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Blue, Offset = 0.5 }, + new GradientStop { Color = Colors.Red, Offset = 1 }, + } + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task ConicGradientBrush_Umbrella() + { + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 200, + Height = 200, + Child = new Border + { + Background = new ConicGradientBrush + { + GradientStops = + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Yellow, Offset = 0.1667 }, + new GradientStop { Color = Colors.Lime, Offset = 0.3333 }, + new GradientStop { Color = Colors.Aqua, Offset = 0.5000 }, + new GradientStop { Color = Colors.Blue, Offset = 0.6667 }, + new GradientStop { Color = Colors.Magenta, Offset = 0.8333 }, + new GradientStop { Color = Colors.Red, Offset = 1 }, + } + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + } +} diff --git a/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png b/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..8e08d02f66d15179e5fd004f39bb13cbd4fe274f GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZk(GggiP_V|F{C2y?d5}<4F(J>2Q&WLuJ)K$$lYNq6T@l^)Bpr#rDy(^>}|HS vWlUjyUX$RcAi%;jN(}Wd07osue&b`j+h#Kt7)_eY2omvh^>bP0l+XkKIR;Xb literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png b/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..8e08d02f66d15179e5fd004f39bb13cbd4fe274f GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZk(GggiP_V|F{C2y?d5}<4F(J>2Q&WLuJ)K$$lYNq6T@l^)Bpr#rDy(^>}|HS vWlUjyUX$RcAi%;jN(}Wd07osue&b`j+h#Kt7)_eY2omvh^>bP0l+XkKIR;Xb literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png b/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..8e08d02f66d15179e5fd004f39bb13cbd4fe274f GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZk(GggiP_V|F{C2y?d5}<4F(J>2Q&WLuJ)K$$lYNq6T@l^)Bpr#rDy(^>}|HS vWlUjyUX$RcAi%;jN(}Wd07osue&b`j+h#Kt7)_eY2omvh^>bP0l+XkKIR;Xb literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png b/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..8e08d02f66d15179e5fd004f39bb13cbd4fe274f GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZk(GggiP_V|F{C2y?d5}<4F(J>2Q&WLuJ)K$$lYNq6T@l^)Bpr#rDy(^>}|HS vWlUjyUX$RcAi%;jN(}Wd07osue&b`j+h#Kt7)_eY2omvh^>bP0l+XkKIR;Xb literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png b/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..8e08d02f66d15179e5fd004f39bb13cbd4fe274f GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZk(GggiP_V|F{C2y?d5}<4F(J>2Q&WLuJ)K$$lYNq6T@l^)Bpr#rDy(^>}|HS vWlUjyUX$RcAi%;jN(}Wd07osue&b`j+h#Kt7)_eY2omvh^>bP0l+XkKIR;Xb literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png b/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..8e08d02f66d15179e5fd004f39bb13cbd4fe274f GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZk(GggiP_V|F{C2y?d5}<4F(J>2Q&WLuJ)K$$lYNq6T@l^)Bpr#rDy(^>}|HS vWlUjyUX$RcAi%;jN(}Wd07osue&b`j+h#Kt7)_eY2omvh^>bP0l+XkKIR;Xb literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png b/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..563bbfcc611681e14d54f391c617f12e3ed7e7e1 GIT binary patch literal 7249 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png b/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Center_and_Rotation.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a8719e2e704e51f328ef90998babfdfc4cc5845b GIT binary patch literal 6792 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png b/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_Rotation.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..1c79db413bb90fee0331107c23578aae6089c841 GIT binary patch literal 7232 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png b/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_RedBlue_SoftEdge.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4cf8eddd81ad5984cd8124fecc2bc2ccbea94fad GIT binary patch literal 8805 zcmZ{KcT^M4_ck2_482N;p^69yic%$#kboc{H6Trb0RiEIN$6dq_b$>xl}>;ly(lGs zVCY2zrI(0O1ytVnegFRLIXkm^cFx_Id+*$td7c|>^bmHLS&*5Eit4g19P)@#5B|HE z7${}6WlbKXqI+>)*OZBJ1u!9_sHlKcx)89b&l_^?e<{K(?)`o~GTK7ShgQ>tOQNQR=ch0o>9;moO;15@Ge%SD4{`l8cH4{F^55whIP-bO% zj3zAU*_0TVVF#QyJzR9|ot{$`V6wOZ1{HZWU;94ilO zUToB#-Kd7$_Css7C46_j3mLpIE#O&43L`2Ri@laA+9Rl0VaZOSLQ~GY>tey$t+{E4 zvI#MGNli-gAD=ZnaU!p0QwxJaRUN@>y^G6Www=5Ihw~S*ZJE3>le8szOuCIEz z`qzbxB#U&{Jz(nPVwBG?SW=ZR*X>q&B;fG&{ja*-H8w^@Hb(~u^B9xkhW2@;6b?J; z5?ljD&1h3IpAsc)2R#yo`D+5lNb0 z)*I(+(*8tS1;1SJQzQ?fF3~zk3S@O5KRv*o#;S^jgFrPF}2 z6ABHf*5AngzF5!}iQZvV;kw=nVh|x=;*~+KxV)jTS=WO655|Rkfb!z;(9& z^!{4cn#)~%H6>ZwPjV#C4Bn0>%p7-#ISX0ep{7>~GT#XO56KM%7L;{0)iTEzTnCyK zMGp2~wPE49fhV*ke5}Ri9wJu<&*BO`tpT|6Acj4gTi>@5qr~%`(RF)~jMAjOBbEiH{SXkU6K-kAmn*+^rrUURR{Fm1M)sq4eL z%N#VF_!i)tzk-cJCA^&FMbn$)!WrVTds2JwwEyc$x258l2Auv)?q)I+usUM)6J*Gs zCsBLu0J5QbnR(K!73f`4&Q{s5V)4~J1ngqYNBp`Jz6)U^7;vlb4-6Abzwo!&qf1y~ z$`0O0(1KjwcPEE_ionr45MRr0ixoOhzD_%kQ8dUg*V~w*zIxE9Fu)H5mYg16rX}4a!h^pv{_#5ID>p%(>W1 zi5ve!OHyH(!jRvl}mPMi2V_DP*fB_kfoOWeLnE|joK(lI zD%u;h3B%iu&G+J6-JP~^r~a5Pt$vS!VkJ_#NcChqceDlSsLj*&q=>$$z!ay$13*Sj zBhD@Fy0xqvx-Gc5A&aIk<1uon??-{D<6Eg6k%3g31i`S96JR~N&)J9$3;npooAeeG zpZK&++Fr4+jbvmlxV1ot+EUzE*5fLPF%_CPRl^KLdeFsY-rL{r2OldMy`g7VK}Eg+ zC&|6|-Oyt>PEQBrsLT%o;X78!X8>^Xji!@rT!3V?QdGvX!@BOr%yAO3z-e1HUKYr@ zWmUYsX!gYRX~5+W)A59aH|cFbd43wNq8Cd*VL%sV)SPWGO}LKS5*Lb??e3JmvysCw%lD zKSKqBRIazmA`-HL4y{6qpY8QkI_o z!YuSaR-8AkM7yA1I99SX`1o2THTxWgN(W=l!&~))S(ROyaT4@YpU}xz-%I1kAT3N% zKrU3pIO+h#m}&J+#Ct8X{xd}xcP<{| zmUj)M*x5JFLr1kRy3H>$RSH_R0~n$o zYACo}VZgJx)mCkUY`6Q}I??MiNm$(mMF}sc1{cp@Y!N%VCCwJnF;O2b9y#&=b`+fd zl2UU*>D(JyBhkpnscJ?1Jv}AioV_$Lfj>8<(|~19=XcI;(Ghl!;+F?;gDLb8&v(Gi z!q4V5)~XMlX3ogPh``!gw*P5mcPlCG&qNVVNRrrsx?sL;ho**67977`w(+cQ8b*5m z;;!1>kQqHAxJT=W`-D>(YFr<6fUud?97hOT_$NuUag@=rJaar?l^zb_)-@M?T{{X} z-V)WsyA}#(g@#gpxX(e2Bin~$y#l@X^(;G`YtrWinc|e6r+R zeeAve8)|nvRcD)%?nk2SaN5_@XJh8&K2EZ2X>^yi)13=2&ul^+=Eb%5ZEHKnhIZdM z!JF2_P39h}4eG={d~f-{Nh`kgJ_^XB$6P`bFR?%;ix8o92UgPTRQh=aGwN=gv_wJC zp2CO-+Um`8!$5(nT`5-tj}WN-tgWZ(VGai0oK*l86Bjs&bds#e}(_ z=r1||@@HPov6hU@&b?Jc-s;~zr@ghUsg9i>ayL_$3{a4=LO81#Y3eE2G(RTY#KgV% zYr-XoH!XAssdqsQM-*t!bf|Z{ZjRSG*33ccjIJm&e8xftgieNx7@v54*Rrz9jQ zku~KuR>BII>>J~AFSNHpR` z0OG0YwPqjCesiy7=k`;V^nzT=`QNDph0zZbch}0=3>DI>YyiZgjS&mQx&AcVmj|w# z4%}H$4fEOHZo{%9G5Q8zjAlZdftp0?Q*jSF@HgrdXxuf03ZT9Y)AVUu(&NK2NhSuH zNL=)D4~HK^t)eV+<2S9VnJBT7WsF}rOm@IF3^x!)OYL<_h@t@Fxe3hSlyWZN0pQ}T zE;kgfEG_7B_RN&zt6WjAPjf$C{+5FCaKqWVRFrQL%=dbJXSshyn|#49={;My%W$-U z9j}f&^{jJ!gZsAX#Vw&l|A(iNxe>^CFVevWG48w~m%4ph(+0)eU}){jlemS!d7M7X7#6nWf@}fc1_@<32(( zD0W7QMc?VXB-wJwf0jHs|7ekTzyf@5BF(mM{DnBSPabZfs<*x_?`7I-tfqygj7yE| zeWr30+bgSwTv6<&@!bE^;WNrIi42W3dL z@Np#DtzJ3R4U!UF?FdLZ*>4Cq?>p7bS9AMd8K~pAiBVHhHC-@RY!>75`*;NwU2%g% z7$&GO3(>_#IJw7Y4z*>L*BAImiLNE?^Ko#T8h5644T-LL?EC+d*)@1)U-wJrym{e^ z{?`|yDj=)(RW%deH#M=V-!7cBCxh>d?;i6tk+s4JwkP}I_lBG0siLBOH~dbdl)$B7 zw*8LZ(n}q0)IK4^R4ruZ7C~e}2P8Pv>p!hgp*EVq=lLRV-q-%VDHrL%DP4o2cJqG` zf5k;t|FH0Lus)C!%1?L2Zo0HQ(<+4q&nQS#InI;ETJu(I|G5-!Z|&ad5)`s(xR3Ic z7hSCmNb&deqfdtm5&8cl2$43LhRkTah(@-UfZP-Nx_g?BYrTTg_Es5BZU0*TT`Q^G zTVp(Z7?kF{|C64(0~%ZK^UFw~SLu?wfAn9;wQnJBe3L3g)YTL=+cn@j+Q(A9=lg{1 ze|iCTX0q+uLV`5Hqkf+$?|AR028eaFYvJV%2_yav1tvK#C&PhB%)G%H?ZlMWu0GXo zonl~1QGpmv5Y3X@)(rma%6|UZ{{~RI&-hQ{16{#F_lCUpA8N)#wSR61a^43f%7;{u zGb5Z3r-HBTZu}jP^;_0&H$ax0_rK7S+t(OGo^O9m3k7dv!#Uhb4l|ww5}P{@twXBr zAddYhQ;$2@uF#)iRNe1(K@m>PkH1XE#>70rSs^@nq2SjTK$3Gwl9M4r8vh%6=N9QvU>I_U)rij?k&~-FbIk~ zxg$*J5er~%rk!p7*SFQ-yxaX@$Xu9_IO$&L?(;4XCEnG#?R~^C&}#9d$OwV1L>@-p zm{`SLld&l37Z;in)wmhVhFh{iv>h(xS;BeMPi228#W2QWeCR^>gw>H64fSpN^B8dD`;RlX3S~$0neoYk)s+k_B1WhVPrq&^A{14 z4l4-8G5%4sSa3Lp8h@yX{ez0U1r$t^177!-V9Ii`rt7gqt2`uhNq?qC-g&h7`}t&@ z(`9W5zXIv7LneT0_$%#;bR6cL$%fiba@E_=YUFo_TR#a15%FwOQ0y!GqcW$} zoWo1}xO&OuS^DCJj-kKcJKP$FpLc&Tr5zn`iwnk?=_Dy+GKoU5!1_^J`X~-wKysnS zxD=Jcc8vglC7KTbf~zlp?pAT_9MxwoJ~g|F7~Nj0O!D!f<9;CH8O8c{vY45ZJPC%5&mN`kCptP)oJ|imHaC)ASTX63Dql{8uu9902IvF)dkMq>M{%h*i}6y z(#L>5mp`c(^CC3RmoW!o7ZM)@i42w~_xXA{Zz1MLSc8;{U&``N#l2FY?Sg09<0?djdY zHQF6KrLwf)VdrJedFnaq#SkU82VrkBs0^^2PmuPrq^g?rTbLP#1zJ;%2d%l8^QYwQ zaZJ$Dk?NWDRO8-Z?{PVP5piKH8Ao4N>!xPrK|Kyx9wRhjV{UR(Z9A2Ng_R~M;@Qsa zJ>w8l@K3Y|KO9w9XAd;OD>2Xl8YG$MRvT3VGiENrhQqoEO`e-dQM{L-40?TqM}a7~ z#J9({fg%dBvI6X8a7A`@{Vz`Bqv^c*rOvtgp0bFC?+Ua$>rg^FlY>MJ`&MA?jfGlY zd7j(mR`DX{dn$@t8t4T%ap%hPNqGA%60Splb{L#JcGZ-gVKGxUXv#Q=vH=;9(IX!;(MMMO(xH!eLf&vs0`9RmCOiw{^|qvfYV*d9eS-C%oO}x8GzNL%0z>6?f*d{SWlrUQtg@`^va)l9 zIll2bp<&ahh~WO9>nyBnfXh0(8aU@(txxn&7TWuC59y1mwa*YC3ig`;e4Oj7J@-Qj zqSm>q11DjaAQs+53%X0!r+;Duz&EM&U=_xt0q|NHlPLr0NBZ@C%*%OPRk?zp$d@Gp)(~mdfo)gy@tLg zod}qpPQuG!W|o8ulC~<#FaT%+$L4pcoml%9VLncGDjA~lU-g`S&Zt|esy%PA%`9^m zeA7<*GX3~!Zbaym4J|NrL3*`#`g@GeA2c;->x00mvxD#(7mHA*-$|O({d#I4=&KHF zYq`}tw0db{-Gl3G+ePGb5y$O}m1@_&a0HJR_yx{L2O>p#fC_g4{tE;TCK8lE_cKljS z)k}!&PjG`eHNMKTltu?X5CuPR02lZWc2(;;mUiR?2S(pNDDuayd;Jn9PI*WjhBkxa zZVD{8i==Dh)@XaqO5?!MK+=lSs0pTgE9#T*y`0#OD6d%oan}jzi2ptVfa(7Dn;Pf4 zf(5q;txNzGY8ns)2XYyo%ic6{ADgwHmPn~o39oyYp>lm3bCtLo^+LT&wWe$=Bx-%|a2W0%+r;uR6T8plsjvNrT9S()>cM-CBV)4nOoHX<^Hh&U!0S7IK%$|R2m3bLm}EP^E~yRq=jV=hl;YuxZ?1MMXxi^e=2hr%da z4AaItzc(Z18i0y}e%0$SNVa%=z}yqeBZjmpxrv54NUQ8Wa1)az?cpBtMuTf_Yvg%g zY2L3hO=X;p7wHBsEq!XDPyi>lY5vD9d`CI#aO2 z5K|1ZIt8n|{R3^%tA)eNCv56^4E%sJL&=H*Pl5IBQbkNGUj}(&Bb@lSO;I~y8vb*q zuma$MyQb9x^Q40X)rE&I?|Gxi`)5$UoithgFIg~f;hGFZA<_bODX+ir$9Xj=u$8A_ zAzB!>AYDSoxVvSJm=Ql5cWfq=`29fS7F>?dR}qrZQ%D{R*}NZqi2dspWRXA>9zhM2 zuZ?F6+gNpjv<%(I7`1SCOJfot=H(-=h~%?ktle|2#s!o*5*{IJ={$<~VJPu=T)lOL ztfj~R7^1akng71A^|H;yX;+e z*PSx)7B985Yj!m4PowTPu6P$_;iaw&Z)1U5-JDHM$n%ZwYFhwlSy^M#a`+@4EsHhZ zqDiz6=;uVvaargT616VZ7{3Z(~0s5}1@h2xli zH0O2O54iZj{5sb~7sM<@ML7poc5gfLBZXi&F*c?<8ra0{+Mn&CQPtYVbLjmqSvI1R zt@?lqk>Y2KGxwfxr3=_sWHxJ(ES+z7uQvQhe!pF|n-wQgnGi3&NgRW)*1Ag;ErsiI zeC$rrc)P2SoN5sf`idOsvf|#5g%yxB`x&C;=j^@SE z2jSGjX{G9es#WVRKy*+d?>`7ARuY`f?;N0JXFxiKh~M+J@H%$BpVFOB0Z-1(JU z7f6mfLVTlb`<^prhjK*J2jYHaBwT}?!pn9Jf8{D?yLGKE>Z*C7yY21m^QuUFgWORg zjBRSXg0w1cruM=L4_L*S=v*B)IwjVW22F=Hjq>RGCS3l!e2-UDnWj+3JN#Stv5|Ge zQkI^QOD?#Sbr`+dYYt+1Z6_tD;3XafTd?+h2T`@r!OVr<9M7dDPU*0Fgo2#$W!clv z*l;0^OfKx# z6Y)b{*A(-o-_q4hYPz571iOB!dr?^>Z1VIVozdlv(>+7K*xT-w%Kyk*|5o^3)A{Ry zt=Y$jvZ0Oc|FOB>z`{fi#V{WNOtrf9uBKOa!`JM(tF4hX+Q%=5`~QDN*N6N}XWCLOWW)wvf?1u-Er<(2?fcx)tsigq_RbS% z`^SCPwo$V`lXKIOC%PD;C0n?_Ngd^G3otT>!rGJYt{%mS^JK{7+{E9Px3J@taX=DM z$tGhdUb5U~x^v;VmE>+EgtNAtR?p0vhhvrLy^P@{hAc`ZKyT_4#kfQ|{#?x4nvRdC zeEAi5n6{T$*Lt;_^a1+GQ1Wxew4x$n&1GBRqG249V0hqi;jIca&XibaQn?3~gF+15 zs{doYE#qE+SCc!}Dz9<8BMQA#e7+4D91S%4A2e&DJOh`!Iw!DKUhXh#c2}lV?0*>! zoK0`}=SfbdGPhXoX8(a&XbC0rOKy9AD92rfY|2s?L|EF>6Uzq)6Gx^}hQ=L{D ROvxLe(uF>R)ZDiZ`+qS-*r5Ob literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png b/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_Umbrella.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..98dfda782dcd436656c74249ec05013b7b6c3608 GIT binary patch literal 11408 zcmZ{~c{o(>8$UjSvG2=R8XAl+Mj=GDv5zJDjGGe|`S=UDxlA@Aq8SInOzNoclTFxu5&KU)TM5C0JP)bFhlA0ssIG6O@6? z`Pl!zlbP}ST7`X;b3QQK*E6wWK0h8Y$fM?(%~^pDr!|}^?Ud!-upIbT zs;6apa#Vi0&a8>cZraGV30JUowg_rknUWk*{P zpaY-V-JNl7LsI2SgdC>Yv~T;R1x-yEyU&g}^kw(q%Jd5VU>Bk#@5Q#Fl^_KDi}y@5 zaRm@qeH#xYB%CA#NKIy2h0aof=Di+->(6a7BLY?fg@p*07{XgP(VjaXYV0MAvoYIC z%zHSO(Eh2in?&~|)UC7;ZeN4FZ;fHyG?ZmM^m`p^+%)yCNj+wCSCu~Tvj4r7LbDv} zrxkLW1RM(zfv@k(Eq(0t_MxL3D$aJaq3~Zv-SaJwdyn$wfuWyL7id11-YgE=p=o!$ zYgD5mlzA!MK6T@x={K>ummka|{Kx;&xv-Uye%fouOOqCw zrzxd{(NKSR4YGZQPL;p#5^Qd~HI%KYZeGgDuG3}Y<$@8vhv6HPB{v^d++?}}xGFxT zpkRf6uHN3TZCi;_cXS3_qBQN^WMLg@z5TOmx%UJu<-#4pUB%`CXYvdOKf_W7 zvBBN>coR5um|T$teKS1R^dOTxW-w4v55btkln5Gj&WZ9uIld&N^$`UyC4mpm7BdzDl|>hHQ21`^)qXUo*5AG2>Gblj4EFi&5=Iyml~zu=Pz=FV9$ z6t@q~6%jJig$H1_n5cFXsQ@f%T^ynRT?GM;)K$=-*O!*by5SK&L2raH)Re+jNSF!4 z=<;I4d+PM$SvUUN%pKH<{6O$E6<-`jKu zs@a{O>N`AKV~0(>(+Ux;aS#|l@}5sCM@US!<|?@)Tp!5gkirkMNtNP5y-a60R80jH zDjlnpHbd^84fk~Hpm{3Au*T%xJaQoSZ0Q!^O_GVwgZXGT1|dg7+=P}|k-GiBP0cyc z=ecj9?U4g|uH`16yDQ+?)_t!s_%r6N{*dztPHkdbQ4F}v)BiW$as;z^o zqErHQsQI_mVUZ#catLn5?ellqq_W@gjj{s4*i0X|G1D@OZdw_bN^H$)!s-yvI}AhT|3J=`W+sRM4ee_)dXfnl*g+Z)}AMaG*nCo)o0L z-uR-XJUULdB91|?l4YWm`94<%D^!G2IC0}e^kgLt=I=c96^$y;@O!T~ohg>~XGqK# zqoNIgV#w$LojD$_fP%t=lMP_OGNl6rC3JeZdFEJbE=mw=+wziZo}ff4B1b7tR{WW` zH-3rGu_g7>xIc*0@46Aajz88IG{0r$d<<~EQ_Gy@g*8j>y6<-quUm<9SQ(7k&)^^z zi=h>0l7|!e%~y#E0tx&*Q3lkB{i@^jLq#w>3RmWG}rbtRanfxtfN4C^^-NlOC5!uI2F6YPX`QfoNd{7+ zHfyuO$~?W1fENw;Xhj!loSXh>rqqLb9*%}HnpF~F(9|ewvjQXGEB5GX4&l2C;@%_1 zs256`sT{pZdH4<#K;mLI4S;%%osmk{uLRkqm~(!WHD-KBx}W;HamJPJ(k|^;2TJWI-GjAIRE{k% zBNA{Q=lBMFJS;F=!KATxV;Wx49WvsnUW>T;@QlfAL5fw$N-O!oh2(tV48=Ti0v!WX z_-aCJ7?%C$lNKx45+#f_aWs?{f;q(2?EYJ9Fwiy9FWBT|VC)M(B2Xpoz`FHCBT<;Wl-ASYwgq@aT^_eI_p3ZWgEgKjm% ziL8nc9RG{$J6si2-85QYgWEQ+@QH>RW0Mz`%&gTwAfZYK^|(pmdWOl3udB>XWtuC>`s$x4r|VQ|XX6hrSXK`GeBiqHxlmsnHl1-5FV zSS&S4t$6qiZ5D|cGNo4MR=F4Zzn?c zabwBU*OInR^K&s=9C{N?uO1Y|;D-rKr4j;@dvjSe@C!Nac7^oGU<-!;Ebb8n64NQ6 zJ9g~-W~f~Rmh)6Nu8VwXbv zKA;sxNB3)bfKJNhX=ubaeS(;HLaQhdKiPfZEu{JC zyv?(uM7xrg@St|V3N%o>V@NX^SPT313o1@!Vz=d4c<4RU*EK~whVd}Uic_OEeM=_k zD|TY-Ig(1m91SIKN7#NBJh29%%;&Kaz!42HGE#@DlWn+IdS#AZvp|VIN>0a(m%;Sw zbY?@AN<=SIPSU#y_a4Xio4lOs!rSKRi5@TUY)Q;f=I0(|8nIF9+5bMD0x|$6F$vy>;E1;=~UMg9qP0~LHk~*S_9L8lAK2==F!tidWREc)xmwZ=HfjPbAlO?U4v2(W3 z1YO;iXGTVgN<}1dunUMDx{Xg6xxkg!n@!ou=a?MmZFY$C_8=7iU99kQdmWjs*yqW5 zvg>CWN@#wAcn1@;eZs8u zboN6gF z5DbWro`a00VavH9m$8dTAC*+jH`eY~f4uANLi_;xmd9&9$D_df)*~)J9#W^B!L&xV z9HXIB!vV2>9a0&KMOqkYs*L?jw=(N?jla#yUvhbzovBSJII)<}V6!eU9Hu3u8227p zySr*#W|yV3j4l#`zYxiXnDIvg`DT4sU2lJpnec(4pxE&2ODeN3rpY-Tq02Y8pyr0b zrQuC=1rlBG%~Y1+DL_xuirr|Gb!-6>Z}@SX9Eu_KT1sa=&^!4@tb?QBWgm&|SHXi0 zWsR4hHoD4?;m6nY5lsV9rnQXF`K|i#N-;2Zn|OMTpo4wdtNE;i*jv1b5dx{7jz&ie z>r7o2W@k4%h~JIhWfk4dDut%sa${N=+xT>d^-BY;+Eqv0tE43AKCQJOgH~@<2);lW z!h}>|9o>lSabk#tkJ?tcLjVey1K3u4`Bf{Ba_9AtV}Koc#o8RKHpM4T&8rO998i-K zm=xl%Ff^q@|GbkKRr7B68JdSNq4<*0c6K}F%A{dfekuGztL-SjCBO?s2O0}dKuHL} z_*0|sU2-h%gCkbOgqqF!k0;-}iRwQ^p^D@@p)Pq4&%@>rwdS0FouJtov*!XH-pC_) zhab<81#cR84`qtReL9J?dbIERyC)*dvmU1exwVh>@#XJ*SS7Zyf~(UMWQwZ2P)Dut zmBC^6V#`tdeh$Qjv^OyfF`yF*v97q1&W{uYK!FK;+JH_|2Pww3<*qD~Em>uGZ%)`u zjyd20YW2y}PI28jK&|*J`7Gv!Q-AR&@~Yf0p+}0zh19xOtrU)Q^6ItwDqOB3IRxfC zI!{9Zc>T&$aWogC5>DQ}z{0{-UqE>bl=!AY4yJd1ED9{X>;5CjB3sZy)MDOy;56)3 zn46M`r#KodpOgA-b!CC->7`}_Ta)Z4>i|v)&Psq%!&3+G%VREJ`d)aINmO8FqkF{H zyNYz6rJC(~)~Uwk6l_Wan%rx*0=(Z#$`Z)OOQ_s%!LP~bX?%Bt8AX1G8}Yv(^*~D#5ZA~32$Nw1i8QM`wd;K zAlf}EwAVVCJ(;6j9_k-tEf|vOurqCs$7D+ z>n|M*?Z~}>oK;}3&?tI94?&;2a>6S_z=c@SlwpPXaJ8MX5z|qyEcvor%@LsRb_1{9 zz$3#5MNHi7r`hH%(;jhP{bX4ajkW6+L%Tqn8~Fp=kJljVG2e-k(T*l_t&osNjG)o&cvn;1996d^>>-Q53sv6mtnvCl zvecF&x>$O(0@+3MhStOz-bbmtfE{nh zUuE>v+Mm}M-|_J;|AfZH8GMsCx6--V288mmvqZmea^Xylh&pE8zLwE;GXN4$CDxYk zdXj0}o};)Rbn1ZM^nTE!1zjD7t7Zs9^ z+q#-8c@TiGX2(ps8;L3@d`9$)@1$0oZ&)8!($i1~P5<&=?c6i`ig0kqdV~$9c}7wA zr2}w>?=7e`QCZFDD_!@)i3vdJ@4kAL_J`!<{un!BbhS+HI5*tZD7-DgiZGAsoLnh1nG8F-tUOVt4$glzf*JYwp2*n1>< znb_KT=j-3#u2)YC>AO7RLobKBmh3*yH`ud$O5&S>esMn*e9G zgQIVsTH|ex`EGUm>hwNcE)Qp|}6#}5U)bNoTTE^7?9PAVAFJpr8E!C`*$VtFsw9m!xDia>c9;HQAKho=)9#2F{Q+#v^g0a# z=m7UX#N2b^dnW>W?U_rzz!^2B(Y5ipkBqH4yyz7fe7`YjapTv+Fs!xNhB!N_Xuid4 zuxhDyG1HTb>Y9(0;p@|4=HZF;NTG?>Cg% zA)XCt$3~5vm{wMoik^S0(qUM}-6pl33T%)@)|zo#hy$_i%&Bee+p&FyL)}^04uZ9Is>vF6a;Mt3O@drB zsEl(0{NmSmM7pxi2KaTXvs`(F^aV$^!f_c{V>NI;;#6DMb|LND!HDP^zeSR z)F7DL5l%em!QT3jzXECR0>C-^x76NR#xKx6kTZ@j_=ip~b4H_kkx%pg_qKd7wE0=X z=0UoK>qLeLQvRE#crrWbXlS=uk+jcbp3ffvTABvz`91#!6MhttBe*nqt zKlPHBvy99f$K}Vi^uOf)@wIpV7^O5^GgFq?-np_B6!~MBp0{{%`cv|0VQALxwjtG< z{I18>|MTi=-UJJwg&uqz63ZR@qA>PF&p!p<-U~`@u2u*i*}rN|JcE4VqaC`@bm#yl zAqihr+V^yRov;tc-4YYs$|ZW`3;lr$hWX~P-dNX-v;X1!^ThL8*tz3Ggui|dF3uq# zM-FlQdg7CMk%z5q3Nuc3{0H|5OiO<LF+yHJ{RUceDx|Oo7A}8CQW*p&5${UhGVy$Yp2U&{| zYVSZL56kA>qH|qNpPdn9cW41~sH$wT|nJDMqWONvC{|G*HJcO;b zyvcN#i=4Z1^U;%QAz>pX6W8v@!oV_4@4f=*OFwMVl1}T6&Ch%PG`Swf3uIGuQ`rg} zere{1!hc;)BQ)&awL}oIex3#`o*VLQt{8fZV0skozbE2fdqr1CiJWb0x|?ApNQFox$Mc8T)F&G=W>QKA??f7FMeCtF^h*Y)3P&j=>0ju zpPAt3$CWy+Dbq76@{7v>7N6&BFaK?9qv(j>zqEFfX%UTu1` zDu@7P_h&dd%fRxlx=H!JHOcKGvw4R@tgBUP2b{Kl@AMxE$_ah!9rr4ox_6-pduXRHYMQt z#Ez}fKf%WfS+{J*Z!^&My6Vwf*S|^q6EoPUJ!&E)`xd}O$5#SvpeLdKiY1zUaan%R zB_;awMzt@pPTZcs1mi%t)4g#xSY|n`wZ&O05%h7bj63nzQ$UhF{-Pi*yr_ib2)30;(#4JNnM3cW>YrCv-y9cmA!WR|Tp{3|i zT#$|^ZeJFmwt#aSNN0pmpL>9JzJPy6lm5L$7D-;e;W_O}X)p$|^X_Edw>k+)k>1{q zF)QnZ4!{S-rJdR+pGBS-ieoIl<0q#Xqjd}JbElY@XJ-24H_PHmA@$A| zKjXxDhIFJUec8hVTvrf{^}&K5gVKSKbQ%(`hG$V7_{Y> zZPH%(m>UGia#jMRic^1HqdWd{F+s;VWT&6{s5aNf%hSO>Wnrb+E7EnB{8v^(9alOc z5N*JPuU4n| zB3LS6(5n|GiaDzgNC}13>i-7hm~p$>y!>Fl4+_$r*>|T|A2oS75;{J4w=niZ6^i)P zT2)G4+t0({XeCewHKQ0?w1U#0c)l7k#+IGiQmc-r`Bg+|%c0_etD;osmCb{MbNtQm zUhKZ$%hS@lt$<*UauQjDot?UAyilIrHkME>L(LnJTzSxRRz}VPtbzsKwvlA`G zga^S{k#?6jUT{{u(nE;)D7qGYv2DQBem0L*y3zMZTAB$mO8HJ1qK_$0iHRoFU4mV+ zrl>5D4>&9pA3X28NBKtu#l+f+@E&B~yZz<8{aciFT+Me^p}7|BU`_{5;7H?Th~?;8 zPPGwEeZ8k9pH(1t|1nz~S%>Z<6$_kG6^+Z}K+f5|rOe+MoLg<&Lv%$2LL%DK$Xl`4 zIqR!ReIw*nr8$Jv10w8?qhDZvxC>cyLbD34G%UiBP%Ks{(?1tr$9EvpaNYF3#EI@i zybu&|RF@N>sDPa!sD>MR!V4beJw3i-A@TEDtdl&cov7pzs5Yuqk=o?T7wd=^cD#@_ zDS&4XGJ6(H5(lae1`OJ8l|V63yHeX?knIHyjs#&>MKzk$V<0c6o8au!uVSCN{=V<3 zOrIPQVTC}cmbx3unqY2dUMPY3Rv3el={t1B>YhpRz44OKEXL)dpG6M8$_e_}bU-<= zcvTD)q4sNnQUjC){qi6+eS9E;Xz?u$a^XyYro8dYNIO3@NghK%D4#}P&?)>vQy*06 zdcs$$9zhKPaCH3l=bmP{@MD>%GPDqJgn zs)pMakDYu@d5f#La7U%pk&fJaqXhC;AVz9?`?)9wwGj}iG-x(6X?31b>K5tanauH( zvgAf5f16kHmxaKPxl$_NYn3O-dregQyj3$pQaqkX2xNgy%_u{dhIMCO=(@lQCiOE! znAwsSDROnMVBsd%MmzOaer2becVksj1HT(An0+q1)jgyEj>pbED!ReWxrOn3qI;qu zg#UA0Fm)Moa~C=h)A^O;TFzwqA~Dfmsu&6l&+$oOt{Og11QeftJS3i28^WU<;hO*e zyT<>t0Fh$+w^>~~T}2VL{9Cnb1{xY->g!>8+?g^|h#uuu+)%bt65W7>w3 z1I28l(pnLN1shNuv56k#D6dq)_oiV(&Vpg>vAXvir^*n^iZ5}kv9}$NO-3(ut*xK- z$4PCpsmGU!d@J0ImE(FkUgHz|R}!fdfEds%Xi2}16$if!EHMo5TB9({jd)ag zE|rO}HC9l}UxlkZd0fsyZxo$x>5DOxxWI@q(mjX!&BYh-fZDDsnVv+w;$fx;Ft-!R zy;HaP*^6@VeGBqg+MO-8nwq|MR(EHsnFj27M4Fir9;}%K7fklpq&k6w8Og`|a?dHA zlhJh6)+vdBc6zk!XCS?3UGZ8l?&-roY@i=74b8i8RA;#^L1BgY+WXRd z6*XgL3FrYbnQxpmX5X>XqFeJI+65*%+;;vZ6+q)4J30U8z96Xa{s4V|sk6vCZxf&M zqfmEci`%0W#tZ{?VwSTe^ino_RU&I)p9aoj=7WZLU&4~tUu&#etR7m3Mv?UiqlDPU zJCzSFG}FaTYge4^+c5+L;BqHqwcdtDhTx0aIsPQ$8)0*16t-Xnrf|EyK7=# zDB(9s3PD!FeP6-Zv%fwOn8R6=TzioP6LR`Dwhp{+l}G(l!j(wM%g~{?yP~oS8qN&# zyXL@%RQW;Og3sL?rL+sU<804~?x^B4qP<;r%UJz|^00K8MosfrV)pk?5#DkM5h_hl z0kK9LTc1vw@ zM{tyfhL$Zxer^&=gSNc17Vy2E)`F-~FpI7=&~0$U{xzP=wM}hu(l^ZQ?KdSBO|;Q5 zzKs~;7~MRCrsiQSi>O_$qHm>{Wl^mfRn4D<^W>4r{FuyiG|yHD?YBrWjqqL3Au-VW z34;tEFsQStX$>KOBBaNBxR}q&&ahs{!|w>dUsUmrT_oWFW5?zp?&`FMckYb)Nc?GV z{QlQu%`4p8dhXBd+F0VP@R{Z~!V!{p?IN0@2~}D-@)Hxry+W|XJS5FLP}rRPXxcqi zDB<81&=5YT%@`M_mwe0{Z0N=X%pX(`p|HXnzBxyJhxvmr`RU-BNTr8(F8*1teRtzv8$?YItDVbGnAG>#);C)7nj0jFtv2-t^=(bMB| zMs$v}Z~j*W94I?yZH$29hY1f;d_wl>FBpTKdGLdMJK+$fQor(ekTHm2UDEswSk^c4 z{ynvJ=Y$w?%%!#Qd{+k5V0ie6EjdpAa`#dhT^gEabd_MPg4ep>y3ul11t}q<9gM8| zGrm%vtWwGE4V4($g+>|C^OeWttOi1Z7>*O&n*@Ph%ZvTp0JR-8yB%5z<7S;z*tmUjTaFIo)wTVGE*?4p+yM@B@r{?~rEQ~8CbVOO z6|xD4_Pk3`-8oX_xArE9i!R!5bHr}bzj7IS7_=lpP4qTC0l2uJ;+W`jQvs3`N=?KcQdDwXLpgan z(|}SfpNxkL=NsQ!kuD#!u|pO%J23~rHd&gJH52;u=e6ffS?>Y;?6-1qsn|BX^%uXV zMaIc5Vhia^l)+OYr~J{K2i6puY_`YEdJX!jI)x%&WJ{H}3w)g3_$nidZ3)V-Kw+qP zO`SZMflh%#a-JGfk{F~aB^Ezu-%WMxKPAi38-Y2m6eXH#+(R2GxlsMxwEwpXY{|oo zkI#!%yuGKJ+n-&a8xQ1zQb2#pL%OP-+wqvXFBQfC3IgQ!(l@?IvGbZ4(zuM=7nGl+%S{3=M$2RbqdA^{&n8jn2ZLw z-asuK)e`1fpM=t5FK}`RF&bHt(_>+9e^FO--p6ouFe8zP0(#yY8LVZbX9J9FT*&f` zTJG$m*76%nleX>>JY?gyD`T*}?LMyR*a>@U9xitOcWW~O>06|(>z0dw7-z3pj$=+G zdba2;8*Vn`yVl<)`CQS5DL17yvd+o3)9Z$ZbpbWUa(_SkAe^OsFniue5_7$Zv^&*8 zWjk!qpYW-a{WjM+C$mMk38M4op7TVGapq=eQZk>0b>Qt765Q(|ZZ^T$%{ZfUJBhd< zGh=toT*bep=37I^^FVm?_D0&srU<{KY=N)$U5zLSw)g$swF`kY=wsriSKGGParyFv zTUr50?6neo&b&^F@juY1{l6t~nte{a%hOGRod$UZn=XxV9_8_V_5v|$V&`%>HnNx+ zLV+qnX3AztY<$V8fVy1YA}?C@Z7*vLAycuLPjbaI1Pkz%y-P9FMVy;TCMMXy*dBlw ztM7JN9Y4A8(=_3wC#M8`42UsW?@N9gzRkf<7w$6ER{@zL-2(_5M;A1F0RmPIg)PQy=-+nEa1~;GU zloJQDRSB`*jgV?}r2r1Y1BYap@1L>4tQT6|dP+CFnsgFzl8<$-eG=2PIZC8hC(i({ zl%&H7Md}tWc>D%mBw>hfh-4RSQ{`66lm3hcTIc;d{UN`PzCWYx4L!Z6{$57<&it{w zlf>1_TbhOx?2Z#w$tR73%7z=9gvD&?~o(|u#9Ix-d-%php9(?g- zGpIbu7G#*)=dymcmG!-d?Iot#Ku+~5&%^px_76Da1Z#JiQbOBWKtj<{gStxdTl)f( zHTR2^SK=p1rcW zJVRdJmAWqGRUutVxThqZjviGLzxkyZ0`-lF5Q9k~H*O2|uHoM_M4W3~Ki{`%zCODc zyGxP`)N6X>Kcr6^Z2b Date: Wed, 7 Oct 2020 23:06:29 -0500 Subject: [PATCH 142/492] Add tests --- .../Primitives/PopupTests.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index f032186bcd..9223b08c81 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -485,6 +485,85 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void Closing_Popup_Sets_Focus_On_PlacementTarget() + { + using (CreateServicesWithFocus()) + { + var window = PreparedWindow(); + + var tb = new TextBox(); + var p = new Popup + { + PlacementTarget = window, + Child = tb + }; + ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + window.Show(); + + p.Open(); + + if (p.Host is OverlayPopupHost host) + { + //Need to measure/arrange for visual children to show up + //in OverlayPopupHost + host.Measure(Size.Infinity); + host.Arrange(new Rect(host.DesiredSize)); + } + + tb.Focus(); + + p.Close(); + + var focus = FocusManager.Instance?.Current; + Assert.True(focus == window); + } + } + + [Fact] + public void Prog_Close_Popup_NoLightDismiss_Doesnt_Move_Focus_To_PlacementTarget() + { + using (CreateServicesWithFocus()) + { + var window = PreparedWindow(); + + var windowTB = new TextBox(); + window.Content = windowTB; + + var popupTB = new TextBox(); + var p = new Popup + { + PlacementTarget = window, + IsLightDismissEnabled = false, + Child = popupTB + }; + ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + window.Show(); + + p.Open(); + + if (p.Host is OverlayPopupHost host) + { + //Need to measure/arrange for visual children to show up + //in OverlayPopupHost + host.Measure(Size.Infinity); + host.Arrange(new Rect(host.DesiredSize)); + } + + popupTB.Focus(); + + windowTB.Focus(); + + var focus = FocusManager.Instance?.Current; + + Assert.True(focus == windowTB); + + p.Close(); + + Assert.True(focus == windowTB); + } + } + private IDisposable CreateServices() { return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: From 73ab16e68c5ae1a55738d628ac9d43d292dec3b4 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 8 Oct 2020 12:00:26 +0200 Subject: [PATCH 143/492] Fix Direct2D1 text rendering --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 34 +++- .../Media/TextShaperImpl.cs | 160 ++++++++++-------- 2 files changed, 121 insertions(+), 73 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index ae927d44a5..9c2ca053e6 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -238,22 +238,44 @@ namespace Avalonia.Direct2D1 width = 0; - for (var i = 0; i < glyphCount; i++) + if (glyphRun.GlyphAdvances.IsEmpty) + { + for (var i = 0; i < glyphCount; i++) + { + var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]); + + run.Advances[i] = advance; + + width += advance; + } + } + else + { + for (var i = 0; i < glyphCount; i++) + { + var advance = (float)glyphRun.GlyphAdvances[i]; + + run.Advances[i] = advance; + + width += advance; + } + } + + if (glyphRun.GlyphOffsets.IsEmpty) { - run.Advances[i] = (float)glyphRun.GlyphAdvances[i]; - width += run.Advances[i]; + return new GlyphRunImpl(run); } run.Offsets = new GlyphOffset[glyphCount]; for (var i = 0; i < glyphCount; i++) { - var offset = glyphRun.GlyphOffsets[i]; + var (x, y) = glyphRun.GlyphOffsets[i]; run.Offsets[i] = new GlyphOffset { - AdvanceOffset = (float)offset.X, - AscenderOffset = (float)offset.Y + AdvanceOffset = (float)x, + AscenderOffset = (float)y }; } diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs index 254b5684a4..20b09a9aac 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs @@ -1,6 +1,6 @@ -using System.Globalization; +using System; +using System.Globalization; using Avalonia.Media; -using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; using Avalonia.Utilities; @@ -15,51 +15,9 @@ namespace Avalonia.Direct2D1.Media { using (var buffer = new Buffer()) { - buffer.ContentType = ContentType.Unicode; + FillBuffer(buffer, text); - var breakCharPosition = text.Length - 1; - - var codepoint = Codepoint.ReadAt(text, breakCharPosition, out var count); - - if (codepoint.IsBreakChar) - { - var breakCharCount = 1; - - if (text.Length > 1) - { - var previousCodepoint = Codepoint.ReadAt(text, breakCharPosition - count, out _); - - if (codepoint == '\r' && previousCodepoint == '\n' - || codepoint == '\n' && previousCodepoint == '\r') - { - breakCharCount = 2; - } - } - - if (breakCharPosition != text.Start) - { - buffer.AddUtf16(text.Buffer.Span.Slice(0, text.Length - breakCharCount)); - } - - var cluster = buffer.GlyphInfos.Length > 0 ? - buffer.GlyphInfos[buffer.Length - 1].Cluster + 1 : - (uint)text.Start; - - switch (breakCharCount) - { - case 1: - buffer.Add('\u200C', cluster); - break; - case 2: - buffer.Add('\u200C', cluster); - buffer.Add('\u200D', cluster); - break; - } - } - else - { - buffer.AddUtf16(text.Buffer.Span); - } + buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); buffer.GuessSegmentProperties(); @@ -67,44 +25,38 @@ namespace Avalonia.Direct2D1.Media var font = ((GlyphTypefaceImpl)glyphTypeface.PlatformImpl).Font; - buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture); - font.Shape(buffer); font.GetScale(out var scaleX, out _); var textScale = fontRenderingEmSize / scaleX; - var len = buffer.Length; + var bufferLength = buffer.Length; - var info = buffer.GetGlyphInfoSpan(); + var glyphInfos = buffer.GetGlyphInfoSpan(); - var pos = buffer.GetGlyphPositionSpan(); + var glyphPositions = buffer.GetGlyphPositionSpan(); - var glyphIndices = new ushort[len]; + var glyphIndices = new ushort[bufferLength]; - var clusters = new ushort[len]; + var clusters = new ushort[bufferLength]; - var glyphAdvances = new double[len]; + double[] glyphAdvances = null; - var glyphOffsets = new Vector[len]; + Vector[] glyphOffsets = null; - for (var i = 0; i < len; i++) + for (var i = 0; i < bufferLength; i++) { - glyphIndices[i] = (ushort)info[i].Codepoint; - - clusters[i] = (ushort)(text.Start + info[i].Cluster); - - var advanceX = pos[i].XAdvance * textScale; - // Depends on direction of layout - //var advanceY = pos[i].YAdvance * textScale; + glyphIndices[i] = (ushort)glyphInfos[i].Codepoint; - glyphAdvances[i] = advanceX; + clusters[i] = (ushort)glyphInfos[i].Cluster; - var offsetX = pos[i].XOffset * textScale; - var offsetY = pos[i].YOffset * textScale; + if (!glyphTypeface.IsFixedPitch) + { + SetAdvance(glyphPositions, i, textScale, ref glyphAdvances); + } - glyphOffsets[i] = new Vector(offsetX, offsetY); + SetOffset(glyphPositions, i, textScale, ref glyphOffsets); } return new GlyphRun(glyphTypeface, fontRenderingEmSize, @@ -115,5 +67,79 @@ namespace Avalonia.Direct2D1.Media new ReadOnlySlice(clusters)); } } + + private static void FillBuffer(Buffer buffer, ReadOnlySlice text) + { + buffer.ContentType = ContentType.Unicode; + + var i = 0; + + while (i < text.Length) + { + var codepoint = Codepoint.ReadAt(text, i, out var count); + + var cluster = (uint)(text.Start + i); + + if (codepoint.IsBreakChar) + { + if (i + 1 < text.Length) + { + var nextCodepoint = Codepoint.ReadAt(text, i + 1, out _); + + if (nextCodepoint == '\r' && codepoint == '\n' || nextCodepoint == '\n' && codepoint == '\r') + { + count++; + + buffer.Add('\u200C', cluster); + + buffer.Add('\u200D', cluster); + } + else + { + buffer.Add('\u200C', cluster); + } + } + else + { + buffer.Add('\u200C', cluster); + } + } + else + { + buffer.Add(codepoint, cluster); + } + + i += count; + } + } + + private static void SetOffset(ReadOnlySpan glyphPositions, int index, double textScale, + ref Vector[] offsetBuffer) + { + var position = glyphPositions[index]; + + if (position.XOffset == 0 && position.YOffset == 0) + { + return; + } + + offsetBuffer ??= new Vector[glyphPositions.Length]; + + var offsetX = position.XOffset * textScale; + + var offsetY = position.YOffset * textScale; + + offsetBuffer[index] = new Vector(offsetX, offsetY); + } + + private static void SetAdvance(ReadOnlySpan glyphPositions, int index, double textScale, + ref double[] advanceBuffer) + { + advanceBuffer ??= new double[glyphPositions.Length]; + + // Depends on direction of layout + // advanceBuffer[index] = buffer.GlyphPositions[index].YAdvance * textScale; + advanceBuffer[index] = glyphPositions[index].XAdvance * textScale; + } } } From 7ad03026f2793b45d1ef607f84bb42197e8cddbf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Oct 2020 11:42:36 +0100 Subject: [PATCH 144/492] update apicompat. --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 39cf5ee4dd..7fb44152c9 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -1,19 +1,37 @@ Compat issues with assembly Avalonia.Visuals: +MembersMustExist : Member 'public void Avalonia.Media.DrawingContext.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.GlyphRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Media.GlyphRunDrawing.BaselineOriginProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Point Avalonia.Media.GlyphRunDrawing.BaselineOrigin.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.GlyphRunDrawing.BaselineOrigin.set(Avalonia.Point)' does not exist in the implementation but it does exist in the contract. CannotSealType : Type 'Avalonia.Media.Typeface' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Typeface' is a 'struct' in the implementation but is a 'class' in the contract. CannotMakeMemberNonVirtual : Member 'public System.Boolean Avalonia.Media.Typeface.Equals(System.Object)' is non-virtual in the implementation but is virtual in the contract. CannotMakeMemberNonVirtual : Member 'public System.Int32 Avalonia.Media.Typeface.GetHashCode()' is non-virtual in the implementation but is virtual in the contract. TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' does not exist in the implementation but it does exist in the contract. +CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size' is abstract in the implementation but is missing in the contract. +MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.DrawableTextRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. +CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. +CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size.get()' is abstract in the implementation but is missing in the contract. +MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.ShapedTextCharacters.Bounds.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.ShapedTextCharacters.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract. +CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract. +MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IDrawingContextLayerImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the contract but not in the implementation. MembersMustExist : Member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation. MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract. MembersMustExist : Member 'public Avalonia.Utilities.IRef Avalonia.Rendering.RenderLayer.Bitmap.get()' does not exist in the implementation but it does exist in the contract. -Total Issues: 17 +Total Issues: 35 From 8f4059654636d52eb5ff3975ebdc5788b3e60652 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Oct 2020 11:59:28 +0100 Subject: [PATCH 145/492] fix nits --- src/Avalonia.Controls/TreeView.cs | 1 - tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index f0e00e1d7e..b2bd5ab2e5 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -217,7 +217,6 @@ namespace Avalonia.Controls private void SelectSingleItem(object item) { - var oldValue = SelectedItem; _syncingSelectedItems = true; SelectedItems.Clear(); SelectedItems.Add(item); diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index b90b74d794..9253d8a07f 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1328,7 +1328,6 @@ namespace Avalonia.Controls.UnitTests private class TestDataContext : ReactiveObject { - private int _counter; private string _selectedItem; public TestDataContext() From 53eeb8e57885a795bfba9272d75c25796457f5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20Tar=C4=B1k=20G=C3=BCnayd=C4=B1n?= Date: Thu, 8 Oct 2020 15:43:03 +0300 Subject: [PATCH 146/492] Fix ApiDiff.props for Turkish locale. --- build/ApiDiff.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ApiDiff.props b/build/ApiDiff.props index da82fbcc51..3d322f56d5 100644 --- a/build/ApiDiff.props +++ b/build/ApiDiff.props @@ -7,6 +7,6 @@ - + From ddd3dfa0a70ef98b9797fae4d1d1d17e2aa1cf00 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Oct 2020 22:18:41 +0100 Subject: [PATCH 147/492] change surface origin. --- src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs index 9ee8d698ba..24464c39d9 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -93,7 +93,7 @@ namespace Avalonia.Skia var target = new GRBackendRenderTarget(pixelSize.Width, pixelSize.Height, 0, 8, new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); Surface = SKSurface.Create(_grContext, target, - GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); + GRSurfaceOrigin.TopLeft, SKColorType.Rgba8888); CanBlit = gl.BlitFramebuffer != null; } From 8fddd4b94e19f3434868bd3367e83a6914a7b0cf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Oct 2020 22:51:48 +0100 Subject: [PATCH 148/492] remove unused var. --- .../Composition/CompositionEglGlPlatformSurface.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs index b30043fedd..547e2d3c0e 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -10,15 +10,13 @@ namespace Avalonia.Win32 { internal class CompositionEglGlPlatformSurface : EglGlPlatformSurfaceBase { - private readonly EglDisplay _display; private EglPlatformOpenGlInterface _egl; private readonly IEglWindowGlPlatformSurfaceInfo _info; private ICompositionDrawingSurfaceInterop _surfaceInterop; - private Windows.UI.Composition.Visual _surface; + private Windows.UI.Composition.Visual _surface; public CompositionEglGlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info) : base() - { - _display = egl.Display; + { _egl = egl; _info = info; } From 7d28b510d7c792a118a30bdc05a3d330774f02c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20So=C3=B3s?= Date: Fri, 9 Oct 2020 11:13:30 +0200 Subject: [PATCH 149/492] Update TextBox.xaml Set CornerRadius via style on TextBox --- src/Avalonia.Themes.Fluent/TextBox.xaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/TextBox.xaml b/src/Avalonia.Themes.Fluent/TextBox.xaml index 85364bffea..162835ae10 100644 --- a/src/Avalonia.Themes.Fluent/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/TextBox.xaml @@ -52,7 +52,6 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" - CornerRadius="{DynamicResource ControlCornerRadius}" MinWidth="{TemplateBinding MinWidth}" MinHeight="{TemplateBinding MinHeight}"> @@ -156,6 +155,10 @@ + + From 9eed4c974e98d840b883e3534588c09eaab333d6 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 9 Oct 2020 18:29:02 +0200 Subject: [PATCH 150/492] Make sure a GlyphRun's bounds are properly calculated --- src/Avalonia.Visuals/Media/GlyphRun.cs | 15 +++++++++++++-- .../Media/TextFormatting/TextLineImpl.cs | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index 155339b985..66a8c1dd0c 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/src/Avalonia.Visuals/Media/GlyphRun.cs @@ -18,7 +18,7 @@ namespace Avalonia.Media private double _fontRenderingEmSize; private Size? _size; private int _biDiLevel; - private Point _baselineOrigin; + private Point? _baselineOrigin; private ReadOnlySlice _glyphIndices; private ReadOnlySlice _glyphAdvances; @@ -97,7 +97,9 @@ namespace Avalonia.Media { get { - return _baselineOrigin; + _baselineOrigin ??= CalculateBaselineOrigin(); + + return _baselineOrigin.Value; } set => Set(ref _baselineOrigin, value); } @@ -538,6 +540,15 @@ namespace Avalonia.Media return GlyphAdvances[index]; } + /// + /// Calculates the default baseline origin of the . + /// + /// The baseline origin. + private Point CalculateBaselineOrigin() + { + return new Point(0, -GlyphTypeface.Ascent * Scale); + } + /// /// Calculates the size of the . /// diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index d13b4836ea..fc98e9f6f8 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -39,7 +39,7 @@ namespace Avalonia.Media.TextFormatting foreach (var textRun in _textRuns) { - var offsetY = LineMetrics.TextBaseline; + var offsetY = LineMetrics.TextBaseline - textRun.GlyphRun.BaselineOrigin.Y; using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY))) { From b9ff1b23c3e09acb9161116907cb37a4756e19e9 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 9 Oct 2020 18:39:09 +0200 Subject: [PATCH 151/492] Make sure we don't crash when RTL text is wrapped --- .../Media/TextFormatting/TextFormatterImpl.cs | 10 +++++++++- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 4a7282af27..394dae8253 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -126,7 +126,15 @@ namespace Avalonia.Media.TextFormatting var lastCluster = glyphRun.GlyphClusters[glyphCount]; - count = lastCluster - firstCluster; + if (glyphRun.IsLeftToRight) + { + count = lastCluster - firstCluster; + } + else + { + count = firstCluster - lastCluster; + } + return count > 0; } diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index 61075171fe..31724bfee9 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -64,7 +64,8 @@ namespace Avalonia.Skia new ReadOnlySlice(glyphAdvances), new ReadOnlySlice(glyphOffsets), text, - new ReadOnlySlice(clusters)); + new ReadOnlySlice(clusters), + buffer.Direction == Direction.LeftToRight ? 0 : 1); } } From d57ad558d2dd2bc86fa2271c8ec02881e3dc40ac Mon Sep 17 00:00:00 2001 From: Will Kennedy Date: Sun, 11 Oct 2020 00:27:07 -0400 Subject: [PATCH 152/492] Fix IsChecked property differing from :checked pseudoclass --- src/Avalonia.Controls/Primitives/ToggleButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs index f96ca9310d..6b2c566422 100644 --- a/src/Avalonia.Controls/Primitives/ToggleButton.cs +++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs @@ -94,7 +94,7 @@ namespace Avalonia.Controls.Primitives set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); - UpdatePseudoClasses(value); + UpdatePseudoClasses(IsChecked); } } From 64c1bf890e04385b601c516132346e4da12e8ef8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 11 Oct 2020 15:55:21 +0300 Subject: [PATCH 153/492] Fixed DRM output --- src/Avalonia.OpenGL/Egl/EglContext.cs | 3 ++- src/Avalonia.OpenGL/Egl/EglDisplay.cs | 22 ++++++++++++------- .../Output/DrmOutput.cs | 12 +--------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index 5365354418..249b4d547f 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -73,7 +73,8 @@ namespace Avalonia.OpenGL.Egl var old = new RestoreContext(_egl, _disp.Handle, _lock); var surf = surface ?? OffscreenSurface; _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context)) + if (!_egl.MakeCurrent(_disp.Handle, surf?.DangerousGetHandle() ?? IntPtr.Zero, + surf?.DangerousGetHandle() ?? IntPtr.Zero, Context)) throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); success = true; return old; diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index fd3de854f5..623364866b 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -158,15 +158,21 @@ namespace Avalonia.OpenGL.Egl var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes); if (ctx == IntPtr.Zero) throw OpenGlException.GetFormattedException("eglCreateContext", _egl); - var surf = _egl.CreatePBufferSurface(_display, _config, new[] + + var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS); + + IntPtr surf = IntPtr.Zero; + if (extensions?.Contains("EGL_KHR_surfaceless_context") != true) { - EGL_WIDTH, 1, - EGL_HEIGHT, 1, - EGL_NONE - }); - if (surf == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl); - var rv = new EglContext(this, _egl, shareCtx, ctx, context => new EglSurface(this, context, surf), + surf = _egl.CreatePBufferSurface(_display, _config, + new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }); + if (surf == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl); + } + + var rv = new EglContext(this, _egl, shareCtx, ctx, + context => + surf == IntPtr.Zero ? null : new EglSurface(this, context, surf), _version, _sampleCount, _stencilSize); return rv; } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 72eed9e543..dc44d2d55f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -141,17 +141,7 @@ namespace Avalonia.LinuxFramebuffer.Output _platformGl = new EglPlatformOpenGlInterface(_eglDisplay); _eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface); - - EglContext CreateContext(EglContext share) - { - var offSurf = gbm_surface_create(device, 1, 1, GbmColorFormats.GBM_FORMAT_XRGB8888, - GbmBoFlags.GBM_BO_USE_RENDERING); - if (offSurf == null) - throw new InvalidOperationException("Unable to create 1x1 sized GBM surface"); - return _eglDisplay.CreateContext(share, _platformGl.CreateWindowSurface(offSurf)); - } - - _deferredContext = CreateContext(null); + _deferredContext = _platformGl.PrimaryEglContext; using (_deferredContext.MakeCurrent(_eglSurface)) { From 7b0598559a1307ebf2eab4da53e11d05884582ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Tue, 6 Oct 2020 12:06:01 +0200 Subject: [PATCH 154/492] GeometryDrawing does not need to allocate new Pen for each GetBounds call. --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 4 +++- src/Avalonia.Visuals/Media/Geometry.cs | 4 ++-- src/Avalonia.Visuals/Media/GeometryDrawing.cs | 10 ++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 7fb44152c9..2d00d82a46 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -2,6 +2,8 @@ Compat issues with assembly Avalonia.Visuals: MembersMustExist : Member 'public void Avalonia.Media.DrawingContext.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.Geometry.GetRenderBounds(Avalonia.Media.Pen)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Boolean Avalonia.Media.Geometry.StrokeContains(Avalonia.Media.Pen, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.GlyphRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Media.GlyphRunDrawing.BaselineOriginProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Point Avalonia.Media.GlyphRunDrawing.BaselineOrigin.get()' does not exist in the implementation but it does exist in the contract. @@ -34,4 +36,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract. MembersMustExist : Member 'public Avalonia.Utilities.IRef Avalonia.Rendering.RenderLayer.Bitmap.get()' does not exist in the implementation but it does exist in the contract. -Total Issues: 35 +Total Issues: 37 diff --git a/src/Avalonia.Visuals/Media/Geometry.cs b/src/Avalonia.Visuals/Media/Geometry.cs index 33aafb58fc..ccef6665a9 100644 --- a/src/Avalonia.Visuals/Media/Geometry.cs +++ b/src/Avalonia.Visuals/Media/Geometry.cs @@ -84,7 +84,7 @@ namespace Avalonia.Media /// /// The stroke thickness. /// The bounding rectangle. - public Rect GetRenderBounds(Pen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Empty; + public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Empty; /// /// Indicates whether the geometry's fill contains the specified point. @@ -102,7 +102,7 @@ namespace Avalonia.Media /// The pen to use. /// The point. /// true if the geometry contains the point; otherwise, false. - public bool StrokeContains(Pen pen, Point point) + public bool StrokeContains(IPen pen, Point point) { return PlatformImpl?.StrokeContains(pen, point) == true; } diff --git a/src/Avalonia.Visuals/Media/GeometryDrawing.cs b/src/Avalonia.Visuals/Media/GeometryDrawing.cs index 4df3aa8ae2..b5052550e6 100644 --- a/src/Avalonia.Visuals/Media/GeometryDrawing.cs +++ b/src/Avalonia.Visuals/Media/GeometryDrawing.cs @@ -1,9 +1,13 @@ -using Avalonia.Metadata; +using Avalonia.Media.Immutable; +using Avalonia.Metadata; namespace Avalonia.Media { public class GeometryDrawing : Drawing { + // Adding the Pen's stroke thickness here could yield wrong results due to transforms. + private static readonly IPen s_boundsPen = new ImmutablePen(Colors.Black.ToUint32(), 0); + public static readonly StyledProperty GeometryProperty = AvaloniaProperty.Register(nameof(Geometry)); @@ -42,9 +46,7 @@ namespace Avalonia.Media public override Rect GetBounds() { - // adding the Pen's stroke thickness here could yield wrong results due to transforms - var pen = new Pen(Brushes.Black, 0); - return Geometry?.GetRenderBounds(pen) ?? new Rect(); + return Geometry?.GetRenderBounds(s_boundsPen) ?? new Rect(); } } } From 13d2c286fdd6aef41624a5fffa4f0dcbbb85f7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Tue, 6 Oct 2020 12:22:41 +0200 Subject: [PATCH 155/492] Add doc comments and cleanup code a bit. --- src/Avalonia.Visuals/Media/Drawing.cs | 14 +++++-- src/Avalonia.Visuals/Media/GeometryDrawing.cs | 38 +++++++++++++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Drawing.cs b/src/Avalonia.Visuals/Media/Drawing.cs index 6bc808e407..66d05e7e48 100644 --- a/src/Avalonia.Visuals/Media/Drawing.cs +++ b/src/Avalonia.Visuals/Media/Drawing.cs @@ -1,11 +1,19 @@ -using Avalonia.Platform; - -namespace Avalonia.Media +namespace Avalonia.Media { + /// + /// Abstract class that describes a 2-D drawing. + /// public abstract class Drawing : AvaloniaObject { + /// + /// Draws this drawing to the given . + /// + /// The drawing context. public abstract void Draw(DrawingContext context); + /// + /// Gets the drawing's bounding rectangle. + /// public abstract Rect GetBounds(); } } diff --git a/src/Avalonia.Visuals/Media/GeometryDrawing.cs b/src/Avalonia.Visuals/Media/GeometryDrawing.cs index b5052550e6..e46e3a7d5f 100644 --- a/src/Avalonia.Visuals/Media/GeometryDrawing.cs +++ b/src/Avalonia.Visuals/Media/GeometryDrawing.cs @@ -3,14 +3,36 @@ using Avalonia.Metadata; namespace Avalonia.Media { + /// + /// Represents a drawing operation that combines + /// a geometry with and brush and/or pen to produce rendered content. + /// public class GeometryDrawing : Drawing { // Adding the Pen's stroke thickness here could yield wrong results due to transforms. private static readonly IPen s_boundsPen = new ImmutablePen(Colors.Black.ToUint32(), 0); - + + /// + /// Defines the property. + /// public static readonly StyledProperty GeometryProperty = AvaloniaProperty.Register(nameof(Geometry)); + /// + /// Defines the property. + /// + public static readonly StyledProperty BrushProperty = + AvaloniaProperty.Register(nameof(Brush), Brushes.Transparent); + + /// + /// Defines the property. + /// + public static readonly StyledProperty PenProperty = + AvaloniaProperty.Register(nameof(Pen)); + + /// + /// Gets or sets the that describes the shape of this . + /// [Content] public Geometry Geometry { @@ -18,18 +40,18 @@ namespace Avalonia.Media set => SetValue(GeometryProperty, value); } - public static readonly StyledProperty BrushProperty = - AvaloniaProperty.Register(nameof(Brush), Brushes.Transparent); - + /// + /// Gets or sets the used to fill the interior of the shape described by this . + /// public IBrush Brush { get => GetValue(BrushProperty); set => SetValue(BrushProperty, value); } - public static readonly StyledProperty PenProperty = - AvaloniaProperty.Register(nameof(Pen)); - + /// + /// Gets or sets the used to stroke this . + /// public IPen Pen { get => GetValue(PenProperty); @@ -46,7 +68,7 @@ namespace Avalonia.Media public override Rect GetBounds() { - return Geometry?.GetRenderBounds(s_boundsPen) ?? new Rect(); + return Geometry?.GetRenderBounds(s_boundsPen) ?? Rect.Empty; } } } From 31d8ada74b25d85374d439a9222a0646962df16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20So=C3=B3s?= Date: Mon, 12 Oct 2020 11:21:02 +0200 Subject: [PATCH 156/492] Update ComboBox.xaml --- src/Avalonia.Themes.Fluent/ComboBox.xaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/ComboBox.xaml b/src/Avalonia.Themes.Fluent/ComboBox.xaml index f77f1b582b..8155264f18 100644 --- a/src/Avalonia.Themes.Fluent/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/ComboBox.xaml @@ -61,7 +61,6 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" - CornerRadius="{DynamicResource ControlCornerRadius}" MinWidth="{DynamicResource ComboBoxThemeMinWidth}" /> + BorderThickness="{TemplateBinding BorderThickness}" /> + + + + From 09fd6650ac13a728210fc35b71664265c6b30fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20So=C3=B3s?= Date: Mon, 12 Oct 2020 11:32:04 +0200 Subject: [PATCH 157/492] Update Button.xaml CornerRadius via style --- src/Avalonia.Themes.Fluent/Button.xaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/Button.xaml b/src/Avalonia.Themes.Fluent/Button.xaml index 8522c933ae..573b9f337f 100644 --- a/src/Avalonia.Themes.Fluent/Button.xaml +++ b/src/Avalonia.Themes.Fluent/Button.xaml @@ -32,7 +32,6 @@ BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" - CornerRadius="{DynamicResource ControlCornerRadius}" Padding="{TemplateBinding Padding}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" /> @@ -95,4 +94,8 @@ + + From 4aa9264daabf5e81b05b05decce7a7dc5e807a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20So=C3=B3s?= Date: Mon, 12 Oct 2020 11:36:26 +0200 Subject: [PATCH 158/492] CheckBox CornerRadius via style --- src/Avalonia.Themes.Fluent/CheckBox.xaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/CheckBox.xaml b/src/Avalonia.Themes.Fluent/CheckBox.xaml index 678ae5c5a3..83d2779872 100644 --- a/src/Avalonia.Themes.Fluent/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/CheckBox.xaml @@ -22,16 +22,14 @@ Grid.ColumnSpan="2" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" - BorderThickness="{TemplateBinding BorderThickness}" - CornerRadius="{DynamicResource ControlCornerRadius}" /> + BorderThickness="{TemplateBinding BorderThickness}" /> + Width="20" /> @@ -52,6 +50,14 @@ + + + + + + + + + From 1d2cf0280e71356ff4ed1176bff694ceb7851e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20So=C3=B3s?= Date: Mon, 12 Oct 2020 11:48:35 +0200 Subject: [PATCH 162/492] TabItem CornerRadius via style --- src/Avalonia.Themes.Fluent/TabItem.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/TabItem.xaml b/src/Avalonia.Themes.Fluent/TabItem.xaml index 2b0a0c1ea0..ca8d4dfeb5 100644 --- a/src/Avalonia.Themes.Fluent/TabItem.xaml +++ b/src/Avalonia.Themes.Fluent/TabItem.xaml @@ -39,7 +39,6 @@ TextBlock.FontSize="{TemplateBinding FontSize}" TextBlock.FontWeight="{TemplateBinding FontWeight}" /> @@ -53,6 +52,7 @@ From 0329cf5d9f2177a7c23b4164a24958e02cb81982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20So=C3=B3s?= Date: Mon, 12 Oct 2020 11:55:49 +0200 Subject: [PATCH 163/492] format --- src/Avalonia.Themes.Fluent/TabItem.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/TabItem.xaml b/src/Avalonia.Themes.Fluent/TabItem.xaml index ca8d4dfeb5..1c9574f169 100644 --- a/src/Avalonia.Themes.Fluent/TabItem.xaml +++ b/src/Avalonia.Themes.Fluent/TabItem.xaml @@ -52,7 +52,7 @@ From cb6785e7007e35d1df99775fec86b9d916a545b1 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 12 Oct 2020 14:51:10 +0200 Subject: [PATCH 164/492] Properly handle multiple line breaks --- src/Avalonia.Visuals/Media/GlyphRun.cs | 4 ++-- .../Media/TextFormatting/TextFormatterImpl.cs | 8 ------- .../Media/TextFormatting/TextLayoutTests.cs | 22 ++++++++++++++++++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index 66a8c1dd0c..af228ec57b 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/src/Avalonia.Visuals/Media/GlyphRun.cs @@ -399,14 +399,14 @@ namespace Avalonia.Media if (characterIndex > GlyphClusters[GlyphClusters.Length - 1]) { - return _glyphClusters.End; + return _glyphClusters.Length - 1; } } else { if (characterIndex < GlyphClusters[GlyphClusters.Length - 1]) { - return _glyphClusters.End; + return _glyphClusters.Length - 1; } if (characterIndex > GlyphClusters[0]) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 394dae8253..6ae5258323 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -339,14 +339,6 @@ namespace Avalonia.Media.TextFormatting return true; } - //The line breaker isn't treating \n\r as a pair so we have to fix that here. - if (textRun.Text[lineBreak.PositionMeasure] == '\n' - && textRun.Text[lineBreak.PositionWrap] == '\r') - { - lineBreak = new LineBreak(lineBreak.PositionMeasure, lineBreak.PositionWrap + 1, - lineBreak.Required); - } - return true; } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 26e8ce4797..f7bc75c05d 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -417,7 +417,6 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting [Theory] [InlineData("abcde\r\n", 7)] // Carriage Return + Line Feed - [InlineData("abcde\n\r", 7)] // This isn't valid but we somehow have to support it. [InlineData("abcde\u000A", 6)] // Line Feed [InlineData("abcde\u000B", 6)] // Vertical Tab [InlineData("abcde\u000C", 6)] // Form Feed @@ -575,6 +574,27 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void Should_Process_Multiple_NewLines_Properly() + { + using (Start()) + { + var text = "123\r\n\r\n456\r\n\r\n"; + var layout = new TextLayout( + text, + Typeface.Default, + 12.0f, + Brushes.Black); + + Assert.Equal(5, layout.TextLines.Count); + + Assert.Equal("123\r\n", layout.TextLines[0].TextRuns[0].Text); + Assert.Equal("\r\n", layout.TextLines[1].TextRuns[0].Text); + Assert.Equal("456\r\n", layout.TextLines[2].TextRuns[0].Text); + Assert.Equal("\r\n", layout.TextLines[3].TextRuns[0].Text); + } + } + [Fact] public void Should_Wrap_Min_OneCharacter_EveryLine() { From e4505ce861c85e117da11d6379489ac2c0c5fff2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 12 Oct 2020 18:09:56 +0300 Subject: [PATCH 165/492] Strong name support --- build/SharedVersion.props | 2 ++ build/XUnit.props | 4 ++++ build/avalonia.snk | Bin 0 -> 596 bytes nukebuild/BuildTasksPatcher.cs | 3 ++- nukebuild/_build.csproj | 2 +- packages/Avalonia/AvaloniaBuildTasks.targets | 3 +++ src/Avalonia.Animation/Properties/AssemblyInfo.cs | 4 ++-- src/Avalonia.Base/Properties/AssemblyInfo.cs | 10 +++++----- .../Avalonia.Build.Tasks.csproj | 2 +- .../CompileAvaloniaXamlTask.cs | 8 +++++++- .../XamlCompilerTaskExecutor.cs | 12 +++++++----- .../Properties/AssemblyInfo.cs | 4 ++-- src/Avalonia.Controls/Properties/AssemblyInfo.cs | 4 ++-- .../Avalonia.FreeDesktop.csproj | 4 +--- src/Avalonia.Layout/Properties/AssemblyInfo.cs | 2 +- src/Avalonia.Styling/Properties/AssemblyInfo.cs | 2 +- src/Avalonia.Visuals/Properties/AssemblyInfo.cs | 8 ++++---- .../Properties/AssemblyInfo.cs | 2 +- .../Avalonia.Markup/Properties/AssemblyInfo.cs | 2 +- src/Skia/Avalonia.Skia/Properties/AssemblyInfo.cs | 4 ++-- .../Avalonia.Direct2D1/Properties/AssemblyInfo.cs | 4 ++-- .../Avalonia.UnitTests/Avalonia.UnitTests.csproj | 2 ++ 22 files changed, 53 insertions(+), 35 deletions(-) create mode 100644 build/avalonia.snk diff --git a/build/SharedVersion.props b/build/SharedVersion.props index d3cebef418..a909855ace 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -15,5 +15,7 @@ avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin https://github.com/AvaloniaUI/Avalonia/releases git + $(MSBuildThisFileDirectory)\avalonia.snk + True diff --git a/build/XUnit.props b/build/XUnit.props index 079565d184..53f524b8e9 100644 --- a/build/XUnit.props +++ b/build/XUnit.props @@ -11,4 +11,8 @@ + + $(MSBuildThisFileDirectory)\avalonia.snk + True + diff --git a/build/avalonia.snk b/build/avalonia.snk new file mode 100644 index 0000000000000000000000000000000000000000..10b49deb317f66acbf94ac91c3fb4e1066b11943 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50098OyP*^!h5iH?GHr0jVfXw1mKGrZq92J|(XpFp)DiBw-Z!BWY`Bn=-i(~@q( zFS=ks<7P~GaI50~9@dlKIFzd>EpCVBc8q@)iz81oG<|TKKgSo7;Jb+ILAUS8ye7lf z@B;^IqY`!iW+5Nz%~CJWE2-xq6i%7T`X=nQVOF!Yfg@pa9Q}co7VO77Md5xwZ8ias zJNxIB-(r{0doj`xP_m5qd-Nw0d@%}{UcaWW63g#RH(D=wP~kJH)|NIB$f38ZKz)1Y z-x(8Cxm8lS#Qm>~w8`L|kXGpBaM=C75+NN3UlQZFP4;nrXlonN iwWP+bhD&zRYgLZM_PoX>$ViOoD||G}Mwg&TAnsL#*B`L} literal 0 HcmV?d00001 diff --git a/nukebuild/BuildTasksPatcher.cs b/nukebuild/BuildTasksPatcher.cs index 44f01da669..0b650ca7cc 100644 --- a/nukebuild/BuildTasksPatcher.cs +++ b/nukebuild/BuildTasksPatcher.cs @@ -29,7 +29,8 @@ public class BuildTasksPatcher InputAssemblies = new[] { temp, typeof(Mono.Cecil.AssemblyDefinition).Assembly.GetModules()[0] - .FullyQualifiedName + .FullyQualifiedName, + typeof(Mono.Cecil.Rocks.MethodBodyRocks).Assembly.GetModules()[0].FullyQualifiedName }, SearchDirectories = new string[0], OutputFile = output diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index b06e49f2eb..77cfb83427 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -16,7 +16,7 @@ - + diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 84a62bb5c0..612c368633 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -87,6 +87,9 @@ ProjectDirectory="$(MSBuildProjectDirectory)" VerifyIl="$(AvaloniaXamlIlVerifyIl)" ReportImportance="$(AvaloniaXamlReportImportance)" + AssemblyOriginatorKeyFile="$(AssemblyOriginatorKeyFile)" + SignAssembly="$(SignAssembly)" + DelaySign="$(DelaySign)" /> - + diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs index 95e59dde2b..8e1f6c257d 100644 --- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs +++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs @@ -39,7 +39,9 @@ namespace Avalonia.Build.Tasks var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input, File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(), - ProjectDirectory, OutputPath, VerifyIl, outputImportance); + ProjectDirectory, OutputPath, VerifyIl, outputImportance, + (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null + ); if (!res.Success) return false; if (!res.WrittenFile) @@ -73,6 +75,10 @@ namespace Avalonia.Build.Tasks public string OutputPath { get; set; } public bool VerifyIl { get; set; } + + public string AssemblyOriginatorKeyFile { get; set; } + public bool SignAssembly { get; set; } + public bool DelaySign { get; set; } public string ReportImportance { get; set; } diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index c9c9c562bd..7c4a9df0f4 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -42,7 +42,7 @@ namespace Avalonia.Build.Tasks } public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory, - string output, bool verifyIl, MessageImportance logImportance) + string output, bool verifyIl, MessageImportance logImportance, string strongNameKey) { var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input); var asm = typeSystem.TargetAssemblyDefinition; @@ -361,10 +361,12 @@ namespace Avalonia.Build.Tasks loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull)); loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - asm.Write(output, new WriterParameters - { - WriteSymbols = asm.MainModule.HasSymbols - }); + + var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; + if (!string.IsNullOrWhiteSpace(strongNameKey)) + writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); + + asm.Write(output, writerParameters); return new CompileResult(true, true); } diff --git a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs index 82b01e99bb..72dd949421 100644 --- a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs @@ -2,8 +2,8 @@ using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Metadata; -[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests")] -[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] +[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.DesignerSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Collections")] diff --git a/src/Avalonia.Controls/Properties/AssemblyInfo.cs b/src/Avalonia.Controls/Properties/AssemblyInfo.cs index 672fbe294e..5e18115ac8 100644 --- a/src/Avalonia.Controls/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls/Properties/AssemblyInfo.cs @@ -2,8 +2,8 @@ using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Metadata; -[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")] -[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] +[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.DesignerSupport, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index 260d7350d2..81af76dc20 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -6,9 +6,7 @@ + - - - diff --git a/src/Avalonia.Layout/Properties/AssemblyInfo.cs b/src/Avalonia.Layout/Properties/AssemblyInfo.cs index 0a6a32493a..efcbf184b5 100644 --- a/src/Avalonia.Layout/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Layout/Properties/AssemblyInfo.cs @@ -1,7 +1,7 @@ using System.Runtime.CompilerServices; using Avalonia.Metadata; -[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")] diff --git a/src/Avalonia.Styling/Properties/AssemblyInfo.cs b/src/Avalonia.Styling/Properties/AssemblyInfo.cs index b231f3b406..f6b69d8150 100644 --- a/src/Avalonia.Styling/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Styling/Properties/AssemblyInfo.cs @@ -5,4 +5,4 @@ using Avalonia.Metadata; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.LogicalTree")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Styling")] -[assembly: InternalsVisibleTo("Avalonia.Styling.UnitTests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Avalonia.Styling.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] diff --git a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs index 5d802c27b9..5bb9ae78b0 100644 --- a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs @@ -2,13 +2,13 @@ using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Metadata; -[assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media.Imaging")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media.Transformation")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] -[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")] -[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")] -[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] diff --git a/src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs b/src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs index 7e4a095bdf..cee2b8a484 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs @@ -5,5 +5,5 @@ using System.Runtime.CompilerServices; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.MarkupExtensions")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Styling")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Templates")] -[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] diff --git a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs index 72c1659a1e..3da708bc9c 100644 --- a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs +++ b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs @@ -4,4 +4,4 @@ using System.Runtime.CompilerServices; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Data")] -[assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] diff --git a/src/Skia/Avalonia.Skia/Properties/AssemblyInfo.cs b/src/Skia/Avalonia.Skia/Properties/AssemblyInfo.cs index f6aabfae39..b923e3a9f6 100644 --- a/src/Skia/Avalonia.Skia/Properties/AssemblyInfo.cs +++ b/src/Skia/Avalonia.Skia/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")] -[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] diff --git a/src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs b/src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs index 5d53fd4aae..78e7b4aad6 100644 --- a/src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs +++ b/src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs @@ -6,6 +6,6 @@ using Avalonia.Direct2D1; [assembly: ExportRenderingSubsystem(OperatingSystemType.WinNT, 1, "Direct2D1", typeof(Direct2D1Platform), nameof(Direct2D1Platform.Initialize), typeof(Direct2DChecker))] -[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")] -[assembly: InternalsVisibleTo("Avalonia.Direct2D1.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.Direct2D1.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index b1d89037da..11394c6e5c 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -6,6 +6,8 @@ Library false latest + ..\..\build\avalonia.snk + True From 94b6f133ea30f1fb6661403d5f46daa4ea45265d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 12 Oct 2020 17:30:36 +0200 Subject: [PATCH 166/492] Added failing test for #4822. --- .../Shapes/PathTests.cs | 22 ++++++++++++++++++- .../MockPlatformRenderInterface.cs | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs index 88c64e76cc..2a0bcc8d39 100644 --- a/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs @@ -1,4 +1,5 @@ -using Avalonia.Controls.Shapes; +using System.Runtime.InteropServices; +using Avalonia.Controls.Shapes; using Avalonia.Media; using Avalonia.UnitTests; using Xunit; @@ -34,5 +35,24 @@ namespace Avalonia.Controls.UnitTests.Shapes root.Child = null; } + + [Fact] + public void Arrange_Without_Measure_Updates_RenderedGeometry_Transform() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Path + { + Data = new RectangleGeometry { Rect = new Rect(0, 0, 100, 100) }, + Stretch = Stretch.Fill, + }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(0, 0, 200, 200)); + Assert.Equal(Matrix.CreateScale(2, 2), target.RenderedGeometry.Transform.Value); + + target.Arrange(new Rect(0, 0, 300, 300)); + Assert.Equal(Matrix.CreateScale(3, 3), target.RenderedGeometry.Transform.Value); + } } } diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 08df23cbe1..e73a76357a 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -34,7 +34,7 @@ namespace Avalonia.UnitTests public IGeometryImpl CreateRectangleGeometry(Rect rect) { - return Mock.Of(); + return Mock.Of(x => x.Bounds == rect); } public IRenderTarget CreateRenderTarget(IEnumerable surfaces) From 0e70ce4999b3a7c452026d7e095c9b62f5262766 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 12 Oct 2020 18:27:26 +0100 Subject: [PATCH 167/492] rename CompositionHost to CompositionConnector. --- .../{CompositionHost.cs => CompositionConnector.cs} | 6 +++--- .../Composition/CompositionEglGlPlatformSurface.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/Windows/Avalonia.Win32/Composition/{CompositionHost.cs => CompositionConnector.cs} (97%) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs b/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs similarity index 97% rename from src/Windows/Avalonia.Win32/Composition/CompositionHost.cs rename to src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs index c88d239670..1519520d89 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionHost.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs @@ -9,7 +9,7 @@ using WinRT; namespace Avalonia.Win32 { - class CompositionHost + class CompositionConnector { internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE { @@ -39,13 +39,13 @@ namespace Avalonia.Win32 [DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)] internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options, out IntPtr dispatcherQueueController); - public static CompositionHost Instance { get; } = new CompositionHost(); + public static CompositionConnector Instance { get; } = new CompositionConnector(); private Compositor _compositor; private Windows.System.DispatcherQueueController _dispatcherQueueController; private CompositionGraphicsDevice _graphicsDevice; - private CompositionHost() + private CompositionConnector() { var glPlatform = AvaloniaLocator.Current.GetService(); diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs index 547e2d3c0e..6b6e272580 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -25,7 +25,7 @@ namespace Avalonia.Win32 { using (_egl.PrimaryContext.MakeCurrent()) { - _surfaceInterop = CompositionHost.Instance.InitialiseWindowCompositionTree(hwnd, out _surface, out var blurHost); + _surfaceInterop = CompositionConnector.Instance.InitialiseWindowCompositionTree(hwnd, out _surface, out var blurHost); return blurHost; } } From ae876bd058675e644924eb24d20837f4191208ad Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 12 Oct 2020 18:48:12 +0100 Subject: [PATCH 168/492] simplify composition connection. --- .../Composition/CompositionConnector.cs | 15 ++------------- .../CompositionEglGlPlatformSurface.cs | 4 ++-- src/Windows/Avalonia.Win32/Win32GlManager.cs | 18 ++++++++++++++---- src/Windows/Avalonia.Win32/Win32Platform.cs | 3 ++- src/Windows/Avalonia.Win32/WindowImpl.cs | 9 +++++---- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs b/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs index 1519520d89..447f8a8c90 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs @@ -39,24 +39,13 @@ namespace Avalonia.Win32 [DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)] internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options, out IntPtr dispatcherQueueController); - public static CompositionConnector Instance { get; } = new CompositionConnector(); - private Compositor _compositor; private Windows.System.DispatcherQueueController _dispatcherQueueController; private CompositionGraphicsDevice _graphicsDevice; - private CompositionConnector() + internal CompositionConnector(EglPlatformOpenGlInterface egl) { - var glPlatform = AvaloniaLocator.Current.GetService(); - - if (glPlatform is EglPlatformOpenGlInterface egl) - { - Initialize(egl); - } - else - { - throw new PlatformNotSupportedException(); - } + Initialize(egl); } private void Initialize(EglPlatformOpenGlInterface egl) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs index 6b6e272580..441da93787 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionEglGlPlatformSurface.cs @@ -21,11 +21,11 @@ namespace Avalonia.Win32 _info = info; } - public IBlurHost AttachToCompositionTree(IntPtr hwnd) + public IBlurHost AttachToCompositionTree(CompositionConnector connector, IntPtr hwnd) { using (_egl.PrimaryContext.MakeCurrent()) { - _surfaceInterop = CompositionConnector.Instance.InitialiseWindowCompositionTree(hwnd, out _surface, out var blurHost); + _surfaceInterop = connector.InitialiseWindowCompositionTree(hwnd, out _surface, out var blurHost); return blurHost; } } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index 523a059e0e..6a6ce2ec3c 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -7,8 +7,6 @@ namespace Avalonia.Win32 { static class Win32GlManager { - private static bool s_attemptedToInitialize; - public static void Initialize() { AvaloniaLocator.CurrentMutable.Bind().ToLazy(() => @@ -19,9 +17,21 @@ namespace Avalonia.Win32 var wgl = WglPlatformOpenGlInterface.TryCreate(); return wgl; } - + if (opts?.AllowEglInitialization == true) - return EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); + { + var egl = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); + + if (egl is { } && + opts?.UseWindowsUIComposition == true && + Win32Platform.WindowsVersion.Major >= 10 && + Win32Platform.WindowsVersion.Build >= 16299) + { + AvaloniaLocator.CurrentMutable.BindToSelf(new CompositionConnector(egl)); + } + + return egl; + } return null; }); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 493ae45656..5b16cae26e 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -12,6 +12,7 @@ using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; @@ -113,7 +114,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()); Win32GlManager.Initialize(); - + _uiThread = Thread.CurrentThread; if (OleContext.Current != null) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1fd8752a8f..715c8fc01d 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -107,9 +107,10 @@ namespace Avalonia.Win32 var glPlatform = AvaloniaLocator.Current.GetService(); - _isUsingComposition = Win32Platform.Options.UseWindowsUIComposition && - Win32Platform.WindowsVersion.Major >= 10 && Win32Platform.WindowsVersion.Build >= 16299 && - glPlatform != null && glPlatform is EglPlatformOpenGlInterface egl && + var compositionConnector = AvaloniaLocator.Current.GetService(); + + _isUsingComposition = compositionConnector is { } && + glPlatform is EglPlatformOpenGlInterface egl && egl.Display is AngleWin32EglDisplay angleDisplay && angleDisplay.PlatformApi == AngleOptions.PlatformApi.DirectX11; @@ -121,7 +122,7 @@ namespace Avalonia.Win32 if (_isUsingComposition) { var cgl = new CompositionEglGlPlatformSurface(glPlatform as EglPlatformOpenGlInterface, this); - _blurHost = cgl.AttachToCompositionTree(_hwnd); + _blurHost = cgl.AttachToCompositionTree(compositionConnector, _hwnd); _gl = cgl; From 614c0c6726eaaf7fbc0a825618cf518cd4320f3e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 12 Oct 2020 18:51:00 +0100 Subject: [PATCH 169/492] tidy composition connector. --- .../Composition/CompositionConnector.cs | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs b/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs index 447f8a8c90..3929dc07bf 100644 --- a/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs +++ b/src/Windows/Avalonia.Win32/Composition/CompositionConnector.cs @@ -9,8 +9,12 @@ using WinRT; namespace Avalonia.Win32 { - class CompositionConnector + internal class CompositionConnector { + private Compositor _compositor; + private Windows.System.DispatcherQueueController _dispatcherQueueController; + private CompositionGraphicsDevice _graphicsDevice; + internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE { DQTAT_COM_NONE = 0, @@ -39,23 +43,15 @@ namespace Avalonia.Win32 [DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)] internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options, out IntPtr dispatcherQueueController); - private Compositor _compositor; - private Windows.System.DispatcherQueueController _dispatcherQueueController; - private CompositionGraphicsDevice _graphicsDevice; - - internal CompositionConnector(EglPlatformOpenGlInterface egl) - { - Initialize(egl); - } - - private void Initialize(EglPlatformOpenGlInterface egl) + public CompositionConnector(EglPlatformOpenGlInterface egl) { EnsureDispatcherQueue(); + if (_dispatcherQueueController != null) _compositor = new Compositor(); var interop = _compositor.As(); - + var display = egl.Display as AngleWin32EglDisplay; _graphicsDevice = interop.CreateGraphicsDevice(display.GetDirect3DDevice()); @@ -78,7 +74,7 @@ namespace Avalonia.Win32 visual.RelativeSizeAdjustment = new System.Numerics.Vector2(1, 1); var container = _compositor.CreateContainerVisual(); - + target.Root = container; var blur = CreateBlur(); @@ -96,9 +92,9 @@ namespace Avalonia.Win32 return surfaceInterop; } - public SpriteVisual CreateBlur() + private SpriteVisual CreateBlur() { - var blurEffect = new GaussianBlurEffect(new CompositionEffectSourceParameter ("backdrop")); + var blurEffect = new GaussianBlurEffect(new CompositionEffectSourceParameter("backdrop")); var blurEffectFactory = _compositor.CreateEffectFactory(blurEffect); var blurBrush = blurEffectFactory.CreateBrush(); @@ -121,7 +117,7 @@ namespace Avalonia.Win32 return visual; } - CompositionTarget CreateDesktopWindowTarget(IntPtr window) + private CompositionTarget CreateDesktopWindowTarget(IntPtr window) { var interop = _compositor.As(); @@ -129,7 +125,7 @@ namespace Avalonia.Win32 return Windows.UI.Composition.Desktop.DesktopWindowTarget.FromAbi(windowTarget); } - void EnsureDispatcherQueue() + private void EnsureDispatcherQueue() { if (_dispatcherQueueController == null) { From d631474ddf75f6ec5a41488aac4339fe4eb34a10 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 12 Oct 2020 15:10:06 +0300 Subject: [PATCH 170/492] add failing test for autoscroll to selected item in listbox #4855 --- .../ListBoxTests.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 2e2ccf7326..145fce4fed 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -407,6 +407,53 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, raised); } + [Fact] + public void Adding_And_Selecting_Item_With_AutoScrollToSelectedItem_Should_NotHide_FirstItem() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = new AvaloniaList(); + + var wnd = new Window() { Width = 100, Height = 100, IsVisible = true }; + + var target = new ListBox() + { + VerticalAlignment = Layout.VerticalAlignment.Top, + AutoScrollToSelectedItem = true, + Width = 50, + VirtualizationMode = ItemVirtualizationMode.Simple, + ItemTemplate = new FuncDataTemplate((c, _) => new Border() { Height = 10 }), + Items = items, + }; + wnd.Content = target; + + var lm = wnd.LayoutManager; + + lm.ExecuteInitialLayoutPass(); + + var panel = target.Presenter.Panel; + + items.Add("Item 1"); + target.Selection.Select(0); + lm.ExecuteLayoutPass(); + + Assert.Equal(1, panel.Children.Count); + + items.Add("Item 2"); + target.Selection.Select(1); + lm.ExecuteLayoutPass(); + + Assert.Equal(2, panel.Children.Count); + + //make sure we have enough space to show all items + Assert.True(panel.Bounds.Height >= panel.Children.Sum(c => c.Bounds.Height)); + + //make sure we show items and they completelly visible, not only partially + Assert.True(panel.Children[0].Bounds.Top >= 0 && panel.Children[0].Bounds.Bottom <= panel.Bounds.Height, "first item is not completelly visible!"); + Assert.True(panel.Children[1].Bounds.Top >= 0 && panel.Children[1].Bounds.Bottom <= panel.Bounds.Height, "second item is not completelly visible!"); + } + } + private FuncControlTemplate ListBoxTemplate() { return new FuncControlTemplate((parent, scope) => From b894a812d356d13da07c57ec597fd0b58ae8812c Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 12 Oct 2020 23:59:07 +0300 Subject: [PATCH 171/492] fix #4855 before try to scroll invalidate panel --- src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 3fac440c40..bdc68bee7e 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -512,6 +512,14 @@ namespace Avalonia.Controls.Presenters var generator = Owner.ItemContainerGenerator; var newOffset = -1.0; + if (!panel.IsMeasureValid && panel.PreviousMeasure.HasValue) + { + //before any kind of scrolling we need to make sure panel measure is valid + //or we risk get panel into not valid state + //we make a preemptive quick measure so scrolling is valid + panel.Measure(panel.PreviousMeasure.Value); + } + if (index >= 0 && index < ItemCount) { if (index <= FirstIndex) From f17c33c149da02afa92a810f134cd0040f244a5c Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 12 Oct 2020 23:11:26 +0200 Subject: [PATCH 172/492] Fix hittest returning top-left and top-right for non-resize corner areas. --- .../WindowImpl.CustomCaptionProc.cs | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index a3b7574369..a31357bce5 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using Avalonia.Controls; using Avalonia.Input; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -11,70 +10,75 @@ namespace Avalonia.Win32 public partial class WindowImpl { // Hit test the frame for resizing and moving. - HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) + private HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) { - // Get the point coordinates for the hit test. + // Get the point coordinates for the hit test (screen space). var ptMouse = PointFromLParam(lParam); - // Get the window rectangle. + // Get the window rectangle. GetWindowRect(hWnd, out var rcWindow); // Get the frame rectangle, adjusted for the style without a caption. - RECT rcFrame = new RECT(); + var rcFrame = new RECT(); AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0); - RECT border_thickness = new RECT(); + var borderThickness = new RECT(); if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) { - AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); - border_thickness.left *= -1; - border_thickness.top *= -1; + AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle()), false, 0); + borderThickness.left *= -1; + borderThickness.top *= -1; } else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) { - border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; + borderThickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; } if (_extendTitleBarHint >= 0) { - border_thickness.top = (int)(_extendedMargins.Top * RenderScaling); + borderThickness.top = (int)(_extendedMargins.Top * RenderScaling); } // Determine if the hit test is for resizing. Default middle (1,1). ushort uRow = 1; ushort uCol = 1; - bool fOnResizeBorder = false; + bool onResizeBorder = false; // Determine if the point is at the top or bottom of the window. - if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + border_thickness.top) + if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + borderThickness.top) { - fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); - uRow = 0; + onResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); + + if (onResizeBorder) + { + uRow = 0; + } } - else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - border_thickness.bottom) + else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - borderThickness.bottom) { uRow = 2; } // Determine if the point is at the left or right of the window. - if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + border_thickness.left) + if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + borderThickness.left) { uCol = 0; // left side } - else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - border_thickness.right) + else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - borderThickness.right) { uCol = 2; // right side } - // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) - HitTestValues[][] hitTests = new[] + ReadOnlySpan hitZones = stackalloc HitTestValues[] { - new []{ HitTestValues.HTTOPLEFT, fOnResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT }, - new []{ HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT }, - new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT }, + HitTestValues.HTTOPLEFT, onResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, + HitTestValues.HTTOPRIGHT, HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT, + HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT }; - return hitTests[uRow][uCol]; + var zoneIndex = uRow * 3 + uCol; + + return hitZones[zoneIndex]; } protected virtual IntPtr CustomCaptionProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool callDwp) From ba8b49d918748ae9ce91eb58bc5e25d3187cb19b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 12 Oct 2020 23:22:28 +0200 Subject: [PATCH 173/492] Fix caption hit test. --- .../WindowImpl.CustomCaptionProc.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index a31357bce5..68bd40da79 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -44,12 +44,25 @@ namespace Avalonia.Win32 ushort uCol = 1; bool onResizeBorder = false; + // Determine if the point is at the left or right of the window. + if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + borderThickness.left) + { + uCol = 0; // left side + } + else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - borderThickness.right) + { + uCol = 2; // right side + } + // Determine if the point is at the top or bottom of the window. if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + borderThickness.top) { onResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); - if (onResizeBorder) + // Two cases where we have a valid row 0 hit test: + // - window resize border (top resize border hit) + // - area below resize border that is actual titlebar (caption hit). + if (onResizeBorder || uCol == 1) { uRow = 0; } @@ -59,16 +72,6 @@ namespace Avalonia.Win32 uRow = 2; } - // Determine if the point is at the left or right of the window. - if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + borderThickness.left) - { - uCol = 0; // left side - } - else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - borderThickness.right) - { - uCol = 2; // right side - } - ReadOnlySpan hitZones = stackalloc HitTestValues[] { HitTestValues.HTTOPLEFT, onResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, From 93f215d8eaca8fa1dd9f04fb562055f338afae02 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 13 Oct 2020 16:47:50 +0200 Subject: [PATCH 174/492] More tests for #4822. Tested WPF's `Path` to confirm expected results: https://github.com/wieslawsoltes/WpfUnitTests --- .../Shapes/PathTests.cs | 108 +++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs index 2a0bcc8d39..a10a03a5ae 100644 --- a/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs @@ -1,5 +1,4 @@ -using System.Runtime.InteropServices; -using Avalonia.Controls.Shapes; +using Avalonia.Controls.Shapes; using Avalonia.Media; using Avalonia.UnitTests; using Xunit; @@ -36,6 +35,111 @@ namespace Avalonia.Controls.UnitTests.Shapes root.Child = null; } + [Theory] + [InlineData(Stretch.None, 100, 200)] + [InlineData(Stretch.Fill, 500, 500)] + [InlineData(Stretch.Uniform, 250, 500)] + [InlineData(Stretch.UniformToFill, 500, 500)] + public void Calculates_Correct_DesiredSize_For_Finite_Bounds(Stretch stretch, double expectedWidth, double expectedHeight) + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Path() + { + Data = new RectangleGeometry { Rect = new Rect(0, 0, 100, 200) }, + Stretch = stretch, + }; + + target.Measure(new Size(500, 500)); + + Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize); + } + + [Theory] + [InlineData(Stretch.None)] + [InlineData(Stretch.Fill)] + [InlineData(Stretch.Uniform)] + [InlineData(Stretch.UniformToFill)] + public void Calculates_Correct_DesiredSize_For_Infinite_Bounds(Stretch stretch) + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Path() + { + Data = new RectangleGeometry { Rect = new Rect(0, 0, 100, 200) }, + Stretch = stretch, + }; + + target.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + + Assert.Equal(new Size(100, 200), target.DesiredSize); + } + + [Fact] + public void Measure_Does_Not_Update_RenderedGeometry_Transform() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Path + { + Data = new RectangleGeometry { Rect = new Rect(0, 0, 100, 200) }, + Stretch = Stretch.Fill, + }; + + target.Measure(new Size(500, 500)); + + Assert.Null(target.RenderedGeometry.Transform); + } + + [Theory] + [InlineData(Stretch.None, 1, 1)] + [InlineData(Stretch.Fill, 5, 2.5)] + [InlineData(Stretch.Uniform, 2.5, 2.5)] + [InlineData(Stretch.UniformToFill, 5, 5)] + public void Arrange_Updates_RenderedGeometry_Transform(Stretch stretch, double expectedScaleX, double expectedScaleY) + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Path + { + Data = new RectangleGeometry { Rect = new Rect(0, 0, 100, 200) }, + Stretch = stretch, + }; + + target.Measure(new Size(500, 500)); + target.Arrange(new Rect(0, 0, 500, 500)); + + if (expectedScaleX == 1 && expectedScaleY == 1) + { + Assert.Null(target.RenderedGeometry.Transform); + } + else + { + Assert.Equal(Matrix.CreateScale(expectedScaleX, expectedScaleY), target.RenderedGeometry.Transform.Value); + } + } + + [Fact] + public void Measure_Without_Arrange_Does_Not_Clear_RenderedGeometry_Transform() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Path + { + Data = new RectangleGeometry { Rect = new Rect(0, 0, 100, 100) }, + Stretch = Stretch.Fill, + }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(0, 0, 200, 200)); + + Assert.Equal(Matrix.CreateScale(2, 2), target.RenderedGeometry.Transform.Value); + + target.Measure(new Size(300, 300)); + + Assert.Equal(Matrix.CreateScale(2, 2), target.RenderedGeometry.Transform.Value); + } + [Fact] public void Arrange_Without_Measure_Updates_RenderedGeometry_Transform() { From beada97aabaf46f44564f4bcdcfe2100c1421ddc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 13 Oct 2020 16:48:06 +0200 Subject: [PATCH 175/492] Set RenderedGeometry.Transform on arrange. Fixes #4822 but breaks a bunch of other tests. --- src/Avalonia.Controls/Shapes/Shape.cs | 39 +++------------------------ 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 7d1525afc4..67a7d48c61 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -248,52 +248,21 @@ namespace Avalonia.Controls.Shapes protected override Size MeasureOverride(Size availableSize) { - bool deferCalculateTransform; - switch (Stretch) + if (DefiningGeometry is null) { - case Stretch.Fill: - case Stretch.UniformToFill: - deferCalculateTransform = double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height); - break; - case Stretch.Uniform: - deferCalculateTransform = double.IsInfinity(availableSize.Width) && double.IsInfinity(availableSize.Height); - break; - case Stretch.None: - default: - deferCalculateTransform = false; - break; + return default; } - if (deferCalculateTransform) - { - _calculateTransformOnArrange = true; - return DefiningGeometry?.Bounds.Size ?? Size.Empty; - } - else - { - _calculateTransformOnArrange = false; - return CalculateShapeSizeAndSetTransform(availableSize); - } + return CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch).Item1; } protected override Size ArrangeOverride(Size finalSize) - { - if (_calculateTransformOnArrange) - { - _calculateTransformOnArrange = false; - CalculateShapeSizeAndSetTransform(finalSize); - } - - return finalSize; - } - - private Size CalculateShapeSizeAndSetTransform(Size availableSize) { if (DefiningGeometry != null) { // This should probably use GetRenderBounds(strokeThickness) but then the calculations // will multiply the stroke thickness as well, which isn't correct. - var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch); + var (size, transform) = CalculateSizeAndTransform(finalSize, DefiningGeometry.Bounds, Stretch); if (_transform != transform) { From 51437baf7301e9313f6727c4075cd0014b21bd10 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 13 Oct 2020 17:31:35 +0200 Subject: [PATCH 176/492] Properly scale fixed pitch fonts --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 9c2ca053e6..6ae27870e8 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -238,11 +238,13 @@ namespace Avalonia.Direct2D1 width = 0; + var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight); + if (glyphRun.GlyphAdvances.IsEmpty) { for (var i = 0; i < glyphCount; i++) { - var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]); + var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale; run.Advances[i] = advance; From 9f8f4b224f188d2cf48d9a613291b3dbb51dcc04 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 13 Oct 2020 17:42:42 +0200 Subject: [PATCH 177/492] Provide a RenderInterface for controls which use shapes. --- tests/Avalonia.Controls.UnitTests/CanvasTests.cs | 9 +++++++++ .../LayoutTransformControlTests.cs | 15 +++++++++++++++ .../RelativePanelTests.cs | 6 ++++++ tests/Avalonia.Controls.UnitTests/ViewboxTests.cs | 13 +++++++++++++ 4 files changed, 43 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/CanvasTests.cs b/tests/Avalonia.Controls.UnitTests/CanvasTests.cs index da1698330f..11a349f53e 100644 --- a/tests/Avalonia.Controls.UnitTests/CanvasTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CanvasTests.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Controls.Shapes; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests @@ -9,6 +10,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Left_Property_Should_Work() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + Rectangle rect; var target = new Canvas { @@ -34,6 +37,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Top_Property_Should_Work() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + Rectangle rect; var target = new Canvas { @@ -59,6 +64,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Property_Should_Work() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + Rectangle rect; var target = new Canvas { @@ -84,6 +91,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Bottom_Property_Should_Work() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + Rectangle rect; var target = new Canvas { diff --git a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs index 13c946b549..60139c2881 100644 --- a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs @@ -1,5 +1,6 @@ using Avalonia.Controls.Shapes; using Avalonia.Media; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests @@ -171,6 +172,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Should_Generate_RotateTransform_90_degrees() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( 100, 25, @@ -193,6 +196,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Should_Generate_RotateTransform_minus_90_degrees() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( 100, 25, @@ -215,6 +220,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Should_Generate_ScaleTransform_x2() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( 100, 50, @@ -236,6 +243,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Should_Generate_SkewTransform_45_degrees() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( 100, 100, @@ -258,6 +267,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Should_Generate_SkewTransform_minus_45_degrees() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( 100, 100, @@ -279,6 +290,8 @@ namespace Avalonia.Controls.UnitTests private static void TransformMeasureSizeTest(Size size, Transform transform, Size expectedSize) { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform( size.Width, size.Height, @@ -292,6 +305,8 @@ namespace Avalonia.Controls.UnitTests private static void TransformRootBoundsTest(Size size, Transform transform, Rect expectedBounds) { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(size.Width, size.Height, transform); Rect outBounds = lt.TransformRoot.Bounds; diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 6e171a58e7..6b2f05c923 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -1,4 +1,5 @@ using Avalonia.Controls.Shapes; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests @@ -8,6 +9,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Lays_Out_1_Child_Next_the_other() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); var rect1 = new Rectangle { Height = 20, Width = 20 }; var rect2 = new Rectangle { Height = 20, Width = 20 }; @@ -34,6 +36,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Lays_Out_1_Child_Below_the_other() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); var rect1 = new Rectangle { Height = 20, Width = 20 }; var rect2 = new Rectangle { Height = 20, Width = 20 }; @@ -60,6 +63,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void RelativePanel_Can_Center() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); var rect1 = new Rectangle { Height = 20, Width = 20 }; var rect2 = new Rectangle { Height = 20, Width = 20 }; @@ -86,6 +90,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void LeftOf_Measures_Correctly() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); var rect1 = new Rectangle { Height = 20, Width = 20 }; var rect2 = new Rectangle { Height = 20, Width = 20 }; @@ -111,6 +116,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Above_Measures_Correctly() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); var rect1 = new Rectangle { Height = 20, Width = 20 }; var rect2 = new Rectangle { Height = 20, Width = 20 }; diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs index ad0f318d2f..e005bafbf9 100644 --- a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs @@ -1,5 +1,6 @@ using Avalonia.Controls.Shapes; using Avalonia.Media; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests @@ -9,6 +10,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Viewbox_Stretch_Uniform_Child() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } }; target.Measure(new Size(200, 200)); @@ -25,6 +28,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Viewbox_Stretch_None_Child() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + var target = new Viewbox() { Stretch = Stretch.None, Child = new Rectangle() { Width = 100, Height = 50 } }; target.Measure(new Size(200, 200)); @@ -41,6 +46,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Viewbox_Stretch_Fill_Child() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + var target = new Viewbox() { Stretch = Stretch.Fill, Child = new Rectangle() { Width = 100, Height = 50 } }; target.Measure(new Size(200, 200)); @@ -57,6 +64,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Viewbox_Stretch_UniformToFill_Child() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + var target = new Viewbox() { Stretch = Stretch.UniformToFill, Child = new Rectangle() { Width = 100, Height = 50 } }; target.Measure(new Size(200, 200)); @@ -73,6 +82,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Viewbox_Stretch_Uniform_Child_With_Unrestricted_Width() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } }; target.Measure(new Size(double.PositiveInfinity, 200)); @@ -89,6 +100,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Viewbox_Stretch_Uniform_Child_With_Unrestricted_Height() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } }; target.Measure(new Size(200, double.PositiveInfinity)); From 10b6cfc0117427bda643b4ba9716df7dfa52e495 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 13 Oct 2020 17:43:24 +0200 Subject: [PATCH 178/492] Reserve all of arrange rect. --- src/Avalonia.Controls/Shapes/Shape.cs | 3 +-- .../Shapes/PathTests.cs | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 67a7d48c61..28206a398a 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -62,7 +62,6 @@ namespace Avalonia.Controls.Shapes private Matrix _transform = Matrix.Identity; private Geometry? _definingGeometry; private Geometry? _renderedGeometry; - private bool _calculateTransformOnArrange; static Shape() { @@ -270,7 +269,7 @@ namespace Avalonia.Controls.Shapes _renderedGeometry = null; } - return size; + return finalSize; } return Size.Empty; diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs index a10a03a5ae..8b8656a76b 100644 --- a/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs @@ -119,6 +119,26 @@ namespace Avalonia.Controls.UnitTests.Shapes } } + [Fact] + public void Arrange_Reserves_All_Of_Arrange_Rect() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + RectangleGeometry geometry; + var target = new Path + { + Data = geometry = new RectangleGeometry { Rect = new Rect(0, 0, 100, 200) }, + Stretch = Stretch.Uniform, + }; + + target.Measure(new Size(400, 400)); + target.Arrange(new Rect(0, 0, 400, 400)); + + Assert.Equal(new Rect(0, 0, 100, 200), geometry.Rect); + Assert.Equal(Matrix.CreateScale(2, 2), target.RenderedGeometry.Transform.Value); + Assert.Equal(new Rect(0, 0, 400, 400), target.Bounds); + } + [Fact] public void Measure_Without_Arrange_Does_Not_Clear_RenderedGeometry_Transform() { From 67215a85904eb250e979a905a3728c0fffff2a8c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 13 Oct 2020 18:07:14 +0200 Subject: [PATCH 179/492] Removed outdated tests. --- .../ShapeLayoutTests.cs | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs diff --git a/tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs deleted file mode 100644 index 2ccd6fb04c..0000000000 --- a/tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Collections.Generic; -using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.UnitTests; -using Xunit; - -namespace Avalonia.Layout.UnitTests - -{ - public class ShapeLayoutTests : TestWithServicesBase - { - - public ShapeLayoutTests() - { - AvaloniaLocator.CurrentMutable - .Bind().ToSingleton(); - } - - [Fact] - public void Shape_Transformation_Calculation_Should_Be_Deferred_To_Arrange_When_Strech_Is_Fill_And_Aviable_Size_Is_Infinite() - { - var shape = new Polygon() - { - Points = new List - { - new Point(0, 0), - new Point(10, 5), - new Point(0, 10) - }, - Stretch = Stretch.Fill - }; - - var availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); - shape.Measure(availableSize); - Geometry postMeasureGeometry = shape.RenderedGeometry; - Transform postMeasureTransform = postMeasureGeometry.Transform; - - var finalSize = new Size(100, 50); - var finalRect = new Rect(finalSize); - shape.Arrange(finalRect); - - Geometry postArrangeGeometry = shape.RenderedGeometry; - Transform postArrangeTransform = postArrangeGeometry.Transform; - - Assert.NotEqual(postMeasureGeometry, postArrangeGeometry); - Assert.NotEqual(postMeasureTransform, postArrangeTransform); - Assert.Equal(finalSize, shape.Bounds.Size); - } - - [Fact] - public void Shape_Transformation_Calculation_Should_Not_Be_Deferred_To_Arrange_When_Strech_Is_Fill_And_Aviable_Size_Is_Finite() - { - var shape = new Polygon() - { - Points = new List - { - new Point(0, 0), - new Point(10, 5), - new Point(0, 10) - }, - Stretch = Stretch.Fill - }; - - var availableSize = new Size(100, 50); - shape.Measure(availableSize); - Geometry postMeasureGeometry = shape.RenderedGeometry; - Transform postMeasureTransform = postMeasureGeometry.Transform; - - var finalRect = new Rect(availableSize); - shape.Arrange(finalRect); - - Geometry postArrangeGeometry = shape.RenderedGeometry; - Transform postArrangeTransform = postArrangeGeometry.Transform; - - Assert.Equal(postMeasureGeometry, postArrangeGeometry); - Assert.Equal(postMeasureTransform, postArrangeTransform); - Assert.Equal(availableSize, shape.Bounds.Size); - } - - [Fact] - public void Shape_Transformation_Calculation_Should_Not_Be_Deferred_To_Arrange_When_Strech_Is_None() - { - var shape = new Polygon() - { - Points = new List - { - new Point(0, 0), - new Point(10, 5), - new Point(0, 10) - }, - Stretch = Stretch.None - }; - - var availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); - shape.Measure(availableSize); - Geometry postMeasureGeometry = shape.RenderedGeometry; - Transform postMeasureTransform = postMeasureGeometry.Transform; - - var finalSize = new Size(100, 50); - var finalRect = new Rect(finalSize); - shape.Arrange(finalRect); - - Geometry postArrangeGeometry = shape.RenderedGeometry; - Transform postArrangeTransform = postArrangeGeometry.Transform; - - Assert.Equal(postMeasureGeometry, postArrangeGeometry); - Assert.Equal(postMeasureTransform, postArrangeTransform); - Assert.Equal(finalSize, shape.Bounds.Size); - } - } -} From 41050b67422be08b8b15700f3e99fd5879acb58a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 13 Oct 2020 18:26:34 +0200 Subject: [PATCH 180/492] Added some tests for Rectangle and Ellipse. Because these classes override the base `Shape` layout. --- .../Shapes/EllipseTests.cs | 57 +++++++++++++++++++ .../Shapes/RectangleTests.cs | 56 ++++++++++++++++-- 2 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/Shapes/EllipseTests.cs diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/EllipseTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/EllipseTests.cs new file mode 100644 index 0000000000..626945894f --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Shapes/EllipseTests.cs @@ -0,0 +1,57 @@ +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Shapes +{ + public class EllipseTests + { + [Fact] + public void Measure_Does_Not_Set_RenderedGeometry_Rect() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Ellipse(); + + target.Measure(new Size(100, 100)); + + var geometry = Assert.IsType(target.RenderedGeometry); + Assert.Equal(default, geometry.Rect); + } + + [Fact] + public void Arrange_Sets_RenderedGeometry_Properties() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Ellipse(); + + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var geometry = Assert.IsType(target.RenderedGeometry); + Assert.Equal(new Rect(0, 0, 100, 100), geometry.Rect); + } + + [Fact] + public void Rearranging_Updates_RenderedGeometry_Rect() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Ellipse(); + + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var geometry = Assert.IsType(target.RenderedGeometry); + Assert.Equal(new Rect(0, 0, 100, 100), geometry.Rect); + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(0, 0, 200, 200)); + + geometry = Assert.IsType(target.RenderedGeometry); + Assert.Equal(new Rect(0, 0, 200, 200), geometry.Rect); + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs index 0ec73edec0..8d8ce10d4c 100644 --- a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Avalonia.Controls.Shapes; +using Avalonia.Controls.Shapes; using Avalonia.Media; using Avalonia.UnitTests; using Moq; @@ -11,6 +8,53 @@ namespace Avalonia.Controls.UnitTests.Shapes { public class RectangleTests { + [Fact] + public void Measure_Does_Not_Set_RenderedGeometry_Rect() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Rectangle(); + + target.Measure(new Size(100, 100)); + + var geometry = Assert.IsType(target.RenderedGeometry); + Assert.Equal(Rect.Empty, geometry.Rect); + } + + [Fact] + public void Arrange_Sets_RenderedGeometry_Rect() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Rectangle(); + + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var geometry = Assert.IsType(target.RenderedGeometry); + Assert.Equal(new Rect(0, 0, 100, 100), geometry.Rect); + } + + [Fact] + public void Rearranging_Updates_RenderedGeometry_Rect() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var target = new Rectangle(); + + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var geometry = Assert.IsType(target.RenderedGeometry); + Assert.Equal(new Rect(0, 0, 100, 100), geometry.Rect); + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(0, 0, 200, 200)); + + geometry = Assert.IsType(target.RenderedGeometry); + Assert.Equal(new Rect(0, 0, 200, 200), geometry.Rect); + } + [Fact] public void Changing_Fill_Brush_Color_Should_Invalidate_Visual() { @@ -21,7 +65,7 @@ namespace Avalonia.Controls.UnitTests.Shapes var root = new TestRoot(target); var renderer = Mock.Get(root.Renderer); - renderer.ResetCalls(); + renderer.Invocations.Clear(); ((SolidColorBrush)target.Fill).Color = Colors.Green; @@ -38,7 +82,7 @@ namespace Avalonia.Controls.UnitTests.Shapes var root = new TestRoot(target); var renderer = Mock.Get(root.Renderer); - renderer.ResetCalls(); + renderer.Invocations.Clear(); ((SolidColorBrush)target.Stroke).Color = Colors.Green; From 374cdd1549562e4e2e47c5950c6cb9589d567e41 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 14 Oct 2020 07:15:00 +0200 Subject: [PATCH 181/492] Use Skia's font metrics --- src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 44 ++++++++------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index 5571bd890d..c649f3d8bf 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.InteropServices; -using Avalonia.Media; using Avalonia.Platform; using HarfBuzzSharp; using SkiaSharp; @@ -24,40 +23,31 @@ namespace Avalonia.Skia Font.SetFunctionsOpenType(); - Font.GetScale(out var xScale, out _); + DesignEmHeight = (short)Typeface.UnitsPerEm; - DesignEmHeight = (short)xScale; + var metrics = Typeface.ToFont().Metrics; - if (!Font.TryGetHorizontalFontExtents(out var fontExtents)) - { - Font.TryGetVerticalFontExtents(out fontExtents); - } + Ascent = (int)(metrics.Ascent / 12.0 * Typeface.UnitsPerEm); - Ascent = -fontExtents.Ascender; + Descent = (int)(metrics.Descent / 12.0 * Typeface.UnitsPerEm); - Descent = -fontExtents.Descender; + LineGap = (int)(metrics.Leading / 12.0 * Typeface.UnitsPerEm); - LineGap = fontExtents.LineGap; + UnderlinePosition = metrics.UnderlinePosition != null ? + (int)(metrics.UnderlinePosition / 12.0 * Typeface.UnitsPerEm) : + 0; - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition)) - { - UnderlinePosition = underlinePosition; - } + UnderlineThickness = metrics.UnderlineThickness != null ? + (int)(metrics.UnderlineThickness / 12.0 * Typeface.UnitsPerEm) : + 0; - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness)) - { - UnderlineThickness = underlineThickness; - } + StrikethroughPosition = metrics.StrikeoutPosition != null ? + (int)(metrics.StrikeoutPosition / 12.0 * Typeface.UnitsPerEm) : + 0; - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition)) - { - StrikethroughPosition = strikethroughPosition; - } - - if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness)) - { - StrikethroughThickness = strikethroughThickness; - } + StrikethroughThickness = metrics.StrikeoutThickness != null ? + (int)(metrics.StrikeoutThickness / 12.0 * Typeface.UnitsPerEm) : + 0; IsFixedPitch = Typeface.IsFixedPitch; } From 7cb133a33a41a8073a840fe538bfcceafb676604 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 14 Oct 2020 11:50:42 +0200 Subject: [PATCH 182/492] Introduce defaultFontRenderingEmSize --- src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index c649f3d8bf..ceccc481f9 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -27,26 +27,28 @@ namespace Avalonia.Skia var metrics = Typeface.ToFont().Metrics; - Ascent = (int)(metrics.Ascent / 12.0 * Typeface.UnitsPerEm); + const double defaultFontRenderingEmSize = 12.0; - Descent = (int)(metrics.Descent / 12.0 * Typeface.UnitsPerEm); + Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm); - LineGap = (int)(metrics.Leading / 12.0 * Typeface.UnitsPerEm); + Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm); + + LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm); UnderlinePosition = metrics.UnderlinePosition != null ? - (int)(metrics.UnderlinePosition / 12.0 * Typeface.UnitsPerEm) : + (int)(metrics.UnderlinePosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : 0; UnderlineThickness = metrics.UnderlineThickness != null ? - (int)(metrics.UnderlineThickness / 12.0 * Typeface.UnitsPerEm) : + (int)(metrics.UnderlineThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : 0; StrikethroughPosition = metrics.StrikeoutPosition != null ? - (int)(metrics.StrikeoutPosition / 12.0 * Typeface.UnitsPerEm) : + (int)(metrics.StrikeoutPosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : 0; StrikethroughThickness = metrics.StrikeoutThickness != null ? - (int)(metrics.StrikeoutThickness / 12.0 * Typeface.UnitsPerEm) : + (int)(metrics.StrikeoutThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : 0; IsFixedPitch = Typeface.IsFixedPitch; From c1bcdb95860739f4b5dd428d935ca7e57fbde8e3 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Wed, 14 Oct 2020 22:19:17 +0200 Subject: [PATCH 183/492] Allow for setting wm shadow hint on a context menu. --- src/Avalonia.Controls/ContextMenu.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index c4df5c1815..ec9649bc64 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -62,6 +62,12 @@ namespace Avalonia.Controls public static readonly StyledProperty PlacementRectProperty = AvaloniaProperty.Register(nameof(PlacementRect)); + /// + /// Defines the property. + /// + public static readonly StyledProperty WindowManagerAddShadowHintProperty = + Popup.WindowManagerAddShadowHintProperty.AddOwner(); + /// /// Defines the property. /// @@ -158,6 +164,12 @@ namespace Avalonia.Controls set { SetValue(PlacementModeProperty, value); } } + public bool WindowManagerAddShadowHint + { + get { return GetValue(WindowManagerAddShadowHintProperty); } + set { SetValue(WindowManagerAddShadowHintProperty, value); } + } + /// /// Gets or sets the the anchor rectangle within the parent that the context menu will be placed /// relative to when is . @@ -267,6 +279,7 @@ namespace Avalonia.Controls PlacementTarget = PlacementTarget ?? control, IsLightDismissEnabled = true, OverlayDismissEventPassThrough = true, + WindowManagerAddShadowHint = WindowManagerAddShadowHint, }; _popup.Opened += PopupOpened; From 8f1586c8249b0f019a958e436badf37515ac0edb Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 15 Oct 2020 10:36:54 +0200 Subject: [PATCH 184/492] Revert change to win32 paint handler that was causing infinite repaint cycle. --- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 01576500e7..370e2a2a4b 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -343,16 +343,21 @@ namespace Avalonia.Win32 case WindowsMessage.WM_PAINT: { - using (_rendererLock.Lock()) + using (_rendererLock.Lock()) + { + if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) { - Paint?.Invoke(default); + var f = RenderScaling; + var r = ps.rcPaint; + Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, + (r.bottom - r.top) / f)); + EndPaint(_hwnd, ref ps); } - - ValidateRect(hWnd, IntPtr.Zero); - - return IntPtr.Zero; } + return IntPtr.Zero; + } + case WindowsMessage.WM_SIZE: { using (_rendererLock.Lock()) From d96c157231c24cc3a167cfa6be10cc13a92b80be Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Oct 2020 11:40:53 +0200 Subject: [PATCH 185/492] Name tuple values. --- src/Avalonia.Controls/Shapes/Shape.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 28206a398a..cd16ac402d 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -252,7 +252,7 @@ namespace Avalonia.Controls.Shapes return default; } - return CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch).Item1; + return CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch).size; } protected override Size ArrangeOverride(Size finalSize) @@ -275,7 +275,7 @@ namespace Avalonia.Controls.Shapes return Size.Empty; } - internal static (Size, Matrix) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch) + internal static (Size size, Matrix transform) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch) { Size shapeSize = new Size(shapeBounds.Right, shapeBounds.Bottom); Matrix translate = Matrix.Identity; From cf6032d8e4d726c97e78f678f6c7630dc52d6235 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Oct 2020 11:41:36 +0200 Subject: [PATCH 186/492] Discard unused value. --- src/Avalonia.Controls/Shapes/Shape.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index cd16ac402d..0b7595ec9a 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -261,7 +261,7 @@ namespace Avalonia.Controls.Shapes { // This should probably use GetRenderBounds(strokeThickness) but then the calculations // will multiply the stroke thickness as well, which isn't correct. - var (size, transform) = CalculateSizeAndTransform(finalSize, DefiningGeometry.Bounds, Stretch); + var (_, transform) = CalculateSizeAndTransform(finalSize, DefiningGeometry.Bounds, Stretch); if (_transform != transform) { From cdb4e9051854d2d7eff66993ae2733de28cec315 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Thu, 15 Oct 2020 19:37:28 +0300 Subject: [PATCH 187/492] Simplified conditions --- .../Primitives/SelectingItemsControl.cs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index e34b3b145f..4786e88fca 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -794,18 +794,13 @@ namespace Avalonia.Controls.Primitives /// The event. private void ContainerSelectionChanged(RoutedEventArgs e) { - if (!_ignoreContainerSelectionChanged) + if (!_ignoreContainerSelectionChanged && + e.Source is IControl control && + e.Source is ISelectable selectable && + control.LogicalParent == this && + ItemContainerGenerator?.IndexFromContainer(control) != -1) { - var control = e.Source as IControl; - var selectable = e.Source as ISelectable; - - if (control != null && - selectable != null && - control.LogicalParent == this && - ItemContainerGenerator?.IndexFromContainer(control) != -1) - { - UpdateSelection(control, selectable.IsSelected); - } + UpdateSelection(control, selectable.IsSelected); } if (e.Source != this) @@ -824,12 +819,11 @@ namespace Avalonia.Controls.Primitives { try { - var selectable = container as ISelectable; bool result; _ignoreContainerSelectionChanged = true; - if (selectable != null) + if (container is ISelectable selectable) { result = selectable.IsSelected; selectable.IsSelected = selected; From 3b48238fe07642d780740a9b1c26a0b6860374ff Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Thu, 15 Oct 2020 19:38:06 +0300 Subject: [PATCH 188/492] Removed dead code --- .../Primitives/SelectingItemsControl.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 4786e88fca..301487ebb6 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -842,21 +842,6 @@ namespace Avalonia.Controls.Primitives } } - /// - /// Sets an item container's 'selected' class or . - /// - /// The index of the item. - /// Whether the item should be selected or deselected. - private void MarkItemSelected(int index, bool selected) - { - var container = ItemContainerGenerator?.ContainerFromIndex(index); - - if (container != null) - { - MarkContainerSelected(container, selected); - } - } - private void UpdateContainerSelection() { if (Presenter?.Panel is IPanel panel) From dad0c4d18afc0462753b04d70bdb5b32ee6af335 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Thu, 15 Oct 2020 20:24:03 +0300 Subject: [PATCH 189/492] Fixed invalid cast issue if source isn't visual --- .../Primitives/SelectingItemsControl.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 4317d795f1..19c2b3b8fa 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -356,17 +356,13 @@ namespace Avalonia.Controls.Primitives /// The container or null if the event did not originate in a container. protected IControl? GetContainerFromEventSource(IInteractive eventSource) { - var parent = (IVisual)eventSource; - - while (parent != null) + for (var current = eventSource as IVisual; current != null; current = current.VisualParent) { - if (parent is IControl control && control.LogicalParent == this - && ItemContainerGenerator?.IndexFromContainer(control) != -1) + if (current is IControl control && control.LogicalParent == this && + ItemContainerGenerator?.IndexFromContainer(control) != -1) { return control; } - - parent = parent.VisualParent; } return null; From 02e6a4ffa5c635f17e6eb51393f88b685599d148 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Thu, 15 Oct 2020 20:24:57 +0300 Subject: [PATCH 190/492] Fixed nullability issues --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 19c2b3b8fa..d3e7751c4e 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -354,7 +354,7 @@ namespace Avalonia.Controls.Primitives /// /// The control that raised the event. /// The container or null if the event did not originate in a container. - protected IControl? GetContainerFromEventSource(IInteractive eventSource) + protected IControl? GetContainerFromEventSource(IInteractive? eventSource) { for (var current = eventSource as IVisual; current != null; current = current.VisualParent) { @@ -666,7 +666,7 @@ namespace Avalonia.Controls.Primitives /// false. /// protected bool UpdateSelectionFromEventSource( - IInteractive eventSource, + IInteractive? eventSource, bool select = true, bool rangeModifier = false, bool toggleModifier = false, From fdd9b1f31ff3c9ea3e70030c3c231983bcf0d765 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Thu, 15 Oct 2020 22:01:11 +0300 Subject: [PATCH 191/492] Fixed key gesture matching when this has an OEM key --- src/Avalonia.Input/KeyGesture.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Input/KeyGesture.cs b/src/Avalonia.Input/KeyGesture.cs index aa6fcc8bff..e155666631 100644 --- a/src/Avalonia.Input/KeyGesture.cs +++ b/src/Avalonia.Input/KeyGesture.cs @@ -144,7 +144,10 @@ namespace Avalonia.Input return s.ToString(); } - public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.KeyModifiers == KeyModifiers; + public bool Matches(KeyEventArgs keyEvent) => + keyEvent != null && + keyEvent.KeyModifiers == KeyModifiers && + ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key); // TODO: Move that to external key parser private static Key ParseKey(string key) From fb1c7fdcbe683dced07ba90e81fbfd3475f653ab Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 16 Oct 2020 14:13:47 +0300 Subject: [PATCH 192/492] add failing test for issue #4875 --- .../Selection/InternalSelectionModelTests.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/Selection/InternalSelectionModelTests.cs b/tests/Avalonia.Controls.UnitTests/Selection/InternalSelectionModelTests.cs index b64812e290..bb893bd35e 100644 --- a/tests/Avalonia.Controls.UnitTests/Selection/InternalSelectionModelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Selection/InternalSelectionModelTests.cs @@ -205,6 +205,33 @@ namespace Avalonia.Controls.UnitTests.Selection Assert.Equal(2, target.SelectedIndex); } + [Fact] + public void Raises_Selection_Changed_On_Items_Reset() + { + var items = new ResettingCollection(new[] { "foo", "bar", "baz" }); + var target = CreateTarget(source: items); + + target.SelectedIndex = 1; + + var changed = new List(); + + target.PropertyChanged += (s, e) => changed.Add(e.PropertyName); + + var oldSelectedIndex = target.SelectedIndex; + var oldSelectedItem = target.SelectedItem; + + items.Reset(new string[0]); + + Assert.NotEqual(oldSelectedIndex, target.SelectedIndex); + Assert.NotEqual(oldSelectedItem, target.SelectedItem); + + Assert.Equal(-1, target.SelectedIndex); + Assert.Equal(null, target.SelectedItem); + + Assert.Contains(nameof(target.SelectedIndex), changed); + Assert.Contains(nameof(target.SelectedItem), changed); + } + [Fact] public void Preserves_Selection_On_Source_Changed() { @@ -222,7 +249,7 @@ namespace Avalonia.Controls.UnitTests.Selection bool nullSource = false) { source ??= !nullSource ? new[] { "foo", "bar", "baz" } : null; - + var result = new InternalSelectionModel { SingleSelect = singleSelect, From 82d17aa8ae98160dca63a021d2cd3b675341d489 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 16 Oct 2020 14:19:06 +0300 Subject: [PATCH 193/492] fix selected item notify after been changed issue #4875 --- src/Avalonia.Controls/Selection/SelectionModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs index 3b5d57a7b8..054974e4f1 100644 --- a/src/Avalonia.Controls/Selection/SelectionModel.cs +++ b/src/Avalonia.Controls/Selection/SelectionModel.cs @@ -442,7 +442,8 @@ namespace Avalonia.Controls.Selection RaisePropertyChanged(nameof(SelectedIndex)); } - if (e.Action == NotifyCollectionChangedAction.Remove && e.OldStartingIndex <= oldSelectedIndex) + if ((e.Action == NotifyCollectionChangedAction.Remove && e.OldStartingIndex <= oldSelectedIndex) || + e.Action == NotifyCollectionChangedAction.Reset) { RaisePropertyChanged(nameof(SelectedItem)); } From 70aef2b17ed84b51b226b6f004efb79ca1b7067b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 16 Oct 2020 15:35:20 +0300 Subject: [PATCH 194/492] We can't randomly reference ReactiveUI anymore due to its maintainer's religious views --- .../Avalonia.Controls.UnitTests.csproj | 1 - .../TreeViewTests.cs | 20 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index 19c4454d3d..7a6d77ef46 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -22,7 +22,6 @@ - diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 9253d8a07f..b805683393 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -2,7 +2,9 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; @@ -14,7 +16,7 @@ using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; -using ReactiveUI; +using JetBrains.Annotations; using Xunit; namespace Avalonia.Controls.UnitTests @@ -480,8 +482,12 @@ namespace Avalonia.Controls.UnitTests var selectedValues = new List(); - dataContext.WhenAnyValue(x => x.SelectedItem) - .Subscribe(x => selectedValues.Add(x)); + dataContext.PropertyChanged += (_, e) => + { + if (e.PropertyName == nameof(TestDataContext.SelectedItem)) + selectedValues.Add(dataContext.SelectedItem); + }; + selectedValues.Add(dataContext.SelectedItem); _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Left); _mouse.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Left); @@ -1326,7 +1332,7 @@ namespace Avalonia.Controls.UnitTests { } - private class TestDataContext : ReactiveObject + private class TestDataContext : INotifyPropertyChanged { private string _selectedItem; @@ -1342,9 +1348,13 @@ namespace Avalonia.Controls.UnitTests get { return _selectedItem; } set { - this.RaiseAndSetIfChanged(ref _selectedItem, value); + _selectedItem = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem))); } } + + public event PropertyChangedEventHandler PropertyChanged; + } } } From 21fadbedd6734fd74e07fdeb7c2383b546ce6b38 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 16 Oct 2020 12:43:17 +0300 Subject: [PATCH 195/492] add tests for text issue:4866 --- .../Controls/TextBlockTests.cs | 54 ++++++++++++++++++ ...estrictedHeight_VerticalAlign.expected.png | Bin 0 -> 527 bytes ...estrictedHeight_VerticalAlign.expected.png | Bin 0 -> 527 bytes 3 files changed, 54 insertions(+) create mode 100644 tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png create mode 100644 tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png diff --git a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs index ab769e4ff0..53a2959848 100644 --- a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs +++ b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs @@ -40,5 +40,59 @@ namespace Avalonia.Direct2D1.RenderTests.Controls await RenderToFile(target); CompareImages(); } + + + [Win32Fact("Has text")] + public async Task RestrictedHeight_VerticalAlign() + { + IControl text(VerticalAlignment verticalAlingnment, bool clip = true, bool restrictHeight = true) + { + return new Border() + { + BorderBrush = Brushes.Blue, + BorderThickness = new Thickness(1), + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + Height = restrictHeight ? 20 : double.NaN, + Margin = new Thickness(1), + Child = new TextBlock + { + FontFamily = new FontFamily("Courier New"), + Background = Brushes.Red, + FontSize = 24, + Foreground = Brushes.Black, + Text = "L", + VerticalAlignment = verticalAlingnment, + ClipToBounds = clip + } + }; + } + Decorator target = new Decorator + { + Padding = new Thickness(8), + Width = 180, + Height = 80, + + Child = new StackPanel() + { + Orientation = Orientation.Horizontal, + Children = + { + text(VerticalAlignment.Stretch, restrictHeight: false), + text(VerticalAlignment.Center), + text(VerticalAlignment.Stretch), + text(VerticalAlignment.Top), + text(VerticalAlignment.Bottom), + text(VerticalAlignment.Center, clip:false), + text(VerticalAlignment.Stretch, clip:false), + text(VerticalAlignment.Top, clip:false), + text(VerticalAlignment.Bottom, clip:false), + } + } + }; + + await RenderToFile(target); + CompareImages(); + } } } diff --git a/tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png b/tests/TestFiles/Direct2D1/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..c5a0a14e52616d76ebd7926248e785e0c6b42296 GIT binary patch literal 527 zcmeAS@N?(olHy`uVBq!ia0vp^TYxx#gAGV-kceLfq*#ibJVQ8upoSx*gMoqZn5T}U;+IFz$-)-2Qso+}Sug{{q>EZbaGG{*& z^BrhqNHVFa5_;F;p$aq=4P+N;Sm#Ql+4(;Fe8A?um%ofx_5WVyv^uebmhOAP3E}(H zCG9Noq|YTwv?afjs6ErKopYshQv6nS-E$W{D;@W4TQ~J+@8*An?jwXI=Mg#)U&G&X+8f+7-6YKb)<8(jSY;J4LA* zLS7lie%u!OY5#?jJ6KrO-q@BLY*L~g?)mTQydzD%YtOHe-W9g&{YH-6{+~*gZH#}j z(QdKPmrGYpeTj~h269&aYirvscK^brXV0%Z-@4hfWUbTo6SKC?Njp1RW&h{GZ);p@ z!i5)L7|fua=M~7lo_83w~vya^yjSMfh`j0J+JDUAFAD)x=QqT z_~xVcen?B5^9CAdVlq2uPwsR5y=Y<0z|in+9^)@(Q)>rl$*mwsPgg&ebxsLQ0Q>~$ ATmS$7 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png b/tests/TestFiles/Skia/Controls/TextBlock/RestrictedHeight_VerticalAlign.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..c5a0a14e52616d76ebd7926248e785e0c6b42296 GIT binary patch literal 527 zcmeAS@N?(olHy`uVBq!ia0vp^TYxx#gAGV-kceLfq*#ibJVQ8upoSx*gMoqZn5T}U;+IFz$-)-2Qso+}Sug{{q>EZbaGG{*& z^BrhqNHVFa5_;F;p$aq=4P+N;Sm#Ql+4(;Fe8A?um%ofx_5WVyv^uebmhOAP3E}(H zCG9Noq|YTwv?afjs6ErKopYshQv6nS-E$W{D;@W4TQ~J+@8*An?jwXI=Mg#)U&G&X+8f+7-6YKb)<8(jSY;J4LA* zLS7lie%u!OY5#?jJ6KrO-q@BLY*L~g?)mTQydzD%YtOHe-W9g&{YH-6{+~*gZH#}j z(QdKPmrGYpeTj~h269&aYirvscK^brXV0%Z-@4hfWUbTo6SKC?Njp1RW&h{GZ);p@ z!i5)L7|fua=M~7lo_83w~vya^yjSMfh`j0J+JDUAFAD)x=QqT z_~xVcen?B5^9CAdVlq2uPwsR5y=Y<0z|in+9^)@(Q)>rl$*mwsPgg&ebxsLQ0Q>~$ ATmS$7 literal 0 HcmV?d00001 From dda966676c62932cc3d92a53a0430d03cb3296eb Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 16 Oct 2020 15:38:43 +0300 Subject: [PATCH 196/492] fix Textblock text alignment with restricted height #4866 --- src/Avalonia.Controls/TextBlock.cs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index d61519e697..31517ba59d 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -3,6 +3,7 @@ using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.Metadata; +using Avalonia.Layout; namespace Avalonia.Controls { @@ -427,14 +428,32 @@ namespace Avalonia.Controls case TextAlignment.Center: offsetX = (width - TextLayout.Size.Width) / 2; break; + case TextAlignment.Right: - offsetX = width - TextLayout.Size.Width; + offsetX = width - TextLayout.Size.Width; break; } var padding = Padding; - using (context.PushPostTransform(Matrix.CreateTranslation(padding.Left + offsetX, padding.Top))) + var top = padding.Top; + var textSize = TextLayout.Size; + + if (Bounds.Height < textSize.Height) + { + switch (VerticalAlignment) + { + case VerticalAlignment.Center: + top += (Bounds.Height - textSize.Height) / 2; + break; + + case VerticalAlignment.Bottom: + top += (Bounds.Height - textSize.Height); + break; + } + } + + using (context.PushPostTransform(Matrix.CreateTranslation(padding.Left + offsetX, top))) { TextLayout.Draw(context); } From 1dc2b0b12a12542a95bfc40373cf28d44e8fc688 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 16 Oct 2020 15:39:06 +0300 Subject: [PATCH 197/492] fix TextPresenter text alignment with restricted height #4866 --- .../Presenters/TextPresenter.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 6a6d37605d..078d8050bf 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -4,6 +4,7 @@ using Avalonia.Media; using Avalonia.Metadata; using Avalonia.Threading; using Avalonia.VisualTree; +using Avalonia.Layout; namespace Avalonia.Controls.Presenters { @@ -312,7 +313,24 @@ namespace Avalonia.Controls.Presenters context.FillRectangle(background, new Rect(Bounds.Size)); } - context.DrawText(Foreground, new Point(), FormattedText); + double top = 0; + var textSize = FormattedText.Bounds.Size; + + if (Bounds.Height < textSize.Height) + { + switch (VerticalAlignment) + { + case VerticalAlignment.Center: + top += (Bounds.Height - textSize.Height) / 2; + break; + + case VerticalAlignment.Bottom: + top += (Bounds.Height - textSize.Height); + break; + } + } + + context.DrawText(Foreground, new Point(0, top), FormattedText); } public override void Render(DrawingContext context) From 640bddf5cabf5f6a8bf4cb07fefe341549c62c03 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 24 Sep 2020 14:59:29 +0200 Subject: [PATCH 198/492] Add interface ICommandSource --- src/Avalonia.Input/ICommandSource.cs | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/Avalonia.Input/ICommandSource.cs diff --git a/src/Avalonia.Input/ICommandSource.cs b/src/Avalonia.Input/ICommandSource.cs new file mode 100644 index 0000000000..b0dd75e12e --- /dev/null +++ b/src/Avalonia.Input/ICommandSource.cs @@ -0,0 +1,31 @@ +using System.Windows.Input; + +namespace Avalonia.Input +{ + /// + /// An interface for classes that know how to invoke a Command. + /// + public interface ICommandSource + { + /// + /// The command that will be executed when the class is "invoked." + /// Classes that implement this interface should enable or disable based on the command's CanExecute return value. + /// The property may be implemented as read-write if desired. + /// + ICommand Command { get; } + + /// + /// The parameter that will be passed to the command when executing the command. + /// The property may be implemented as read-write if desired. + /// + object CommandParameter { get; } + + + /// + /// Bor the bheavior CanExecuteChanged + /// + /// + /// + void CanExecuteChanged(object sender, System.EventArgs e); + } +} From ef385675470f347aee079f3c9517f88014aa3789 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 24 Sep 2020 15:48:10 +0200 Subject: [PATCH 199/492] Implemented ICommandSource --- src/Avalonia.Controls/Button.cs | 4 +++- src/Avalonia.Controls/MenuItem.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index e94d00b2ff..91eef3947b 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -30,7 +30,7 @@ namespace Avalonia.Controls /// A button control. /// [PseudoClasses(":pressed")] - public class Button : ContentControl + public class Button : ContentControl, ICommandSource { /// /// Defines the property. @@ -492,5 +492,7 @@ namespace Avalonia.Controls { PseudoClasses.Set(":pressed", isPressed); } + + void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e); } } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 7d4fef009d..d034f54df2 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls /// A menu item control. /// [PseudoClasses(":separator", ":icon", ":open", ":pressed", ":selected")] - public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable + public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable, ICommandSource { /// /// Defines the property. @@ -609,6 +609,8 @@ namespace Avalonia.Controls SelectedItem = null; } + void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e); + /// /// A dependency resolver which returns a . /// From fa1476a6622101c383a19e499eb44ad352306e14 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 24 Sep 2020 16:05:59 +0200 Subject: [PATCH 200/492] Fix Hotkey CommandParameter --- src/Avalonia.Controls/HotkeyManager.cs | 17 +++++++++++------ src/Avalonia.Controls/NativeMenuItem.cs | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index 95752e7875..b104b7a9a8 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -1,6 +1,7 @@ using System; using System.Windows.Input; using Avalonia.Controls.Utils; +using Avalonia.Data.Core; using Avalonia.Input; namespace Avalonia.Controls @@ -12,14 +13,14 @@ namespace Avalonia.Controls class HotkeyCommandWrapper : ICommand { - public HotkeyCommandWrapper(IControl control) + public HotkeyCommandWrapper(ICommandSource control) { - Control = control; + CommandSource = control; } - public readonly IControl Control; + public readonly ICommandSource CommandSource; - private ICommand GetCommand() => Control.GetValue(Button.CommandProperty); + private ICommand GetCommand() => CommandSource.Command; public bool CanExecute(object parameter) => GetCommand()?.CanExecute(parameter) ?? false; @@ -37,6 +38,7 @@ namespace Avalonia.Controls private TopLevel _root; private IDisposable _parentSub; private IDisposable _hotkeySub; + private IDisposable _commandParameterChangedSubscriber; private KeyGesture _hotkey; private readonly HotkeyCommandWrapper _wrapper; private KeyBinding _binding; @@ -44,7 +46,7 @@ namespace Avalonia.Controls public Manager(IControl control) { _control = control; - _wrapper = new HotkeyCommandWrapper(_control); + _wrapper = new HotkeyCommandWrapper(_control as ICommandSource); } public void Init() @@ -77,6 +79,7 @@ namespace Avalonia.Controls { if (_root != null && _binding != null) _root.KeyBindings.Remove(_binding); + _commandParameterChangedSubscriber?.Dispose(); _binding = null; } @@ -85,6 +88,8 @@ namespace Avalonia.Controls if (_root != null && _hotkey != null) { _binding = new KeyBinding() {Gesture = _hotkey, Command = _wrapper}; + _commandParameterChangedSubscriber = _binding.Bind(KeyBinding.CommandParameterProperty + , ExpressionObserver.Create(((ICommandSource)_control), o => o.CommandParameter)); _root.KeyBindings.Add(_binding); } } @@ -102,7 +107,7 @@ namespace Avalonia.Controls HotKeyProperty.Changed.Subscribe(args => { var control = args.Sender as IControl; - if (args.OldValue != null|| control == null) + if (args.OldValue != null || control == null || !(control is ICommandSource)) return; new Manager(control).Init(); }); diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index a0fec9e677..d1adda3b0d 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -148,7 +148,7 @@ namespace Avalonia.Controls void CanExecuteChanged() { - IsEnabled = _command?.CanExecute(null) ?? true; + IsEnabled = _command?.CanExecute(CommandParameter) ?? true; } public bool HasClickHandlers => Clicked != null; From 9ffd78a3baa31772de40dd3aad018451486927e7 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 24 Sep 2020 16:29:51 +0200 Subject: [PATCH 201/492] Add Test --- .../Utils/HotKeyManagerTests.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index 9f2712c93c..a29eb0f0c7 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -5,6 +5,9 @@ using Avalonia.Input; using Avalonia.Platform; using Avalonia.Styling; using Xunit; +using System; +using Avalonia.Input.Raw; +using Factory = System.Func, Avalonia.Controls.Window, Avalonia.AvaloniaObject>; namespace Avalonia.Controls.UnitTests.Utils { @@ -54,6 +57,84 @@ namespace Avalonia.Controls.UnitTests.Utils } } + [Theory] + [MemberData(nameof(ElementsFactory))] + public void HotKeyManager_Should_Use_CommandParameter(string factoryName, Factory factory) + { + using (AvaloniaLocator.EnterScope()) + { + var styler = new Mock(); + var target = new KeyboardDevice(); + var commandResult = 0; + var expectedParameter = 1; + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(new WindowingPlatformMock()) + .Bind().ToConstant(styler.Object); + + var gesture = new KeyGesture(Key.A, KeyModifiers.Control); + + var action = new Action(parameter => + { + if (parameter is int value) + { + commandResult = value; + } + }); + + var root = new Window(); + var element = factory(expectedParameter, action, root); + + root.Template = CreateWindowTemplate(); + root.ApplyTemplate(); + root.Presenter.ApplyTemplate(); + + HotKeyManager.SetHotKey(element, gesture); + + target.ProcessRawEvent(new RawKeyEventArgs(target, + 0, + root, + RawKeyEventType.KeyDown, + Key.A, + RawInputModifiers.Control)); + + Assert.True(expectedParameter == commandResult, $"{factoryName} HotKey did not carry the CommandParameter."); + } + } + + public static TheoryData ElementsFactory => + new TheoryData() + { + {nameof(Button), MakeButton}, + {nameof(MenuItem),MakeMenu}, + }; + + private static AvaloniaObject MakeMenu(int expectedParameter, Action action, Window root) + { + var menuitem = new MenuItem() + { + Command = new Command(action), + CommandParameter = expectedParameter, + }; + var rootMenu = new Menu(); + + rootMenu.Items = new[] { menuitem }; + + root.Content = rootMenu; + return menuitem; + } + + private static AvaloniaObject MakeButton(int expectedParameter, Action action, Window root) + { + var button = new Button() + { + Command = new Command(action), + CommandParameter = expectedParameter, + }; + + root.Content = button; + return button; + } + private FuncControlTemplate CreateWindowTemplate() { return new FuncControlTemplate((parent, scope) => @@ -65,5 +146,22 @@ namespace Avalonia.Controls.UnitTests.Utils }.RegisterInNameScope(scope); }); } + + class Command : System.Windows.Input.ICommand + { + private readonly Action _execeute; + +#pragma warning disable 67 // Event not used + public event EventHandler CanExecuteChanged; +#pragma warning restore 67 // Event not used + + public Command(Action execeute) + { + _execeute = execeute; + } + public bool CanExecute(object parameter) => true; + + public void Execute(object parameter) => _execeute?.Invoke(parameter); + } } } From f1f2df8dd48d42ddc7cc669235988b38cf24b259 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 25 Sep 2020 17:32:26 +0200 Subject: [PATCH 202/492] Fixes Issue #4734 : HotKeys trigger command when control is disabled --- src/Avalonia.Controls/HotkeyManager.cs | 2 +- src/Avalonia.Input/ICommandSource.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index b104b7a9a8..f210ecfdaf 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls private ICommand GetCommand() => CommandSource.Command; - public bool CanExecute(object parameter) => GetCommand()?.CanExecute(parameter) ?? false; + public bool CanExecute(object parameter) => CommandSource.IsEffectivelyEnabled; public void Execute(object parameter) => GetCommand()?.Execute(parameter); diff --git a/src/Avalonia.Input/ICommandSource.cs b/src/Avalonia.Input/ICommandSource.cs index b0dd75e12e..ba2e8eed4e 100644 --- a/src/Avalonia.Input/ICommandSource.cs +++ b/src/Avalonia.Input/ICommandSource.cs @@ -27,5 +27,10 @@ namespace Avalonia.Input /// /// void CanExecuteChanged(object sender, System.EventArgs e); + + /// + /// Gets a value indicating whether this control and all its parents are enabled. + /// + bool IsEffectivelyEnabled { get; } } } From f0efe9aef0d2e4953f5ed034e21359c95dcc4499 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 25 Sep 2020 17:33:13 +0200 Subject: [PATCH 203/492] Add test Issue #4734 --- .../Utils/HotKeyManagerTests.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index a29eb0f0c7..151c3070f4 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -101,6 +101,49 @@ namespace Avalonia.Controls.UnitTests.Utils } } + + [Theory] + [MemberData(nameof(ElementsFactory))] + public void HotKeyManager_Should_Do_Not_Executed_When_IsEnabled_False(string factoryName, Factory factory) + { + using (AvaloniaLocator.EnterScope()) + { + var styler = new Mock(); + var target = new KeyboardDevice(); + var isExecuted = false; + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(new WindowingPlatformMock()) + .Bind().ToConstant(styler.Object); + + var gesture = new KeyGesture(Key.A, KeyModifiers.Control); + + var action = new Action(parameter => + { + isExecuted = true; + }); + + var root = new Window(); + var element = factory(0, action, root) as InputElement; + + element.IsEnabled = false; + + root.Template = CreateWindowTemplate(); + root.ApplyTemplate(); + root.Presenter.ApplyTemplate(); + + HotKeyManager.SetHotKey(element, gesture); + + target.ProcessRawEvent(new RawKeyEventArgs(target, + 0, + root, + RawKeyEventType.KeyDown, + Key.A, + RawInputModifiers.Control)); + + Assert.True(isExecuted == false, $"{factoryName} Execution raised when IsEnabled is false."); + } + } + public static TheoryData ElementsFactory => new TheoryData() { From c7a21d7268aad472e9022777b626120cdc6b29e3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 9 Oct 2020 09:51:54 +0200 Subject: [PATCH 204/492] Removed CommandParameter Binding --- src/Avalonia.Controls/HotkeyManager.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index f210ecfdaf..dabc35dd24 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -1,7 +1,6 @@ using System; using System.Windows.Input; using Avalonia.Controls.Utils; -using Avalonia.Data.Core; using Avalonia.Input; namespace Avalonia.Controls @@ -22,9 +21,12 @@ namespace Avalonia.Controls private ICommand GetCommand() => CommandSource.Command; - public bool CanExecute(object parameter) => CommandSource.IsEffectivelyEnabled; + public bool CanExecute(object parameter) => + CommandSource.Command?.CanExecute(CommandSource.CommandParameter) == true + && CommandSource.IsEffectivelyEnabled; - public void Execute(object parameter) => GetCommand()?.Execute(parameter); + public void Execute(object parameter) => + GetCommand()?.Execute(CommandSource.CommandParameter); #pragma warning disable 67 // Event not used public event EventHandler CanExecuteChanged; @@ -38,7 +40,6 @@ namespace Avalonia.Controls private TopLevel _root; private IDisposable _parentSub; private IDisposable _hotkeySub; - private IDisposable _commandParameterChangedSubscriber; private KeyGesture _hotkey; private readonly HotkeyCommandWrapper _wrapper; private KeyBinding _binding; @@ -79,7 +80,6 @@ namespace Avalonia.Controls { if (_root != null && _binding != null) _root.KeyBindings.Remove(_binding); - _commandParameterChangedSubscriber?.Dispose(); _binding = null; } @@ -87,9 +87,7 @@ namespace Avalonia.Controls { if (_root != null && _hotkey != null) { - _binding = new KeyBinding() {Gesture = _hotkey, Command = _wrapper}; - _commandParameterChangedSubscriber = _binding.Bind(KeyBinding.CommandParameterProperty - , ExpressionObserver.Create(((ICommandSource)_control), o => o.CommandParameter)); + _binding = new KeyBinding() { Gesture = _hotkey, Command = _wrapper }; _root.KeyBindings.Add(_binding); } } From b8728c8f8ccf08a95f68114d0c3f2f00bb7ff0d3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 9 Oct 2020 09:53:54 +0200 Subject: [PATCH 205/492] Add warning when HotKey is attached to a control that does not support ICommandSource. --- src/Avalonia.Controls/HotkeyManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index dabc35dd24..baae37e022 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -105,8 +105,13 @@ namespace Avalonia.Controls HotKeyProperty.Changed.Subscribe(args => { var control = args.Sender as IControl; - if (args.OldValue != null || control == null || !(control is ICommandSource)) + if (args.OldValue != null || control == null || !(control is ICommandSource)) + { + Logging.Logger.TryGet(Logging.LogEventLevel.Warning, Logging.LogArea.Control)?. + Log(control, $"The element {args.Sender.GetType().Name} do not support HotKey({args.NewValue})."); return; + } + new Manager(control).Init(); }); } From abff848c8d844eebd32a8a61062a471ccc807db7 Mon Sep 17 00:00:00 2001 From: workgroupengineering Date: Tue, 13 Oct 2020 17:01:37 +0200 Subject: [PATCH 206/492] Update warning message Co-authored-by: Steven Kirk --- src/Avalonia.Controls/HotkeyManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index baae37e022..b76b7fbed5 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -108,7 +108,7 @@ namespace Avalonia.Controls if (args.OldValue != null || control == null || !(control is ICommandSource)) { Logging.Logger.TryGet(Logging.LogEventLevel.Warning, Logging.LogArea.Control)?. - Log(control, $"The element {args.Sender.GetType().Name} do not support HotKey({args.NewValue})."); + Log(control, $"The element {args.Sender.GetType().Name} does not support binding a HotKey ({args.NewValue})."); return; } From 919c1edde4e7be6716023204df3547b1b0c9d49a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 17 Oct 2020 10:30:45 +0200 Subject: [PATCH 207/492] Remove warning sample --- samples/BindingDemo/App.xaml.cs | 20 +++++++++++------- samples/ControlCatalog.Desktop/Program.cs | 8 ++----- .../ControlCatalog.NetCore.csproj | 1 - samples/Previewer/App.xaml.cs | 10 ++++++++- samples/Previewer/Program.cs | 15 ++++++------- samples/RenderDemo/App.xaml.cs | 11 +++++++++- samples/RenderDemo/Pages/GlyphRunPage.xaml | 6 +++--- samples/RenderDemo/Pages/GlyphRunPage.xaml.cs | 7 ++++--- samples/VirtualizationDemo/App.xaml.cs | 8 +++++++ samples/VirtualizationDemo/Program.cs | 20 ++++++++---------- .../interop/Direct3DInteropSample/App.paml.cs | 8 +++++++ .../interop/Direct3DInteropSample/Program.cs | 21 ++++++++----------- .../NativeEmbedSample.csproj | 1 - 13 files changed, 83 insertions(+), 53 deletions(-) diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs index 13875aeb21..5c38ab8305 100644 --- a/samples/BindingDemo/App.xaml.cs +++ b/samples/BindingDemo/App.xaml.cs @@ -1,6 +1,5 @@ -using System; using Avalonia; -using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; @@ -13,13 +12,20 @@ namespace BindingDemo AvaloniaXamlLoader.Load(this); } - private static void Main() + public override void OnFrameworkInitializationCompleted() { - AppBuilder.Configure() + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + desktop.MainWindow = new MainWindow(); + base.OnFrameworkInitializationCompleted(); + } + + public static int Main(string[] args) + => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() .UsePlatformDetect() .UseReactiveUI() - .LogToDebug() - .Start(); - } + .LogToDebug(); } } diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b2df1953f5..e0cc8cc904 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -10,12 +10,8 @@ namespace ControlCatalog internal class Program { [STAThread] - static void Main(string[] args) - { - // TODO: Make this work with GTK/Skia/Cairo depending on command-line args - // again. - BuildAvaloniaApp().Start(); - } + public static int Main(string[] args) + => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); /// /// This method is needed for IDE previewer infrastructure diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 28b0257eda..2752703e21 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -11,7 +11,6 @@ - diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs index fffa987a27..ab83d45cd3 100644 --- a/samples/Previewer/App.xaml.cs +++ b/samples/Previewer/App.xaml.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; namespace Previewer @@ -9,6 +10,13 @@ namespace Previewer { AvaloniaXamlLoader.Load(this); } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + desktop.MainWindow = new MainWindow(); + base.OnFrameworkInitializationCompleted(); + } } -} \ No newline at end of file +} diff --git a/samples/Previewer/Program.cs b/samples/Previewer/Program.cs index 48363e27f2..b12b93974a 100644 --- a/samples/Previewer/Program.cs +++ b/samples/Previewer/Program.cs @@ -1,13 +1,14 @@ -using System; -using Avalonia; +using Avalonia; namespace Previewer { class Program { - static void Main(string[] args) - { - AppBuilder.Configure().UsePlatformDetect().Start(); - } + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect(); + + public static int Main(string[] args) + => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } -} \ No newline at end of file +} diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index 340ccdae19..2247fd7c5a 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; @@ -11,9 +12,17 @@ namespace RenderDemo AvaloniaXamlLoader.Load(this); } + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + desktop.MainWindow = new MainWindow(); + base.OnFrameworkInitializationCompleted(); + } + // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. - static void Main(string[] args) => BuildAvaloniaApp().Start(); + static void Main(string[] args) + => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); // App configuration, used by the entry point and previewer static AppBuilder BuildAvaloniaApp() diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml b/samples/RenderDemo/Pages/GlyphRunPage.xaml index fb3e318a0e..c2914e8847 100644 --- a/samples/RenderDemo/Pages/GlyphRunPage.xaml +++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml @@ -6,9 +6,9 @@ x:Class="RenderDemo.Pages.GlyphRunPage"> - - + diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs index ddee880288..857358f6b2 100644 --- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs +++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs @@ -9,7 +9,7 @@ namespace RenderDemo.Pages { public class GlyphRunPage : UserControl { - private DrawingPresenter _drawingPresenter; + private Image _imageControl; private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private readonly Random _rand = new Random(); private ushort[] _glyphIndices = new ushort[1]; @@ -25,7 +25,8 @@ namespace RenderDemo.Pages { AvaloniaXamlLoader.Load(this); - _drawingPresenter = this.FindControl("drawingPresenter"); + _imageControl = this.FindControl("imageControl"); + _imageControl.Source = new DrawingImage(); DispatcherTimer.Run(() => { @@ -73,7 +74,7 @@ namespace RenderDemo.Pages drawingGroup.Children.Add(geometryDrawing); - _drawingPresenter.Drawing = drawingGroup; + (_imageControl.Source as DrawingImage).Drawing = drawingGroup; } } } diff --git a/samples/VirtualizationDemo/App.xaml.cs b/samples/VirtualizationDemo/App.xaml.cs index 5990dd282c..81b80c1f40 100644 --- a/samples/VirtualizationDemo/App.xaml.cs +++ b/samples/VirtualizationDemo/App.xaml.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; namespace VirtualizationDemo @@ -9,5 +10,12 @@ namespace VirtualizationDemo { AvaloniaXamlLoader.Load(this); } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + desktop.MainWindow = new MainWindow(); + base.OnFrameworkInitializationCompleted(); + } } } diff --git a/samples/VirtualizationDemo/Program.cs b/samples/VirtualizationDemo/Program.cs index 93ea5e1b88..c23bfd7ad9 100644 --- a/samples/VirtualizationDemo/Program.cs +++ b/samples/VirtualizationDemo/Program.cs @@ -1,19 +1,17 @@ -using System; -using Avalonia; -using Avalonia.Controls; +using Avalonia; using Avalonia.ReactiveUI; namespace VirtualizationDemo { class Program { - static void Main(string[] args) - { - AppBuilder.Configure() - .UsePlatformDetect() - .UseReactiveUI() - .LogToDebug() - .Start(); - } + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .UseReactiveUI() + .LogToDebug(); + + public static int Main(string[] args) + => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } } diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs index 1b6d5fd39c..29365decfe 100644 --- a/samples/interop/Direct3DInteropSample/App.paml.cs +++ b/samples/interop/Direct3DInteropSample/App.paml.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; namespace Direct3DInteropSample @@ -9,5 +10,12 @@ namespace Direct3DInteropSample { AvaloniaXamlLoader.Load(this); } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + desktop.MainWindow = new MainWindow(); + base.OnFrameworkInitializationCompleted(); + } } } diff --git a/samples/interop/Direct3DInteropSample/Program.cs b/samples/interop/Direct3DInteropSample/Program.cs index 21302fa68a..bf8e76d7e4 100644 --- a/samples/interop/Direct3DInteropSample/Program.cs +++ b/samples/interop/Direct3DInteropSample/Program.cs @@ -1,19 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia; +using Avalonia; namespace Direct3DInteropSample { class Program { - static void Main(string[] args) - { - AppBuilder.Configure() - .With(new Win32PlatformOptions {UseDeferredRendering = false}) - .UseWin32().UseDirect2D1().Start(); - } + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .With(new Win32PlatformOptions { UseDeferredRendering = false }) + .UseWin32() + .UseDirect2D1(); + + public static int Main(string[] args) + => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } } diff --git a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj index cc831ef8ae..0e0e71fbb2 100644 --- a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj +++ b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj @@ -9,7 +9,6 @@ - From a6c705bf70f56389392fadabff43e05b3010f143 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 17 Oct 2020 10:35:18 +0200 Subject: [PATCH 208/492] Update XML Comment --- .../DataGridColumn.cs | 1 + .../Primitives/IPopupHost.cs | 1 + src/Avalonia.Controls/Window.cs | 2 +- src/Avalonia.Visuals/Media/Imaging/Bitmap.cs | 1 - .../Platform/IDrawingContextImpl.cs | 2 +- .../SceneGraph/ExperimentalAcrylicNode.cs | 7 ++--- .../Rendering/SceneGraph/GeometryNode.cs | 1 - .../Rendering/SceneGraph/RectangleNode.cs | 2 +- .../Avalonia.LinuxFramebuffer/Output/Drm.cs | 16 +++++----- .../AvaloniaRuntimeXamlLoader.cs | 31 ++++++++++++++++--- .../Media/DrawingContextImpl.cs | 1 - .../Avalonia.Win32/Interop/TaskBarList.cs | 1 + 12 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 23c4acdf6c..92ddd4e736 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -665,6 +665,7 @@ namespace Avalonia.Controls /// /// The data item represented by the row that contains the intended cell. /// + /// When the method returns, contains the applied binding. /// /// A new editing element that is bound to the column's property value. /// diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index 82a49c4189..ab81fe869e 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs @@ -47,6 +47,7 @@ namespace Avalonia.Controls.Primitives /// The offset, in device-independent pixels. /// The anchor point. /// The popup gravity. + /// Defines how a popup position will be adjusted if the unadjusted position would result in the popup being partly constrained. /// /// The anchor rect. If null, the bounds of will be used. /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 317b6d3f2e..54574a7e1c 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -451,7 +451,7 @@ namespace Avalonia.Controls /// /// The dialog result. /// - /// When the window is shown with the + /// When the window is shown with the /// or method, the /// resulting task will produce the value when the window /// is closed. diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index ca303211cd..5a0c57b333 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -99,7 +99,6 @@ namespace Avalonia.Media.Imaging /// Initializes a new instance of the class. /// /// The pixel format. - /// The alpha format. /// The pointer to the source bytes. /// The size of the bitmap in device pixels. /// The DPI of the bitmap. diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index cfe2cf979a..9cf4e62030 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -63,7 +63,7 @@ namespace Avalonia.Platform /// The brush used to fill the rectangle, or null for no fill. /// The pen used to stroke the rectangle, or null for no stroke. /// The rectangle bounds. - /// Box shadow effect parameters + /// Box shadow effect parameters /// /// The brush and the pen can both be null. If the brush is null, then no fill is performed. /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs index 336d11e3fd..8bd079d070 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs @@ -13,10 +13,9 @@ namespace Avalonia.Rendering.SceneGraph /// /// Initializes a new instance of the class. /// - /// The transform. + /// The transform. + /// /// The rectangle to draw. - /// The box shadow parameters - /// Child scenes for drawing visual brushes. public ExperimentalAcrylicNode( Matrix transform, IExperimentalAcrylicMaterial material, @@ -44,7 +43,7 @@ namespace Avalonia.Rendering.SceneGraph /// Determines if this draw operation equals another. /// /// The transform of the other draw operation. - /// The fill of the other draw operation. + /// The fill of the other draw operation. /// The rectangle of the other draw operation. /// True if the draw operations are the same, otherwise false. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs index 8a19679c77..508ca0ad18 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs @@ -63,7 +63,6 @@ namespace Avalonia.Rendering.SceneGraph /// The fill of the other draw operation. /// The stroke of the other draw operation. /// The geometry of the other draw operation. - /// The box shadow parameters /// True if the draw operations are the same, otherwise false. /// /// The properties of the other draw operation are passed in as arguments to prevent diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index ec1a7753b1..d0c4566485 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -73,7 +73,7 @@ namespace Avalonia.Rendering.SceneGraph /// The fill of the other draw operation. /// The stroke of the other draw operation. /// The rectangle of the other draw operation. - /// The box shadow parameters of the other draw operation + /// The box shadow parameters of the other draw operation /// True if the draw operations are the same, otherwise false. /// /// The properties of the other draw operation are passed in as arguments to prevent diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs index 787a2e4cb8..9c476b1b63 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs @@ -113,22 +113,22 @@ namespace Avalonia.LinuxFramebuffer.Output [StructLayout(LayoutKind.Sequential)] public struct drmModeConnector { public uint connector_id; - public uint encoder_id; /**< Encoder currently connected to */ + public uint encoder_id; // Encoder currently connected to public uint connector_type; public uint connector_type_id; public DrmModeConnection connection; - public uint mmWidth, mmHeight; /**< HxW in millimeters */ + public uint mmWidth, mmHeight; // HxW in millimeters public DrmModeSubPixel subpixel; public int count_modes; public drmModeModeInfo* modes; public int count_props; - public uint *props; /**< List of property ids */ - public ulong *prop_values; /**< List of property values */ + public uint *props; // List of property ids + public ulong *prop_values; // List of property values public int count_encoders; - public uint *encoders; /**< List of encoder ids */ + public uint *encoders; //List of encoder ids } [StructLayout(LayoutKind.Sequential)] @@ -143,14 +143,14 @@ namespace Avalonia.LinuxFramebuffer.Output [StructLayout(LayoutKind.Sequential)] public struct drmModeCrtc { public uint crtc_id; - public uint buffer_id; /**< FB id to connect to 0 = disconnect */ + public uint buffer_id; // FB id to connect to 0 = disconnect - public uint x, y; /**< Position on the framebuffer */ + public uint x, y; // Position on the framebuffer public uint width, height; public int mode_valid; public drmModeModeInfo mode; - public int gamma_size; /**< Number of gamma stops */ + public int gamma_size; // Number of gamma stops } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs index 4569970d01..4df07bcdd8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs @@ -3,7 +3,6 @@ using System.IO; using System.Reflection; using System.Text; using Avalonia.Markup.Xaml.XamlIl; -// ReSharper disable CheckNamespace namespace Avalonia.Markup.Xaml { @@ -13,10 +12,10 @@ namespace Avalonia.Markup.Xaml /// Loads XAML from a string. /// /// The string containing the XAML. - /// Default assembly for clr-namespace: - /// - /// The optional instance into which the XAML should be loaded. - /// + /// Default assembly for clr-namespace:. + /// The optional instance into which the XAML should be loaded. + /// The URI of the XAML being loaded. + /// Indicates whether the XAML is being loaded in design mode. /// The loaded object. public static object Load(string xaml, Assembly localAssembly = null, object rootInstance = null, Uri uri = null, bool designMode = false) { @@ -28,13 +27,35 @@ namespace Avalonia.Markup.Xaml } } + /// + /// Loads XAML from a stream. + /// + /// The stream containing the XAML. + /// Default assembly for clr-namespace: + /// The optional instance into which the XAML should be loaded. + /// The URI of the XAML being loaded. + /// Indicates whether the XAML is being loaded in design mode. + /// The loaded object. public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null, bool designMode = false) => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode); + /// + /// Parse XAML from a string. + /// + /// The string containing the XAML. + /// Default assembly for clr-namespace:. + /// The loaded object. public static object Parse(string xaml, Assembly localAssembly = null) => Load(xaml, localAssembly); + /// + /// Parse XAML from a string. + /// + /// The type of the returned object. + /// >The string containing the XAML. + /// >Default assembly for clr-namespace:. + /// The loaded object. public static T Parse(string xaml, Assembly localAssembly = null) => (T)Parse(xaml, localAssembly); diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 59292d605c..ace658654d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -323,7 +323,6 @@ namespace Avalonia.Direct2D1.Media /// /// The foreground. /// The glyph run. - /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { using (var brush = CreateBrush(foreground, glyphRun.Size)) diff --git a/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs b/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs index 1b01ebbe7f..88b907aeec 100644 --- a/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs +++ b/src/Windows/Avalonia.Win32/Interop/TaskBarList.cs @@ -13,6 +13,7 @@ namespace Avalonia.Win32.Interop /// /// Ported from https://github.com/chromium/chromium/blob/master/ui/views/win/fullscreen_handler.cc /// + /// The window handle. /// Fullscreen state. public static unsafe void MarkFullscreen(IntPtr hwnd, bool fullscreen) { From c98918bc2afc97da3692a616bf5e74db393523a9 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 17 Oct 2020 10:38:17 +0200 Subject: [PATCH 209/492] Update tests --- .../AnimatableTests.cs | 8 +- .../AvaloniaObjectTests_Metadata.cs | 4 +- .../Collections/AvaloniaListExtenionsTests.cs | 4 + .../Data/Core/BindingExpressionTests.cs | 2 +- .../Data/DefaultValueConverterTests.cs | 14 ++-- tests/Avalonia.Benchmarks/NullRenderer.cs | 3 +- .../NullThreadingPlatform.cs | 3 + .../BorderTests.cs | 2 +- .../Avalonia.Controls.UnitTests/PanelTests.cs | 2 +- .../DefaultMenuInteractionHandlerTests.cs | 10 +-- .../ContentPresenterTests_Standalone.cs | 2 +- .../TextBlockTests.cs | 4 +- .../App.xaml.cs | 13 ++-- .../Program.cs | 20 ++--- .../LayoutableTests.cs | 6 +- ...ayoutableTests_EffectiveViewportChanged.cs | 2 + tests/Avalonia.LeakTests/ControlTests.cs | 7 +- ...ExpressionObserverBuilderTests_Negation.cs | 15 +++- .../Avalonia.RenderTests/Media/BitmapTests.cs | 2 + .../ResourceDictionaryTests.cs | 12 +-- .../SelectorTests_Child.cs | 75 ------------------- .../SelectorTests_Descendent.cs | 74 ------------------ .../Avalonia.Styling.UnitTests/StyleTests.cs | 2 +- .../Avalonia.Styling.UnitTests/StylesTests.cs | 8 +- .../Rendering/DeferredRendererTests.cs | 2 +- .../Avalonia.Visuals.UnitTests/VisualTests.cs | 2 +- 26 files changed, 86 insertions(+), 212 deletions(-) diff --git a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs index 784f40fe1f..7633a761a3 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs @@ -113,7 +113,7 @@ namespace Avalonia.Animation.UnitTests It.IsAny(), 1.0, 0.5)); - target.ResetCalls(); + target.Invocations.Clear(); control.SetValue(Visual.OpacityProperty, 0.8, BindingPriority.StyleTrigger); @@ -135,7 +135,7 @@ namespace Avalonia.Animation.UnitTests target.Setup(x => x.Apply(control, It.IsAny(), 1.0, 0.5)).Returns(sub.Object); control.Opacity = 0.5; - sub.ResetCalls(); + sub.Invocations.Clear(); control.Opacity = 0.4; sub.Verify(x => x.Dispose()); @@ -158,7 +158,7 @@ namespace Avalonia.Animation.UnitTests control.Opacity = 0.5; Assert.Equal(0.9, control.Opacity); - target.ResetCalls(); + target.Invocations.Clear(); control.Opacity = 0.4; @@ -182,7 +182,7 @@ namespace Avalonia.Animation.UnitTests It.IsAny(), 1.0, 0.5)); - target.ResetCalls(); + target.Invocations.Clear(); var root = (TestRoot)control.Parent; root.Child = null; diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs index 161911dfd5..2edb3deff0 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs @@ -67,7 +67,7 @@ namespace Avalonia.Base.UnitTests public static readonly DirectProperty DirectProperty = AvaloniaProperty.RegisterDirect("Styled", o => o.Direct, unsetValue: "foo"); - private string _direct; + private string _direct = default; public string Direct { @@ -92,7 +92,7 @@ namespace Avalonia.Base.UnitTests public static readonly DirectProperty DirectProperty = Class1.DirectProperty.AddOwner(o => o.Direct, unsetValue: "baz"); - private string _direct; + private string _direct = default; static Class3() { diff --git a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs index 7f118a2c1d..b1b2d3d8f2 100644 --- a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs +++ b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs @@ -6,10 +6,12 @@ namespace Avalonia.Base.UnitTests.Collections { public class AvaloniaListExtenionsTests { +#pragma warning disable CS0618 // Type or member is obsolete [Fact] public void CreateDerivedList_Creates_Initial_Items() { var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); + var target = source.CreateDerivedList(x => new Wrapper(x)); var result = target.Select(x => x.Value).ToList(); @@ -137,6 +139,8 @@ namespace Avalonia.Base.UnitTests.Collections Assert.Equal(source, result); } +#pragma warning restore CS0618 // Type or member is obsolete + private class Wrapper { diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs index 1e1f4bd4b2..339cf8a334 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs @@ -356,7 +356,7 @@ namespace Avalonia.Base.UnitTests.Data.Core } [Fact] - public async Task Null_Value_Should_Use_TargetNullValue() + public void Null_Value_Should_Use_TargetNullValue() { var data = new Class1 { StringValue = "foo" }; diff --git a/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs b/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs index efa81dcc1b..f522acf9ce 100644 --- a/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs @@ -1,12 +1,11 @@ +using System; +using System.ComponentModel; using System.Globalization; -using Avalonia.Controls; -using Avalonia.Data; -using Xunit; using System.Windows.Input; -using System; +using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.Layout; -using System.ComponentModel; +using Xunit; namespace Avalonia.Base.UnitTests.Data.Converters { @@ -251,6 +250,11 @@ namespace Avalonia.Base.UnitTests.Data.Converters { return obj is CustomType other && this.Value == other.Value; } + + public override int GetHashCode() + { + return 8399587^Value.GetHashCode(); + } } private class CustomTypeConverter : TypeConverter diff --git a/tests/Avalonia.Benchmarks/NullRenderer.cs b/tests/Avalonia.Benchmarks/NullRenderer.cs index 9a756aaf0b..c1701b5d57 100644 --- a/tests/Avalonia.Benchmarks/NullRenderer.cs +++ b/tests/Avalonia.Benchmarks/NullRenderer.cs @@ -9,8 +9,9 @@ namespace Avalonia.Benchmarks { public bool DrawFps { get; set; } public bool DrawDirtyRects { get; set; } +#pragma warning disable CS0067 public event EventHandler SceneInvalidated; - +#pragma warning restore CS0067 public void AddDirty(IVisual visual) { } diff --git a/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs b/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs index ba84b5afcc..bb469a6b33 100644 --- a/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs @@ -23,6 +23,9 @@ namespace Avalonia.Benchmarks public bool CurrentThreadIsLoopThread => true; +#pragma warning disable CS0067 public event Action Signaled; +#pragma warning restore CS0067 + } } diff --git a/tests/Avalonia.Controls.UnitTests/BorderTests.cs b/tests/Avalonia.Controls.UnitTests/BorderTests.cs index 6e2599c9fd..ab33eaaff9 100644 --- a/tests/Avalonia.Controls.UnitTests/BorderTests.cs +++ b/tests/Avalonia.Controls.UnitTests/BorderTests.cs @@ -54,7 +54,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); var renderer = Mock.Get(root.Renderer); - renderer.ResetCalls(); + renderer.Invocations.Clear(); ((SolidColorBrush)target.Background).Color = Colors.Green; diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs index 79ee6d1236..f189638c7d 100644 --- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs @@ -127,7 +127,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); var renderer = Mock.Get(root.Renderer); - renderer.ResetCalls(); + renderer.Invocations.Clear(); ((SolidColorBrush)target.Background).Color = Colors.Green; diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index 64f35049ce..1a11091b81 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -503,26 +503,26 @@ namespace Avalonia.Controls.UnitTests.Platform target.PointerEnter(item, enter); Assert.True(timer.ActionIsQueued); Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item); - Mock.Get(parentItem).ResetCalls(); + Mock.Get(parentItem).Invocations.Clear(); // SubMenu shown after a delay. timer.Pulse(); Mock.Get(item).Verify(x => x.Open()); Mock.Get(item).SetupGet(x => x.IsSubMenuOpen).Returns(true); - Mock.Get(item).ResetCalls(); + Mock.Get(item).Invocations.Clear(); // Pointer briefly exits item, but submenu remains open. target.PointerLeave(item, leave); Mock.Get(item).Verify(x => x.Close(), Times.Never); - Mock.Get(item).ResetCalls(); + Mock.Get(item).Invocations.Clear(); // Pointer enters child item; is selected. enter.Source = childItem; target.PointerEnter(childItem, enter); Mock.Get(item).VerifySet(x => x.SelectedItem = childItem); Mock.Get(parentItem).VerifySet(x => x.SelectedItem = item); - Mock.Get(item).ResetCalls(); - Mock.Get(parentItem).ResetCalls(); + Mock.Get(item).Invocations.Clear(); + Mock.Get(parentItem).Invocations.Clear(); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index c0440ebc7b..c7aa583b6f 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs @@ -212,7 +212,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var root = new TestRoot(target); var renderer = Mock.Get(root.Renderer); - renderer.ResetCalls(); + renderer.Invocations.Clear(); ((SolidColorBrush)target.Background).Color = Colors.Green; diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index 2d2c197220..b180a536a5 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs @@ -37,7 +37,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); var renderer = Mock.Get(root.Renderer); - renderer.ResetCalls(); + renderer.Invocations.Clear(); ((SolidColorBrush)target.Background).Color = Colors.Green; @@ -54,7 +54,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); var renderer = Mock.Get(root.Renderer); - renderer.ResetCalls(); + renderer.Invocations.Clear(); ((SolidColorBrush)target.Foreground).Color = Colors.Green; diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs index 653a51232b..97b6b9021a 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; namespace Avalonia.DesignerSupport.TestApp @@ -13,5 +9,12 @@ namespace Avalonia.DesignerSupport.TestApp { AvaloniaXamlLoader.Load(this); } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + desktop.MainWindow = new MainWindow(); + base.OnFrameworkInitializationCompleted(); + } } } diff --git a/tests/Avalonia.DesignerSupport.TestApp/Program.cs b/tests/Avalonia.DesignerSupport.TestApp/Program.cs index c7c60a4eca..b8428511b3 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/Program.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/Program.cs @@ -1,24 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Avalonia.Controls; - -namespace Avalonia.DesignerSupport.TestApp +namespace Avalonia.DesignerSupport.TestApp { static class Program { /// /// The main entry point for the application. /// - static void Main() - { - BuildAvaloniaApp().Start(); - } + public static int Main(string[] args) + => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); - private static AppBuilder BuildAvaloniaApp() - { - return AppBuilder.Configure().UsePlatformDetect(); - } + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure().UsePlatformDetect(); } } diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs index 44a5af94b9..a8aa0bbf0e 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs @@ -112,7 +112,7 @@ namespace Avalonia.Layout.UnitTests root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); - target.ResetCalls(); + target.Invocations.Clear(); control.InvalidateMeasure(); control.InvalidateMeasure(); @@ -133,7 +133,7 @@ namespace Avalonia.Layout.UnitTests root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); - target.ResetCalls(); + target.Invocations.Clear(); control.InvalidateArrange(); control.InvalidateArrange(); @@ -163,7 +163,7 @@ namespace Avalonia.Layout.UnitTests Assert.False(control.IsMeasureValid); Assert.True(root.IsMeasureValid); - target.ResetCalls(); + target.Invocations.Clear(); root.Child = control; diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs index 504e3fa585..f1cec24516 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs @@ -15,7 +15,9 @@ namespace Avalonia.Layout.UnitTests [Fact] public async Task EffectiveViewportChanged_Not_Raised_When_Control_Added_To_Tree() { +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously await RunOnUIThread.Execute(async () => +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { var root = CreateRoot(); var target = new Canvas(); diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 0c7b966f29..b03ae00cfe 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -460,7 +460,7 @@ namespace Avalonia.LeakTests AttachShowAndDetachContextMenu(window); - Mock.Get(window.PlatformImpl).ResetCalls(); + Mock.Get(window.PlatformImpl).Invocations.Clear(); dotMemory.Check(memory => Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); dotMemory.Check(memory => @@ -505,7 +505,7 @@ namespace Avalonia.LeakTests BuildAndShowContextMenu(window); BuildAndShowContextMenu(window); - Mock.Get(window.PlatformImpl).ResetCalls(); + Mock.Get(window.PlatformImpl).Invocations.Clear(); dotMemory.Check(memory => Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); dotMemory.Check(memory => @@ -601,8 +601,9 @@ namespace Avalonia.LeakTests { public bool DrawFps { get; set; } public bool DrawDirtyRects { get; set; } +#pragma warning disable CS0067 public event EventHandler SceneInvalidated; - +#pragma warning restore CS0067 public void AddDirty(IVisual visual) { } diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs index 0be3bbbb9f..0028502eeb 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs @@ -168,10 +168,23 @@ namespace Avalonia.Markup.UnitTests.Parsers private class Test : INotifyDataErrorInfo { + private string _dataValidationError; + public bool Foo { get; set; } public object Bar { get; set; } - public string DataValidationError { get; set; } + public string DataValidationError + { + get => _dataValidationError; + set + { + if (value == _dataValidationError) + return; + _dataValidationError = value; + ErrorsChanged? + .Invoke(this, new DataErrorsChangedEventArgs(nameof(DataValidationError))); + } + } public bool HasErrors => !string.IsNullOrWhiteSpace(DataValidationError); public event EventHandler ErrorsChanged; diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index d52539c371..b629304b77 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs @@ -102,7 +102,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media [InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgba8888)] public void WriteableBitmapShouldBeUsable(PixelFormat fmt) { +#pragma warning disable CS0618 // Type or member is obsolete var writeableBitmap = new WriteableBitmap(new PixelSize(256, 256), new Vector(96, 96), fmt); +#pragma warning restore CS0618 // Type or member is obsolete var data = new int[256 * 256]; for (int y = 0; y < 256; y++) diff --git a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs index 84b0d09b61..d91d1fab9e 100644 --- a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs @@ -108,7 +108,7 @@ namespace Avalonia.Styling.UnitTests var target = new ResourceDictionary { { "foo", "bar" } }; ((IResourceProvider)target).AddOwner(host.Object); - host.ResetCalls(); + host.Invocations.Clear(); ((IResourceProvider)target).RemoveOwner(host.Object); host.Verify(x => x.NotifyHostedResourcesChanged(It.IsAny())); @@ -120,7 +120,7 @@ namespace Avalonia.Styling.UnitTests var host = new Mock(); var target = new ResourceDictionary(host.Object); - host.ResetCalls(); + host.Invocations.Clear(); target.Add("foo", "bar"); host.Verify(x => x.NotifyHostedResourcesChanged(It.IsAny())); @@ -132,7 +132,7 @@ namespace Avalonia.Styling.UnitTests var host = new Mock(); var target = new ResourceDictionary(host.Object); - host.ResetCalls(); + host.Invocations.Clear(); target.MergedDictionaries.Add(new ResourceDictionary { { "foo", "bar" }, @@ -149,7 +149,7 @@ namespace Avalonia.Styling.UnitTests var host = new Mock(); var target = new ResourceDictionary(host.Object); - host.ResetCalls(); + host.Invocations.Clear(); target.MergedDictionaries.Add(new ResourceDictionary()); host.Verify( @@ -169,7 +169,7 @@ namespace Avalonia.Styling.UnitTests } }; - host.ResetCalls(); + host.Invocations.Clear(); target.MergedDictionaries.RemoveAt(0); host.Verify( @@ -189,7 +189,7 @@ namespace Avalonia.Styling.UnitTests } }; - host.ResetCalls(); + host.Invocations.Clear(); ((IResourceDictionary)target.MergedDictionaries[0]).Add("foo", "bar"); host.Verify( diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs index 8ae556cc6f..d011c16ebb 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs @@ -1,12 +1,7 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; -using Avalonia.Collections; using Avalonia.Controls; -using Avalonia.Data; using Avalonia.LogicalTree; using Xunit; @@ -85,76 +80,6 @@ namespace Avalonia.Styling.UnitTests get => Parent; set => ((ISetLogicalParent)this).SetParent(value); } - - public void ClearValue(AvaloniaProperty property) - { - throw new NotImplementedException(); - } - - public void ClearValue(AvaloniaProperty property) - { - throw new NotImplementedException(); - } - - public void AddInheritanceChild(IAvaloniaObject child) - { - throw new NotImplementedException(); - } - - public void RemoveInheritanceChild(IAvaloniaObject child) - { - throw new NotImplementedException(); - } - - public void InheritanceParentChanged(StyledPropertyBase property, IAvaloniaObject oldParent, IAvaloniaObject newParent) - { - throw new NotImplementedException(); - } - - public void InheritedPropertyChanged(AvaloniaProperty property, Optional oldValue, Optional newValue) - { - throw new NotImplementedException(); - } - - public void ClearValue(StyledPropertyBase property) - { - throw new NotImplementedException(); - } - - public void ClearValue(DirectPropertyBase property) - { - throw new NotImplementedException(); - } - - public T GetValue(StyledPropertyBase property) - { - throw new NotImplementedException(); - } - - public T GetValue(DirectPropertyBase property) - { - throw new NotImplementedException(); - } - - public void SetValue(StyledPropertyBase property, T value, BindingPriority priority = BindingPriority.LocalValue) - { - throw new NotImplementedException(); - } - - public void SetValue(DirectPropertyBase property, T value) - { - throw new NotImplementedException(); - } - - public IDisposable Bind(StyledPropertyBase property, IObservable> source, BindingPriority priority = BindingPriority.LocalValue) - { - throw new NotImplementedException(); - } - - public IDisposable Bind(DirectPropertyBase property, IObservable> source) - { - throw new NotImplementedException(); - } } public class TestLogical1 : TestLogical diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs index 81485f0345..aacf2ce223 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs @@ -1,11 +1,7 @@ -using System; using System.Linq; -using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; -using Avalonia.Collections; using Avalonia.Controls; -using Avalonia.Data; using Avalonia.LogicalTree; using Xunit; @@ -115,76 +111,6 @@ namespace Avalonia.Styling.UnitTests get => Parent; set => ((ISetLogicalParent)this).SetParent(value); } - - public void ClearValue(AvaloniaProperty property) - { - throw new NotImplementedException(); - } - - public void ClearValue(AvaloniaProperty property) - { - throw new NotImplementedException(); - } - - public void AddInheritanceChild(IAvaloniaObject child) - { - throw new NotImplementedException(); - } - - public void RemoveInheritanceChild(IAvaloniaObject child) - { - throw new NotImplementedException(); - } - - public void InheritanceParentChanged(StyledPropertyBase property, IAvaloniaObject oldParent, IAvaloniaObject newParent) - { - throw new NotImplementedException(); - } - - public void InheritedPropertyChanged(AvaloniaProperty property, Optional oldValue, Optional newValue) - { - throw new NotImplementedException(); - } - - public void ClearValue(StyledPropertyBase property) - { - throw new NotImplementedException(); - } - - public void ClearValue(DirectPropertyBase property) - { - throw new NotImplementedException(); - } - - public T GetValue(StyledPropertyBase property) - { - throw new NotImplementedException(); - } - - public T GetValue(DirectPropertyBase property) - { - throw new NotImplementedException(); - } - - public void SetValue(StyledPropertyBase property, T value, BindingPriority priority = BindingPriority.LocalValue) - { - throw new NotImplementedException(); - } - - public void SetValue(DirectPropertyBase property, T value) - { - throw new NotImplementedException(); - } - - public IDisposable Bind(StyledPropertyBase property, IObservable> source, BindingPriority priority = BindingPriority.LocalValue) - { - throw new NotImplementedException(); - } - - public IDisposable Bind(DirectPropertyBase property, IObservable> source) - { - throw new NotImplementedException(); - } } public class TestLogical1 : TestLogical diff --git a/tests/Avalonia.Styling.UnitTests/StyleTests.cs b/tests/Avalonia.Styling.UnitTests/StyleTests.cs index 3e2bd68cf0..df94887340 100644 --- a/tests/Avalonia.Styling.UnitTests/StyleTests.cs +++ b/tests/Avalonia.Styling.UnitTests/StyleTests.cs @@ -443,7 +443,7 @@ namespace Avalonia.Styling.UnitTests var resources = new Mock(); target.Resources = resources.Object; - host.ResetCalls(); + host.Invocations.Clear(); ((IResourceProvider)target).AddOwner(host.Object); resources.Verify(x => x.AddOwner(host.Object), Times.Once); } diff --git a/tests/Avalonia.Styling.UnitTests/StylesTests.cs b/tests/Avalonia.Styling.UnitTests/StylesTests.cs index 9d3704c91d..88ab124e01 100644 --- a/tests/Avalonia.Styling.UnitTests/StylesTests.cs +++ b/tests/Avalonia.Styling.UnitTests/StylesTests.cs @@ -15,7 +15,7 @@ namespace Avalonia.Styling.UnitTests var style = new Mock(); var rp = style.As(); - host.ResetCalls(); + host.Invocations.Clear(); target.Add(style.Object); rp.Verify(x => x.AddOwner(host.Object)); @@ -29,7 +29,7 @@ namespace Avalonia.Styling.UnitTests var style = new Mock(); var rp = style.As(); - host.ResetCalls(); + host.Invocations.Clear(); target.Add(style.Object); target.Remove(style.Object); @@ -58,7 +58,7 @@ namespace Avalonia.Styling.UnitTests var resources = new Mock(); target.Resources = resources.Object; - host.ResetCalls(); + host.Invocations.Clear(); ((IResourceProvider)target).AddOwner(host.Object); resources.Verify(x => x.AddOwner(host.Object), Times.Once); } @@ -87,7 +87,7 @@ namespace Avalonia.Styling.UnitTests var resourceProvider = style.As(); target.Add(style.Object); - host.ResetCalls(); + host.Invocations.Clear(); ((IResourceProvider)target).AddOwner(host.Object); resourceProvider.Verify(x => x.AddOwner(host.Object), Times.Once); } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index bfcc341eed..e58eea42d8 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -756,7 +756,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering private void IgnoreFirstFrame(IRenderLoopTask task, Mock sceneBuilder) { RunFrame(task); - sceneBuilder.ResetCalls(); + sceneBuilder.Invocations.Clear(); } private void RunFrame(IRenderLoopTask task) diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs index 97ad608346..447a68aa69 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs @@ -173,7 +173,7 @@ namespace Avalonia.Visuals.UnitTests }; root.Child = child; - renderer.ResetCalls(); + renderer.Invocations.Clear(); root.Child = null; renderer.Verify(x => x.AddDirty(child)); From 5a4a172652444c1e39d3da18ebbd76e8af14528f Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 17 Oct 2020 10:43:04 +0200 Subject: [PATCH 210/492] fix NU5040 --- azure-pipelines.yml | 4 ++++ build/Assets/Icon.png | Bin 0 -> 14349 bytes build/SharedVersion.props | 6 +++++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 build/Assets/Icon.png diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 721a0415f4..e67fa14c57 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,6 +31,8 @@ jobs: condition: not(canceled()) - job: macOS + variables: + SolutionDir: '$(Build.SourcesDirectory)' pool: vmImage: 'macOS-10.14' steps: @@ -97,6 +99,8 @@ jobs: - job: Windows pool: vmImage: 'windows-2019' + variables: + SolutionDir: '$(Build.SourcesDirectory)' steps: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 3.1.401' diff --git a/build/Assets/Icon.png b/build/Assets/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..41a2a618fb02e4cb7f6a15caf572b693bfe1ebb1 GIT binary patch literal 14349 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 latest MIT - https://avatars2.githubusercontent.com/u/14075148?s=200 + Icon.png Avalonia is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS. avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin https://github.com/AvaloniaUI/Avalonia/releases git + + + + From c364ca6f9bc268a4dfc336a8e39f6d826df6721e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 17 Oct 2020 10:44:27 +0200 Subject: [PATCH 211/492] fix NU5128 --- build/SourceLink.props | 4 ++-- dirs.proj | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build/SourceLink.props b/build/SourceLink.props index 0c9b6a34f8..e27727c9e8 100644 --- a/build/SourceLink.props +++ b/build/SourceLink.props @@ -1,5 +1,5 @@ - + - \ No newline at end of file + diff --git a/dirs.proj b/dirs.proj index bf32abef72..594f2c22d3 100644 --- a/dirs.proj +++ b/dirs.proj @@ -21,6 +21,7 @@ + From a2296478ffc11106d635370d8e9761e84abc2dbc Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 17 Oct 2020 11:46:59 +0200 Subject: [PATCH 212/492] Supress warning MSBXXXX --- .../Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj index 0d0dd98a77..0e11ee3b92 100644 --- a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj +++ b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj @@ -2,7 +2,7 @@ net461 true - true + true true true Avalonia.Win32.Interoperability From 4403d94c5c26d50c7910dc580cdd7b313a85e1d1 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 17 Oct 2020 11:51:05 +0200 Subject: [PATCH 213/492] Change boxShadow to boxShadows --- src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 9cf4e62030..d6e88a7507 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -63,13 +63,13 @@ namespace Avalonia.Platform /// The brush used to fill the rectangle, or null for no fill. /// The pen used to stroke the rectangle, or null for no stroke. /// The rectangle bounds. - /// Box shadow effect parameters + /// Box shadow effect parameters /// /// The brush and the pen can both be null. If the brush is null, then no fill is performed. /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. /// void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, - BoxShadows boxShadow = default); + BoxShadows boxShadows = default); /// /// Draws text. From 77cd5153329cde4763a55eae30f1718f47c3c535 Mon Sep 17 00:00:00 2001 From: mj Date: Sun, 18 Oct 2020 09:10:33 -0600 Subject: [PATCH 214/492] Fixed returns value; Issue 4883 --- src/Avalonia.Controls/ItemsControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 3aec06e4eb..4dc8aec6f3 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -169,7 +169,7 @@ namespace Avalonia.Controls /// /// The collection. /// The index. - /// The index of the item or -1 if the item was not found. + /// The item at the given index or null if the index is out of bounds. protected static object ElementAt(IEnumerable items, int index) { if (index != -1 && index < items.Count()) From e5df3970e888ddcb62380f8cd3f08a9aaadddc85 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 20 Oct 2020 01:54:03 +0300 Subject: [PATCH 215/492] Workaround for corert --- .../XamlCompilerTaskExecutor.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 7c4a9df0f4..6b01af2ede 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -345,6 +345,20 @@ namespace Avalonia.Build.Tasks } res.Remove(); } + + + // Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds + int dupeCounter = 1; + foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name)) + { + if (grp.Count() > 1) + { + foreach (var dupe in grp) + dupe.Name += "_dup" + dupeCounter++; + } + } + + return true; } From 1aa50361bb1a27821a407a26df8f4216cdad00c4 Mon Sep 17 00:00:00 2001 From: bmello4688 Date: Tue, 20 Oct 2020 08:55:17 -0400 Subject: [PATCH 216/492] add XmlnsPrefix which is in WPF --- .../Metadata/XmlnsPrefixAttribute.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/Avalonia.Base/Metadata/XmlnsPrefixAttribute.cs diff --git a/src/Avalonia.Base/Metadata/XmlnsPrefixAttribute.cs b/src/Avalonia.Base/Metadata/XmlnsPrefixAttribute.cs new file mode 100644 index 0000000000..0f091d540a --- /dev/null +++ b/src/Avalonia.Base/Metadata/XmlnsPrefixAttribute.cs @@ -0,0 +1,30 @@ +using System; + +namespace Avalonia.Metadata +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class XmlnsPrefixAttribute : Attribute + { + /// + /// Constructor + /// + /// XML namespce + /// recommended prefix + public XmlnsPrefixAttribute(string xmlNamespace, string prefix) + { + XmlNamespace = xmlNamespace ?? throw new ArgumentNullException(nameof(xmlNamespace)); + + Prefix = prefix ?? throw new ArgumentNullException(nameof(prefix)); + } + + /// + /// XML Namespace + /// + public string XmlNamespace { get; } + + /// + /// New Xml Namespace + /// + public string Prefix { get; } + } +} From 1497882cf64941e1f1b21957b1e490290637bdb2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 20 Oct 2020 16:54:48 +0300 Subject: [PATCH 217/492] Enable light dismiss overlay for popups --- src/Avalonia.Controls/Primitives/VisualLayerManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index d8d3450c6f..457a9ea282 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -67,8 +67,6 @@ namespace Avalonia.Controls.Primitives { get { - if (IsPopup) - return null; var rv = FindLayer(); if (rv == null) { From 32b8cc811c087ddd8cda77668a7babed025b5093 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 20 Oct 2020 15:46:06 +0100 Subject: [PATCH 218/492] pass surfaceorigin to FboSkiaSurface. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 7 ++++++- src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs | 2 +- src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs | 2 ++ src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs | 4 ++-- src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs | 4 ++++ src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs | 4 ++-- src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs | 3 ++- src/Skia/Avalonia.Skia/PlatformRenderInterface.cs | 2 +- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 6 +++--- 9 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index cbc896813f..dac284ce1a 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -36,6 +36,7 @@ namespace Avalonia.Skia private readonly SKPaint _fillPaint = new SKPaint(); private readonly SKPaint _boxShadowPaint = new SKPaint(); private static SKShader s_acrylicNoiseShader; + private readonly GRSurfaceOrigin _surfaceOrigin; /// /// Context create info. @@ -76,6 +77,8 @@ namespace Avalonia.Skia /// Skia GPU provider context (optional) /// public ISkiaGpu Gpu; + + public ISkiaGpuRenderSession CurrentSession; } /// @@ -96,6 +99,8 @@ namespace Avalonia.Skia Surface = createInfo.Surface; Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas; + _surfaceOrigin = createInfo.CurrentSession?.SurfaceOrigin ?? GRSurfaceOrigin.TopLeft; + if (Canvas == null) { throw new ArgumentException("Invalid create info - no Canvas provided", nameof(createInfo)); @@ -972,7 +977,7 @@ namespace Avalonia.Skia Gpu = _gpu }; - return new SurfaceRenderTarget(createInfo); + return new SurfaceRenderTarget(createInfo, _surfaceOrigin); } /// diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index 869c261f1b..7442f90ecf 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -21,7 +21,7 @@ namespace Avalonia.Skia /// Creates an offscreen render target surface /// /// size in pixels - ISkiaSurface TryCreateSurface(PixelSize size); + ISkiaSurface TryCreateSurface(PixelSize size, GRSurfaceOrigin surfaceOrigin); } public interface ISkiaSurface : IDisposable diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs index a4e2bfed52..5faa6a41ec 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs @@ -22,5 +22,7 @@ namespace Avalonia.Skia /// Scaling factor. /// double ScaleFactor { get; } + + GRSurfaceOrigin SurfaceOrigin { get; } } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs index 24464c39d9..8ab275df63 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -14,7 +14,7 @@ namespace Avalonia.Skia private int _texture; private static readonly bool[] TrueFalse = new[] { true, false }; - public FboSkiaSurface(GRContext grContext, IGlContext glContext, PixelSize pixelSize) + public FboSkiaSurface(GRContext grContext, IGlContext glContext, PixelSize pixelSize, GRSurfaceOrigin surfaceOrigin) { _grContext = grContext; _glContext = glContext; @@ -93,7 +93,7 @@ namespace Avalonia.Skia var target = new GRBackendRenderTarget(pixelSize.Width, pixelSize.Height, 0, 8, new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); Surface = SKSurface.Create(_grContext, target, - GRSurfaceOrigin.TopLeft, SKColorType.Rgba8888); + surfaceOrigin, SKColorType.Rgba8888); CanBlit = gl.BlitFramebuffer != null; } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index e109922a3f..b4c5619c85 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -39,6 +39,8 @@ namespace Avalonia.Skia _backendRenderTarget = backendRenderTarget; _surface = surface; _glSession = glSession; + + SurfaceOrigin = glSession.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft; } public void Dispose() { @@ -48,6 +50,8 @@ namespace Avalonia.Skia GrContext.Flush(); _glSession.Dispose(); } + + public GRSurfaceOrigin SurfaceOrigin { get; } public GRContext GrContext { get; } public SKSurface SkSurface => _surface; diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index c02d813e24..02e3766d40 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -47,7 +47,7 @@ namespace Avalonia.Skia return null; } - public ISkiaSurface TryCreateSurface(PixelSize size) + public ISkiaSurface TryCreateSurface(PixelSize size, GRSurfaceOrigin surfaceOrigin) { // Only windows platform needs our FBO trickery if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -62,7 +62,7 @@ namespace Avalonia.Skia return null; try { - var surface = new FboSkiaSurface(_grContext, _glContext, size); + var surface = new FboSkiaSurface(_grContext, _glContext, size, surfaceOrigin); _canCreateSurfaces = true; return surface; } diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs index 9992c9ba8c..6626546c0c 100644 --- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs @@ -33,7 +33,8 @@ namespace Avalonia.Skia Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = true, - Gpu = _skiaGpu + Gpu = _skiaGpu, + CurrentSession = session }; return new DrawingContextImpl(nfo, session); diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index d6f76a2c20..6048482110 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -127,7 +127,7 @@ namespace Avalonia.Skia DisableTextLcdRendering = false }; - return new SurfaceRenderTarget(createInfo); + return new SurfaceRenderTarget(createInfo, GRSurfaceOrigin.TopLeft); } /// diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 61b599a731..905c9160a7 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -42,7 +42,7 @@ namespace Avalonia.Skia /// Create new surface render target. /// /// Create info. - public SurfaceRenderTarget(CreateInfo createInfo) + public SurfaceRenderTarget(CreateInfo createInfo, GRSurfaceOrigin surfaceOrigin) { PixelSize = new PixelSize(createInfo.Width, createInfo.Height); Dpi = createInfo.Dpi; @@ -51,7 +51,7 @@ namespace Avalonia.Skia _grContext = createInfo.GrContext; _gpu = createInfo.Gpu; - _surface = _gpu?.TryCreateSurface(PixelSize); + _surface = _gpu?.TryCreateSurface(PixelSize, surfaceOrigin); if (_surface == null) _surface = new SkiaSurfaceWrapper(CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, createInfo.Format)); @@ -100,7 +100,7 @@ namespace Avalonia.Skia VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = _disableLcdRendering, GrContext = _grContext, - Gpu = _gpu + Gpu = _gpu, }; return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++)); From d218e2be2074a39cc0348c90c2cfe7334fdab73b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 20 Oct 2020 20:32:32 +0100 Subject: [PATCH 219/492] dont save surfaceorigin as a field of drawing context. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index dac284ce1a..136b14140e 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -36,7 +36,7 @@ namespace Avalonia.Skia private readonly SKPaint _fillPaint = new SKPaint(); private readonly SKPaint _boxShadowPaint = new SKPaint(); private static SKShader s_acrylicNoiseShader; - private readonly GRSurfaceOrigin _surfaceOrigin; + private readonly ISkiaGpuRenderSession _session; /// /// Context create info. @@ -99,7 +99,7 @@ namespace Avalonia.Skia Surface = createInfo.Surface; Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas; - _surfaceOrigin = createInfo.CurrentSession?.SurfaceOrigin ?? GRSurfaceOrigin.TopLeft; + _session = createInfo.CurrentSession; if (Canvas == null) { @@ -977,7 +977,7 @@ namespace Avalonia.Skia Gpu = _gpu }; - return new SurfaceRenderTarget(createInfo, _surfaceOrigin); + return new SurfaceRenderTarget(createInfo, _session?.SurfaceOrigin ?? GRSurfaceOrigin.TopLeft); } /// From ce08964f0371452ce8301cd2206d6e32f295982f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 20 Oct 2020 20:34:51 +0100 Subject: [PATCH 220/492] refactor how surface origin is passed. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 5 +++-- src/Skia/Avalonia.Skia/PlatformRenderInterface.cs | 2 +- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 136b14140e..98692b7483 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -974,10 +974,11 @@ namespace Avalonia.Skia Format = format, DisableTextLcdRendering = !_canTextUseLcdRendering, GrContext = _grContext, - Gpu = _gpu + Gpu = _gpu, + SurfaceOrigin = _session?.SurfaceOrigin ?? GRSurfaceOrigin.TopLeft }; - return new SurfaceRenderTarget(createInfo, _session?.SurfaceOrigin ?? GRSurfaceOrigin.TopLeft); + return new SurfaceRenderTarget(createInfo); } /// diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 6048482110..d6f76a2c20 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -127,7 +127,7 @@ namespace Avalonia.Skia DisableTextLcdRendering = false }; - return new SurfaceRenderTarget(createInfo, GRSurfaceOrigin.TopLeft); + return new SurfaceRenderTarget(createInfo); } /// diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 905c9160a7..d3d4515689 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -42,7 +42,7 @@ namespace Avalonia.Skia /// Create new surface render target. /// /// Create info. - public SurfaceRenderTarget(CreateInfo createInfo, GRSurfaceOrigin surfaceOrigin) + public SurfaceRenderTarget(CreateInfo createInfo) { PixelSize = new PixelSize(createInfo.Width, createInfo.Height); Dpi = createInfo.Dpi; @@ -51,7 +51,7 @@ namespace Avalonia.Skia _grContext = createInfo.GrContext; _gpu = createInfo.Gpu; - _surface = _gpu?.TryCreateSurface(PixelSize, surfaceOrigin); + _surface = _gpu?.TryCreateSurface(PixelSize, createInfo.SurfaceOrigin); if (_surface == null) _surface = new SkiaSurfaceWrapper(CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, createInfo.Format)); @@ -218,6 +218,8 @@ namespace Avalonia.Skia public GRContext GrContext; public ISkiaGpu Gpu; + + public GRSurfaceOrigin SurfaceOrigin; } } } From e04fe994fb9c032992c07380749933b4f8b6dca8 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Wed, 21 Oct 2020 00:06:10 +0300 Subject: [PATCH 221/492] do not make any layout in ItemVirtualizer while not attached to visual tree --- src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index bdc68bee7e..7d50ef7d33 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -512,6 +512,13 @@ namespace Avalonia.Controls.Presenters var generator = Owner.ItemContainerGenerator; var newOffset = -1.0; + //better not trigger any container generation/recycle while or layout stuff + //before panel is attached/visible + if (!panel.IsAttachedToVisualTree) + { + return null; + } + if (!panel.IsMeasureValid && panel.PreviousMeasure.HasValue) { //before any kind of scrolling we need to make sure panel measure is valid From a93ecd1c66d0126409fdfaea5cfc73124fed069d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 21 Oct 2020 12:47:33 +0300 Subject: [PATCH 222/492] [X11] Handle PointerLeave through XI2 --- src/Avalonia.X11/XI2Manager.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index 742973e0da..9349824525 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/src/Avalonia.X11/XI2Manager.cs @@ -162,7 +162,9 @@ namespace Avalonia.X11 | XEventMask.Button4MotionMask | XEventMask.Button5MotionMask | XEventMask.ButtonPressMask - | XEventMask.ButtonReleaseMask; + | XEventMask.ButtonReleaseMask + | XEventMask.LeaveWindowMask + | XEventMask.EnterWindowMask; } public void OnWindowDestroyed(IntPtr xid) => _clients.Remove(xid); @@ -201,6 +203,12 @@ namespace Avalonia.X11 return; } + if (ev.Type == XiEventType.XI_Leave) + { + client.ScheduleXI2Input(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot, + RawPointerEventType.LeaveWindow, ev.Position, ev.Modifiers)); + } + if (_multitouch && ev.Emulated) return; From fb7b7a002ce8f46a924ea307a8b6710be1de0670 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 21 Oct 2020 13:04:05 +0300 Subject: [PATCH 223/492] Fixed initial pointerover at (0, 0) when window is just shown --- src/Avalonia.Input/MouseDevice.cs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index cec5029c18..5c63546f5d 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -19,6 +19,7 @@ namespace Avalonia.Input private readonly Pointer _pointer; private bool _disposed; + private PixelPoint? _position; public MouseDevice(Pointer? pointer = null) { @@ -39,10 +40,11 @@ namespace Avalonia.Input /// /// Gets the mouse position, in screen coordinates. /// + [Obsolete("Use events instead")] public PixelPoint Position { - get; - protected set; + get => _position ?? new PixelPoint(-1, -1); + protected set => _position = value; } /// @@ -91,7 +93,16 @@ namespace Avalonia.Input public void SceneInvalidated(IInputRoot root, Rect rect) { - var clientPoint = root.PointToClient(Position); + // Pointer is outside of the target area + if (_position == null ) + { + if (root.PointerOverElement != null) + ClearPointerOver(this, 0, root, PointerPointProperties.None, KeyModifiers.None); + return; + } + + + var clientPoint = root.PointToClient(_position.Value); if (rect.Contains(clientPoint)) { @@ -132,7 +143,7 @@ namespace Avalonia.Input if(mouse._disposed) return; - Position = e.Root.PointToScreen(e.Position); + _position = e.Root.PointToScreen(e.Position); var props = CreateProperties(e); var keyModifiers = KeyModifiersUtils.ConvertToKey(e.InputModifiers); switch (e.Type) @@ -176,6 +187,7 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); + _position = null; ClearPointerOver(this, timestamp, root, properties, inputModifiers); } From aa48b5733e14be820a01c452e15c947144cb81bc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Oct 2020 12:52:23 +0200 Subject: [PATCH 224/492] Replaced LogToDebug with LogToTrace. Calls to `System.Diagnostics.Debug` are removed in release builds meaning nothing was getting logged. Fixes #4901 --- samples/BindingDemo/App.xaml.cs | 2 +- samples/ControlCatalog.Desktop/Program.cs | 2 +- samples/ControlCatalog.NetCore/Program.cs | 2 +- samples/RenderDemo/App.xaml.cs | 2 +- samples/Sandbox/Program.cs | 2 +- samples/VirtualizationDemo/Program.cs | 2 +- src/Avalonia.Base/ApiCompatBaseline.txt | 3 ++- .../{DebugLogSink.cs => TraceLogSink.cs} | 14 +++++++------- src/Avalonia.Controls/LoggingExtensions.cs | 19 +++++++++++++++---- 9 files changed, 30 insertions(+), 18 deletions(-) rename src/Avalonia.Base/Logging/{DebugLogSink.cs => TraceLogSink.cs} (92%) diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs index 5c38ab8305..eb2da03a7e 100644 --- a/samples/BindingDemo/App.xaml.cs +++ b/samples/BindingDemo/App.xaml.cs @@ -26,6 +26,6 @@ namespace BindingDemo => AppBuilder.Configure() .UsePlatformDetect() .UseReactiveUI() - .LogToDebug(); + .LogToTrace(); } } diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index e0cc8cc904..5af646b180 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -18,7 +18,7 @@ namespace ControlCatalog /// public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() - .LogToDebug() + .LogToTrace() .UsePlatformDetect() .UseReactiveUI(); diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 142736a0bb..c1fcdd6216 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -121,7 +121,7 @@ namespace ControlCatalog.NetCore .UseSkia() .UseReactiveUI() .UseManagedSystemDialogs() - .LogToDebug(); + .LogToTrace(); static void SilenceConsole() { diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index 2247fd7c5a..e6ec963d75 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -33,6 +33,6 @@ namespace RenderDemo }) .UsePlatformDetect() .UseReactiveUI() - .LogToDebug(); + .LogToTrace(); } } diff --git a/samples/Sandbox/Program.cs b/samples/Sandbox/Program.cs index 4d7eda8d9f..7d41a8b8c0 100644 --- a/samples/Sandbox/Program.cs +++ b/samples/Sandbox/Program.cs @@ -10,7 +10,7 @@ namespace Sandbox AppBuilder.Configure() .UsePlatformDetect() .UseReactiveUI() - .LogToDebug() + .LogToTrace() .StartWithClassicDesktopLifetime(args); } } diff --git a/samples/VirtualizationDemo/Program.cs b/samples/VirtualizationDemo/Program.cs index c23bfd7ad9..46c05f74b2 100644 --- a/samples/VirtualizationDemo/Program.cs +++ b/samples/VirtualizationDemo/Program.cs @@ -9,7 +9,7 @@ namespace VirtualizationDemo => AppBuilder.Configure() .UsePlatformDetect() .UseReactiveUI() - .LogToDebug(); + .LogToTrace(); public static int Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt index 4668a572c5..5d19373a92 100644 --- a/src/Avalonia.Base/ApiCompatBaseline.txt +++ b/src/Avalonia.Base/ApiCompatBaseline.txt @@ -1,3 +1,4 @@ Compat issues with assembly Avalonia.Base: CannotAddAbstractMembers : Member 'protected System.IObservable Avalonia.AvaloniaProperty.GetChanged()' is abstract in the implementation but is missing in the contract. -Total Issues: 1 +TypesMustExist : Type 'Avalonia.Logging.DebugLogSink' does not exist in the implementation but it does exist in the contract. +Total Issues: 2 diff --git a/src/Avalonia.Base/Logging/DebugLogSink.cs b/src/Avalonia.Base/Logging/TraceLogSink.cs similarity index 92% rename from src/Avalonia.Base/Logging/DebugLogSink.cs rename to src/Avalonia.Base/Logging/TraceLogSink.cs index 3695afa860..f597844378 100644 --- a/src/Avalonia.Base/Logging/DebugLogSink.cs +++ b/src/Avalonia.Base/Logging/TraceLogSink.cs @@ -6,12 +6,12 @@ using Avalonia.Utilities; namespace Avalonia.Logging { - public class DebugLogSink : ILogSink + public class TraceLogSink : ILogSink { private readonly LogEventLevel _level; private readonly IList _areas; - public DebugLogSink( + public TraceLogSink( LogEventLevel minimumLevel, IList areas = null) { @@ -28,7 +28,7 @@ namespace Avalonia.Logging { if (IsEnabled(level, area)) { - Debug.WriteLine(Format(area, messageTemplate, source)); + Trace.WriteLine(Format(area, messageTemplate, source)); } } @@ -36,7 +36,7 @@ namespace Avalonia.Logging { if (IsEnabled(level, area)) { - Debug.WriteLine(Format(area, messageTemplate, source, propertyValue0)); + Trace.WriteLine(Format(area, messageTemplate, source, propertyValue0)); } } @@ -44,7 +44,7 @@ namespace Avalonia.Logging { if (IsEnabled(level, area)) { - Debug.WriteLine(Format(area, messageTemplate, source, propertyValue0, propertyValue1)); + Trace.WriteLine(Format(area, messageTemplate, source, propertyValue0, propertyValue1)); } } @@ -52,7 +52,7 @@ namespace Avalonia.Logging { if (IsEnabled(level, area)) { - Debug.WriteLine(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2)); + Trace.WriteLine(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2)); } } @@ -60,7 +60,7 @@ namespace Avalonia.Logging { if (IsEnabled(level, area)) { - Debug.WriteLine(Format(area, messageTemplate, source, propertyValues)); + Trace.WriteLine(Format(area, messageTemplate, source, propertyValues)); } } diff --git a/src/Avalonia.Controls/LoggingExtensions.cs b/src/Avalonia.Controls/LoggingExtensions.cs index 44e570bdfa..9eb3b140f6 100644 --- a/src/Avalonia.Controls/LoggingExtensions.cs +++ b/src/Avalonia.Controls/LoggingExtensions.cs @@ -1,25 +1,36 @@ -using Avalonia.Controls; +using System; +using Avalonia.Controls; using Avalonia.Logging; namespace Avalonia { public static class LoggingExtensions { + [Obsolete("Use LogToTrace")] + public static T LogToDebug( + this T builder, + LogEventLevel level = LogEventLevel.Warning, + params string[] areas) + where T : AppBuilderBase, new() + { + return LogToTrace(builder, level, areas); + } + /// - /// Logs Avalonia events to the sink. + /// Logs Avalonia events to the sink. /// /// The application class type. /// The app builder instance. /// The minimum level to log. /// The areas to log. Valid values are listed in . /// The app builder instance. - public static T LogToDebug( + public static T LogToTrace( this T builder, LogEventLevel level = LogEventLevel.Warning, params string[] areas) where T : AppBuilderBase, new() { - Logger.Sink = new DebugLogSink(level, areas); + Logger.Sink = new TraceLogSink(level, areas); return builder; } } From 4f6dd5a44a9da62446b91ed0f620b645bff00f60 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 21 Oct 2020 14:42:00 +0300 Subject: [PATCH 225/492] [X11] XI2 fix --- src/Avalonia.X11/XI2Manager.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index 9349824525..d0655d05c1 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/src/Avalonia.X11/XI2Manager.cs @@ -13,7 +13,8 @@ namespace Avalonia.X11 { XiEventType.XI_Motion, XiEventType.XI_ButtonPress, - XiEventType.XI_ButtonRelease + XiEventType.XI_ButtonRelease, + XiEventType.XI_Leave }; private static readonly XiEventType[] MultiTouchEventTypes = new XiEventType[] @@ -177,9 +178,10 @@ namespace Avalonia.X11 _pointerDevice.Update(changed->Classes, changed->NumClasses); } - + if ((xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion) - || (xev->evtype>=XiEventType.XI_TouchBegin&&xev->evtype<=XiEventType.XI_TouchEnd)) + || (xev->evtype >= XiEventType.XI_TouchBegin && xev->evtype <= XiEventType.XI_TouchEnd) + || xev->evtype == XiEventType.XI_Leave) { var dev = (XIDeviceEvent*)xev; if (_clients.TryGetValue(dev->EventWindow, out var client)) @@ -306,7 +308,7 @@ namespace Avalonia.X11 if (state.HasFlag(XModifierMask.Mod4Mask)) Modifiers |= RawInputModifiers.Meta; - if (ev->buttons.MaskLen > 0) + if (ev->buttons.MaskLen > 1 && Type != XiEventType.XI_Leave) { var buttons = ev->buttons.Mask; if (XIMaskIsSet(buttons, 1)) @@ -324,9 +326,11 @@ namespace Avalonia.X11 Valuators = new Dictionary(); Position = new Point(ev->event_x, ev->event_y); var values = ev->valuators.Values; - for (var c = 0; c < ev->valuators.MaskLen * 8; c++) - if (XIMaskIsSet(ev->valuators.Mask, c)) - Valuators[c] = *values++; + if(ev->valuators.Mask != null) + for (var c = 0; c < ev->valuators.MaskLen * 8; c++) + if (XIMaskIsSet(ev->valuators.Mask, c)) + Valuators[c] = *values++; + if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease) Button = ev->detail; Detail = ev->detail; From 5f3d81bbfcd33ad5bca32b188135355dcbde08c3 Mon Sep 17 00:00:00 2001 From: Mikhail Poliudov Date: Wed, 21 Oct 2020 19:10:25 +0700 Subject: [PATCH 226/492] 1. implemented Label control (again) 2. changed implementation of ControlPresenter to be same as WPF one. Added additional prebuild DataTemplate (for ContentPresenter) 3. added default style for Label control (both Default and Fluent themes) 4. Added sample to TextBox page (not sure needed other sample) 5. Added styles for Label in ControlCatalog/App.xaml --- samples/ControlCatalog/App.xaml | 10 +++ samples/ControlCatalog/Pages/TextBoxPage.xaml | 36 ++++----- src/Avalonia.Controls/Label.cs | 74 +++++++++++++++++++ .../Presenters/ContentPresenter.cs | 25 ++++++- .../Templates/FuncDataTemplate.cs | 28 ++++++- src/Avalonia.Themes.Default/DefaultTheme.xaml | 3 +- src/Avalonia.Themes.Default/Label.xaml | 17 +++++ src/Avalonia.Themes.Fluent/FluentTheme.xaml | 3 +- src/Avalonia.Themes.Fluent/Label.xaml | 17 +++++ 9 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 src/Avalonia.Controls/Label.cs create mode 100644 src/Avalonia.Themes.Default/Label.xaml create mode 100644 src/Avalonia.Themes.Fluent/Label.xaml diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 9bac320c79..6aad44c0d5 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -12,6 +12,16 @@ + + + diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 481a459159..8b07ac3f85 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -2,8 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.TextBoxPage"> - TextBox - A control into which the user can input text + + - + - - resm fonts - - - - - - - - res fonts - - - - - + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Controls/Label.cs b/src/Avalonia.Controls/Label.cs new file mode 100644 index 0000000000..76708b8f00 --- /dev/null +++ b/src/Avalonia.Controls/Label.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Interactivity; + +namespace Avalonia.Controls +{ + /// + /// Label control. Focuses on pointer click or access key press (Alt + accessKey) + /// + public class Label : ContentControl + { + /// + /// Defines the Direct property + /// + public static readonly DirectProperty TargetProperty = + AvaloniaProperty.RegisterDirect(nameof(Target), lbl => lbl.Target, (lbl, inp) => lbl.Target = inp); + + /// + /// Label focus target storage field + /// + private IInputElement _target; + + /// + /// Label focus Target + /// + public IInputElement Target + { + get => _target; + set => SetAndRaise(TargetProperty, ref _target, value); + } + + static Label() + { + AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler

4gnSxr zD!5z7t1Gm0V`bA%O1n!5Ub$$_OqGg*u52wm<`P)^3KjPl5Iq%EUm?q->>8haUBo0; zcp!;_VCa|6weHn)U1+50l~Gip`I#u}9Fy=^v9g8HMe3f37Y~+q|(l z`tWmJfMKIaSgY1@{Xm*FO(bv58RMpm>4rK$z%j5-Ha{j(647ZvxNWzhb74~8Q@Ew< zQP|gHVN~fc6SZ&AT>4LQU3x|Z{0Z6xY`3FUrliP7p65i9mp1aDVbtZrdIP2O1pOOG zAJ=d*+PRL215@&c-;%7myaUpGML&2?(6UDMhM&~K&mHWyMQfu2#!I#2u%TainpB># zZM%SQ2wAWY7S^mJO~-%QH%l|JPBs&F{|6&Z6`xIQw(=?iXKs7M;%?GOTdIAT;e-7T z*9Z<~r-6LsWy#*90$EaaJG->#AK2C7Pj-eVp3Y~PL+w%IC`iMf-e-z_gbI=5TdtNu z!qjK7jCRt*sAhUnTtyXr%4 zfbkFL_+Nn|7=TjuIs4h(rkeWcRkG!I_uMtI=k+AqYq~~YZ{yljyD!@u>qk2hZ?_l=v=++%&NH^z1S=O&OFGdZrc32eE+TZa~JA^_|$>hiiF#@tWz zT$4tT&XL95!tl!sEi=NI?4k|)G3-NSDe&++AEGQ)y&;4&OcdYhljy#7v@e&`7;gnJ zK!@BBT0g0at+WQINI0#sVAs)RO#T+!Pub*_!wF)2;~JPa%rm|{F*X4)c*K{FQCXkX zGpo7U<)nIHPyCGI_X~IVS*Ru*%x4@iU%^AZAcZ|~^<}o2cXO&{3LchGq~<$WT2Pzu z5P||mdqEwLc9O7-z})vM0iN|KtsQ;5&~5Xy3ol*Q)Q%qJIU;#<%5SSlb~M9 z=HY#Uwzpb@Dnx55)lM8jYUmMVGKptY93=v48?v>;>6AHsb*p4vfxqvOWpOE49*5d( z%kAvTuim3H9q)1p@DvUoLFYhofuUheVd%U2}o^yyzxxpx1-yAfA;c{M)q!}r( zm|;r>L(240kS7SF1Vt+g_=g-s@%{^B!e;a#imcn3v%jATbbY~VU^V^=l&4QF)6D;X z;AE{qxNv&w;Ol6Fia*7ENiC3!hP3OZ+hG(!C%J}yefqrxy6%v!EBB+~#xyEPK%{ro z+2o&b0!1q)nnJjb{p>zhIMPe3b~{X)cf@Jt6RDQ&IZ$&`%yCwYHv7Ni_~9YLnzwoYbDP zaFP91p!z;fSH4>)=$&2p(uTet24uSFTR?wps39f|uWYg|R_4m4Fp-`{eMDfU3ip}+ z0Zrl)F!0D3M;tJjffzra@Qb(GE{S%mMWxh_g(2FQT9uTk=pCCPJHY>x}%F@9}Zz^#u`OXJ|qLWj`50sc|z$Kl{8UYf=} z)PIt)7aKR+#fkpE((lk)rFc`OFSK1l|1(AVFY^)i5D#{3S&g4xz{HghjPB!3sF*Ul zmRvH2WN|OsSK1xmrommp76U?uodVS}CieevH?GhmYLm;gThMqas{7>3M>7bnUJ#Q( zHSV|a_T1W8Gd-$d@UX|*8YF-?)8jND_^Z3kkNJ*)`Ba+|@fjDFk2h{F0k2Zz`5FrPOy z{rI~$85XiWP$8qe?S-B>zFN+2@Y9gc#HkBsN&(dWj0l3a{RP&fA6Zr zQFQ=aOCn}yDv|D%j-Y~`2OI+UukSsT#>7(>ZeNEd$QZ6i@5z}x|CfvUkhxe8mu4SE z^wogpgJoXN=7UfPZWD_V9ZH~)0Bj-QIcp#hcGU9+-wb3DGOXc^K z5jh6}ym&*uC`i@JvC@Kd0@X~Mq-KyUXrPJyE^t_Rp$X4x=%nd;Q{3Voi8dn~BBVO_ z>GBTEe_1CmAi9Qb|I27p%P9wjpz$J_jEIU3C2~GGALkf{VeQ#w0ivBL>g;uYsdPeH z^K13(gJ}0s%|5Fl%7J9cs*embwLi5WpLL|&d2*|0L6&)u?VsLq8mz%$cXKa2e9Gpe zS51w7+T!w{hmR77o?)2#(%nv9LuM)^G6^j#^==IPHGGD&bH88GIG&D>&sH+!U;=~%R&7C9_O4V&dk(^{fxjF1_lQ9o5uPU^k?_K ziO=1Z*%QM0%jKS0 z{4Fjo@gO;${*o0_spYi6nAFB}8mwLYD9)%-fACOVL~&8-ki!@x%Xs6IxT}C`$?qzs zu2+pM)Ww6VzS&_OnIT*DPFyk%^jBv!mGgYFeLe3tYi)O&h5dhTpEsWSIuO~Ky2 zWj`$~t>UDgXVLQ8xd8sEbAN9vUse2d73ZYZdYsc*n21wH`C1)!+|=S-CJ@&n4t92` zBZ7kWce9^gK33sboO8bH#D93%Tv7H$hoe$-J(GhS)36a&vx^6XHx3)E#nF1itDw-j zt-AY;D*f;k{MX%|JWjN!3*C<$Ix3Z~I446zH;5O*u7;o7*_JK*wV%ayHb}mEov0ac zytCsT;ldVGVH$d?d*Wi)Eh;yI6@wznFtBL|d0r!LRUIR}=)qM~BV9ud8}8k)O?Ia? z>x2aFm$kmGFx`1KRB;DT$x!zi#ph)x#iHcLanTQi`&gv6Y0i>i6JjwTurpB2dekY` z>kCcX2I@ov@AtR197P`0XiK&HDAW&s{<8}!h3fU67;Ue6Ei^e~_Z;Uu`0{W_rX^zh z?9z76QoWe=F)oQvIyyH!aS`|h3=f8eB{%!rUcH-UehY8smHx0 z8o+l8SWc?#-(jOe+fJxq2Y7N)Z6Q2P%kM>b{@UIk+>?1>cE2?Ae;KENtD$mI~k5`P)0Odox_(U|y4R`(SA9 zKnlujl57$MRez;qT;khZ)G3PZ25UuplXY7*1Gsx+Ale3$1F#2Qc~;mSCVQStSSqwq z6ciLr+C@0w(@C5yoaTVg(|yF3is8I8%?;@CkU;ZX_7ySUuROPo{7|AP?QVY&?EJS~ zH9=2A$tB)n{7iOdUMM5m4*VEPm*uEq80yXzRWOhIDL7FGNOP!RRXNsl8b;O5J$7_` z=@xVf;j9^h*h z*O(a;$l*S)x3D1S@m|Eym55XZ>ZAElM)aA?7`Hto;J6ydYZ?Doeu`ufSh0MIfIP0e zG#|HsAXom->C0Xb?z&y!yf0mSDSI;dF#F{2eZ=!qRKIx%YOqvO?0H0LHI--89`Em;eOAt{++~{f!{dn0S^?jti%!=iiEFpK)+q#blYtHNQFe zy_vb{!je5B*Y=8XSUVSv`s$Q6Q#pK(vM5ctdy}u z<0~So;et_&SgI*s`>i`nUQ$$YUBM1(sl<=edWo9}@e9 z7OB`()%FB~ve!F*rn9F1}Y0(5*2&qXK< zB}aTK1Ho$sIrEmuIGLGOB2qWvA44lOTNuKY#hZp(ymq$QGa$r)ld>I>YDIy1Gf!ZP zyct{-?rt2ee|U%$w+Vfx@=ImsWCi2|HMT`?Xq_-{UcQD!MznKQakp}>!f%jgRy}ga z$#Wl066Br{{9C;=Jle;$g7(A{+rNu%nLqWvm|U& zh4Fa~-MQL^;i-Im7{1Azao{bmH8As#n@dK4!v<68kFT=QS~=- zx69Q23X^Sis8!54pmy-R`Tg|6{AKsQ)bp-2yVP52^-T<#_G*@pxQ*r}v{W#OnK4!x zbw?pB244G2>#9bsB;2v~5}&P+P|l; z%}SAH)xR3Jw`iIk6fPMD{kBnE-QHE7YkxCCkB(0DLf4=EJ^Oi;u5%<=GJu#n=UVs_ zbm@DSLHP2Wj@QV|R6Td=F6jlI?{g%n3jlkCuM^+TLz!bzp2$Jh;l_tA6hx*qWCy3{ww+ z?ro@KkdN*gov_w)VwJ8j5VEM4npM5$P3(Ia6$p%=tN+302TeCX@0R)Maxba_cZxQ( zg-w$7)`Gjg42B49W?c_P>WFyk&9}F;hB2Y7dG*#*)^3hecdy{O3%s6JtbG<0Fg{8B z(dPA_E&i@r&R@2Op}s~)PZ*c%p4HAkVbrS9M?N~X}j6Ono(x2+qwJn4Lewmrg1l*ajf7>NtqBsebZSVw+}aO8~JTpu{vOy?$HrP|GQw zt}iTxa1yGkH0giYc)X6GI6*5_5BBX#pA=_<=T_8~q;8r8mw+LB#Gg;8mMUgGm@m$s8ulhb$nL~| zN~KUCqNjCN$S*Ve)nq=RxTUa4kZErwhNu2qiRS8!$laDV`11tLL1_|$>)KT4j~jql ztTaL804TgqRbf*E^gR(27*x`YZz7zAH=ie2DP99!J9;Y=EBY?f640nIJ1zLrkl@`G z5zV0b=0xHtM}!oMt-t&!|DHxEH52)0_YhCh(YDW?Be~6Z*yK4SomWAZf!EeE17>B1 zW%>3wR0=ooe3u55tWW2H&DCLUTuM94TZ!Tk5VER!lpMgG)owHev(xo>C z#EkAVZQ$%ROw!_JQ^D$1`)pD~Vi($QQ>iWD&*_L}CP4SXYUMh2@`m?Sqepg6Rpc2@ zw<6>S&D%TTdyGlxa0|qSGP_yG;HgF%gN*A7$~*#*y9c2;NB4cj1j1m6P?C_IB}uc% z$Oqvg>lP&H$skzW#Sno=6h!4NCd0*(_<5vp@IO}Z-OOfT`nEqW_)w)p@jOvvxfT|P zPHr3?ie4wPADenGEAVKKDe7`<^`vP-4+VZ8=I2!?6W>A*`v+mN&t^mbhXgx@m~QAQ z-P-l?dQ$6J9(fsefi0`Qg{VKjPKWs4dfvV5_yNiC4Vya4+-LG~(9D$~Jq%+#c(zS; zisUf1w=AS3`qLmBcN$nxJlAQ(lQF%oHi^<^h}da7rkwFih_Ow}k>qWw5lUmry(SuS zC!yQ1^`|Yv>TVV?eZ{mV>Z|nyjV;*+NQWoqYCLJ`j|#MnzU$I;w&mLTy~gZ5m@6&R z^d&r1Z4ls?zsqpA3g54gRJz1>jYB22`CxC}r(4-lxaPN(r|Zaip9uj4ng1LH!phgO z7G34ywy^g`P*6Sqpxn575~=uikT8J5eY*m4Wf#4aF50laF5MfK&IinW$!zLGmDS8 z!?%DRQdXG>!9m67Y#%|538hfGtPOl7vX1!1F58Rv%8&r9R0`21imm~PKm0y$txH_0 zt|hN*zuy3tnV6C8ekY01q}T_M>Go6=;61>`T5^}_Hf#8n9zgVai$MAZ_)r#araDql z4U?WRdoPW#JQ({}ezq+}Bg>6=M1g|^Q!E!F4qHwr+>tR~(WWBW27bg~j)@x}MK>WY zf@Knon{Y(>Cb1{ZvZkt@T=S5YD~(Tzo+G{L`L5NhS%Y0D;E4FWRI=i5%YOZNIL3Eb zi8o3A1mLhlzIC`E>Zud)p~J!3!JnwS2nH97r}$^NU>=>H6W8s;1mhE03t1(UCuMqc=Koz&un%UK>lXfi|>P!4bLKQIyJu=&r zR$9n{m!p^ZyarY5xYsoFP31tlXp>J?6!9l^eHa^}CXrOux8UL-I#^tT|Hz_|VC*;R z?C7$W%X%A-=RX#^QBWbk+PpqW%V~6kycMNn5HskK$7F~`5zk!eju=^4t{^8-3+2m0 z*z6=l-hRizCvyDQy$NTJ6g_*=t%~;z@LN59&*E21TYI*3wu=E9=F8r-X*Ah?{Z##-N{Ic~+a63oH6TSt}V!OY==Edpq6C7IeLus#&{EaIrt zW}vHpfdm^vV|A}+zNN_^z*rSBEvKZuStZSDn-;4dVh z685Q2Q)*AezQ2umWs_VZFB-dlD;$$QyY3)KIulAVIf&4if4;)tYvxb1#8f=YabF!) zrhZMq8<3e_WdO>%hBfs<KX88*0l_LxXn#=XqwN<-MTecEEz>y%xggv(F?H?sC)%wL&}vog zAD3(rT4}*0L9f3(ZK^mU{^ye9>}kQbn%krB$@mB=;0EY4@J&op{w`iu5_`5_+-ApL zFgs>*>p54&5YUQ5-5{&a$G8ZnjiB_g306GUE0t+t{+Gfzgv=Ai5M!b4Yv^g5OwHvq zsmWEI!@BiK^9Dfcp3CvNUBuY{-qzZ{`53q5Q!^$UwFnwOn^%`a-WK682R3uZOa|9| zUcSeMkk__es!ES%p%eQbxcqPtmwc#9Z(XLE+dju2Y)cb@pW*E0-uV(^rtMwHb3nmt zP2$6dr))c=#$Ke=!;z!j>u=wQhvRoBG-azhs#t`~`)lly_^u8lxkvU)OZ| z1&$}=4%`Oi?o@`OlWldi-J`(w+Lx`fvfN_UyTLV8ZmT5~m@l(I=|KdIXOA}929MkA z$-5MMfONzgz2A(h4(=`aB1uq@&@uKSRZBzAmk^%}Lyj8!t8RM-X{&5 z`hcaRe^=4L0ZOQ0#atT|j6Y0-bU+7}&al_u4eZx5MHt?CC0yJVNn8`28Dh@_KXC>6 ztPMMFj{hq8S1WI3BNKibgdVVIx*hHV;8iWPcfE&HX3=9+!5$%hZh ze^=f&b-dSudEs~?cG87#HKz?f0C4wD7>Nru4kgy~PD^8_v1V9>6HyX1;jrYz_}>EO z-26l%^-JO>M`YL8j@Bn1zv?wh#9phR;>%(+*Wa6q`owEA-NI-#OUQd-W10@9mh^;Q ziwb*wj*tN680n*POwKl!2dGjLHT+aaKCY+fd`40|o_|fR-_3rJFvxuPYi^Q@O7Uni zmhVc?sMFYR>0!*)>hHk`^QnNOeaBRMlJ8fbU^Dx|!GdJIzRPNuwe0?|A+%r7^*VWx zs#5JUy^ON67&-?c$3^oJ%1@7lOuE{(!A^?z5ii%iG4CG>p1^xHb<%xf`KC&g>hSws z$R}Z92;*L**Cm>Hvuny^lOZo$d%+pJl3v2Ge64HXeyXgBZw`sIctu@=RoG(jor?|7 zdfjMB$8-rig`;Ub^OJ9jXzpdM(#zats*>7YSlKU=~wR*>T z8Ru}*=Np5`M{-D(*kj*HbG115$uzii)_<}TJGz8^<51_*Z4lVOz+qn0V&P6v0||yr z;9qVA-K*%>@H>8mlh&>+O~W84-(kpeon_a}LO_1AT$Ayt-sjb2&ac91-t%$~HLQ2v z*SuZGi@-T*#U^y#oQDmrR|M{pi7V7!`i@@s$)CSJwmwv>eZ|kPnu4_=y+zel;Rll!F zJ1lu~&mC81&a~JsFnc-fpO1%e7RtrRu>n7!!(;g{%2_nXd{AbP+$!DabTn07SpOIg zk&yStzAcg&1-m8DL=&s@dszDI1T_dsIsT^&)W=^Ql3}fohSJ+ z^p6p|ZJixVoue_ZWE9_ZVcabc92+CF+P5}l+$F9i>v*olslR-Xp4T3(g;3RJ$hjO!{_NSx>2Q`Gd{l}& z4-;5CeW{G5snlA(Ue9A>91TbF0aBi)SpCn~+w7!T3r)cqJw2HxMi zFJMVliLXVoBwow-L^j%l`*SNGMN$x5I5fl6z&qcJ+%9PK4|1k`A-xaZk6v5&#!?NO zn2PF{1%Y><9CjoqK<0UOIHNW}ur0mA$VuSIPc1u!uHS5@UBiMmRW?ep+f+}SGPl?5 zS`|MC$;WzIy_wFVufy>*v{#STiw^sng>fiKYVUqUIWFzTYDoa^6*p(aR|HF~7LWov zlpY}h-g|z3#HZggCg`JArmi8Z?mvDSF{Z$m=yL7e$(b+h(i(G3xhDY>NDd|qo8?%0 z$aFZ-xJ?2=WLxc5&c29O;%*%t5!cZJIgb6^7IKm%op;ZE%Hd4owk9?OhlrbDD47MFv7I=h#rd!?bF zWsB?!sT1uFoTvfyh`(Ym%K3b-UQw`HyhhQiI&?=-oS` zo{{cu7bd!cqck~_)#SQ3OLjBDxYnx2*Mzst4NEqg@0)<>FKr4t{~z z5lU){lX@D`_bD}xG~RY!JMw6w{p_M1w!eY>r1H|=H+7gr=Yd>q7M4KACUUG}Vb(7}ngmt^iHv^=#1 zaex2yshw}U(D=ZpO<~2GT9aVG7dPyELH9;>a*fo2N2bRtRgg56u|x~ewkgJ-!|V1P zn=+V}^un@yRs>xU2-T$1Fp6~J_ICpf+L!{1?}~rd?HJQ+`{F1*vmlXt%NoR(Wrdse z6Qh~D^ppA8Q*j1TT35qdEfC*RMfk%?8?do9pF)R40HfYaz7z;cK)gfE+xY0xA4Ugz z6<(5@P`k*p@lM)m&cftGzBHrsi71f7Jk1TfO6k_f7vCJMUQ zoxbbnP=j0+)BIRHP8}dBeeS!zmB*o$)QA*KMVr3L^)4ybm&0{2Bb`5g2}khm^zF#|j!*kEph~7o&hmR&oQ&e`}Aey)V_%1^^ zU)OkzZs8-X5u-A!Q`u6J5XwkD`)a5T*ta+^93kyv$t_ho0RL9K8r}?_($7Q0LKLeh%ZYzB3}1lk)G%DJRf!BfK9k*X9#L`+@OaXP05t6 zRfCJkf}=W4WM@~E!t$8E<_C$1^>Qq(34eX+JhR~L+s!;JJ~*iS?2u#)ImU7a3P^a8 zb;RV3=f`c=Nli*tWtA=xGrHQ&YqHp*ctywtlX5R;1%=1_r+IpD5vEQGXsMzOV(@Db z;pLwE2>o3{o!Gk3#_k39Q||aE9jw*Kk26)TwNOBbt- zyB`s?dsA|ovF*JE1NUlY3@$6kQBhCJf&O~p`KkW?7iPlYyBn-8uQTE9kU!AX|W zMrvg~M6iCCc$4zAp1bDvh;pXryV=}XD!F{QoE@W=1g(kjH^A}-J+nMOwVl^sQ3#Tc z;p*M~Tl+2C=C1wTX9Mhzn8t-_9Zd2w|wF|SaY36oh)M-7O(~hEx2krVUAO6p?x}*@&@r| zfo;8TDN_T}D~UrkgAnvQV+s4+zeB+QE6F*IItd_375z9_JS#S|UPwl#PK`I~;(m5u zbs#>*(;iNlrHx9T~{2pL^6Zcy$h@=suJJ_FwS2O=-}geQ`mK7`j2yK1R$z~2eMDNgc`dU9L5 z_s%)O0)?)nIoqBwYq`Z9&u9qoT7cV#jm&nxxuX;N^JWFPy1b@LTAhMN?qTqy*NXz) zHRO{(zzOeo%Qv=03M4Qb4g zH96T~-mzTdZ;^df7@zpW)JQ zkCmBn)?L@fa{X1`WGe4wB;FWL1vtETQjXpTFNAq%Or-E~)z4Xh2!x(RIQq4ao3Ni9 z%hj~VU?C(anPBlOZcn-%uFwM;p6$EddPB%0)+Jj;oosyP=to+X>uSk#dhVzQ<5+C5 z2d;_nYZ*Rd{?lrGF!3d>EQgH4CvE zuc2YAf!I=&;4o3$vdd+u{%e+QbGrLYWLsy>dq!}CiXk{jaAy&A1Y8cQkLLsB(gwPb z4VV#dK-g41!qQi0t-H9|1E&RS#)|^7clpB4#t2<&&E-?%Z(BoBZiqyrH-?ujG>cW3 zyQOD~C$-<%6H?7q8N1MwxFIu}tn)?*^f1*q)GK;a{WAo!Y=y9cBMe+TKF}gfjmoFY z@reV<^a>6X`qOzHuyoN^>`VLW(4xQXLSKADmVbYJKU0G+P}b4gT~{jiCXIE~T%P4L zmyA~(gN=B4QI@{I!i{#XcTshtYAoH!uV4zk>MdpN9z``b|JNbpt!thRZEz;Wl30|n z4y#@^wh)6`C)VQ=YfYi>MfNSF@2f`SPw_=r%l4`xmYzvlq`1btUErw4ytTSzb*5l9#O^^OhV-)f-A5ZxBv&Q@NZ znTgUs{VvKpjtUEu|F$azRv6TKF62Z8SMUnVCjOZY%IfmZy|~71j{kGFDH9HNHV{Sq zYP}(v<3JmF1NJZW|0g$wrQCHja-4C@eg14_%$zGnE+`HBH`>U==T+mx(B5~vvd&x~ z8LK4tZSZN@Z|#|&{`d!;i+Net`>_+_n~tDv@m_C;aXYynu!s%mToNdyib-EGrX!-A zrapf*OOHbr#0RWAE>S&tv!#{+o|+T}C*5wvEd&`B4+o~hJUcg@YYh^(EkZhU?^R&j zV2C?|=V|>F;h>J?itb|{p_8fCV8Nr2iLoEE!uwC3Tj@vWFUL%9sg61AvRKxGqk=>hE*F%y?EdKbs|V7@!W}{P@kC#- zPc6AT$K#rBKV`VZnrQeS@hZfp1MNC|9_1kGUx4uno5G0XBocO==Rr0)&3>8Il3Shh z+F8>LP79FJ!m2PDG{Q#K-3-S^9zJyp%#!JIRBvAk3VNhIVV0Io(~H<1_zwvgBh-jD z*$j}*o0B(vS=A%=c7CeIKK|)_VI6}o9o%uM{Dai1*|_B2;yE3L>o72CWl&j_2Pum9mG$PNi*Y5#zA79q1d4WBt~T$?*4CCnY}>}JOuk5g;5KSk8IlBu4e zs1r`ygqTk}b*tQR@sJAMtvf!}GbL2zHyh~N#+4|aG5~y(!KtLV)OF{MR;#UrIDy7; z{D+*1e5jmbGRODsmWMhweva?VLjS=|7Ba$QGLq`7yr+9fLF{@;5H6aplBq?=>P=@f zW@U)E$KYw|BO6Y6J$<{x3$s~nOF1h4rjh*ue2HtSIiW?!TTeAzwV}WB)1k#fZuARC zJPl^((E~lV>J?pJ58V4Yu`2Ce$78xU%(nBM-3ww+cfsnF!yd)0Ud%zZqw<0kQyd>P zK>fvEry(FFu!!ZJC{$iBwwr61lvm?IY(RaI5!oqAJQnBm+%ZXb1ml=I?Ga+~9RFU+ zowxb=+?ed5uST4a0xL^gZ!RpUlr8!(E_E3bZRXs)#;;B;X=JTqaSK1iSE-L9UsIs@ zbg=UzsRj}Cz$)7TkRAlbSf;HA{4f{hhb^3-_-p?!AiNvoP2a?c#rB)(Lv(&XWo-iSBHB#hU>RuH3n!qguE^gGb@l7ca&mLq~>3S7K_3 zg)2kHG8o@;5w)`FebR>-)J$wW9*4&MH;h*;vJfcAlPAwAK_89|5(TzJ6K-#-WGmLO zkI3CzaKeIV1@5)(ux>wCJdFdef|^`4G|WNV?YQ$dIAN936%v%719B{GO{dKKTBF>8 zVDvn-i|-Hm7B7)ZgWfVLIZTE@>xBI2mn~;J+Op)E*nH)Ms&kaJ+}>PrE;8|hNo8E5 zd*pEPEDI##1&%D|LFw!kY{KxJiZ7NF#V^*(jJ-w&zd$ooOpI&KY)EBKw+zu5dl=6vAoZeqOnHWKfIX?)>7E*Ny(a^FX* zZ?CdB&EH+dM|-KqGBf&%UvL`AwPZU*8`3kvNLd-QshIh`MD!Ps1aG`Bg^}_CvkYg* zdI~J)d|detN_RT2uqEDB{?1Ybk5f?`47@d5kM6SKgc#UYi#f>Olg=?kQl<17|hgcY{Y0{lp(|d9f>Yo#Y2{hdZN&)}Gq?A%-rY3b_D7 z<7z?}ypipy3@p^AL?%$>u`u~(Z?8l;WVVpRD(Vg;o@$h_S#K zWYL*yc_z8(Y`N)vV9%6v6%x*ux`y>(z@(JIJ_nu(_hlVkdpLv7R# zqXJmUBV4QFRdGK0x>z^%YV-5eZy#lr^Op_s8p{0EDl6JCCZqD z9o(y$&^{tFK=5=H6|T1kZJ+BYd?nA4FUzt&#mpE+{CL+0Q!vDw11$Pa*|&5b+6N4;W_-rk$h<9tZnfr2W?P7loEcD+J;Ne=@6Ob8!R`VW=HHKeY{st0GBVfZ z`A%Yo{3%1#QnCii^QD2^kfP<}&@tMA8TP#>+O+squi3}4bR!j7XUh}9-UP&+KcoYw zyL0}H7t~3~R1W1Tx|*k5?&>L6CI9nW%@c9=+^9arF`0M0cvWyRt`G`JQnm6V5BXko za$o@iz7PLt?E%tHW8bOYsqm?~)*u@M9w?<&@pMEuq!mocvtEnU@qshB$};-tgGg)4 zjD#{tAKd*wEq9Jh&dsAulYciD1+xYOo;M@No2DLUaK z1A3vfHsvlO2h(O$+vn3`vAGx9B5$*L7JVGBT{oCuE$R|qqZJComf$wj-^Q!%7ktT} z*|#G@Yb3%kbT3^}BGQkV`XpO1#rHBZdJmv9Y4dtU%MwKctSBIT+r1puwEE4->p%5y znYHmkS??#@;*s@1Z{cnZkj#z{K0;Gq1YOuamy2!Lak_#08^vDV3hBi_$ct;-UnLZu z-tc!Q5d5LA+p5fwxC<7I!UJ2z%oSkwMGnvxVqUed<*xVzNrDmZTEI zOOFvcx=jsYDZbXBL-D(HdCmU%qQ~9Rc65RC6q$9SdG#BOT}r>u)4No%5OP+@ShHeG zs0G#M{x=`dneF!beMA&pQcaKoTQuWNl#UH2XH1fTnJ^bR3<1>*vD0sY_)Y-1K3vPC zomuvAHWuPinZt)jvY0e?Tc+#P_5OoM!bVekM{s6RTcb5+8_PMph$m1^f2CLy)TKk+ z;4NgMPtTX}ULajYo2&8GDMcZA%A&I^=6Gb0AOUn+cO<^bZWzBH+LQ?k$l4wmygZKd z)f9~itPzW3$4c^k{>QScVAnljio_R}c4>vs3Hr%7>EvoWG}Nh z`=ZK?gY`IU@Gw8)HUs4!k&G^-O?zr^dZ%F{`HHp@uX>@nYc*)K^+B3+pZ$KB6pqc` zmgi-v9Q9$OzL(*ZNd3vH%X5MEhW~Q0%(e#45yj18#=v2O!CCC+Y#YM`{X)@UQo?L- zFUoN`7%1TVF}{{mnC`QXKex{lRLgd1vRG+mU->kP9dCz|e2hLQNS<{mF-SQ67?A>N z(03To?We1hY?2sd{9pE>SH0!>pzI)_j)L*yArX_Xof7ztO+`m=hUd$`X1bv-r-}0} zIzSs%jA1yypq3wkYBluF?cxZcYMUYm6-vD0QclSWoW1Hqp_Pwz z8>s!+2&cO~zzSuR8lc3dw9KXKi9-))UE|gLmt+3r{@GkxACQrRA0nN%Ogkn}%84^j z#9v>U#nR1=E~gb^9HGsI^KX%Y#L*dIrG%sJ)Dm;ooVz7;s)cUZdQWg^cNxVSgUwnI z#sAnl&Hzx--T7+p%r~EgwC7cd1ZIKV#-`39iiGJ}ksG*%_s6M^c$g~1`{{w6D*F9$C1;Th>iDYjz~u6y?YJkoMpMYHFejotf!53~tvW!l9B0k}BuU^`W5yGNeY zL4kmde~Eg`=Y-*0ce+HMGt%Cqt2CIIUtnQ(Zy`PWz1fO(ELk!Tyr6{ zvx(M|x_(MGi!s>Ge$Xj$E~*#%cCL|G+*^e1DTRjkwp)O#vn3U}9xuTh|45O{Uo;wH z2yMFjJIAfh@k?PC-OhLHwOz4Eaj4b&cg6d~OJJBH0gzhS|BN=xOrNB{VWK5>sFEt$ILX z3ty!)56}y;|7iS=elBM8$)DrW(C&{P&uV9DMesjHCAq0nOKPOQ?}}^82|{|A#nt9N ztr#ylqYTLlE@kv8vFxHg_?T%nj+D?g%8KAvWHJByaZ``5ss^z?A}%bFii;P*G^bi#zvvqXzfV}W_asBX}f2)r+v zA*j?){-=KNW4QicLSY`Weqnpqzo9HI)k-4SS+zkqP!c@=+es>pHN13?Rav$+>ObX! zEsR!hgR(X~mP~WZq5jD~qQ~23D!EgN7#E5hi5Zspu^*-03DCT^YONv z44%`dcJjjOf_ZK;mn7Gu;WQClJFT2mU4(wW44zP_1x`r^U*I(VZ^V3eHodZkta0eK z0MwLpoI(J7yfDdFl{B%~xqR7Jyd6`jz=?vNVbq>{GN6#tlg}6}A6N#_j-=iCY68$d zzPS_+=R75f{(zP@$x{z`(0d9bk^`Y9DcEqZuCJyKALB4OSwOAHg?!~yxikM1L~Lj9 z)FAWawywA&zwZ~OT^Wa;=D>nXi#S03dK~bVR6Q8Z(Uo0j_5Ez{G^_rx^+LTE8`0Ui zqY{7CnL>78VgZ|p{!2MPAGNq{WUk8h;NdCdpi)*0wVwW0>BuBka;f*qbFSm2G=riU*E0IBLZv@^e-mjIgk6OO;37GWlgMaLFI+hky5nwUhI9_3Fb z-{ISC$BP%{@hSgtcyQSke;T!@rwJpO&Z{(&G z+Ljn;8^k&SFJ!w#BBVXGc@wbFhsH%|gyFPpSEVd9xJ(Zl9Nf>%Ud&CeUikd^odCpdjvpJT?sa0fbDa!d! za>~qPCNYOG$ILNjeyi(uUBBOTeXs8y+kNla=Y79Vuh;u?-|zRGaNW`rco=k;kB<*% zZf5)+@7VtPBMjhum)Vi?cn5yJ|4gs&k-KGQcs~R^3@r@#_{s@??c6%Z$9JmN+}Q9& zFzTmMQrd(zelDD~yU%2~-QNaK4gY#!p7>Dr{ksyJu%O}f4|mMxyXK#L-}Pm;Cd+8~ z3|0NllO>a-rv`4(`3C#Pv@G4ujX5828k@@h(W2cm&>-{I&MU{3TEp`P@r&1*JRj2z zIT{929f?~2hStL&;POeF+a^%B-nP^1+?I&GRY6LPJw3xnC&Xm4mRt5qmy| zy+!}ai2<3$>D)EF=X0dVGg)H3NxE1AQ~xg*O$p$9HozcW`pD&2VQUTdqBP(P*_U$I ztQAD|p^d5-xeq!Rr;x17bp>ZXw0v8ReOK#=|00v&oBhB`pwC)ynuk6(1{mvKk>HnC zbWt}fDurtbCj4~AwX=&rf?+39e(+}-S`nE}u7UR8 zncS?IE>F3lkDs+XQyTYt+v$A!8%vGWZg0;>DGol7Pf%=~KC~g#ESeIX9fT-*NwD7l zy{XJ3k4sMo&msf(Ujj4t&B=+`5C*VXEPb4r+!bR|g+oKpAO1FO(``X3Y zt5bQyUkOHzAqX%7W|ba>1vKAk8#8sgVc|;k-4Sg5a0xRWta_H`#=tGr>nrE$(l!&J zVF=6c)7^RXWt8o@5>=s_rt?NiuVh+a8X0$^ignCJWG4m&4ZT!}N=?GWAF!%T&yl2s z|HN`>Z-QK3Ggd-uk{)8X!zZ093>R-=EtY8V`zv#clzWn~YQ(Opmvtr^&QpB8OL-km)6jgZB;&?_Xh-0#@{pP= zaTt|3gUd2}VvPikch1dPver@>ozuhK05&&k&%EG3=)ySc-S`ca{<^XdzbHO8K|R5! zWLMb8tJ7lEuY<;=hdY9xX_f2fv)BE^cw!L4s|mh(j0Q zP0I51`1qpM&8&mJ0pXNNn~D9g!#V-w^6ZJooek~? zC~m~lTO`H8@Mq3vt@e>U$j1k;Prqhyhkx|xV5pkx;W&e-zT5!@&+4=CrRdupxFc+X z*XA%MtexLo>tcZKqK)b8^prta$e!(A20PUzY_t7s=D947x4>LC6FtFSC&BS!%yQBn z8>&mNxvF^>>Rir*wMIK_gnC^=IR5k^A_tpFy#=B!QJin`6K*~tcA6c)9ljCnm>TEO z&z@OT4-8PEL02T$pe(1U>KZGPeH>G>qv)QvC*B7eh44%95*3dmW$u1WfR?beWz-5f z&CuOICI!nBU*6;3AzYwQ1&$)@{48& zt(Md#t^CpF?$wVxpyhs6A{tBK99G~t9Fm8+F!|;D3h5OkhSl2?-|M- zliEBqp5x{t{sR@+qVJX7pUnTz+QziJ_sPxM#0}kNn=UrifoNBfMa8sHcGFt5h zd(e65O6jnZ-;ed3%)tUbHCDQ&k4mW>z<~!KKVG_}KM+tWEAI{I{?1`%*iijE<`a=D z6@`WUy3--w0L@5<=BGsM9T>ci({s96m5nfJQH=$g?hYvlX%SKfw^?%>rv+5dFCC|c z0mMOj*KXRqG%YXjo#Z28`;mJal##)saV*c|lJByEClS~bOrIhSea*)~vVLKrnmYn% z4E*w*87{aPkggXPo?d5<9rY9y)8}!!1D>S;x~+aV`-+T7j({)f*A7Wivsu&i?hoZ} z*{1Y4OL8fCg;G@9&_-HVxf-zq*bKQFZ;4f=3+`{K;5iVUK8V*5CwFiFpM-P)B7s!b zKOLZiwAHZMNtiV-Cv(&q6r4Idb0)k&w|A0lKAKOj=&qFNOozhrAq|FnvOxz$?-@I) zdJf&%V3X&D{A|``*}F2~7hL7Yo@rY5#P^h^3Y|5~`_y>=2E`z9KF4$3o~BQJK?sR^ z7nbP^TpHp7x1LLD8_Ii;*Gn)ks%;xS$yD!d! zqKaFT<#{rw*4*;Y9ajEHf|mI(B>0ncp^)v&e#?3u+MACvD0@}?NRD|n~pGpy?_dxmdfXC>)LJ8 zEXu_D?1?kLm`3(!5&UoDM00n$ZjL~OmB3oH!TEe0VqGD|b#ml{cd(+ngTBvN5gJO02pM^mj~AzX@TFYUAf!%bbA z9qEe%$LUATc`o3D&v3mC@Xsu=9Mc$5+lkV7HF4 zcdWX?a8-Qjxy*Qb6`9)9{u)y5!goy8>FTFe$K9f2*o@rtNJM@OnY$*`l#N{AJ_f$q zTje8SyW2-Zx-w^}Ia(lruWCq~qb|OqASulH4QXSq?#NROVbR6x5I;Lv3l~;To7??9 zljvnd&MO&A;;C|x9$JqLl6_LIvmtV)_A!V4!&7m7+z+zVO(!D9oe`;t{ zaCs71Xs9!VTo2&0FqWNfc^VHO-i+GaTLtLX}N9fUK3w%NIquNP|@&XI0;=}tZ zO98>fh7VR(3p^%NlcA@MU#={QSy-}E4JbwZ`?JGCyo5jhXjM%N?2Zs-*ggW-!u@** z0quieMnk4;Jjr*V#YgFl#{FRv+%eyfqubw9p> zFkaumg$WkPv}i@s^japQ*Y4a<@js@p@X5~CQOlVaVGL7Cy!@I!pAw}+9`zV#3v+r( zGhVIpIO6#Lpd@oUOm5Koa7y}XTQOYJVMVTS87qXQ1RsO+zR&&;yDe!ox zV;e2T8W=JozZzK2CUNmrwvI0$-Mj@9)EH&bfh~c4IAT{?MM4etGChz-{eSm>;WX5v?_I$FD zRsLcKK1?<*u%$}P<2!%b*&uPJ{3TuvR$FxEUEUjV+?xc#EeV5u2jzbHof4~PmD}=E zkHcp%v}tD#d`=2?%@pt;9CfdYX>dA@Nq%^ou3r-d_y7`2Z;}{))Vc(se#%Z`Vkn=W zmK|U?Rj$-e&Uc>C)8V7t(!?-vq|EVdJJ% z+1N_B)y!BuThes4a8ktGTh-N8W?f&CpJm*pf2Bc@xX#CD^4Sx)zG>?K|h^~aJ993_=$$3)Di^EM@Pgr<`n9D1rw68mu zaQUlY9I%6Ye4XTM{pT)ZkK_#B`##S=@~Ibd9dce8!uZ(8EDwNl5(VEO@Fp&O3jtqW zV=*SS)!=z@>0|sQ(IxKJFO(Pny6O&$R0-fKm$sR~sK@MQZq8{qWc8DuJ!W+@d{Mj|;t*Uq?@8ZKz(JD2uYcVaMPPJ?bW;+Yk!$ zt@{wP`-WmCp=MmUA!qh1qd3VJ_q`6$PVMHAK19)Yl?f@LLYN>7JNA5Fv9hbIs*qyet z$}TA{z$Z;Jo1r(AesmML#Pujg8UD3s8&UuI!@u08=+zOW4(s-FzC{Lx@ybG@XZ`!C z{tAX(hu+>Bwb?sQ+%BG)8uKLhmNxt9$3Fd13TOe*x9QqgT0XE2@z^q4BX)FPVS_{P zH=5!UA3>$VkXuG?_Ed^_FuS;gi5o6w)F|t8n{xR-uS7ucc9WNxp@PAr!zA5{wm$m( zL^@=NS}QieXsOCcb$-9vb8mG|r7ByjB413OR?>fh=_TpM4ZeVed}}&8()`R*_1odO zElk(pqDbtG_`fxY1}=T8nJXJ%Lz45y1 zF;=parALwc(BR5HlqTtwL7y_kV~bKJZsm0NBL3lY+g;2)O6*SBlzhIg{X$FBN$bGt zHhms6<~f%qZk?y_v4S6WOSyc9480b8_xJ?`p(DC2yT-@abR<0+ah~x+yu>dj&iS@! zB(-M~u6pCgl9w*OdJlTrskmxM2W8pEmH-BBu6gwC3C^$v;6Qj@k%2n&L1$NF20gsK z%?i8~k5W_Gd^7Di`}3miyoCKnGkE3qB=wYgE?Po0EVU@DA76eUONGDx8>B6o3?^6a zNj%S&qZzeLKd0tXme#_-%#69`ZJ2Y5@qoY;T5LbERP?R*)z5W05V0f!ZIowyXTEv( z-vP&VtK{;k;mr3&_;q?{6Bz)eMw5~J_cIDhZ0?4~+}5I@_!SL^`<57%m=jxd1K{9TiRPs< zp9V?oA*l_otJilmy7Ir=4Og`4z#NP26bRH5c>;X;OZb);2-A}tM`rV2;5MD%l1*hqw#hOj?pEk zy!lR?6(Sv)MDmaux%I2k2eWZ?D=%UwWIVz&KlEF&J+Qsi1k|p6k=eh`%#DC2!pCqFpx_!&i2cM$xnsNvviQLAGJ8~8o!;Cb3fz4rzi;!E_D?sXg5r1_jE#g_#FI{KAg)>xLa^jAWT%YZN6~r(5GS?^i_nGeBR`&k&2>K zB*A#iw0QmI@DJuQWGbdRt|+1B8>bJP(z17J zy*otOpVd<2yj)rIOXm16W&OCOt8U2{D`%4QPx<=ysw94fc0sQPy6LQt!i6ifh6=VC zr?ed0DmGUYUvyigp-Xq~FSl0C#czVcyZ@b<3drChxYCU6(qDwib7yBZr}ZHuQ(mfj z%J^Yi*Eua=@FyVzr5!t}Sb1p~>b%Qr)MOTGa!OY46~k`*T>X^R#RnmS3dQOsQ5rn8i{Yl=>8PoaWtL_$tvs}TKUzcTY3=5%~b#LwQP zJ{a7=Z_WLashk^eE4aIBK0=37f5%(v?2Ib!US~EHz}x%y@;!p5O))0J{==8IBmzn) zj4+mxdH05XCPI$B%~BhwLW@Ka5Tup!q+JiH=_!4AJut66m4IM2$l$Bk>DLHmj*e*T zyOJ|)N*$z}i47mt*RW}2Exe~nH`DR!-H{np_uKkO_Q2v+p3B8(6~`?Z!MZA@;qe8emaf0Ij|HpcE>GF>~>9( zI~I3*6{neA4L69rO?_Vyu-Rpmu`opR^>gyv9JCXlt{KV&m3r)Dvlndy!3iGvqY}f- z-x^#u&3?X#(Bw!2!VtQCN%1uX&r|!Kvwt?d#}E3}^)fxW-SV5KMU3;5_8*k(rA&sa zA5fu`2vmYY9{(#Nm?Js4;trl^^C#ynJGzbjcXahmLg^8@EHHoSChil!7^f@)ShvIT~%KB0{n;%iXEg(05 z7LO8B@Mgz?eW&9cZYGmvDWkKSi2CI)oPP$%W6$xc)Uc^%YUC5T^a=Y=^}Mz20^doi zFyJn5CuD0~et+vr<3R_vxB*>xbLvu!gSD07jNg&?W#uCr^VPK8m#Iae%g@uRXU zeD|#DDP$hU&%5-YW~^?~oa!$e2KOaX2;}t-Fp@qI_3mz#j38lFGY);~^=u`)cWUTJ zzURqs%RxQa73BjZwN1GRX2|m1%%E7iFL+8w(T6$xBSD@6bm`+-!XE%aAJO2E8_o3Pkv}=d%dVrP>n$d-cadl%T*|rpHa5e?No%j zy>(^%EX-Erk=>%pB8X904NOqI?YJay2(_yL9QQl8 zFkJ|)T(9HmQ&hMTt~N|oU5Ud89cEjZYsYvSY6>kheEW}f3sI*(xv<4E*qh?Ms*`XB z_wXibPUcbSVu;`n!$^PDa#goe?fz3cI^KDbr@h8afs#1TteD5^e`)+Bq_ZuE5Tm?+ znej#+d3GhrA5Vo504E*?sog1`@&YI%K-ftX<{ z@oNmvWR(0H%FZAFF?=wuz=hnfF0MvNcQz*nPugr=MWTvqO}Mv&+@R%#7n6PbQmR@# zLvwBXq{?rjep~{}d(dd2CYMKIb04RD^N(n~d{A4j15kdlOJ+yg;M3Qk|hwhuobyiH2ICa?%An)ks~#Whigo!bc)HoN0i{ZgpIjb%@_+%+baoc1JFP$%-BlR%0neJ$BxaJKY4^n$ zW~Fy0#NedRI?I)6O){y z@}%AK=7%c7t~M3faPdV4#RAY`$iX4|LY)B4X3hG$ut~1!O@^v>&wr7LF5m0$u5osv6z_zRvaWk8`IT_UH>c5s!_+r=<-G`A7gxHxGgS<(!Q~}^ zi$SOl)J_E(VqIpw^GW*#^Q_01oFmMH=uH1uUON&n$zDvg~}g9FuGA_0^4)CYKvn+U9n%z*IKx*8^8{=cVcNqT~Hc!dH7iOniEL9QCo zS>Jg*kBMklu5A%DhDkB%ib9H>B(JX($2!f7-2ukrB)eZ9AH^h3SFSI1Hoz%~dKDow z-NY!g!t$}I_i>oig_f&GaK|%K4Xe_yiFEtnZJUN(y}V{tWb$!M;U_n9E}LISyt=kH z>tM~3{iFMLsG)|1AumY-jLe`@|UO+R@)Niy2tbsb^Bc6{9o>Zoc zi+Xpz$19}FoL@Zln3IYh@BF%)85ui}+Ac)QURO6D)qA{MpC9`IjG1gR)Yoh+KjWUf zVTTPKJ~0{~c_=?9TIq~>(eL38&rM!#R9v+bZo%G`F@YFC2Ql3yfkxns%^q zlq{3psgqCJrx2?(Mon{P4?rC}qKkO)3nn?4n3ps)nwtDKr|-eCfm|=}Ogp=Kehdo| zv~DY8#`c_UuTWYzI{902v^YA|lUIcl@`F|u8#r!&IlCEUTAgPW1;!I>=#2-2e$)>$ z!}5J_9F&5k*o33KDa#dzNynD<@M)2(& zSH&)B^u`|WlW;L0jCC;YMGv&srb|C3SDyWBlOSFzx#@C)S5s|x_j6LPI>V?8s_R(M45f=}?5tgTr-8yPq7hL^Iz&XoWTp!I!lI-r>}wixjgKnE>k zH8cO@$e`crZ$&Cy!jPUN1w z>(p-`uvTXu0f8NXXhimmkzLNodG#h=zz;nwTr3>Ly zAPzbD6C@~B`I&Xn<8eUMRvXBzjfg5#yL2XheXG6t&CqsC&eCi7()xJF) z_)|4)pZHiIC@aCJ`v#v^tJj)hEG%21- zPk;vo=GYf0#q46D*;bu`_++lx#*WCMKaUFwEo5?Cr-9MDE3fPV^O`xn`m7Izd^Z8R z7BSCtm;XP3=j~z8GcoHLyj-Z!rb<+-I<%6|66JoWl+xDG5ZdRPTEZ7a8^If0M7+Yx zO5){=AccbL_&VX>afua%Zfwo6IPy=GpqvcUTsACi^IQ4_Jb(lA7|BoeAx?&)B;p^! z(M?Q#hVg_=lQpz2Y1Q%%bO}dlL7NcU<;{&_zyq>5kj^*%*9l&G>C&G_-D?d~Dzgsy z&FU^il0%%0TU78qbM%|K@I`sde}1#dX)ubgu@0}TGsbVae{NCTwgy8TgsVS))@6uSXs&takx2?Yd7^9SV(Z#(goEuFP#{h|;Q}4<=pQcfzBr%S;`Ix?dgUY{bVUKq z7Qx9UUtmC*>}K0!Za2sa2RoTSzDb1TzHc9$%eF?Mjv-N6&YO9F)}ZF6p5jffSMOr^ zGEILkO6-zvH+9i`gpS%=Yzq$q^58q%k!eqD+OXV6175-Xvr!DVbuvuU7zKdp#B8b5 z|0a@}l}^D{?&2$6#h5VxmZOgpEEnrZT(>Yr31vmcVN`|_R46$}uHa233i=O1y|Oa+(?27 zusxbSqQyVG4zs})_Y3>`17yBQ-xIpd+eT$;}E4G%$esVP|QN?hiwvEF!&TTqa1z?Fb^%Ka6dS^^;%rDdY#sHID14ho2hl};G44IfpNirPThvS5614?@?_1_rbfvfa zjSIa9!Aro!_}qi;qlK)i`3b3-#e0XVWKK4R6_`^M<#+++A<_l*60l~=BRZ{MOJ&R_ z1u!oelJYaYMK`f&F^U)|$uZKK;>D~_0Je{=lDREFkN)Up@P#m(5 z;Hgw{L8VP;#g7Fy5m3Y8m)*WLN-h9G_{UEzQy0zr#B)zeSLNJFt~G=SQ&1mjrMnA# nQvR2BuYSLcMM;o1*ppWhrbdy9Ur~5(d-0i@SQ?YBxWxP)_Q{@w literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..667aa9151e8d97b711ce1dad1f05c7f5f6f83b89 GIT binary patch literal 9840 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..1d660f79d1aa8d0f6bf42fd80bc8546e03a43327 GIT binary patch literal 15084 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�#q)yoM}zR(et5z0jK7Mc$&~6V zXPk+(%IgvVLDf99Xu! zqqk?Zx8OH@4^-!4+)pma>o@CiUwKX5k z)bMpsLn^sZD8hwt8Ah>(aS>P+J{^QCG;s=f;MN(;h7rqCHK}0|O3HFP6BdN4Ccu`_ zc^i7IRdpL4-vgmF;EPuQ@3jY-dwxuMDf*UvwrsHMgo2n66|RYBNqIC5bk%`*Kq_rV zC0cEinXSeGr}+S})smF0g(+PoS2z54;DFpQ$y}z_waZiSea|9#cQ*rmI0+Cuku|d2 z{5Dyc)PG;$O2J(VVOLROvh;mfL$PyXU^Y7fg-V8+vFy*4MS#G=jkHPuzLf+GgW7kz zaBSwVW`WX&sOmnt-PMfS%HKTChhu^^l@xkRY5f<=d~TrwZo3!4?HLESiwwI!T>K}6 zI?y9b$$S?yryZrf&p7pvLlHFMlz$LqfuMh}qGm9??YKIthq>p-VX~U9z3Yi$MaBgD z_{o{xb7J^yQ1UFcrHQnMM6*eExTJF%z3HK~EihEjYdFP!^9iAfzET5%TSPQjA& z>VHj={O^2+V&(V_in_^DQSgtGTl3m2xTvs-15maweI(58<%D&c41GAcp{~aqZLgfF zjtX(7t8Ni@#4Ylxybh@U4zZ!Sc4%3#I!W7D@|i>Cq%)Od_s6SsO|$Kw0L!-3pBP>zM4I}3JNpJD7BCP0a0O&1u(x5NxLisTqfKpz?k6X3VWsjY$uYf)gi5~{WB;kTeJj@@pT}tMPNo%1%kB~wXXA8< zzHM2WdLdO zHIuB|Eacg03Tp8Oi;Y~fCgb<()|fjT6j&CAB!A}7OC5Ff()9Ln%hAuMUznPYsUpa| zL76MSX2MlqEve7i84Ut!zI)>bCb#Z!|KX2M^OaTJ)Dzg6YUnTpcg+iQv+ICyi+CaN zbBq{znEibxO&`D4@DoKQ@;x*S@W^{}~fX z9C0m>BW(;kA>UhQ>0j#i&-l1icxvWWe^UEgMuXQ(g;>~%`gnB>1j7gA1etT-x>oS; zO}IZuWhvUr6yp+E@&l|vBQWmAR6QW>qWTZ#Sy~uXKXHmx??1zWPUZ}uZ~efsMc38( zAD7wvfkig1`jZj(SYL4Kr*CB4?Ylu7xuRhX>|hFC4t~ELPHT!77n<_TM%Ndhfh$uS z^{H13?JS)H1#^_>)q0&IP%$-W`1A0Sv)p_p1OJCWWTIW=ho99+d<1PJ?0vjsjHxSd zVSw2Nm%V~kvkHmOfw~RoqzMmDXk}@^7X4q3SK5Mx7vMPizBw9k=l>#4fi2HunU^2b zU%QPQ%`dTa?k$wXdY@rlw!B}jwo{s^L(#c9fLb+2K|h2b-|2UyxN5X_wdXj)ThMB% z0}9hG`5PJXwy^E=KCEMY)Z#YKyz!`0A$9g|kUYw!EK(-uZCoas`pp*h8{p$l#hy}L zefhR?u|&nDy3IPeog#pwYO1t07j2$4?<);h6i)CX{4R&`9>yX35?q8O$5*@Q7D85S zEK?F3s!>rQWAJ5ykM|$)Y9*9nK|RB0gL_#hgZuh_J9|Yl6$FWlMkNJfHx^AKG|hY7 z`Htb8fQQ7HZc}a=bsZ^P-P_9a-tydgtG{JaDhS;D5uJJRk0m~teW&B%I^t0qR%zf9x$ zGd{UhqWfFWiwQc73_L1}L^rZM9%Dw7Y9ozA`*5ID5yHcry<(}fHt{ETNDeoJFG2