From 90b405952e13080f842d03c4597b13e374b5b630 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 25 Jun 2020 21:10:02 +0300 Subject: [PATCH 001/829] Extracted ANGLE-based EGL display to a separate class and added some DX-interop features --- .../Angle/AngleEglInterface.cs | 31 +++++++ .../Angle/AngleWin32EglDisplay.cs | 88 +++++++++++++++++++ src/Avalonia.OpenGL/EglConsts.cs | 10 +++ src/Avalonia.OpenGL/EglDisplay.cs | 76 ++++++++-------- src/Avalonia.OpenGL/EglGlPlatformFeature.cs | 7 +- src/Avalonia.OpenGL/EglInterface.cs | 29 ++++-- src/Windows/Avalonia.Win32/Win32GlManager.cs | 3 +- 7 files changed, 190 insertions(+), 54 deletions(-) create mode 100644 src/Avalonia.OpenGL/Angle/AngleEglInterface.cs create mode 100644 src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs new file mode 100644 index 0000000000..375b93c27c --- /dev/null +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Platform; +using Avalonia.Platform.Interop; + +namespace Avalonia.OpenGL.Angle +{ + public class AngleEglInterface : EglInterface + { + [DllImport("libegl.dll", CharSet = CharSet.Ansi)] + static extern IntPtr eglGetProcAddress(string proc); + + public AngleEglInterface() : base(LoadAngle()) + { + + } + + static Func LoadAngle() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + throw new PlatformNotSupportedException(); + { + var disp = eglGetProcAddress("eglGetPlatformDisplayEXT"); + if (disp == IntPtr.Zero) + throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point"); + return eglGetProcAddress; + } + } + + } +} diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs new file mode 100644 index 0000000000..530411bfea --- /dev/null +++ b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using static Avalonia.OpenGL.EglConsts; + +namespace Avalonia.OpenGL.Angle +{ + public class AngleWin32EglDisplay : EglDisplay + { + struct AngleInfo + { + public IntPtr Display { get; set; } + public AngleOptions.PlatformApi PlatformApi { get; set; } + } + + static AngleInfo CreateAngleDisplay(EglInterface _egl) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + throw new PlatformNotSupportedException(); + var display = IntPtr.Zero; + AngleOptions.PlatformApi angleApi = default; + { + if (_egl.GetPlatformDisplayEXT == null) + throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); + + var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis + ?? new List {AngleOptions.PlatformApi.DirectX9}; + + foreach (var platformApi in allowedApis) + { + int dapi; + if (platformApi == AngleOptions.PlatformApi.DirectX9) + dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE; + else if (platformApi == AngleOptions.PlatformApi.DirectX11) + dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE; + else + continue; + + display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, + new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE }); + if (display != IntPtr.Zero) + { + angleApi = platformApi; + break; + } + } + + if (display == IntPtr.Zero) + throw new OpenGlException("Unable to create ANGLE display"); + return new AngleInfo { Display = display, PlatformApi = angleApi }; + } + } + + private AngleWin32EglDisplay(EglInterface egl, AngleInfo info) : base(egl, info.Display) + { + PlatformApi = info.PlatformApi; + } + + public AngleWin32EglDisplay(EglInterface egl) : this(egl, CreateAngleDisplay(egl)) + { + + } + + public AngleWin32EglDisplay() : this(new AngleEglInterface()) + { + + } + + public AngleOptions.PlatformApi PlatformApi { get; } + + public IntPtr GetDirect3DDevice() + { + if (!EglInterface.QueryDisplayAttribExt(Handle, EglConsts.EGL_DEVICE_EXT, out var eglDevice)) + throw new OpenGlException("Unable to get EGL_DEVICE_EXT"); + if (!EglInterface.QueryDeviceAttribExt(eglDevice, PlatformApi == AngleOptions.PlatformApi.DirectX9 ? EGL_D3D9_DEVICE_ANGLE : EGL_D3D11_DEVICE_ANGLE, out var d3dDeviceHandle)) + throw new OpenGlException("Unable to get EGL_D3D9_DEVICE_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_NONE, EGL_NONE }); + } + } +} diff --git a/src/Avalonia.OpenGL/EglConsts.cs b/src/Avalonia.OpenGL/EglConsts.cs index 62fb3faef6..d09537eb86 100644 --- a/src/Avalonia.OpenGL/EglConsts.cs +++ b/src/Avalonia.OpenGL/EglConsts.cs @@ -192,5 +192,15 @@ namespace Avalonia.OpenGL public const int EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE = 0x320F; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE = 0x320B; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_REFERENCE_ANGLE = 0x320C; + + //EXT_device_query + public const int EGL_DEVICE_EXT = 0x322C; + + //ANGLE_device_d3d + public const int EGL_D3D9_DEVICE_ANGLE = 0x33A0; + public const int EGL_D3D11_DEVICE_ANGLE = 0x33A1; + + public const int EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE = 0x3200; + public const int EGL_D3D_TEXTURE_ANGLE = 0x33A3; } } diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 0436f6ac52..7b194e4346 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -15,7 +15,6 @@ namespace Avalonia.OpenGL private readonly int _surfaceType; public IntPtr Handle => _display; - private AngleOptions.PlatformApi? _angleApi; private int _sampleCount; private int _stencilSize; private GlVersion _version; @@ -24,56 +23,41 @@ namespace Avalonia.OpenGL { } - public EglDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs) - { - _egl = egl; + static IntPtr CreateDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs) + { + var display = IntPtr.Zero; if (platformType == -1 && platformDisplay == IntPtr.Zero) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - if (_egl.GetPlatformDisplayEXT == null) - throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); - - var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis - ?? new List {AngleOptions.PlatformApi.DirectX9}; - - foreach (var platformApi in allowedApis) - { - int dapi; - if (platformApi == AngleOptions.PlatformApi.DirectX9) - dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE; - else if (platformApi == AngleOptions.PlatformApi.DirectX11) - dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE; - else - continue; - - _display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, - new[] {EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE}); - if (_display != IntPtr.Zero) - { - _angleApi = platformApi; - break; - } - } - - if (_display == IntPtr.Zero) - throw new OpenGlException("Unable to create ANGLE display"); - } - - if (_display == IntPtr.Zero) - _display = _egl.GetDisplay(IntPtr.Zero); + if (display == IntPtr.Zero) + display = egl.GetDisplay(IntPtr.Zero); } else { - if (_egl.GetPlatformDisplayEXT == null) + if (egl.GetPlatformDisplayEXT == null) throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl"); - _display = _egl.GetPlatformDisplayEXT(platformType, platformDisplay, attrs); + display = egl.GetPlatformDisplayEXT(platformType, platformDisplay, attrs); } + + if (display == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglGetDisplay", egl); + return display; + } - if (_display == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglGetDisplay", _egl); + public EglDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs) + : this(egl, CreateDisplay(egl, platformType, platformDisplay, attrs)) + { + + } + public EglDisplay(EglInterface egl, IntPtr display) + { + _egl = egl; + _display = display; + if(_display == IntPtr.Zero) + throw new ArgumentException(); + + if (!_egl.Initialize(_display, out var major, out var minor)) throw OpenGlException.GetFormattedException("eglInitialize", _egl); @@ -172,5 +156,15 @@ namespace Avalonia.OpenGL throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl); return new EglSurface(this, _egl, s); } + + public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs) + { + var s = _egl.CreatePbufferFromClientBuffer(_display, bufferType, handle, + _config, attribs); + + if (s == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", _egl); + return new EglSurface(this, _egl, s); + } } } diff --git a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs index f59c6b7751..7e9383432c 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs @@ -20,12 +20,13 @@ namespace Avalonia.OpenGL if (feature != null) AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); } - - public static EglGlPlatformFeature TryCreate() + + public static EglGlPlatformFeature TryCreate() => TryCreate(() => new EglDisplay()); + public static EglGlPlatformFeature TryCreate(Func displayFactory) { try { - var disp = new EglDisplay(); + var disp = displayFactory(); return new EglGlPlatformFeature { _display = disp, diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs index c0665a1ea1..666c0d8351 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -17,25 +17,21 @@ namespace Avalonia.OpenGL } + public EglInterface(Func getProcAddress) : base(getProcAddress) + { + + } + public EglInterface(string library) : base(Load(library)) { } - [DllImport("libegl.dll", CharSet = CharSet.Ansi)] - static extern IntPtr eglGetProcAddress(string proc); static Func Load() { var os = AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem; if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android) return Load("libEGL.so.1"); - if (os == OperatingSystemType.WinNT) - { - var disp = eglGetProcAddress("eglGetPlatformDisplayEXT"); - if (disp == IntPtr.Zero) - throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point"); - return eglGetProcAddress; - } throw new PlatformNotSupportedException(); } @@ -147,6 +143,21 @@ namespace Avalonia.OpenGL return null; return Marshal.PtrToStringAnsi(rv); } + + public delegate IntPtr EglCreatePbufferFromClientBuffer(IntPtr display, int buftype, IntPtr buffer, IntPtr config, int[] attrib_list); + [GlEntryPoint("eglCreatePbufferFromClientBuffer")] + + public EglCreatePbufferFromClientBuffer CreatePbufferFromClientBuffer { get; } + + public delegate bool EglQueryDisplayAttribEXT(IntPtr display, int attr, out IntPtr res); + + [GlEntryPoint("eglQueryDisplayAttribEXT"), GlOptionalEntryPoint] + public EglQueryDisplayAttribEXT QueryDisplayAttribExt { get; } + + public delegate bool EglQueryDeviceAttribEXT(IntPtr display, int attr, out IntPtr res); + + [GlEntryPoint("eglQueryDeviceAttribEXT"), GlOptionalEntryPoint] + public EglQueryDisplayAttribEXT QueryDeviceAttribExt { get; } // ReSharper restore UnassignedGetOnlyAutoProperty } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index 585e68056b..bd188ad53a 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -1,4 +1,5 @@ using Avalonia.OpenGL; +using Avalonia.OpenGL.Angle; namespace Avalonia.Win32 { @@ -15,7 +16,7 @@ namespace Avalonia.Win32 { if (!s_attemptedToInitialize) { - EglFeature = EglGlPlatformFeature.TryCreate(); + EglFeature = EglGlPlatformFeature.TryCreate(() => new AngleWin32EglDisplay()); s_attemptedToInitialize = true; } From a34fefc998d9dce6416b8d7de8f89f86869cb3a7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 25 Jun 2020 21:32:29 +0300 Subject: [PATCH 002/829] Allow more customization for EglGlPlatformSurface --- src/Avalonia.OpenGL/EglGlPlatformSurface.cs | 84 ++------------ .../EglGlPlatformSurfaceBase.cs | 103 ++++++++++++++++++ 2 files changed, 112 insertions(+), 75 deletions(-) create mode 100644 src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs index 3e4befe2c6..21fadff19e 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs @@ -3,33 +3,26 @@ using System.Threading; namespace Avalonia.OpenGL { - public class EglGlPlatformSurface : IGlPlatformSurface + public class EglGlPlatformSurface : EglGlPlatformSurfaceBase { - public interface IEglWindowGlPlatformSurfaceInfo - { - IntPtr Handle { get; } - PixelSize Size { get; } - double Scaling { get; } - } - private readonly EglDisplay _display; private readonly EglContext _context; private readonly IEglWindowGlPlatformSurfaceInfo _info; - public EglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) + public EglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base() { _display = context.Display; _context = context; _info = info; } - - public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + + public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() { var glSurface = _display.CreateWindowSurface(_info.Handle); return new RenderTarget(_display, _context, glSurface, _info); } - class RenderTarget : IGlPlatformSurfaceRenderTargetWithCorruptionInfo + class RenderTarget : EglPlatformSurfaceRenderTargetBase { private readonly EglDisplay _display; private readonly EglContext _context; @@ -38,7 +31,7 @@ namespace Avalonia.OpenGL private PixelSize _initialSize; public RenderTarget(EglDisplay display, EglContext context, - EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) + EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context) { _display = display; _context = context; @@ -47,70 +40,11 @@ namespace Avalonia.OpenGL _initialSize = info.Size; } - public void Dispose() => _glSurface.Dispose(); + public override void Dispose() => _glSurface.Dispose(); - public bool IsCorrupted => _initialSize != _info.Size; - - public IGlPlatformSurfaceRenderingSession BeginDraw() - { - var l = _context.Lock(); - try - { - if (IsCorrupted) - throw new RenderTargetCorruptedException(); - var restoreContext = _context.MakeCurrent(_glSurface); - _display.EglInterface.WaitClient(); - _display.EglInterface.WaitGL(); - _display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); - - return new Session(_display, _context, _glSurface, _info, l, restoreContext); - } - catch - { - l.Dispose(); - throw; - } - } + public override bool IsCorrupted => _initialSize != _info.Size; - class Session : IGlPlatformSurfaceRenderingSession - { - private readonly EglContext _context; - private readonly EglSurface _glSurface; - private readonly IEglWindowGlPlatformSurfaceInfo _info; - private readonly EglDisplay _display; - private IDisposable _lock; - private readonly IDisposable _restoreContext; - - - public Session(EglDisplay display, EglContext context, - EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info, - IDisposable @lock, IDisposable restoreContext) - { - _context = context; - _display = display; - _glSurface = glSurface; - _info = info; - _lock = @lock; - _restoreContext = restoreContext; - } - - 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(); - } - - public IGlContext Context => _context; - public PixelSize Size => _info.Size; - public double Scaling => _info.Scaling; - public bool IsYFlipped { get; } - } + public override IGlPlatformSurfaceRenderingSession BeginDraw() => base.BeginDraw(_glSurface, _info); } } } diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs b/src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs new file mode 100644 index 0000000000..00c7c4796c --- /dev/null +++ b/src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs @@ -0,0 +1,103 @@ +using System; + +namespace Avalonia.OpenGL +{ + public abstract class EglGlPlatformSurfaceBase : IGlPlatformSurface + { + public interface IEglWindowGlPlatformSurfaceInfo + { + IntPtr Handle { get; } + PixelSize Size { get; } + double Scaling { get; } + } + + public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(); + } + + public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTargetWithCorruptionInfo + { + private readonly EglDisplay _display; + private readonly EglContext _context; + + protected EglPlatformSurfaceRenderTargetBase(EglDisplay display, EglContext context) + { + _display = display; + _context = context; + } + + public abstract bool IsCorrupted { get; } + + public virtual void Dispose() + { + + } + + public abstract IGlPlatformSurfaceRenderingSession BeginDraw(); + + protected IGlPlatformSurfaceRenderingSession BeginDraw(EglSurface surface, + EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, Action onFinish = null, bool isYFlipped = false) + { + var l = _context.Lock(); + try + { + if (IsCorrupted) + throw new RenderTargetCorruptedException(); + var restoreContext = _context.MakeCurrent(surface); + _display.EglInterface.WaitClient(); + _display.EglInterface.WaitGL(); + _display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); + + return new Session(_display, _context, surface, info, l, restoreContext, onFinish, isYFlipped); + } + catch + { + l.Dispose(); + throw; + } + } + + 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 92880f6d037de3a5e62f770a32bda4a33edc92bc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 Jun 2020 17:32:02 -0300 Subject: [PATCH 003/829] 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 004/829] 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 005/829] 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 006/829] 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 007/829] 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 008/829] 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 009/829] 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 010/829] 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 011/829] 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 012/829] 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 013/829] 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 014/829] 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 a42e8cbdede4d8d2509eaf5ac8a848ef62835351 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 22:19:33 +0200 Subject: [PATCH 015/829] Nullable enable Avalonia.Input. --- src/Avalonia.Input/AccessKeyHandler.cs | 22 +++--- src/Avalonia.Input/Avalonia.Input.csproj | 2 + src/Avalonia.Input/DataObject.cs | 6 +- src/Avalonia.Input/DragDropDevice.cs | 6 +- src/Avalonia.Input/FocusManager.cs | 47 ++++++------ .../GestureRecognizerCollection.cs | 16 ++-- .../ScrollGestureRecognizer.cs | 15 ++-- src/Avalonia.Input/Gestures.cs | 9 ++- src/Avalonia.Input/IAccessKeyHandler.cs | 2 +- src/Avalonia.Input/IDataObject.cs | 6 +- src/Avalonia.Input/IFocusManager.cs | 6 +- src/Avalonia.Input/IInputElement.cs | 2 +- src/Avalonia.Input/IInputRoot.cs | 4 +- src/Avalonia.Input/IKeyboardDevice.cs | 4 +- src/Avalonia.Input/IPointer.cs | 4 +- src/Avalonia.Input/IPointerDevice.cs | 4 +- src/Avalonia.Input/InputElement.cs | 8 +- src/Avalonia.Input/InputExtensions.cs | 2 +- src/Avalonia.Input/KeyEventArgs.cs | 2 +- src/Avalonia.Input/KeyboardDevice.cs | 8 +- .../KeyboardNavigationHandler.cs | 13 ++-- src/Avalonia.Input/MouseDevice.cs | 73 ++++++++++--------- .../Navigation/TabNavigation.cs | 42 ++++++----- src/Avalonia.Input/Pointer.cs | 9 +-- src/Avalonia.Input/PointerEventArgs.cs | 14 ++-- src/Avalonia.Input/Raw/RawDragEventType.cs | 2 +- src/Avalonia.Input/Raw/RawInputEventArgs.cs | 2 +- src/Avalonia.Input/TextInputEventArgs.cs | 4 +- 28 files changed, 172 insertions(+), 162 deletions(-) diff --git a/src/Avalonia.Input/AccessKeyHandler.cs b/src/Avalonia.Input/AccessKeyHandler.cs index 96f0bb59b3..660584e2ed 100644 --- a/src/Avalonia.Input/AccessKeyHandler.cs +++ b/src/Avalonia.Input/AccessKeyHandler.cs @@ -28,7 +28,7 @@ namespace Avalonia.Input /// /// The window to which the handler belongs. /// - private IInputRoot _owner; + private IInputRoot? _owner; /// /// Whether access keys are currently being shown; @@ -48,17 +48,17 @@ namespace Avalonia.Input /// /// Element to restore following AltKey taking focus. /// - private IInputElement _restoreFocusElement; + private IInputElement? _restoreFocusElement; /// /// The window's main menu. /// - private IMainMenu _mainMenu; + private IMainMenu? _mainMenu; /// /// Gets or sets the window's main menu. /// - public IMainMenu MainMenu + public IMainMenu? MainMenu { get => _mainMenu; set @@ -86,14 +86,12 @@ namespace Avalonia.Input /// public void SetOwner(IInputRoot owner) { - Contract.Requires(owner != null); - if (_owner != null) { throw new InvalidOperationException("AccessKeyHandler owner has already been set."); } - _owner = owner; + _owner = owner ?? throw new ArgumentNullException(nameof(owner)); _owner.AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel); _owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown, RoutingStrategies.Bubble); @@ -149,7 +147,7 @@ namespace Avalonia.Input // When Alt is pressed without a main menu, or with a closed main menu, show // access key markers in the window (i.e. "_File"). - _owner.ShowAccessKeys = _showingAccessKeys = true; + _owner!.ShowAccessKeys = _showingAccessKeys = true; } else { @@ -241,7 +239,7 @@ namespace Avalonia.Input { if (_showingAccessKeys) { - _owner.ShowAccessKeys = false; + _owner!.ShowAccessKeys = false; } } @@ -250,13 +248,13 @@ namespace Avalonia.Input /// private void CloseMenu() { - MainMenu.Close(); - _owner.ShowAccessKeys = _showingAccessKeys = false; + MainMenu!.Close(); + _owner!.ShowAccessKeys = _showingAccessKeys = false; } private void MainMenuClosed(object sender, EventArgs e) { - _owner.ShowAccessKeys = false; + _owner!.ShowAccessKeys = false; } } } diff --git a/src/Avalonia.Input/Avalonia.Input.csproj b/src/Avalonia.Input/Avalonia.Input.csproj index ea560ce2ea..58927e2c93 100644 --- a/src/Avalonia.Input/Avalonia.Input.csproj +++ b/src/Avalonia.Input/Avalonia.Input.csproj @@ -1,6 +1,8 @@  netstandard2.0 + Enable + CS8600;CS8602;CS8603 diff --git a/src/Avalonia.Input/DataObject.cs b/src/Avalonia.Input/DataObject.cs index 60d7d67606..688f5f9cc8 100644 --- a/src/Avalonia.Input/DataObject.cs +++ b/src/Avalonia.Input/DataObject.cs @@ -11,7 +11,7 @@ namespace Avalonia.Input return _items.ContainsKey(dataFormat); } - public object Get(string dataFormat) + public object? Get(string dataFormat) { if (_items.ContainsKey(dataFormat)) return _items[dataFormat]; @@ -23,12 +23,12 @@ namespace Avalonia.Input return _items.Keys; } - public IEnumerable GetFileNames() + public IEnumerable? GetFileNames() { return Get(DataFormats.FileNames) as IEnumerable; } - public string GetText() + public string? GetText() { return Get(DataFormats.Text) as string; } diff --git a/src/Avalonia.Input/DragDropDevice.cs b/src/Avalonia.Input/DragDropDevice.cs index bcd962bc31..30a08eda17 100644 --- a/src/Avalonia.Input/DragDropDevice.cs +++ b/src/Avalonia.Input/DragDropDevice.cs @@ -9,9 +9,9 @@ namespace Avalonia.Input { public static readonly DragDropDevice Instance = new DragDropDevice(); - private Interactive _lastTarget = null; + private Interactive? _lastTarget = null; - private Interactive GetTarget(IInputRoot root, Point local) + private Interactive? GetTarget(IInputRoot root, Point local) { var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault(); if (target != null && DragDrop.GetAllowDrop(target)) @@ -19,7 +19,7 @@ namespace Avalonia.Input return null; } - private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers) + private DragDropEffects RaiseDragEvent(Interactive? target, IInputRoot inputRoot, Point point, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers) { if (target == null) return DragDropEffects.None; diff --git a/src/Avalonia.Input/FocusManager.cs b/src/Avalonia.Input/FocusManager.cs index 66355da8b9..a1f1478f51 100644 --- a/src/Avalonia.Input/FocusManager.cs +++ b/src/Avalonia.Input/FocusManager.cs @@ -15,8 +15,8 @@ namespace Avalonia.Input /// /// The focus scopes in which the focus is currently defined. /// - private readonly ConditionalWeakTable _focusScopes = - new ConditionalWeakTable(); + private readonly ConditionalWeakTable _focusScopes = + new ConditionalWeakTable(); /// /// Initializes a new instance of the class. @@ -37,12 +37,12 @@ namespace Avalonia.Input /// /// Gets the currently focused . /// - public IInputElement Current => KeyboardDevice.Instance?.FocusedElement; + public IInputElement? Current => KeyboardDevice.Instance?.FocusedElement; /// /// Gets the current focus scope. /// - public IFocusScope Scope + public IFocusScope? Scope { get; private set; @@ -55,7 +55,7 @@ namespace Avalonia.Input /// The method by which focus was changed. /// Any key modifiers active at the time of focus. public void Focus( - IInputElement control, + IInputElement? control, NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None) { @@ -75,17 +75,18 @@ namespace Avalonia.Input // If control is null, set focus to the topmost focus scope. foreach (var scope in GetFocusScopeAncestors(Current).Reverse().ToList()) { - IInputElement element; - - if (_focusScopes.TryGetValue(scope, out element) && element != null) + if (_focusScopes.TryGetValue(scope, out var element) && element != null) { Focus(element, method); return; } } - // Couldn't find a focus scope, clear focus. - SetFocusedElement(Scope, null); + if (Scope is object) + { + // Couldn't find a focus scope, clear focus. + SetFocusedElement(Scope, null); + } } } @@ -102,13 +103,13 @@ namespace Avalonia.Input /// public void SetFocusedElement( IFocusScope scope, - IInputElement element, + IInputElement? element, NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None) { - Contract.Requires(scope != null); + scope = scope ?? throw new ArgumentNullException(nameof(scope)); - if (_focusScopes.TryGetValue(scope, out IInputElement existingElement)) + if (_focusScopes.TryGetValue(scope, out var existingElement)) { if (element != existingElement) { @@ -133,11 +134,9 @@ namespace Avalonia.Input /// The new focus scope. public void SetFocusScope(IFocusScope scope) { - Contract.Requires(scope != null); + scope = scope ?? throw new ArgumentNullException(nameof(scope)); - IInputElement e; - - if (!_focusScopes.TryGetValue(scope, out e)) + if (!_focusScopes.TryGetValue(scope, out var e)) { // TODO: Make this do something useful, i.e. select the first focusable // control, select a control that the user has specified to have default @@ -164,17 +163,19 @@ namespace Avalonia.Input /// The focus scopes. private static IEnumerable GetFocusScopeAncestors(IInputElement control) { - while (control != null) + IInputElement? c = control; + + while (c != null) { - var scope = control as IFocusScope; + var scope = c as IFocusScope; - if (scope != null && control.VisualRoot?.IsVisible == true) + if (scope != null && c.VisualRoot?.IsVisible == true) { yield return scope; } - control = control.GetVisualParent() ?? - ((control as IHostedVisualTreeRoot)?.Host as IInputElement); + c = c.GetVisualParent() ?? + ((c as IHostedVisualTreeRoot)?.Host as IInputElement); } } @@ -190,7 +191,7 @@ namespace Avalonia.Input if (sender == e.Source && ev.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { - IVisual element = ev.Pointer?.Captured ?? e.Source as IInputElement; + IVisual? element = ev.Pointer?.Captured ?? e.Source as IInputElement; while (element != null) { diff --git a/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs index 112abb1a4e..54ef0b1a68 100644 --- a/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -1,8 +1,6 @@ -using System; using System.Collections; using System.Collections.Generic; using Avalonia.Controls; -using Avalonia.Data; using Avalonia.LogicalTree; using Avalonia.Styling; @@ -11,8 +9,8 @@ namespace Avalonia.Input.GestureRecognizers public class GestureRecognizerCollection : IReadOnlyCollection, IGestureRecognizerActionsDispatcher { private readonly IInputElement _inputElement; - private List _recognizers; - private Dictionary _pointerGrabs; + private List? _recognizers; + private Dictionary? _pointerGrabs; public GestureRecognizerCollection(IInputElement inputElement) @@ -72,7 +70,7 @@ namespace Avalonia.Input.GestureRecognizers { if (_recognizers == null) return false; - if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) + if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture)) { capture.PointerReleased(e); } @@ -90,7 +88,7 @@ namespace Avalonia.Input.GestureRecognizers { if (_recognizers == null) return false; - if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) + if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture)) { capture.PointerMoved(e); } @@ -108,7 +106,7 @@ namespace Avalonia.Input.GestureRecognizers { if (_recognizers == null) return; - _pointerGrabs.Remove(e.Pointer); + _pointerGrabs!.Remove(e.Pointer); foreach (var r in _recognizers) { r.PointerCaptureLost(e.Pointer); @@ -118,8 +116,8 @@ namespace Avalonia.Input.GestureRecognizers void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) { pointer.Capture(_inputElement); - _pointerGrabs[pointer] = recognizer; - foreach (var r in _recognizers) + _pointerGrabs![pointer] = recognizer; + foreach (var r in _recognizers!) { if (r != recognizer) r.PointerCaptureLost(pointer); diff --git a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs index e022401c8e..3858cc04f2 100644 --- a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using Avalonia.Interactivity; using Avalonia.Threading; namespace Avalonia.Input.GestureRecognizers @@ -11,9 +10,9 @@ namespace Avalonia.Input.GestureRecognizers { private bool _scrolling; private Point _trackedRootPoint; - private IPointer _tracking; - private IInputElement _target; - private IGestureRecognizerActionsDispatcher _actions; + private IPointer? _tracking; + private IInputElement? _target; + private IGestureRecognizerActionsDispatcher? _actions; private bool _canHorizontallyScroll; private bool _canVerticallyScroll; private int _gestureId; @@ -95,7 +94,7 @@ namespace Avalonia.Input.GestureRecognizers _scrolling = true; if (_scrolling) { - _actions.Capture(e.Pointer, this); + _actions!.Capture(e.Pointer, this); } } @@ -110,7 +109,7 @@ namespace Avalonia.Input.GestureRecognizers _trackedRootPoint = rootPoint; if (elapsed.TotalSeconds > 0) _inertia = vector / elapsed.TotalSeconds; - _target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); + _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); e.Handled = true; } } @@ -128,7 +127,7 @@ namespace Avalonia.Input.GestureRecognizers { _inertia = default; _scrolling = false; - _target.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); + _target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); _gestureId = 0; _lastMoveTimestamp = null; } @@ -165,7 +164,7 @@ namespace Avalonia.Input.GestureRecognizers var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds); var distance = speed * elapsedSinceLastTick.TotalSeconds; - _target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); + _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index 0efc20b196..5751719d61 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -29,7 +29,9 @@ namespace Avalonia.Input RoutedEvent.Register( "ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. private static WeakReference s_lastPress = new WeakReference(null); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. static Gestures() { @@ -69,6 +71,11 @@ namespace Avalonia.Input private static void PointerPressed(RoutedEventArgs ev) { + if (ev.Source is null) + { + return; + } + if (ev.Route == RoutingStrategies.Bubble) { var e = (PointerPressedEventArgs)ev; @@ -76,7 +83,7 @@ namespace Avalonia.Input if (e.ClickCount <= 1) { - s_lastPress = new WeakReference(e.Source); + s_lastPress = new WeakReference(ev.Source); } else if (s_lastPress != null && e.ClickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { diff --git a/src/Avalonia.Input/IAccessKeyHandler.cs b/src/Avalonia.Input/IAccessKeyHandler.cs index 3e6510320f..e484d003c7 100644 --- a/src/Avalonia.Input/IAccessKeyHandler.cs +++ b/src/Avalonia.Input/IAccessKeyHandler.cs @@ -8,7 +8,7 @@ namespace Avalonia.Input /// /// Gets or sets the window's main menu. /// - IMainMenu MainMenu { get; set; } + IMainMenu? MainMenu { get; set; } /// /// Sets the owner of the access key handler. diff --git a/src/Avalonia.Input/IDataObject.cs b/src/Avalonia.Input/IDataObject.cs index 1aa8fd63d5..1db008aa3a 100644 --- a/src/Avalonia.Input/IDataObject.cs +++ b/src/Avalonia.Input/IDataObject.cs @@ -23,17 +23,17 @@ namespace Avalonia.Input /// Returns the dragged text if the DataObject contains any text. /// /// - string GetText(); + string? GetText(); /// /// Returns a list of filenames if the DataObject contains filenames. /// /// - IEnumerable GetFileNames(); + IEnumerable? GetFileNames(); /// /// Tries to get the data of the given DataFormat. /// - object Get(string dataFormat); + object? Get(string dataFormat); } } diff --git a/src/Avalonia.Input/IFocusManager.cs b/src/Avalonia.Input/IFocusManager.cs index 9122cc428d..e1b5087c3d 100644 --- a/src/Avalonia.Input/IFocusManager.cs +++ b/src/Avalonia.Input/IFocusManager.cs @@ -8,12 +8,12 @@ namespace Avalonia.Input /// /// Gets the currently focused . /// - IInputElement Current { get; } + IInputElement? Current { get; } /// /// Gets the current focus scope. /// - IFocusScope Scope { get; } + IFocusScope? Scope { get; } /// /// Focuses a control. @@ -22,7 +22,7 @@ namespace Avalonia.Input /// The method by which focus was changed. /// Any key modifiers active at the time of focus. void Focus( - IInputElement control, + IInputElement? control, NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None); diff --git a/src/Avalonia.Input/IInputElement.cs b/src/Avalonia.Input/IInputElement.cs index c30d74c965..12fec82368 100644 --- a/src/Avalonia.Input/IInputElement.cs +++ b/src/Avalonia.Input/IInputElement.cs @@ -78,7 +78,7 @@ namespace Avalonia.Input /// /// Gets or sets the associated mouse cursor. /// - Cursor Cursor { get; } + Cursor? Cursor { get; } /// /// Gets a value indicating whether this control and all its parents are enabled. diff --git a/src/Avalonia.Input/IInputRoot.cs b/src/Avalonia.Input/IInputRoot.cs index eeb7e4323e..3e2b8cc477 100644 --- a/src/Avalonia.Input/IInputRoot.cs +++ b/src/Avalonia.Input/IInputRoot.cs @@ -20,7 +20,7 @@ namespace Avalonia.Input /// /// Gets or sets the input element that the pointer is currently over. /// - IInputElement PointerOverElement { get; set; } + IInputElement? PointerOverElement { get; set; } /// /// Gets or sets a value indicating whether access keys are shown in the window. @@ -31,6 +31,6 @@ namespace Avalonia.Input /// Gets associated mouse device /// [CanBeNull] - IMouseDevice MouseDevice { get; } + IMouseDevice? MouseDevice { get; } } } diff --git a/src/Avalonia.Input/IKeyboardDevice.cs b/src/Avalonia.Input/IKeyboardDevice.cs index ba7e0484ee..9506dc36fb 100644 --- a/src/Avalonia.Input/IKeyboardDevice.cs +++ b/src/Avalonia.Input/IKeyboardDevice.cs @@ -58,10 +58,10 @@ namespace Avalonia.Input public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged { - IInputElement FocusedElement { get; } + IInputElement? FocusedElement { get; } void SetFocusedElement( - IInputElement element, + IInputElement? element, NavigationMethod method, KeyModifiers modifiers); } diff --git a/src/Avalonia.Input/IPointer.cs b/src/Avalonia.Input/IPointer.cs index a3f051ce7f..7af48cef82 100644 --- a/src/Avalonia.Input/IPointer.cs +++ b/src/Avalonia.Input/IPointer.cs @@ -3,8 +3,8 @@ namespace Avalonia.Input public interface IPointer { int Id { get; } - void Capture(IInputElement control); - IInputElement Captured { get; } + void Capture(IInputElement? control); + IInputElement? Captured { get; } PointerType Type { get; } bool IsPrimary { get; } diff --git a/src/Avalonia.Input/IPointerDevice.cs b/src/Avalonia.Input/IPointerDevice.cs index bf001dda15..1f82cb1ed7 100644 --- a/src/Avalonia.Input/IPointerDevice.cs +++ b/src/Avalonia.Input/IPointerDevice.cs @@ -6,10 +6,10 @@ namespace Avalonia.Input public interface IPointerDevice : IInputDevice { [Obsolete("Use IPointer")] - IInputElement Captured { get; } + IInputElement? Captured { get; } [Obsolete("Use IPointer")] - void Capture(IInputElement control); + void Capture(IInputElement? control); [Obsolete("Use PointerEventArgs.GetPosition")] Point GetPosition(IVisual relativeTo); diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 0616c70d82..25f2d553d7 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -37,8 +37,8 @@ namespace Avalonia.Input /// /// Gets or sets associated mouse cursor. /// - public static readonly StyledProperty CursorProperty = - AvaloniaProperty.Register(nameof(Cursor), null, true); + public static readonly StyledProperty CursorProperty = + AvaloniaProperty.Register(nameof(Cursor), null, true); /// /// Defines the property. @@ -160,7 +160,7 @@ namespace Avalonia.Input private bool _isFocused; private bool _isFocusVisible; private bool _isPointerOver; - private GestureRecognizerCollection _gestureRecognizers; + private GestureRecognizerCollection? _gestureRecognizers; /// /// Initializes static members of the class. @@ -336,7 +336,7 @@ namespace Avalonia.Input /// /// Gets or sets associated mouse cursor. /// - public Cursor Cursor + public Cursor? Cursor { get { return GetValue(CursorProperty); } set { SetValue(CursorProperty, value); } diff --git a/src/Avalonia.Input/InputExtensions.cs b/src/Avalonia.Input/InputExtensions.cs index 4babe711f2..0c7615a472 100644 --- a/src/Avalonia.Input/InputExtensions.cs +++ b/src/Avalonia.Input/InputExtensions.cs @@ -33,7 +33,7 @@ namespace Avalonia.Input /// The element to test. /// The point on . /// The topmost at the specified position. - public static IInputElement InputHitTest(this IInputElement element, Point p) + public static IInputElement? InputHitTest(this IInputElement element, Point p) { Contract.Requires(element != null); diff --git a/src/Avalonia.Input/KeyEventArgs.cs b/src/Avalonia.Input/KeyEventArgs.cs index 267376262b..67cd5a520a 100644 --- a/src/Avalonia.Input/KeyEventArgs.cs +++ b/src/Avalonia.Input/KeyEventArgs.cs @@ -5,7 +5,7 @@ namespace Avalonia.Input { public class KeyEventArgs : RoutedEventArgs { - public IKeyboardDevice Device { get; set; } + public IKeyboardDevice? Device { get; set; } public Key Key { get; set; } diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 0321b0bdf3..187670a26b 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -8,9 +8,9 @@ namespace Avalonia.Input { public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged { - private IInputElement _focusedElement; + private IInputElement? _focusedElement; - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService(); @@ -18,7 +18,7 @@ namespace Avalonia.Input public IFocusManager FocusManager => AvaloniaLocator.Current.GetService(); - public IInputElement FocusedElement + public IInputElement? FocusedElement { get { @@ -33,7 +33,7 @@ namespace Avalonia.Input } public void SetFocusedElement( - IInputElement element, + IInputElement? element, NavigationMethod method, KeyModifiers keyModifiers) { diff --git a/src/Avalonia.Input/KeyboardNavigationHandler.cs b/src/Avalonia.Input/KeyboardNavigationHandler.cs index c425eeeedb..dbefe63789 100644 --- a/src/Avalonia.Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Input/KeyboardNavigationHandler.cs @@ -13,7 +13,7 @@ namespace Avalonia.Input /// /// The window to which the handler belongs. /// - private IInputRoot _owner; + private IInputRoot? _owner; /// /// Sets the owner of the keyboard navigation handler. @@ -24,15 +24,12 @@ namespace Avalonia.Input /// public void SetOwner(IInputRoot owner) { - Contract.Requires(owner != null); - if (_owner != null) { throw new InvalidOperationException("AccessKeyHandler owner has already been set."); } - _owner = owner; - + _owner = owner ?? throw new ArgumentNullException(nameof(owner)); _owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown); } @@ -45,11 +42,11 @@ namespace Avalonia.Input /// The next element in the specified direction, or null if /// was the last in the requested direction. /// - public static IInputElement GetNext( + public static IInputElement? GetNext( IInputElement element, NavigationDirection direction) { - Contract.Requires(element != null); + element = element ?? throw new ArgumentNullException(nameof(element)); var customHandler = element.GetSelfAndVisualAncestors() .OfType() @@ -97,7 +94,7 @@ namespace Avalonia.Input NavigationDirection direction, KeyModifiers keyModifiers = KeyModifiers.None) { - Contract.Requires(element != null); + element = element ?? throw new ArgumentNullException(nameof(element)); var next = GetNext(element, direction); diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 188ddd9835..cec5029c18 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -20,7 +20,7 @@ namespace Avalonia.Input private readonly Pointer _pointer; private bool _disposed; - public MouseDevice(Pointer pointer = null) + public MouseDevice(Pointer? pointer = null) { _pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); } @@ -34,7 +34,7 @@ namespace Avalonia.Input /// method. /// [Obsolete("Use IPointer instead")] - public IInputElement Captured => _pointer.Captured; + public IInputElement? Captured => _pointer.Captured; /// /// Gets the mouse position, in screen coordinates. @@ -54,7 +54,7 @@ namespace Avalonia.Input /// within the control's bounds or not. The current mouse capture control is exposed /// by the property. /// - public void Capture(IInputElement control) + public void Capture(IInputElement? control) { _pointer.Capture(control); } @@ -66,7 +66,7 @@ namespace Avalonia.Input /// The mouse position in the control's coordinates. public Point GetPosition(IVisual relativeTo) { - Contract.Requires(relativeTo != null); + relativeTo = relativeTo ?? throw new ArgumentNullException(nameof(relativeTo)); if (relativeTo.VisualRoot == null) { @@ -75,7 +75,7 @@ namespace Avalonia.Input var rootPoint = relativeTo.VisualRoot.PointToClient(Position); var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo); - return rootPoint * transform.Value; + return rootPoint * transform!.Value; } public void ProcessRawEvent(RawInputEventArgs e) @@ -126,7 +126,7 @@ namespace Avalonia.Input private void ProcessRawEvent(RawPointerEventArgs e) { - Contract.Requires(e != null); + e = e ?? throw new ArgumentNullException(nameof(e)); var mouse = (MouseDevice)e.Device; if(mouse._disposed) @@ -173,8 +173,8 @@ namespace Avalonia.Input private void LeaveWindow(IMouseDevice device, ulong timestamp, IInputRoot root, PointerPointProperties properties, KeyModifiers inputModifiers) { - Contract.Requires(device != null); - Contract.Requires(root != null); + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); ClearPointerOver(this, timestamp, root, properties, inputModifiers); } @@ -214,8 +214,8 @@ namespace Avalonia.Input PointerPointProperties properties, KeyModifiers inputModifiers) { - Contract.Requires(device != null); - Contract.Requires(root != null); + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); var hit = HitTest(root, p); @@ -250,10 +250,10 @@ namespace Avalonia.Input private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties, KeyModifiers inputModifiers) { - Contract.Requires(device != null); - Contract.Requires(root != null); + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); - IInputElement source; + IInputElement? source; if (_pointer.Captured == null) { @@ -265,18 +265,23 @@ namespace Avalonia.Input source = _pointer.Captured; } - var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root, - p, timestamp, properties, inputModifiers); + if (source is object) + { + var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root, + p, timestamp, properties, inputModifiers); - source?.RaiseEvent(e); - return e.Handled; + source.RaiseEvent(e); + return e.Handled; + } + + return false; } private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props, KeyModifiers inputModifiers) { - Contract.Requires(device != null); - Contract.Requires(root != null); + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); var hit = HitTest(root, p); @@ -298,8 +303,8 @@ namespace Avalonia.Input PointerPointProperties props, Vector delta, KeyModifiers inputModifiers) { - Contract.Requires(device != null); - Contract.Requires(root != null); + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); var hit = HitTest(root, p); @@ -317,21 +322,21 @@ namespace Avalonia.Input private IInteractive GetSource(IVisual hit) { - Contract.Requires(hit != null); + hit = hit ?? throw new ArgumentNullException(nameof(hit)); return _pointer.Captured ?? (hit as IInteractive) ?? hit.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); } - private IInputElement HitTest(IInputElement root, Point p) + private IInputElement? HitTest(IInputElement root, Point p) { - Contract.Requires(root != null); + root = root ?? throw new ArgumentNullException(nameof(root)); return _pointer.Captured ?? root.InputHitTest(p); } - PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive source, + PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive? source, PointerPointProperties properties, KeyModifiers inputModifiers) { @@ -343,8 +348,8 @@ namespace Avalonia.Input PointerPointProperties properties, KeyModifiers inputModifiers) { - Contract.Requires(device != null); - Contract.Requires(root != null); + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); var element = root.PointerOverElement; var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, element, properties, inputModifiers); @@ -384,12 +389,12 @@ namespace Avalonia.Input } } - private IInputElement SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, Point p, + private IInputElement? SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties, KeyModifiers inputModifiers) { - Contract.Requires(device != null); - Contract.Requires(root != null); + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); var element = root.InputHitTest(p); @@ -412,11 +417,11 @@ namespace Avalonia.Input PointerPointProperties properties, KeyModifiers inputModifiers) { - Contract.Requires(device != null); - Contract.Requires(root != null); - Contract.Requires(element != null); + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); + element = element ?? throw new ArgumentNullException(nameof(element)); - IInputElement branch = null; + IInputElement? branch = null; var el = element; diff --git a/src/Avalonia.Input/Navigation/TabNavigation.cs b/src/Avalonia.Input/Navigation/TabNavigation.cs index cd377f1df6..6f6d68940b 100644 --- a/src/Avalonia.Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Input/Navigation/TabNavigation.cs @@ -22,15 +22,17 @@ namespace Avalonia.Input.Navigation /// The next element in the specified direction, or null if /// was the last in the requested direction. /// - public static IInputElement GetNextInTabOrder( + public static IInputElement? GetNextInTabOrder( IInputElement element, NavigationDirection direction, bool outsideElement = false) { - Contract.Requires(element != null); - Contract.Requires( - direction == NavigationDirection.Next || - direction == NavigationDirection.Previous); + element = element ?? throw new ArgumentNullException(nameof(element)); + + if (direction != NavigationDirection.Next && direction != NavigationDirection.Previous) + { + throw new ArgumentException("Invalid direction: must be Next or Previous."); + } var container = element.GetVisualParent(); @@ -110,7 +112,7 @@ namespace Avalonia.Input.Navigation if (customNext.handled) { - yield return customNext.next; + yield return customNext.next!; } else { @@ -143,12 +145,14 @@ namespace Avalonia.Input.Navigation /// If true will not descend into to find next control. /// /// The next element, or null if the element is the last. - private static IInputElement GetNextInContainer( + private static IInputElement? GetNextInContainer( IInputElement element, IInputElement container, NavigationDirection direction, bool outsideElement) { + IInputElement? e = element; + if (direction == NavigationDirection.Next && !outsideElement) { var descendant = GetFocusableDescendants(element, direction).FirstOrDefault(); @@ -167,13 +171,13 @@ namespace Avalonia.Input.Navigation // INavigableContainer. if (navigable != null) { - while (element != null) + while (e != null) { - element = navigable.GetControl(direction, element, false); + e = navigable.GetControl(direction, e, false); - if (element != null && - element.CanFocus() && - KeyboardNavigation.GetIsTabStop((InputElement) element)) + if (e != null && + e.CanFocus() && + KeyboardNavigation.GetIsTabStop((InputElement)e)) { break; } @@ -183,12 +187,12 @@ namespace Avalonia.Input.Navigation { // TODO: Do a spatial search here if the container doesn't implement // INavigableContainer. - element = null; + e = null; } - if (element != null && direction == NavigationDirection.Previous) + if (e != null && direction == NavigationDirection.Previous) { - var descendant = GetFocusableDescendants(element, direction).LastOrDefault(); + var descendant = GetFocusableDescendants(e, direction).LastOrDefault(); if (descendant != null) { @@ -196,7 +200,7 @@ namespace Avalonia.Input.Navigation } } - return element; + return e; } return null; @@ -209,13 +213,13 @@ namespace Avalonia.Input.Navigation /// The container. /// The direction of the search. /// The first element, or null if there are no more elements. - private static IInputElement GetFirstInNextContainer( + private static IInputElement? GetFirstInNextContainer( IInputElement element, IInputElement container, NavigationDirection direction) { var parent = container.GetVisualParent(); - IInputElement next = null; + IInputElement? next = null; if (parent != null) { @@ -268,7 +272,7 @@ namespace Avalonia.Input.Navigation return next; } - private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, + private static (bool handled, IInputElement? next) GetCustomNext(IInputElement element, NavigationDirection direction) { if (element is ICustomKeyboardNavigation custom) diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs index 00222e92cf..a477711584 100644 --- a/src/Avalonia.Input/Pointer.cs +++ b/src/Avalonia.Input/Pointer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Avalonia.Interactivity; using Avalonia.VisualTree; namespace Avalonia.Input @@ -20,7 +19,7 @@ namespace Avalonia.Input public int Id { get; } - IInputElement FindCommonParent(IInputElement control1, IInputElement control2) + IInputElement? FindCommonParent(IInputElement? control1, IInputElement? control2) { if (control1 == null || control2 == null) return null; @@ -28,12 +27,12 @@ namespace Avalonia.Input return control2.GetSelfAndVisualAncestors().OfType().FirstOrDefault(seen.Contains); } - protected virtual void PlatformCapture(IInputElement element) + protected virtual void PlatformCapture(IInputElement? element) { } - public void Capture(IInputElement control) + public void Capture(IInputElement? control) { if (Captured != null) Captured.DetachedFromVisualTree -= OnCaptureDetached; @@ -66,7 +65,7 @@ namespace Avalonia.Input } - public IInputElement Captured { get; private set; } + public IInputElement? Captured { get; private set; } public PointerType Type { get; } public bool IsPrimary { get; } diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index 9cc42ffa69..1cbddf89aa 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/src/Avalonia.Input/PointerEventArgs.cs @@ -7,14 +7,14 @@ namespace Avalonia.Input { public class PointerEventArgs : RoutedEventArgs { - private readonly IVisual _rootVisual; + private readonly IVisual? _rootVisual; private readonly Point _rootVisualPosition; private readonly PointerPointProperties _properties; public PointerEventArgs(RoutedEvent routedEvent, - IInteractive source, + IInteractive? source, IPointer pointer, - IVisual rootVisual, Point rootVisualPosition, + IVisual? rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers) @@ -40,8 +40,8 @@ namespace Avalonia.Input public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException(); - public IInputElement Captured => _ev.Pointer.Captured; - public void Capture(IInputElement control) + public IInputElement? Captured => _ev.Pointer.Captured; + public void Capture(IInputElement? control) { _ev.Pointer.Capture(control); } @@ -52,7 +52,7 @@ namespace Avalonia.Input public IPointer Pointer { get; } public ulong Timestamp { get; } - private IPointerDevice _device; + private IPointerDevice? _device; [Obsolete("Use Pointer to get pointer-specific information")] public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this)); @@ -76,7 +76,7 @@ namespace Avalonia.Input public KeyModifiers KeyModifiers { get; } - public Point GetPosition(IVisual relativeTo) + public Point GetPosition(IVisual? relativeTo) { if (_rootVisual == null) return default; diff --git a/src/Avalonia.Input/Raw/RawDragEventType.cs b/src/Avalonia.Input/Raw/RawDragEventType.cs index 9635f77467..77f17a5a41 100644 --- a/src/Avalonia.Input/Raw/RawDragEventType.cs +++ b/src/Avalonia.Input/Raw/RawDragEventType.cs @@ -7,4 +7,4 @@ DragLeave, Drop } -} \ No newline at end of file +} diff --git a/src/Avalonia.Input/Raw/RawInputEventArgs.cs b/src/Avalonia.Input/Raw/RawInputEventArgs.cs index b85563b24a..dcc5f27a79 100644 --- a/src/Avalonia.Input/Raw/RawInputEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawInputEventArgs.cs @@ -21,7 +21,7 @@ namespace Avalonia.Input.Raw /// The root from which the event originates. public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root) { - Contract.Requires(device != null); + device = device ?? throw new ArgumentNullException(nameof(device)); Device = device; Timestamp = timestamp; diff --git a/src/Avalonia.Input/TextInputEventArgs.cs b/src/Avalonia.Input/TextInputEventArgs.cs index 6e763d3b56..cda0103749 100644 --- a/src/Avalonia.Input/TextInputEventArgs.cs +++ b/src/Avalonia.Input/TextInputEventArgs.cs @@ -4,8 +4,8 @@ namespace Avalonia.Input { public class TextInputEventArgs : RoutedEventArgs { - public IKeyboardDevice Device { get; set; } + public IKeyboardDevice? Device { get; set; } - public string Text { get; set; } + public string? Text { get; set; } } } From c22b71aa50cc0b74848be1db1723376a736f7b3a Mon Sep 17 00:00:00 2001 From: Yatao Li Date: Thu, 6 Aug 2020 17:41:32 +0800 Subject: [PATCH 016/829] 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 017/829] 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 018/829] 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 019/829] 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 020/829] 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 021/829] 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 022/829] 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 023/829] 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 024/829] 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 025/829] 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 bc0ada009461679ef8517cc30ad1b2e5c0165576 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Mon, 31 Aug 2020 18:35:40 +0200 Subject: [PATCH 026/829] Preferring user-provided string conversion over TypeDescriptor --- src/Avalonia.Controls/ColumnDefinitions.cs | 10 ++++- .../ViewModels/PropertyViewModel.cs | 37 ++++++++++++++----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls/ColumnDefinitions.cs b/src/Avalonia.Controls/ColumnDefinitions.cs index ed4f9dbe99..7e355ab357 100644 --- a/src/Avalonia.Controls/ColumnDefinitions.cs +++ b/src/Avalonia.Controls/ColumnDefinitions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Specialized; using System.Linq; +using System.Text; using Avalonia.Collections; namespace Avalonia.Controls @@ -13,7 +14,7 @@ namespace Avalonia.Controls /// /// Initializes a new instance of the class. /// - public ColumnDefinitions() : base () + public ColumnDefinitions() { } @@ -27,6 +28,11 @@ namespace Avalonia.Controls AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x))); } + public override string ToString() + { + return string.Join(",", this.Select(x => x.Width)); + } + /// /// Parses a string representation of column definitions collection. /// @@ -34,4 +40,4 @@ namespace Avalonia.Controls /// The . public static ColumnDefinitions Parse(string s) => new ColumnDefinitions(s); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index ddbdae7ed9..2d69764ae8 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Reflection; @@ -8,8 +9,8 @@ namespace Avalonia.Diagnostics.ViewModels internal abstract class PropertyViewModel : ViewModelBase { private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; - private static readonly Type[] StringParameter = new[] { typeof(string) }; - private static readonly Type[] StringIFormatProviderParameters = new[] { typeof(string), typeof(IFormatProvider) }; + private static readonly Type[] StringParameter = { typeof(string) }; + private static readonly Type[] StringIFormatProviderParameters = { typeof(string), typeof(IFormatProvider) }; public abstract object Key { get; } public abstract string Name { get; } @@ -25,19 +26,37 @@ namespace Avalonia.Diagnostics.ViewModels return "(null)"; } - var converter = TypeDescriptor.GetConverter(value); - return converter?.ConvertToString(value) ?? value.ToString(); + //Check if there's an user provided ToString(), prefer that over the TypeDescriptor conversion + if (value.GetType().GetMethod(nameof(ToString), System.Type.EmptyTypes) + .DeclaringType != typeof(object)) + { + return value.ToString(); + } + + try + { + var converter = TypeDescriptor.GetConverter(value); + + return converter.ConvertToString(value); + } + catch + { + return value.ToString(); + } } protected static object ConvertFromString(string s, Type targetType) { - var converter = TypeDescriptor.GetConverter(targetType); - - if (converter != null && converter.CanConvertFrom(typeof(string))) + try { - return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s); + var converter = TypeDescriptor.GetConverter(targetType); + + if (converter.CanConvertFrom(typeof(string))) + { + return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s); + } } - else + catch { var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null); From c25a130903faca7764b7e45f8b13e9fd3fde04fa Mon Sep 17 00:00:00 2001 From: GMIKE Date: Tue, 1 Sep 2020 01:07:49 +0300 Subject: [PATCH 027/829] Useful methods and operations --- src/Avalonia.Visuals/Point.cs | 115 ++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 4cce2c925b..f9ae1f2fe1 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -76,6 +76,17 @@ namespace Avalonia return left.Equals(right); } + /// + /// Checks for equality between a point and cortege off double numbers s. + /// + /// The point. + /// Cortege off double numbers + /// True if the point and cortege off double numbers are equal; otherwise false. + public static bool operator ==(Point left, (double x, double y) right) + { + return !((left._x.Equals(right.x)) && (left._y.Equals(right.y))); + } + /// /// Checks for inequality between two s. /// @@ -87,6 +98,17 @@ namespace Avalonia return !(left == right); } + /// + /// Checks for inequality between a point and cortege off double numberss. + /// + /// The point. + /// Cortege off double numbers + /// True if the point and cortege off double numbers are unequal; otherwise false. + public static bool operator !=(Point left, (double x, double y) right) + { + return !((left._x == right.x) && (left._y== right.y)); + } + /// /// Adds two points. /// @@ -109,6 +131,28 @@ namespace Avalonia return new Point(a._x + b.X, a._y + b.Y); } + /// + /// Add a cortege off double numbers + /// + /// The point. + /// cortege off doubles + /// A point that is the result of the addition. + public static Point operator +(Point a, (double x, double y) b) + { + return new Point(a._x + b.x, a._y + b.y); + } + + /// + /// Add a double number to both coordinates + /// + /// The point. + /// double number + /// A point that is the result of the addition. + public static Point operator +(Point a, double b) + { + return new Point(a._x + b, a._y + b); + } + /// /// Subtracts two points. /// @@ -131,6 +175,28 @@ namespace Avalonia return new Point(a._x - b.X, a._y - b.Y); } + /// + /// Subtracts a cortege off double numbers + /// + /// The point. + /// cortege off doubles + /// A point that is the result of the subtraction. + public static Point operator -(Point a, (double x, double y) b) + { + return new Point(a._x - b.x, a._y - b.y); + } + + /// + /// Subtracts a double number to both coordinates + /// + /// The point. + /// double number + /// A point that is the result of the subtraction. + public static Point operator -(Point a, double b) + { + return new Point(a._x - b, a._y - b); + } + /// /// Multiplies a point by a factor coordinate-wise /// @@ -147,6 +213,17 @@ namespace Avalonia /// Points having its coordinates multiplied public static Point operator *(double k, Point p) => new Point(p.X * k, p.Y * k); + /// + /// Multiplies a cortege off double numbers + /// + /// The point. + /// cortege off doubles + /// Points having its coordinates multiplied. + public static Point operator *(Point a, (double x, double y) b) + { + return new Point(a._x * b.x, a._y * b.y); + } + /// /// Divides a point by a factor coordinate-wise /// @@ -155,6 +232,17 @@ namespace Avalonia /// Points having its coordinates divided public static Point operator /(Point p, double k) => new Point(p.X / k, p.Y / k); + /// + /// Divides a point by a cortege off double numbers + /// + /// The point. + /// cortege off doubles + /// Points having its coordinates divided + public static Point operator /(Point a, (double x, double y) b) + { + return new Point(a._x / b.x, a._y / b.y); + } + /// /// Applies a matrix to a point. /// @@ -267,5 +355,32 @@ namespace Avalonia { return new Point(_x, y); } + + /// + /// Returns a new point with the opposite coordinates. + /// + /// The new point. + public Point Miror() + { + return new Point(-_x, -_y); + } + + /// + /// Returns a new point with the opposite X coordinate. + /// + /// The new point. + public Point WithMirorX() + { + return new Point(-_x, _y); + } + + /// + /// Returns a new point with the opposite Y coordinate. + /// + /// The new point. + public Point WithMirorY() + { + return new Point(_x, -_y); + } } } From cf42bc3bd0899545149d947127a1fa0bef8c97d9 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 1 Sep 2020 09:12:44 +0300 Subject: [PATCH 028/829] 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 029/829] 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 030/829] 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 031/829] 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 032/829] 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 3cdfa305b59e2e9eb2784b4da470eb5008ca14c1 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 1 Sep 2020 10:27:30 +0200 Subject: [PATCH 033/829] typo, removed using --- .../Diagnostics/ViewModels/PropertyViewModel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index 2d69764ae8..bd60a46b95 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Reflection; @@ -26,7 +25,7 @@ namespace Avalonia.Diagnostics.ViewModels return "(null)"; } - //Check if there's an user provided ToString(), prefer that over the TypeDescriptor conversion + //Check if there's an user-provided ToString(), prefer that over the TypeDescriptor conversion if (value.GetType().GetMethod(nameof(ToString), System.Type.EmptyTypes) .DeclaringType != typeof(object)) { From 8e03d121ac1357d94e9ece9fcd2e06d9b175c264 Mon Sep 17 00:00:00 2001 From: GMIKE Date: Tue, 1 Sep 2020 14:10:20 +0300 Subject: [PATCH 034/829] Deconstruct and IsEmpty propterty --- src/Avalonia.Visuals/Point.cs | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index f9ae1f2fe1..2e77f5528b 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -357,30 +357,22 @@ namespace Avalonia } /// - /// Returns a new point with the opposite coordinates. + /// Deconstructor for decomposition Point /// - /// The new point. - public Point Miror() - { - return new Point(-_x, -_y); - } - - /// - /// Returns a new point with the opposite X coordinate. - /// - /// The new point. - public Point WithMirorX() + /// The X position. + /// The Y position. + public void Deconstruct(out double x, out double y) { - return new Point(-_x, _y); + x = this._x; + y = this._y; } /// - /// Returns a new point with the opposite Y coordinate. + /// Gets a value indicating that Point coordinatrs is zero /// - /// The new point. - public Point WithMirorY() + public bool IsEmpty { - return new Point(_x, -_y); + get { return (_x == 0) && (_y == 0); } } } } From a339c68e8cb5ac4741a6bff936b0b462f1a155ca Mon Sep 17 00:00:00 2001 From: GMIKE Date: Tue, 1 Sep 2020 16:18:15 +0300 Subject: [PATCH 035/829] Equals for cortege off double numbers --- src/Avalonia.Visuals/Point.cs | 44 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 2e77f5528b..52fd4d05c9 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -8,7 +8,7 @@ namespace Avalonia /// /// Defines a point. /// - public readonly struct Point : IEquatable + public readonly struct Point : IEquatable, IEquatable<(double x,double y)> { static Point() { @@ -76,17 +76,6 @@ namespace Avalonia return left.Equals(right); } - /// - /// Checks for equality between a point and cortege off double numbers s. - /// - /// The point. - /// Cortege off double numbers - /// True if the point and cortege off double numbers are equal; otherwise false. - public static bool operator ==(Point left, (double x, double y) right) - { - return !((left._x.Equals(right.x)) && (left._y.Equals(right.y))); - } - /// /// Checks for inequality between two s. /// @@ -98,17 +87,6 @@ namespace Avalonia return !(left == right); } - /// - /// Checks for inequality between a point and cortege off double numberss. - /// - /// The point. - /// Cortege off double numbers - /// True if the point and cortege off double numbers are unequal; otherwise false. - public static bool operator !=(Point left, (double x, double y) right) - { - return !((left._x == right.x) && (left._y== right.y)); - } - /// /// Adds two points. /// @@ -367,6 +345,26 @@ namespace Avalonia y = this._y; } + /// + /// Returns a boolean indicating whether the point is equal to the other given point. + /// + /// The other point to test equality against. + /// True if this point is equal to other; False otherwise. + + + /// + /// Returns a boolean indicating whether the point is equal to cortege off double numbers. + /// + /// ortege off double numbers + /// + /// True if is cortege off double numbersthat equals the current point. + /// + public bool Equals((double x, double y) other) + { + return _x == other.x && + _y == other.y; + } + /// /// Gets a value indicating that Point coordinatrs is zero /// From faad15571ac2beca8b78063c04391ab9b2a809d3 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 1 Sep 2020 15:48:16 +0200 Subject: [PATCH 036/829] Filter out CollectionConverter Making sure to call Parse when conversion with TypeConverter is not possible --- .../ViewModels/PropertyViewModel.cs | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index bd60a46b95..e23d6f1471 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -25,54 +25,47 @@ namespace Avalonia.Diagnostics.ViewModels return "(null)"; } - //Check if there's an user-provided ToString(), prefer that over the TypeDescriptor conversion - if (value.GetType().GetMethod(nameof(ToString), System.Type.EmptyTypes) - .DeclaringType != typeof(object)) + var converter = TypeDescriptor.GetConverter(value); + + //CollectionConverter does not deliver any important information. It just displays "(Collection)". + if (!converter.CanConvertTo(typeof(string)) || + converter.GetType() == typeof(CollectionConverter)) { return value.ToString(); } - try - { - var converter = TypeDescriptor.GetConverter(value); + return converter.ConvertToString(value); + } + + private static object InvokeParse(string s, Type targetType) + { + var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null); - return converter.ConvertToString(value); + if (method != null) + { + return method.Invoke(null, new object[] { s, CultureInfo.InvariantCulture }); } - catch + + method = targetType.GetMethod("Parse", PublicStatic, null, StringParameter, null); + + if (method != null) { - return value.ToString(); + return method.Invoke(null, new object[] { s }); } + + throw new InvalidCastException("Unable to convert value."); } protected static object ConvertFromString(string s, Type targetType) { - try - { - var converter = TypeDescriptor.GetConverter(targetType); + var converter = TypeDescriptor.GetConverter(targetType); - if (converter.CanConvertFrom(typeof(string))) - { - return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s); - } - } - catch + if (converter.CanConvertFrom(typeof(string))) { - var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null); - - if (method != null) - { - return method.Invoke(null, new object[] { s, CultureInfo.InvariantCulture }); - } - - method = targetType.GetMethod("Parse", PublicStatic, null, StringParameter, null); - - if (method != null) - { - return method.Invoke(null, new object[] { s }); - } + return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s); } - throw new InvalidCastException("Unable to convert value."); + return InvokeParse(s, targetType); } } } From 031722ebb2f98cedb391aa628e4e7c06fb633a33 Mon Sep 17 00:00:00 2001 From: GMIKE Date: Wed, 2 Sep 2020 19:12:28 +0300 Subject: [PATCH 037/829] Change IsEmpty on IsDefault --- src/Avalonia.Visuals/Point.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 52fd4d05c9..ae713e57a5 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -345,19 +345,12 @@ namespace Avalonia y = this._y; } - /// - /// Returns a boolean indicating whether the point is equal to the other given point. - /// - /// The other point to test equality against. - /// True if this point is equal to other; False otherwise. - - /// /// Returns a boolean indicating whether the point is equal to cortege off double numbers. /// /// ortege off double numbers /// - /// True if is cortege off double numbersthat equals the current point. + /// True if is cortege off double numbersthat equals the current point. /// public bool Equals((double x, double y) other) { @@ -368,7 +361,7 @@ namespace Avalonia /// /// Gets a value indicating that Point coordinatrs is zero /// - public bool IsEmpty + public bool IsDefault { get { return (_x == 0) && (_y == 0); } } From 115c4adadbb5f3d9cadbe53cdb78e617d9602d45 Mon Sep 17 00:00:00 2001 From: GMIKE Date: Wed, 2 Sep 2020 19:51:18 +0300 Subject: [PATCH 038/829] fix for xml --- src/Avalonia.Visuals/Point.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index ae713e57a5..d95512c976 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -110,10 +110,10 @@ namespace Avalonia } /// - /// Add a cortege off double numbers + /// Add a 2-tuple of double /// /// The point. - /// cortege off doubles + /// 2-tuple of double /// A point that is the result of the addition. public static Point operator +(Point a, (double x, double y) b) { @@ -154,10 +154,10 @@ namespace Avalonia } /// - /// Subtracts a cortege off double numbers + /// Subtracts a 2-tuple of double /// /// The point. - /// cortege off doubles + /// 2-tuple of double /// A point that is the result of the subtraction. public static Point operator -(Point a, (double x, double y) b) { @@ -192,10 +192,10 @@ namespace Avalonia public static Point operator *(double k, Point p) => new Point(p.X * k, p.Y * k); /// - /// Multiplies a cortege off double numbers + /// Multiplies a 2-tuple of double /// /// The point. - /// cortege off doubles + /// 2-tuple of double /// Points having its coordinates multiplied. public static Point operator *(Point a, (double x, double y) b) { @@ -211,10 +211,10 @@ namespace Avalonia public static Point operator /(Point p, double k) => new Point(p.X / k, p.Y / k); /// - /// Divides a point by a cortege off double numbers + /// Divides a point by a 2-tuple of doubles /// /// The point. - /// cortege off doubles + /// 2-tuple of double /// Points having its coordinates divided public static Point operator /(Point a, (double x, double y) b) { @@ -346,11 +346,11 @@ namespace Avalonia } /// - /// Returns a boolean indicating whether the point is equal to cortege off double numbers. + /// Returns a boolean indicating whether the point coordinates are equal to 2-tuple of double /// - /// ortege off double numbers + /// 2-tuple of double /// - /// True if is cortege off double numbersthat equals the current point. + /// True if is 2-tuple of double equals coordinates current point. /// public bool Equals((double x, double y) other) { @@ -359,7 +359,7 @@ namespace Avalonia } /// - /// Gets a value indicating that Point coordinatrs is zero + /// Gets a value indicating that point coordinates are zero /// public bool IsDefault { From 9e7c4d1b0943f10a600d03a8722bb19407e92337 Mon Sep 17 00:00:00 2001 From: ShadowDancer Date: Wed, 2 Sep 2020 23:20:01 +0200 Subject: [PATCH 039/829] Update Visual.cs documentation `OnAttachedToVisualTree`/`OnDetachedFromVisualTree` is called only for root visual tree. Keep xmldoc in line with event description. --- src/Avalonia.Visuals/Visual.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index cd6eb6aac7..283d9deb52 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -455,7 +455,7 @@ namespace Avalonia } /// - /// Called when the control is added to a visual tree. + /// Called when the control is added to a rooted visual tree. /// /// The event args. protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) @@ -463,7 +463,7 @@ namespace Avalonia } /// - /// Called when the control is removed from a visual tree. + /// Called when the control is removed from a rooted visual tree. /// /// The event args. protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) From de5dd5096efb3a52803d4f7cb207b9565020916b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Onak?= Date: Thu, 3 Sep 2020 00:09:52 +0200 Subject: [PATCH 040/829] Schedule opening popup if cannot be opened right now --- src/Avalonia.Controls/Primitives/Popup.cs | 21 ++++++++++++-- .../Primitives/PopupTests.cs | 28 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index a676892384..1e5e80d144 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -128,6 +128,7 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty TopmostProperty = AvaloniaProperty.Register(nameof(Topmost)); + private bool _isOpenRequested = false; private bool _isOpen; private bool _ignoreIsOpenChanged; private PopupOpenState? _openState; @@ -361,17 +362,19 @@ namespace Avalonia.Controls.Primitives if (placementTarget == null) { - throw new InvalidOperationException("Popup has no logical parent and PlacementTarget is null"); + _isOpenRequested = true; + return; } var topLevel = placementTarget.VisualRoot as TopLevel; if (topLevel == null) { - throw new InvalidOperationException( - "Attempted to open a popup not attached to a TopLevel"); + _isOpenRequested = true; + return; } + _isOpenRequested = false; var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver); var handlerCleanup = new CompositeDisposable(5); @@ -492,6 +495,17 @@ namespace Avalonia.Controls.Primitives return new Size(); } + + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + if (_isOpenRequested) + { + Open(); + } + } + /// protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { @@ -552,6 +566,7 @@ namespace Avalonia.Controls.Primitives private void CloseCore() { + _isOpenRequested = false; if (_openState is null) { using (BeginIgnoringIsOpen()) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index d9176ca55d..73871cf79f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -22,6 +22,34 @@ namespace Avalonia.Controls.UnitTests.Primitives { protected bool UsePopupHost; + + [Fact] + public void Popup_Without_TopLevel_Shouldnt_Call_Open() + { + int openedEvent = 0; + var target = new Popup(); + target.Opened += (s, a) => openedEvent++; + target.IsOpen = true; + + Assert.Equal(0, openedEvent); + } + + [Fact] + public void Opening_Popup_Shouldnt_Throw_When_Not_In_Visual_Tree() + { + var target = new Popup(); + target.IsOpen = true; + } + + [Fact] + public void Opening_Popup_Shouldnt_Throw_When_In_Tree_Without_TopLevel() + { + Control c = new Control(); + var target = new Popup(); + ((ISetLogicalParent)target).SetParent(c); + target.IsOpen = true; + } + [Fact] public void Setting_Child_Should_Set_Child_Controls_LogicalParent() { From c4ebf899e50f079be187c227cfba61c4c9d70ae9 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Thu, 3 Sep 2020 15:05:28 +0200 Subject: [PATCH 041/829] Add option to enable/disable dirty rects --- .../Diagnostics/ViewModels/MainViewModel.cs | 27 ++++++++++++++----- .../Diagnostics/Views/MainView.xaml | 7 +++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 1c49b24f52..1bd4d711ab 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -9,7 +9,7 @@ namespace Avalonia.Diagnostics.ViewModels { internal class MainViewModel : ViewModelBase, IDisposable { - private readonly IControl _root; + private readonly TopLevel _root; private readonly TreePageViewModel _logicalTree; private readonly TreePageViewModel _visualTree; private readonly EventsPageViewModel _events; @@ -19,8 +19,9 @@ namespace Avalonia.Diagnostics.ViewModels private string _focusedControl; private string _pointerOverElement; private bool _shouldVisualizeMarginPadding = true; + private bool _shouldVisualizeDirtyRects; - public MainViewModel(IControl root) + public MainViewModel(TopLevel root) { _root = root; _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root)); @@ -40,6 +41,22 @@ namespace Avalonia.Diagnostics.ViewModels get => _shouldVisualizeMarginPadding; set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value); } + + public bool ShouldVisualizeDirtyRects + { + get => _shouldVisualizeDirtyRects; + set + { + RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value); + + _root.Renderer.DrawDirtyRects = value; + } + } + + public void ToggleVisualizeDirtyRects() + { + ShouldVisualizeDirtyRects = !ShouldVisualizeDirtyRects; + } public void ToggleVisualizeMarginPadding() { @@ -128,10 +145,7 @@ namespace Avalonia.Diagnostics.ViewModels { var tree = Content as TreePageViewModel; - if (tree != null) - { - tree.SelectControl(control); - } + tree?.SelectControl(control); } public void Dispose() @@ -140,6 +154,7 @@ namespace Avalonia.Diagnostics.ViewModels _pointerOverSubscription.Dispose(); _logicalTree.Dispose(); _visualTree.Dispose(); + _root.Renderer.DrawDirtyRects = false; } private void UpdateFocusedControl() diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 0165398718..e0b73957bf 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -24,6 +24,13 @@ IsEnabled="False"/> + + + + + From 2bd8156fae206d321a446836e1ce33b45caca931 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Thu, 3 Sep 2020 16:28:29 +0200 Subject: [PATCH 042/829] Also add option to show fps overlay --- .../Diagnostics/ViewModels/MainViewModel.cs | 20 +++++++++++++++++-- .../Diagnostics/Views/MainView.xaml | 7 +++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 1bd4d711ab..bf7d0e232a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -20,6 +20,7 @@ namespace Avalonia.Diagnostics.ViewModels private string _pointerOverElement; private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeDirtyRects; + private bool _showFpsOverlay; public MainViewModel(TopLevel root) { @@ -47,9 +48,8 @@ namespace Avalonia.Diagnostics.ViewModels get => _shouldVisualizeDirtyRects; set { - RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value); - _root.Renderer.DrawDirtyRects = value; + RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value); } } @@ -63,6 +63,21 @@ namespace Avalonia.Diagnostics.ViewModels ShouldVisualizeMarginPadding = !ShouldVisualizeMarginPadding; } + public bool ShowFpsOverlay + { + get => _showFpsOverlay; + set + { + _root.Renderer.DrawFps = value; + RaiseAndSetIfChanged(ref _showFpsOverlay, value); + } + } + + public void ToggleFpsOverlay() + { + ShowFpsOverlay = !ShowFpsOverlay; + } + public ConsoleViewModel Console { get; } public ViewModelBase Content @@ -155,6 +170,7 @@ namespace Avalonia.Diagnostics.ViewModels _logicalTree.Dispose(); _visualTree.Dispose(); _root.Renderer.DrawDirtyRects = false; + _root.Renderer.DrawFps = false; } private void UpdateFocusedControl() diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index e0b73957bf..8c4db33f91 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -31,6 +31,13 @@ IsEnabled="False"/> + + + + + From db98c340d65532b136750cae5f5379c54b7c64a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Onak?= Date: Thu, 3 Sep 2020 20:47:52 +0200 Subject: [PATCH 043/829] Add test to check if popup is displayed correctly after window is shown --- .../Primitives/PopupTests.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 73871cf79f..5bad646aa7 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -21,7 +21,22 @@ namespace Avalonia.Controls.UnitTests.Primitives public class PopupTests { protected bool UsePopupHost; - + + [Fact] + public void Popup_Open_Without_Target_Should_Attach_Itself_Later() + { + using (CreateServices()) + { + int openedEvent = 0; + var target = new Popup(); + target.Opened += (s, a) => openedEvent++; + target.IsOpen = true; + + var window = PreparedWindow(target); + window.Show(); + Assert.Equal(1, openedEvent); + } + } [Fact] public void Popup_Without_TopLevel_Shouldnt_Call_Open() From d0f0d02b923980f3c113b130035d2796d6535fdf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 3 Sep 2020 22:00:25 +0100 Subject: [PATCH 044/829] install typescript with npm. --- nukebuild/Build.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index a46b55f6f3..5b4e5a5bad 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -131,6 +131,7 @@ partial class Build : NukeBuild var webappDir = RootDirectory / "src" / "Avalonia.DesignerSupport" / "Remote" / "HtmlTransport" / "webapp"; NpmTasks.NpmInstall(c => c + .AddPackages("typescript") .SetWorkingDirectory(webappDir) .SetArgumentConfigurator(a => a.Add("--silent"))); NpmTasks.NpmRun(c => c From 6eeeb6c8eaa9f4969986ef878b75ce26817c6660 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 3 Sep 2020 22:25:43 +0100 Subject: [PATCH 045/829] skip building web previewer on osx. --- azure-pipelines.yml | 2 +- nukebuild/Build.cs | 2 +- nukebuild/BuildParameters.cs | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 89504a498e..29d2c62caf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -81,7 +81,7 @@ jobs: export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - nuke --target CiAzureOSX --configuration Release + nuke --target CiAzureOSX --configuration Release --skip-previewer - task: PublishTestResults@2 inputs: diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 5b4e5a5bad..24398accbb 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -126,12 +126,12 @@ partial class Build : NukeBuild Target CompileHtmlPreviewer => _ => _ .DependsOn(Clean) + .OnlyWhenStatic(() => !Parameters.SkipPreviewer) .Executes(() => { var webappDir = RootDirectory / "src" / "Avalonia.DesignerSupport" / "Remote" / "HtmlTransport" / "webapp"; NpmTasks.NpmInstall(c => c - .AddPackages("typescript") .SetWorkingDirectory(webappDir) .SetArgumentConfigurator(a => a.Add("--silent"))); NpmTasks.NpmRun(c => c diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs index 149716b416..a167e9d892 100644 --- a/nukebuild/BuildParameters.cs +++ b/nukebuild/BuildParameters.cs @@ -19,10 +19,14 @@ public partial class Build [Parameter("force-nuget-version")] public string ForceNugetVersion { get; set; } + [Parameter("skip-previewer")] + public bool SkipPreviewer { get; set; } + public class BuildParameters { public string Configuration { get; } public bool SkipTests { get; } + public bool SkipPreviewer {get;} public string MainRepo { get; } public string MasterBranch { get; } public string RepositoryName { get; } @@ -63,6 +67,7 @@ public partial class Build // ARGUMENTS Configuration = b.Configuration ?? "Release"; SkipTests = b.SkipTests; + SkipPreviewer = b.SkipPreviewer; // CONFIGURATION MainRepo = "https://github.com/AvaloniaUI/Avalonia"; From 67a87e09cf617f0e8c7c4916da50c610cf3cb677 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 4 Sep 2020 15:48:27 +0100 Subject: [PATCH 046/829] use directx11. --- src/Avalonia.OpenGL/AngleOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.OpenGL/AngleOptions.cs b/src/Avalonia.OpenGL/AngleOptions.cs index 4b9c04f4e6..56435d2705 100644 --- a/src/Avalonia.OpenGL/AngleOptions.cs +++ b/src/Avalonia.OpenGL/AngleOptions.cs @@ -12,7 +12,7 @@ namespace Avalonia.OpenGL public List AllowedPlatformApis = new List { - PlatformApi.DirectX9 + PlatformApi.DirectX11 }; } } From 6ed1669803d62f7a02f75d89b0e0ad66b8d5d983 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 4 Sep 2020 15:49:04 +0100 Subject: [PATCH 047/829] set a sensible cache size for skia.. based on what flutter uses. --- src/Skia/Avalonia.Skia/SkiaOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/SkiaOptions.cs b/src/Skia/Avalonia.Skia/SkiaOptions.cs index cbe0b5ef42..d14120204c 100644 --- a/src/Skia/Avalonia.Skia/SkiaOptions.cs +++ b/src/Skia/Avalonia.Skia/SkiaOptions.cs @@ -16,6 +16,6 @@ namespace Avalonia /// /// The maximum number of bytes for video memory to store textures and resources. /// - public long? MaxGpuResourceSizeBytes { get; set; } + public long? MaxGpuResourceSizeBytes { get; set; } = 1024 * 600 * 4 * 12; // ~28mb 12x 1024 x 600 textures. } } From 1eda1b6c1fbee01f7a09cc47d78c30f33a259111 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 4 Sep 2020 16:13:18 +0100 Subject: [PATCH 048/829] better way to set default directx apis. --- src/Avalonia.OpenGL/AngleOptions.cs | 5 +---- src/Avalonia.OpenGL/EglDisplay.cs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.OpenGL/AngleOptions.cs b/src/Avalonia.OpenGL/AngleOptions.cs index 56435d2705..af1d1177ed 100644 --- a/src/Avalonia.OpenGL/AngleOptions.cs +++ b/src/Avalonia.OpenGL/AngleOptions.cs @@ -10,9 +10,6 @@ namespace Avalonia.OpenGL DirectX11 } - public List AllowedPlatformApis = new List - { - PlatformApi.DirectX11 - }; + public List AllowedPlatformApis { get; set; } = null; } } diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 0436f6ac52..cbc9bbfb85 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -36,7 +36,7 @@ namespace Avalonia.OpenGL throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis - ?? new List {AngleOptions.PlatformApi.DirectX9}; + ?? new List {AngleOptions.PlatformApi.DirectX11}; foreach (var platformApi in allowedApis) { From 531b64e4665184d9df041f2b6b4e911b22151861 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 4 Sep 2020 16:35:43 +0100 Subject: [PATCH 049/829] dont allocate lists for angleoptions. --- src/Avalonia.OpenGL/AngleOptions.cs | 2 +- src/Avalonia.OpenGL/EglDisplay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.OpenGL/AngleOptions.cs b/src/Avalonia.OpenGL/AngleOptions.cs index af1d1177ed..84744288ed 100644 --- a/src/Avalonia.OpenGL/AngleOptions.cs +++ b/src/Avalonia.OpenGL/AngleOptions.cs @@ -10,6 +10,6 @@ namespace Avalonia.OpenGL DirectX11 } - public List AllowedPlatformApis { get; set; } = null; + public IList AllowedPlatformApis { get; set; } = null; } } diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index cbc9bbfb85..2c3cadd620 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -36,7 +36,7 @@ namespace Avalonia.OpenGL throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis - ?? new List {AngleOptions.PlatformApi.DirectX11}; + ?? new [] {AngleOptions.PlatformApi.DirectX11}; foreach (var platformApi in allowedApis) { From a752c398bb809db466f68a5365142f20e40baa93 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 4 Sep 2020 16:38:47 +0100 Subject: [PATCH 050/829] allow fallback to dx9. --- src/Avalonia.OpenGL/EglDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 2c3cadd620..89dee4fc37 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -36,7 +36,7 @@ namespace Avalonia.OpenGL throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis - ?? new [] {AngleOptions.PlatformApi.DirectX11}; + ?? new [] {AngleOptions.PlatformApi.DirectX11, AngleOptions.PlatformApi.DirectX9}; foreach (var platformApi in allowedApis) { From 8614c060028ded20b6fd5e72103425f929134cb8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 4 Sep 2020 16:47:25 +0100 Subject: [PATCH 051/829] add WGL option. --- src/Avalonia.OpenGL/AngleOptions.cs | 3 ++- src/Avalonia.OpenGL/EglDisplay.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.OpenGL/AngleOptions.cs b/src/Avalonia.OpenGL/AngleOptions.cs index 84744288ed..40a673b574 100644 --- a/src/Avalonia.OpenGL/AngleOptions.cs +++ b/src/Avalonia.OpenGL/AngleOptions.cs @@ -7,7 +7,8 @@ namespace Avalonia.OpenGL public enum PlatformApi { DirectX9, - DirectX11 + DirectX11, + WGL } public IList AllowedPlatformApis { get; set; } = null; diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 89dee4fc37..c84ef4533d 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -36,7 +36,12 @@ namespace Avalonia.OpenGL throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis - ?? new [] {AngleOptions.PlatformApi.DirectX11, AngleOptions.PlatformApi.DirectX9}; + ?? new [] + { + AngleOptions.PlatformApi.WGL, + AngleOptions.PlatformApi.DirectX11, + AngleOptions.PlatformApi.DirectX9 + }; foreach (var platformApi in allowedApis) { @@ -45,6 +50,8 @@ namespace Avalonia.OpenGL dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE; else if (platformApi == AngleOptions.PlatformApi.DirectX11) dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE; + else if (platformApi == AngleOptions.PlatformApi.WGL) + dapi = EGL_PLATFORM_ANGLE_TYPE_ANGLE; else continue; From fc12b9fee9ba06e48702340878ee79fe3ebb9a87 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 4 Sep 2020 16:50:41 +0100 Subject: [PATCH 052/829] define correct consts for opengl. --- src/Avalonia.OpenGL/EglConsts.cs | 3 +++ src/Avalonia.OpenGL/EglDisplay.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.OpenGL/EglConsts.cs b/src/Avalonia.OpenGL/EglConsts.cs index 62fb3faef6..5cc732ef6a 100644 --- a/src/Avalonia.OpenGL/EglConsts.cs +++ b/src/Avalonia.OpenGL/EglConsts.cs @@ -186,6 +186,9 @@ namespace Avalonia.OpenGL public const int EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE = 0x345E; + + public const int EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D; + public const int EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E; //EGL_ANGLE_platform_angle_d3d public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209; diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index c84ef4533d..55d0ebb59a 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -51,7 +51,7 @@ namespace Avalonia.OpenGL else if (platformApi == AngleOptions.PlatformApi.DirectX11) dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE; else if (platformApi == AngleOptions.PlatformApi.WGL) - dapi = EGL_PLATFORM_ANGLE_TYPE_ANGLE; + dapi = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; else continue; From 126737fd1bd5d182bdf803adfa7de949b77be0a1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 4 Sep 2020 18:04:06 +0200 Subject: [PATCH 053/829] Update ScrollBar visibility with local value priority. Using `SetValue` with `Style` priority caused a new entry to be added to the `IsVisible` priority store each time the value was updated, causing a leak. Setting it with local value priority subtly changes its semantics but hopefully not so that anyone notices. --- src/Avalonia.Controls/Primitives/ScrollBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index fc82fcc7a7..477d24dc99 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -141,7 +141,7 @@ namespace Avalonia.Controls.Primitives _ => throw new InvalidOperationException("Invalid value for ScrollBar.Visibility.") }; - SetValue(IsVisibleProperty, isVisible, BindingPriority.Style); + SetValue(IsVisibleProperty, isVisible); } protected override void OnKeyDown(KeyEventArgs e) From 39a82ce66e2c8e2d11226d6a0e7a2557b20380d4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 5 Sep 2020 12:21:20 +0100 Subject: [PATCH 054/829] Remove WGL option. --- src/Avalonia.OpenGL/AngleOptions.cs | 3 +-- src/Avalonia.OpenGL/EglDisplay.cs | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Avalonia.OpenGL/AngleOptions.cs b/src/Avalonia.OpenGL/AngleOptions.cs index 40a673b574..84744288ed 100644 --- a/src/Avalonia.OpenGL/AngleOptions.cs +++ b/src/Avalonia.OpenGL/AngleOptions.cs @@ -7,8 +7,7 @@ namespace Avalonia.OpenGL public enum PlatformApi { DirectX9, - DirectX11, - WGL + DirectX11 } public IList AllowedPlatformApis { get; set; } = null; diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 55d0ebb59a..ce5fd50a07 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -38,7 +38,6 @@ namespace Avalonia.OpenGL var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis ?? new [] { - AngleOptions.PlatformApi.WGL, AngleOptions.PlatformApi.DirectX11, AngleOptions.PlatformApi.DirectX9 }; @@ -50,8 +49,6 @@ namespace Avalonia.OpenGL dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE; else if (platformApi == AngleOptions.PlatformApi.DirectX11) dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE; - else if (platformApi == AngleOptions.PlatformApi.WGL) - dapi = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; else continue; From 3c62c43cc689a2e727d3e15f7137f82fd26bfef7 Mon Sep 17 00:00:00 2001 From: Kir-Antipov Date: Sun, 6 Sep 2020 04:37:37 +0300 Subject: [PATCH 055/829] Hid maximize box on Windows for `CanResize = false` --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ddc0cc4e42..4929283874 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1007,10 +1007,12 @@ namespace Avalonia.Win32 if (newProperties.IsResizable) { style |= WindowStyles.WS_SIZEFRAME; + style |= WindowStyles.WS_MAXIMIZEBOX; } else { style &= ~WindowStyles.WS_SIZEFRAME; + style &= ~WindowStyles.WS_MAXIMIZEBOX; } SetStyle(style); From 8079ebe1dc3d96e4f2a23897071b16e26a9e505a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 8 Sep 2020 10:49:48 +0200 Subject: [PATCH 056/829] Added failing test for #4599. --- tests/Avalonia.LeakTests/ControlTests.cs | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 0b81276240..d61813b50b 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -552,6 +552,42 @@ namespace Avalonia.LeakTests } } + [Fact] + public void ItemsRepeater_Is_Freed() + { + using (Start()) + { + var geometry = new EllipseGeometry { Rect = new Rect(0, 0, 10, 10) }; + + Func run = () => + { + var window = new Window + { + Content = new ItemsRepeater(), + }; + + window.Show(); + + window.LayoutManager.ExecuteInitialLayoutPass(); + Assert.IsType(window.Presenter.Child); + + window.Content = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + + // We are keeping geometry alive to simulate a resource that outlives the control. + GC.KeepAlive(geometry); + } + } + private IDisposable Start() { return UnitTestApplication.Start(TestServices.StyledWindow.With( From 60c0a44a8737da790d68c73f9d8036b72396aad3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 8 Sep 2020 10:51:52 +0200 Subject: [PATCH 057/829] Clean up EffectiveViewportChanged earlier. Clean up the `EffectiveViewportChanged` subscriptions before calling `OnDetachedFromVisualTree` so that (un)subscribing to the `EffectiveViewportChanged` event in `OnDetachedFromVisualTree` doesn't cause a leak. --- src/Avalonia.Layout/Layoutable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index e62e22f8ec..aca2965ea6 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -758,8 +758,6 @@ namespace Avalonia.Layout protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { - base.OnDetachedFromVisualTreeCore(e); - if (e.Root is ILayoutRoot r) { if (_layoutUpdated is object) @@ -772,6 +770,8 @@ namespace Avalonia.Layout r.LayoutManager.UnregisterEffectiveViewportListener(this); } } + + base.OnDetachedFromVisualTreeCore(e); } /// From 68504d212d068cf3786eb97199359be1222d4259 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 8 Sep 2020 13:20:22 +0200 Subject: [PATCH 058/829] Use weak event handlers. Seems that C++/WinRT uses weak event handlers by default so we need to do the same here. --- .../Repeater/ItemsRepeater.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 8bc356bdec..772a17e4ea 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -7,10 +7,10 @@ using System; using System.Collections; using System.Collections.Specialized; using Avalonia.Controls.Templates; -using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Logging; +using Avalonia.Utilities; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -681,8 +681,15 @@ namespace Avalonia.Controls if (oldValue != null) { oldValue.UninitializeForContext(LayoutContext); - oldValue.MeasureInvalidated -= InvalidateMeasureForLayout; - oldValue.ArrangeInvalidated -= InvalidateArrangeForLayout; + + WeakEventHandlerManager.Unsubscribe( + oldValue, + nameof(newValue.MeasureInvalidated), + InvalidateMeasureForLayout); + WeakEventHandlerManager.Unsubscribe( + oldValue, + nameof(newValue.ArrangeInvalidated), + InvalidateArrangeForLayout); // Walk through all the elements and make sure they are cleared foreach (var element in Children) @@ -699,8 +706,15 @@ namespace Avalonia.Controls if (newValue != null) { newValue.InitializeForContext(LayoutContext); - newValue.MeasureInvalidated += InvalidateMeasureForLayout; - newValue.ArrangeInvalidated += InvalidateArrangeForLayout; + + WeakEventHandlerManager.Subscribe( + newValue, + nameof(newValue.MeasureInvalidated), + InvalidateMeasureForLayout); + WeakEventHandlerManager.Subscribe( + newValue, + nameof(newValue.ArrangeInvalidated), + InvalidateArrangeForLayout); } bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout; From e579e61f3820cd37e33258b3688cfc974477aa9c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 8 Sep 2020 13:46:25 +0200 Subject: [PATCH 059/829] Teaks and remove copy-pasta. --- src/Avalonia.Controls/Repeater/ItemsRepeater.cs | 8 ++++---- tests/Avalonia.LeakTests/ControlTests.cs | 5 ----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 772a17e4ea..40f1b8dbb9 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -684,11 +684,11 @@ namespace Avalonia.Controls WeakEventHandlerManager.Unsubscribe( oldValue, - nameof(newValue.MeasureInvalidated), + nameof(AttachedLayout.MeasureInvalidated), InvalidateMeasureForLayout); WeakEventHandlerManager.Unsubscribe( oldValue, - nameof(newValue.ArrangeInvalidated), + nameof(AttachedLayout.ArrangeInvalidated), InvalidateArrangeForLayout); // Walk through all the elements and make sure they are cleared @@ -709,11 +709,11 @@ namespace Avalonia.Controls WeakEventHandlerManager.Subscribe( newValue, - nameof(newValue.MeasureInvalidated), + nameof(AttachedLayout.MeasureInvalidated), InvalidateMeasureForLayout); WeakEventHandlerManager.Subscribe( newValue, - nameof(newValue.ArrangeInvalidated), + nameof(AttachedLayout.ArrangeInvalidated), InvalidateArrangeForLayout); } diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index d61813b50b..0c7b966f29 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -557,8 +557,6 @@ namespace Avalonia.LeakTests { using (Start()) { - var geometry = new EllipseGeometry { Rect = new Rect(0, 0, 10, 10) }; - Func run = () => { var window = new Window @@ -582,9 +580,6 @@ namespace Avalonia.LeakTests dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); - - // We are keeping geometry alive to simulate a resource that outlives the control. - GC.KeepAlive(geometry); } } From 04c781ca1431b3bc3d741d9ea864ad00569c33a9 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 8 Sep 2020 14:55:10 +0200 Subject: [PATCH 060/829] Make sure GlyphTypefaces are always cached --- Avalonia.sln | 28 +++++++- .../Presenters/TextPresenter.cs | 4 +- src/Avalonia.Controls/TextBlock.cs | 2 +- .../HeadlessPlatformStubs.cs | 4 +- src/Avalonia.Visuals/ApiCompatBaseline.txt | 12 +++- src/Avalonia.Visuals/Media/FontManager.cs | 67 ++++++------------- src/Avalonia.Visuals/Media/Fonts/FontKey.cs | 40 ----------- .../Media/TextFormatting/TextCharacters.cs | 7 +- src/Avalonia.Visuals/Media/Typeface.cs | 31 ++------- .../Platform/IFontManagerImpl.cs | 5 +- .../Rendering/RendererBase.cs | 2 +- src/Skia/Avalonia.Skia/FontManagerImpl.cs | 7 +- .../Avalonia.Skia/SKTypefaceCollection.cs | 19 +++--- .../SKTypefaceCollectionCache.cs | 2 +- .../Media/FontManagerImpl.cs | 7 +- .../Media/FormattedTextImplTests.cs | 2 +- .../Media/CustomFontManagerImpl.cs | 8 +-- .../TextFormatting/TextFormatterTests.cs | 2 +- .../Avalonia.UnitTests/MockFontManagerImpl.cs | 5 +- .../Media/FontManagerTests.cs | 7 +- 20 files changed, 103 insertions(+), 158 deletions(-) delete mode 100644 src/Avalonia.Visuals/Media/Fonts/FontKey.cs diff --git a/Avalonia.sln b/Avalonia.sln index 66777f33eb..ddcd61408d 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -222,9 +222,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events", "src\Avalonia.ReactiveUI.Events\Avalonia.ReactiveUI.Events.csproj", "{28F18757-C3E6-4BBE-A37D-11BA2AB9177C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.ReactiveUI.Events.UnitTests", "tests\Avalonia.ReactiveUI.Events.UnitTests\Avalonia.ReactiveUI.Events.UnitTests.csproj", "{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events.UnitTests", "tests\Avalonia.ReactiveUI.Events.UnitTests\Avalonia.ReactiveUI.Events.UnitTests.csproj", "{780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -2040,6 +2040,30 @@ Global {28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhone.Build.0 = Release|Any CPU {28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {28F18757-C3E6-4BBE-A37D-11BA2AB9177C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhone.Build.0 = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhone.Build.0 = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|Any CPU.Build.0 = Release|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.ActiveCfg = Release|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhone.Build.0 = Release|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {780AC0FE-8DD2-419F-A1DC-AC7E3EB393F7}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 37490c3ef3..cb7bee1d33 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -282,7 +282,7 @@ namespace Avalonia.Controls.Presenters return new FormattedText { Constraint = constraint, - Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight), + Typeface = new Typeface(FontFamily, FontStyle, FontWeight), FontSize = FontSize, Text = text ?? string.Empty, TextAlignment = TextAlignment, @@ -499,7 +499,7 @@ namespace Avalonia.Controls.Presenters return new FormattedText { Text = "X", - Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight), + Typeface = new Typeface(FontFamily, FontStyle, FontWeight), FontSize = FontSize, TextAlignment = TextAlignment, Constraint = availableSize, diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 7e5287f81f..3b9e9c4751 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -452,7 +452,7 @@ namespace Avalonia.Controls return new TextLayout( text ?? string.Empty, - FontManager.Current?.GetOrAddTypeface(FontFamily, FontStyle, FontWeight), + new Typeface(FontFamily, FontStyle, FontWeight), FontSize, Foreground, TextAlignment, diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index 763d192693..4c0e2982f4 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -155,9 +155,9 @@ namespace Avalonia.Headless return new List { "Arial" }; } - public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out FontKey fontKey) + public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, CultureInfo culture, out Typeface typeface) { - fontKey = new FontKey("Arial", fontStyle, fontWeight); + typeface = new Typeface("Arial", fontStyle, fontWeight); return true; } } diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 5058cff26d..5aa497861d 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -1,5 +1,15 @@ Compat issues with assembly Avalonia.Visuals: +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. +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.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. -Total Issues: 3 +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 diff --git a/src/Avalonia.Visuals/Media/FontManager.cs b/src/Avalonia.Visuals/Media/FontManager.cs index ad3fee7eb7..db87c7c6c4 100644 --- a/src/Avalonia.Visuals/Media/FontManager.cs +++ b/src/Avalonia.Visuals/Media/FontManager.cs @@ -13,8 +13,8 @@ namespace Avalonia.Media /// public sealed class FontManager { - private readonly ConcurrentDictionary _typefaceCache = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _glyphTypefaceCache = + new ConcurrentDictionary(); private readonly FontFamily _defaultFontFamily; public FontManager(IFontManagerImpl platformImpl) @@ -76,79 +76,52 @@ namespace Avalonia.Media PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); /// - /// Returns a new typeface, or an existing one if a matching typeface exists. + /// Returns a new , or an existing one if a matching exists. /// - /// The font family. - /// The font style. - /// The font weight. + /// The typeface. /// - /// The typeface. + /// The . /// - public Typeface GetOrAddTypeface(FontFamily fontFamily, FontStyle fontStyle = FontStyle.Normal, - FontWeight fontWeight = FontWeight.Normal) + public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) { while (true) { - if (fontFamily.IsDefault) + if (_glyphTypefaceCache.TryGetValue(typeface, out var glyphTypeface)) { - fontFamily = _defaultFontFamily; + return glyphTypeface; } - var key = new FontKey(fontFamily.Name, fontStyle, fontWeight); + glyphTypeface = new GlyphTypeface(typeface); - if (_typefaceCache.TryGetValue(key, out var typeface)) + if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface)) { - return typeface; + return glyphTypeface; } - typeface = new Typeface(fontFamily, fontStyle, fontWeight); - - if (_typefaceCache.TryAdd(key, typeface)) - { - return typeface; - } - - if (fontFamily == _defaultFontFamily) + if (typeface.FontFamily == _defaultFontFamily) { return null; } - fontFamily = _defaultFontFamily; + typeface = new Typeface(_defaultFontFamily, typeface.Style, typeface.Weight); } } /// - /// Tries to match a specified character to a typeface that supports specified font properties. - /// Returns null if no fallback was found. + /// Tries to match a specified character to a that supports specified font properties. /// /// The codepoint to match against. /// The font style. /// The font weight. /// The font family. This is optional and used for fallback lookup. /// The culture. + /// The matching . /// - /// The matched typeface. + /// True, if the could match the character to specified parameters, False otherwise. /// - public Typeface MatchCharacter(int codepoint, - FontStyle fontStyle = FontStyle.Normal, - FontWeight fontWeight = FontWeight.Normal, - FontFamily fontFamily = null, CultureInfo culture = null) - { - foreach (var cachedTypeface in _typefaceCache.Values) - { - // First try to find a cached typeface by style and weight to avoid redundant glyph index lookup. - if (cachedTypeface.Style == fontStyle && cachedTypeface.Weight == fontWeight - && cachedTypeface.GlyphTypeface.GetGlyph((uint)codepoint) != 0) - { - return cachedTypeface; - } - } - - var matchedTypeface = PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out var key) ? - _typefaceCache.GetOrAdd(key, new Typeface(key.FamilyName, key.Style, key.Weight)) : - null; - - return matchedTypeface; - } + public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, + FontWeight fontWeight, + FontFamily fontFamily, CultureInfo culture, out Typeface typeface) => + PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out typeface); } } diff --git a/src/Avalonia.Visuals/Media/Fonts/FontKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontKey.cs deleted file mode 100644 index b330db8462..0000000000 --- a/src/Avalonia.Visuals/Media/Fonts/FontKey.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace Avalonia.Media.Fonts -{ - public readonly struct FontKey : IEquatable - { - public FontKey(string familyName, FontStyle style, FontWeight weight) - { - FamilyName = familyName; - Style = style; - Weight = weight; - } - - public string FamilyName { get; } - public FontStyle Style { get; } - public FontWeight Weight { get; } - - public override int GetHashCode() - { - var hash = FamilyName.GetHashCode(); - - hash = hash * 31 + (int)Style; - hash = hash * 31 + (int)Weight; - - return hash; - } - - public override bool Equals(object other) - { - return other is FontKey key && Equals(key); - } - - public bool Equals(FontKey other) - { - return FamilyName == other.FamilyName && - Style == other.Style && - Weight == other.Weight; - } - } -} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs index 47e716982c..b91a50a27c 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs @@ -70,10 +70,11 @@ namespace Avalonia.Media.TextFormatting var codepoint = Codepoint.ReadAt(text, count, out _); //ToDo: Fix FontFamily fallback - currentTypeface = - FontManager.Current.MatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.FontFamily); + var matchFound = + FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, + defaultTypeface.FontFamily, defaultProperties.CultureInfo, out currentTypeface); - if (currentTypeface != null && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count)) + if (matchFound && TryGetRunProperties(text, currentTypeface, defaultTypeface, out count)) { //Fallback found return new ShapeableTextCharacters(text.Take(count), diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs index 677e930804..17824c3c5e 100644 --- a/src/Avalonia.Visuals/Media/Typeface.cs +++ b/src/Avalonia.Visuals/Media/Typeface.cs @@ -8,17 +8,15 @@ namespace Avalonia.Media /// Represents a typeface. /// [DebuggerDisplay("Name = {FontFamily.Name}, Weight = {Weight}, Style = {Style}")] - public class Typeface : IEquatable + public readonly struct Typeface : IEquatable { - private GlyphTypeface _glyphTypeface; - /// /// Initializes a new instance of the class. /// /// The font family. /// The font style. /// The font weight. - public Typeface([NotNull]FontFamily fontFamily, + public Typeface([NotNull] FontFamily fontFamily, FontStyle style = FontStyle.Normal, FontWeight weight = FontWeight.Normal) { @@ -45,7 +43,7 @@ namespace Avalonia.Media { } - public static Typeface Default => FontManager.Current?.GetOrAddTypeface(FontFamily.Default); + public static Typeface Default { get; } = new Typeface(FontFamily.Default); /// /// Gets the font family. @@ -68,7 +66,7 @@ namespace Avalonia.Media /// /// The glyph typeface. /// - public GlyphTypeface GlyphTypeface => _glyphTypeface ?? (_glyphTypeface = new GlyphTypeface(this)); + public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); public static bool operator !=(Typeface a, Typeface b) { @@ -77,32 +75,17 @@ namespace Avalonia.Media public static bool operator ==(Typeface a, Typeface b) { - if (ReferenceEquals(a, b)) - { - return true; - } - - return !(a is null) && a.Equals(b); + return a.Equals(b); } public override bool Equals(object obj) { - if (obj is Typeface typeface) - { - return Equals(typeface); - } - - return false; + return obj is Typeface typeface && Equals(typeface); } public bool Equals(Typeface other) { - if (other is null) - { - return false; - } - - return FontFamily.Equals(other.FontFamily) && Style == other.Style && Weight == other.Weight; + return FontFamily == other.FontFamily && Style == other.Style && Weight == other.Weight; } public override int GetHashCode() diff --git a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs index 59b08aae0a..e562b45ca8 100644 --- a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs +++ b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Globalization; using Avalonia.Media; -using Avalonia.Media.Fonts; namespace Avalonia.Platform { @@ -26,13 +25,13 @@ namespace Avalonia.Platform /// The font weight. /// The font family. This is optional and used for fallback lookup. /// The culture. - /// The matching font key. + /// The matching typeface. /// /// True, if the could match the character to specified parameters, False otherwise. /// bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, - FontFamily fontFamily, CultureInfo culture, out FontKey fontKey); + FontFamily fontFamily, CultureInfo culture, out Typeface typeface); /// /// Creates a glyph typeface. diff --git a/src/Avalonia.Visuals/Rendering/RendererBase.cs b/src/Avalonia.Visuals/Rendering/RendererBase.cs index b37d5d660b..5c9cace4cd 100644 --- a/src/Avalonia.Visuals/Rendering/RendererBase.cs +++ b/src/Avalonia.Visuals/Rendering/RendererBase.cs @@ -20,7 +20,7 @@ namespace Avalonia.Rendering _useManualFpsCounting = useManualFpsCounting; _fpsText = new FormattedText { - Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily.Default), + Typeface = new Typeface(FontFamily.Default), FontSize = s_fontSize }; } diff --git a/src/Skia/Avalonia.Skia/FontManagerImpl.cs b/src/Skia/Avalonia.Skia/FontManagerImpl.cs index 91bc937475..62ec39346a 100644 --- a/src/Skia/Avalonia.Skia/FontManagerImpl.cs +++ b/src/Skia/Avalonia.Skia/FontManagerImpl.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using Avalonia.Media; -using Avalonia.Media.Fonts; using Avalonia.Platform; using SkiaSharp; @@ -31,7 +30,7 @@ namespace Avalonia.Skia public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, - FontFamily fontFamily, CultureInfo culture, out FontKey fontKey) + FontFamily fontFamily, CultureInfo culture, out Typeface fontKey) { SKFontStyle skFontStyle; @@ -81,7 +80,7 @@ namespace Avalonia.Skia continue; } - fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight); + fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight); return true; } @@ -92,7 +91,7 @@ namespace Avalonia.Skia if (skTypeface != null) { - fontKey = new FontKey(skTypeface.FamilyName, fontStyle, fontWeight); + fontKey = new Typeface(skTypeface.FamilyName, fontStyle, fontWeight); return true; } diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs index 6c2ac17923..71deb1235f 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs @@ -2,31 +2,28 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Avalonia.Media; -using Avalonia.Media.Fonts; using SkiaSharp; namespace Avalonia.Skia { internal class SKTypefaceCollection { - private readonly ConcurrentDictionary _typefaces = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _typefaces = + new ConcurrentDictionary(); - public void AddTypeface(FontKey key, SKTypeface typeface) + public void AddTypeface(Typeface key, SKTypeface typeface) { _typefaces.TryAdd(key, typeface); } public SKTypeface Get(Typeface typeface) { - var key = new FontKey(typeface.FontFamily.Name, typeface.Style, typeface.Weight); - - return GetNearestMatch(_typefaces, key); + return GetNearestMatch(_typefaces, typeface); } - private static SKTypeface GetNearestMatch(IDictionary typefaces, FontKey key) + private static SKTypeface GetNearestMatch(IDictionary typefaces, Typeface key) { - if (typefaces.TryGetValue(new FontKey(key.FamilyName, key.Style, key.Weight), out var typeface)) + if (typefaces.TryGetValue(key, out var typeface)) { return typeface; } @@ -42,7 +39,7 @@ namespace Avalonia.Skia { if (weight - j >= 100) { - if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight - j)), out typeface)) + if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight - j)), out typeface)) { return typeface; } @@ -53,7 +50,7 @@ namespace Avalonia.Skia continue; } - if (typefaces.TryGetValue(new FontKey(key.FamilyName, (FontStyle)i, (FontWeight)(weight + j)), out typeface)) + if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight + j)), out typeface)) { return typeface; } diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs index 7ca44e7282..de77c9186e 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs @@ -56,7 +56,7 @@ namespace Avalonia.Skia continue; } - var key = new FontKey(fontFamily.Name, typeface.FontSlant.ToAvalonia(), + var key = new Typeface(fontFamily, typeface.FontSlant.ToAvalonia(), (FontWeight)typeface.FontWeight); typeFaceCollection.AddTypeface(key, typeface); diff --git a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs index 33af15076d..6d95d759ec 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Globalization; using Avalonia.Media; -using Avalonia.Media.Fonts; using Avalonia.Platform; using SharpDX.DirectWrite; using FontFamily = Avalonia.Media.FontFamily; @@ -34,7 +33,7 @@ namespace Avalonia.Direct2D1.Media public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, - FontFamily fontFamily, CultureInfo culture, out FontKey fontKey) + FontFamily fontFamily, CultureInfo culture, out Typeface typeface) { var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount; @@ -51,12 +50,12 @@ namespace Avalonia.Direct2D1.Media var fontFamilyName = font.FontFamily.FamilyNames.GetString(0); - fontKey = new FontKey(fontFamilyName, fontStyle, fontWeight); + typeface = new Typeface(fontFamilyName, fontStyle, fontWeight); return true; } - fontKey = default; + typeface = default; return false; } diff --git a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs index 8683da9a01..7528424521 100644 --- a/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs +++ b/tests/Avalonia.RenderTests/Media/FormattedTextImplTests.cs @@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media { var r = AvaloniaLocator.Current.GetService(); return r.CreateFormattedText(text, - FontManager.Current.GetOrAddTypeface(fontFamily, fontStyle, fontWeight), + new Typeface(fontFamily, fontStyle, fontWeight), fontSize, textAlignment, wrapping, diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index f36d6d9e4a..a0fe348166 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -39,7 +39,7 @@ namespace Avalonia.Skia.UnitTests.Media private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName }; public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, - CultureInfo culture, out FontKey fontKey) + CultureInfo culture, out Typeface typeface) { foreach (var customTypeface in _customTypefaces) { @@ -48,7 +48,7 @@ namespace Avalonia.Skia.UnitTests.Media continue; } - fontKey = new FontKey(customTypeface.FontFamily.Name, fontStyle, fontWeight); + typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight); return true; } @@ -56,7 +56,7 @@ namespace Avalonia.Skia.UnitTests.Media var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight, SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint); - fontKey = new FontKey(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight); + typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight); return true; } @@ -73,13 +73,13 @@ namespace Avalonia.Skia.UnitTests.Media skTypeface = typefaceCollection.Get(typeface); break; } - case "Noto Sans": { var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_italicTypeface.FontFamily); skTypeface = typefaceCollection.Get(typeface); break; } + case FontFamily.DefaultFontFamilyName: case "Noto Mono": { var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 4a88b259bc..ea806e01fe 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -130,7 +130,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { using (Start()) { - const string text = "1234الدولي"; + const string text = "ぁぁぁぁالدولي"; var defaultProperties = new GenericTextRunProperties(Typeface.Default); diff --git a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs index e614c60310..ba3b346f1b 100644 --- a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Globalization; using Avalonia.Media; -using Avalonia.Media.Fonts; using Avalonia.Platform; namespace Avalonia.UnitTests @@ -26,9 +25,9 @@ namespace Avalonia.UnitTests } public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, - CultureInfo culture, out FontKey fontKey) + CultureInfo culture, out Typeface fontKey) { - fontKey = default; + fontKey = new Typeface(_defaultFamilyName); return false; } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs index 81a4ca6495..2b0ffa4ed6 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs @@ -1,6 +1,5 @@ using System; using Avalonia.Media; -using Avalonia.Platform; using Avalonia.UnitTests; using Xunit; @@ -15,9 +14,11 @@ namespace Avalonia.Visuals.UnitTests.Media { var fontFamily = new FontFamily("MyFont"); - var typeface = FontManager.Current.GetOrAddTypeface(fontFamily); + var typeface = new Typeface(fontFamily); - Assert.Same(typeface, FontManager.Current.GetOrAddTypeface(fontFamily)); + var glyphTypeface = FontManager.Current.GetOrAddGlyphTypeface(typeface); + + Assert.Same(glyphTypeface, FontManager.Current.GetOrAddGlyphTypeface(typeface)); } } From cf7b2966fe126845cc7019f5700c34eaf8652c85 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 8 Sep 2020 19:34:08 +0100 Subject: [PATCH 061/829] brew extract. --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 29d2c62caf..92e4d9b7fb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -61,7 +61,7 @@ jobs: inputs: script: | brew update - brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb + brew extract castxml https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb - task: CmdLine@2 displayName: 'Install Nuke' From 71eaddfedbf4d5abd951966f076862a03d64e68c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 8 Sep 2020 19:47:51 +0100 Subject: [PATCH 062/829] dont install castxml on osx. --- azure-pipelines.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 92e4d9b7fb..721a0415f4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -56,13 +56,6 @@ jobs: xcodeVersion: '10' # Options: 8, 9, default, specifyPath args: '-derivedDataPath ./' - - task: CmdLine@2 - displayName: 'Install CastXML' - inputs: - script: | - brew update - brew extract castxml https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb - - task: CmdLine@2 displayName: 'Install Nuke' inputs: From 588d4377eb85bca48b647b5f826e47f739079c29 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 5 Sep 2020 16:53:07 +0100 Subject: [PATCH 063/829] Radio button and checkbox vertically center the content. --- src/Avalonia.Themes.Fluent/CheckBox.xaml | 2 +- src/Avalonia.Themes.Fluent/RadioButton.xaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/CheckBox.xaml b/src/Avalonia.Themes.Fluent/CheckBox.xaml index 5f82fed08c..0ba6fb81c2 100644 --- a/src/Avalonia.Themes.Fluent/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/CheckBox.xaml @@ -12,7 +12,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/RadioButton.xaml b/src/Avalonia.Themes.Fluent/RadioButton.xaml index acde4ea0be..35cc902d91 100644 --- a/src/Avalonia.Themes.Fluent/RadioButton.xaml +++ b/src/Avalonia.Themes.Fluent/RadioButton.xaml @@ -17,7 +17,7 @@ - + From dd9c4666a97620571c93536e9543cd5a11aee32d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 8 Sep 2020 21:16:43 +0100 Subject: [PATCH 064/829] clarify max skia cache size property. --- src/Skia/Avalonia.Skia/SkiaOptions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Skia/Avalonia.Skia/SkiaOptions.cs b/src/Skia/Avalonia.Skia/SkiaOptions.cs index d14120204c..afd17acf97 100644 --- a/src/Skia/Avalonia.Skia/SkiaOptions.cs +++ b/src/Skia/Avalonia.Skia/SkiaOptions.cs @@ -15,6 +15,8 @@ namespace Avalonia /// /// The maximum number of bytes for video memory to store textures and resources. + /// This is set by default to the recommended value for Avalonia. + /// Setting this to null will give you the default Skia value. /// public long? MaxGpuResourceSizeBytes { get; set; } = 1024 * 600 * 4 * 12; // ~28mb 12x 1024 x 600 textures. } From d26445fdcdbb2970e4c61ed51c7c41f7fd55fc1b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 8 Sep 2020 21:31:47 +0100 Subject: [PATCH 065/829] use remarks tag. --- src/Skia/Avalonia.Skia/SkiaOptions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/SkiaOptions.cs b/src/Skia/Avalonia.Skia/SkiaOptions.cs index afd17acf97..493263677d 100644 --- a/src/Skia/Avalonia.Skia/SkiaOptions.cs +++ b/src/Skia/Avalonia.Skia/SkiaOptions.cs @@ -15,9 +15,11 @@ namespace Avalonia /// /// The maximum number of bytes for video memory to store textures and resources. + /// + /// /// This is set by default to the recommended value for Avalonia. /// Setting this to null will give you the default Skia value. - /// + /// public long? MaxGpuResourceSizeBytes { get; set; } = 1024 * 600 * 4 * 12; // ~28mb 12x 1024 x 600 textures. } } From c8e5c1762d7e4ef3806f47f8b47cd45f34156f18 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 9 Sep 2020 09:51:16 +0100 Subject: [PATCH 066/829] budget surfaces. --- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 27b29c6e1e..ed9b06cc97 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -51,7 +51,7 @@ namespace Avalonia.Skia { var imageInfo = MakeImageInfo(width, height, format); if (gpu != null) - return SKSurface.Create(gpu, false, imageInfo); + return SKSurface.Create(gpu, true, imageInfo); return SKSurface.Create(imageInfo); } From ff0a6f0518570169c3e9363afdb4e49421c8feb6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 9 Sep 2020 10:36:52 +0100 Subject: [PATCH 067/829] dispose old layer before creating new one. --- src/Avalonia.Visuals/Rendering/RenderLayer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index d6676e25ff..ddf5f4e5cf 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs @@ -30,12 +30,13 @@ namespace Avalonia.Rendering { if (Size != size || Scaling != scaling) { + Bitmap.Dispose(); var resized = RefCountable.Create(drawingContext.CreateLayer(size)); using (var context = resized.Item.CreateDrawingContext(null)) { context.Clear(Colors.Transparent); - Bitmap.Dispose(); + Bitmap = resized; Scaling = scaling; Size = size; From a8100d9df53a7159a7aa6aa8ae48b959e726928e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 9 Sep 2020 10:59:04 +0100 Subject: [PATCH 068/829] Revert "budget surfaces." This reverts commit c8e5c1762d7e4ef3806f47f8b47cd45f34156f18. --- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index ed9b06cc97..27b29c6e1e 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -51,7 +51,7 @@ namespace Avalonia.Skia { var imageInfo = MakeImageInfo(width, height, format); if (gpu != null) - return SKSurface.Create(gpu, true, imageInfo); + return SKSurface.Create(gpu, false, imageInfo); return SKSurface.Create(imageInfo); } From 54fb09a304de8f748f7dbfa0dabb7033e818ca14 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 9 Sep 2020 15:40:36 +0200 Subject: [PATCH 069/829] Fix unit test --- .../Media/TextFormatting/TextFormatterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index ea806e01fe..adcc79e029 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -130,7 +130,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { using (Start()) { - const string text = "ぁぁぁぁالدولي"; + const string text = "ABCDالدولي"; var defaultProperties = new GenericTextRunProperties(Typeface.Default); From 556b934d77877b9bb546ce4b0fcc6910963ffb84 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 10 Sep 2020 08:58:36 +0200 Subject: [PATCH 070/829] Store args in ClassicDesktopStyleApplicationLifetime. --- .../ClassicDesktopStyleApplicationLifetime.cs | 14 ++++++++++---- .../IClassicDesktopStyleApplicationLifetime.cs | 7 +++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index 6dd5b8cc81..e2c8e7e8e2 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -47,6 +47,11 @@ namespace Avalonia.Controls.ApplicationLifetimes /// public event EventHandler Exit; + /// + /// Gets the arguments passed to the AppBuilder Start method. + /// + public string[] Args { get; set; } + /// public ShutdownMode ShutdownMode { get; set; } @@ -68,9 +73,6 @@ namespace Avalonia.Controls.ApplicationLifetimes else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow) Shutdown(); } - - - public void Shutdown(int exitCode = 0) { @@ -123,7 +125,11 @@ namespace Avalonia this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) where T : AppBuilderBase, new() { - var lifetime = new ClassicDesktopStyleApplicationLifetime() {ShutdownMode = shutdownMode}; + var lifetime = new ClassicDesktopStyleApplicationLifetime() + { + Args = args, + ShutdownMode = shutdownMode + }; builder.SetupWithLifetime(lifetime); return lifetime.Start(args); } diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index a1006d907b..212f0b8617 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -8,6 +8,13 @@ namespace Avalonia.Controls.ApplicationLifetimes /// public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime { + /// + /// Gets the arguments passed to the + /// + /// method. + /// + string[] Args { get; } + /// /// Gets or sets the . This property indicates whether the application is shutdown explicitly or implicitly. /// If is set to OnExplicitShutdown the application is only closes if Shutdown is called. From 7b8a6226028657d9c2486d59e24aad5064b24611 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 10 Sep 2020 11:16:00 +0200 Subject: [PATCH 071/829] Update API compat baseline. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 11708b360f..af88c569a6 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -12,7 +12,9 @@ MembersMustExist : Member 'public Avalonia.DirectProperty Avalonia.Interactivity.RoutedEvent Avalonia.Controls.TreeView.SelectionChangedEvent' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Controls.ISelectionModel Avalonia.Controls.TreeView.Selection.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.TreeView.Selection.set(Avalonia.Controls.ISelectionModel)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.String[] Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.Args' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.String[] Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.Args.get()' is present in the implementation but not in the contract. MembersMustExist : Member 'public Avalonia.DirectProperty Avalonia.DirectProperty Avalonia.Controls.Primitives.SelectingItemsControl.SelectionProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected Avalonia.Controls.ISelectionModel Avalonia.Controls.Primitives.SelectingItemsControl.Selection.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Controls.Primitives.SelectingItemsControl.Selection.set(Avalonia.Controls.ISelectionModel)' does not exist in the implementation but it does exist in the contract. -Total Issues: 16 +Total Issues: 18 From ed7a75acbcb72c890932bace587fe55326804d65 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 10 Sep 2020 12:51:55 +0200 Subject: [PATCH 072/829] Ensure windows set to fullscreen are shown. Previously, `UnmanagedMethods.ShowWindow` wasn't called when trying to show a window with `WindowState="FullScreen"`. This is because win32 conflates showing a window and setting a window state, and setting all windows states except fullscreen require a call to `ShowWindow`, except fullscreen which does *not*. However it does require a call to `ShowWindow` when we are actually showing the window ;) --- src/Windows/Avalonia.Win32/WindowImpl.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4929283874..9c6bce1c90 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -853,7 +853,7 @@ namespace Avalonia.Win32 private void ShowWindow(WindowState state) { - ShowWindowCommand command; + ShowWindowCommand? command; var newWindowProperties = _windowProperties; @@ -875,8 +875,8 @@ namespace Avalonia.Win32 case WindowState.FullScreen: newWindowProperties.IsFullScreen = true; - UpdateWindowProperties(newWindowProperties); - return; + command = IsWindowVisible(_hwnd) ? (ShowWindowCommand?)null : ShowWindowCommand.Restore; + break; default: throw new ArgumentException("Invalid WindowState."); @@ -884,7 +884,10 @@ namespace Avalonia.Win32 UpdateWindowProperties(newWindowProperties); - UnmanagedMethods.ShowWindow(_hwnd, command); + if (command.HasValue) + { + UnmanagedMethods.ShowWindow(_hwnd, command.Value); + } if (state == WindowState.Maximized) { From 70d062ba39e618d5084720324d5996e2609465d7 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Thu, 10 Sep 2020 16:26:47 +0100 Subject: [PATCH 073/829] make code more readable. --- src/Avalonia.OpenGL/Angle/AngleEglInterface.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index 375b93c27c..3ec6ba1772 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -18,11 +18,18 @@ namespace Avalonia.OpenGL.Angle static Func LoadAngle() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { throw new PlatformNotSupportedException(); + } + else { var disp = eglGetProcAddress("eglGetPlatformDisplayEXT"); + if (disp == IntPtr.Zero) + { throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point"); + } + return eglGetProcAddress; } } From ecfd4bf56107b6235a316611d0a11673cd63db32 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 10 Sep 2020 17:14:49 +0100 Subject: [PATCH 074/829] remove padding from checkbox and radiobutton. --- src/Avalonia.Themes.Fluent/CheckBox.xaml | 1 - src/Avalonia.Themes.Fluent/RadioButton.xaml | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/CheckBox.xaml b/src/Avalonia.Themes.Fluent/CheckBox.xaml index 0ba6fb81c2..04799b3bc2 100644 --- a/src/Avalonia.Themes.Fluent/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/CheckBox.xaml @@ -8,7 +8,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/RadioButton.xaml b/src/Avalonia.Themes.Fluent/RadioButton.xaml index 35cc902d91..8fd569816d 100644 --- a/src/Avalonia.Themes.Fluent/RadioButton.xaml +++ b/src/Avalonia.Themes.Fluent/RadioButton.xaml @@ -13,7 +13,6 @@ - From c48c82f171ecb4658e10474636505216547c5ec3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 10 Sep 2020 17:17:23 +0100 Subject: [PATCH 075/829] formatting. --- src/Avalonia.OpenGL/Angle/AngleEglInterface.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index 3ec6ba1772..8565d99b45 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -17,21 +17,19 @@ namespace Avalonia.OpenGL.Angle static Func LoadAngle() { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - throw new PlatformNotSupportedException(); - } - else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var disp = eglGetProcAddress("eglGetPlatformDisplayEXT"); - + if (disp == IntPtr.Zero) { throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point"); } - + return eglGetProcAddress; } + + throw new PlatformNotSupportedException(); } } From acab1102081e3d09c359b79174e8706ac127f34b Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 8 Sep 2020 16:57:38 -0500 Subject: [PATCH 076/829] PopupRoot IFocusScope --- src/Avalonia.Controls/Primitives/PopupRoot.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index da7352b77f..2721ab879f 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Reactive.Disposables; using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Platform; @@ -14,7 +15,7 @@ namespace Avalonia.Controls.Primitives /// /// The root window of a . /// - public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost + public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost, IFocusScope { private readonly TopLevel _parent; private PopupPositionerParameters _positionerParameters; From 4e2dc7f5dbb9a2c38ce9e24a75318b5b32b38758 Mon Sep 17 00:00:00 2001 From: amwx Date: Tue, 8 Sep 2020 17:41:25 -0500 Subject: [PATCH 077/829] Add test --- .../Primitives/PopupTests.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index d9176ca55d..a0b7368a4e 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -395,6 +395,34 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void Focusable_Controls_In_Popup_Should_Get_Focus() + { + using (CreateServicesWithFocus()) + { + var tb = new TextBox(); + var b = new Button(); + var p = new Popup + { + PlacementTarget = PreparedWindow(), + Child = new StackPanel + { + Children = + { + tb, + b + } + } + }; + ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + + p.Open(); + tb.Focus(); + + Assert.True(FocusManager.Instance?.Current == tb); + } + } + private IDisposable CreateServices() { return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: @@ -407,6 +435,21 @@ namespace Avalonia.Controls.UnitTests.Primitives }))); } + private IDisposable CreateServicesWithFocus() + { + return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: + new MockWindowingPlatform(null, + x => + { + if (UsePopupHost) + return null; + return MockWindowingPlatform.CreatePopupMock(x).Object; + }), + focusManager: new FocusManager(), + keyboardDevice: () => new KeyboardDevice())); + } + + private PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p) { var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); From 05c6978617d87c30ae7cfb6a541c397b1547e400 Mon Sep 17 00:00:00 2001 From: amwx Date: Thu, 10 Sep 2020 16:29:16 -0500 Subject: [PATCH 078/829] Try to get test to pass on CI --- tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index a0b7368a4e..e5dcba9912 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -416,8 +416,12 @@ namespace Avalonia.Controls.UnitTests.Primitives }; ((ISetLogicalParent)p).SetParent(p.PlacementTarget); + p.Opened += (s, e) => + { + tb.Focus(); + }; + p.Open(); - tb.Focus(); Assert.True(FocusManager.Instance?.Current == tb); } From 7782261ec3b3148fac846e1b0e77c36672b4f0b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Fri, 11 Sep 2020 03:26:58 +0100 Subject: [PATCH 079/829] Added typed AvaloniaProperty.Changed. --- src/Avalonia.Base/ApiCompatBaseline.txt | 3 +++ src/Avalonia.Base/AvaloniaProperty.cs | 17 +++--------- src/Avalonia.Base/AvaloniaProperty`1.cs | 27 +++++++++++++++++++ .../Mixins/SelectableMixin.cs | 4 +-- src/Avalonia.Controls/NativeMenu.Export.cs | 2 +- src/Avalonia.Controls/NativeMenuItem.cs | 2 +- .../Presenters/TextPresenter.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 2 +- .../AvaloniaObjectTests_Direct.cs | 2 +- .../AvaloniaPropertyTests.cs | 4 +-- 10 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 src/Avalonia.Base/ApiCompatBaseline.txt diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt new file mode 100644 index 0000000000..4668a572c5 --- /dev/null +++ b/src/Avalonia.Base/ApiCompatBaseline.txt @@ -0,0 +1,3 @@ +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 diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 39391490b0..3ae0445e9b 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Utilities; @@ -18,7 +17,6 @@ namespace Avalonia public static readonly object UnsetValue = new UnsetValueType(); private static int s_nextId; - private readonly Subject _changed; private readonly PropertyMetadata _defaultMetadata; private readonly Dictionary _metadata; private readonly Dictionary _metadataCache = new Dictionary(); @@ -50,7 +48,6 @@ namespace Avalonia throw new ArgumentException("'name' may not contain periods."); } - _changed = new Subject(); _metadata = new Dictionary(); Name = name; @@ -77,7 +74,6 @@ namespace Avalonia Contract.Requires(source != null); Contract.Requires(ownerType != null); - _changed = source._changed; _metadata = new Dictionary(); Name = source.Name; @@ -139,7 +135,7 @@ namespace Avalonia /// An observable that is fired when this property changes on any /// instance. /// - public IObservable Changed => _changed; + public IObservable Changed => GetChanged(); /// /// Gets a method that gets called before and after the property starts being notified on an @@ -474,15 +470,6 @@ namespace Avalonia public abstract void Accept(IAvaloniaPropertyVisitor vistor, ref TData data) where TData : struct; - /// - /// Notifies the observable. - /// - /// The observable arguments. - internal void NotifyChanged(AvaloniaPropertyChangedEventArgs e) - { - _changed.OnNext(e); - } - /// /// Routes an untyped ClearValue call to a typed call. /// @@ -553,6 +540,8 @@ namespace Avalonia _hasMetadataOverrides = true; } + protected abstract IObservable GetChanged(); + private PropertyMetadata GetMetadataWithOverrides(Type type) { if (type is null) diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 2f26d855f2..7480d9c9c5 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Utilities; @@ -10,6 +11,8 @@ namespace Avalonia /// The value type of the property. public abstract class AvaloniaProperty : AvaloniaProperty { + private readonly Subject> _changed; + /// /// Initializes a new instance of the class. /// @@ -24,6 +27,7 @@ namespace Avalonia Action notifying = null) : base(name, typeof(TValue), ownerType, metadata, notifying) { + _changed = new Subject>(); } /// @@ -38,8 +42,31 @@ namespace Avalonia PropertyMetadata metadata) : base(source, ownerType, metadata) { + _changed = source is AvaloniaProperty p ? p._changed : new Subject>(); } + /// + /// Gets an observable that is fired when this property changes on any + /// instance. + /// + /// + /// An observable that is fired when this property changes on any + /// instance. + /// + + public new IObservable> Changed => _changed; + + /// + /// Notifies the observable. + /// + /// The observable arguments. + internal void NotifyChanged(AvaloniaPropertyChangedEventArgs e) + { + _changed.OnNext(e); + } + + protected override IObservable GetChanged() => Changed; + protected BindingValue TryConvert(object value) { if (value == UnsetValue) diff --git a/src/Avalonia.Controls/Mixins/SelectableMixin.cs b/src/Avalonia.Controls/Mixins/SelectableMixin.cs index d2586ab6e8..e7dbecb06e 100644 --- a/src/Avalonia.Controls/Mixins/SelectableMixin.cs +++ b/src/Avalonia.Controls/Mixins/SelectableMixin.cs @@ -42,7 +42,7 @@ namespace Avalonia.Controls.Mixins { Contract.Requires(isSelected != null); - isSelected.Changed.Subscribe(x => + isSelected.Changed.Subscribe((AvaloniaPropertyChangedEventArgs x) => { var sender = x.Sender as TControl; @@ -58,4 +58,4 @@ namespace Avalonia.Controls.Mixins }); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index 776e9d2171..89e4c9e492 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -73,7 +73,7 @@ namespace Avalonia.Controls throw new InvalidOperationException("IsNativeMenuExported property is read-only"); info.ChangingIsExported = false; }); - MenuProperty.Changed.Subscribe(args => + MenuProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs args) => { if (args.Sender is TopLevel tl) { diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 4c94d82eb4..d4badbc559 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -20,7 +20,7 @@ namespace Avalonia.Controls static NativeMenuItem() { - MenuProperty.Changed.Subscribe(args => + MenuProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs args) => { var item = (NativeMenuItem)args.Sender; var value = (NativeMenu)args.NewValue; diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index cb7bee1d33..f5115a2f7c 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -82,7 +82,7 @@ namespace Avalonia.Controls.Presenters TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty, TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty); - Observable.Merge(TextProperty.Changed, TextBlock.ForegroundProperty.Changed, + Observable.Merge(TextProperty.Changed, TextBlock.ForegroundProperty.Changed, TextAlignmentProperty.Changed, TextWrappingProperty.Changed, TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed, TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed, diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 3b9e9c4751..d8477840af 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -138,7 +138,7 @@ namespace Avalonia.Controls FontStyleProperty, TextWrappingProperty, FontFamilyProperty, TextTrimmingProperty, TextProperty, PaddingProperty, LineHeightProperty, MaxLinesProperty); - Observable.Merge(TextProperty.Changed, ForegroundProperty.Changed, + Observable.Merge(TextProperty.Changed, ForegroundProperty.Changed, TextAlignmentProperty.Changed, TextWrappingProperty.Changed, TextTrimmingProperty.Changed, FontSizeProperty.Changed, FontStyleProperty.Changed, FontWeightProperty.Changed, diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 81a8de1046..83ae663419 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -92,7 +92,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); bool raised = false; - Class1.FooProperty.Changed.Subscribe(e => + Class1.FooProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs e) => raised = e.Property == Class1.FooProperty && (string)e.OldValue == "initial" && (string)e.NewValue == "newvalue" && diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index d7f927372e..19040ff584 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs @@ -83,7 +83,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); string value = null; - Class1.FooProperty.Changed.Subscribe(x => value = (string)x.NewValue); + Class1.FooProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs x) => value = (string)x.NewValue); target.SetValue(Class1.FooProperty, "newvalue"); Assert.Equal("newvalue", value); @@ -95,7 +95,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var result = new List(); - Class1.FooProperty.Changed.Subscribe(x => result.Add((string)x.NewValue)); + Class1.FooProperty.Changed.Subscribe((AvaloniaPropertyChangedEventArgs x) => result.Add((string)x.NewValue)); target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation); target.SetValue(Class1.FooProperty, "local"); From ca5ec1fba49884af7edba49a982e0b5d849bd8a9 Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Fri, 11 Sep 2020 00:22:56 -0400 Subject: [PATCH 080/829] Add left padding for radiobutton and checkbox --- src/Avalonia.Themes.Fluent/CheckBox.xaml | 1 + src/Avalonia.Themes.Fluent/RadioButton.xaml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Avalonia.Themes.Fluent/CheckBox.xaml b/src/Avalonia.Themes.Fluent/CheckBox.xaml index 04799b3bc2..73c44c02ab 100644 --- a/src/Avalonia.Themes.Fluent/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/CheckBox.xaml @@ -8,6 +8,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/RadioButton.xaml b/src/Avalonia.Themes.Fluent/RadioButton.xaml index 8fd569816d..078f51c87a 100644 --- a/src/Avalonia.Themes.Fluent/RadioButton.xaml +++ b/src/Avalonia.Themes.Fluent/RadioButton.xaml @@ -13,6 +13,7 @@ + From f33e12c22996fbe3a23938462f5fe66de41df9fd Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Fri, 11 Sep 2020 04:05:05 -0400 Subject: [PATCH 081/829] DataGridTextColumn: Make FontFamily, FontSize, FontStyle, FontWeight and Foreground properties bindable --- .../ApiCompatBaseline.txt | 5 + .../DataGridTextColumn.cs | 231 +++++------------- .../Utils/DataGridHelper.cs | 22 ++ 3 files changed, 92 insertions(+), 166 deletions(-) create mode 100644 src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs diff --git a/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt new file mode 100644 index 0000000000..82472c505a --- /dev/null +++ b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt @@ -0,0 +1,5 @@ +Compat issues with assembly Avalonia.Controls.DataGrid: +MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.DataGridTextColumn.FontFamilyProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.String Avalonia.Controls.DataGridTextColumn.FontFamily.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Controls.DataGridTextColumn.FontFamily.set(System.String)' does not exist in the implementation but it does exist in the contract. +Total Issues: 3 diff --git a/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs index d31204b9e6..1cf6ab68ac 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs @@ -20,11 +20,6 @@ namespace Avalonia.Controls { private const string DATAGRID_TextColumnCellTextBlockMarginKey = "DataGridTextColumnCellTextBlockMargin"; - private double? _fontSize; - private FontStyle? _fontStyle; - private FontWeight? _fontWeight; - private IBrush _foreground; - /// /// Initializes a new instance of the class. /// @@ -36,18 +31,24 @@ namespace Avalonia.Controls /// /// Identifies the FontFamily dependency property. /// - public static readonly StyledProperty FontFamilyProperty = - AvaloniaProperty.Register(nameof(FontFamily)); + public static readonly AttachedProperty FontFamilyProperty = + TextBlock.FontFamilyProperty.AddOwner(); /// /// Gets or sets the font name. /// - public string FontFamily + public FontFamily FontFamily { - get { return GetValue(FontFamilyProperty); } - set { SetValue(FontFamilyProperty, value); } + get => GetValue(FontFamilyProperty); + set => SetValue(FontFamilyProperty, value); } + /// + /// Identifies the FontSize dependency property. + /// + public static readonly AttachedProperty FontSizeProperty = + TextBlock.FontSizeProperty.AddOwner(); + /// /// Gets or sets the font size. /// @@ -55,74 +56,66 @@ namespace Avalonia.Controls [DefaultValue(double.NaN)] public double FontSize { - get - { - return _fontSize ?? Double.NaN; - } - set - { - if (_fontSize != value) - { - _fontSize = value; - NotifyPropertyChanged(nameof(FontSize)); - } - } + get => GetValue(FontSizeProperty); + set => SetValue(FontSizeProperty, value); } + /// + /// Identifies the FontStyle dependency property. + /// + public static readonly AttachedProperty FontStyleProperty = + TextBlock.FontStyleProperty.AddOwner(); + /// /// Gets or sets the font style. /// public FontStyle FontStyle { - get - { - return _fontStyle ?? FontStyle.Normal; - } - set - { - if (_fontStyle != value) - { - _fontStyle = value; - NotifyPropertyChanged(nameof(FontStyle)); - } - } + get => GetValue(FontStyleProperty); + set => SetValue(FontStyleProperty, value); } + /// + /// Identifies the FontWeight dependency property. + /// + public static readonly AttachedProperty FontWeightProperty = + TextBlock.FontWeightProperty.AddOwner(); + /// /// Gets or sets the font weight or thickness. /// public FontWeight FontWeight { - get - { - return _fontWeight ?? FontWeight.Normal; - } - set - { - if (_fontWeight != value) - { - _fontWeight = value; - NotifyPropertyChanged(nameof(FontWeight)); - } - } + get => GetValue(FontWeightProperty); + set => SetValue(FontWeightProperty, value); } + /// + /// Identifies the Foreground dependency property. + /// + public static readonly AttachedProperty ForegroundProperty = + TextBlock.ForegroundProperty.AddOwner(); + /// /// Gets or sets a brush that describes the foreground of the column cells. /// public IBrush Foreground { - get - { - return _foreground; - } - set + get => GetValue(ForegroundProperty); + set => SetValue(ForegroundProperty, value); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == FontFamilyProperty + || change.Property == FontSizeProperty + || change.Property == FontStyleProperty + || change.Property == FontWeightProperty + || change.Property == ForegroundProperty) { - if (_foreground != value) - { - _foreground = value; - NotifyPropertyChanged(nameof(Foreground)); - } + NotifyPropertyChanged(change.Property.Name); } } @@ -154,26 +147,7 @@ namespace Avalonia.Controls Background = new SolidColorBrush(Colors.Transparent) }; - if (IsSet(FontFamilyProperty)) - { - textBox.FontFamily = FontFamily; - } - if (_fontSize.HasValue) - { - textBox.FontSize = _fontSize.Value; - } - if (_fontStyle.HasValue) - { - textBox.FontStyle = _fontStyle.Value; - } - if (_fontWeight.HasValue) - { - textBox.FontWeight = _fontWeight.Value; - } - if (_foreground != null) - { - textBox.Foreground = _foreground; - } + SyncProperties(textBox); return textBox; } @@ -192,26 +166,8 @@ namespace Avalonia.Controls VerticalAlignment = VerticalAlignment.Center }; - if (IsSet(FontFamilyProperty)) - { - textBlockElement.FontFamily = FontFamily; - } - if (_fontSize.HasValue) - { - textBlockElement.FontSize = _fontSize.Value; - } - if (_fontStyle.HasValue) - { - textBlockElement.FontStyle = _fontStyle.Value; - } - if (_fontWeight.HasValue) - { - textBlockElement.FontWeight = _fontWeight.Value; - } - if (_foreground != null) - { - textBlockElement.Foreground = _foreground; - } + SyncProperties(textBlockElement); + if (Binding != null) { textBlockElement.Bind(TextBlock.TextProperty, Binding); @@ -261,99 +217,42 @@ namespace Avalonia.Controls throw new ArgumentNullException("element"); } - if(element is TextBox textBox) + if (element is AvaloniaObject content) { if (propertyName == nameof(FontFamily)) { - textBox.FontFamily = FontFamily; + DataGridHelper.SyncColumnProperty(this, content, FontFamilyProperty); } else if (propertyName == nameof(FontSize)) { - SetTextFontSize(textBox, TextBox.FontSizeProperty); + DataGridHelper.SyncColumnProperty(this, content, FontSizeProperty); } else if (propertyName == nameof(FontStyle)) { - textBox.FontStyle = FontStyle; + DataGridHelper.SyncColumnProperty(this, content, FontStyleProperty); } else if (propertyName == nameof(FontWeight)) { - textBox.FontWeight = FontWeight; + DataGridHelper.SyncColumnProperty(this, content, FontWeightProperty); } else if (propertyName == nameof(Foreground)) { - textBox.Foreground = Foreground; - } - else - { - if (FontFamily != null) - { - textBox.FontFamily = FontFamily; - } - SetTextFontSize(textBox, TextBox.FontSizeProperty); - textBox.FontStyle = FontStyle; - textBox.FontWeight = FontWeight; - if (Foreground != null) - { - textBox.Foreground = Foreground; - } - } - - } - else if (element is TextBlock textBlock) - { - if (propertyName == nameof(FontFamily)) - { - textBlock.FontFamily = FontFamily; - } - else if (propertyName == nameof(FontSize)) - { - SetTextFontSize(textBlock, TextBlock.FontSizeProperty); - } - else if (propertyName == nameof(FontStyle)) - { - textBlock.FontStyle = FontStyle; - } - else if (propertyName == nameof(FontWeight)) - { - textBlock.FontWeight = FontWeight; - } - else if (propertyName == nameof(Foreground)) - { - textBlock.Foreground = Foreground; - } - else - { - if (FontFamily != null) - { - textBlock.FontFamily = FontFamily; - } - SetTextFontSize(textBlock, TextBlock.FontSizeProperty); - textBlock.FontStyle = FontStyle; - textBlock.FontWeight = FontWeight; - if (Foreground != null) - { - textBlock.Foreground = Foreground; - } + DataGridHelper.SyncColumnProperty(this, content, ForegroundProperty); } } else { - throw DataGridError.DataGrid.ValueIsNotAnInstanceOfEitherOr("element", typeof(TextBox), typeof(TextBlock)); + throw DataGridError.DataGrid.ValueIsNotAnInstanceOf("element", typeof(AvaloniaObject)); } } - private void SetTextFontSize(AvaloniaObject textElement, AvaloniaProperty fontSizeProperty) + private void SyncProperties(AvaloniaObject content) { - double newFontSize = FontSize; - if (double.IsNaN(newFontSize)) - { - textElement.ClearValue(fontSizeProperty); - } - else - { - textElement.SetValue(fontSizeProperty, newFontSize); - } + DataGridHelper.SyncColumnProperty(this, content, FontFamilyProperty); + DataGridHelper.SyncColumnProperty(this, content, FontSizeProperty); + DataGridHelper.SyncColumnProperty(this, content, FontStyleProperty); + DataGridHelper.SyncColumnProperty(this, content, FontWeightProperty); + DataGridHelper.SyncColumnProperty(this, content, ForegroundProperty); } - } } diff --git a/src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs new file mode 100644 index 0000000000..d553a82fa0 --- /dev/null +++ b/src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs @@ -0,0 +1,22 @@ +namespace Avalonia.Controls +{ + public static class DataGridHelper + { + internal static void SyncColumnProperty(AvaloniaObject column, AvaloniaObject content, AvaloniaProperty property) + { + SyncColumnProperty(column, content, property, property); + } + + internal static void SyncColumnProperty(AvaloniaObject column, AvaloniaObject content, AvaloniaProperty contentProperty, AvaloniaProperty columnProperty) + { + if (!column.IsSet(columnProperty)) + { + content.ClearValue(contentProperty); + } + else + { + content.SetValue(contentProperty, column.GetValue(columnProperty)); + } + } + } +} From ab5c3dd190088d80464edbdd76bb366b7ae52bab Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Fri, 11 Sep 2020 04:06:10 -0400 Subject: [PATCH 082/829] DataGridCheckBoxColumn: Make IsThreeState property bindable --- .../DataGridCheckBoxColumn.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs index f1bbea9949..e2a067ac61 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs @@ -17,9 +17,7 @@ namespace Avalonia.Controls /// public class DataGridCheckBoxColumn : DataGridBoundColumn { - private bool _beganEditWithKeyboard; - private bool _isThreeState; private CheckBox _currentCheckBox; private DataGrid _owningGrid; @@ -31,6 +29,12 @@ namespace Avalonia.Controls BindingTarget = CheckBox.IsCheckedProperty; } + /// + /// Defines the property. + /// + public static StyledProperty IsThreeStateProperty = + CheckBox.IsThreeStateProperty.AddOwner(); + /// /// Gets or sets a value that indicates whether the hosted controls allow three states or two. /// @@ -39,17 +43,17 @@ namespace Avalonia.Controls /// public bool IsThreeState { - get - { - return _isThreeState; - } - set + get => GetValue(IsThreeStateProperty); + set => SetValue(IsThreeStateProperty, value); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == IsThreeStateProperty) { - if (_isThreeState != value) - { - _isThreeState = value; - NotifyPropertyChanged(nameof(IsThreeState)); - } + NotifyPropertyChanged(change.Property.Name); } } @@ -203,9 +207,9 @@ namespace Avalonia.Controls { throw new ArgumentNullException("element"); } - if(element is CheckBox checkBox) + if (element is CheckBox checkBox) { - checkBox.IsThreeState = IsThreeState; + DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty); } else { @@ -229,7 +233,7 @@ namespace Avalonia.Controls { checkBox.HorizontalAlignment = HorizontalAlignment.Center; checkBox.VerticalAlignment = VerticalAlignment.Center; - checkBox.IsThreeState = IsThreeState; + DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty); } private bool EnsureOwningGrid() From fae969644a110f1a78b9d9307de9eeefa7cc86c7 Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Fri, 11 Sep 2020 04:11:02 -0400 Subject: [PATCH 083/829] Update DataGrid sample page to test column bindings --- samples/ControlCatalog/Pages/DataGridPage.xaml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index d045626c2c..cacc2204bd 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -11,12 +11,17 @@ - + DataGrid A control for displaying and interacting with a data source. - + + + + + + @@ -39,13 +44,13 @@ - + - - - + + +