From 25b00749c58cd69b317305d780f597684309bff4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 3 Sep 2018 09:36:10 +0200 Subject: [PATCH 001/118] Updated portable.xaml submodule to latest develop. --- .../Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index 8abbe09592..31ea8e6900 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit 8abbe09592668efb573ac4d5548ba2d7e464ba78 +Subproject commit 31ea8e6900d859a8d1bf45972954b075d2a59f30 From 66bebd0c1b5a5c4ece8c08c675c0ef926b41e3c9 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Sep 2018 16:45:27 +0200 Subject: [PATCH 002/118] Initial --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 91 ++++++++++--------- .../ExternalRenderTarget.cs | 22 +---- .../FramebufferShimRenderTarget.cs | 27 +----- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 10 +- .../Media/Direct2D1FontCollectionCache.cs | 13 +-- .../Media/DrawingContextImpl.cs | 10 +- .../Media/FormattedTextImpl.cs | 4 +- .../Avalonia.Direct2D1/Media/GeometryImpl.cs | 3 +- .../Media/Imaging/BitmapImpl.cs | 7 -- .../Media/Imaging/D2DBitmapImpl.cs | 28 +++--- .../Imaging/D2DRenderTargetBitmapImpl.cs | 39 +++----- .../Media/Imaging/WicBitmapImpl.cs | 33 +++---- .../Imaging/WicRenderTargetBitmapImpl.cs | 22 ++--- .../Media/Imaging/WriteableWicBitmapImpl.cs | 8 +- .../Media/StreamGeometryImpl.cs | 6 +- .../Avalonia.Direct2D1/RenderTarget.cs | 19 +--- .../SwapChainRenderTarget.cs | 55 +++-------- 17 files changed, 132 insertions(+), 265 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 296edcb2d9..437bae6fd1 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -4,14 +4,12 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using Avalonia.Direct2D1.Media; -using Avalonia.Media; -using Avalonia.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; -using Avalonia.Rendering; +using Avalonia.Media; +using Avalonia.Platform; namespace Avalonia { @@ -31,15 +29,16 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); - private static SharpDX.Direct2D1.Factory s_d2D1Factory; + public static SharpDX.Direct2D1.Factory1 Direct2D1Factory { get; private set; } - private static SharpDX.DirectWrite.Factory s_dwfactory; + public static SharpDX.Direct2D1.Device1 Direct2D1Device { get; private set; } - private static SharpDX.WIC.ImagingFactory s_imagingFactory; + public static SharpDX.DirectWrite.Factory1 DirectWriteFactory { get; private set; } - private static SharpDX.DXGI.Device s_dxgiDevice; + public static SharpDX.WIC.ImagingFactory ImagingFactory { get; private set; } + + public static SharpDX.DXGI.Device1 DxgiDevice { get; private set; } - private static SharpDX.Direct2D1.Device s_d2D1Device; private static readonly object s_initLock = new object(); private static bool s_initialized = false; @@ -49,13 +48,14 @@ namespace Avalonia.Direct2D1 lock (s_initLock) { if (s_initialized) + { return; + } #if DEBUG try { - s_d2D1Factory = - - new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, + Direct2D1Factory = new SharpDX.Direct2D1.Factory1( + SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); } catch @@ -63,12 +63,19 @@ namespace Avalonia.Direct2D1 // } #endif - s_dwfactory = new SharpDX.DirectWrite.Factory(); - s_imagingFactory = new SharpDX.WIC.ImagingFactory(); - if (s_d2D1Factory == null) - s_d2D1Factory = new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, + if (Direct2D1Factory == null) + { + Direct2D1Factory = new SharpDX.Direct2D1.Factory1( + SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None); + } + using (var factory = new SharpDX.DirectWrite.Factory()) + { + DirectWriteFactory = factory.QueryInterface(); + } + + ImagingFactory = new SharpDX.WIC.ImagingFactory(); var featureLevels = new[] { @@ -83,17 +90,18 @@ namespace Avalonia.Direct2D1 using (var d3dDevice = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, featureLevels)) { - s_dxgiDevice = d3dDevice.QueryInterface(); + DxgiDevice = d3dDevice.QueryInterface(); } - using (var factory1 = s_d2D1Factory.QueryInterface()) + using (var device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice)) { - s_d2D1Device = new SharpDX.Direct2D1.Device(factory1, s_dxgiDevice); + Direct2D1Device = device.QueryInterface(); } + s_initialized = true; } } @@ -101,19 +109,13 @@ namespace Avalonia.Direct2D1 public static void Initialize() { InitializeDirect2D(); - AvaloniaLocator.CurrentMutable - .Bind().ToConstant(s_instance) - .BindToSelf(s_d2D1Factory) - .BindToSelf(s_dwfactory) - .BindToSelf(s_imagingFactory) - .BindToSelf(s_dxgiDevice) - .BindToSelf(s_d2D1Device); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(s_instance); SharpDX.Configuration.EnableReleaseOnFinalizer = true; } public IBitmapImpl CreateBitmap(int width, int height) { - return new WicBitmapImpl(s_imagingFactory, width, height); + return new WicBitmapImpl(width, height); } public IFormattedTextImpl CreateFormattedText( @@ -140,14 +142,22 @@ namespace Avalonia.Direct2D1 if (s is IPlatformHandle nativeWindow) { if (nativeWindow.HandleDescriptor != "HWND") + { throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " + nativeWindow.HandleDescriptor); + } + return new HwndRenderTarget(nativeWindow); } if (s is IExternalDirect2DRenderTargetSurface external) - return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory); + { + return new ExternalRenderTarget(external); + } + if (s is IFramebufferPlatformSurface fb) - return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory); + { + return new FramebufferShimRenderTarget(fb); + } } throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } @@ -158,19 +168,12 @@ namespace Avalonia.Direct2D1 double dpiX, double dpiY) { - return new WicRenderTargetBitmapImpl( - s_imagingFactory, - s_d2D1Factory, - s_dwfactory, - width, - height, - dpiX, - dpiY); + return new WicRenderTargetBitmapImpl(width, height, dpiX, dpiY); } public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null) { - return new WriteableWicBitmapImpl(s_imagingFactory, width, height, format); + return new WriteableWicBitmapImpl(width, height, format); } public IStreamGeometryImpl CreateStreamGeometry() @@ -180,17 +183,17 @@ namespace Avalonia.Direct2D1 public IBitmapImpl LoadBitmap(string fileName) { - return new WicBitmapImpl(s_imagingFactory, fileName); + return new WicBitmapImpl(fileName); } public IBitmapImpl LoadBitmap(Stream stream) { - return new WicBitmapImpl(s_imagingFactory, stream); + return new WicBitmapImpl(stream); } public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) { - return new WicBitmapImpl(s_imagingFactory, format, data, width, height, stride); + return new WicBitmapImpl(format, data, width, height, stride); } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index 176cedd377..aad50331d2 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -1,27 +1,19 @@ -using System; -using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; -using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1 { class ExternalRenderTarget : IRenderTarget, ILayerFactory { private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; - private readonly DirectWriteFactory _dwFactory; - private readonly SharpDX.WIC.ImagingFactory _wicFactory; public ExternalRenderTarget( - IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, - DirectWriteFactory dwFactory, - SharpDX.WIC.ImagingFactory wicFactory) + IExternalDirect2DRenderTargetSurface externalRenderTargetProvider) { _externalRenderTargetProvider = externalRenderTargetProvider; - _dwFactory = dwFactory; - _wicFactory = wicFactory; } public void Dispose() @@ -33,7 +25,7 @@ namespace Avalonia.Direct2D1 { var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); _externalRenderTargetProvider.BeforeDrawing(); - return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () => + return new DrawingContextImpl(visualBrushRenderer, null, target, null, () => { try { @@ -48,12 +40,8 @@ namespace Avalonia.Direct2D1 public IRenderTargetBitmapImpl CreateLayer(Size size) { - var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); - return D2DRenderTargetBitmapImpl.CreateCompatible( - _wicFactory, - _dwFactory, - target, - size); + var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget(); + return D2DRenderTargetBitmapImpl.CreateCompatible(renderTarget, size); } } } diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs index 523cfeed46..5ae174083c 100644 --- a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs @@ -1,14 +1,9 @@ using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Text; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Direct2D1.Media; -using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Interop; -using SharpDX.Direct2D1; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -17,22 +12,14 @@ namespace Avalonia.Direct2D1 class FramebufferShimRenderTarget : IRenderTarget { private readonly IFramebufferPlatformSurface _surface; - private readonly ImagingFactory _imagingFactory; - private readonly Factory _d2DFactory; - private readonly SharpDX.DirectWrite.Factory _dwriteFactory; - public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface, - ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory) + public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface) { _surface = surface; - _imagingFactory = imagingFactory; - _d2DFactory = d2dFactory; - _dwriteFactory = dwriteFactory; } public void Dispose() - { - + { } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) @@ -44,7 +31,7 @@ namespace Avalonia.Direct2D1 throw new ArgumentException("Unsupported pixel format: " + locked.Format); } - return new FramebufferShim(locked, _imagingFactory, _d2DFactory, _dwriteFactory) + return new FramebufferShim(locked) .CreateDrawingContext(visualBrushRenderer); } @@ -52,10 +39,8 @@ namespace Avalonia.Direct2D1 { private readonly ILockedFramebuffer _target; - public FramebufferShim(ILockedFramebuffer target, - ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory - ) : base(imagingFactory, d2dFactory, dwriteFactory, - target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) + public FramebufferShim(ILockedFramebuffer target) : + base(target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) { _target = target; } @@ -76,10 +61,8 @@ namespace Avalonia.Direct2D1 } Dispose(); _target.Dispose(); - }); } } - } } diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 49402d54b9..589f85f208 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Controls.Platform.Surfaces; -using Avalonia.Platform; +using Avalonia.Platform; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.DXGI; @@ -22,7 +16,7 @@ namespace Avalonia.Direct2D1 protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) { - return new SwapChain1(dxgiFactory, DxgiDevice, _window.Handle, ref swapChainDesc); + return new SwapChain1(dxgiFactory, Direct2D1Platform.DxgiDevice, _window.Handle, ref swapChainDesc); } protected override Size2F GetWindowDpi() diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs index 4199c73c54..d60aa15a5e 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs @@ -7,16 +7,13 @@ namespace Avalonia.Direct2D1.Media internal static class Direct2D1FontCollectionCache { private static readonly ConcurrentDictionary s_cachedCollections; - private static readonly SharpDX.DirectWrite.Factory s_factory; private static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; static Direct2D1FontCollectionCache() { s_cachedCollections = new ConcurrentDictionary(); - s_factory = AvaloniaLocator.Current.GetService(); - - s_installedFontCollection = s_factory.GetSystemFontCollection(false); + s_installedFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false); } public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface) @@ -39,7 +36,7 @@ namespace Avalonia.Direct2D1.Media } return new SharpDX.DirectWrite.TextFormat( - s_factory, + Direct2D1Platform.DirectWriteFactory, fontFamilyName, fontCollection, (SharpDX.DirectWrite.FontWeight)typeface.Weight, @@ -57,9 +54,9 @@ namespace Avalonia.Direct2D1.Media { var assets = FontFamilyLoader.LoadFontAssets(key); - var fontLoader = new DWriteResourceFontLoader(s_factory, assets); + var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets); - return new SharpDX.DirectWrite.FontCollection(s_factory, fontLoader, fontLoader.Key); + return new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index ae5dd3ae13..3aec6c6eb1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -24,8 +24,6 @@ namespace Avalonia.Direct2D1.Media private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; - private readonly SharpDX.WIC.ImagingFactory _imagingFactory; - private SharpDX.DirectWrite.Factory _directWriteFactory; /// /// Initializes a new instance of the class. @@ -36,16 +34,12 @@ namespace Avalonia.Direct2D1.Media /// An object to use to create layers. May be null, in which case a /// will created when a new layer is requested. /// - /// The DirectWrite factory. - /// The WIC imaging factory. /// An optional swap chain associated with this drawing context. /// An optional delegate to be called when context is disposed. public DrawingContextImpl( IVisualBrushRenderer visualBrushRenderer, ILayerFactory layerFactory, SharpDX.Direct2D1.RenderTarget renderTarget, - SharpDX.DirectWrite.Factory directWriteFactory, - SharpDX.WIC.ImagingFactory imagingFactory, SharpDX.DXGI.SwapChain1 swapChain = null, Action finishedCallback = null) { @@ -54,8 +48,6 @@ namespace Avalonia.Direct2D1.Media _renderTarget = renderTarget; _swapChain = swapChain; _finishedCallback = finishedCallback; - _directWriteFactory = directWriteFactory; - _imagingFactory = imagingFactory; _renderTarget.BeginDraw(); } @@ -443,7 +435,7 @@ namespace Avalonia.Direct2D1.Media return new ImageBrushImpl( visualBrush, _renderTarget, - new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap), + new D2DBitmapImpl(intermediate.Bitmap), destinationSize); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index c3fe60e790..09b249e19f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -21,15 +21,13 @@ namespace Avalonia.Direct2D1.Media { Text = text; - var factory = AvaloniaLocator.Current.GetService(); - var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface); textFormat.WordWrapping = wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; TextLayout = new DWrite.TextLayout( - factory, + Direct2D1Platform.DirectWriteFactory, Text ?? string.Empty, textFormat, (float)constraint.Width, diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index 120ab71ead..7c8ddaca3f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -53,10 +53,9 @@ namespace Avalonia.Direct2D1.Media public ITransformedGeometryImpl WithTransform(Matrix transform) { - var factory = AvaloniaLocator.Current.GetService(); return new TransformedGeometryImpl( new TransformedGeometry( - factory, + Direct2D1Platform.Direct2D1Factory, GetSourceGeometry(), transform.ToDirect2D()), this); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index d58f023391..30af01283a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -1,19 +1,12 @@ using System; using System.IO; using Avalonia.Platform; -using SharpDX.WIC; using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { public abstract class BitmapImpl : IBitmapImpl, IDisposable { - public BitmapImpl(ImagingFactory imagingFactory) - { - WicImagingFactory = imagingFactory; - } - - public ImagingFactory WicImagingFactory { get; } public abstract int PixelWidth { get; } public abstract int PixelHeight { get; } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 6713cb13be..139100490c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,7 +1,6 @@ using System; using System.IO; using SharpDX.Direct2D1; -using WICFactory = SharpDX.WIC.ImagingFactory; using ImagingFactory2 = SharpDX.WIC.ImagingFactory2; using ImageParameters = SharpDX.WIC.ImageParameters; using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder; @@ -13,52 +12,51 @@ namespace Avalonia.Direct2D1.Media /// public class D2DBitmapImpl : BitmapImpl { - private Bitmap _direct2D; + private Bitmap _direct2DBitmap; /// /// Initialize a new instance of the class /// with a bitmap backed by GPU memory. /// - /// The image factory to use when saving out this bitmap. /// The GPU bitmap. /// /// This bitmap must be either from the same render target, /// or if the render target is a , /// the device associated with this context, to be renderable. /// - public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap) - : base(imagingFactory) + public D2DBitmapImpl(Bitmap d2DBitmap) { - _direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); + _direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); } - public override int PixelWidth => _direct2D.PixelSize.Width; - public override int PixelHeight => _direct2D.PixelSize.Height; + public override int PixelWidth => _direct2DBitmap.PixelSize.Width; + public override int PixelHeight => _direct2DBitmap.PixelSize.Height; public override void Dispose() { base.Dispose(); - _direct2D.Dispose(); + _direct2DBitmap.Dispose(); } public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) { - return new OptionalDispose(_direct2D, false); + return new OptionalDispose(_direct2DBitmap, false); } public override void Save(Stream stream) { - using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder)) - using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null)) + //ToDo: Not supported under Windows 7! + using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)Direct2D1Platform.ImagingFactory, null)) { var parameters = new ImageParameters( new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied), - _direct2D.DotsPerInch.Width, - _direct2D.DotsPerInch.Height, + _direct2DBitmap.DotsPerInch.Width, + _direct2DBitmap.DotsPerInch.Height, 0, 0, PixelWidth, PixelHeight); - imageEncoder.WriteFrame(_direct2D, frameEncode, parameters); + imageEncoder.WriteFrame(_direct2DBitmap, frameEncode, parameters); frameEncode.Commit(); encoder.Commit(); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 2843848fac..7ea303345a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -1,35 +1,25 @@ -using System; -using Avalonia.Platform; +using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; -using SharpDX.WIC; using D2DBitmap = SharpDX.Direct2D1.Bitmap; -using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media.Imaging { public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory { - private readonly DirectWriteFactory _dwriteFactory; - private readonly BitmapRenderTarget _target; + private readonly BitmapRenderTarget _renderTarget; - public D2DRenderTargetBitmapImpl( - ImagingFactory imagingFactory, - DirectWriteFactory dwriteFactory, - BitmapRenderTarget target) - : base(imagingFactory, target.Bitmap) + public D2DRenderTargetBitmapImpl(BitmapRenderTarget renderTarget) + : base(renderTarget.Bitmap) { - _dwriteFactory = dwriteFactory; - _target = target; + _renderTarget = renderTarget; } - public override int PixelWidth => _target.PixelSize.Width; - public override int PixelHeight => _target.PixelSize.Height; + public override int PixelWidth => _renderTarget.PixelSize.Width; + public override int PixelHeight => _renderTarget.PixelSize.Height; public static D2DRenderTargetBitmapImpl CreateCompatible( - ImagingFactory imagingFactory, - DirectWriteFactory dwriteFactory, SharpDX.Direct2D1.RenderTarget renderTarget, Size size) { @@ -37,32 +27,27 @@ namespace Avalonia.Direct2D1.Media.Imaging renderTarget, CompatibleRenderTargetOptions.None, new Size2F((float)size.Width, (float)size.Height)); - return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget); + return new D2DRenderTargetBitmapImpl(bitmapRenderTarget); } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl( - visualBrushRenderer, - this, - _target, - _dwriteFactory, - WicImagingFactory); + return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget); } public IRenderTargetBitmapImpl CreateLayer(Size size) { - return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size); + return CreateCompatible(_renderTarget, size); } public override void Dispose() { - _target.Dispose(); + _renderTarget.Dispose(); } public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) { - return new OptionalDispose(_target.Bitmap, false); + return new OptionalDispose(_renderTarget.Bitmap, false); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index 371dfcfc3e..2c41f1203f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -19,57 +19,52 @@ namespace Avalonia.Direct2D1.Media /// /// Initializes a new instance of the class. /// - /// The WIC imaging factory to use. /// The filename of the bitmap to load. - public WicBitmapImpl(ImagingFactory factory, string fileName) - : base(factory) + public WicBitmapImpl(string fileName) { - using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand)) + using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand)) { - WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); } } /// /// Initializes a new instance of the class. /// - /// The WIC imaging factory to use. /// The stream to read the bitmap from. - public WicBitmapImpl(ImagingFactory factory, Stream stream) - : base(factory) + public WicBitmapImpl(Stream stream) { - using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad)) + using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad)) { - WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); } } /// /// Initializes a new instance of the class. /// - /// The WIC imaging factory to use. /// The width of the bitmap. /// The height of the bitmap. /// Pixel format - public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null) - : base(factory) + public WicBitmapImpl(int width, int height, APixelFormat? pixelFormat = null) { if (!pixelFormat.HasValue) + { pixelFormat = APixelFormat.Bgra8888; + } PixelFormat = pixelFormat; WicImpl = new Bitmap( - factory, + Direct2D1Platform.ImagingFactory, width, height, pixelFormat.Value.ToWic(), BitmapCreateCacheOption.CacheOnLoad); } - public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride) - : base(factory) + public WicBitmapImpl(APixelFormat format, IntPtr data, int width, int height, int stride) { - WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); PixelFormat = format; using (var l = WicImpl.Lock(BitmapLockFlags.Write)) { @@ -112,14 +107,14 @@ namespace Avalonia.Direct2D1.Media /// The Direct2D bitmap. public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) { - FormatConverter converter = new FormatConverter(WicImagingFactory); + FormatConverter converter = new FormatConverter(Direct2D1Platform.ImagingFactory); converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } public override void Save(Stream stream) { - using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frame = new BitmapFrameEncode(encoder)) { frame.Initialize(); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index 0eb2608047..aa8b3ead42 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -5,26 +5,20 @@ using System; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX.Direct2D1; -using SharpDX.WIC; -using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media { public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl { - private readonly DirectWriteFactory _dwriteFactory; - private readonly WicRenderTarget _target; + private readonly WicRenderTarget _renderTarget; public WicRenderTargetBitmapImpl( - ImagingFactory imagingFactory, - Factory d2dFactory, - DirectWriteFactory dwriteFactory, int width, int height, double dpiX, double dpiY, Platform.PixelFormat? pixelFormat = null) - : base(imagingFactory, width, height, pixelFormat) + : base(width, height, pixelFormat) { var props = new RenderTargetProperties { @@ -32,17 +26,16 @@ namespace Avalonia.Direct2D1.Media DpiY = (float)dpiY, }; - _target = new WicRenderTarget( - d2dFactory, + _renderTarget = new WicRenderTarget( + Direct2D1Platform.Direct2D1Factory, WicImpl, props); - - _dwriteFactory = dwriteFactory; } public override void Dispose() { - _target.Dispose(); + _renderTarget.Dispose(); + base.Dispose(); } @@ -51,8 +44,7 @@ namespace Avalonia.Direct2D1.Media public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback) { - return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory, - finishedCallback: finishedCallback); + return new DrawingContextImpl(visualBrushRenderer, null, _renderTarget, finishedCallback: finishedCallback); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index fc931c32db..075fef5ab2 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Platform; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -11,8 +7,8 @@ namespace Avalonia.Direct2D1.Media.Imaging { class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl { - public WriteableWicBitmapImpl(ImagingFactory factory, int width, int height, PixelFormat? pixelFormat) - : base(factory, width, height, pixelFormat) + public WriteableWicBitmapImpl(int width, int height, PixelFormat? pixelFormat) + : base(width, height, pixelFormat) { } diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs index 4c1bc3d6f7..a07d5c1c72 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs @@ -31,8 +31,7 @@ namespace Avalonia.Direct2D1.Media /// public IStreamGeometryImpl Clone() { - Factory factory = AvaloniaLocator.Current.GetService(); - var result = new PathGeometry(factory); + var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory); var sink = result.Open(); ((PathGeometry)Geometry).Stream(sink); sink.Close(); @@ -47,8 +46,7 @@ namespace Avalonia.Direct2D1.Media private static Geometry CreateGeometry() { - Factory factory = AvaloniaLocator.Current.GetService(); - return new PathGeometry(factory); + return new PathGeometry(Direct2D1Platform.Direct2D1Factory); } } } diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index 6086b0c67c..a21b5571c6 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -1,14 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; -using SharpDX.Direct2D1; -using DwFactory = SharpDX.DirectWrite.Factory; -using WicFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1 { @@ -25,32 +21,21 @@ namespace Avalonia.Direct2D1 /// The render target. public RenderTarget(SharpDX.Direct2D1.RenderTarget renderTarget) { - Direct2DFactory = AvaloniaLocator.Current.GetService(); - DirectWriteFactory = AvaloniaLocator.Current.GetService(); - WicFactory = AvaloniaLocator.Current.GetService(); _renderTarget = renderTarget; } - public Factory Direct2DFactory { get; } - public DwFactory DirectWriteFactory { get; } - public WicFactory WicFactory { get; } - /// /// Creates a drawing context for a rendering session. /// /// An . public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory); + return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget); } public IRenderTargetBitmapImpl CreateLayer(Size size) { - return D2DRenderTargetBitmapImpl.CreateCompatible( - WicFactory, - DirectWriteFactory, - _renderTarget, - size); + return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size); } public void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 0a23c63498..f820129b8d 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -1,16 +1,14 @@ -using System; +using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; +using Avalonia.Rendering; + using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; + using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using Device = SharpDX.Direct2D1.Device; -using Factory = SharpDX.Direct2D1.Factory; -using Factory2 = SharpDX.DXGI.Factory2; -using Avalonia.Rendering; -using Avalonia.Direct2D1.Media; -using Avalonia.Direct2D1.Media.Imaging; +using PixelFormat = SharpDX.Direct2D1.PixelFormat; namespace Avalonia.Direct2D1 { @@ -21,23 +19,6 @@ namespace Avalonia.Direct2D1 private DeviceContext _deviceContext; private SwapChain1 _swapChain; - protected SwapChainRenderTarget() - { - DxgiDevice = AvaloniaLocator.Current.GetService(); - D2DDevice = AvaloniaLocator.Current.GetService(); - Direct2DFactory = AvaloniaLocator.Current.GetService(); - DirectWriteFactory = AvaloniaLocator.Current.GetService(); - WicImagingFactory = AvaloniaLocator.Current.GetService(); - } - - public Factory Direct2DFactory { get; } - public SharpDX.DirectWrite.Factory DirectWriteFactory { get; } - public SharpDX.WIC.ImagingFactory WicImagingFactory { get; } - - protected SharpDX.DXGI.Device DxgiDevice { get; } - - public Device D2DDevice { get; } - /// /// Creates a drawing context for a rendering session. /// @@ -54,13 +35,7 @@ namespace Avalonia.Direct2D1 CreateSwapChain(); } - return new DrawingContextImpl( - visualBrushRenderer, - this, - _deviceContext, - DirectWriteFactory, - WicImagingFactory, - _swapChain); + return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain); } public IRenderTargetBitmapImpl CreateLayer(Size size) @@ -70,11 +45,7 @@ namespace Avalonia.Direct2D1 CreateSwapChain(); } - return D2DRenderTargetBitmapImpl.CreateCompatible( - WicImagingFactory, - DirectWriteFactory, - _deviceContext, - size); + return D2DRenderTargetBitmapImpl.CreateCompatible(_deviceContext, size); } public void Dispose() @@ -85,11 +56,11 @@ namespace Avalonia.Direct2D1 private void CreateSwapChain() { - using (var dxgiAdaptor = DxgiDevice.Adapter) - using (var dxgiFactory = dxgiAdaptor.GetParent()) + using (var dxgiAdaptor = Direct2D1Platform.DxgiDevice.Adapter) + using (var dxgiFactory = dxgiAdaptor.GetParent()) { _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; + _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi }; var swapChainDesc = new SwapChainDescription1 { @@ -119,7 +90,7 @@ namespace Avalonia.Direct2D1 new BitmapProperties1( new PixelFormat { - AlphaMode = AlphaMode.Ignore, + AlphaMode = AlphaMode.Premultiplied, Format = Format.B8G8R8A8_UNorm }, _savedDpi.Width, @@ -131,7 +102,7 @@ namespace Avalonia.Direct2D1 } } - protected abstract SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc); + protected abstract SwapChain1 CreateSwapChain(SharpDX.DXGI.Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc); protected abstract Size2F GetWindowDpi(); From e76b694ebdcc4b2ca79532cb30183ea8aa98fc79 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Sep 2018 17:02:22 +0200 Subject: [PATCH 003/118] InterpolationMode.HighQuality support --- .../Media/DrawingContextImpl.cs | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 3aec6c6eb1..b842f26edb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -22,6 +22,7 @@ namespace Avalonia.Direct2D1.Media private readonly IVisualBrushRenderer _visualBrushRenderer; private readonly ILayerFactory _layerFactory; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; + private readonly DeviceContext _deviceContext; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; @@ -48,7 +49,17 @@ namespace Avalonia.Direct2D1.Media _renderTarget = renderTarget; _swapChain = swapChain; _finishedCallback = finishedCallback; - _renderTarget.BeginDraw(); + + if (_renderTarget is DeviceContext deviceContext) + { + _deviceContext = deviceContext; + } + else + { + _deviceContext = _renderTarget.QueryInterface(); + } + + _deviceContext.BeginDraw(); } /// @@ -56,14 +67,14 @@ namespace Avalonia.Direct2D1.Media /// public Matrix Transform { - get { return _renderTarget.Transform.ToAvalonia(); } - set { _renderTarget.Transform = value.ToDirect2D(); } + get { return _deviceContext.Transform.ToAvalonia(); } + set { _deviceContext.Transform = value.ToDirect2D(); } } /// public void Clear(Color color) { - _renderTarget.Clear(color.ToDirect2D()); + _deviceContext.Clear(color.ToDirect2D()); } /// @@ -72,10 +83,13 @@ namespace Avalonia.Direct2D1.Media public void Dispose() { foreach (var layer in _layerPool) + { layer.Dispose(); + } + try { - _renderTarget.EndDraw(); + _deviceContext.EndDraw(); _swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None); _finishedCallback?.Invoke(); @@ -96,29 +110,32 @@ namespace Avalonia.Direct2D1.Media /// The bitmap interpolation mode. public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { - using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) + using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) { var interpolationMode = GetInterpolationMode(bitmapInterpolationMode); - _renderTarget.DrawBitmap( + _deviceContext.DrawBitmap( d2d.Value, destRect.ToSharpDX(), (float)opacity, interpolationMode, - sourceRect.ToSharpDX()); + sourceRect.ToSharpDX(), + null); } } - private static SharpDX.Direct2D1.BitmapInterpolationMode GetInterpolationMode(BitmapInterpolationMode interpolationMode) + private static InterpolationMode GetInterpolationMode(BitmapInterpolationMode interpolationMode) { switch (interpolationMode) { case BitmapInterpolationMode.LowQuality: - return SharpDX.Direct2D1.BitmapInterpolationMode.NearestNeighbor; + return InterpolationMode.NearestNeighbor; case BitmapInterpolationMode.MediumQuality: + return InterpolationMode.Linear; case BitmapInterpolationMode.HighQuality: + return InterpolationMode.HighQualityCubic; case BitmapInterpolationMode.Default: - return SharpDX.Direct2D1.BitmapInterpolationMode.Linear; + return InterpolationMode.Linear; default: throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); } @@ -133,17 +150,17 @@ namespace Avalonia.Direct2D1.Media /// The rect in the output to draw to. public void DrawImage(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { - using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) - using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value)) + using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) + using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value)) using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) - using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) + using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_deviceContext.Factory, destRect.ToDirect2D())) { if (d2dOpacityMask.PlatformBrush != null) { d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D(); } - _renderTarget.FillGeometry( + _deviceContext.FillGeometry( geometry, sourceBrush, d2dOpacityMask.PlatformBrush); @@ -163,11 +180,11 @@ namespace Avalonia.Direct2D1.Media var size = new Rect(p1, p2).Size; using (var d2dBrush = CreateBrush(pen.Brush, size)) - using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) { - _renderTarget.DrawLine( + _deviceContext.DrawLine( p1.ToSharpDX(), p2.ToSharpDX(), d2dBrush.PlatformBrush, @@ -193,7 +210,7 @@ namespace Avalonia.Direct2D1.Media if (d2dBrush.PlatformBrush != null) { var impl = (GeometryImpl)geometry; - _renderTarget.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush); + _deviceContext.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush); } } } @@ -201,12 +218,12 @@ namespace Avalonia.Direct2D1.Media if (pen != null) { using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size)) - using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) { var impl = (GeometryImpl)geometry; - _renderTarget.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke); + _deviceContext.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke); } } } @@ -221,13 +238,13 @@ namespace Avalonia.Direct2D1.Media public void DrawRectangle(Pen pen, Rect rect, float cornerRadius) { using (var brush = CreateBrush(pen.Brush, rect.Size)) - using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (brush.PlatformBrush != null) { if (cornerRadius == 0) { - _renderTarget.DrawRectangle( + _deviceContext.DrawRectangle( rect.ToDirect2D(), brush.PlatformBrush, (float)pen.Thickness, @@ -235,7 +252,7 @@ namespace Avalonia.Direct2D1.Media } else { - _renderTarget.DrawRoundedRectangle( + _deviceContext.DrawRoundedRectangle( new RoundedRectangle { Rect = rect.ToDirect2D(), RadiusX = cornerRadius, RadiusY = cornerRadius }, brush.PlatformBrush, (float)pen.Thickness, @@ -258,7 +275,7 @@ namespace Avalonia.Direct2D1.Media var impl = (FormattedTextImpl)text; using (var brush = CreateBrush(foreground, impl.Size)) - using (var renderer = new AvaloniaTextRenderer(this, _renderTarget, brush.PlatformBrush)) + using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush)) { if (brush.PlatformBrush != null) { @@ -282,11 +299,11 @@ namespace Avalonia.Direct2D1.Media { if (cornerRadius == 0) { - _renderTarget.FillRectangle(rect.ToDirect2D(), b.PlatformBrush); + _deviceContext.FillRectangle(rect.ToDirect2D(), b.PlatformBrush); } else { - _renderTarget.FillRoundedRectangle( + _deviceContext.FillRoundedRectangle( new RoundedRectangle { Rect = new RawRectangleF( @@ -312,7 +329,7 @@ namespace Avalonia.Direct2D1.Media else { var platform = AvaloniaLocator.Current.GetService(); - var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height); + var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); var pixelSize = size * (dpi / 96); return platform.CreateRenderTargetBitmap( (int)pixelSize.Width, @@ -329,12 +346,12 @@ namespace Avalonia.Direct2D1.Media /// A disposable used to undo the clip rectangle. public void PushClip(Rect clip) { - _renderTarget.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive); + _deviceContext.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive); } public void PopClip() { - _renderTarget.PopAxisAlignedClip(); + _deviceContext.PopAxisAlignedClip(); } readonly Stack _layers = new Stack(); @@ -355,8 +372,8 @@ namespace Avalonia.Direct2D1.Media Opacity = (float)opacity, }; - var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); - _renderTarget.PushLayer(ref parameters, layer); + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); + _deviceContext.PushLayer(ref parameters, layer); _layers.Push(layer); } @@ -374,7 +391,7 @@ namespace Avalonia.Direct2D1.Media var layer = _layers.Pop(); if (layer != null) { - _renderTarget.PopLayer(); + _deviceContext.PopLayer(); _layerPool.Push(layer); } } @@ -395,21 +412,21 @@ namespace Avalonia.Direct2D1.Media if (solidColorBrush != null) { - return new SolidColorBrushImpl(solidColorBrush, _renderTarget); + return new SolidColorBrushImpl(solidColorBrush, _deviceContext); } else if (linearGradientBrush != null) { - return new LinearGradientBrushImpl(linearGradientBrush, _renderTarget, destinationSize); + return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize); } else if (radialGradientBrush != null) { - return new RadialGradientBrushImpl(radialGradientBrush, _renderTarget, destinationSize); + return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize); } else if (imageBrush?.Source != null) { return new ImageBrushImpl( imageBrush, - _renderTarget, + _deviceContext, (BitmapImpl)imageBrush.Source.PlatformImpl.Item, destinationSize); } @@ -422,7 +439,7 @@ namespace Avalonia.Direct2D1.Media if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1) { using (var intermediate = new BitmapRenderTarget( - _renderTarget, + _deviceContext, CompatibleRenderTargetOptions.None, intermediateSize.ToSharpDX())) { @@ -434,7 +451,7 @@ namespace Avalonia.Direct2D1.Media return new ImageBrushImpl( visualBrush, - _renderTarget, + _deviceContext, new D2DBitmapImpl(intermediate.Bitmap), destinationSize); } @@ -446,7 +463,7 @@ namespace Avalonia.Direct2D1.Media } } - return new SolidColorBrushImpl(null, _renderTarget); + return new SolidColorBrushImpl(null, _deviceContext); } public void PushGeometryClip(IGeometryImpl clip) @@ -458,8 +475,8 @@ namespace Avalonia.Direct2D1.Media Opacity = 1, GeometricMask = ((GeometryImpl)clip).Geometry }; - var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); - _renderTarget.PushLayer(ref parameters, layer); + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); + _deviceContext.PushLayer(ref parameters, layer); _layers.Push(layer); @@ -479,8 +496,8 @@ namespace Avalonia.Direct2D1.Media Opacity = 1, OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush }; - var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); - _renderTarget.PushLayer(ref parameters, layer); + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); + _deviceContext.PushLayer(ref parameters, layer); _layers.Push(layer); } From 0646106f8fe9d6ead0e433024ed909405cd93c6a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Sep 2018 19:53:57 +0200 Subject: [PATCH 004/118] Introduce ResizeBuffers to prevent SwapChain recreation --- .../SwapChainRenderTarget.cs | 105 ++++++++++-------- 1 file changed, 61 insertions(+), 44 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index f820129b8d..94d407ed0b 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -7,11 +7,13 @@ using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; - namespace Avalonia.Direct2D1 { + + using AlphaMode = SharpDX.Direct2D1.AlphaMode; + using DeviceContext = SharpDX.Direct2D1.DeviceContext; + using PixelFormat = SharpDX.Direct2D1.PixelFormat; + public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory { private Size2 _savedSize; @@ -32,7 +34,8 @@ namespace Avalonia.Direct2D1 { _savedSize = size; _savedDpi = dpi; - CreateSwapChain(); + + Resize(); } return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain); @@ -42,7 +45,7 @@ namespace Avalonia.Direct2D1 { if (_deviceContext == null) { - CreateSwapChain(); + CreateDeviceContext(); } return D2DRenderTargetBitmapImpl.CreateCompatible(_deviceContext, size); @@ -51,54 +54,68 @@ namespace Avalonia.Direct2D1 public void Dispose() { _deviceContext?.Dispose(); + _swapChain?.Dispose(); } + private void Resize() + { + _deviceContext?.Dispose(); + _deviceContext = null; + + _swapChain?.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None); + + CreateDeviceContext(); + } + private void CreateSwapChain() { - using (var dxgiAdaptor = Direct2D1Platform.DxgiDevice.Adapter) - using (var dxgiFactory = dxgiAdaptor.GetParent()) + var swapChainDescription = new SwapChainDescription1 { - _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi }; - - var swapChainDesc = new SwapChainDescription1 + Width = _savedSize.Width, + Height = _savedSize.Height, + Format = Format.B8G8R8A8_UNorm, + SampleDescription = new SampleDescription { - Width = _savedSize.Width, - Height = _savedSize.Height, - Format = Format.B8G8R8A8_UNorm, - Stereo = false, - SampleDescription = new SampleDescription + Count = 1, + Quality = 0, + }, + Usage = Usage.RenderTargetOutput, + BufferCount = 1, + SwapEffect = SwapEffect.Discard, + }; + + using (var dxgiAdapter = Direct2D1Platform.DxgiDevice.Adapter) + using (var dxgiFactory = dxgiAdapter.GetParent()) + { + _swapChain = CreateSwapChain(dxgiFactory, swapChainDescription); + } + } + + private void CreateDeviceContext() + { + _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi }; + + if (_swapChain == null) + { + CreateSwapChain(); + } + + using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) + using (var d2dBackBuffer = new Bitmap1( + _deviceContext, + dxgiBackBuffer, + new BitmapProperties1( + new PixelFormat { - Count = 1, - Quality = 0, + AlphaMode = AlphaMode.Premultiplied, + Format = Format.B8G8R8A8_UNorm }, - Usage = Usage.RenderTargetOutput, - BufferCount = 1, - Scaling = Scaling.Stretch, - SwapEffect = SwapEffect.Discard, - Flags = 0, - }; - - _swapChain?.Dispose(); - _swapChain = CreateSwapChain(dxgiFactory, swapChainDesc); - - using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) - using (var d2dBackBuffer = new Bitmap1( - _deviceContext, - dxgiBackBuffer, - new BitmapProperties1( - new PixelFormat - { - AlphaMode = AlphaMode.Premultiplied, - Format = Format.B8G8R8A8_UNorm - }, - _savedDpi.Width, - _savedDpi.Height, - BitmapOptions.Target | BitmapOptions.CannotDraw))) - { - _deviceContext.Target = d2dBackBuffer; - } + _savedSize.Width, + _savedSize.Height, + BitmapOptions.Target | BitmapOptions.CannotDraw))) + { + _deviceContext.Target = d2dBackBuffer; } } From 98ee428d6f4c31e5e7bfa0c2cda49a11a4a86459 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 14:05:56 +0200 Subject: [PATCH 005/118] Merge fix --- .../SwapChainRenderTarget.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 000ce23cd2..ffa0f5c8bc 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -1,19 +1,13 @@ -using Avalonia.Direct2D1.Media; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using Device = SharpDX.Direct2D1.Device; -using Factory = SharpDX.Direct2D1.Factory; -using Factory2 = SharpDX.DXGI.Factory2; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; - -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using DeviceContext = SharpDX.Direct2D1.DeviceContext; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; namespace Avalonia.Direct2D1 { @@ -109,9 +103,9 @@ namespace Avalonia.Direct2D1 _deviceContext, dxgiBackBuffer, new BitmapProperties1( - new PixelFormat + new SharpDX.Direct2D1.PixelFormat { - AlphaMode = AlphaMode.Premultiplied, + AlphaMode = SharpDX.Direct2D1.AlphaMode.Premultiplied, Format = Format.B8G8R8A8_UNorm }, _savedSize.Width, From 024e0da1da398b765422344cbb768f1efef4cfe9 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 15:47:02 +0200 Subject: [PATCH 006/118] Direct3DInterop fixes --- .../Direct3DInteropSample/MainWindow.cs | 276 ++++++++++-------- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 14 +- .../Media/Imaging/D2DBitmapImpl.cs | 29 +- .../Imaging/D2DRenderTargetBitmapImpl.cs | 5 +- .../Media/Imaging/WriteableWicBitmapImpl.cs | 5 +- 5 files changed, 176 insertions(+), 153 deletions(-) diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index 19c31a3af1..065f1a285a 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -1,81 +1,83 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + using Avalonia; using Avalonia.Controls; +using Avalonia.Direct2D1; using Avalonia.Direct2D1.Media; using Avalonia.Markup.Xaml; using Avalonia.Platform; using Avalonia.Rendering; + using SharpDX; using SharpDX.D3DCompiler; using SharpDX.Direct2D1; using SharpDX.Direct3D; using SharpDX.Direct3D11; using SharpDX.DXGI; -using SharpDX.WIC; -using SharpDX.Mathematics; + using AlphaMode = SharpDX.Direct2D1.AlphaMode; using Buffer = SharpDX.Direct3D11.Buffer; -using DeviceContext = SharpDX.Direct3D11.DeviceContext; -using Factory1 = SharpDX.DXGI.Factory1; +using DeviceContext = SharpDX.Direct2D1.DeviceContext; +using Factory2 = SharpDX.DXGI.Factory2; using InputElement = SharpDX.Direct3D11.InputElement; using Matrix = SharpDX.Matrix; using PixelFormat = SharpDX.Direct2D1.PixelFormat; +using Resource = SharpDX.Direct3D11.Resource; namespace Direct3DInteropSample { - class MainWindow : Window + public class MainWindow : Window { - private SharpDX.Direct3D11.Device _d3dDevice; - private SharpDX.DXGI.Device _dxgiDevice; - Texture2D backBuffer = null; - RenderTargetView renderView = null; - Texture2D depthBuffer = null; - DepthStencilView depthView = null; + Texture2D _backBuffer; + RenderTargetView _renderView; + Texture2D _depthBuffer; + DepthStencilView _depthView; private readonly SwapChain _swapChain; - private SwapChainDescription _desc; + private SwapChainDescription1 _desc; private Matrix _proj = Matrix.Identity; - private Matrix _view; + private readonly Matrix _view; private Buffer _contantBuffer; - private SharpDX.Direct2D1.Device _d2dDevice; - private SharpDX.Direct2D1.DeviceContext _d2dContext; - private RenderTarget _d2dRenderTarget; - private MainWindowViewModel _model; + private DeviceContext _deviceContext; + private readonly MainWindowViewModel _model; public MainWindow() { - _dxgiDevice = AvaloniaLocator.Current.GetService(); - _d3dDevice = _dxgiDevice.QueryInterface(); - _d2dDevice = AvaloniaLocator.Current.GetService(); DataContext = _model = new MainWindowViewModel(); - _desc = new SwapChainDescription() + + _desc = new SwapChainDescription1() { BufferCount = 1, - ModeDescription = - new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height, - new Rational(60, 1), Format.R8G8B8A8_UNorm), - IsWindowed = true, - OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero, + Width = (int)ClientSize.Width, + Height = (int)ClientSize.Height, + Format = Format.R8G8B8A8_UNorm, SampleDescription = new SampleDescription(1, 0), SwapEffect = SwapEffect.Discard, Usage = Usage.RenderTargetOutput }; - _swapChain = new SwapChain(new Factory1(), _d3dDevice, _desc); + using (var factory = Direct2D1Platform.DxgiDevice.Adapter.GetParent()) + { + _swapChain = new SwapChain1(factory, Direct2D1Platform.DxgiDevice, PlatformImpl?.Handle.Handle ?? IntPtr.Zero, ref _desc); + } - _d2dContext = new SharpDX.Direct2D1.DeviceContext(_d2dDevice, DeviceContextOptions.None) + _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = new Size2F(96, 96) }; CreateMesh(); + _view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY); + this.GetObservable(ClientSizeProperty).Subscribe(Resize); + Resize(ClientSize); + AvaloniaXamlLoader.Load(this); + Background = Avalonia.Media.Brushes.Transparent; } @@ -83,29 +85,32 @@ namespace Direct3DInteropSample protected override void HandlePaint(Rect rect) { var viewProj = Matrix.Multiply(_view, _proj); - var context = _d3dDevice.ImmediateContext; + var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; + // Clear views - context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0); - context.ClearRenderTargetView(renderView, Color.White); + context.ClearDepthStencilView(_depthView, DepthStencilClearFlags.Depth, 1.0f, 0); + context.ClearRenderTargetView(_renderView, Color.White); // Update WorldViewProj Matrix - var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) * - Matrix.RotationZ((float) _model.RotationZ) - * Matrix.Scaling((float) _model.Zoom) * viewProj; + var worldViewProj = Matrix.RotationX((float)_model.RotationX) * Matrix.RotationY((float)_model.RotationY) + * Matrix.RotationZ((float)_model.RotationZ) + * Matrix.Scaling((float)_model.Zoom) + * viewProj; worldViewProj.Transpose(); context.UpdateSubresource(ref worldViewProj, _contantBuffer); // Draw the cube context.Draw(36, 0); base.HandlePaint(rect); + // Present! _swapChain.Present(0, PresentFlags.None); } - - void CreateMesh() + private void CreateMesh() { - var device = _d3dDevice; + var device = Direct2D1Platform.Direct3D11Device; + // Compile Vertex and Pixel shaders var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0"); var vertexShader = new VertexShader(device, vertexShaderByteCode); @@ -114,63 +119,72 @@ namespace Direct3DInteropSample var pixelShader = new PixelShader(device, pixelShaderByteCode); var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode); + + var inputElements = new[] + { + new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), + new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) + }; + // Layout from VertexShader input signature - var layout = new InputLayout(device, signature, new[] - { - new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), - new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) - }); - + var layout = new InputLayout( + device, + signature, + inputElements); + // Instantiate Vertex buiffer from vertex data - var vertices = Buffer.Create(device, BindFlags.VertexBuffer, new[] - { - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - - new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom - new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - }); + var vertices = Buffer.Create( + device, + BindFlags.VertexBuffer, + new[] + { + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + }); // Create Constant Buffer _contantBuffer = new Buffer(device, Utilities.SizeOf(), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0); - var context = _d3dDevice.ImmediateContext; + var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; // Prepare All the stages context.InputAssembler.InputLayout = layout; @@ -181,63 +195,73 @@ namespace Direct3DInteropSample context.PixelShader.Set(pixelShader); } - void Resize(Size size) + private void Resize(Size size) { - Utilities.Dispose(ref _d2dRenderTarget); - Utilities.Dispose(ref backBuffer); - Utilities.Dispose(ref renderView); - Utilities.Dispose(ref depthBuffer); - Utilities.Dispose(ref depthView); - var context = _d3dDevice.ImmediateContext; + Utilities.Dispose(ref _deviceContext); + Utilities.Dispose(ref _backBuffer); + Utilities.Dispose(ref _renderView); + Utilities.Dispose(ref _depthBuffer); + Utilities.Dispose(ref _depthView); + var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; + // Resize the backbuffer - _swapChain.ResizeBuffers(_desc.BufferCount, (int)size.Width, (int)size.Height, Format.Unknown, SwapChainFlags.None); + _swapChain.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None); // Get the backbuffer from the swapchain - backBuffer = Texture2D.FromSwapChain(_swapChain, 0); + _backBuffer = Resource.FromSwapChain(_swapChain, 0); // Renderview on the backbuffer - renderView = new RenderTargetView(_d3dDevice, backBuffer); + _renderView = new RenderTargetView(Direct2D1Platform.Direct3D11Device, _backBuffer); // Create the depth buffer - depthBuffer = new Texture2D(_d3dDevice, new Texture2DDescription() - { - Format = Format.D32_Float_S8X24_UInt, - ArraySize = 1, - MipLevels = 1, - Width = (int)size.Width, - Height = (int)size.Height, - SampleDescription = new SampleDescription(1, 0), - Usage = ResourceUsage.Default, - BindFlags = BindFlags.DepthStencil, - CpuAccessFlags = CpuAccessFlags.None, - OptionFlags = ResourceOptionFlags.None - }); + _depthBuffer = new Texture2D( + Direct2D1Platform.Direct3D11Device, + new Texture2DDescription() + { + Format = Format.D32_Float_S8X24_UInt, + ArraySize = 1, + MipLevels = 1, + Width = (int)size.Width, + Height = (int)size.Height, + SampleDescription = new SampleDescription(1, 0), + Usage = ResourceUsage.Default, + BindFlags = BindFlags.DepthStencil, + CpuAccessFlags = CpuAccessFlags.None, + OptionFlags = ResourceOptionFlags.None + }); // Create the depth buffer view - depthView = new DepthStencilView(_d3dDevice, depthBuffer); + _depthView = new DepthStencilView(Direct2D1Platform.Direct3D11Device, _depthBuffer); // Setup targets and viewport for rendering context.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f)); - context.OutputMerger.SetTargets(depthView, renderView); + context.OutputMerger.SetTargets(_depthView, _renderView); // Setup new projection matrix with correct aspect ratio _proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f); using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) { - _d2dRenderTarget = new RenderTarget(AvaloniaLocator.Current.GetService() - , dxgiBackBuffer, new RenderTargetProperties + var renderTarget = new SharpDX.Direct2D1.RenderTarget( + Direct2D1Platform.Direct2D1Factory, + dxgiBackBuffer, + new RenderTargetProperties { DpiX = 96, DpiY = 96, Type = RenderTargetType.Default, - PixelFormat = new PixelFormat(Format.Unknown, AlphaMode.Premultiplied) + PixelFormat = new PixelFormat( + Format.Unknown, + AlphaMode.Premultiplied) }); - } + _deviceContext = renderTarget.QueryInterface(); + + renderTarget.Dispose(); + } } - class D3DRenderTarget: IRenderTarget + private class D3DRenderTarget : IRenderTarget { private readonly MainWindow _window; @@ -245,16 +269,14 @@ namespace Direct3DInteropSample { _window = window; } + public void Dispose() { - } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget, - AvaloniaLocator.Current.GetService(), - AvaloniaLocator.Current.GetService()); + return new DrawingContextImpl(visualBrushRenderer, null, _window._deviceContext); } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 437bae6fd1..38c49924bc 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -29,6 +29,8 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); + public static SharpDX.Direct3D11.Device Direct3D11Device { get; private set; } + public static SharpDX.Direct2D1.Factory1 Direct2D1Factory { get; private set; } public static SharpDX.Direct2D1.Device1 Direct2D1Device { get; private set; } @@ -88,14 +90,12 @@ namespace Avalonia.Direct2D1 SharpDX.Direct3D.FeatureLevel.Level_9_1, }; - using (var d3dDevice = new SharpDX.Direct3D11.Device( + Direct3D11Device = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | - SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, - featureLevels)) - { - DxgiDevice = d3dDevice.QueryInterface(); - } + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, + featureLevels); + + DxgiDevice = Direct3D11Device.QueryInterface(); using (var device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice)) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 5711a79c2e..b65e32bbbb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,19 +1,20 @@ using System; using System.IO; using SharpDX.Direct2D1; -using ImageParameters = SharpDX.WIC.ImageParameters; -using ImagingFactory2 = SharpDX.WIC.ImagingFactory2; -using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder; -using WICFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1.Media { + using SharpDX.WIC; + + using Bitmap = SharpDX.Direct2D1.Bitmap; + using PixelFormat = SharpDX.Direct2D1.PixelFormat; + /// /// A Direct2D Bitmap implementation that uses a GPU memory bitmap as its image. /// public class D2DBitmapImpl : BitmapImpl { - private Bitmap _direct2DBitmap; + private readonly Bitmap _direct2DBitmap; /// /// Initialize a new instance of the class @@ -29,7 +30,7 @@ namespace Avalonia.Direct2D1.Media { _direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); } - + public override int PixelWidth => _direct2DBitmap.PixelSize.Width; public override int PixelHeight => _direct2DBitmap.PixelSize.Height; @@ -47,18 +48,12 @@ namespace Avalonia.Direct2D1.Media public override void Save(Stream stream) { using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) - using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder)) - //ToDo: Not supported under Windows 7! - using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)Direct2D1Platform.ImagingFactory, null)) + using (var frame = new BitmapFrameEncode(encoder)) + using (var bitmapSource = _direct2DBitmap.QueryInterface()) { - var parameters = new ImageParameters( - new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied), - _direct2DBitmap.DotsPerInch.Width, - _direct2DBitmap.DotsPerInch.Height, - 0, 0, PixelWidth, PixelHeight); - - imageEncoder.WriteFrame(_direct2DBitmap, frameEncode, parameters); - frameEncode.Commit(); + frame.Initialize(); + frame.WriteSource(bitmapSource); + frame.Commit(); encoder.Commit(); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 7ea303345a..3646d9e9e1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -1,4 +1,7 @@ -using Avalonia.Platform; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index 075fef5ab2..5ca78ef278 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using Avalonia.Platform; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; From 2a64f133d56ac6d1272bca6cb452e964714b44af Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 7 Sep 2018 13:45:05 +0200 Subject: [PATCH 007/118] SharpDX update to current version --- build/SharpDX.props | 10 ++--- .../Direct3DInteropSample/MainWindow.cs | 2 +- .../Media/AvaloniaTextRenderer.cs | 40 ++----------------- .../Media/FormattedTextImpl.cs | 15 ++++++- 4 files changed, 24 insertions(+), 43 deletions(-) diff --git a/build/SharpDX.props b/build/SharpDX.props index 69aa817a01..fdc802f135 100644 --- a/build/SharpDX.props +++ b/build/SharpDX.props @@ -1,9 +1,9 @@  - - - - - + + + + + diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index 065f1a285a..1ac4b44a74 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -132,7 +132,7 @@ namespace Direct3DInteropSample signature, inputElements); - // Instantiate Vertex buiffer from vertex data + // Instantiate Vertex buffer from vertex data var vertices = Buffer.Create( device, BindFlags.VertexBuffer, diff --git a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs index 51b4eef62d..6e6cf47254 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs @@ -1,7 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; using SharpDX; using SharpDX.Direct2D1; using SharpDX.DirectWrite; @@ -9,7 +8,7 @@ using SharpDX.Mathematics.Interop; namespace Avalonia.Direct2D1.Media { - internal class AvaloniaTextRenderer : TextRenderer + internal class AvaloniaTextRenderer : TextRendererBase { private readonly DrawingContextImpl _context; @@ -27,18 +26,7 @@ namespace Avalonia.Direct2D1.Media _foreground = foreground; } - public IDisposable Shadow - { - get; - set; - } - - public void Dispose() - { - Shadow?.Dispose(); - } - - public Result DrawGlyphRun( + public override Result DrawGlyphRun( object clientDrawingContext, float baselineOriginX, float baselineOriginY, @@ -68,34 +56,14 @@ namespace Avalonia.Direct2D1.Media return Result.Ok; } - public Result DrawInlineObject(object clientDrawingContext, float originX, float originY, InlineObject inlineObject, bool isSideways, bool isRightToLeft, ComObject clientDrawingEffect) - { - throw new NotImplementedException(); - } - - public Result DrawStrikethrough(object clientDrawingContext, float baselineOriginX, float baselineOriginY, ref Strikethrough strikethrough, ComObject clientDrawingEffect) - { - throw new NotImplementedException(); - } - - public Result DrawUnderline(object clientDrawingContext, float baselineOriginX, float baselineOriginY, ref Underline underline, ComObject clientDrawingEffect) - { - throw new NotImplementedException(); - } - - public RawMatrix3x2 GetCurrentTransform(object clientDrawingContext) + public override RawMatrix3x2 GetCurrentTransform(object clientDrawingContext) { return _renderTarget.Transform; } - public float GetPixelsPerDip(object clientDrawingContext) + public override float GetPixelsPerDip(object clientDrawingContext) { return _renderTarget.DotsPerInch.Width / 96; } - - public bool IsPixelSnappingDisabled(object clientDrawingContext) - { - return false; - } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index 09b249e19f..e2c33a51f1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -9,6 +9,8 @@ using DWrite = SharpDX.DirectWrite; namespace Avalonia.Direct2D1.Media { + using System; + public class FormattedTextImpl : IFormattedTextImpl { public FormattedTextImpl( @@ -107,7 +109,18 @@ namespace Avalonia.Direct2D1.Media private Size Measure() { - var metrics = TextLayout.Metrics; + DWrite.TextMetrics metrics; + + // SharpDX bug + try + { + metrics = TextLayout.Metrics; + } + catch (ObjectDisposedException) + { + metrics = TextLayout.Metrics; + } + var width = metrics.WidthIncludingTrailingWhitespace; if (float.IsNaN(width)) From d531b36b9631e069424f98f22057f00584311b9c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 8 Sep 2018 19:55:36 +0200 Subject: [PATCH 008/118] Make brushes raise a Changed event. When their visual representation changes. --- src/Avalonia.Visuals/Media/Brush.cs | 36 +++++++- src/Avalonia.Visuals/Media/GradientBrush.cs | 65 +++++++++++--- src/Avalonia.Visuals/Media/GradientStop.cs | 40 ++++++--- src/Avalonia.Visuals/Media/GradientStops.cs | 23 +++++ src/Avalonia.Visuals/Media/IGradientBrush.cs | 4 +- src/Avalonia.Visuals/Media/IGradientStop.cs | 18 ++++ src/Avalonia.Visuals/Media/IMutableBrush.cs | 9 +- src/Avalonia.Visuals/Media/ImageBrush.cs | 12 ++- .../Media/Immutable/ImmutableGradientBrush.cs | 9 +- .../Media/Immutable/ImmutableGradientStop.cs | 20 +++++ .../Immutable/ImmutableLinearGradientBrush.cs | 4 +- .../Immutable/ImmutableRadialGradientBrush.cs | 4 +- .../Media/LinearGradientBrush.cs | 13 ++- .../Media/RadialGradientBrush.cs | 10 ++- src/Avalonia.Visuals/Media/SolidColorBrush.cs | 13 ++- src/Avalonia.Visuals/Media/TileBrush.cs | 7 ++ src/Avalonia.Visuals/Media/VisualBrush.cs | 12 ++- .../Controls/CustomRenderTests.cs | 2 +- .../Media/LinearGradientBrushTests.cs | 4 +- .../Media/RadialGradientBrushTests.cs | 2 +- .../Avalonia.RenderTests/OpacityMaskTests.cs | 4 +- .../Media/ImageBrushTests.cs | 27 ++++++ .../Media/LinearGradientBrushTests.cs | 86 +++++++++++++++++++ .../Media/SolidColorBrushTests.cs | 21 +++++ 24 files changed, 386 insertions(+), 59 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/GradientStops.cs create mode 100644 src/Avalonia.Visuals/Media/IGradientStop.cs create mode 100644 src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index eef6e1a43c..8ba7c1be04 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media /// Describes how an area is painted. /// [TypeConverter(typeof(BrushConverter))] - public abstract class Brush : AvaloniaObject, IBrush + public abstract class Brush : AvaloniaObject, IMutableBrush { /// /// Defines the property. @@ -18,6 +18,9 @@ namespace Avalonia.Media public static readonly StyledProperty OpacityProperty = AvaloniaProperty.Register(nameof(Opacity), 1.0); + /// + public event EventHandler Changed; + /// /// Gets or sets the opacity of the brush. /// @@ -50,5 +53,36 @@ namespace Avalonia.Media throw new FormatException($"Invalid brush string: '{s}'."); } + + /// + public abstract IBrush ToImmutable(); + + /// + /// Marks a property as affecting the brush's visual representation. + /// + /// The properties. + /// + /// After a call to this method in a brush's static constructor, any change to the + /// property will cause the event to be raised on the brush. + /// + protected static void AffectsRender(params AvaloniaProperty[] properties) + where T : Brush + { + void Invalidate(AvaloniaPropertyChangedEventArgs e) + { + (e.Sender as T)?.RaiseChanged(EventArgs.Empty); + } + + foreach (var property in properties) + { + property.Changed.Subscribe(Invalidate); + } + } + + /// + /// Raises the event. + /// + /// The event args. + protected void RaiseChanged(EventArgs e) => Changed?.Invoke(this, e); } } diff --git a/src/Avalonia.Visuals/Media/GradientBrush.cs b/src/Avalonia.Visuals/Media/GradientBrush.cs index 41c3afc8c3..c123813cee 100644 --- a/src/Avalonia.Visuals/Media/GradientBrush.cs +++ b/src/Avalonia.Visuals/Media/GradientBrush.cs @@ -1,7 +1,11 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using Avalonia.Collections; using Avalonia.Metadata; namespace Avalonia.Media @@ -20,35 +24,74 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty> GradientStopsProperty = - AvaloniaProperty.Register>(nameof(GradientStops)); + public static readonly StyledProperty GradientStopsProperty = + AvaloniaProperty.Register(nameof(GradientStops)); + + private IDisposable _gradientStopsSubscription; + + static GradientBrush() + { + GradientStopsProperty.Changed.Subscribe(GradientStopsChanged); + AffectsRender(SpreadMethodProperty); + } /// /// Initializes a new instance of the class. /// public GradientBrush() { - this.GradientStops = new List(); + this.GradientStops = new GradientStops(); } - /// - /// Gets or sets the brush's spread method that defines how to draw a gradient that - /// doesn't fill the bounds of the destination control. - /// + /// public GradientSpreadMethod SpreadMethod { get { return GetValue(SpreadMethodProperty); } set { SetValue(SpreadMethodProperty, value); } } - /// - /// Gets or sets the brush's gradient stops. - /// + /// [Content] - public IList GradientStops + public GradientStops GradientStops { get { return GetValue(GradientStopsProperty); } set { SetValue(GradientStopsProperty, value); } } + + /// + IReadOnlyList IGradientBrush.GradientStops => GradientStops; + + private static void GradientStopsChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.Sender is GradientBrush brush) + { + var oldValue = (GradientStops)e.OldValue; + var newValue = (GradientStops)e.NewValue; + + if (oldValue != null) + { + oldValue.CollectionChanged -= brush.GradientStopsChanged; + brush._gradientStopsSubscription.Dispose(); + } + + if (newValue != null) + { + newValue.CollectionChanged += brush.GradientStopsChanged; + brush._gradientStopsSubscription = newValue.TrackItemPropertyChanged(brush.GradientStopChanged); + } + + brush.RaiseChanged(EventArgs.Empty); + } + } + + private void GradientStopsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + RaiseChanged(EventArgs.Empty); + } + + private void GradientStopChanged(Tuple e) + { + RaiseChanged(EventArgs.Empty); + } } } diff --git a/src/Avalonia.Visuals/Media/GradientStop.cs b/src/Avalonia.Visuals/Media/GradientStop.cs index 78dd32a18a..00d96a0b3c 100644 --- a/src/Avalonia.Visuals/Media/GradientStop.cs +++ b/src/Avalonia.Visuals/Media/GradientStop.cs @@ -4,10 +4,22 @@ namespace Avalonia.Media { /// - /// GradientStop + /// Describes the location and color of a transition point in a gradient. /// - public sealed class GradientStop + public sealed class GradientStop : AvaloniaObject, IGradientStop { + /// + /// Describes the property. + /// + public static StyledProperty OffsetProperty = + AvaloniaProperty.Register(nameof(Offset)); + + /// + /// Describes the property. + /// + public static StyledProperty ColorProperty = + AvaloniaProperty.Register(nameof(Color)); + /// /// Initializes a new instance of the class. /// @@ -24,16 +36,18 @@ namespace Avalonia.Media Offset = offset; } - // TODO: Make these dependency properties. - - /// - /// The offset - /// - public double Offset { get; set; } + /// + public double Offset + { + get => GetValue(OffsetProperty); + set => SetValue(OffsetProperty, value); + } - /// - /// The color - /// - public Color Color { get; set; } + /// + public Color Color + { + get => GetValue(ColorProperty); + set => SetValue(ColorProperty, value); + } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/GradientStops.cs b/src/Avalonia.Visuals/Media/GradientStops.cs new file mode 100644 index 0000000000..efc11bacd6 --- /dev/null +++ b/src/Avalonia.Visuals/Media/GradientStops.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Linq; +using Avalonia.Collections; +using Avalonia.Media.Immutable; + +namespace Avalonia.Media +{ + /// + /// A collection of s. + /// + public class GradientStops : AvaloniaList + { + public GradientStops() + { + ResetBehavior = ResetBehavior.Remove; + } + + public IReadOnlyList ToImmutable() + { + return this.Select(x => new ImmutableGradientStop(x.Offset, x.Color)).ToList(); + } + } +} diff --git a/src/Avalonia.Visuals/Media/IGradientBrush.cs b/src/Avalonia.Visuals/Media/IGradientBrush.cs index 390ce6ee5b..18db0af660 100644 --- a/src/Avalonia.Visuals/Media/IGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/IGradientBrush.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media /// /// Gets the brush's gradient stops. /// - IList GradientStops { get; } + IReadOnlyList GradientStops { get; } /// /// Gets the brush's spread method that defines how to draw a gradient that doesn't fill @@ -18,4 +18,4 @@ namespace Avalonia.Media /// GradientSpreadMethod SpreadMethod { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/IGradientStop.cs b/src/Avalonia.Visuals/Media/IGradientStop.cs new file mode 100644 index 0000000000..22eb9df60d --- /dev/null +++ b/src/Avalonia.Visuals/Media/IGradientStop.cs @@ -0,0 +1,18 @@ +namespace Avalonia.Media +{ + /// + /// Describes the location and color of a transition point in a gradient. + /// + public interface IGradientStop + { + /// + /// Gets the gradient stop color. + /// + Color Color { get; } + + /// + /// Gets the gradient stop offset. + /// + double Offset { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/IMutableBrush.cs b/src/Avalonia.Visuals/Media/IMutableBrush.cs index 39dd8b80c4..762731a6a8 100644 --- a/src/Avalonia.Visuals/Media/IMutableBrush.cs +++ b/src/Avalonia.Visuals/Media/IMutableBrush.cs @@ -1,10 +1,17 @@ -namespace Avalonia.Media +using System; + +namespace Avalonia.Media { /// /// Represents a mutable brush which can return an immutable clone of itself. /// public interface IMutableBrush : IBrush { + /// + /// Raised when the brush changes visually. + /// + event EventHandler Changed; + /// /// Creates an immutable clone of the brush. /// diff --git a/src/Avalonia.Visuals/Media/ImageBrush.cs b/src/Avalonia.Visuals/Media/ImageBrush.cs index fa491ed3e1..8b42a51d9f 100644 --- a/src/Avalonia.Visuals/Media/ImageBrush.cs +++ b/src/Avalonia.Visuals/Media/ImageBrush.cs @@ -2,13 +2,14 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Media.Imaging; +using Avalonia.Media.Immutable; namespace Avalonia.Media { /// /// Paints an area with an . /// - public class ImageBrush : TileBrush, IImageBrush, IMutableBrush + public class ImageBrush : TileBrush, IImageBrush { /// /// Defines the property. @@ -16,6 +17,11 @@ namespace Avalonia.Media public static readonly StyledProperty SourceProperty = AvaloniaProperty.Register(nameof(Source)); + static ImageBrush() + { + AffectsRender(SourceProperty); + } + /// /// Initializes a new instance of the class. /// @@ -42,9 +48,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableImageBrush(this); + return new ImmutableImageBrush(this); } } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs index 6664a2b30e..1f6e3bbcfd 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; namespace Avalonia.Media.Immutable { @@ -15,7 +14,7 @@ namespace Avalonia.Media.Immutable /// The opacity of the brush. /// The spread method. protected ImmutableGradientBrush( - IList gradientStops, + IReadOnlyList gradientStops, double opacity, GradientSpreadMethod spreadMethod) { @@ -28,14 +27,14 @@ namespace Avalonia.Media.Immutable /// Initializes a new instance of the class. /// /// The brush from which this brush's properties should be copied. - protected ImmutableGradientBrush(IGradientBrush source) - : this(source.GradientStops.ToList(), source.Opacity, source.SpreadMethod) + protected ImmutableGradientBrush(GradientBrush source) + : this(source.GradientStops.ToImmutable(), source.Opacity, source.SpreadMethod) { } /// - public IList GradientStops { get; } + public IReadOnlyList GradientStops { get; } /// public double Opacity { get; } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs new file mode 100644 index 0000000000..f3e2e52fd0 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs @@ -0,0 +1,20 @@ +namespace Avalonia.Media.Immutable +{ + /// + /// Describes the location and color of a transition point in a gradient. + /// + public class ImmutableGradientStop : IGradientStop + { + public ImmutableGradientStop(double offset, Color color) + { + Offset = offset; + Color = color; + } + + /// + public double Offset { get; } + + /// + public Color Color { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs index 142eb34625..912d77d763 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs @@ -16,7 +16,7 @@ namespace Avalonia.Media.Immutable /// The start point for the gradient. /// The end point for the gradient. public ImmutableLinearGradientBrush( - IList gradientStops, + IReadOnlyList gradientStops, double opacity = 1, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? startPoint = null, @@ -31,7 +31,7 @@ namespace Avalonia.Media.Immutable /// Initializes a new instance of the class. /// /// The brush from which this brush's properties should be copied. - public ImmutableLinearGradientBrush(ILinearGradientBrush source) + public ImmutableLinearGradientBrush(LinearGradientBrush source) : base(source) { StartPoint = source.StartPoint; diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs index f36a1cd2de..e26fbab5f5 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs @@ -21,7 +21,7 @@ namespace Avalonia.Media.Immutable /// The horizontal and vertical radius of the outermost circle of the radial gradient. /// public ImmutableRadialGradientBrush( - IList gradientStops, + IReadOnlyList gradientStops, double opacity = 1, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? center = null, @@ -38,7 +38,7 @@ namespace Avalonia.Media.Immutable /// Initializes a new instance of the class. /// /// The brush from which this brush's properties should be copied. - public ImmutableRadialGradientBrush(IRadialGradientBrush source) + public ImmutableRadialGradientBrush(RadialGradientBrush source) : base(source) { Center = source.Center; diff --git a/src/Avalonia.Visuals/Media/LinearGradientBrush.cs b/src/Avalonia.Visuals/Media/LinearGradientBrush.cs index d092bebf0f..14adc0e0cd 100644 --- a/src/Avalonia.Visuals/Media/LinearGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/LinearGradientBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media.Immutable; + namespace Avalonia.Media { /// /// A brush that draws with a linear gradient. /// - public sealed class LinearGradientBrush : GradientBrush, ILinearGradientBrush, IMutableBrush + public sealed class LinearGradientBrush : GradientBrush, ILinearGradientBrush { /// /// Defines the property. @@ -24,6 +26,11 @@ namespace Avalonia.Media nameof(EndPoint), RelativePoint.BottomRight); + static LinearGradientBrush() + { + AffectsRender(StartPointProperty, EndPointProperty); + } + /// /// Gets or sets the start point for the gradient. /// @@ -43,9 +50,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableLinearGradientBrush(this); + return new ImmutableLinearGradientBrush(this); } } } diff --git a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs index 003e2e05f9..589cd83ca1 100644 --- a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media.Immutable; + namespace Avalonia.Media { /// /// Paints an area with a radial gradient. /// - public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush, IMutableBrush + public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush { /// /// Defines the property. @@ -63,9 +65,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableRadialGradientBrush(this); + return new ImmutableRadialGradientBrush(this); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/SolidColorBrush.cs b/src/Avalonia.Visuals/Media/SolidColorBrush.cs index d84e407cb4..32b87df56b 100644 --- a/src/Avalonia.Visuals/Media/SolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/SolidColorBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media.Immutable; + namespace Avalonia.Media { /// /// Fills an area with a solid color. /// - public class SolidColorBrush : Brush, ISolidColorBrush, IMutableBrush + public class SolidColorBrush : Brush, ISolidColorBrush { /// /// Defines the property. @@ -14,6 +16,11 @@ namespace Avalonia.Media public static readonly StyledProperty ColorProperty = AvaloniaProperty.Register(nameof(Color)); + static SolidColorBrush() + { + AffectsRender(ColorProperty); + } + /// /// Initializes a new instance of the class. /// @@ -75,9 +82,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableSolidColorBrush(this); + return new ImmutableSolidColorBrush(this); } } } diff --git a/src/Avalonia.Visuals/Media/TileBrush.cs b/src/Avalonia.Visuals/Media/TileBrush.cs index 2033754137..47f20fa285 100644 --- a/src/Avalonia.Visuals/Media/TileBrush.cs +++ b/src/Avalonia.Visuals/Media/TileBrush.cs @@ -79,6 +79,13 @@ namespace Avalonia.Media static TileBrush() { + AffectsRender( + AlignmentXProperty, + AlignmentYProperty, + DestinationRectProperty, + SourceRectProperty, + StretchProperty, + TileModeProperty); RenderOptions.BitmapInterpolationModeProperty.OverrideDefaultValue(BitmapInterpolationMode.Default); } diff --git a/src/Avalonia.Visuals/Media/VisualBrush.cs b/src/Avalonia.Visuals/Media/VisualBrush.cs index 435f4ba1b1..963ba8f4a1 100644 --- a/src/Avalonia.Visuals/Media/VisualBrush.cs +++ b/src/Avalonia.Visuals/Media/VisualBrush.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media.Immutable; using Avalonia.VisualTree; namespace Avalonia.Media @@ -8,7 +9,7 @@ namespace Avalonia.Media /// /// Paints an area with an . /// - public class VisualBrush : TileBrush, IVisualBrush, IMutableBrush + public class VisualBrush : TileBrush, IVisualBrush { /// /// Defines the property. @@ -16,6 +17,11 @@ namespace Avalonia.Media public static readonly StyledProperty VisualProperty = AvaloniaProperty.Register(nameof(Visual)); + static VisualBrush() + { + AffectsRender(VisualProperty); + } + /// /// Initializes a new instance of the class. /// @@ -42,9 +48,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableVisualBrush(this); + return new ImmutableVisualBrush(this); } } } diff --git a/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs b/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs index 8356e78cc3..6a01536b12 100644 --- a/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs +++ b/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs @@ -124,7 +124,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls { StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), - GradientStops = new[] + GradientStops = { new GradientStop(Color.FromUInt32(0xffffffff), 0), new GradientStop(Color.FromUInt32(0x00ffffff), 1) diff --git a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs index 656e77fc31..a0d6f1e423 100644 --- a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media { StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative), EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative), - GradientStops = new[] + GradientStops = { new GradientStop { Color = Colors.Red, Offset = 0 }, new GradientStop { Color = Colors.Blue, Offset = 1 } @@ -63,7 +63,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media { StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative), - GradientStops = new[] + GradientStops = { new GradientStop { Color = Colors.Red, Offset = 0 }, new GradientStop { Color = Colors.Blue, Offset = 1 } diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs index 0017feb106..bd1d26ce70 100644 --- a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs @@ -34,7 +34,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media { Background = new RadialGradientBrush { - GradientStops = new[] + GradientStops = { new GradientStop { Color = Colors.Red, Offset = 0 }, new GradientStop { Color = Colors.Blue, Offset = 1 } diff --git a/tests/Avalonia.RenderTests/OpacityMaskTests.cs b/tests/Avalonia.RenderTests/OpacityMaskTests.cs index 4edf4daa13..2f01b03db6 100644 --- a/tests/Avalonia.RenderTests/OpacityMaskTests.cs +++ b/tests/Avalonia.RenderTests/OpacityMaskTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Direct2D1.RenderTests { StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), - GradientStops = new List + GradientStops = { new GradientStop(Color.FromUInt32(0xffffffff), 0), new GradientStop(Color.FromUInt32(0x00ffffff), 1) @@ -65,7 +65,7 @@ namespace Avalonia.Direct2D1.RenderTests { StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), - GradientStops = new List + GradientStops = { new GradientStop(Color.FromUInt32(0xffffffff), 0), new GradientStop(Color.FromUInt32(0x00ffffff), 1) diff --git a/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs new file mode 100644 index 0000000000..f843a6e333 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Moq; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class ImageBrushTests + { + [Fact] + public void Changing_Source_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new ImageBrush(bitmap1); + var raised = false; + + target.Changed += (s, e) => raised = true; + target.Source = bitmap2; + + Assert.True(raised); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs new file mode 100644 index 0000000000..62f53108e6 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs @@ -0,0 +1,86 @@ +using System; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Moq; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class LinearGradientBrushTests + { + [Fact] + public void Changing_StartPoint_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.StartPoint = new RelativePoint(); + target.Changed += (s, e) => raised = true; + target.StartPoint = new RelativePoint(10, 10, RelativeUnit.Absolute); + + Assert.True(raised); + } + + [Fact] + public void Changing_EndPoint_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.EndPoint = new RelativePoint(); + target.Changed += (s, e) => raised = true; + target.EndPoint = new RelativePoint(10, 10, RelativeUnit.Absolute); + + Assert.True(raised); + } + + [Fact] + public void Changing_GradientStops_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; + target.Changed += (s, e) => raised = true; + target.GradientStops = new GradientStops { new GradientStop(Colors.Green, 0) }; + + Assert.True(raised); + } + + [Fact] + public void Adding_GradientStop_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; + target.Changed += (s, e) => raised = true; + target.GradientStops.Add(new GradientStop(Colors.Green, 1)); + + Assert.True(raised); + } + + [Fact] + public void Changing_GradientStop_Offset_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; + target.Changed += (s, e) => raised = true; + target.GradientStops[0].Offset = 0.5; + + Assert.True(raised); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs new file mode 100644 index 0000000000..4e87b7081d --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs @@ -0,0 +1,21 @@ +using System; +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class SolidColorBrushTests + { + [Fact] + public void Changing_Color_Raises_Changed() + { + var target = new SolidColorBrush(Colors.Red); + var raised = false; + + target.Changed += (s, e) => raised = true; + target.Color = Colors.Green; + + Assert.True(raised); + } + } +} From 0b4e6b847170d34184288bb031d294b62a471a1b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Jun 2018 10:04:49 +0200 Subject: [PATCH 009/118] Make centralized RenderLoop. - Renamed `RenderLoop` to `RenderTimer` - Added new `RenderLoop` which `DeferredRenderer`s register themselves with for updates --- .../InternalPlatformThreadingInterface.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 2 +- .../Remote/PreviewerWindowingPlatform.cs | 3 +- src/Avalonia.Visuals/Avalonia.Visuals.csproj | 1 + ...ultRenderLoop.cs => DefaultRenderTimer.cs} | 9 +- .../Rendering/DeferredRenderer.cs | 44 +++---- src/Avalonia.Visuals/Rendering/IRenderLoop.cs | 17 +-- .../Rendering/IRenderLoopTask.cs | 12 ++ .../Rendering/IRenderTimer.cs | 20 ++++ src/Avalonia.Visuals/Rendering/RenderLoop.cs | 111 ++++++++++++++++++ src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 3 +- .../LinuxFramebufferPlatform.cs | 3 +- src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs | 5 +- .../{RenderLoop.cs => RenderTimer.cs} | 4 +- .../{RenderLoop.cs => RenderTimer.cs} | 4 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 3 +- tests/Avalonia.UnitTests/TestServices.cs | 4 +- .../Rendering/DeferredRendererTests.cs | 6 +- 18 files changed, 191 insertions(+), 62 deletions(-) rename src/Avalonia.Visuals/Rendering/{DefaultRenderLoop.cs => DefaultRenderTimer.cs} (92%) create mode 100644 src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs create mode 100644 src/Avalonia.Visuals/Rendering/IRenderTimer.cs create mode 100644 src/Avalonia.Visuals/Rendering/RenderLoop.cs rename src/OSX/Avalonia.MonoMac/{RenderLoop.cs => RenderTimer.cs} (91%) rename src/Windows/Avalonia.Win32/{RenderLoop.cs => RenderTimer.cs} (88%) diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 400bf5ccc3..47e600b9c8 100644 --- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -9,7 +9,7 @@ using Avalonia.Threading; namespace Avalonia.Controls.Platform { - public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop + public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderTimer { public InternalPlatformThreadingInterface() { diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 1161ded25f..fb5b932fd8 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -96,7 +96,7 @@ namespace Avalonia.Controls _applicationLifecycle = TryGetService(dependencyResolver); _renderInterface = TryGetService(dependencyResolver); - var renderLoop = TryGetService(dependencyResolver); + var renderLoop = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); impl.SetInputRoot(this); diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index 01998052d9..20acc30118 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -53,7 +53,8 @@ namespace Avalonia.DesignerSupport.Remote .Bind().ToConstant(Keyboard) .Bind().ToConstant(instance) .Bind().ToConstant(threading) - .Bind().ToConstant(threading) + .Bind().ToConstant(new RenderLoop()) + .Bind().ToConstant(threading) .Bind().ToSingleton() .Bind().ToConstant(instance) .Bind().ToSingleton(); diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index c34752a3ef..c88001cc0a 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -1,6 +1,7 @@  netstandard2.0 + Avalonia diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs similarity index 92% rename from src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs rename to src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs index 9cf849f59b..7ad8915bc4 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs @@ -2,18 +2,19 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Threading.Tasks; using Avalonia.Platform; namespace Avalonia.Rendering { /// - /// Defines a default render loop that uses a standard timer. + /// Defines a default render timer that uses a standard timer. /// /// /// This class may be overridden by platform implementations to use a specialized timer /// implementation. /// - public class DefaultRenderLoop : IRenderLoop + public class DefaultRenderTimer : IRenderTimer { private IRuntimePlatform _runtime; private int _subscriberCount; @@ -21,12 +22,12 @@ namespace Avalonia.Rendering private IDisposable _subscription; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The number of frames per second at which the loop should run. /// - public DefaultRenderLoop(int framesPerSecond) + public DefaultRenderTimer(int framesPerSecond) { FramesPerSecond = framesPerSecond; } diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index dc1d2933d0..f937964994 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -13,6 +13,7 @@ using Avalonia.Rendering.SceneGraph; using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.VisualTree; +using System.Threading.Tasks; namespace Avalonia.Rendering { @@ -20,7 +21,7 @@ namespace Avalonia.Rendering /// A renderer which renders the state of the visual tree to an intermediate scene graph /// representation which is then rendered on a rendering thread. /// - public class DeferredRenderer : RendererBase, IRenderer, IVisualBrushRenderer + public class DeferredRenderer : RendererBase, IRenderer, IRenderLoopTask, IVisualBrushRenderer { private readonly IDispatcher _dispatcher; private readonly IRenderLoop _renderLoop; @@ -149,7 +150,7 @@ namespace Avalonia.Rendering { if (!_running && _renderLoop != null) { - _renderLoop.Tick += OnRenderLoopTick; + _renderLoop.Add(this); _running = true; } } @@ -159,11 +160,23 @@ namespace Avalonia.Rendering { if (_running && _renderLoop != null) { - _renderLoop.Tick -= OnRenderLoopTick; + _renderLoop.Remove(this); _running = false; } } + bool IRenderLoopTask.NeedsUpdate => _dirty == null || _dirty.Count > 0; + + void IRenderLoopTask.Update() => UpdateScene(); + + void IRenderLoopTask.Render() + { + using (var scene = _scene?.Clone()) + { + Render(scene?.Item); + } + } + /// Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) { @@ -420,31 +433,6 @@ namespace Avalonia.Rendering } } - private void OnRenderLoopTick(object sender, EventArgs e) - { - if (Monitor.TryEnter(_rendering)) - { - try - { - if (!_updateQueued && (_dirty == null || _dirty.Count > 0)) - { - _updateQueued = true; - _dispatcher.Post(UpdateScene, DispatcherPriority.Render); - } - - using (var scene = _scene?.Clone()) - { - Render(scene?.Item); - } - } - catch { } - finally - { - Monitor.Exit(_rendering); - } - } - } - private IRef GetOverlay( IDrawingContextImpl parentContext, Size size, diff --git a/src/Avalonia.Visuals/Rendering/IRenderLoop.cs b/src/Avalonia.Visuals/Rendering/IRenderLoop.cs index 36d915ddbd..bd1086d178 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderLoop.cs @@ -1,19 +1,8 @@ -using System; - -namespace Avalonia.Rendering +namespace Avalonia.Rendering { - /// - /// Defines the interface implemented by an application render loop. - /// public interface IRenderLoop { - /// - /// Raised when the render loop ticks to signal a new frame should be drawn. - /// - /// - /// This event can be raised on any thread; it is the responsibility of the subscriber to - /// switch execution to the right thread. - /// - event EventHandler Tick; + void Add(IRenderLoopTask i); + void Remove(IRenderLoopTask i); } } \ No newline at end of file diff --git a/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs b/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs new file mode 100644 index 0000000000..2f251a5c17 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; + +namespace Avalonia.Rendering +{ + public interface IRenderLoopTask + { + bool NeedsUpdate { get; } + void Update(); + void Render(); + } +} diff --git a/src/Avalonia.Visuals/Rendering/IRenderTimer.cs b/src/Avalonia.Visuals/Rendering/IRenderTimer.cs new file mode 100644 index 0000000000..2665d6dd0b --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/IRenderTimer.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; + +namespace Avalonia.Rendering +{ + /// + /// Defines the interface implemented by an application render timer. + /// + public interface IRenderTimer + { + /// + /// Raised when the render timer ticks to signal a new frame should be drawn. + /// + /// + /// This event can be raised on any thread; it is the responsibility of the subscriber to + /// switch execution to the right thread. + /// + event EventHandler Tick; + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs new file mode 100644 index 0000000000..25febf8187 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using Avalonia.Logging; +using Avalonia.Threading; + +namespace Avalonia.Rendering +{ + public class RenderLoop : IRenderLoop + { + private readonly IDispatcher _dispatcher; + private List _items = new List(); + private IRenderTimer _timer; + private volatile bool inTick; + + public RenderLoop() + { + _dispatcher = Dispatcher.UIThread; + } + + public RenderLoop(IRenderTimer timer, IDispatcher dispatcher) + { + _timer = timer; + _dispatcher = dispatcher; + } + + protected IRenderTimer Timer + { + get + { + if (_timer == null) + { + _timer = AvaloniaLocator.Current.GetService(); + } + + return _timer; + } + } + + public void Add(IRenderLoopTask i) + { + Contract.Requires(i != null); + Dispatcher.UIThread.VerifyAccess(); + + _items.Add(i); + + if (_items.Count == 1) + { + Timer.Tick += TimerTick; + } + } + + public void Remove(IRenderLoopTask i) + { + Contract.Requires(i != null); + Dispatcher.UIThread.VerifyAccess(); + + _items.Remove(i); + + if (_items.Count == 0) + { + Timer.Tick -= TimerTick; + } + } + + private async void TimerTick(object sender, EventArgs e) + { + if (!inTick) + { + inTick = true; + + try + { + var needsUpdate = false; + + foreach (var i in _items) + { + if (i.NeedsUpdate) + { + needsUpdate = true; + break; + } + } + + if (needsUpdate) + { + await _dispatcher.InvokeAsync(() => + { + foreach (var i in _items) + { + i.Update(); + } + }); + } + + foreach (var i in _items) + { + i.Render(); + } + } + catch (Exception ex) + { + Logger.Error(LogArea.Visual, this, "Exception in render loop: {Error}", ex); + } + finally + { + inTick = false; + } + } + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index ca8a1ad3a4..bbb6a01c05 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -52,7 +52,8 @@ namespace Avalonia.Gtk3 .Bind().ToConstant(Instance) .Bind().ToConstant(Instance) .Bind().ToSingleton() - .Bind().ToConstant(new DefaultRenderLoop(60)) + .Bind().ToConstant(new RenderLoop()) + .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new PlatformIconLoader()); } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 810be77b2b..9046c26cee 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -35,7 +35,8 @@ namespace Avalonia.LinuxFramebuffer .Bind().ToConstant(KeyboardDevice) .Bind().ToSingleton() .Bind().ToConstant(Threading) - .Bind().ToConstant(Threading); + .Bind().ToConstant(new RenderLoop()) + .Bind().ToConstant(Threading); } internal static TopLevel Initialize(T builder, string fbdev = null) where T : AppBuilderBase, new() diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs index ba45ad8403..5dbf18b1de 100644 --- a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -21,6 +21,7 @@ namespace Avalonia.MonoMac private static bool s_monoMacInitialized; private static bool s_showInDock = true; private static IRenderLoop s_renderLoop; + private static IRenderTimer s_renderTimer; void DoInitialize() { @@ -35,6 +36,7 @@ namespace Avalonia.MonoMac .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(s_renderLoop) + .Bind().ToConstant(s_renderTimer) .Bind().ToConstant(PlatformThreadingInterface.Instance) /*.Bind().ToTransient()*/; } @@ -83,7 +85,8 @@ namespace Avalonia.MonoMac ThreadHelper.InitializeCocoaThreadingLocks(); App = NSApplication.SharedApplication; UpdateActivationPolicy(); - s_renderLoop = new RenderLoop(); //TODO: use CVDisplayLink + s_renderLoop = new RenderLoop(); + s_renderTimer = new RenderTimer(); //TODO: use CVDisplayLink s_monoMacInitialized = true; } diff --git a/src/OSX/Avalonia.MonoMac/RenderLoop.cs b/src/OSX/Avalonia.MonoMac/RenderTimer.cs similarity index 91% rename from src/OSX/Avalonia.MonoMac/RenderLoop.cs rename to src/OSX/Avalonia.MonoMac/RenderTimer.cs index 4d1f9b4201..d2fd50484a 100644 --- a/src/OSX/Avalonia.MonoMac/RenderLoop.cs +++ b/src/OSX/Avalonia.MonoMac/RenderTimer.cs @@ -6,12 +6,12 @@ using MonoMac.Foundation; namespace Avalonia.MonoMac { //TODO: Switch to using CVDisplayLink - public class RenderLoop : IRenderLoop + public class RenderTimer : IRenderTimer { private readonly object _lock = new object(); private readonly IDisposable _timer; - public RenderLoop() + public RenderTimer() { _timer = AvaloniaLocator.Current.GetService().StartSystemTimer(new TimeSpan(0, 0, 0, 0, 1000 / 60), () => diff --git a/src/Windows/Avalonia.Win32/RenderLoop.cs b/src/Windows/Avalonia.Win32/RenderTimer.cs similarity index 88% rename from src/Windows/Avalonia.Win32/RenderLoop.cs rename to src/Windows/Avalonia.Win32/RenderTimer.cs index 7d7befcc33..321a745fae 100644 --- a/src/Windows/Avalonia.Win32/RenderLoop.cs +++ b/src/Windows/Avalonia.Win32/RenderTimer.cs @@ -5,11 +5,11 @@ using Avalonia.Win32.Interop; namespace Avalonia.Win32 { - internal class RenderLoop : DefaultRenderLoop + internal class RenderTimer : DefaultRenderTimer { private UnmanagedMethods.TimeCallback timerDelegate; - public RenderLoop(int framesPerSecond) + public RenderTimer(int framesPerSecond) : base(framesPerSecond) { } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 9afb1218af..d5eb97292a 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -82,7 +82,8 @@ namespace Avalonia.Win32 .Bind().ToConstant(WindowsKeyboardDevice.Instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) - .Bind().ToConstant(new RenderLoop(60)) + .Bind().ToConstant(new RenderLoop()) + .Bind().ToConstant(new RenderTimer(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index d990defe3d..d68f1d167a 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -64,7 +64,7 @@ namespace Avalonia.UnitTests Func mouseDevice = null, IRuntimePlatform platform = null, IPlatformRenderInterface renderInterface = null, - IRenderLoop renderLoop = null, + IRenderTimer renderLoop = null, IScheduler scheduler = null, IStandardCursorFactory standardCursorFactory = null, IStyler styler = null, @@ -115,7 +115,7 @@ namespace Avalonia.UnitTests Func mouseDevice = null, IRuntimePlatform platform = null, IPlatformRenderInterface renderInterface = null, - IRenderLoop renderLoop = null, + IRenderTimer renderLoop = null, IScheduler scheduler = null, IStandardCursorFactory standardCursorFactory = null, IStyler styler = null, diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 2350a31d5c..1af9a9499d 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -42,7 +42,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering [Fact] public void First_Frame_Calls_SceneBuilder_UpdateAll() { - var loop = new Mock(); + var loop = new Mock(); var root = new TestRoot(); var sceneBuilder = MockSceneBuilder(root); @@ -198,7 +198,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering [Fact] public void Should_Create_Layer_For_Root() { - var loop = new Mock(); + var loop = new Mock(); var root = new TestRoot(); var rootLayer = new Mock(); @@ -374,7 +374,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering private void RunFrame(Mock loop) { - loop.Raise(x => x.Tick += null, EventArgs.Empty); + //loop.Raise(x => x.Tick += null, EventArgs.Empty); } private IRenderTargetBitmapImpl CreateLayer() From 8ec2c8f661839f7826e2e262431c0b7debe4ad0e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Jun 2018 10:36:35 +0200 Subject: [PATCH 010/118] Notify tick count in IRenderTimer.Tick. --- .../Platform/InternalPlatformThreadingInterface.cs | 4 ++-- src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs | 12 ++++++------ src/Avalonia.Visuals/Rendering/IRenderTimer.cs | 2 +- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 2 +- src/OSX/Avalonia.MonoMac/RenderTimer.cs | 4 ++-- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 3 +++ src/Windows/Avalonia.Win32/RenderTimer.cs | 8 ++++++-- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 47e600b9c8..501e15653a 100644 --- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Platform public InternalPlatformThreadingInterface() { TlsCurrentThreadIsLoopThread = true; - StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); + StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(Environment.TickCount)); } private readonly AutoResetEvent _signaled = new AutoResetEvent(false); @@ -105,7 +105,7 @@ namespace Avalonia.Controls.Platform public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread; public event Action Signaled; - public event EventHandler Tick; + public event Action Tick; } } \ No newline at end of file diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs index 7ad8915bc4..6b16ff8038 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs @@ -18,7 +18,7 @@ namespace Avalonia.Rendering { private IRuntimePlatform _runtime; private int _subscriberCount; - private EventHandler _tick; + private Action _tick; private IDisposable _subscription; /// @@ -38,7 +38,7 @@ namespace Avalonia.Rendering public int FramesPerSecond { get; } /// - public event EventHandler Tick + public event Action Tick { add { @@ -77,14 +77,14 @@ namespace Avalonia.Rendering /// This can be overridden by platform implementations to use a specialized timer /// implementation. /// - protected virtual IDisposable StartCore(Action tick) + protected virtual IDisposable StartCore(Action tick) { if (_runtime == null) { _runtime = AvaloniaLocator.Current.GetService(); } - return _runtime.StartSystemTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), tick); + return _runtime.StartSystemTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), () => tick(Environment.TickCount)); } /// @@ -96,9 +96,9 @@ namespace Avalonia.Rendering _subscription = null; } - private void InternalTick() + private void InternalTick(long tickCount) { - _tick(this, EventArgs.Empty); + _tick(tickCount); } } } diff --git a/src/Avalonia.Visuals/Rendering/IRenderTimer.cs b/src/Avalonia.Visuals/Rendering/IRenderTimer.cs index 2665d6dd0b..78f6183994 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderTimer.cs @@ -15,6 +15,6 @@ namespace Avalonia.Rendering /// This event can be raised on any thread; it is the responsibility of the subscriber to /// switch execution to the right thread. /// - event EventHandler Tick; + event Action Tick; } } \ No newline at end of file diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 25febf8187..bdcfdcba8e 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -62,7 +62,7 @@ namespace Avalonia.Rendering } } - private async void TimerTick(object sender, EventArgs e) + private async void TimerTick(long tickCount) { if (!inTick) { diff --git a/src/OSX/Avalonia.MonoMac/RenderTimer.cs b/src/OSX/Avalonia.MonoMac/RenderTimer.cs index d2fd50484a..71f657b690 100644 --- a/src/OSX/Avalonia.MonoMac/RenderTimer.cs +++ b/src/OSX/Avalonia.MonoMac/RenderTimer.cs @@ -20,12 +20,12 @@ namespace Avalonia.MonoMac { using (new NSAutoreleasePool()) { - Tick?.Invoke(this, EventArgs.Empty); + Tick?.Invoke(Environment.TickCount); } } }); } - public event EventHandler Tick; + public event Action Tick; } } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f89086ccb7..1c70b3f03d 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -972,6 +972,9 @@ namespace Avalonia.Win32.Interop [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetMonitorInfo([In] IntPtr hMonitor, [Out] MONITORINFO lpmi); + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool QueryPerformanceCounter(out long lpPerformanceCount); + [return: MarshalAs(UnmanagedType.Bool)] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); diff --git a/src/Windows/Avalonia.Win32/RenderTimer.cs b/src/Windows/Avalonia.Win32/RenderTimer.cs index 321a745fae..cea1bf94a5 100644 --- a/src/Windows/Avalonia.Win32/RenderTimer.cs +++ b/src/Windows/Avalonia.Win32/RenderTimer.cs @@ -14,9 +14,13 @@ namespace Avalonia.Win32 { } - protected override IDisposable StartCore(Action tick) + protected override IDisposable StartCore(Action tick) { - timerDelegate = (id, uMsg, user, dw1, dw2) => tick(); + timerDelegate = (id, uMsg, user, dw1, dw2) => + { + UnmanagedMethods.QueryPerformanceCounter(out long tickCount); + tick(tickCount); + }; var handle = UnmanagedMethods.timeSetEvent( (uint)(1000 / FramesPerSecond), From 166f9f8cf01d27dd8a9afa040497c357f0e5eb6b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Jun 2018 11:02:50 +0200 Subject: [PATCH 011/118] Pulse animation timer from render loop. --- src/Avalonia.Animation/Timing.cs | 35 +++++++++----------- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 4 ++- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs index 6367425911..d6a353fb34 100644 --- a/src/Avalonia.Animation/Timing.cs +++ b/src/Avalonia.Animation/Timing.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Threading; namespace Avalonia.Animation @@ -13,42 +14,38 @@ namespace Avalonia.Animation /// public static class Timing { - /// - /// The number of frames per second. - /// - public const int FramesPerSecond = 60; - - /// - /// The time span of each frame. - /// - internal static readonly TimeSpan FrameTick = TimeSpan.FromSeconds(1.0 / FramesPerSecond); + static TimerObservable _timer = new TimerObservable(); /// /// Initializes static members of the class. /// static Timing() { - var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); - - AnimationsTimer = globalTimer - .Select(_ => GetTickCount()) + AnimationsTimer = _timer .Publish() .RefCount(); } + public static bool HasSubscriptions => _timer.HasSubscriptions; + internal static TimeSpan GetTickCount() => TimeSpan.FromMilliseconds(Environment.TickCount); /// /// Gets the animation timer. /// - /// - /// The animation timer triggers usually at 60 times per second or as - /// defined in . - /// The parameter passed to a subsciber is the current playstate of the animation. - /// internal static IObservable AnimationsTimer { get; } + + public static void Pulse(long tickCount) => _timer.Pulse(tickCount); + + private class TimerObservable : LightweightObservableBase + { + public bool HasSubscriptions { get; private set; } + public void Pulse(long tickCount) => PublishNext(TimeSpan.FromMilliseconds(tickCount)); + protected override void Initialize() => HasSubscriptions = true; + protected override void Deinitialize() => HasSubscriptions = false; + } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index bdcfdcba8e..5cac6993ff 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -70,7 +70,7 @@ namespace Avalonia.Rendering try { - var needsUpdate = false; + var needsUpdate = Animation.Timing.HasSubscriptions; foreach (var i in _items) { @@ -85,6 +85,8 @@ namespace Avalonia.Rendering { await _dispatcher.InvokeAsync(() => { + Animation.Timing.Pulse(tickCount); + foreach (var i in _items) { i.Update(); From c7c9b0a2052488c8642fe155c80a7e4686c0dbe1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 29 Jun 2018 23:14:00 +0200 Subject: [PATCH 012/118] Use `Stopwatch.GetTimestamp()` for tickCount. --- src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs index 6b16ff8038..b05ecd5456 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Diagnostics; using System.Threading.Tasks; using Avalonia.Platform; @@ -84,7 +85,7 @@ namespace Avalonia.Rendering _runtime = AvaloniaLocator.Current.GetService(); } - return _runtime.StartSystemTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), () => tick(Environment.TickCount)); + return _runtime.StartSystemTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), () => tick(Stopwatch.GetTimestamp())); } /// From 33aec77f16313cfc6beb4f28701de4d3adc1411f Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 30 Jun 2018 18:31:35 +0800 Subject: [PATCH 013/118] Move the playstate handling to the State machine instead of being pipelined by the timer itself. Removed unused AnimationStateTimer. --- src/Avalonia.Animation/Animatable.cs | 2 +- src/Avalonia.Animation/Timing.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 8a1a17a6fc..e51103aa55 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -74,4 +74,4 @@ namespace Avalonia.Animation } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs index d6a353fb34..8ee78018a9 100644 --- a/src/Avalonia.Animation/Timing.cs +++ b/src/Avalonia.Animation/Timing.cs @@ -12,7 +12,7 @@ namespace Avalonia.Animation /// /// Provides global timing functions for animations. /// - public static class Timing + public class Timing { static TimerObservable _timer = new TimerObservable(); From f2ba884e0ad3d210660bb60f8e387b652aa5d33b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 27 Jul 2018 22:45:25 -0500 Subject: [PATCH 014/118] Cleanup logic in LightweightObservableBase. --- .../Reactive/LightweightObservableBase.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs index a2786d63da..41009e4cd3 100644 --- a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs +++ b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs @@ -82,18 +82,10 @@ namespace Avalonia.Reactive if (observers.Count == 0) { observers.TrimExcess(); + Deinitialize(); } - else - { - return; - } - } else - { - return; } } - - Deinitialize(); } } From 87e98cacf994eab709c368f283d8f58293926cfa Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 27 Jul 2018 23:57:44 -0500 Subject: [PATCH 015/118] Rewrite Win32 RenderTimer to use undeprecated APIs. --- .../Interop/UnmanagedMethods.cs | 28 +++++++++---- src/Windows/Avalonia.Win32/RenderTimer.cs | 41 +++++++++++++------ 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 1c70b3f03d..d6b95bc7b0 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -26,6 +26,9 @@ namespace Avalonia.Win32.Interop public delegate void TimeCallback(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2); + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate void WaitOrTimerCallback(IntPtr lpParameter, bool timerOrWaitFired); + public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); public enum Cursor @@ -848,11 +851,25 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow); - [DllImport("Winmm.dll")] - public static extern uint timeKillEvent(uint uTimerID); + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr CreateTimerQueue(); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool DeleteTimerQueueEx(IntPtr TimerQueue, IntPtr CompletionEvent); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CreateTimerQueueTimer( + out IntPtr phNewTimer, + IntPtr TimerQueue, + WaitOrTimerCallback Callback, + IntPtr Parameter, + uint DueTime, + uint Period, + uint Flags); - [DllImport("Winmm.dll")] - public static extern uint timeSetEvent(uint uDelay, uint uResolution, TimeCallback lpTimeProc, UIntPtr dwUser, uint fuEvent); + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool DeleteTimerQueueTimer(IntPtr TimerQueue, IntPtr Timer, IntPtr CompletionEvent); [DllImport("user32.dll")] public static extern int ToUnicode( @@ -972,9 +989,6 @@ namespace Avalonia.Win32.Interop [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetMonitorInfo([In] IntPtr hMonitor, [Out] MONITORINFO lpmi); - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool QueryPerformanceCounter(out long lpPerformanceCount); - [return: MarshalAs(UnmanagedType.Bool)] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); diff --git a/src/Windows/Avalonia.Win32/RenderTimer.cs b/src/Windows/Avalonia.Win32/RenderTimer.cs index cea1bf94a5..0cb107d3a9 100644 --- a/src/Windows/Avalonia.Win32/RenderTimer.cs +++ b/src/Windows/Avalonia.Win32/RenderTimer.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Disposables; +using System.Threading; using Avalonia.Rendering; using Avalonia.Win32.Interop; @@ -7,7 +8,21 @@ namespace Avalonia.Win32 { internal class RenderTimer : DefaultRenderTimer { - private UnmanagedMethods.TimeCallback timerDelegate; + private UnmanagedMethods.WaitOrTimerCallback timerDelegate; + + private static IntPtr _timerQueue; + + private static void EnsureTimerQueueCreated() + { + if (Volatile.Read(ref _timerQueue) == null) + { + var queue = UnmanagedMethods.CreateTimerQueue(); + if (Interlocked.CompareExchange(ref _timerQueue, queue, IntPtr.Zero) != IntPtr.Zero) + { + UnmanagedMethods.DeleteTimerQueueEx(queue, IntPtr.Zero); + } + } + } public RenderTimer(int framesPerSecond) : base(framesPerSecond) @@ -16,23 +31,25 @@ namespace Avalonia.Win32 protected override IDisposable StartCore(Action tick) { - timerDelegate = (id, uMsg, user, dw1, dw2) => - { - UnmanagedMethods.QueryPerformanceCounter(out long tickCount); - tick(tickCount); - }; + EnsureTimerQueueCreated(); + var msPerFrame = 1000 / FramesPerSecond; + + timerDelegate = (_, __) => tick(TimeStampToFrames()); - var handle = UnmanagedMethods.timeSetEvent( - (uint)(1000 / FramesPerSecond), - 0, + UnmanagedMethods.CreateTimerQueueTimer( + out var timer, + _timerQueue, timerDelegate, - UIntPtr.Zero, - 1); + IntPtr.Zero, + (uint)msPerFrame, + (uint)msPerFrame, + 0 + ); return Disposable.Create(() => { timerDelegate = null; - UnmanagedMethods.timeKillEvent(handle); + UnmanagedMethods.DeleteTimerQueueTimer(_timerQueue, timer, IntPtr.Zero); }); } } From e24a125ec0b4add42060e25935521e5557a644f7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 28 Jul 2018 00:49:13 -0500 Subject: [PATCH 016/118] Make MonoMac platform use DefaultRenderTimer infrastructure in its RenderTimer. --- src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs | 4 ++-- src/OSX/Avalonia.MonoMac/RenderTimer.cs | 21 +++++++++------------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs index 5dbf18b1de..5757413b7a 100644 --- a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -86,7 +86,7 @@ namespace Avalonia.MonoMac App = NSApplication.SharedApplication; UpdateActivationPolicy(); s_renderLoop = new RenderLoop(); - s_renderTimer = new RenderTimer(); //TODO: use CVDisplayLink + s_renderTimer = new RenderTimer(60); //TODO: use CVDisplayLink s_monoMacInitialized = true; } @@ -136,4 +136,4 @@ namespace Avalonia return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize, "MonoMac"); } } -} \ No newline at end of file +} diff --git a/src/OSX/Avalonia.MonoMac/RenderTimer.cs b/src/OSX/Avalonia.MonoMac/RenderTimer.cs index 71f657b690..22ad2e81a2 100644 --- a/src/OSX/Avalonia.MonoMac/RenderTimer.cs +++ b/src/OSX/Avalonia.MonoMac/RenderTimer.cs @@ -6,26 +6,23 @@ using MonoMac.Foundation; namespace Avalonia.MonoMac { //TODO: Switch to using CVDisplayLink - public class RenderTimer : IRenderTimer + public class RenderTimer : DefaultRenderTimer { - private readonly object _lock = new object(); - private readonly IDisposable _timer; + public RenderTimer(int framesPerSecond) : base(framesPerSecond) + { + } - public RenderTimer() + protected override IDisposable StartCore(Action tick) { - _timer = AvaloniaLocator.Current.GetService().StartSystemTimer(new TimeSpan(0, 0, 0, 0, 1000 / 60), + return AvaloniaLocator.Current.GetService().StartSystemTimer( + TimeSpan.FromSeconds(1.0 / FramesPerSecond), () => { - lock (_lock) + using (new NSAutoreleasePool()) { - using (new NSAutoreleasePool()) - { - Tick?.Invoke(Environment.TickCount); - } + tick?.Invoke(Environment.TickCount); } }); } - - public event Action Tick; } } From 179ea4eca9666e53dfc678abb4d1f670128a3243 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 10 Aug 2018 21:38:54 +0200 Subject: [PATCH 017/118] inTick doesn't need to be volatile. --- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 5cac6993ff..076e8bbd33 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -10,7 +10,7 @@ namespace Avalonia.Rendering private readonly IDispatcher _dispatcher; private List _items = new List(); private IRenderTimer _timer; - private volatile bool inTick; + private bool inTick; public RenderLoop() { From 25b72971880093256d8773c19f950a8c470f1a9f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 10 Aug 2018 21:46:11 +0200 Subject: [PATCH 018/118] Added RenderLoop docs. --- src/Avalonia.Visuals/Rendering/IRenderLoop.cs | 22 ++++++++++++++++++- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 20 +++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/IRenderLoop.cs b/src/Avalonia.Visuals/Rendering/IRenderLoop.cs index bd1086d178..dd7442e7f8 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderLoop.cs @@ -1,8 +1,28 @@ namespace Avalonia.Rendering { + /// + /// The application render loop. + /// + /// + /// The render loop is responsible for advancing the animation timer and updating the scene + /// graph for visible windows. + /// public interface IRenderLoop { + /// + /// Adds an update task. + /// + /// The update task. + /// + /// Registered update tasks will be polled on each tick of the render loop after the + /// animation timer has been pulsed. + /// void Add(IRenderLoopTask i); + + /// + /// Removes an update task. + /// + /// The update task. void Remove(IRenderLoopTask i); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 076e8bbd33..98fc12b4b1 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -5,6 +5,13 @@ using Avalonia.Threading; namespace Avalonia.Rendering { + /// + /// The application render loop. + /// + /// + /// The render loop is responsible for advancing the animation timer and updating the scene + /// graph for visible windows. + /// public class RenderLoop : IRenderLoop { private readonly IDispatcher _dispatcher; @@ -12,17 +19,28 @@ namespace Avalonia.Rendering private IRenderTimer _timer; private bool inTick; + /// + /// Initializes a new instance of the class. + /// public RenderLoop() { _dispatcher = Dispatcher.UIThread; } + /// + /// Initializes a new instance of the class. + /// + /// The render timer. + /// The UI thread dispatcher. public RenderLoop(IRenderTimer timer, IDispatcher dispatcher) { _timer = timer; _dispatcher = dispatcher; } + /// + /// Gets the render timer. + /// protected IRenderTimer Timer { get @@ -36,6 +54,7 @@ namespace Avalonia.Rendering } } + /// public void Add(IRenderLoopTask i) { Contract.Requires(i != null); @@ -49,6 +68,7 @@ namespace Avalonia.Rendering } } + /// public void Remove(IRenderLoopTask i) { Contract.Requires(i != null); From 5e1e25e6fa8a17e7ead7c55b1b0b17b73b0fbb05 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 17:31:05 -0500 Subject: [PATCH 019/118] Clean up render timers to use Environment.TickCount. --- src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs | 2 +- src/Windows/Avalonia.Win32/RenderTimer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs index b05ecd5456..a83334ff5e 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs @@ -85,7 +85,7 @@ namespace Avalonia.Rendering _runtime = AvaloniaLocator.Current.GetService(); } - return _runtime.StartSystemTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), () => tick(Stopwatch.GetTimestamp())); + return _runtime.StartSystemTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), () => tick(Environment.TickCount)); } /// diff --git a/src/Windows/Avalonia.Win32/RenderTimer.cs b/src/Windows/Avalonia.Win32/RenderTimer.cs index 0cb107d3a9..c911bc3adf 100644 --- a/src/Windows/Avalonia.Win32/RenderTimer.cs +++ b/src/Windows/Avalonia.Win32/RenderTimer.cs @@ -34,7 +34,7 @@ namespace Avalonia.Win32 EnsureTimerQueueCreated(); var msPerFrame = 1000 / FramesPerSecond; - timerDelegate = (_, __) => tick(TimeStampToFrames()); + timerDelegate = (_, __) => tick(Environment.TickCount); UnmanagedMethods.CreateTimerQueueTimer( out var timer, From c5898b98af57b33b2db31a5de19680eedc5b1349 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 17:31:37 -0500 Subject: [PATCH 020/118] Remove unused field. --- .../Rendering/DeferredRenderer.cs | 49 ++++++++----------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index f937964994..fcc90edd43 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -32,7 +32,6 @@ namespace Avalonia.Rendering private volatile IRef _scene; private DirtyVisuals _dirty; private IRef _overlay; - private bool _updateQueued; private object _rendering = new object(); private int _lastSceneId = -1; private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); @@ -394,42 +393,34 @@ namespace Avalonia.Rendering private void UpdateScene() { Dispatcher.UIThread.VerifyAccess(); - - try + if (_root.IsVisible) { - if (_root.IsVisible) - { - var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root)); - var scene = sceneRef.Item; + var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root)); + var scene = sceneRef.Item; - if (_dirty == null) - { - _dirty = new DirtyVisuals(); - _sceneBuilder.UpdateAll(scene); - } - else if (_dirty.Count > 0) + if (_dirty == null) + { + _dirty = new DirtyVisuals(); + _sceneBuilder.UpdateAll(scene); + } + else if (_dirty.Count > 0) + { + foreach (var visual in _dirty) { - foreach (var visual in _dirty) - { - _sceneBuilder.Update(scene, visual); - } + _sceneBuilder.Update(scene, visual); } + } - var oldScene = Interlocked.Exchange(ref _scene, sceneRef); - oldScene?.Dispose(); + var oldScene = Interlocked.Exchange(ref _scene, sceneRef); + oldScene?.Dispose(); - _dirty.Clear(); - (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size)); - } - else - { - var oldScene = Interlocked.Exchange(ref _scene, null); - oldScene?.Dispose(); - } + _dirty.Clear(); + (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size)); } - finally + else { - _updateQueued = false; + var oldScene = Interlocked.Exchange(ref _scene, null); + oldScene?.Dispose(); } } From 6fe8dba8d957648109978237d9aa665d9524f967 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 22:23:13 -0500 Subject: [PATCH 021/118] Remove timing class and create Clock classes that can hook into the render loop. --- src/Avalonia.Animation/AnimationInstance`1.cs | 5 +- src/Avalonia.Animation/Clock.cs | 47 +++++++++++++++++ src/Avalonia.Animation/Timing.cs | 51 ------------------- src/Avalonia.Animation/TransitionInstance.cs | 7 ++- .../Animation/RenderLoopClock.cs | 21 ++++++++ .../Rendering/DeferredRenderer.cs | 2 +- .../Rendering/IRenderLoopTask.cs | 15 +++++- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 17 +++---- src/Windows/Avalonia.Win32/Win32Platform.cs | 6 +++ 9 files changed, 101 insertions(+), 70 deletions(-) create mode 100644 src/Avalonia.Animation/Clock.cs delete mode 100644 src/Avalonia.Animation/Timing.cs create mode 100644 src/Avalonia.Visuals/Animation/RenderLoopClock.cs diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 5a72904ed2..a8d8dc73ef 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -82,8 +82,7 @@ namespace Avalonia.Animation protected override void Subscribed() { - _timerSubscription = Timing.AnimationsTimer - .Subscribe(p => this.Step(p)); + _timerSubscription = Clock.GlobalClock.Subscribe(Step); } public void Step(TimeSpan frameTick) @@ -225,4 +224,4 @@ namespace Avalonia.Animation } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs new file mode 100644 index 0000000000..4c37d663b1 --- /dev/null +++ b/src/Avalonia.Animation/Clock.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Text; +using Avalonia.Reactive; + +namespace Avalonia.Animation +{ + public class Clock : IObservable + { + public static Clock GlobalClock => AvaloniaLocator.Current.GetService(); + + private ClockObservable _observable; + + private IObservable _connectedObservable; + + public Clock() + { + _observable = new ClockObservable(); + _connectedObservable = _observable.Publish().RefCount(); + } + + public bool HasSubscriptions => _observable.HasSubscriptions; + + public TimeSpan CurrentTime { get; private set; } + + public void Pulse(long tickCount) + { + var time = TimeSpan.FromMilliseconds(tickCount); + _observable.Pulse(time); + CurrentTime = time; + } + + public IDisposable Subscribe(IObserver observer) + { + return _connectedObservable.Subscribe(observer); + } + + private class ClockObservable : LightweightObservableBase + { + public bool HasSubscriptions { get; private set; } + public void Pulse(TimeSpan tickCount) => PublishNext(tickCount); + protected override void Initialize() => HasSubscriptions = true; + protected override void Deinitialize() => HasSubscriptions = false; + } + } +} diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs deleted file mode 100644 index 8ee78018a9..0000000000 --- a/src/Avalonia.Animation/Timing.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Linq; -using System.Reactive.Linq; -using Avalonia.Reactive; -using Avalonia.Threading; - -namespace Avalonia.Animation -{ - /// - /// Provides global timing functions for animations. - /// - public class Timing - { - static TimerObservable _timer = new TimerObservable(); - - /// - /// Initializes static members of the class. - /// - static Timing() - { - AnimationsTimer = _timer - .Publish() - .RefCount(); - } - - public static bool HasSubscriptions => _timer.HasSubscriptions; - - internal static TimeSpan GetTickCount() => TimeSpan.FromMilliseconds(Environment.TickCount); - - /// - /// Gets the animation timer. - /// - internal static IObservable AnimationsTimer - { - get; - } - - public static void Pulse(long tickCount) => _timer.Pulse(tickCount); - - private class TimerObservable : LightweightObservableBase - { - public bool HasSubscriptions { get; private set; } - public void Pulse(long tickCount) => PublishNext(TimeSpan.FromMilliseconds(tickCount)); - protected override void Initialize() => HasSubscriptions = true; - protected override void Deinitialize() => HasSubscriptions = false; - } - } -} diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index e2719cb472..4c61adea28 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -45,10 +45,9 @@ namespace Avalonia.Animation protected override void Subscribed() { - startTime = Timing.GetTickCount(); - timerSubscription = Timing.AnimationsTimer - .Subscribe(t => TimerTick(t)); + startTime = Clock.GlobalClock.CurrentTime; + timerSubscription = Clock.GlobalClock.Subscribe(TimerTick); PublishNext(0.0d); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs new file mode 100644 index 0000000000..3d166035ec --- /dev/null +++ b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Rendering; + +namespace Avalonia.Animation +{ + public class RenderLoopClock : Clock, IRenderLoopTask + { + bool IRenderLoopTask.NeedsUpdate => HasSubscriptions; + + void IRenderLoopTask.Render() + { + } + + void IRenderLoopTask.Update(long tickCount) + { + Pulse(tickCount); + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index fcc90edd43..3221dd85c6 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -166,7 +166,7 @@ namespace Avalonia.Rendering bool IRenderLoopTask.NeedsUpdate => _dirty == null || _dirty.Count > 0; - void IRenderLoopTask.Update() => UpdateScene(); + void IRenderLoopTask.Update(long tickCount) => UpdateScene(); void IRenderLoopTask.Render() { diff --git a/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs b/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs index 2f251a5c17..b031bf00df 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs @@ -6,7 +6,20 @@ namespace Avalonia.Rendering public interface IRenderLoopTask { bool NeedsUpdate { get; } - void Update(); + void Update(long tickCount); void Render(); } + + public class MockRenderLoopTask : IRenderLoopTask + { + public bool NeedsUpdate => true; + + public void Render() + { + } + + public void Update(long tickCount) + { + } + } } diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 98fc12b4b1..a850b99c5e 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using Avalonia.Logging; using Avalonia.Threading; @@ -17,7 +18,7 @@ namespace Avalonia.Rendering private readonly IDispatcher _dispatcher; private List _items = new List(); private IRenderTimer _timer; - private bool inTick; + private int inTick; /// /// Initializes a new instance of the class. @@ -84,13 +85,11 @@ namespace Avalonia.Rendering private async void TimerTick(long tickCount) { - if (!inTick) + if (Interlocked.CompareExchange(ref inTick, 1, 0) == 0) { - inTick = true; - try { - var needsUpdate = Animation.Timing.HasSubscriptions; + var needsUpdate = false; foreach (var i in _items) { @@ -105,13 +104,11 @@ namespace Avalonia.Rendering { await _dispatcher.InvokeAsync(() => { - Animation.Timing.Pulse(tickCount); - foreach (var i in _items) { - i.Update(); + i.Update(tickCount); } - }); + }).ConfigureAwait(false); } foreach (var i in _items) @@ -125,7 +122,7 @@ namespace Avalonia.Rendering } finally { - inTick = false; + Interlocked.Exchange(ref inTick, 0); } } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index d5eb97292a..812a557765 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -9,6 +9,7 @@ using System.IO; using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading; +using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; @@ -76,6 +77,8 @@ namespace Avalonia.Win32 public static void Initialize(bool deferredRendering = true) { + var clock = new RenderLoopClock(); + AvaloniaLocator.CurrentMutable .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) @@ -84,6 +87,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new RenderTimer(60)) + .Bind().ToConstant(clock) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); @@ -93,6 +97,8 @@ namespace Avalonia.Win32 if (OleContext.Current != null) AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); + + AvaloniaLocator.Current.GetService().Add(clock); } public bool HasMessages() From 5c8ced89a30e3339f070d75b3f361f8342ae99fa Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 22:52:41 -0500 Subject: [PATCH 022/118] Move GlobalPlayState tracking to Clock. --- src/Avalonia.Animation/Animation.cs | 7 ++++++- src/Avalonia.Animation/Clock.cs | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 2c359ecac3..7b3aa06ea0 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -17,10 +17,15 @@ namespace Avalonia.Animation /// public class Animation : AvaloniaList, IAnimation { + /// /// Gets or sets the animation play state for all animations /// - public static PlayState GlobalPlayState { get; set; } = PlayState.Run; + public static PlayState GlobalPlayState + { + get => AvaloniaLocator.Current.GetService().PlayState; + set => AvaloniaLocator.Current.GetService().PlayState = value; + } /// /// Gets or sets the active time of this animation. diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index 4c37d663b1..1ba6425551 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -24,6 +24,8 @@ namespace Avalonia.Animation public TimeSpan CurrentTime { get; private set; } + public PlayState PlayState { get; set; } + public void Pulse(long tickCount) { var time = TimeSpan.FromMilliseconds(tickCount); From a987c4648b77407c9b17519a52747d4868ae524a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 22:57:06 -0500 Subject: [PATCH 023/118] Move Pause PlayState time tracking into Clock. --- src/Avalonia.Animation/AnimationInstance`1.cs | 26 +++--------------- src/Avalonia.Animation/Clock.cs | 27 ++++++++++++++++--- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index a8d8dc73ef..a2d25e4524 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -8,7 +8,7 @@ using Avalonia.Reactive; namespace Avalonia.Animation { /// - /// Handles interpolatoin and time-related functions + /// Handles interpolation and time-related functions /// for keyframe animations. /// internal class AnimationInstance : SingleSubscriberObservableBase @@ -30,8 +30,6 @@ namespace Avalonia.Animation private TimeSpan _delay; private TimeSpan _duration; private TimeSpan _firstFrameCount; - private TimeSpan _internalClock; - private TimeSpan? _previousClock; private Easings.Easing _easeFunc; private Action _onCompleteAction; private Func _interpolator; @@ -120,23 +118,6 @@ namespace Avalonia.Animation if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop) DoComplete(); - if (!_previousClock.HasValue) - { - _previousClock = systemTime; - _internalClock = TimeSpan.Zero; - } - else - { - if (Animation.GlobalPlayState == PlayState.Pause || _targetControl.PlayState == PlayState.Pause) - { - _previousClock = systemTime; - return; - } - var delta = systemTime - _previousClock; - _internalClock += delta.Value; - _previousClock = systemTime; - } - if (!_gotFirstKFValue) { _firstKFValue = (T)_parent.First().Value; @@ -145,7 +126,7 @@ namespace Avalonia.Animation if (!_gotFirstFrameCount) { - _firstFrameCount = _internalClock; + _firstFrameCount = systemTime; _gotFirstFrameCount = true; } } @@ -154,7 +135,7 @@ namespace Avalonia.Animation { DoPlayStatesAndTime(systemTime); - var time = _internalClock - _firstFrameCount; + var time = systemTime - _firstFrameCount; var delayEndpoint = _delay; var iterationEndpoint = delayEndpoint + _duration; @@ -179,7 +160,6 @@ namespace Avalonia.Animation } else { - _previousClock = systemTime; return; } diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index 1ba6425551..ced63163c9 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -14,6 +14,9 @@ namespace Avalonia.Animation private IObservable _connectedObservable; + private TimeSpan? _previousTime; + private TimeSpan _internalTime; + public Clock() { _observable = new ClockObservable(); @@ -28,9 +31,27 @@ namespace Avalonia.Animation public void Pulse(long tickCount) { - var time = TimeSpan.FromMilliseconds(tickCount); - _observable.Pulse(time); - CurrentTime = time; + var systemTime = TimeSpan.FromMilliseconds(tickCount); + + if (!_previousTime.HasValue) + { + _previousTime = systemTime; + _internalTime = TimeSpan.Zero; + } + else + { + if (PlayState == PlayState.Pause) + { + _previousTime = systemTime; + return; + } + var delta = systemTime - _previousTime; + _internalTime += delta.Value; + _previousTime = systemTime; + } + + _observable.Pulse(_internalTime); + CurrentTime = _internalTime; } public IDisposable Subscribe(IObserver observer) From 9b9676d3fe346610fdc05eededd2c90253430bd7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 23:03:53 -0500 Subject: [PATCH 024/118] Enable chaining clocks. --- src/Avalonia.Animation/Clock.cs | 16 +++++++++++----- .../Animation/RenderLoopClock.cs | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index ced63163c9..2de781ea57 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -14,25 +14,31 @@ namespace Avalonia.Animation private IObservable _connectedObservable; + private IDisposable _parentSubscription; + private TimeSpan? _previousTime; private TimeSpan _internalTime; - public Clock() + protected Clock() { _observable = new ClockObservable(); _connectedObservable = _observable.Publish().RefCount(); } + public Clock(Clock parent) + :this() + { + _parentSubscription = parent.Subscribe(Pulse); + } + public bool HasSubscriptions => _observable.HasSubscriptions; public TimeSpan CurrentTime { get; private set; } public PlayState PlayState { get; set; } - public void Pulse(long tickCount) + protected void Pulse(TimeSpan systemTime) { - var systemTime = TimeSpan.FromMilliseconds(tickCount); - if (!_previousTime.HasValue) { _previousTime = systemTime; @@ -62,7 +68,7 @@ namespace Avalonia.Animation private class ClockObservable : LightweightObservableBase { public bool HasSubscriptions { get; private set; } - public void Pulse(TimeSpan tickCount) => PublishNext(tickCount); + public void Pulse(TimeSpan time) => PublishNext(time); protected override void Initialize() => HasSubscriptions = true; protected override void Deinitialize() => HasSubscriptions = false; } diff --git a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs index 3d166035ec..d60d366ad4 100644 --- a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs +++ b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs @@ -15,7 +15,7 @@ namespace Avalonia.Animation void IRenderLoopTask.Update(long tickCount) { - Pulse(tickCount); + Pulse(TimeSpan.FromMilliseconds(tickCount)); } } } From bf1b78c4ab0da4ccb60a1d5456da50a2bf5092f9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 23:39:25 -0500 Subject: [PATCH 025/118] Enable Animations to run on a non-global clock. --- src/Avalonia.Animation/Animation.cs | 15 ++++++++++----- src/Avalonia.Animation/AnimationInstance`1.cs | 8 +++++--- src/Avalonia.Animation/Animator`1.cs | 8 ++++---- src/Avalonia.Animation/Clock.cs | 10 ++++++++++ src/Avalonia.Animation/IAnimation.cs | 4 ++-- src/Avalonia.Animation/IAnimator.cs | 2 +- src/Avalonia.Styling/Styling/Style.cs | 2 +- src/Avalonia.Visuals/Animation/RenderLoopClock.cs | 5 +++++ .../Animation/TransformAnimator.cs | 6 +++--- 9 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 7b3aa06ea0..e787143b59 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -154,12 +154,12 @@ namespace Avalonia.Animation } /// - public IDisposable Apply(Animatable control, IObservable match, Action onComplete) + public IDisposable Apply(Animatable control, Clock clock, IObservable match, Action onComplete) { var (animators, subscriptions) = InterpretKeyframes(control); if (animators.Count == 1) { - subscriptions.Add(animators[0].Apply(this, control, match, onComplete)); + subscriptions.Add(animators[0].Apply(this, control, clock, match, onComplete)); } else { @@ -173,7 +173,7 @@ namespace Avalonia.Animation animatorOnComplete = () => tcs.SetResult(null); completionTasks.Add(tcs.Task); } - subscriptions.Add(animator.Apply(this, control, match, animatorOnComplete)); + subscriptions.Add(animator.Apply(this, control, clock, match, animatorOnComplete)); } if (onComplete != null) @@ -185,15 +185,20 @@ namespace Avalonia.Animation } /// - public Task RunAsync(Animatable control) + public Task RunAsync(Animatable control, Clock clock = null) { + if (clock == null) + { + clock = Clock.GlobalClock; + } + var run = new TaskCompletionSource(); if (this.RepeatCount == RepeatCount.Loop) run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); IDisposable subscriptions = null; - subscriptions = this.Apply(control, Observable.Return(true), () => + subscriptions = this.Apply(control, clock, Observable.Return(true), () => { run.SetResult(null); subscriptions?.Dispose(); diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index a2d25e4524..fe84dd879a 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -34,8 +34,9 @@ namespace Avalonia.Animation private Action _onCompleteAction; private Func _interpolator; private IDisposable _timerSubscription; + private readonly Clock _clock; - public AnimationInstance(Animation animation, Animatable control, Animator animator, Action OnComplete, Func Interpolator) + public AnimationInstance(Animation animation, Animatable control, Animator animator, Clock clock, Action OnComplete, Func Interpolator) { if (animation.SpeedRatio <= 0) throw new InvalidOperationException("Speed ratio cannot be negative or zero."); @@ -71,6 +72,7 @@ namespace Avalonia.Animation _fillMode = animation.FillMode; _onCompleteAction = OnComplete; _interpolator = Interpolator; + _clock = clock; } protected override void Unsubscribed() @@ -80,7 +82,7 @@ namespace Avalonia.Animation protected override void Subscribed() { - _timerSubscription = Clock.GlobalClock.Subscribe(Step); + _timerSubscription = _clock.Subscribe(Step); } public void Step(TimeSpan frameTick) @@ -115,7 +117,7 @@ namespace Avalonia.Animation private void DoPlayStatesAndTime(TimeSpan systemTime) { - if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop) + if (_clock.PlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop) DoComplete(); if (!_gotFirstKFValue) diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index f0ef55aa9e..c699ff635a 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -32,7 +32,7 @@ namespace Avalonia.Animation } /// - public virtual IDisposable Apply(Animation animation, Animatable control, IObservable match, Action onComplete) + public virtual IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable match, Action onComplete) { if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); @@ -41,7 +41,7 @@ namespace Avalonia.Animation .Where(p => p) .Subscribe(_ => { - var timerObs = RunKeyFrames(animation, control, onComplete); + var timerObs = RunKeyFrames(animation, control, clock, onComplete); }); } @@ -101,9 +101,9 @@ namespace Avalonia.Animation /// /// Runs the KeyFrames Animation. /// - private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete) + private IDisposable RunKeyFrames(Animation animation, Animatable control, Clock clock, Action onComplete) { - var instance = new AnimationInstance(animation, control, this, onComplete, DoInterpolation); + var instance = new AnimationInstance(animation, control, this, clock, onComplete, DoInterpolation); return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index 2de781ea57..e8616c9694 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -58,6 +58,16 @@ namespace Avalonia.Animation _observable.Pulse(_internalTime); CurrentTime = _internalTime; + + if (PlayState == PlayState.Stop) + { + Stop(); + } + } + + protected virtual void Stop() + { + _parentSubscription?.Dispose(); } public IDisposable Subscribe(IObserver observer) diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index 1d545a322a..f726cf43dc 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -11,11 +11,11 @@ namespace Avalonia.Animation /// /// Apply the animation to the specified control /// - IDisposable Apply(Animatable control, IObservable match, Action onComplete = null); + IDisposable Apply(Animatable control, Clock clock, IObservable match, Action onComplete = null); /// /// Run the animation to the specified control /// - Task RunAsync(Animatable control); + Task RunAsync(Animatable control, Clock clock); } } diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index 9a4da35a02..134b30a555 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -16,6 +16,6 @@ namespace Avalonia.Animation /// /// Applies the current KeyFrame group to the specified control. /// - IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete); + IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable obsMatch, Action onComplete); } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 399be5470d..a033184588 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -120,7 +120,7 @@ namespace Avalonia.Styling obsMatch = Observable.Return(true); } - var sub = animation.Apply((Animatable)control, obsMatch); + var sub = animation.Apply((Animatable)control, Clock.GlobalClock, obsMatch); subs.Add(sub); } diff --git a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs index d60d366ad4..d9ee269739 100644 --- a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs +++ b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs @@ -7,6 +7,11 @@ namespace Avalonia.Animation { public class RenderLoopClock : Clock, IRenderLoopTask { + protected override void Stop() + { + AvaloniaLocator.Current.GetService().Remove(this); + } + bool IRenderLoopTask.NeedsUpdate => HasSubscriptions; void IRenderLoopTask.Render() diff --git a/src/Avalonia.Visuals/Animation/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/TransformAnimator.cs index 2be1226abe..4476058bfe 100644 --- a/src/Avalonia.Visuals/Animation/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/TransformAnimator.cs @@ -12,7 +12,7 @@ namespace Avalonia.Animation DoubleAnimator childKeyFrames; /// - public override IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete) + public override IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable obsMatch, Action onComplete) { var ctrl = (Visual)control; @@ -44,7 +44,7 @@ namespace Avalonia.Animation // It's a transform object so let's target that. if (renderTransformType == Property.OwnerType) { - return childKeyFrames.Apply(animation, ctrl.RenderTransform, obsMatch, onComplete); + return childKeyFrames.Apply(animation, ctrl.RenderTransform, clock, obsMatch, onComplete); } // It's a TransformGroup and try finding the target there. else if (renderTransformType == typeof(TransformGroup)) @@ -53,7 +53,7 @@ namespace Avalonia.Animation { if (transform.GetType() == Property.OwnerType) { - return childKeyFrames.Apply(animation, transform, obsMatch, onComplete); + return childKeyFrames.Apply(animation, transform, clock, obsMatch, onComplete); } } } From b0368c80b29e7ea7fcc99f32c53252c2218fbc4b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 23:43:06 -0500 Subject: [PATCH 026/118] Enable transitions to run on custom clocks. --- src/Avalonia.Animation/Animatable.cs | 2 +- src/Avalonia.Animation/ITransition.cs | 2 +- src/Avalonia.Animation/TransitionInstance.cs | 8 +++++--- src/Avalonia.Animation/Transition`1.cs | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index e51103aa55..5208356570 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -69,7 +69,7 @@ namespace Avalonia.Animation if (match != null) { - match.Apply(this, e.OldValue, e.NewValue); + match.Apply(this, Clock.GlobalClock, e.OldValue, e.NewValue); } } } diff --git a/src/Avalonia.Animation/ITransition.cs b/src/Avalonia.Animation/ITransition.cs index e2ffe7fc6e..7afaa2325a 100644 --- a/src/Avalonia.Animation/ITransition.cs +++ b/src/Avalonia.Animation/ITransition.cs @@ -13,7 +13,7 @@ namespace Avalonia.Animation /// /// Applies the transition to the specified . /// - IDisposable Apply(Animatable control, object oldValue, object newValue); + IDisposable Apply(Animatable control, Clock clock, object oldValue, object newValue); /// /// Gets the property to be animated. diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index 4c61adea28..b0c927f3cd 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -18,10 +18,12 @@ namespace Avalonia.Animation private IDisposable timerSubscription; private TimeSpan startTime; private TimeSpan duration; + private readonly Clock _clock; - public TransitionInstance(TimeSpan Duration) + public TransitionInstance(Clock clock, TimeSpan Duration) { duration = Duration; + _clock = clock; } private void TimerTick(TimeSpan t) @@ -45,8 +47,8 @@ namespace Avalonia.Animation protected override void Subscribed() { - startTime = Clock.GlobalClock.CurrentTime; - timerSubscription = Clock.GlobalClock.Subscribe(TimerTick); + startTime = _clock.CurrentTime; + timerSubscription = _clock.Subscribe(TimerTick); PublishNext(0.0d); } } diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition`1.cs index 4b01c54f5c..23df7f9807 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/src/Avalonia.Animation/Transition`1.cs @@ -49,9 +49,9 @@ namespace Avalonia.Animation public abstract IObservable DoTransition(IObservable progress, T oldValue, T newValue); /// - public virtual IDisposable Apply(Animatable control, object oldValue, object newValue) + public virtual IDisposable Apply(Animatable control, Clock clock, object oldValue, object newValue) { - var transition = DoTransition(new TransitionInstance(Duration), (T)oldValue, (T)newValue); + var transition = DoTransition(new TransitionInstance(clock, Duration), (T)oldValue, (T)newValue); return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } From 6a380d6591e0f07e15edcbb6ace96baabec9b287 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 23:49:52 -0500 Subject: [PATCH 027/118] Reorganize RenderLoopClock registration. --- src/Avalonia.Controls/AppBuilderBase.cs | 2 +- src/Avalonia.Controls/Application.cs | 7 +++++++ src/Windows/Avalonia.Win32/Win32Platform.cs | 5 ----- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index c92d5d7694..9561282274 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -272,10 +272,10 @@ namespace Avalonia.Controls s_setupWasAlreadyCalled = true; - Instance.RegisterServices(); RuntimePlatformServicesInitializer(); WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); + Instance.RegisterServices(); Instance.Initialize(); AfterSetupCallback(Self); } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 4c549ac7d4..8f6544ccd8 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -4,12 +4,14 @@ using System; using System.Reactive.Concurrency; using System.Threading; +using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Threading; @@ -335,6 +337,11 @@ namespace Avalonia .Bind().ToConstant(AvaloniaScheduler.Instance) .Bind().ToConstant(DragDropDevice.Instance) .Bind().ToTransient(); + + var clock = new RenderLoopClock(); + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(clock) + .GetService().Add(clock); } } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 812a557765..ef2dfd3c1a 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -77,8 +77,6 @@ namespace Avalonia.Win32 public static void Initialize(bool deferredRendering = true) { - var clock = new RenderLoopClock(); - AvaloniaLocator.CurrentMutable .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) @@ -87,7 +85,6 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new RenderTimer(60)) - .Bind().ToConstant(clock) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); @@ -97,8 +94,6 @@ namespace Avalonia.Win32 if (OleContext.Current != null) AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - - AvaloniaLocator.Current.GetService().Add(clock); } public bool HasMessages() From 0c611b981d86e979b0989b0c5b3c15ce0a5ef81a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 23:57:38 -0500 Subject: [PATCH 028/118] Refactor clock types. --- src/Avalonia.Animation/Clock.cs | 71 ++---------------- src/Avalonia.Animation/ClockBase.cs | 75 +++++++++++++++++++ src/Avalonia.Animation/IClock.cs | 13 ++++ src/Avalonia.Controls/Application.cs | 2 +- .../Animation/RenderLoopClock.cs | 2 +- 5 files changed, 95 insertions(+), 68 deletions(-) create mode 100644 src/Avalonia.Animation/ClockBase.cs create mode 100644 src/Avalonia.Animation/IClock.cs diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index e8616c9694..f61a4c7db1 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -6,81 +6,20 @@ using Avalonia.Reactive; namespace Avalonia.Animation { - public class Clock : IObservable + public class Clock : ClockBase { - public static Clock GlobalClock => AvaloniaLocator.Current.GetService(); - - private ClockObservable _observable; - - private IObservable _connectedObservable; + public static IClock GlobalClock => AvaloniaLocator.Current.GetService(); private IDisposable _parentSubscription; - - private TimeSpan? _previousTime; - private TimeSpan _internalTime; - - protected Clock() - { - _observable = new ClockObservable(); - _connectedObservable = _observable.Publish().RefCount(); - } - - public Clock(Clock parent) - :this() + + public Clock(IClock parent) { _parentSubscription = parent.Subscribe(Pulse); } - public bool HasSubscriptions => _observable.HasSubscriptions; - - public TimeSpan CurrentTime { get; private set; } - - public PlayState PlayState { get; set; } - - protected void Pulse(TimeSpan systemTime) - { - if (!_previousTime.HasValue) - { - _previousTime = systemTime; - _internalTime = TimeSpan.Zero; - } - else - { - if (PlayState == PlayState.Pause) - { - _previousTime = systemTime; - return; - } - var delta = systemTime - _previousTime; - _internalTime += delta.Value; - _previousTime = systemTime; - } - - _observable.Pulse(_internalTime); - CurrentTime = _internalTime; - - if (PlayState == PlayState.Stop) - { - Stop(); - } - } - - protected virtual void Stop() + protected override void Stop() { _parentSubscription?.Dispose(); } - - public IDisposable Subscribe(IObserver observer) - { - return _connectedObservable.Subscribe(observer); - } - - private class ClockObservable : LightweightObservableBase - { - public bool HasSubscriptions { get; private set; } - public void Pulse(TimeSpan time) => PublishNext(time); - protected override void Initialize() => HasSubscriptions = true; - protected override void Deinitialize() => HasSubscriptions = false; - } } } diff --git a/src/Avalonia.Animation/ClockBase.cs b/src/Avalonia.Animation/ClockBase.cs new file mode 100644 index 0000000000..ea784269d9 --- /dev/null +++ b/src/Avalonia.Animation/ClockBase.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Text; +using Avalonia.Reactive; + +namespace Avalonia.Animation +{ + public class ClockBase : IClock + { + private ClockObservable _observable; + + private IObservable _connectedObservable; + + private TimeSpan? _previousTime; + private TimeSpan _internalTime; + + protected ClockBase() + { + _observable = new ClockObservable(); + _connectedObservable = _observable.Publish().RefCount(); + } + + public bool HasSubscriptions => _observable.HasSubscriptions; + + public TimeSpan CurrentTime { get; private set; } + + public PlayState PlayState { get; set; } + + protected void Pulse(TimeSpan systemTime) + { + if (!_previousTime.HasValue) + { + _previousTime = systemTime; + _internalTime = TimeSpan.Zero; + } + else + { + if (PlayState == PlayState.Pause) + { + _previousTime = systemTime; + return; + } + var delta = systemTime - _previousTime; + _internalTime += delta.Value; + _previousTime = systemTime; + } + + _observable.Pulse(_internalTime); + CurrentTime = _internalTime; + + if (PlayState == PlayState.Stop) + { + Stop(); + } + } + + protected virtual void Stop() + { + } + + public IDisposable Subscribe(IObserver observer) + { + return _connectedObservable.Subscribe(observer); + } + + private class ClockObservable : LightweightObservableBase + { + public bool HasSubscriptions { get; private set; } + public void Pulse(TimeSpan time) => PublishNext(time); + protected override void Initialize() => HasSubscriptions = true; + protected override void Deinitialize() => HasSubscriptions = false; + } + } +} diff --git a/src/Avalonia.Animation/IClock.cs b/src/Avalonia.Animation/IClock.cs new file mode 100644 index 0000000000..58c997841d --- /dev/null +++ b/src/Avalonia.Animation/IClock.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Animation +{ + public interface IClock : IObservable + { + bool HasSubscriptions { get; } + TimeSpan CurrentTime { get; } + PlayState PlayState { get; set; } + } +} diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 8f6544ccd8..8c03bac61a 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -340,7 +340,7 @@ namespace Avalonia var clock = new RenderLoopClock(); AvaloniaLocator.CurrentMutable - .Bind().ToConstant(clock) + .Bind().ToConstant(clock) .GetService().Add(clock); } } diff --git a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs index d9ee269739..e59b3aac0d 100644 --- a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs +++ b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs @@ -5,7 +5,7 @@ using Avalonia.Rendering; namespace Avalonia.Animation { - public class RenderLoopClock : Clock, IRenderLoopTask + public class RenderLoopClock : ClockBase, IRenderLoopTask { protected override void Stop() { From 59cad1cf86651e4ab3f3c015d1e0636c50c2ea08 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 7 Sep 2018 00:02:41 -0500 Subject: [PATCH 029/118] Fix unfinished clock type refactor. --- src/Avalonia.Animation/Animatable.cs | 5 ++++- src/Avalonia.Animation/Animation.cs | 4 ++-- src/Avalonia.Animation/Animator`1.cs | 4 ++-- src/Avalonia.Animation/Clock.cs | 5 +++++ src/Avalonia.Animation/IAnimation.cs | 4 ++-- src/Avalonia.Animation/IAnimator.cs | 2 +- src/Avalonia.Animation/ITransition.cs | 2 +- src/Avalonia.Animation/Transition`1.cs | 2 +- 8 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 5208356570..03a7a32e02 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -14,7 +14,10 @@ namespace Avalonia.Animation /// Base class for all animatable objects. /// public class Animatable : AvaloniaObject - { + { + public static readonly StyledProperty ClockProperty = + AvaloniaProperty.Register(nameof(Clock), inherits: true); + /// /// Defines the property. /// diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index e787143b59..1d2a7b4559 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -154,7 +154,7 @@ namespace Avalonia.Animation } /// - public IDisposable Apply(Animatable control, Clock clock, IObservable match, Action onComplete) + public IDisposable Apply(Animatable control, IClock clock, IObservable match, Action onComplete) { var (animators, subscriptions) = InterpretKeyframes(control); if (animators.Count == 1) @@ -185,7 +185,7 @@ namespace Avalonia.Animation } /// - public Task RunAsync(Animatable control, Clock clock = null) + public Task RunAsync(Animatable control, IClock clock = null) { if (clock == null) { diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index c699ff635a..44a10db545 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -32,7 +32,7 @@ namespace Avalonia.Animation } /// - public virtual IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable match, Action onComplete) + public virtual IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) { if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); @@ -101,7 +101,7 @@ namespace Avalonia.Animation /// /// Runs the KeyFrames Animation. /// - private IDisposable RunKeyFrames(Animation animation, Animatable control, Clock clock, Action onComplete) + private IDisposable RunKeyFrames(Animation animation, Animatable control, IClock clock, Action onComplete) { var instance = new AnimationInstance(animation, control, this, clock, onComplete, DoInterpolation); return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index f61a4c7db1..e009c2aad5 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -11,6 +11,11 @@ namespace Avalonia.Animation public static IClock GlobalClock => AvaloniaLocator.Current.GetService(); private IDisposable _parentSubscription; + + public Clock() + :this(GlobalClock) + { + } public Clock(IClock parent) { diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index f726cf43dc..34b0a5d769 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -11,11 +11,11 @@ namespace Avalonia.Animation /// /// Apply the animation to the specified control /// - IDisposable Apply(Animatable control, Clock clock, IObservable match, Action onComplete = null); + IDisposable Apply(Animatable control, IClock clock, IObservable match, Action onComplete = null); /// /// Run the animation to the specified control /// - Task RunAsync(Animatable control, Clock clock); + Task RunAsync(Animatable control, IClock clock); } } diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index 134b30a555..04bad8e112 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -16,6 +16,6 @@ namespace Avalonia.Animation /// /// Applies the current KeyFrame group to the specified control. /// - IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable obsMatch, Action onComplete); + IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete); } } diff --git a/src/Avalonia.Animation/ITransition.cs b/src/Avalonia.Animation/ITransition.cs index 7afaa2325a..e5d8466f04 100644 --- a/src/Avalonia.Animation/ITransition.cs +++ b/src/Avalonia.Animation/ITransition.cs @@ -13,7 +13,7 @@ namespace Avalonia.Animation /// /// Applies the transition to the specified . /// - IDisposable Apply(Animatable control, Clock clock, object oldValue, object newValue); + IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue); /// /// Gets the property to be animated. diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition`1.cs index 23df7f9807..b54ec8f51c 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/src/Avalonia.Animation/Transition`1.cs @@ -49,7 +49,7 @@ namespace Avalonia.Animation public abstract IObservable DoTransition(IObservable progress, T oldValue, T newValue); /// - public virtual IDisposable Apply(Animatable control, Clock clock, object oldValue, object newValue) + public virtual IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue) { var transition = DoTransition(new TransitionInstance(clock, Duration), (T)oldValue, (T)newValue); return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); From ec9c61bbbe4afcb18852b4992428817d3fa111a9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 7 Sep 2018 00:11:49 -0500 Subject: [PATCH 030/118] Allow clocks to be bindable and inherited down the logical tree. --- src/Avalonia.Animation/Animatable.cs | 23 ++++--------------- src/Avalonia.Animation/AnimationInstance`1.cs | 4 ++-- src/Avalonia.Animation/TransitionInstance.cs | 2 +- src/Avalonia.Styling/Styling/Style.cs | 19 ++++++++------- .../Animation/TransformAnimator.cs | 2 +- 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 03a7a32e02..516f383b92 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -18,25 +18,10 @@ namespace Avalonia.Animation public static readonly StyledProperty ClockProperty = AvaloniaProperty.Register(nameof(Clock), inherits: true); - /// - /// Defines the property. - /// - public static readonly DirectProperty PlayStateProperty = - AvaloniaProperty.RegisterDirect( - nameof(PlayState), - o => o.PlayState, - (o, v) => o.PlayState = v); - - private PlayState _playState = PlayState.Run; - - /// - /// Gets or sets the state of the animation for this - /// control. - /// - public PlayState PlayState + public IClock Clock { - get { return _playState; } - set { SetAndRaise(PlayStateProperty, ref _playState, value); } + get => GetValue(ClockProperty); + set => SetValue(ClockProperty, value); } /// @@ -72,7 +57,7 @@ namespace Avalonia.Animation if (match != null) { - match.Apply(this, Clock.GlobalClock, e.OldValue, e.NewValue); + match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); } } } diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index fe84dd879a..99ebbe752a 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -34,7 +34,7 @@ namespace Avalonia.Animation private Action _onCompleteAction; private Func _interpolator; private IDisposable _timerSubscription; - private readonly Clock _clock; + private readonly IClock _clock; public AnimationInstance(Animation animation, Animatable control, Animator animator, Clock clock, Action OnComplete, Func Interpolator) { @@ -117,7 +117,7 @@ namespace Avalonia.Animation private void DoPlayStatesAndTime(TimeSpan systemTime) { - if (_clock.PlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop) + if (_clock.PlayState == PlayState.Stop) DoComplete(); if (!_gotFirstKFValue) diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index b0c927f3cd..ad87ad7010 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -18,7 +18,7 @@ namespace Avalonia.Animation private IDisposable timerSubscription; private TimeSpan startTime; private TimeSpan duration; - private readonly Clock _clock; + private readonly IClock _clock; public TransitionInstance(Clock clock, TimeSpan Duration) { diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index a033184588..62b3ca72ae 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -111,17 +111,20 @@ namespace Avalonia.Styling { var subs = GetSubscriptions(control); - foreach (var animation in Animations) + if (control is Animatable animatable) { - IObservable obsMatch = match.ObservableResult; - - if (match.ImmediateResult == true) + foreach (var animation in Animations) { - obsMatch = Observable.Return(true); - } + IObservable obsMatch = match.ObservableResult; - var sub = animation.Apply((Animatable)control, Clock.GlobalClock, obsMatch); - subs.Add(sub); + if (match.ImmediateResult == true) + { + obsMatch = Observable.Return(true); + } + + var sub = animation.Apply(animatable, animatable.Clock ?? Clock.GlobalClock, obsMatch); + subs.Add(sub); + } } foreach (var setter in Setters) diff --git a/src/Avalonia.Visuals/Animation/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/TransformAnimator.cs index 4476058bfe..6336c49dc5 100644 --- a/src/Avalonia.Visuals/Animation/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/TransformAnimator.cs @@ -12,7 +12,7 @@ namespace Avalonia.Animation DoubleAnimator childKeyFrames; /// - public override IDisposable Apply(Animation animation, Animatable control, Clock clock, IObservable obsMatch, Action onComplete) + public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete) { var ctrl = (Visual)control; From 51faa94534c2ca0a0de03305f96d10a8cdb6ce8e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 7 Sep 2018 13:49:44 -0500 Subject: [PATCH 031/118] Allow users to supply custom clocks in XAML or code before animations are applied. Change AnimationsPage to show an example with a custom clock and the animations on that page. --- samples/RenderDemo/Pages/AnimationsPage.xaml | 7 +++-- .../RenderDemo/Pages/AnimationsPage.xaml.cs | 16 ++++++++++++ .../ViewModels/AnimationsPageViewModel.cs | 26 +++++-------------- src/Avalonia.Animation/Animation.cs | 10 ------- src/Avalonia.Animation/Animator`1.cs | 8 +++++- src/Avalonia.Styling/Styling/Style.cs | 2 +- .../Animation/TransformAnimator.cs | 4 +-- 7 files changed, 37 insertions(+), 36 deletions(-) diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index 5287e4e373..473807ac50 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -107,9 +107,12 @@ + + + Hover to activate Transform Keyframe Animations. - public class Animation : AvaloniaList, IAnimation { - - /// - /// Gets or sets the animation play state for all animations - /// - public static PlayState GlobalPlayState - { - get => AvaloniaLocator.Current.GetService().PlayState; - set => AvaloniaLocator.Current.GetService().PlayState = value; - } - /// /// Gets or sets the active time of this animation. /// diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 44a10db545..b68f2fc79a 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -103,7 +103,13 @@ namespace Avalonia.Animation /// private IDisposable RunKeyFrames(Animation animation, Animatable control, IClock clock, Action onComplete) { - var instance = new AnimationInstance(animation, control, this, clock, onComplete, DoInterpolation); + var instance = new AnimationInstance( + animation, + control, + this, + clock ?? control.Clock ?? Clock.GlobalClock, + onComplete, + DoInterpolation); return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 62b3ca72ae..067bb59fe9 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -122,7 +122,7 @@ namespace Avalonia.Styling obsMatch = Observable.Return(true); } - var sub = animation.Apply(animatable, animatable.Clock ?? Clock.GlobalClock, obsMatch); + var sub = animation.Apply(animatable, null, obsMatch); subs.Add(sub); } } diff --git a/src/Avalonia.Visuals/Animation/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/TransformAnimator.cs index 6336c49dc5..721a895900 100644 --- a/src/Avalonia.Visuals/Animation/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/TransformAnimator.cs @@ -44,7 +44,7 @@ namespace Avalonia.Animation // It's a transform object so let's target that. if (renderTransformType == Property.OwnerType) { - return childKeyFrames.Apply(animation, ctrl.RenderTransform, clock, obsMatch, onComplete); + return childKeyFrames.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete); } // It's a TransformGroup and try finding the target there. else if (renderTransformType == typeof(TransformGroup)) @@ -53,7 +53,7 @@ namespace Avalonia.Animation { if (transform.GetType() == Property.OwnerType) { - return childKeyFrames.Apply(animation, transform, clock, obsMatch, onComplete); + return childKeyFrames.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete); } } } From 6b0ef13027e60cd63131077a6695af14d40cb717 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 7 Sep 2018 15:29:11 -0500 Subject: [PATCH 032/118] Clean up naming in TransformAnimator. --- .../Animation/TransformAnimator.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/TransformAnimator.cs index 721a895900..64b3ebd626 100644 --- a/src/Avalonia.Visuals/Animation/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/TransformAnimator.cs @@ -9,7 +9,7 @@ namespace Avalonia.Animation /// public class TransformAnimator : Animator { - DoubleAnimator childKeyFrames; + DoubleAnimator childAnimator; /// public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete) @@ -36,15 +36,15 @@ namespace Avalonia.Animation var renderTransformType = ctrl.RenderTransform.GetType(); - if (childKeyFrames == null) + if (childAnimator == null) { - InitializeChildKeyFrames(); + InitializeChildAnimator(); } // It's a transform object so let's target that. if (renderTransformType == Property.OwnerType) { - return childKeyFrames.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete); + return childAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete); } // It's a TransformGroup and try finding the target there. else if (renderTransformType == typeof(TransformGroup)) @@ -53,7 +53,7 @@ namespace Avalonia.Animation { if (transform.GetType() == Property.OwnerType) { - return childKeyFrames.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete); + return childAnimator.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete); } } } @@ -73,16 +73,16 @@ namespace Avalonia.Animation return null; } - void InitializeChildKeyFrames() + void InitializeChildAnimator() { - childKeyFrames = new DoubleAnimator(); + childAnimator = new DoubleAnimator(); foreach (AnimatorKeyFrame keyframe in this) { - childKeyFrames.Add(keyframe); + childAnimator.Add(keyframe); } - childKeyFrames.Property = Property; + childAnimator.Property = Property; } /// From 58a85c53c757962040abe68b7f1e00f02b3b50c3 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 7 Sep 2018 16:15:06 -0500 Subject: [PATCH 033/118] Have each AnimationInstance and TransitionInstance use their own internal clock instead of relying on tracking the start time of the global clock. Use a binary search to find the correct keyframe instead of linear search. --- .../ViewModels/AnimationsPageViewModel.cs | 2 +- src/Avalonia.Animation/Animation.cs | 5 -- src/Avalonia.Animation/AnimationInstance`1.cs | 32 +++---- src/Avalonia.Animation/Animator`1.cs | 84 +++++++++++-------- src/Avalonia.Animation/ClockBase.cs | 5 +- src/Avalonia.Animation/IClock.cs | 2 - src/Avalonia.Animation/TransitionInstance.cs | 26 +++--- src/Avalonia.Animation/Transition`1.cs | 3 - 8 files changed, 76 insertions(+), 83 deletions(-) diff --git a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs index e4276fa1b5..7b89b7944c 100644 --- a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs @@ -8,7 +8,7 @@ namespace RenderDemo.ViewModels { private bool _isPlaying = true; - private string _playStateText = "Pause all animations"; + private string _playStateText = "Pause animations on this page"; public void TogglePlayState() { diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 65463f3d52..d7efc69e10 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -177,11 +177,6 @@ namespace Avalonia.Animation /// public Task RunAsync(Animatable control, IClock clock = null) { - if (clock == null) - { - clock = Clock.GlobalClock; - } - var run = new TaskCompletionSource(); if (this.RepeatCount == RepeatCount.Loop) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 99ebbe752a..468c9c93a4 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -19,7 +19,6 @@ namespace Avalonia.Animation private double _currentIteration; private bool _isLooping; private bool _gotFirstKFValue; - private bool _gotFirstFrameCount; private bool _iterationDelay; private FillMode _fillMode; private PlaybackDirection _animationDirection; @@ -29,14 +28,14 @@ namespace Avalonia.Animation private double _speedRatio; private TimeSpan _delay; private TimeSpan _duration; - private TimeSpan _firstFrameCount; private Easings.Easing _easeFunc; private Action _onCompleteAction; private Func _interpolator; private IDisposable _timerSubscription; - private readonly IClock _clock; + private readonly IClock _baseClock; + private IClock _clock; - public AnimationInstance(Animation animation, Animatable control, Animator animator, Clock clock, Action OnComplete, Func Interpolator) + public AnimationInstance(Animation animation, Animatable control, Animator animator, IClock baseClock, Action OnComplete, Func Interpolator) { if (animation.SpeedRatio <= 0) throw new InvalidOperationException("Speed ratio cannot be negative or zero."); @@ -72,16 +71,18 @@ namespace Avalonia.Animation _fillMode = animation.FillMode; _onCompleteAction = OnComplete; _interpolator = Interpolator; - _clock = clock; + _baseClock = baseClock; } protected override void Unsubscribed() { _timerSubscription?.Dispose(); + _clock.PlayState = PlayState.Stop; } protected override void Subscribed() { + _clock = new Clock(_baseClock); _timerSubscription = _clock.Subscribe(Step); } @@ -115,9 +116,9 @@ namespace Avalonia.Animation PublishNext(_lastInterpValue); } - private void DoPlayStatesAndTime(TimeSpan systemTime) + private void DoPlayStates() { - if (_clock.PlayState == PlayState.Stop) + if (_clock.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop) DoComplete(); if (!_gotFirstKFValue) @@ -125,19 +126,12 @@ namespace Avalonia.Animation _firstKFValue = (T)_parent.First().Value; _gotFirstKFValue = true; } - - if (!_gotFirstFrameCount) - { - _firstFrameCount = systemTime; - _gotFirstFrameCount = true; - } } - private void InternalStep(TimeSpan systemTime) + private void InternalStep(TimeSpan time) { - DoPlayStatesAndTime(systemTime); - - var time = systemTime - _firstFrameCount; + DoPlayStates(); + var delayEndpoint = _delay; var iterationEndpoint = delayEndpoint + _duration; @@ -158,14 +152,14 @@ namespace Avalonia.Animation } //Calculate the current iteration number - _currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2; + _currentIteration = (int)Math.Floor((double)((double)time.Ticks / iterationEndpoint.Ticks)) + 2; } else { return; } - time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks); + time = TimeSpan.FromTicks((long)(time.Ticks % iterationEndpoint.Ticks)); if (!_isLooping) { diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index b68f2fc79a..b79e2d9342 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -52,52 +52,72 @@ namespace Avalonia.Animation /// (i.e., the normalized time between the selected keyframes, relative to the /// time parameter). /// - /// The time parameter, relative to the total animation time - protected (double IntraKFTime, KeyFramePair KFPair) GetKFPairAndIntraKFTime(double t) + /// The time parameter, relative to the total animation time + protected (double IntraKFTime, KeyFramePair KFPair) GetKFPairAndIntraKFTime(double animationTime) { - AnimatorKeyFrame firstCue, lastCue ; + AnimatorKeyFrame firstKeyframe, lastKeyframe ; int kvCount = _convertedKeyframes.Count; if (kvCount > 2) { - if (t <= 0.0) + if (animationTime <= 0.0) { - firstCue = _convertedKeyframes[0]; - lastCue = _convertedKeyframes[1]; + firstKeyframe = _convertedKeyframes[0]; + lastKeyframe = _convertedKeyframes[1]; } - else if (t >= 1.0) + else if (animationTime >= 1.0) { - firstCue = _convertedKeyframes[_convertedKeyframes.Count - 2]; - lastCue = _convertedKeyframes[_convertedKeyframes.Count - 1]; + firstKeyframe = _convertedKeyframes[_convertedKeyframes.Count - 2]; + lastKeyframe = _convertedKeyframes[_convertedKeyframes.Count - 1]; } else { - (double time, int index) maxval = (0.0d, 0); - for (int i = 0; i < _convertedKeyframes.Count; i++) - { - var comp = _convertedKeyframes[i].Cue.CueValue; - if (t >= comp) - { - maxval = (comp, i); - } - } - firstCue = _convertedKeyframes[maxval.index]; - lastCue = _convertedKeyframes[maxval.index + 1]; + int index = FindClosestBeforeKeyFrame(animationTime); + firstKeyframe = _convertedKeyframes[index]; + lastKeyframe = _convertedKeyframes[index + 1]; } } else { - firstCue = _convertedKeyframes[0]; - lastCue = _convertedKeyframes[1]; + firstKeyframe = _convertedKeyframes[0]; + lastKeyframe = _convertedKeyframes[1]; } - double t0 = firstCue.Cue.CueValue; - double t1 = lastCue.Cue.CueValue; - var intraframeTime = (t - t0) / (t1 - t0); - var firstFrameData = (firstCue.GetTypedValue(), firstCue.isNeutral); - var lastFrameData = (lastCue.GetTypedValue(), lastCue.isNeutral); + double t0 = firstKeyframe.Cue.CueValue; + double t1 = lastKeyframe.Cue.CueValue; + var intraframeTime = (animationTime - t0) / (t1 - t0); + var firstFrameData = (firstKeyframe.GetTypedValue(), firstKeyframe.isNeutral); + var lastFrameData = (lastKeyframe.GetTypedValue(), lastKeyframe.isNeutral); return (intraframeTime, new KeyFramePair(firstFrameData, lastFrameData)); } + private int FindClosestBeforeKeyFrame(double time) + { + int FindClosestBeforeKeyFrame(int startIndex, int length) + { + if (length == 0 || length == 1) + { + return startIndex; + } + + int middle = startIndex + (length / 2); + + if (_convertedKeyframes[middle].Cue.CueValue < time) + { + return FindClosestBeforeKeyFrame(middle, length - middle); + } + else if (_convertedKeyframes[middle].Cue.CueValue > time) + { + return FindClosestBeforeKeyFrame(startIndex, middle - startIndex); + } + else + { + return middle; + } + } + + return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count); + } + /// /// Runs the KeyFrames Animation. /// @@ -130,14 +150,6 @@ namespace Avalonia.Animation AddNeutralKeyFramesIfNeeded(); - var copy = _convertedKeyframes.ToList().OrderBy(p => p.Cue.CueValue); - _convertedKeyframes.Clear(); - - foreach (AnimatorKeyFrame keyframe in copy) - { - _convertedKeyframes.Add(keyframe); - } - _isVerifiedAndConverted = true; } @@ -167,7 +179,7 @@ namespace Avalonia.Animation { if (!hasStartKey) { - _convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true }); + _convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true }); } if (!hasEndKey) diff --git a/src/Avalonia.Animation/ClockBase.cs b/src/Avalonia.Animation/ClockBase.cs index ea784269d9..a2b29e728e 100644 --- a/src/Avalonia.Animation/ClockBase.cs +++ b/src/Avalonia.Animation/ClockBase.cs @@ -21,9 +21,7 @@ namespace Avalonia.Animation _connectedObservable = _observable.Publish().RefCount(); } - public bool HasSubscriptions => _observable.HasSubscriptions; - - public TimeSpan CurrentTime { get; private set; } + protected bool HasSubscriptions => _observable.HasSubscriptions; public PlayState PlayState { get; set; } @@ -47,7 +45,6 @@ namespace Avalonia.Animation } _observable.Pulse(_internalTime); - CurrentTime = _internalTime; if (PlayState == PlayState.Stop) { diff --git a/src/Avalonia.Animation/IClock.cs b/src/Avalonia.Animation/IClock.cs index 58c997841d..ae44102077 100644 --- a/src/Avalonia.Animation/IClock.cs +++ b/src/Avalonia.Animation/IClock.cs @@ -6,8 +6,6 @@ namespace Avalonia.Animation { public interface IClock : IObservable { - bool HasSubscriptions { get; } - TimeSpan CurrentTime { get; } PlayState PlayState { get; set; } } } diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index ad87ad7010..eff2c4e9f3 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -15,23 +15,22 @@ namespace Avalonia.Animation /// internal class TransitionInstance : SingleSubscriberObservableBase { - private IDisposable timerSubscription; - private TimeSpan startTime; - private TimeSpan duration; - private readonly IClock _clock; + private IDisposable _timerSubscription; + private TimeSpan _duration; + private readonly IClock _baseClock; + private IClock _clock; - public TransitionInstance(Clock clock, TimeSpan Duration) + public TransitionInstance(IClock clock, TimeSpan Duration) { - duration = Duration; - _clock = clock; + _duration = Duration; + _baseClock = clock; } private void TimerTick(TimeSpan t) { - var interpVal = (double)(t.Ticks - startTime.Ticks) / duration.Ticks; + var interpVal = (double)t.Ticks / _duration.Ticks; - if (interpVal > 1d - || interpVal < 0d) + if (interpVal > 1d || interpVal < 0d) { PublishCompleted(); return; @@ -42,13 +41,14 @@ namespace Avalonia.Animation protected override void Unsubscribed() { - timerSubscription?.Dispose(); + _timerSubscription?.Dispose(); + _clock.PlayState = PlayState.Stop; } protected override void Subscribed() { - startTime = _clock.CurrentTime; - timerSubscription = _clock.Subscribe(TimerTick); + _clock = new Clock(_baseClock); + _timerSubscription = _clock.Subscribe(TimerTick); PublishNext(0.0d); } } diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition`1.cs index b54ec8f51c..cd0d5d9ce9 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/src/Avalonia.Animation/Transition`1.cs @@ -14,7 +14,6 @@ namespace Avalonia.Animation public abstract class Transition : AvaloniaObject, ITransition { private AvaloniaProperty _prop; - private Easing _easing; /// /// Gets the duration of the animation. @@ -54,7 +53,5 @@ namespace Avalonia.Animation var transition = DoTransition(new TransitionInstance(clock, Duration), (T)oldValue, (T)newValue); return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } - - } } From 3f5ec49b4a32762da011346f0e989cc8970c9524 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 7 Sep 2018 16:36:41 -0500 Subject: [PATCH 034/118] Update iOS and Android projects to use RenderTimers. --- src/Android/Avalonia.Android/AndroidPlatform.cs | 3 ++- ...isplayLinkRenderLoop.cs => DisplayLinkRenderTimer.cs} | 9 +++++---- src/iOS/Avalonia.iOS/iOSPlatform.cs | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) rename src/iOS/Avalonia.iOS/{DisplayLinkRenderLoop.cs => DisplayLinkRenderTimer.cs} (72%) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 2b46bfa492..5f0edadf63 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -52,7 +52,8 @@ namespace Avalonia.Android .Bind().ToTransient() .Bind().ToConstant(Instance) .Bind().ToSingleton() - .Bind().ToConstant(new DefaultRenderLoop(60)) + .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new AssetLoader(app.GetType().Assembly)); SkiaPlatform.Initialize(); diff --git a/src/iOS/Avalonia.iOS/DisplayLinkRenderLoop.cs b/src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs similarity index 72% rename from src/iOS/Avalonia.iOS/DisplayLinkRenderLoop.cs rename to src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs index 4f275dd8ea..1357a4f642 100644 --- a/src/iOS/Avalonia.iOS/DisplayLinkRenderLoop.cs +++ b/src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs @@ -5,11 +5,12 @@ using Foundation; namespace Avalonia.iOS { - class DisplayLinkRenderLoop : IRenderLoop + class DisplayLinkRenderTimer : IRenderTimer { - public event EventHandler Tick; + public event Action Tick; private CADisplayLink _link; - public DisplayLinkRenderLoop() + + public DisplayLinkRenderTimer() { _link = CADisplayLink.Create(OnFrame); @@ -20,7 +21,7 @@ namespace Avalonia.iOS { try { - Tick?.Invoke(this, new EventArgs()); + Tick?.Invoke(Environment.TickCount); } catch (Exception) { diff --git a/src/iOS/Avalonia.iOS/iOSPlatform.cs b/src/iOS/Avalonia.iOS/iOSPlatform.cs index abaebca489..7b50040bf2 100644 --- a/src/iOS/Avalonia.iOS/iOSPlatform.cs +++ b/src/iOS/Avalonia.iOS/iOSPlatform.cs @@ -41,7 +41,8 @@ namespace Avalonia.iOS .Bind().ToConstant(PlatformThreadingInterface.Instance) .Bind().ToSingleton() .Bind().ToSingleton() - .Bind().ToSingleton(); + .Bind().ToSingleton() + .Bind().ToSingleton(); } } } From 8357bc86b02980a13a597d52cf72c7e5798b6755 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 7 Sep 2018 18:10:03 -0500 Subject: [PATCH 035/118] Add tests for the new RenderLoop logic. --- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 16 +-- .../Rendering/DeferredRendererTests.cs | 64 ++++------ .../Rendering/RenderLoopTests.cs | 119 ++++++++++++++++++ 3 files changed, 146 insertions(+), 53 deletions(-) create mode 100644 tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index a850b99c5e..d920be2706 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using Avalonia.Logging; using Avalonia.Threading; @@ -89,18 +90,7 @@ namespace Avalonia.Rendering { try { - var needsUpdate = false; - - foreach (var i in _items) - { - if (i.NeedsUpdate) - { - needsUpdate = true; - break; - } - } - - if (needsUpdate) + if (_items.Any(item => item.NeedsUpdate)) { await _dispatcher.InvokeAsync(() => { @@ -108,7 +98,7 @@ namespace Avalonia.Rendering { i.Update(tickCount); } - }).ConfigureAwait(false); + }, DispatcherPriority.Render).ConfigureAwait(false); } foreach (var i in _items) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 1af9a9499d..e2a5c0c54c 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Subjects; - +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Media; @@ -22,27 +22,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering { public class DeferredRendererTests { - [Fact] - public void First_Frame_Calls_UpdateScene_On_Dispatcher() - { - var root = new TestRoot(); - - var dispatcher = new Mock(); - dispatcher.Setup(x => x.Post(It.IsAny(), DispatcherPriority.Render)) - .Callback((a, p) => a()); - - CreateTargetAndRunFrame(root, dispatcher: dispatcher.Object); - - dispatcher.Verify(x => - x.Post( - It.Is(a => a.Method.Name == "UpdateScene"), - DispatcherPriority.Render)); - } - [Fact] public void First_Frame_Calls_SceneBuilder_UpdateAll() { - var loop = new Mock(); var root = new TestRoot(); var sceneBuilder = MockSceneBuilder(root); @@ -54,6 +36,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering [Fact] public void Frame_Does_Not_Call_SceneBuilder_If_No_Dirty_Controls() { + var dispatcher = new ImmediateDispatcher(); var loop = new Mock(); var root = new TestRoot(); var sceneBuilder = MockSceneBuilder(root); @@ -63,8 +46,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering sceneBuilder: sceneBuilder.Object); target.Start(); - IgnoreFirstFrame(loop, sceneBuilder); - RunFrame(loop); + IgnoreFirstFrame(target, sceneBuilder); + RunFrame(target); sceneBuilder.Verify(x => x.UpdateAll(It.IsAny()), Times.Never); sceneBuilder.Verify(x => x.Update(It.IsAny(), It.IsAny()), Times.Never); @@ -73,8 +56,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering [Fact] public void Should_Update_Dirty_Controls_In_Order() { - var loop = new Mock(); var dispatcher = new ImmediateDispatcher(); + var loop = new Mock(); Border border; Decorator decorator; @@ -98,7 +81,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering dispatcher: dispatcher); target.Start(); - IgnoreFirstFrame(loop, sceneBuilder); + IgnoreFirstFrame(target, sceneBuilder); target.AddDirty(border); target.AddDirty(canvas); target.AddDirty(root); @@ -108,7 +91,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering sceneBuilder.Setup(x => x.Update(It.IsAny(), It.IsAny())) .Callback((_, v) => result.Add(v)); - RunFrame(loop); + RunFrame(target); Assert.Equal(new List { root, decorator, border, canvas }, result); } @@ -198,7 +181,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering [Fact] public void Should_Create_Layer_For_Root() { - var loop = new Mock(); var root = new TestRoot(); var rootLayer = new Mock(); @@ -239,19 +221,19 @@ namespace Avalonia.Visuals.UnitTests.Rendering root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); - var loop = new Mock(); - var target = CreateTargetAndRunFrame(root, loop: loop); + var timer = new Mock(); + var target = CreateTargetAndRunFrame(root, timer); Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot)); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - RunFrame(loop); + RunFrame(target); Assert.Equal(new IVisual[] { root, border }, target.Layers.Select(x => x.LayerRoot)); animation.OnCompleted(); - RunFrame(loop); + RunFrame(target); Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot)); } @@ -280,8 +262,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); - var loop = new Mock(); - var target = CreateTargetAndRunFrame(root, loop: loop); + var timer = new Mock(); + var target = CreateTargetAndRunFrame(root, timer); Assert.Single(target.Layers); } @@ -345,19 +327,20 @@ namespace Avalonia.Visuals.UnitTests.Rendering private DeferredRenderer CreateTargetAndRunFrame( TestRoot root, - Mock loop = null, + Mock timer = null, ISceneBuilder sceneBuilder = null, IDispatcher dispatcher = null) { - loop = loop ?? new Mock(); + timer = timer ?? new Mock(); + dispatcher = dispatcher ?? new ImmediateDispatcher(); var target = new DeferredRenderer( root, - loop.Object, + new RenderLoop(timer.Object, dispatcher), sceneBuilder: sceneBuilder, - dispatcher: dispatcher ?? new ImmediateDispatcher()); + dispatcher: dispatcher); root.Renderer = target; target.Start(); - RunFrame(loop); + RunFrame(target); return target; } @@ -366,15 +349,16 @@ namespace Avalonia.Visuals.UnitTests.Rendering return Mock.Get(renderer.Layers[layerRoot].Bitmap.Item.CreateDrawingContext(null)); } - private void IgnoreFirstFrame(Mock loop, Mock sceneBuilder) + private void IgnoreFirstFrame(IRenderLoopTask task, Mock sceneBuilder) { - RunFrame(loop); + RunFrame(task); sceneBuilder.ResetCalls(); } - private void RunFrame(Mock loop) + private void RunFrame(IRenderLoopTask task) { - //loop.Raise(x => x.Tick += null, EventArgs.Empty); + task.Update(0); + task.Render(); } private IRenderTargetBitmapImpl CreateLayer() diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs new file mode 100644 index 0000000000..30ef35a2bb --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Rendering; +using Avalonia.Threading; +using Moq; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering +{ + public class RenderLoopTests + { + [Fact] + public void RenderLoop_Update_Runs_On_Dispatcher() + { + var dispatcher = new Mock(); + + bool inDispatcher = false; + + dispatcher.Setup( + d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render)) + .Callback((Action a, DispatcherPriority _) => + { + inDispatcher = true; + a(); + inDispatcher = false; + }) + .Returns(Task.CompletedTask); + + var timer = new Mock(); + + var loop = new RenderLoop(timer.Object, dispatcher.Object); + + var renderTask = new Mock(); + + renderTask.Setup(t => t.NeedsUpdate).Returns(true); + renderTask.Setup(t => t.Update(It.IsAny())) + .Callback((long _) => Assert.True(inDispatcher)); + + loop.Add(renderTask.Object); + + timer.Raise(t => t.Tick += null, 0L); + + renderTask.Verify(t => t.Update(It.IsAny()), Times.Once()); + } + + [Fact] + public void RenderLoop_Does_Not_Update_When_No_Tasks_Need_Update() + { + var dispatcher = new Mock(); + dispatcher.Setup( + d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render)) + .Callback((Action a, DispatcherPriority _) => a()) + .Returns(Task.CompletedTask); + + var timer = new Mock(); + var loop = new RenderLoop(timer.Object, dispatcher.Object); + var renderTask = new Mock(); + renderTask.Setup(t => t.NeedsUpdate).Returns(false); + + loop.Add(renderTask.Object); + timer.Raise(t => t.Tick += null, 0L); + + renderTask.Verify(t => t.Update(It.IsAny()), Times.Never()); + } + + [Fact] + public void RenderLoop_Render_Runs_Off_Dispatcher() + { + var dispatcher = new Mock(); + bool inDispatcher = false; + dispatcher.Setup( + d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render)) + .Callback((Action a, DispatcherPriority _) => + { + inDispatcher = true; + a(); + inDispatcher = false; + }) + .Returns(Task.CompletedTask); + + var timer = new Mock(); + var loop = new RenderLoop(timer.Object, dispatcher.Object); + + var renderTask = new Mock(); + + renderTask.Setup(t => t.NeedsUpdate).Returns(true); + renderTask.Setup(t => t.Render()) + .Callback(() => Assert.False(inDispatcher)); + + loop.Add(renderTask.Object); + timer.Raise(t => t.Tick += null, 0L); + + renderTask.Verify(t => t.Update(It.IsAny()), Times.Once()); + } + + [Fact] + public void RenderLoop_Passes_Tick_Count_To_Update() + { + var dispatcher = new Mock(); + dispatcher.Setup( + d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render)) + .Callback((Action a, DispatcherPriority _) => a()) + .Returns(Task.CompletedTask); + + var timer = new Mock(); + var loop = new RenderLoop(timer.Object, dispatcher.Object); + var renderTask = new Mock(); + renderTask.Setup(t => t.NeedsUpdate).Returns(true); + + loop.Add(renderTask.Object); + var tickCount = 12345L; + timer.Raise(t => t.Tick += null, tickCount); + + renderTask.Verify(t => t.Update(tickCount), Times.Once()); + } + } +} From ce95625d66509c15bb9f20607596eff900eef3a7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 7 Sep 2018 18:45:06 -0500 Subject: [PATCH 036/118] Only add the clock to the render loop if there is a render loop. --- src/Avalonia.Controls/Application.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 8c03bac61a..586a73b75c 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -341,7 +341,7 @@ namespace Avalonia var clock = new RenderLoopClock(); AvaloniaLocator.CurrentMutable .Bind().ToConstant(clock) - .GetService().Add(clock); + .GetService()?.Add(clock); } } } From afdfb28da8b507370771438133c9e0a4916ea010 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 9 Sep 2018 02:02:50 +0200 Subject: [PATCH 037/118] Added failing tests for brush invalidation. --- .../BorderTests.cs | 21 ++++++++ .../Avalonia.Controls.UnitTests/PanelTests.cs | 21 ++++++++ .../ContentPresenterTests_Standalone.cs | 20 +++++++- .../Shapes/RectangleTests.cs | 48 +++++++++++++++++++ .../TextBlockTests.cs | 38 +++++++++++++++ tests/Avalonia.UnitTests/TestRoot.cs | 7 +++ 6 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs diff --git a/tests/Avalonia.Controls.UnitTests/BorderTests.cs b/tests/Avalonia.Controls.UnitTests/BorderTests.cs index 9a6a041ec7..0ac9392bc6 100644 --- a/tests/Avalonia.Controls.UnitTests/BorderTests.cs +++ b/tests/Avalonia.Controls.UnitTests/BorderTests.cs @@ -1,6 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media; +using Avalonia.Rendering; +using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -42,5 +46,22 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(6, 6, 0, 0), content.Bounds); } + + [Fact] + public void Changing_Background_Brush_Color_Should_Invalidate_Visual() + { + var target = new Border() + { + Background = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Background).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs index ed239120d6..4a404ea97e 100644 --- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs @@ -3,7 +3,11 @@ using System.Linq; using Avalonia.LogicalTree; +using Avalonia.Media; +using Avalonia.Rendering; +using Avalonia.UnitTests; using Avalonia.VisualTree; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -115,5 +119,22 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren()); Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren()); } + + [Fact] + public void Changing_Background_Brush_Color_Should_Invalidate_Visual() + { + var target = new Panel() + { + Background = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Background).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index 6716456c78..9d65f2cba7 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs @@ -13,6 +13,7 @@ using System; using System.Linq; using Xunit; using Avalonia.Rendering; +using Avalonia.Media; namespace Avalonia.Controls.UnitTests.Presenters { @@ -203,5 +204,22 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.NotEqual(foo, logicalChildren.First()); } + + [Fact] + public void Changing_Background_Brush_Color_Should_Invalidate_Visual() + { + var target = new ContentPresenter() + { + Background = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Background).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs new file mode 100644 index 0000000000..0ec73edec0 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.UnitTests; +using Moq; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Shapes +{ + public class RectangleTests + { + [Fact] + public void Changing_Fill_Brush_Color_Should_Invalidate_Visual() + { + var target = new Rectangle() + { + Fill = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Fill).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } + + [Fact] + public void Changing_Stroke_Brush_Color_Should_Invalidate_Visual() + { + var target = new Rectangle() + { + Stroke = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Stroke).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index 9a1140fc05..45e683455b 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs @@ -2,6 +2,10 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Data; +using Avalonia.Media; +using Avalonia.Rendering; +using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -25,5 +29,39 @@ namespace Avalonia.Controls.UnitTests "", textBlock.Text); } + + [Fact] + public void Changing_Background_Brush_Color_Should_Invalidate_Visual() + { + var target = new TextBlock() + { + Background = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Background).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } + + [Fact] + public void Changing_Foreground_Brush_Color_Should_Invalidate_Visual() + { + var target = new TextBlock() + { + Foreground = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Foreground).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } } } diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 884df33fc0..972b1d78c0 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -19,6 +19,13 @@ namespace Avalonia.UnitTests public TestRoot() { + Renderer = Mock.Of(); + } + + public TestRoot(IControl child) + : this() + { + Child = child; } event EventHandler INameScope.Registered From a5a5b36ddc2f8a1334bc152438600f0eb93a7912 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 9 Sep 2018 02:06:30 +0200 Subject: [PATCH 038/118] Invalidate controls when brush changed. --- src/Avalonia.Controls/Border.cs | 3 +- src/Avalonia.Controls/Panel.cs | 1 + .../Presenters/ContentPresenter.cs | 3 +- src/Avalonia.Controls/Shapes/Shape.cs | 4 +- src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Visuals/Visual.cs | 40 +++++++++++++++++++ 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index 5f84421c64..c4bc121a27 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -43,8 +43,9 @@ namespace Avalonia.Controls /// static Border() { - AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); + AffectsRender(BorderThicknessProperty, CornerRadiusProperty); AffectsMeasure(BorderThicknessProperty); + BrushAffectsRender(BackgroundProperty, BorderBrushProperty); } /// diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index c0d211effb..9b768749df 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -30,6 +30,7 @@ namespace Avalonia.Controls /// static Panel() { + BrushAffectsRender(BackgroundProperty); ClipToBoundsProperty.OverrideDefaultValue(true); } diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 6badf91367..6c1d6e2cc1 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -90,8 +90,9 @@ namespace Avalonia.Controls.Presenters /// static ContentPresenter() { - AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); + AffectsRender(BorderThicknessProperty, CornerRadiusProperty); AffectsMeasure(BorderThicknessProperty, PaddingProperty); + BrushAffectsRender(BackgroundProperty, BorderBrushProperty); ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); ContentTemplateProperty.Changed.AddClassHandler(x => x.ContentChanged); TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 604051ef28..d2a4a37531 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -30,11 +30,11 @@ namespace Avalonia.Controls.Shapes private Geometry _renderedGeometry; bool _calculateTransformOnArrange = false; - static Shape() { AffectsMeasure(StretchProperty, StrokeThicknessProperty); - AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty); + AffectsRender(StrokeDashArrayProperty); + BrushAffectsRender(FillProperty, StrokeProperty); } public Geometry DefiningGeometry diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index e91d2e8fa7..8689d11e13 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -99,10 +99,10 @@ namespace Avalonia.Controls static TextBlock() { ClipToBoundsProperty.OverrideDefaultValue(true); - AffectsRender(ForegroundProperty); AffectsRender(FontWeightProperty); AffectsRender(FontSizeProperty); AffectsRender(FontStyleProperty); + BrushAffectsRender(BackgroundProperty, ForegroundProperty); } /// diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 81e1a93a6f..8631d2001d 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -328,6 +328,44 @@ namespace Avalonia } } + /// + /// Indicates that a brush property change should cause to be + /// called. + /// + /// The properties. + /// + /// This method should be called in a control's static constructor with each property + /// on the control which when changed should cause a redraw. It not only triggers an + /// invalidation when the property itself changes, but also when the brush raises + /// the event. + /// + protected static void BrushAffectsRender(params AvaloniaProperty[] properties) + where T : Visual + { + void Invalidate(AvaloniaPropertyChangedEventArgs e) + { + if (e.Sender is T sender) + { + if (e.OldValue is IMutableBrush oldValue) + { + oldValue.Changed -= sender.BrushChanged; + } + + if (e.NewValue is IMutableBrush newValue) + { + newValue.Changed += sender.BrushChanged; + } + + sender.InvalidateVisual(); + } + } + + foreach (var property in properties) + { + property.Changed.Subscribe(Invalidate); + } + } + /// /// Calls the method /// for this control and all of its visual descendants. @@ -530,6 +568,8 @@ namespace Avalonia OnVisualParentChanged(old, value); } + private void BrushChanged(object sender, EventArgs e) => InvalidateVisual(); + /// /// Called when the collection changes. /// From 460d63736da2bd98f5b216b4a1860aa0354ff53f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 9 Sep 2018 03:17:28 +0200 Subject: [PATCH 039/118] Use immutable brush as default Foreground value. Prevents memory leak. --- src/Avalonia.Controls/TextBlock.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 8689d11e13..ee3e3e361a 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -6,6 +6,7 @@ using System.Reactive; using System.Reactive.Linq; using Avalonia.LogicalTree; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Metadata; namespace Avalonia.Controls @@ -65,7 +66,7 @@ namespace Avalonia.Controls public static readonly AttachedProperty ForegroundProperty = AvaloniaProperty.RegisterAttached( nameof(Foreground), - new SolidColorBrush(0xff000000), + Brushes.Black, inherits: true); /// From ec695433dec3a6b66967f692b66538e95366a842 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Sep 2018 01:02:35 +0800 Subject: [PATCH 040/118] Cancel an animation instance when the selector turns false. --- src/Avalonia.Animation/Animator`1.cs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index f0ef55aa9e..74f09d5488 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -37,12 +37,29 @@ namespace Avalonia.Animation if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); - return match + var matchStream = match + .DistinctUntilChanged() + .Publish() + .RefCount(); + + var activeInstance = matchStream .Where(p => p) - .Subscribe(_ => - { - var timerObs = RunKeyFrames(animation, control, onComplete); - }); + .Select(p => RunKeyFrames(animation, control, onComplete)); + + var negationStream = matchStream + .Where(p => !p); + + return Observable + .WithLatestFrom( + negationStream, + activeInstance, + (isMatch, instance) => + { + if (!isMatch && animation.RepeatCount.IsLoop) + instance?.Dispose(); + return true; + }) + .Subscribe(); } /// From bc850b49c16090d81d5d049dd9ff2cfc7efa0de1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 9 Sep 2018 23:52:20 -0500 Subject: [PATCH 041/118] Add in custom hit testing for ImmediateRenderer. Fixes #1879. --- src/Avalonia.Controls/Primitives/AdornerLayer.cs | 8 +++++++- .../Rendering/ICustomSimpleHitTest.cs | 12 ++++++++++++ src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs | 11 ++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 676cdc456a..4b58197ef3 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -6,11 +6,12 @@ using System.Collections.Specialized; using System.Linq; using Avalonia.VisualTree; using Avalonia.Media; +using Avalonia.Rendering; namespace Avalonia.Controls.Primitives { // TODO: Need to track position of adorned elements and move the adorner if they move. - public class AdornerLayer : Panel + public class AdornerLayer : Panel, ICustomSimpleHitTest { public static AttachedProperty AdornedElementProperty = AvaloniaProperty.RegisterAttached("AdornedElement"); @@ -137,6 +138,11 @@ namespace Avalonia.Controls.Primitives } } + public bool HitTest(Point point) + { + return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true); + } + private class AdornedElementInfo { public IDisposable Subscription { get; set; } diff --git a/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs new file mode 100644 index 0000000000..7199053b08 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs @@ -0,0 +1,12 @@ +namespace Avalonia.Rendering +{ + /// + /// An interface to allow non-templated controls to customize their hit-testing + /// when using a renderer with a simple hit-testing algorithm without a scene graph, + /// such as + /// + public interface ICustomSimpleHitTest + { + bool HitTest(Point point); + } +} diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index fd62c8a59f..d373e7ef2a 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -200,7 +200,16 @@ namespace Avalonia.Rendering if (filter?.Invoke(visual) != false) { - bool containsPoint = visual.TransformedBounds?.Contains(p) == true; + bool containsPoint = false; + + if (visual is ICustomSimpleHitTest custom) + { + containsPoint = custom.HitTest(p); + } + else + { + containsPoint = visual.TransformedBounds?.Contains(p) == true; + } if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Count > 0) { From 1bf495433469fdfdfdb9a81582fd8518f5996b27 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 10 Sep 2018 10:50:36 +0200 Subject: [PATCH 042/118] Downgrade SharpDX to 4.0.1 --- build/SharpDX.props | 10 ++--- .../Media/FormattedTextImpl.cs | 43 +++++++------------ 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/build/SharpDX.props b/build/SharpDX.props index fdc802f135..69aa817a01 100644 --- a/build/SharpDX.props +++ b/build/SharpDX.props @@ -1,9 +1,9 @@  - - - - - + + + + + diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index e2c33a51f1..7164ec7c0d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -9,8 +9,6 @@ using DWrite = SharpDX.DirectWrite; namespace Avalonia.Direct2D1.Media { - using System; - public class FormattedTextImpl : IFormattedTextImpl { public FormattedTextImpl( @@ -23,22 +21,21 @@ namespace Avalonia.Direct2D1.Media { Text = text; - var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface); - - textFormat.WordWrapping = - wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; - - TextLayout = new DWrite.TextLayout( - Direct2D1Platform.DirectWriteFactory, - Text ?? string.Empty, - textFormat, - (float)constraint.Width, - (float)constraint.Height) + using (var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface)) { - TextAlignment = textAlignment.ToDirect2D() - }; - - textFormat.Dispose(); + textFormat.WordWrapping = + wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; + + TextLayout = new DWrite.TextLayout( + Direct2D1Platform.DirectWriteFactory, + Text ?? string.Empty, + textFormat, + (float)constraint.Width, + (float)constraint.Height) + { + TextAlignment = textAlignment.ToDirect2D() + }; + } if (spans != null) { @@ -109,17 +106,7 @@ namespace Avalonia.Direct2D1.Media private Size Measure() { - DWrite.TextMetrics metrics; - - // SharpDX bug - try - { - metrics = TextLayout.Metrics; - } - catch (ObjectDisposedException) - { - metrics = TextLayout.Metrics; - } + var metrics = TextLayout.Metrics; var width = metrics.WidthIncludingTrailingWhitespace; From 50f15b1154a28e58ce40253554df2111adec4440 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 10 Sep 2018 10:54:31 +0200 Subject: [PATCH 043/118] Namespace fix --- .../Media/Imaging/D2DBitmapImpl.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index b65e32bbbb..b0edb9c3bf 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,14 +1,13 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using System.IO; -using SharpDX.Direct2D1; +using SharpDX.WIC; +using Bitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { - using SharpDX.WIC; - - using Bitmap = SharpDX.Direct2D1.Bitmap; - using PixelFormat = SharpDX.Direct2D1.PixelFormat; - /// /// A Direct2D Bitmap implementation that uses a GPU memory bitmap as its image. /// From e625f03c1659a76fbd5d601de75a2ff25d68c953 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 10 Sep 2018 20:24:26 +0200 Subject: [PATCH 044/118] Use ID2D1Device instead of ID2D1Device1 to keep wi --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 38c49924bc..c330377e20 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -33,7 +33,7 @@ namespace Avalonia.Direct2D1 public static SharpDX.Direct2D1.Factory1 Direct2D1Factory { get; private set; } - public static SharpDX.Direct2D1.Device1 Direct2D1Device { get; private set; } + public static SharpDX.Direct2D1.Device Direct2D1Device { get; private set; } public static SharpDX.DirectWrite.Factory1 DirectWriteFactory { get; private set; } @@ -97,10 +97,7 @@ namespace Avalonia.Direct2D1 DxgiDevice = Direct3D11Device.QueryInterface(); - using (var device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice)) - { - Direct2D1Device = device.QueryInterface(); - } + Direct2D1Device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice); s_initialized = true; } From 64a4a6d82af01a3ba2c69182b2fcd16c33ddc153 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 11 Sep 2018 15:04:12 +0800 Subject: [PATCH 045/118] Simplify Fix; Invalidate when IsIndeterminate property changes. --- src/Avalonia.Animation/Animator`1.cs | 39 ++++++++++------------------ src/Avalonia.Controls/ProgressBar.cs | 8 +++++- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 74f09d5488..ab82bfb35d 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -17,7 +17,7 @@ namespace Avalonia.Animation /// List of type-converted keyframes. /// private readonly List _convertedKeyframes = new List(); - + private bool _isVerifiedAndConverted; /// @@ -28,38 +28,25 @@ namespace Avalonia.Animation public Animator() { // Invalidate keyframes when changed. - this.CollectionChanged += delegate { _isVerifiedAndConverted = false; }; + this.CollectionChanged += delegate { _isVerifiedAndConverted = false; }; } /// public virtual IDisposable Apply(Animation animation, Animatable control, IObservable match, Action onComplete) { - if (!_isVerifiedAndConverted) + if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); - var matchStream = match - .DistinctUntilChanged() - .Publish() - .RefCount(); - - var activeInstance = matchStream - .Where(p => p) - .Select(p => RunKeyFrames(animation, control, onComplete)); - - var negationStream = matchStream - .Where(p => !p); - - return Observable - .WithLatestFrom( - negationStream, - activeInstance, - (isMatch, instance) => + return match + .DistinctUntilChanged() + .Select(x => x ? RunKeyFrames(animation, control, onComplete) : null) + .Buffer(2, 1) + .Where(x => x.Count > 1) + .Subscribe(x => { - if (!isMatch && animation.RepeatCount.IsLoop) - instance?.Dispose(); - return true; - }) - .Subscribe(); + if (animation.RepeatCount.IsLoop) + x[0]?.Dispose(); + }); } /// @@ -72,7 +59,7 @@ namespace Avalonia.Animation /// The time parameter, relative to the total animation time protected (double IntraKFTime, KeyFramePair KFPair) GetKFPairAndIntraKFTime(double t) { - AnimatorKeyFrame firstCue, lastCue ; + AnimatorKeyFrame firstCue, lastCue; int kvCount = _convertedKeyframes.Count; if (kvCount > 2) { diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 7f4b549849..fe7b8e64c7 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -38,6 +38,7 @@ namespace Avalonia.Controls PseudoClass(IsIndeterminateProperty, ":indeterminate"); ValueProperty.Changed.AddClassHandler(x => x.ValueChanged); + IsIndeterminateProperty.Changed.AddClassHandler(x => x.IsIndeterminateChanged); } public bool IsIndeterminate @@ -118,5 +119,10 @@ namespace Avalonia.Controls { UpdateIndicator(Bounds.Size); } + + private void IsIndeterminateChanged(AvaloniaPropertyChangedEventArgs e) + { + UpdateIndicator(Bounds.Size); + } } -} +} \ No newline at end of file From 388df18e52efa177e694156607e369390e9de22e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 7 Sep 2018 13:58:02 +0200 Subject: [PATCH 046/118] Initial commit of Event Viewer. --- .../Avalonia.Diagnostics.csproj | 8 ++ src/Avalonia.Diagnostics/DevTools.xaml | 1 + src/Avalonia.Diagnostics/DevTools.xaml.cs | 23 +++++ src/Avalonia.Diagnostics/Models/ChainLink.cs | 34 +++++++ .../ViewModels/ControlTreeNode.cs | 65 +++++++++++++ .../ViewModels/DevToolsViewModel.cs | 5 + .../ViewModels/EventEntryTreeNode.cs | 97 +++++++++++++++++++ .../ViewModels/EventTreeNode.cs | 80 +++++++++++++++ .../ViewModels/EventsViewModel.cs | 75 ++++++++++++++ .../ViewModels/FiredEvent.cs | 93 ++++++++++++++++++ .../Views/EventsView.xaml | 53 ++++++++++ .../Views/EventsView.xaml.cs | 28 ++++++ 12 files changed, 562 insertions(+) create mode 100644 src/Avalonia.Diagnostics/Models/ChainLink.cs create mode 100644 src/Avalonia.Diagnostics/ViewModels/ControlTreeNode.cs create mode 100644 src/Avalonia.Diagnostics/ViewModels/EventEntryTreeNode.cs create mode 100644 src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs create mode 100644 src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs create mode 100644 src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs create mode 100644 src/Avalonia.Diagnostics/Views/EventsView.xaml create mode 100644 src/Avalonia.Diagnostics/Views/EventsView.xaml.cs diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index a2ff0ecf02..b807571a38 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -2,6 +2,9 @@ netstandard2.0 + + + @@ -17,4 +20,9 @@ + + + MSBuild:Compile + + \ No newline at end of file diff --git a/src/Avalonia.Diagnostics/DevTools.xaml b/src/Avalonia.Diagnostics/DevTools.xaml index 844670e794..a3b12f0ec5 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml +++ b/src/Avalonia.Diagnostics/DevTools.xaml @@ -3,6 +3,7 @@ + diff --git a/src/Avalonia.Diagnostics/DevTools.xaml.cs b/src/Avalonia.Diagnostics/DevTools.xaml.cs index be0863954a..d084ec3014 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml.cs +++ b/src/Avalonia.Diagnostics/DevTools.xaml.cs @@ -10,6 +10,7 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using Avalonia.Rendering; using Avalonia.VisualTree; namespace Avalonia @@ -28,6 +29,7 @@ namespace Avalonia.Diagnostics public class DevTools : UserControl { private static Dictionary s_open = new Dictionary(); + private static HashSet s_visualTreeRoots = new HashSet(); private IDisposable _keySubscription; public DevTools(IControl root) @@ -79,6 +81,7 @@ namespace Avalonia.Diagnostics devToolsWindow.Closed += devTools.DevToolsClosed; s_open.Add(control, devToolsWindow); + MarkAsDevTool(devToolsWindow); devToolsWindow.Show(); } } @@ -89,6 +92,7 @@ namespace Avalonia.Diagnostics var devToolsWindow = (Window)sender; var devTools = (DevTools)devToolsWindow.Content; s_open.Remove((TopLevel)devTools.Root); + RemoveDevTool(devToolsWindow); _keySubscription.Dispose(); devToolsWindow.Closed -= DevToolsClosed; } @@ -116,5 +120,24 @@ namespace Avalonia.Diagnostics } } } + + /// + /// Marks a visual as part of the DevTools, so it can be excluded from event tracking. + /// + /// The visual whose root is to be marked. + public static void MarkAsDevTool(IVisual visual) + { + s_visualTreeRoots.Add(visual.GetVisualRoot()); + } + + public static void RemoveDevTool(IVisual visual) + { + s_visualTreeRoots.Remove(visual.GetVisualRoot()); + } + + public static bool BelongsToDevTool(IVisual visual) + { + return s_visualTreeRoots.Contains(visual.GetVisualRoot()); + } } } diff --git a/src/Avalonia.Diagnostics/Models/ChainLink.cs b/src/Avalonia.Diagnostics/Models/ChainLink.cs new file mode 100644 index 0000000000..9ea99ff1ae --- /dev/null +++ b/src/Avalonia.Diagnostics/Models/ChainLink.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Interactivity; + +namespace Avalonia.Diagnostics.Models +{ + internal class ChainLink + { + public object Handler { get; private set; } + public string HandlerName + { + get + { + if (Handler is INamed named && !string.IsNullOrEmpty(named.Name)) + { + return named.Name + " (" + Handler.GetType().Name + ")"; + } + return Handler.GetType().Name; + } + } + public bool Handled { get; private set; } + public RoutingStrategies Route { get; private set; } + + public ChainLink(object handler, bool handled, RoutingStrategies route) + { + Contract.Requires(handler != null); + + this.Handler = handler; + this.Handled = handled; + this.Route = route; + } + } +} diff --git a/src/Avalonia.Diagnostics/ViewModels/ControlTreeNode.cs b/src/Avalonia.Diagnostics/ViewModels/ControlTreeNode.cs new file mode 100644 index 0000000000..d8a6a3bdc3 --- /dev/null +++ b/src/Avalonia.Diagnostics/ViewModels/ControlTreeNode.cs @@ -0,0 +1,65 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal class ControlTreeNode : EventTreeNode + { + public ControlTreeNode(Type type, IEnumerable events, EventsViewModel vm) + : base(null, type.Name) + { + this.Children = new AvaloniaList(events.OrderBy(e => e.Name).Select(e => new EventEntryTreeNode(this, e, vm) { IsEnabled = IsDefault(e) })); + this.IsExpanded = true; + } + + RoutedEvent[] defaultEvents = new RoutedEvent[] + { + Button.ClickEvent, + InputElement.KeyDownEvent, + InputElement.KeyUpEvent, + InputElement.TextInputEvent, + InputElement.PointerReleasedEvent, + InputElement.PointerPressedEvent, + }; + + private bool IsDefault(RoutedEvent e) + { + return defaultEvents.Contains(e); + } + + public override bool? IsEnabled + { + get => base.IsEnabled; + set + { + if (base.IsEnabled != value) + { + base.IsEnabled = value; + if (_updateChildren && value != null) + { + foreach (var child in Children) + { + try + { + child._updateParent = false; + child.IsEnabled = value; + } + finally + { + child._updateParent = true; + } + } + } + } + } + } + } +} diff --git a/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs b/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs index ce8ad36c17..c6d3f02e8b 100644 --- a/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs +++ b/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs @@ -14,6 +14,7 @@ namespace Avalonia.Diagnostics.ViewModels private int _selectedTab; private TreePageViewModel _logicalTree; private TreePageViewModel _visualTree; + private EventsViewModel _eventsView; private string _focusedControl; private string _pointerOverElement; @@ -21,6 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels { _logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root)); _visualTree = new TreePageViewModel(VisualTreeNode.Create(root)); + _eventsView = new EventsViewModel(root); UpdateFocusedControl(); KeyboardDevice.Instance.PropertyChanged += (s, e) => @@ -57,6 +59,9 @@ namespace Avalonia.Diagnostics.ViewModels case 1: Content = _visualTree; break; + case 2: + Content = _eventsView; + break; } RaisePropertyChanged(); diff --git a/src/Avalonia.Diagnostics/ViewModels/EventEntryTreeNode.cs b/src/Avalonia.Diagnostics/ViewModels/EventEntryTreeNode.cs new file mode 100644 index 0000000000..8eead869d5 --- /dev/null +++ b/src/Avalonia.Diagnostics/ViewModels/EventEntryTreeNode.cs @@ -0,0 +1,97 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Avalonia.Diagnostics.Models; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Avalonia.VisualTree; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal class EventEntryTreeNode : EventTreeNode + { + RoutedEvent _event; + EventsViewModel _parentViewModel; + bool _isRegistered; + FiredEvent _currentEvent; + + public EventEntryTreeNode(ControlTreeNode parent, RoutedEvent @event, EventsViewModel vm) + : base(parent, @event.Name) + { + Contract.Requires(@event != null); + Contract.Requires(vm != null); + + this._event = @event; + this._parentViewModel = vm; + } + + public override bool? IsEnabled + { + get => base.IsEnabled; + set + { + if (base.IsEnabled != value) + { + base.IsEnabled = value; + UpdateTracker(); + if (Parent != null && _updateParent) + { + try + { + Parent._updateChildren = false; + Parent.UpdateChecked(); + } + finally + { + Parent._updateChildren = true; + } + } + } + } + } + + private void UpdateTracker() + { + if (IsEnabled.GetValueOrDefault() && !_isRegistered) + { + _event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); + _isRegistered = true; + } + } + + private void HandleEvent(object sender, RoutedEventArgs e) + { + if (!_isRegistered || IsEnabled == false) + return; + if (sender is IVisual v && DevTools.BelongsToDevTool(v)) + return; + + var s = sender; + var handled = e.Handled; + var route = e.Route; + + Action handler = delegate + { + if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e)) + { + _currentEvent = new FiredEvent(e, new ChainLink(s, handled, route)); + + _parentViewModel.RecordedEvents.Add(_currentEvent); + + while (_parentViewModel.RecordedEvents.Count > 100) + _parentViewModel.RecordedEvents.RemoveAt(0); + } + else + { + _currentEvent.AddToChain(new ChainLink(s, handled, route)); + } + }; + + if (!Dispatcher.UIThread.CheckAccess()) + Dispatcher.UIThread.Post(handler); + else + handler(); + } + } +} diff --git a/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs new file mode 100644 index 0000000000..50ea0c9c31 --- /dev/null +++ b/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs @@ -0,0 +1,80 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System.Diagnostics; +using Avalonia.Collections; +using Avalonia.VisualTree; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal abstract class EventTreeNode : ViewModelBase + { + internal bool _updateChildren = true; + internal bool _updateParent = true; + private bool _isExpanded; + private bool? _isEnabled = false; + + public EventTreeNode(EventTreeNode parent, string text) + { + this.Parent = parent; + this.Text = text; + } + + public IAvaloniaReadOnlyList Children + { + get; + protected set; + } + + public bool IsExpanded + { + get { return _isExpanded; } + set { RaiseAndSetIfChanged(ref _isExpanded, value); } + } + + public virtual bool? IsEnabled + { + get { return _isEnabled; } + set { RaiseAndSetIfChanged(ref _isEnabled, value); } + } + + public EventTreeNode Parent + { + get; + } + + public string Text + { + get; + private set; + } + + internal void UpdateChecked() + { + IsEnabled = GetValue(); + + bool? GetValue() + { + if (Children == null) + return false; + bool? value = false; + for (int i = 0; i < Children.Count; i++) + { + if (i == 0) + { + value = Children[i].IsEnabled; + continue; + } + + if (value != Children[i].IsEnabled) + { + value = null; + break; + } + } + + return value; + } + } + } +} diff --git a/src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs b/src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs new file mode 100644 index 0000000000..a9a6334689 --- /dev/null +++ b/src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs @@ -0,0 +1,75 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Windows.Input; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Data.Converters; +using Avalonia.Interactivity; +using Avalonia.Media; +using Avalonia.Threading; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal class EventsViewModel : ViewModelBase + { + private IControl _root; + private FiredEvent _selectedEvent; + private ICommand ClearCommand { get; } + + public EventsViewModel(IControl root) + { + this._root = root; + this.Nodes = RoutedEventRegistry.Instance.GetAllRegistered() + .GroupBy(e => e.OwnerType) + .OrderBy(e => e.Key.Name) + .Select(g => new ControlTreeNode(g.Key, g, this)) + .ToArray(); + } + + private void ClearExecute() + { + Action action = delegate + { + RecordedEvents.Clear(); + }; + if (!Dispatcher.UIThread.CheckAccess()) + { + Dispatcher.UIThread.Post(action); + } + else + { + action(); + } + } + + public EventTreeNode[] Nodes { get; } + + public ObservableCollection RecordedEvents { get; } = new ObservableCollection(); + + public FiredEvent SelectedEvent + { + get => _selectedEvent; + set => RaiseAndSetIfChanged(ref _selectedEvent, value); + } + } + + internal class BoolToBrushConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (bool)value ? Brushes.LightGreen : Brushes.Transparent; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs b/src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs new file mode 100644 index 0000000000..523525b634 --- /dev/null +++ b/src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs @@ -0,0 +1,93 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.ObjectModel; +using Avalonia.Diagnostics.Models; +using Avalonia.Interactivity; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal class FiredEvent : ViewModelBase + { + private RoutedEventArgs _eventArgs; + + private ChainLink _handledBy; + private ChainLink _originator; + + public FiredEvent(RoutedEventArgs eventArgs, ChainLink originator) + { + Contract.Requires(eventArgs != null); + Contract.Requires(originator != null); + + this._eventArgs = eventArgs; + this._originator = originator; + AddToChain(originator); + } + + public bool IsPartOfSameEventChain(RoutedEventArgs e) + { + return e == _eventArgs; + } + + public RoutedEvent Event => _eventArgs.RoutedEvent; + + public bool IsHandled => HandledBy?.Handled == true; + + public ObservableCollection EventChain { get; } = new ObservableCollection(); + + public string DisplayText + { + get + { + if (IsHandled) + { + return $"{Event.Name} on {Originator.HandlerName};" + Environment.NewLine + + $"strategies: {Event.RoutingStrategies}; handled by: {HandledBy.HandlerName}"; + } + return $"{Event.Name} on {Originator.HandlerName}; strategies: {Event.RoutingStrategies}"; + } + } + + public ChainLink Originator + { + get { return _originator; } + set + { + if (_originator != value) + { + _originator = value; + RaisePropertyChanged(); + RaisePropertyChanged(nameof(DisplayText)); + } + } + } + + public ChainLink HandledBy + { + get { return _handledBy; } + set + { + if (_handledBy != value) + { + _handledBy = value; + RaisePropertyChanged(); + RaisePropertyChanged(nameof(IsHandled)); + RaisePropertyChanged(nameof(DisplayText)); + } + } + } + + public void AddToChain(object handler, bool handled, RoutingStrategies route) + { + AddToChain(new ChainLink(handler, handled, route)); + } + + public void AddToChain(ChainLink link) + { + EventChain.Add(link); + if (HandledBy == null && link.Handled) + HandledBy = link; + } + } +} diff --git a/src/Avalonia.Diagnostics/Views/EventsView.xaml b/src/Avalonia.Diagnostics/Views/EventsView.xaml new file mode 100644 index 0000000000..79f78d2bba --- /dev/null +++ b/src/Avalonia.Diagnostics/Views/EventsView.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + static Border() { - AffectsRender(BorderThicknessProperty, CornerRadiusProperty); + AffectsRender( + BackgroundProperty, + BorderBrushProperty, + BorderThicknessProperty, + CornerRadiusProperty); AffectsMeasure(BorderThicknessProperty); - BrushAffectsRender(BackgroundProperty, BorderBrushProperty); } /// diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 9b768749df..5415f3974d 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -30,7 +30,7 @@ namespace Avalonia.Controls /// static Panel() { - BrushAffectsRender(BackgroundProperty); + AffectsRender(BackgroundProperty); ClipToBoundsProperty.OverrideDefaultValue(true); } diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index a8019fc306..8d703cfc1c 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -90,9 +90,8 @@ namespace Avalonia.Controls.Presenters /// static ContentPresenter() { - AffectsRender(BorderThicknessProperty, CornerRadiusProperty); + AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); AffectsMeasure(BorderThicknessProperty, PaddingProperty); - BrushAffectsRender(BackgroundProperty, BorderBrushProperty); ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); ContentTemplateProperty.Changed.AddClassHandler(x => x.ContentChanged); TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 7ca0867031..f77c43acd0 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -33,8 +33,7 @@ namespace Avalonia.Controls.Shapes static Shape() { AffectsMeasure(StretchProperty, StrokeThicknessProperty); - AffectsRender(StrokeDashArrayProperty); - BrushAffectsRender(FillProperty, StrokeProperty); + AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty); } public Geometry DefiningGeometry diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 5a98ef3ecc..af7b0f835e 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -101,10 +101,11 @@ namespace Avalonia.Controls { ClipToBoundsProperty.OverrideDefaultValue(true); AffectsRender( + BackgroundProperty, + ForegroundProperty, FontWeightProperty, FontSizeProperty, FontStyleProperty); - BrushAffectsRender(BackgroundProperty, ForegroundProperty); } /// diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index 8ba7c1be04..c2c041f073 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -19,7 +19,7 @@ namespace Avalonia.Media AvaloniaProperty.Register(nameof(Opacity), 1.0); /// - public event EventHandler Changed; + public event EventHandler Invalidated; /// /// Gets or sets the opacity of the brush. @@ -63,14 +63,14 @@ namespace Avalonia.Media /// The properties. /// /// After a call to this method in a brush's static constructor, any change to the - /// property will cause the event to be raised on the brush. + /// property will cause the event to be raised on the brush. /// protected static void AffectsRender(params AvaloniaProperty[] properties) where T : Brush { void Invalidate(AvaloniaPropertyChangedEventArgs e) { - (e.Sender as T)?.RaiseChanged(EventArgs.Empty); + (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty); } foreach (var property in properties) @@ -80,9 +80,9 @@ namespace Avalonia.Media } /// - /// Raises the event. + /// Raises the event. /// /// The event args. - protected void RaiseChanged(EventArgs e) => Changed?.Invoke(this, e); + protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); } } diff --git a/src/Avalonia.Visuals/Media/GradientBrush.cs b/src/Avalonia.Visuals/Media/GradientBrush.cs index c123813cee..8fd2dcf27f 100644 --- a/src/Avalonia.Visuals/Media/GradientBrush.cs +++ b/src/Avalonia.Visuals/Media/GradientBrush.cs @@ -80,18 +80,18 @@ namespace Avalonia.Media brush._gradientStopsSubscription = newValue.TrackItemPropertyChanged(brush.GradientStopChanged); } - brush.RaiseChanged(EventArgs.Empty); + brush.RaiseInvalidated(EventArgs.Empty); } } private void GradientStopsChanged(object sender, NotifyCollectionChangedEventArgs e) { - RaiseChanged(EventArgs.Empty); + RaiseInvalidated(EventArgs.Empty); } private void GradientStopChanged(Tuple e) { - RaiseChanged(EventArgs.Empty); + RaiseInvalidated(EventArgs.Empty); } } } diff --git a/src/Avalonia.Visuals/Media/IAffectsRender.cs b/src/Avalonia.Visuals/Media/IAffectsRender.cs new file mode 100644 index 0000000000..0024195ce5 --- /dev/null +++ b/src/Avalonia.Visuals/Media/IAffectsRender.cs @@ -0,0 +1,16 @@ +using System; + +namespace Avalonia.Media +{ + /// + /// Signals to a self-rendering control that changes to the resource should invoke + /// . + /// + public interface IAffectsRender + { + /// + /// Raised when the resource changes visually. + /// + event EventHandler Invalidated; + } +} diff --git a/src/Avalonia.Visuals/Media/IMutableBrush.cs b/src/Avalonia.Visuals/Media/IMutableBrush.cs index 762731a6a8..415db61d68 100644 --- a/src/Avalonia.Visuals/Media/IMutableBrush.cs +++ b/src/Avalonia.Visuals/Media/IMutableBrush.cs @@ -5,13 +5,8 @@ namespace Avalonia.Media /// /// Represents a mutable brush which can return an immutable clone of itself. /// - public interface IMutableBrush : IBrush + public interface IMutableBrush : IBrush, IAffectsRender { - /// - /// Raised when the brush changes visually. - /// - event EventHandler Changed; - /// /// Creates an immutable clone of the brush. /// diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index fdff16494f..e5fcf1ba1d 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -338,45 +338,20 @@ namespace Avalonia /// FrameworkPropertyMetadata.AffectsRender flag. /// protected static void AffectsRender(params AvaloniaProperty[] properties) - where T : class, IVisual - { - void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.InvalidateVisual(); - } - - foreach (var property in properties) - { - property.Changed.Subscribe(Invalidate); - } - } - - /// - /// Indicates that a brush property change should cause to be - /// called. - /// - /// The properties. - /// - /// This method should be called in a control's static constructor with each property - /// on the control which when changed should cause a redraw. It not only triggers an - /// invalidation when the property itself changes, but also when the brush raises - /// the event. - /// - protected static void BrushAffectsRender(params AvaloniaProperty[] properties) where T : Visual { void Invalidate(AvaloniaPropertyChangedEventArgs e) { if (e.Sender is T sender) { - if (e.OldValue is IMutableBrush oldValue) + if (e.OldValue is IAffectsRender oldValue) { - oldValue.Changed -= sender.BrushChanged; + oldValue.Invalidated -= sender.AffectsRenderInvalidated; } - if (e.NewValue is IMutableBrush newValue) + if (e.NewValue is IAffectsRender newValue) { - newValue.Changed += sender.BrushChanged; + newValue.Invalidated += sender.AffectsRenderInvalidated; } sender.InvalidateVisual(); @@ -582,7 +557,7 @@ namespace Avalonia OnVisualParentChanged(old, value); } - private void BrushChanged(object sender, EventArgs e) => InvalidateVisual(); + private void AffectsRenderInvalidated(object sender, EventArgs e) => InvalidateVisual(); /// /// Called when the collection changes. diff --git a/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs index f843a6e333..ff7b94105a 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs @@ -11,14 +11,14 @@ namespace Avalonia.Visuals.UnitTests.Media public class ImageBrushTests { [Fact] - public void Changing_Source_Raises_Changed() + public void Changing_Source_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); var target = new ImageBrush(bitmap1); var raised = false; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.Source = bitmap2; Assert.True(raised); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs index 62f53108e6..b3f78dfe8f 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs @@ -9,7 +9,7 @@ namespace Avalonia.Visuals.UnitTests.Media public class LinearGradientBrushTests { [Fact] - public void Changing_StartPoint_Raises_Changed() + public void Changing_StartPoint_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -17,14 +17,14 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.StartPoint = new RelativePoint(); - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.StartPoint = new RelativePoint(10, 10, RelativeUnit.Absolute); Assert.True(raised); } [Fact] - public void Changing_EndPoint_Raises_Changed() + public void Changing_EndPoint_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -32,14 +32,14 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.EndPoint = new RelativePoint(); - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.EndPoint = new RelativePoint(10, 10, RelativeUnit.Absolute); Assert.True(raised); } [Fact] - public void Changing_GradientStops_Raises_Changed() + public void Changing_GradientStops_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -47,14 +47,14 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.GradientStops = new GradientStops { new GradientStop(Colors.Green, 0) }; Assert.True(raised); } [Fact] - public void Adding_GradientStop_Raises_Changed() + public void Adding_GradientStop_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -62,14 +62,14 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.GradientStops.Add(new GradientStop(Colors.Green, 1)); Assert.True(raised); } [Fact] - public void Changing_GradientStop_Offset_Raises_Changed() + public void Changing_GradientStop_Offset_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -77,7 +77,7 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.GradientStops[0].Offset = 0.5; Assert.True(raised); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs index 4e87b7081d..bcf63f9660 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs @@ -7,12 +7,12 @@ namespace Avalonia.Visuals.UnitTests.Media public class SolidColorBrushTests { [Fact] - public void Changing_Color_Raises_Changed() + public void Changing_Color_Raises_Invalidated() { var target = new SolidColorBrush(Colors.Red); var raised = false; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.Color = Colors.Green; Assert.True(raised); From 3cbcd0ac0fab750a413b0e9aaeba88d092faa238 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 12 Sep 2018 10:42:44 +0800 Subject: [PATCH 048/118] Match CSS's behavior on selectors & animations. --- src/Avalonia.Animation/Animator`1.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index ab82bfb35d..e4af0f356d 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -37,16 +37,11 @@ namespace Avalonia.Animation if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); - return match - .DistinctUntilChanged() - .Select(x => x ? RunKeyFrames(animation, control, onComplete) : null) - .Buffer(2, 1) - .Where(x => x.Count > 1) - .Subscribe(x => - { - if (animation.RepeatCount.IsLoop) - x[0]?.Dispose(); - }); + return match.DistinctUntilChanged() + .Select(x => x ? RunKeyFrames(animation, control, onComplete) : null) + .Buffer(2, 1) + .Where(x => x.Count > 1) + .Subscribe(x => x[0]?.Dispose()); } /// From 5fce271ff8bfff57987199d0fde5839c4788d650 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 11 Sep 2018 21:43:05 -0500 Subject: [PATCH 049/118] PR Feedback --- src/Avalonia.Animation/IAnimation.cs | 4 ++-- src/Avalonia.Animation/IAnimator.cs | 2 +- src/Avalonia.Animation/IGlobalClock.cs | 10 ++++++++++ src/Avalonia.Controls/Application.cs | 2 +- .../InternalPlatformThreadingInterface.cs | 9 ++++++--- src/Avalonia.Controls/TopLevel.cs | 1 - src/Avalonia.Visuals/Animation/RenderLoopClock.cs | 6 +++--- .../Rendering/DefaultRenderTimer.cs | 12 +++++++----- .../Rendering/DeferredRenderer.cs | 2 +- src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs | 15 +-------------- src/Avalonia.Visuals/Rendering/IRenderTimer.cs | 4 ++-- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 4 ++-- .../Rendering/RenderLoopTests.cs | 14 +++++++------- 13 files changed, 43 insertions(+), 42 deletions(-) create mode 100644 src/Avalonia.Animation/IGlobalClock.cs diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index 34b0a5d769..ff85535d8a 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -9,12 +9,12 @@ namespace Avalonia.Animation public interface IAnimation { /// - /// Apply the animation to the specified control + /// Apply the animation to the specified control and run it when produces true. /// IDisposable Apply(Animatable control, IClock clock, IObservable match, Action onComplete = null); /// - /// Run the animation to the specified control + /// Run the animation on the specified control. /// Task RunAsync(Animatable control, IClock clock); } diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index 04bad8e112..d0fb173c54 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -16,6 +16,6 @@ namespace Avalonia.Animation /// /// Applies the current KeyFrame group to the specified control. /// - IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete); + IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete); } } diff --git a/src/Avalonia.Animation/IGlobalClock.cs b/src/Avalonia.Animation/IGlobalClock.cs new file mode 100644 index 0000000000..b0455e2c80 --- /dev/null +++ b/src/Avalonia.Animation/IGlobalClock.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Animation +{ + public interface IGlobalClock : IClock + { + } +} diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 586a73b75c..37796ff9ba 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -340,7 +340,7 @@ namespace Avalonia var clock = new RenderLoopClock(); AvaloniaLocator.CurrentMutable - .Bind().ToConstant(clock) + .Bind().ToConstant(clock) .GetService()?.Add(clock); } } diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 501e15653a..bb357453ff 100644 --- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -14,7 +14,10 @@ namespace Avalonia.Controls.Platform public InternalPlatformThreadingInterface() { TlsCurrentThreadIsLoopThread = true; - StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(Environment.TickCount)); + StartTimer( + DispatcherPriority.Render, + new TimeSpan(0, 0, 0, 0, 66), + () => Tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount))); } private readonly AutoResetEvent _signaled = new AutoResetEvent(false); @@ -105,7 +108,7 @@ namespace Avalonia.Controls.Platform public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread; public event Action Signaled; - public event Action Tick; + public event Action Tick; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index fb5b932fd8..630753396f 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -96,7 +96,6 @@ namespace Avalonia.Controls _applicationLifecycle = TryGetService(dependencyResolver); _renderInterface = TryGetService(dependencyResolver); - var renderLoop = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); impl.SetInputRoot(this); diff --git a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs index e59b3aac0d..504caef461 100644 --- a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs +++ b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs @@ -5,7 +5,7 @@ using Avalonia.Rendering; namespace Avalonia.Animation { - public class RenderLoopClock : ClockBase, IRenderLoopTask + public class RenderLoopClock : ClockBase, IRenderLoopTask, IGlobalClock { protected override void Stop() { @@ -18,9 +18,9 @@ namespace Avalonia.Animation { } - void IRenderLoopTask.Update(long tickCount) + void IRenderLoopTask.Update(TimeSpan time) { - Pulse(TimeSpan.FromMilliseconds(tickCount)); + Pulse(time); } } } diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs index a83334ff5e..d0eb181c65 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering { private IRuntimePlatform _runtime; private int _subscriberCount; - private Action _tick; + private Action _tick; private IDisposable _subscription; /// @@ -39,7 +39,7 @@ namespace Avalonia.Rendering public int FramesPerSecond { get; } /// - public event Action Tick + public event Action Tick { add { @@ -78,14 +78,16 @@ namespace Avalonia.Rendering /// This can be overridden by platform implementations to use a specialized timer /// implementation. /// - protected virtual IDisposable StartCore(Action tick) + protected virtual IDisposable StartCore(Action tick) { if (_runtime == null) { _runtime = AvaloniaLocator.Current.GetService(); } - return _runtime.StartSystemTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), () => tick(Environment.TickCount)); + return _runtime.StartSystemTimer( + TimeSpan.FromSeconds(1.0 / FramesPerSecond), + () => tick(TimeSpan.FromMilliseconds(Environment.TickCount))); } /// @@ -97,7 +99,7 @@ namespace Avalonia.Rendering _subscription = null; } - private void InternalTick(long tickCount) + private void InternalTick(TimeSpan tickCount) { _tick(tickCount); } diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 3221dd85c6..fc67b5c461 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -166,7 +166,7 @@ namespace Avalonia.Rendering bool IRenderLoopTask.NeedsUpdate => _dirty == null || _dirty.Count > 0; - void IRenderLoopTask.Update(long tickCount) => UpdateScene(); + void IRenderLoopTask.Update(TimeSpan time) => UpdateScene(); void IRenderLoopTask.Render() { diff --git a/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs b/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs index b031bf00df..15f0afc797 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs @@ -6,20 +6,7 @@ namespace Avalonia.Rendering public interface IRenderLoopTask { bool NeedsUpdate { get; } - void Update(long tickCount); + void Update(TimeSpan time); void Render(); } - - public class MockRenderLoopTask : IRenderLoopTask - { - public bool NeedsUpdate => true; - - public void Render() - { - } - - public void Update(long tickCount) - { - } - } } diff --git a/src/Avalonia.Visuals/Rendering/IRenderTimer.cs b/src/Avalonia.Visuals/Rendering/IRenderTimer.cs index 78f6183994..d333e928a0 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderTimer.cs @@ -15,6 +15,6 @@ namespace Avalonia.Rendering /// This event can be raised on any thread; it is the responsibility of the subscriber to /// switch execution to the right thread. /// - event Action Tick; + event Action Tick; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index d920be2706..d0d5b2250d 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -84,7 +84,7 @@ namespace Avalonia.Rendering } } - private async void TimerTick(long tickCount) + private async void TimerTick(TimeSpan time) { if (Interlocked.CompareExchange(ref inTick, 1, 0) == 0) { @@ -96,7 +96,7 @@ namespace Avalonia.Rendering { foreach (var i in _items) { - i.Update(tickCount); + i.Update(time); } }, DispatcherPriority.Render).ConfigureAwait(false); } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs index 30ef35a2bb..bf992f4027 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs @@ -35,14 +35,14 @@ namespace Avalonia.Visuals.UnitTests.Rendering var renderTask = new Mock(); renderTask.Setup(t => t.NeedsUpdate).Returns(true); - renderTask.Setup(t => t.Update(It.IsAny())) + renderTask.Setup(t => t.Update(It.IsAny())) .Callback((long _) => Assert.True(inDispatcher)); loop.Add(renderTask.Object); timer.Raise(t => t.Tick += null, 0L); - renderTask.Verify(t => t.Update(It.IsAny()), Times.Once()); + renderTask.Verify(t => t.Update(It.IsAny()), Times.Once()); } [Fact] @@ -62,7 +62,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering loop.Add(renderTask.Object); timer.Raise(t => t.Tick += null, 0L); - renderTask.Verify(t => t.Update(It.IsAny()), Times.Never()); + renderTask.Verify(t => t.Update(It.IsAny()), Times.Never()); } [Fact] @@ -92,7 +92,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering loop.Add(renderTask.Object); timer.Raise(t => t.Tick += null, 0L); - renderTask.Verify(t => t.Update(It.IsAny()), Times.Once()); + renderTask.Verify(t => t.Update(It.IsAny()), Times.Once()); } [Fact] @@ -110,10 +110,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering renderTask.Setup(t => t.NeedsUpdate).Returns(true); loop.Add(renderTask.Object); - var tickCount = 12345L; - timer.Raise(t => t.Tick += null, tickCount); + var time = new TimeSpan(123456789L); + timer.Raise(t => t.Tick += null, time); - renderTask.Verify(t => t.Update(tickCount), Times.Once()); + renderTask.Verify(t => t.Update(time), Times.Once()); } } } From df014f3b1e2ec60f7eb8651e5dd01b90c4fa8d5c Mon Sep 17 00:00:00 2001 From: ahopper Date: Wed, 12 Sep 2018 03:46:46 +0100 Subject: [PATCH 050/118] Fix drawing presenter clipping (#1882) * fix clipping offset * simplified clipping calculation --- src/Avalonia.Controls/DrawingPresenter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs index 34ce598218..b30a8668fd 100644 --- a/src/Avalonia.Controls/DrawingPresenter.cs +++ b/src/Avalonia.Controls/DrawingPresenter.cs @@ -49,7 +49,7 @@ namespace Avalonia.Controls if (Drawing != null) { using (context.PushPreTransform(_transform)) - using (context.PushClip(Bounds)) + using (context.PushClip(new Rect(Bounds.Size))) { Drawing.Draw(context); } From 0aa5866ea862ae9a4836dc1899361fd90f7b0739 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 11 Sep 2018 22:40:36 -0500 Subject: [PATCH 051/118] Fix missed changes in IRenderTimer. --- src/OSX/Avalonia.MonoMac/RenderTimer.cs | 4 ++-- src/Windows/Avalonia.Win32/RenderTimer.cs | 4 ++-- src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs | 4 ++-- .../Rendering/DeferredRendererTests.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/RenderTimer.cs b/src/OSX/Avalonia.MonoMac/RenderTimer.cs index 22ad2e81a2..f3c49828d6 100644 --- a/src/OSX/Avalonia.MonoMac/RenderTimer.cs +++ b/src/OSX/Avalonia.MonoMac/RenderTimer.cs @@ -12,7 +12,7 @@ namespace Avalonia.MonoMac { } - protected override IDisposable StartCore(Action tick) + protected override IDisposable StartCore(Action tick) { return AvaloniaLocator.Current.GetService().StartSystemTimer( TimeSpan.FromSeconds(1.0 / FramesPerSecond), @@ -20,7 +20,7 @@ namespace Avalonia.MonoMac { using (new NSAutoreleasePool()) { - tick?.Invoke(Environment.TickCount); + tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount)); } }); } diff --git a/src/Windows/Avalonia.Win32/RenderTimer.cs b/src/Windows/Avalonia.Win32/RenderTimer.cs index c911bc3adf..7dbb745a23 100644 --- a/src/Windows/Avalonia.Win32/RenderTimer.cs +++ b/src/Windows/Avalonia.Win32/RenderTimer.cs @@ -29,12 +29,12 @@ namespace Avalonia.Win32 { } - protected override IDisposable StartCore(Action tick) + protected override IDisposable StartCore(Action tick) { EnsureTimerQueueCreated(); var msPerFrame = 1000 / FramesPerSecond; - timerDelegate = (_, __) => tick(Environment.TickCount); + timerDelegate = (_, __) => tick(TimeSpan.FromMilliseconds(Environment.TickCount)); UnmanagedMethods.CreateTimerQueueTimer( out var timer, diff --git a/src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs b/src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs index 1357a4f642..0cefba7f19 100644 --- a/src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs +++ b/src/iOS/Avalonia.iOS/DisplayLinkRenderTimer.cs @@ -7,7 +7,7 @@ namespace Avalonia.iOS { class DisplayLinkRenderTimer : IRenderTimer { - public event Action Tick; + public event Action Tick; private CADisplayLink _link; public DisplayLinkRenderTimer() @@ -21,7 +21,7 @@ namespace Avalonia.iOS { try { - Tick?.Invoke(Environment.TickCount); + Tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount)); } catch (Exception) { diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index e2a5c0c54c..8c103360d4 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -357,7 +357,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering private void RunFrame(IRenderLoopTask task) { - task.Update(0); + task.Update(TimeSpan.Zero); task.Render(); } From 4f11ecdd38a6dc7bd50a342b22537f6970de7e95 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Sep 2018 13:07:32 +0200 Subject: [PATCH 052/118] Added an example of dynamic menus to the ControlCatalog. Creates a menu using `DataTemplates` on the MenuPage. --- samples/ControlCatalog/App.xaml | 9 ++- samples/ControlCatalog/Pages/MenuPage.xaml | 59 ++++++++++++------- samples/ControlCatalog/Pages/MenuPage.xaml.cs | 28 +++++++++ 3 files changed, 72 insertions(+), 24 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 43971dec4f..95d515ec60 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -14,6 +14,11 @@ - + + + - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index 296cfa8704..6eeb86a00f 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -7,29 +7,44 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16"> - - - - - - - + + Defined in XAML + + + + + + + + + + + + + + + + + + - - - - + + - - - - - - - - - - + + + + + Dyanamically generated + + + + + + - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index d637c172e1..880d4bc59a 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Markup.Xaml; @@ -8,6 +9,27 @@ namespace ControlCatalog.Pages public MenuPage() { this.InitializeComponent(); + DataContext = new[] + { + new MenuItemViewModel + { + Header = "_File", + Items = + { + new MenuItemViewModel { Header = "_Open..." }, + new MenuItemViewModel { Header = "Save" }, + } + }, + new MenuItemViewModel + { + Header = "_Edit", + Items = + { + new MenuItemViewModel { Header = "_Copy" }, + new MenuItemViewModel { Header = "_Paste" }, + } + } + }; } private void InitializeComponent() @@ -15,4 +37,10 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } } + + public class MenuItemViewModel + { + public string Header { get; set; } + public IList Items { get; } = new List(); + } } From b44900395800eec65d3fc1b7b6bcef8152638a43 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Sep 2018 15:21:14 +0200 Subject: [PATCH 053/118] Allow MenuItems to be separators. If `MenuItem.Header == "-"` then apply the `:separator` pseudoclass to the `MenuItem` and display it as a separator. --- samples/ControlCatalog/Pages/MenuPage.xaml.cs | 16 ++++++++++++--- src/Avalonia.Controls/MenuItem.cs | 20 +++++++++++++++---- src/Avalonia.Themes.Default/MenuItem.xaml | 12 ++++++++++- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 880d4bc59a..01add3e76e 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -14,16 +14,26 @@ namespace ControlCatalog.Pages new MenuItemViewModel { Header = "_File", - Items = + Items = new[] { new MenuItemViewModel { Header = "_Open..." }, new MenuItemViewModel { Header = "Save" }, + new MenuItemViewModel { Header = "-" }, + new MenuItemViewModel + { + Header = "Recent", + Items = new[] + { + new MenuItemViewModel { Header = "File1.txt" }, + new MenuItemViewModel { Header = "File2.txt" }, + } + }, } }, new MenuItemViewModel { Header = "_Edit", - Items = + Items = new[] { new MenuItemViewModel { Header = "_Copy" }, new MenuItemViewModel { Header = "_Paste" }, @@ -41,6 +51,6 @@ namespace ControlCatalog.Pages public class MenuItemViewModel { public string Header { get; set; } - public IList Items { get; } = new List(); + public IList Items { get; set; } } } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 7b57783c5a..03a43b5164 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -105,6 +105,7 @@ namespace Avalonia.Controls ClickEvent.AddClassHandler(x => x.OnClick); SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); IsSubMenuOpenProperty.Changed.AddClassHandler(x => x.SubMenuOpenChanged); + PseudoClass(HeaderProperty, x => x as string == "-", ":separator"); } public MenuItem() @@ -357,10 +358,21 @@ namespace Avalonia.Controls { base.OnTemplateApplied(e); - _popup = e.NameScope.Get("PART_Popup"); - _popup.DependencyResolver = DependencyResolver.Instance; - _popup.Opened += PopupOpened; - _popup.Closed += PopupClosed; + if (_popup != null) + { + _popup.Opened -= PopupOpened; + _popup.Closed -= PopupClosed; + _popup.DependencyResolver = null; + } + + _popup = e.NameScope.Find("PART_Popup"); + + if (_popup != null) + { + _popup.DependencyResolver = DependencyResolver.Instance; + _popup.Opened += PopupOpened; + _popup.Closed += PopupClosed; + } } /// diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 8a2ed2a802..07faf7a632 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -73,7 +73,17 @@ - + + + diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 01add3e76e..ec7c000fe6 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -1,6 +1,10 @@ using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using System.Windows.Input; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using ReactiveUI; namespace ControlCatalog.Pages { @@ -9,23 +13,35 @@ namespace ControlCatalog.Pages public MenuPage() { this.InitializeComponent(); - DataContext = new[] + var vm = new MenuPageViewModel(); + + vm.MenuItems = new[] { new MenuItemViewModel { Header = "_File", Items = new[] { - new MenuItemViewModel { Header = "_Open..." }, - new MenuItemViewModel { Header = "Save" }, + new MenuItemViewModel { Header = "_Open...", Command = vm.OpenCommand }, + new MenuItemViewModel { Header = "Save", Command = vm.SaveCommand }, new MenuItemViewModel { Header = "-" }, new MenuItemViewModel { Header = "Recent", Items = new[] { - new MenuItemViewModel { Header = "File1.txt" }, - new MenuItemViewModel { Header = "File2.txt" }, + new MenuItemViewModel + { + Header = "File1.txt", + Command = vm.OpenRecentCommand, + CommandParameter = @"c:\foo\File1.txt" + }, + new MenuItemViewModel + { + Header = "File2.txt", + Command = vm.OpenRecentCommand, + CommandParameter = @"c:\foo\File2.txt" + }, } }, } @@ -40,6 +56,8 @@ namespace ControlCatalog.Pages } } }; + + DataContext = vm; } private void InitializeComponent() @@ -48,9 +66,50 @@ namespace ControlCatalog.Pages } } + public class MenuPageViewModel + { + public MenuPageViewModel() + { + OpenCommand = ReactiveCommand.CreateFromTask(Open); + SaveCommand = ReactiveCommand.Create(Save); + OpenRecentCommand = ReactiveCommand.Create(OpenRecent); + } + + public IReadOnlyList MenuItems { get; set; } + public ReactiveCommand OpenCommand { get; } + public ReactiveCommand SaveCommand { get; } + public ReactiveCommand OpenRecentCommand { get; } + + public async Task Open() + { + var dialog = new OpenFileDialog(); + var result = await dialog.ShowAsync(); + + if (result != null) + { + foreach (var path in result) + { + System.Diagnostics.Debug.WriteLine($"Opened: {path}"); + } + } + } + + public void Save() + { + System.Diagnostics.Debug.WriteLine("Save"); + } + + public void OpenRecent(string path) + { + System.Diagnostics.Debug.WriteLine($"Open recent: {path}"); + } + } + public class MenuItemViewModel { public string Header { get; set; } + public ICommand Command { get; set; } + public object CommandParameter { get; set; } public IList Items { get; set; } } } From a8d4c8d799ee1abf5338028361b0855745ebae47 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 13 Sep 2018 14:24:13 +0800 Subject: [PATCH 057/118] Add a new Disposable Extention. --- src/Avalonia.Animation/Animator`1.cs | 11 +++-- .../Reactive/DisposeOnNextObservable.cs | 40 +++++++++++++++++++ src/Avalonia.Base/Reactive/ObservableEx.cs | 17 +++++++- 3 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index e4af0f356d..888450e7f0 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Animation.Utils; using Avalonia.Collections; using Avalonia.Data; +using Avalonia.Reactive; + namespace Avalonia.Animation { @@ -38,10 +41,10 @@ namespace Avalonia.Animation VerifyConvertKeyFrames(); return match.DistinctUntilChanged() - .Select(x => x ? RunKeyFrames(animation, control, onComplete) : null) - .Buffer(2, 1) - .Where(x => x.Count > 1) - .Subscribe(x => x[0]?.Dispose()); + .ObserveOn(Avalonia.Threading.AvaloniaScheduler.Instance) + .Select(x => x ? RunKeyFrames(animation, control, onComplete) : Disposable.Empty) + .DisposeCurrentOnNext() + .Subscribe(); } /// diff --git a/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs b/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs new file mode 100644 index 0000000000..8650fe5400 --- /dev/null +++ b/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs @@ -0,0 +1,40 @@ +using System; +using Avalonia.Threading; + +namespace Avalonia.Reactive +{ + public class DisposeOnNextObservable : LightweightObservableBase, IObserver where T : IDisposable + { + private IDisposable lastValue; + + private void ValueNext(T value) + { + this.PublishNext(value); + lastValue?.Dispose(); + lastValue = value; + } + + public void OnCompleted() + { + this.PublishCompleted(); + } + + public void OnError(Exception error) + { + this.PublishError(error); + } + + void IObserver.OnNext(T value) + { + ValueNext(value); + } + + protected override void Initialize() + { + } + + protected override void Deinitialize() + { + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Reactive/ObservableEx.cs b/src/Avalonia.Base/Reactive/ObservableEx.cs index 5b2a39d5ff..dc3be36015 100644 --- a/src/Avalonia.Base/Reactive/ObservableEx.cs +++ b/src/Avalonia.Base/Reactive/ObservableEx.cs @@ -22,6 +22,20 @@ namespace Avalonia.Reactive return new SingleValueImpl(value); } + /// + /// Disposes the current and saves the next. + /// + /// The type of the value. + /// The source . + /// The observable. + public static IObservable DisposeCurrentOnNext(this IObservable observable) + where T : IDisposable + { + var subject = new DisposeOnNextObservable(); + observable.Subscribe(subject); + return subject; + } + private class SingleValueImpl : IObservable { private T _value; @@ -30,7 +44,6 @@ namespace Avalonia.Reactive { _value = value; } - public IDisposable Subscribe(IObserver observer) { observer.OnNext(_value); @@ -38,4 +51,4 @@ namespace Avalonia.Reactive } } } -} +} \ No newline at end of file From 74c8cedde9070956d283137e0407d1888572c72b Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 13 Sep 2018 15:17:32 +0800 Subject: [PATCH 058/118] Try fixing the sporadic Bindings Exceptions. --- src/Avalonia.Animation/Animator`1.cs | 7 ++----- src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 888450e7f0..decca8e858 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -1,14 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Animation.Utils; using Avalonia.Collections; using Avalonia.Data; using Avalonia.Reactive; - namespace Avalonia.Animation { /// @@ -41,8 +39,7 @@ namespace Avalonia.Animation VerifyConvertKeyFrames(); return match.DistinctUntilChanged() - .ObserveOn(Avalonia.Threading.AvaloniaScheduler.Instance) - .Select(x => x ? RunKeyFrames(animation, control, onComplete) : Disposable.Empty) + .Select(x => x ? RunKeyFrames(animation, control, onComplete) : null) .DisposeCurrentOnNext() .Subscribe(); } @@ -172,4 +169,4 @@ namespace Avalonia.Animation } } } -} +} \ No newline at end of file diff --git a/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs b/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs index 8650fe5400..18af9e8752 100644 --- a/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs +++ b/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs @@ -9,9 +9,9 @@ namespace Avalonia.Reactive private void ValueNext(T value) { - this.PublishNext(value); lastValue?.Dispose(); lastValue = value; + this.PublishNext(value); } public void OnCompleted() From f2f96e2f46a520be9f84febfeb79276b5da91586 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 13 Sep 2018 20:42:18 +0200 Subject: [PATCH 059/118] Don't dispose completed binding. --- src/Avalonia.Base/PriorityBindingEntry.cs | 7 +++++++ src/Avalonia.Base/PriorityLevel.cs | 14 +++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/PriorityBindingEntry.cs b/src/Avalonia.Base/PriorityBindingEntry.cs index 570bfe03dc..d4a47306a7 100644 --- a/src/Avalonia.Base/PriorityBindingEntry.cs +++ b/src/Avalonia.Base/PriorityBindingEntry.cs @@ -50,6 +50,11 @@ namespace Avalonia get; } + /// + /// Gets a value indicating whether the binding has completed. + /// + public bool HasCompleted { get; private set; } + /// /// The current value of the binding. /// @@ -129,6 +134,8 @@ namespace Avalonia private void Completed() { + HasCompleted = true; + if (Dispatcher.UIThread.CheckAccess()) { _owner.Completed(this); diff --git a/src/Avalonia.Base/PriorityLevel.cs b/src/Avalonia.Base/PriorityLevel.cs index 96661bd7ea..909558b0ce 100644 --- a/src/Avalonia.Base/PriorityLevel.cs +++ b/src/Avalonia.Base/PriorityLevel.cs @@ -112,12 +112,16 @@ namespace Avalonia return Disposable.Create(() => { - Bindings.Remove(node); - entry.Dispose(); - - if (entry.Index >= ActiveBindingIndex) + if (!entry.HasCompleted) { - ActivateFirstBinding(); + Bindings.Remove(node); + + entry.Dispose(); + + if (entry.Index >= ActiveBindingIndex) + { + ActivateFirstBinding(); + } } }); } From bca37513b4666b522104be1b1d1b0aa2cbecd19c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 13 Sep 2018 22:05:02 +0200 Subject: [PATCH 060/118] Added failing test for disposing completed binding. --- .../AvaloniaObjectTests_Binding.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 4638aa84a5..23984a7c8d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -479,6 +479,18 @@ namespace Avalonia.Base.UnitTests Assert.False(source.SetterCalled); } + [Fact] + public void Disposing_Completed_Binding_Does_Not_Throw() + { + var target = new Class1(); + var source = new Subject(); + var subscription = target.Bind(Class1.FooProperty, source); + + source.OnCompleted(); + + subscription.Dispose(); + } + /// /// Returns an observable that returns a single value but does not complete. /// @@ -595,4 +607,4 @@ namespace Avalonia.Base.UnitTests public bool SetterCalled { get; private set; } } } -} \ No newline at end of file +} From e58b4d5152ac16e4bb9cafdb0e11ea710b2baef5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 13 Sep 2018 20:42:18 +0200 Subject: [PATCH 061/118] Don't dispose completed binding. --- src/Avalonia.Base/PriorityBindingEntry.cs | 7 +++++++ src/Avalonia.Base/PriorityLevel.cs | 14 +++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/PriorityBindingEntry.cs b/src/Avalonia.Base/PriorityBindingEntry.cs index 570bfe03dc..d4a47306a7 100644 --- a/src/Avalonia.Base/PriorityBindingEntry.cs +++ b/src/Avalonia.Base/PriorityBindingEntry.cs @@ -50,6 +50,11 @@ namespace Avalonia get; } + /// + /// Gets a value indicating whether the binding has completed. + /// + public bool HasCompleted { get; private set; } + /// /// The current value of the binding. /// @@ -129,6 +134,8 @@ namespace Avalonia private void Completed() { + HasCompleted = true; + if (Dispatcher.UIThread.CheckAccess()) { _owner.Completed(this); diff --git a/src/Avalonia.Base/PriorityLevel.cs b/src/Avalonia.Base/PriorityLevel.cs index 96661bd7ea..909558b0ce 100644 --- a/src/Avalonia.Base/PriorityLevel.cs +++ b/src/Avalonia.Base/PriorityLevel.cs @@ -112,12 +112,16 @@ namespace Avalonia return Disposable.Create(() => { - Bindings.Remove(node); - entry.Dispose(); - - if (entry.Index >= ActiveBindingIndex) + if (!entry.HasCompleted) { - ActivateFirstBinding(); + Bindings.Remove(node); + + entry.Dispose(); + + if (entry.Index >= ActiveBindingIndex) + { + ActivateFirstBinding(); + } } }); } From 74a5b1f65ad0fd947c4118854e64fe29327d515c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 13 Sep 2018 22:45:22 +0200 Subject: [PATCH 062/118] Added a failing MenuItem separator test. --- .../MenuItemTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/Avalonia.Controls.UnitTests/MenuItemTests.cs diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs new file mode 100644 index 0000000000..e7352af23e --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class MenuItemTests + { + [Fact] + public void Header_Of_Minus_Should_Apply_Separator_Pseudoclass() + { + var target = new MenuItem { Header = "-" }; + + Assert.True(target.Classes.Contains(":separator")); + } + + [Fact] + public void Separator_Item_Should_Set_Focusable_False() + { + var target = new MenuItem { Header = "-" }; + + Assert.False(target.Focusable); + } + } +} From f55b556b1c42e810ab6e66ad9f986a59d8868c56 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 13 Sep 2018 22:45:35 +0200 Subject: [PATCH 063/118] Make separator MenuItem non-focusable. --- src/Avalonia.Controls/MenuItem.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 03a43b5164..055d49fb0b 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -99,13 +99,13 @@ namespace Avalonia.Controls SelectableMixin.Attach(IsSelectedProperty); CommandProperty.Changed.Subscribe(CommandChanged); FocusableProperty.OverrideDefaultValue(true); + HeaderProperty.Changed.AddClassHandler(x => x.HeaderChanged); IconProperty.Changed.AddClassHandler(x => x.IconChanged); IsSelectedProperty.Changed.AddClassHandler(x => x.IsSelectedChanged); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); ClickEvent.AddClassHandler(x => x.OnClick); SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); IsSubMenuOpenProperty.Changed.AddClassHandler(x => x.SubMenuOpenChanged); - PseudoClass(HeaderProperty, x => x as string == "-", ":separator"); } public MenuItem() @@ -420,6 +420,24 @@ namespace Avalonia.Controls IsEnabled = Command == null || Command.CanExecute(CommandParameter); } + /// + /// Called when the property changes. + /// + /// The property change event. + private void HeaderChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.NewValue is string newValue && newValue == "-") + { + PseudoClasses.Add(":separator"); + Focusable = false; + } + else if (e.OldValue is string oldValue && oldValue == "-") + { + PseudoClasses.Remove(":separator"); + Focusable = true; + } + } + /// /// Called when the property changes. /// From ee1a8ee30fb0f10e94335ce666af4fe19e59734d Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 14 Sep 2018 10:49:49 +0800 Subject: [PATCH 064/118] Make a specialized observable for instance lifetime handling. Delete DisposeOnNextObservable. --- src/Avalonia.Animation/AnimationInstance`1.cs | 7 +-- src/Avalonia.Animation/Animator`1.cs | 18 +++--- .../DisposeAnimationInstanceObservable.cs | 62 +++++++++++++++++++ .../Reactive/DisposeOnNextObservable.cs | 40 ------------ src/Avalonia.Base/Reactive/ObservableEx.cs | 16 +---- 5 files changed, 73 insertions(+), 70 deletions(-) create mode 100644 src/Avalonia.Animation/DisposeAnimationInstanceObservable.cs delete mode 100644 src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 5a72904ed2..c264663b56 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -154,7 +154,7 @@ namespace Avalonia.Animation private void InternalStep(TimeSpan systemTime) { DoPlayStatesAndTime(systemTime); - + var time = _internalClock - _firstFrameCount; var delayEndpoint = _delay; var iterationEndpoint = delayEndpoint + _duration; @@ -188,10 +188,7 @@ namespace Avalonia.Animation if (!_isLooping) { - if (_currentIteration > _repeatCount) - DoComplete(); - - if (time > iterationEndpoint) + if ((_currentIteration > _repeatCount) | (time > iterationEndpoint)) DoComplete(); } diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index decca8e858..0de3991a88 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; @@ -38,10 +41,8 @@ namespace Avalonia.Animation if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); - return match.DistinctUntilChanged() - .Select(x => x ? RunKeyFrames(animation, control, onComplete) : null) - .DisposeCurrentOnNext() - .Subscribe(); + var subject = new DisposeAnimationInstanceObservable(this, animation, control, onComplete); + return match.Subscribe(subject); } /// @@ -96,11 +97,8 @@ namespace Avalonia.Animation var lastFrameData = (lastCue.GetTypedValue(), lastCue.isNeutral); return (intraframeTime, new KeyFramePair(firstFrameData, lastFrameData)); } - - /// - /// Runs the KeyFrames Animation. - /// - private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete) + + internal IDisposable Run(Animation animation, Animatable control, Action onComplete) { var instance = new AnimationInstance(animation, control, this, onComplete, DoInterpolation); return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); diff --git a/src/Avalonia.Animation/DisposeAnimationInstanceObservable.cs b/src/Avalonia.Animation/DisposeAnimationInstanceObservable.cs new file mode 100644 index 0000000000..902a09030b --- /dev/null +++ b/src/Avalonia.Animation/DisposeAnimationInstanceObservable.cs @@ -0,0 +1,62 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using Avalonia.Animation.Utils; +using Avalonia.Collections; +using Avalonia.Data; +using Avalonia.Reactive; + +namespace Avalonia.Animation +{ + /// + /// Manages the lifetime of animation instances as determined by its selector state. + /// + internal class DisposeAnimationInstanceObservable : IObserver, IDisposable + { + private IDisposable _lastInstance; + private bool _lastMatch; + private Animator _animator; + private Animation _animation; + private Animatable _control; + private Action _onComplete; + + public DisposeAnimationInstanceObservable(Animator animator, Animation animation, Animatable control, Action onComplete) + { + this._animator = animator; + this._animation = animation; + this._control = control; + this._onComplete = onComplete; + } + + public void Dispose() + { + _lastInstance?.Dispose(); + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + _lastInstance?.Dispose(); + } + + void IObserver.OnNext(bool matchVal) + { + if (matchVal != _lastMatch) + { + _lastInstance?.Dispose(); + if (matchVal) + { + _lastInstance = _animator.RunAnimation(_animation, _control, _onComplete); + } + _lastMatch = matchVal; + } + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs b/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs deleted file mode 100644 index 18af9e8752..0000000000 --- a/src/Avalonia.Base/Reactive/DisposeOnNextObservable.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using Avalonia.Threading; - -namespace Avalonia.Reactive -{ - public class DisposeOnNextObservable : LightweightObservableBase, IObserver where T : IDisposable - { - private IDisposable lastValue; - - private void ValueNext(T value) - { - lastValue?.Dispose(); - lastValue = value; - this.PublishNext(value); - } - - public void OnCompleted() - { - this.PublishCompleted(); - } - - public void OnError(Exception error) - { - this.PublishError(error); - } - - void IObserver.OnNext(T value) - { - ValueNext(value); - } - - protected override void Initialize() - { - } - - protected override void Deinitialize() - { - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Reactive/ObservableEx.cs b/src/Avalonia.Base/Reactive/ObservableEx.cs index dc3be36015..a1ec8f9a8a 100644 --- a/src/Avalonia.Base/Reactive/ObservableEx.cs +++ b/src/Avalonia.Base/Reactive/ObservableEx.cs @@ -21,21 +21,7 @@ namespace Avalonia.Reactive { return new SingleValueImpl(value); } - - /// - /// Disposes the current and saves the next. - /// - /// The type of the value. - /// The source . - /// The observable. - public static IObservable DisposeCurrentOnNext(this IObservable observable) - where T : IDisposable - { - var subject = new DisposeOnNextObservable(); - observable.Subscribe(subject); - return subject; - } - + private class SingleValueImpl : IObservable { private T _value; From a462d0563c75e8c4c461e57847d7b4ad55e94157 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 14 Sep 2018 10:50:18 +0800 Subject: [PATCH 065/118] Add missing license headers on Avalonia.Animations. --- src/Avalonia.Animation/DoubleAnimator.cs | 5 ++++- src/Avalonia.Animation/FillMode.cs | 5 ++++- src/Avalonia.Animation/IAnimation.cs | 3 +++ src/Avalonia.Animation/IAnimationSetter.cs | 3 +++ src/Avalonia.Animation/IAnimator.cs | 5 ++++- src/Avalonia.Animation/KeyFrame.cs | 5 ++++- src/Avalonia.Animation/KeyFramePair`1.cs | 3 +++ src/Avalonia.Animation/PlayState.cs | 5 ++++- src/Avalonia.Animation/PlaybackDirection.cs | 5 ++++- 9 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Animation/DoubleAnimator.cs b/src/Avalonia.Animation/DoubleAnimator.cs index aeeb29a7dd..2e0ce64185 100644 --- a/src/Avalonia.Animation/DoubleAnimator.cs +++ b/src/Avalonia.Animation/DoubleAnimator.cs @@ -1,4 +1,7 @@ -namespace Avalonia.Animation +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Avalonia.Animation { /// /// Animator that handles properties. diff --git a/src/Avalonia.Animation/FillMode.cs b/src/Avalonia.Animation/FillMode.cs index 001e1cdeb4..39beecf455 100644 --- a/src/Avalonia.Animation/FillMode.cs +++ b/src/Avalonia.Animation/FillMode.cs @@ -1,4 +1,7 @@ -namespace Avalonia.Animation +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Avalonia.Animation { public enum FillMode { diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index 1d545a322a..831391ce46 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -1,3 +1,6 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + using System; using System.Threading.Tasks; diff --git a/src/Avalonia.Animation/IAnimationSetter.cs b/src/Avalonia.Animation/IAnimationSetter.cs index 2d22377286..9c8365ea37 100644 --- a/src/Avalonia.Animation/IAnimationSetter.cs +++ b/src/Avalonia.Animation/IAnimationSetter.cs @@ -1,3 +1,6 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + namespace Avalonia.Animation { public interface IAnimationSetter diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index 9a4da35a02..0f26b7dc2f 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using System.Collections.Generic; namespace Avalonia.Animation diff --git a/src/Avalonia.Animation/KeyFrame.cs b/src/Avalonia.Animation/KeyFrame.cs index 5eb0d2e901..44e39e042e 100644 --- a/src/Avalonia.Animation/KeyFrame.cs +++ b/src/Avalonia.Animation/KeyFrame.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using System.Collections.Generic; using Avalonia.Collections; diff --git a/src/Avalonia.Animation/KeyFramePair`1.cs b/src/Avalonia.Animation/KeyFramePair`1.cs index b0622a1580..60a16d094f 100644 --- a/src/Avalonia.Animation/KeyFramePair`1.cs +++ b/src/Avalonia.Animation/KeyFramePair`1.cs @@ -1,3 +1,6 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + namespace Avalonia.Animation { /// diff --git a/src/Avalonia.Animation/PlayState.cs b/src/Avalonia.Animation/PlayState.cs index 313d33d586..8d28f06eb1 100644 --- a/src/Avalonia.Animation/PlayState.cs +++ b/src/Avalonia.Animation/PlayState.cs @@ -1,4 +1,7 @@ -namespace Avalonia.Animation +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Avalonia.Animation { /// /// Determines the playback state of an animation. diff --git a/src/Avalonia.Animation/PlaybackDirection.cs b/src/Avalonia.Animation/PlaybackDirection.cs index bbce6106e1..a44dd388ae 100644 --- a/src/Avalonia.Animation/PlaybackDirection.cs +++ b/src/Avalonia.Animation/PlaybackDirection.cs @@ -1,4 +1,7 @@ -namespace Avalonia.Animation +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Avalonia.Animation { /// /// Determines the playback direction of an animation. From d9f1da005251757fcee83de2600f65ecd15f4ba9 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 14 Sep 2018 10:51:59 +0800 Subject: [PATCH 066/118] Fix missed method rename. --- src/Avalonia.Animation/DisposeAnimationInstanceObservable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/DisposeAnimationInstanceObservable.cs b/src/Avalonia.Animation/DisposeAnimationInstanceObservable.cs index 902a09030b..f58e816e54 100644 --- a/src/Avalonia.Animation/DisposeAnimationInstanceObservable.cs +++ b/src/Avalonia.Animation/DisposeAnimationInstanceObservable.cs @@ -53,7 +53,7 @@ namespace Avalonia.Animation _lastInstance?.Dispose(); if (matchVal) { - _lastInstance = _animator.RunAnimation(_animation, _control, _onComplete); + _lastInstance = _animator.Run(_animation, _control, _onComplete); } _lastMatch = matchVal; } From 032fc349f93551e37e5855f940470b03cb6de4d1 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 14 Sep 2018 07:35:25 +0200 Subject: [PATCH 067/118] Apply requested changes and some more clean up. --- .../{ChainLink.cs => EventChainLink.cs} | 32 ++--- .../ViewModels/EventEntryTreeNode.cs | 97 -------------- ...ntrolTreeNode.cs => EventOwnerTreeNode.cs} | 18 ++- .../ViewModels/EventTreeNode.cs | 118 ++++++++++-------- .../ViewModels/EventTreeNodeBase.cs | 78 ++++++++++++ .../ViewModels/EventsViewModel.cs | 33 ++--- .../ViewModels/FiredEvent.cs | 31 ++--- .../Views/EventsView.xaml | 4 +- .../Views/EventsView.xaml.cs | 8 +- 9 files changed, 197 insertions(+), 222 deletions(-) rename src/Avalonia.Diagnostics/Models/{ChainLink.cs => EventChainLink.cs} (59%) delete mode 100644 src/Avalonia.Diagnostics/ViewModels/EventEntryTreeNode.cs rename src/Avalonia.Diagnostics/ViewModels/{ControlTreeNode.cs => EventOwnerTreeNode.cs} (76%) create mode 100644 src/Avalonia.Diagnostics/ViewModels/EventTreeNodeBase.cs diff --git a/src/Avalonia.Diagnostics/Models/ChainLink.cs b/src/Avalonia.Diagnostics/Models/EventChainLink.cs similarity index 59% rename from src/Avalonia.Diagnostics/Models/ChainLink.cs rename to src/Avalonia.Diagnostics/Models/EventChainLink.cs index 9ea99ff1ae..aab50a13dd 100644 --- a/src/Avalonia.Diagnostics/Models/ChainLink.cs +++ b/src/Avalonia.Diagnostics/Models/EventChainLink.cs @@ -1,13 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Text; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using Avalonia.Interactivity; namespace Avalonia.Diagnostics.Models { - internal class ChainLink + internal class EventChainLink { - public object Handler { get; private set; } + public EventChainLink(object handler, bool handled, RoutingStrategies route) + { + Contract.Requires(handler != null); + + this.Handler = handler; + this.Handled = handled; + this.Route = route; + } + + public object Handler { get; } + public string HandlerName { get @@ -19,16 +30,9 @@ namespace Avalonia.Diagnostics.Models return Handler.GetType().Name; } } - public bool Handled { get; private set; } - public RoutingStrategies Route { get; private set; } - public ChainLink(object handler, bool handled, RoutingStrategies route) - { - Contract.Requires(handler != null); + public bool Handled { get; } - this.Handler = handler; - this.Handled = handled; - this.Route = route; - } + public RoutingStrategies Route { get; } } } diff --git a/src/Avalonia.Diagnostics/ViewModels/EventEntryTreeNode.cs b/src/Avalonia.Diagnostics/ViewModels/EventEntryTreeNode.cs deleted file mode 100644 index 8eead869d5..0000000000 --- a/src/Avalonia.Diagnostics/ViewModels/EventEntryTreeNode.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using Avalonia.Diagnostics.Models; -using Avalonia.Interactivity; -using Avalonia.Threading; -using Avalonia.VisualTree; - -namespace Avalonia.Diagnostics.ViewModels -{ - internal class EventEntryTreeNode : EventTreeNode - { - RoutedEvent _event; - EventsViewModel _parentViewModel; - bool _isRegistered; - FiredEvent _currentEvent; - - public EventEntryTreeNode(ControlTreeNode parent, RoutedEvent @event, EventsViewModel vm) - : base(parent, @event.Name) - { - Contract.Requires(@event != null); - Contract.Requires(vm != null); - - this._event = @event; - this._parentViewModel = vm; - } - - public override bool? IsEnabled - { - get => base.IsEnabled; - set - { - if (base.IsEnabled != value) - { - base.IsEnabled = value; - UpdateTracker(); - if (Parent != null && _updateParent) - { - try - { - Parent._updateChildren = false; - Parent.UpdateChecked(); - } - finally - { - Parent._updateChildren = true; - } - } - } - } - } - - private void UpdateTracker() - { - if (IsEnabled.GetValueOrDefault() && !_isRegistered) - { - _event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); - _isRegistered = true; - } - } - - private void HandleEvent(object sender, RoutedEventArgs e) - { - if (!_isRegistered || IsEnabled == false) - return; - if (sender is IVisual v && DevTools.BelongsToDevTool(v)) - return; - - var s = sender; - var handled = e.Handled; - var route = e.Route; - - Action handler = delegate - { - if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e)) - { - _currentEvent = new FiredEvent(e, new ChainLink(s, handled, route)); - - _parentViewModel.RecordedEvents.Add(_currentEvent); - - while (_parentViewModel.RecordedEvents.Count > 100) - _parentViewModel.RecordedEvents.RemoveAt(0); - } - else - { - _currentEvent.AddToChain(new ChainLink(s, handled, route)); - } - }; - - if (!Dispatcher.UIThread.CheckAccess()) - Dispatcher.UIThread.Post(handler); - else - handler(); - } - } -} diff --git a/src/Avalonia.Diagnostics/ViewModels/ControlTreeNode.cs b/src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs similarity index 76% rename from src/Avalonia.Diagnostics/ViewModels/ControlTreeNode.cs rename to src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs index d8a6a3bdc3..0674918400 100644 --- a/src/Avalonia.Diagnostics/ViewModels/ControlTreeNode.cs +++ b/src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs @@ -11,16 +11,9 @@ using Avalonia.Interactivity; namespace Avalonia.Diagnostics.ViewModels { - internal class ControlTreeNode : EventTreeNode + internal class EventOwnerTreeNode : EventTreeNodeBase { - public ControlTreeNode(Type type, IEnumerable events, EventsViewModel vm) - : base(null, type.Name) - { - this.Children = new AvaloniaList(events.OrderBy(e => e.Name).Select(e => new EventEntryTreeNode(this, e, vm) { IsEnabled = IsDefault(e) })); - this.IsExpanded = true; - } - - RoutedEvent[] defaultEvents = new RoutedEvent[] + private static readonly RoutedEvent[] s_defaultEvents = new RoutedEvent[] { Button.ClickEvent, InputElement.KeyDownEvent, @@ -30,9 +23,12 @@ namespace Avalonia.Diagnostics.ViewModels InputElement.PointerPressedEvent, }; - private bool IsDefault(RoutedEvent e) + public EventOwnerTreeNode(Type type, IEnumerable events, EventsViewModel vm) + : base(null, type.Name) { - return defaultEvents.Contains(e); + this.Children = new AvaloniaList(events.OrderBy(e => e.Name) + .Select(e => new EventTreeNode(this, e, vm) { IsEnabled = s_defaultEvents.Contains(e) })); + this.IsExpanded = true; } public override bool? IsEnabled diff --git a/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs index 50ea0c9c31..59c0f82414 100644 --- a/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs @@ -1,80 +1,98 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System.Diagnostics; -using Avalonia.Collections; +using System; + +using Avalonia.Diagnostics.Models; +using Avalonia.Interactivity; +using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels { - internal abstract class EventTreeNode : ViewModelBase + internal class EventTreeNode : EventTreeNodeBase { - internal bool _updateChildren = true; - internal bool _updateParent = true; - private bool _isExpanded; - private bool? _isEnabled = false; - - public EventTreeNode(EventTreeNode parent, string text) - { - this.Parent = parent; - this.Text = text; - } + private RoutedEvent _event; + private EventsViewModel _parentViewModel; + private bool _isRegistered; + private FiredEvent _currentEvent; - public IAvaloniaReadOnlyList Children + public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsViewModel vm) + : base(parent, @event.Name) { - get; - protected set; - } + Contract.Requires(@event != null); + Contract.Requires(vm != null); - public bool IsExpanded - { - get { return _isExpanded; } - set { RaiseAndSetIfChanged(ref _isExpanded, value); } + this._event = @event; + this._parentViewModel = vm; } - public virtual bool? IsEnabled + public override bool? IsEnabled { - get { return _isEnabled; } - set { RaiseAndSetIfChanged(ref _isEnabled, value); } + get => base.IsEnabled; + set + { + if (base.IsEnabled != value) + { + base.IsEnabled = value; + UpdateTracker(); + if (Parent != null && _updateParent) + { + try + { + Parent._updateChildren = false; + Parent.UpdateChecked(); + } + finally + { + Parent._updateChildren = true; + } + } + } + } } - public EventTreeNode Parent + private void UpdateTracker() { - get; + if (IsEnabled.GetValueOrDefault() && !_isRegistered) + { + _event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); + _isRegistered = true; + } } - public string Text + private void HandleEvent(object sender, RoutedEventArgs e) { - get; - private set; - } + if (!_isRegistered || IsEnabled == false) + return; + if (sender is IVisual v && DevTools.BelongsToDevTool(v)) + return; - internal void UpdateChecked() - { - IsEnabled = GetValue(); + var s = sender; + var handled = e.Handled; + var route = e.Route; - bool? GetValue() + Action handler = delegate { - if (Children == null) - return false; - bool? value = false; - for (int i = 0; i < Children.Count; i++) + if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e)) { - if (i == 0) - { - value = Children[i].IsEnabled; - continue; - } + _currentEvent = new FiredEvent(e, new EventChainLink(s, handled, route)); - if (value != Children[i].IsEnabled) - { - value = null; - break; - } + _parentViewModel.RecordedEvents.Add(_currentEvent); + + while (_parentViewModel.RecordedEvents.Count > 100) + _parentViewModel.RecordedEvents.RemoveAt(0); } + else + { + _currentEvent.AddToChain(new EventChainLink(s, handled, route)); + } + }; - return value; - } + if (!Dispatcher.UIThread.CheckAccess()) + Dispatcher.UIThread.Post(handler); + else + handler(); } } } diff --git a/src/Avalonia.Diagnostics/ViewModels/EventTreeNodeBase.cs b/src/Avalonia.Diagnostics/ViewModels/EventTreeNodeBase.cs new file mode 100644 index 0000000000..146a8cea8e --- /dev/null +++ b/src/Avalonia.Diagnostics/ViewModels/EventTreeNodeBase.cs @@ -0,0 +1,78 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Collections; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal abstract class EventTreeNodeBase : ViewModelBase + { + internal bool _updateChildren = true; + internal bool _updateParent = true; + private bool _isExpanded; + private bool? _isEnabled = false; + + public EventTreeNodeBase(EventTreeNodeBase parent, string text) + { + this.Parent = parent; + this.Text = text; + } + + public IAvaloniaReadOnlyList Children + { + get; + protected set; + } + + public bool IsExpanded + { + get { return _isExpanded; } + set { RaiseAndSetIfChanged(ref _isExpanded, value); } + } + + public virtual bool? IsEnabled + { + get { return _isEnabled; } + set { RaiseAndSetIfChanged(ref _isEnabled, value); } + } + + public EventTreeNodeBase Parent + { + get; + } + + public string Text + { + get; + private set; + } + + internal void UpdateChecked() + { + IsEnabled = GetValue(); + + bool? GetValue() + { + if (Children == null) + return false; + bool? value = false; + for (int i = 0; i < Children.Count; i++) + { + if (i == 0) + { + value = Children[i].IsEnabled; + continue; + } + + if (value != Children[i].IsEnabled) + { + value = null; + break; + } + } + + return value; + } + } + } +} diff --git a/src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs b/src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs index a9a6334689..a23677afc8 100644 --- a/src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs +++ b/src/Avalonia.Diagnostics/ViewModels/EventsViewModel.cs @@ -2,26 +2,22 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; -using System.Text; using System.Windows.Input; -using Avalonia.Collections; + using Avalonia.Controls; using Avalonia.Data.Converters; using Avalonia.Interactivity; using Avalonia.Media; -using Avalonia.Threading; namespace Avalonia.Diagnostics.ViewModels { internal class EventsViewModel : ViewModelBase { - private IControl _root; + private readonly IControl _root; private FiredEvent _selectedEvent; - private ICommand ClearCommand { get; } public EventsViewModel(IControl root) { @@ -29,27 +25,11 @@ namespace Avalonia.Diagnostics.ViewModels this.Nodes = RoutedEventRegistry.Instance.GetAllRegistered() .GroupBy(e => e.OwnerType) .OrderBy(e => e.Key.Name) - .Select(g => new ControlTreeNode(g.Key, g, this)) + .Select(g => new EventOwnerTreeNode(g.Key, g, this)) .ToArray(); } - private void ClearExecute() - { - Action action = delegate - { - RecordedEvents.Clear(); - }; - if (!Dispatcher.UIThread.CheckAccess()) - { - Dispatcher.UIThread.Post(action); - } - else - { - action(); - } - } - - public EventTreeNode[] Nodes { get; } + public EventTreeNodeBase[] Nodes { get; } public ObservableCollection RecordedEvents { get; } = new ObservableCollection(); @@ -58,6 +38,11 @@ namespace Avalonia.Diagnostics.ViewModels get => _selectedEvent; set => RaiseAndSetIfChanged(ref _selectedEvent, value); } + + private void Clear() + { + RecordedEvents.Clear(); + } } internal class BoolToBrushConverter : IValueConverter diff --git a/src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs b/src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs index 523525b634..049280c390 100644 --- a/src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs +++ b/src/Avalonia.Diagnostics/ViewModels/FiredEvent.cs @@ -3,6 +3,7 @@ using System; using System.Collections.ObjectModel; + using Avalonia.Diagnostics.Models; using Avalonia.Interactivity; @@ -11,17 +12,15 @@ namespace Avalonia.Diagnostics.ViewModels internal class FiredEvent : ViewModelBase { private RoutedEventArgs _eventArgs; + private EventChainLink _handledBy; - private ChainLink _handledBy; - private ChainLink _originator; - - public FiredEvent(RoutedEventArgs eventArgs, ChainLink originator) + public FiredEvent(RoutedEventArgs eventArgs, EventChainLink originator) { Contract.Requires(eventArgs != null); Contract.Requires(originator != null); this._eventArgs = eventArgs; - this._originator = originator; + this.Originator = originator; AddToChain(originator); } @@ -34,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels public bool IsHandled => HandledBy?.Handled == true; - public ObservableCollection EventChain { get; } = new ObservableCollection(); + public ObservableCollection EventChain { get; } = new ObservableCollection(); public string DisplayText { @@ -49,21 +48,9 @@ namespace Avalonia.Diagnostics.ViewModels } } - public ChainLink Originator - { - get { return _originator; } - set - { - if (_originator != value) - { - _originator = value; - RaisePropertyChanged(); - RaisePropertyChanged(nameof(DisplayText)); - } - } - } + public EventChainLink Originator { get; } - public ChainLink HandledBy + public EventChainLink HandledBy { get { return _handledBy; } set @@ -80,10 +67,10 @@ namespace Avalonia.Diagnostics.ViewModels public void AddToChain(object handler, bool handled, RoutingStrategies route) { - AddToChain(new ChainLink(handler, handled, route)); + AddToChain(new EventChainLink(handler, handled, route)); } - public void AddToChain(ChainLink link) + public void AddToChain(EventChainLink link) { EventChain.Add(link); if (HandledBy == null && link.Handled) diff --git a/src/Avalonia.Diagnostics/Views/EventsView.xaml b/src/Avalonia.Diagnostics/Views/EventsView.xaml index 79f78d2bba..a5be86b613 100644 --- a/src/Avalonia.Diagnostics/Views/EventsView.xaml +++ b/src/Avalonia.Diagnostics/Views/EventsView.xaml @@ -7,7 +7,7 @@ - @@ -46,7 +46,7 @@ - public class WicBitmapImpl : BitmapImpl { + private BitmapDecoder _decoder; + /// /// Initializes a new instance of the class. /// @@ -33,10 +35,10 @@ namespace Avalonia.Direct2D1.Media /// The stream to read the bitmap from. public WicBitmapImpl(Stream stream) { - using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad)) - { - WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); - } + // https://stackoverflow.com/questions/48982749/decoding-image-from-stream-using-wic/48982889#48982889 + _decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad); + + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, _decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); } /// @@ -72,7 +74,7 @@ namespace Avalonia.Direct2D1.Media UnmanagedMethods.CopyMemory( (l.Data.DataPointer + row * l.Stride), (data + row * stride), - (UIntPtr) l.Data.Pitch); + (UIntPtr)l.Data.Pitch); } } } @@ -92,6 +94,7 @@ namespace Avalonia.Direct2D1.Media public override void Dispose() { WicImpl.Dispose(); + _decoder?.Dispose(); } /// From 57058f19cda987c1ca199f3957f09bdd12930f00 Mon Sep 17 00:00:00 2001 From: wojciech krysiak Date: Sun, 23 Sep 2018 10:11:49 +0200 Subject: [PATCH 075/118] Fix SelectedItemsChanged event arguments in SelectingItemsControl --- .../Primitives/SelectingItemsControl.cs | 4 +-- .../SelectingItemsControlTests_Multiple.cs | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index bb39b005cc..d7db04f369 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -871,8 +871,8 @@ namespace Avalonia.Controls.Primitives RaisePropertyChanged(SelectedItemProperty, oldItem, item, BindingPriority.LocalValue); } - added = e.OldItems; - removed = e.NewItems; + added = e.NewItems; + removed = e.OldItems; break; } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 3c6b278e07..9ef7d567c8 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -541,6 +541,35 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.True(called); } + [Fact] + public void Replacing_SelectedItems_Should_Raise_SelectionChanged_With_CorrectItems() + { + var items = new[] { "foo", "bar", "baz" }; + + var target = new TestSelector + { + Items = items, + Template = Template(), + SelectedItem = "bar", + }; + + var called = false; + + target.SelectionChanged += (s, e) => + { + Assert.Equal(new[] { "foo",}, e.AddedItems.Cast()); + Assert.Equal(new[] { "bar" }, e.RemovedItems.Cast()); + called = true; + }; + + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + target.SelectedItems[0] = "foo"; + + Assert.True(called); + } + + private FuncControlTemplate Template() { return new FuncControlTemplate(control => From c5419a3c265ba6dc2c7f9222d2cc57b3549c2d33 Mon Sep 17 00:00:00 2001 From: ahopper Date: Mon, 24 Sep 2018 10:12:11 +0100 Subject: [PATCH 076/118] create only one identity matrix --- src/Avalonia.Visuals/Matrix.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 287ad1b3a2..cdaaa2b4b4 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -47,7 +47,7 @@ namespace Avalonia /// /// Returns the multiplicative identity matrix. /// - public static Matrix Identity => new Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); + public static Matrix Identity { get; } = new Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); /// /// Returns whether the matrix is the identity matrix. From d4c102998cae116d9d961c05d1da2d0a957b6db4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 26 Sep 2018 11:56:11 +0200 Subject: [PATCH 077/118] Updated portable.xml submodule. --- .../Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index 31ea8e6900..8e4700d4b2 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit 31ea8e6900d859a8d1bf45972954b075d2a59f30 +Subproject commit 8e4700d4b24935ed5400e5e0d6fce96b5b4a317a From 63928f62420df2f96f0cbee1366d112b367c5241 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 26 Sep 2018 13:09:27 +0100 Subject: [PATCH 078/118] [Image] Measure is triggered when source or stretch property is changed. --- src/Avalonia.Controls/Image.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 802b700a07..40203bab02 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -26,6 +26,7 @@ namespace Avalonia.Controls static Image() { AffectsRender(SourceProperty, StretchProperty); + AffectsMeasure(SourceProperty, StretchProperty); } /// From f1622f48026a4f4e825df98a128146bc4cbd75f7 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 28 Sep 2018 17:56:56 +0300 Subject: [PATCH 079/118] don't reset data template on attached to visual tree, but on attached to logical tree --- src/Avalonia.Controls/Presenters/ContentPresenter.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 8d703cfc1c..83d8616e90 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -197,13 +197,6 @@ namespace Avalonia.Controls.Presenters } } - /// - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - _dataTemplate = null; - } - /// /// Updates the control based on the control's . /// @@ -268,6 +261,7 @@ namespace Avalonia.Controls.Presenters protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); + _dataTemplate = null; _createdChild = false; InvalidateMeasure(); } From c311474d5b5a507e73b96c8000d96480c5f8088f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 28 Sep 2018 17:28:13 +0200 Subject: [PATCH 080/118] Don't await updates in render loop. The render loop should not be waiting for an update to occur on the UI thread before rendering a frame. Instead of awaiting the call, simply call `IDispatcher.Post` to fire-and-forget. Placed a guard around the update to make sure multiple updates don't get queued if an update doesn't complete in a single frame. Also don't call `IRenderLoopTask.Update` unless `IRenderLoopTask.NeedsUpdate == true`. Fixes #1920 --- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 31 +++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index d0d5b2250d..427c91658b 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -19,7 +19,8 @@ namespace Avalonia.Rendering private readonly IDispatcher _dispatcher; private List _items = new List(); private IRenderTimer _timer; - private int inTick; + private int _inTick; + private int _inUpdate; /// /// Initializes a new instance of the class. @@ -84,21 +85,35 @@ namespace Avalonia.Rendering } } - private async void TimerTick(TimeSpan time) + private void TimerTick(TimeSpan time) { - if (Interlocked.CompareExchange(ref inTick, 1, 0) == 0) + if (Interlocked.CompareExchange(ref _inTick, 1, 0) == 0) { try { - if (_items.Any(item => item.NeedsUpdate)) + if (_items.Any(item => item.NeedsUpdate) && + Interlocked.CompareExchange(ref _inUpdate, 1, 0) == 0) { - await _dispatcher.InvokeAsync(() => + System.Diagnostics.Debug.WriteLine("Posted update"); + _dispatcher.Post(() => { foreach (var i in _items) { - i.Update(time); + if (i.NeedsUpdate) + { + try + { + i.Update(time); + } + catch (Exception ex) + { + Logger.Error(LogArea.Visual, this, "Exception in render update: {Error}", ex); + } + } } - }, DispatcherPriority.Render).ConfigureAwait(false); + + Interlocked.Exchange(ref _inUpdate, 0); + }, DispatcherPriority.Render); } foreach (var i in _items) @@ -112,7 +127,7 @@ namespace Avalonia.Rendering } finally { - Interlocked.Exchange(ref inTick, 0); + Interlocked.Exchange(ref _inTick, 0); } } } From cabdb077a13769eaf1e0219129fad51955dcd259 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 28 Sep 2018 18:33:10 +0300 Subject: [PATCH 081/118] add failing test for #1930 PathMarkupParser does not end figure when trailing spaces (newlines) are present --- .../Media/PathMarkupParserTests.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs index 8dca52e6a7..5570378063 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs @@ -9,6 +9,8 @@ namespace Avalonia.Visuals.UnitTests.Media { using System.Globalization; using System.IO; + using Avalonia.Platform; + using Moq; public class PathMarkupParserTests { @@ -18,7 +20,7 @@ namespace Avalonia.Visuals.UnitTests.Media var pathGeometry = new PathGeometry(); using (var context = new PathGeometryContext(pathGeometry)) using (var parser = new PathMarkupParser(context)) - { + { parser.Parse("M10 10"); var figure = pathGeometry.Figures[0]; @@ -202,6 +204,25 @@ namespace Avalonia.Visuals.UnitTests.Media } } + [Theory] + [InlineData("M0 0L10 10")] + [InlineData("M0 0L10 10z")] + [InlineData("M0 0L10 10 \n ")] + [InlineData("M0 0L10 10z \n ")] + [InlineData("M0 0L10 10 ")] + [InlineData("M0 0L10 10z ")] + public void Should_AlwaysEndFigure(string pathData) + { + var context = new Mock(); + + using (var parser = new PathMarkupParser(context.Object)) + { + parser.Parse(pathData); + } + + context.Verify(v => v.EndFigure(It.IsAny()), Times.AtLeastOnce()); + } + [Theory] [InlineData("0 0")] [InlineData("j")] @@ -215,4 +236,4 @@ namespace Avalonia.Visuals.UnitTests.Media } } } -} \ No newline at end of file +} From 6844cc1450dc7046fdb59e85dc46f2c223195411 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 28 Sep 2018 18:33:57 +0300 Subject: [PATCH 082/118] fixes #1930 PathMarkupParser ends figure always --- src/Avalonia.Visuals/Media/PathMarkupParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs index 656526890a..8852883dcc 100644 --- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs +++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs @@ -107,7 +107,7 @@ namespace Avalonia.Media { if(!ReadCommand(ref span, out var command, out var relative)) { - return; + break; } bool initialCommand = true; @@ -565,4 +565,4 @@ namespace Avalonia.Media return true; } } -} \ No newline at end of file +} From 51f85cfac9a375912b8efd7bbf483b01d069d8d4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 28 Sep 2018 20:10:44 +0200 Subject: [PATCH 083/118] Removed debug code. --- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 427c91658b..e10e1c4006 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -94,7 +94,6 @@ namespace Avalonia.Rendering if (_items.Any(item => item.NeedsUpdate) && Interlocked.CompareExchange(ref _inUpdate, 1, 0) == 0) { - System.Diagnostics.Debug.WriteLine("Posted update"); _dispatcher.Post(() => { foreach (var i in _items) From 62a5b66228fbf5e08f0b63e3cd888f9c7e971165 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 28 Sep 2018 20:11:35 +0200 Subject: [PATCH 084/118] Fix failing tests. --- .../Rendering/RenderLoopTests.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs index 16c2d3ee18..e9aefc46e6 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs @@ -19,14 +19,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering bool inDispatcher = false; dispatcher.Setup( - d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render)) + d => d.Post(It.IsAny(), DispatcherPriority.Render)) .Callback((Action a, DispatcherPriority _) => { inDispatcher = true; a(); inDispatcher = false; - }) - .Returns(Task.CompletedTask); + }); var timer = new Mock(); @@ -71,14 +70,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering var dispatcher = new Mock(); bool inDispatcher = false; dispatcher.Setup( - d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render)) + d => d.Post(It.IsAny(), DispatcherPriority.Render)) .Callback((Action a, DispatcherPriority _) => { inDispatcher = true; a(); inDispatcher = false; - }) - .Returns(Task.CompletedTask); + }); var timer = new Mock(); var loop = new RenderLoop(timer.Object, dispatcher.Object); @@ -100,9 +98,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering { var dispatcher = new Mock(); dispatcher.Setup( - d => d.InvokeAsync(It.IsAny(), DispatcherPriority.Render)) - .Callback((Action a, DispatcherPriority _) => a()) - .Returns(Task.CompletedTask); + d => d.Post(It.IsAny(), DispatcherPriority.Render)) + .Callback((Action a, DispatcherPriority _) => a()); var timer = new Mock(); var loop = new RenderLoop(timer.Object, dispatcher.Object); From 4645ec1c4515f193237bbba0a0056c9d5c16ea75 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 28 Sep 2018 20:12:34 +0200 Subject: [PATCH 085/118] Use indexer for iterating items. --- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index e10e1c4006..df32d9eec1 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -96,13 +96,15 @@ namespace Avalonia.Rendering { _dispatcher.Post(() => { - foreach (var i in _items) + for (var i = 0; i < _items.Count; ++i) { - if (i.NeedsUpdate) + var item = _items[i]; + + if (item.NeedsUpdate) { try { - i.Update(time); + item.Update(time); } catch (Exception ex) { From 1df826281bbd61df064bcb334e5f968c2fbedb71 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 29 Sep 2018 16:26:32 +0200 Subject: [PATCH 086/118] Added failing test for #1932. --- .../Primitives/SelectingItemsControlTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 14e1b15ebc..bbe1d85acb 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -707,6 +707,26 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.True(target.SelectedIndex == 1); } + [Fact] + public void Binding_With_DelayedBinding_And_Initialization_Where_DataContext_Is_Root_Works() + { + // Test for #1932. + var root = new RootWithItems(); + + root.BeginInit(); + root.DataContext = root; + + var target = new ListBox(); + target.BeginInit(); + root.Child = target; + + DelayedBinding.Add(target, ItemsControl.ItemsProperty, new Binding(nameof(RootWithItems.Items))); + DelayedBinding.Add(target, ListBox.SelectedItemProperty, new Binding(nameof(RootWithItems.Selected))); + target.EndInit(); + root.EndInit(); + + Assert.Equal("b", target.SelectedItem); + } private FuncControlTemplate Template() { @@ -745,5 +765,11 @@ namespace Avalonia.Controls.UnitTests.Primitives public IList Items { get; set; } public Item SelectedItem { get; set; } } + + private class RootWithItems : TestRoot + { + public List Items { get; set; } = new List() { "a", "b", "c", "d", "e" }; + public string Selected { get; set; } = "b"; + } } } From d0b77ecca191fe630f01e8af8eb2945f9f89ce55 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 29 Sep 2018 16:30:21 +0200 Subject: [PATCH 087/118] Update before calling base.EndInit. Fixes #1932. --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index d7db04f369..f8440aac47 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -289,12 +289,12 @@ namespace Avalonia.Controls.Primitives /// public override void EndInit() { - base.EndInit(); - if (--_updateCount == 0) { UpdateFinished(); } + + base.EndInit(); } /// From add3e8109588d6d88662ef67ab263e7b1f3fd85c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 25 Sep 2018 18:14:53 +0300 Subject: [PATCH 088/118] Extracted interop bits from GTK3 backend to Avalonia.Base and IRuntimePlatform --- Avalonia.sln | 26 ++++++++++ src/Avalonia.Base/Avalonia.Base.csproj | 1 + .../Platform/Interop/IDynamicLibraryLoader.cs | 18 +++++++ .../Platform}/Interop/Utf8Buffer.cs | 4 +- src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs | 1 + src/Gtk/Avalonia.Gtk3/CursorFactory.cs | 1 + src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 1 + src/Gtk/Avalonia.Gtk3/Interop/GException.cs | 1 + src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 1 + src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs | 1 + src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs | 5 +- src/Gtk/Avalonia.Gtk3/Interop/Signal.cs | 1 + src/Gtk/Avalonia.Gtk3/SystemDialogs.cs | 1 + src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 1 + src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 1 + .../PlatformSupport}/DynLoader.cs | 52 +++++++++++-------- .../PlatformSupport/PlatformSupport.projitems | 1 + .../StandardRuntimePlatformServices.cs | 16 +++++- 18 files changed, 104 insertions(+), 29 deletions(-) create mode 100644 src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs rename src/{Gtk/Avalonia.Gtk3 => Avalonia.Base/Platform}/Interop/Utf8Buffer.cs (94%) rename src/{Gtk/Avalonia.Gtk3/Interop => Shared/PlatformSupport}/DynLoader.cs (73%) diff --git a/Avalonia.sln b/Avalonia.sln index 76d8a14524..f71a94888d 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -186,6 +186,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Designer.HostApp.N EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", "tests\Avalonia.Skia.UnitTests\Avalonia.Skia.UnitTests.csproj", "{E1240B49-7B4B-4371-A00E-068778C5CF0B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.OpenGL", "src\Avalonia.OpenGL\Avalonia.OpenGL.csproj", "{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -1663,6 +1665,30 @@ Global {E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhone.Build.0 = Release|Any CPU {E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhone.Build.0 = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhone.Build.0 = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|Any CPU.Build.0 = Release|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhone.ActiveCfg = Release|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhone.Build.0 = Release|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index bc068857ff..3d13169aa8 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -3,6 +3,7 @@ netstandard2.0 Avalonia.Base Avalonia + True diff --git a/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs b/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs new file mode 100644 index 0000000000..8124ce6bc4 --- /dev/null +++ b/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs @@ -0,0 +1,18 @@ +using System; + +namespace Avalonia.Platform.Interop +{ + public interface IDynamicLibraryLoader + { + IntPtr LoadLibrary(string dll); + IntPtr GetProcAddress(IntPtr dll, string proc, bool optional); + } + + public class DynamicLibraryLoaderException : Exception + { + public DynamicLibraryLoaderException(string message) : base(message) + { + + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs b/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs similarity index 94% rename from src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs rename to src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs index c932aac950..dc7a3e5634 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs +++ b/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs @@ -2,9 +2,9 @@ using System.Runtime.InteropServices; using System.Text; -namespace Avalonia.Gtk3.Interop +namespace Avalonia.Platform.Interop { - class Utf8Buffer : SafeHandle + public class Utf8Buffer : SafeHandle { private GCHandle _gchandle; private byte[] _data; diff --git a/src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs b/src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs index a3e7590cf5..a860673732 100644 --- a/src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia.Gtk3.Interop; using Avalonia.Input.Platform; +using Avalonia.Platform.Interop; namespace Avalonia.Gtk3 { diff --git a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs index d6a3c1f260..a28b1cbb1a 100644 --- a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs +++ b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Avalonia.Gtk3.Interop; using Avalonia.Input; using Avalonia.Platform; +using Avalonia.Platform.Interop; using CursorType = Avalonia.Gtk3.GdkCursorType; namespace Avalonia.Gtk3 { diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index bbb6a01c05..1ab0c7576c 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -8,6 +8,7 @@ using Avalonia.Gtk3.Interop; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Platform; +using Avalonia.Platform.Interop; using Avalonia.Rendering; using Avalonia.Threading; diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GException.cs b/src/Gtk/Avalonia.Gtk3/Interop/GException.cs index b0c171089e..5ff6cbe8ed 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GException.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GException.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Avalonia.Platform.Interop; namespace Avalonia.Gtk3.Interop { diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 914d0feafc..98a73a98a9 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -2,6 +2,7 @@ using System; using System.Runtime.InteropServices; using Avalonia.Controls; +using Avalonia.Platform.Interop; using gdouble = System.Double; using gint = System.Int32; using gint16 = System.Int16; diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs b/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs index 989a96a4f0..322b9bdfae 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs @@ -2,6 +2,7 @@ using System.IO; using System.Runtime.InteropServices; using Avalonia.Platform; +using Avalonia.Platform.Interop; namespace Avalonia.Gtk3.Interop { diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs index 57c8ac7c3a..c39cb9e394 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using Avalonia.Platform; +using Avalonia.Platform.Interop; namespace Avalonia.Gtk3.Interop { @@ -82,7 +83,7 @@ namespace Avalonia.Gtk3.Interop } } - static IntPtr LoadDll(IDynLoader loader, GtkDll dll) + static IntPtr LoadDll(IDynamicLibraryLoader loader, GtkDll dll) { var exceptions = new List(); @@ -118,7 +119,7 @@ namespace Avalonia.Gtk3.Interop public static void Resolve(string basePath = null) { - var loader = Platform.Value == OperatingSystemType.WinNT ? (IDynLoader)new Win32Loader() : new UnixLoader(); + var loader = AvaloniaLocator.Current.GetService(); var dlls = Enum.GetValues(typeof(GtkDll)).Cast().ToDictionary(x => x, x => LoadDll(loader, x)); diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs index bc55558551..8eaca93152 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Avalonia.Platform.Interop; namespace Avalonia.Gtk3.Interop { diff --git a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs index d4bd878d53..5e6615f92d 100644 --- a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs +++ b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs @@ -7,6 +7,7 @@ using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Gtk3.Interop; using Avalonia.Platform; +using Avalonia.Platform.Interop; namespace Avalonia.Gtk3 { diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 67ddcf1e5f..8cc7974c3e 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -8,6 +8,7 @@ using Avalonia.Gtk3.Interop; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; +using Avalonia.Platform.Interop; using Avalonia.Rendering; using Avalonia.Threading; diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index a0b754c229..34950ca8b8 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Gtk3.Interop; using Avalonia.Platform; +using Avalonia.Platform.Interop; namespace Avalonia.Gtk3 { diff --git a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs b/src/Shared/PlatformSupport/DynLoader.cs similarity index 73% rename from src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs rename to src/Shared/PlatformSupport/DynLoader.cs index 112e00c879..5884558baa 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs +++ b/src/Shared/PlatformSupport/DynLoader.cs @@ -1,22 +1,12 @@ -using System; +using System; using System.Runtime.InteropServices; +using Avalonia.Platform; +using Avalonia.Platform.Interop; -/* - * Source code imported from https://github.com/kekekeks/evhttp-sharp - * Source is provided under MIT license for Avalonia project and derived works - */ - - -namespace Avalonia.Gtk3.Interop +namespace Avalonia.Shared.PlatformSupport { - internal interface IDynLoader - { - IntPtr LoadLibrary(string dll); - IntPtr GetProcAddress(IntPtr dll, string proc, bool optional); - - } - - class UnixLoader : IDynLoader +#if !__IOS__ + class UnixLoader : IDynamicLibraryLoader { // ReSharper disable InconsistentNaming static class LinuxImports @@ -37,6 +27,7 @@ namespace Avalonia.Gtk3.Interop DlError = dlerror; } } + static class OsXImports { @@ -86,7 +77,7 @@ namespace Avalonia.Gtk3.Interop { var handle = DlOpen(dll, 1); if (handle == IntPtr.Zero) - throw new NativeException(DlErrorString()); + throw new DynamicLibraryLoaderException(DlErrorString()); return handle; } @@ -94,12 +85,12 @@ namespace Avalonia.Gtk3.Interop { var ptr = DlSym(dll, proc); if (ptr == IntPtr.Zero && !optional) - throw new NativeException(DlErrorString()); + throw new DynamicLibraryLoaderException(DlErrorString()); return ptr; } } - internal class Win32Loader : IDynLoader + internal class Win32Loader : IDynamicLibraryLoader { [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); @@ -107,22 +98,37 @@ namespace Avalonia.Gtk3.Interop [DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr LoadLibrary(string lpszLib); - IntPtr IDynLoader.LoadLibrary(string dll) + IntPtr IDynamicLibraryLoader.LoadLibrary(string dll) { var handle = LoadLibrary(dll); if (handle != IntPtr.Zero) return handle; var err = Marshal.GetLastWin32Error(); - throw new NativeException("Error loading " + dll + " error " + err); + throw new DynamicLibraryLoaderException("Error loading " + dll + " error " + err); } - IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc, bool optional) + IntPtr IDynamicLibraryLoader.GetProcAddress(IntPtr dll, string proc, bool optional) { var ptr = GetProcAddress(dll, proc); if (ptr == IntPtr.Zero && !optional) - throw new NativeException("Error " + Marshal.GetLastWin32Error()); + throw new DynamicLibraryLoaderException("Error " + Marshal.GetLastWin32Error()); return ptr; } } + +#else + internal class IOSLoader : IDynamicLibraryLoader + { + IntPtr IDynamicLibraryLoader.LoadLibrary(string dll) + { + throw new PlatformNotSupportedException(); + } + + IntPtr IDynamicLibraryLoader.GetProcAddress(IntPtr dll, string proc, bool optional) + { + throw new PlatformNotSupportedException(); + } + } +#endif } diff --git a/src/Shared/PlatformSupport/PlatformSupport.projitems b/src/Shared/PlatformSupport/PlatformSupport.projitems index a9b6a8fa2a..34515a0912 100644 --- a/src/Shared/PlatformSupport/PlatformSupport.projitems +++ b/src/Shared/PlatformSupport/PlatformSupport.projitems @@ -10,6 +10,7 @@ + diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatformServices.cs b/src/Shared/PlatformSupport/StandardRuntimePlatformServices.cs index 9ccf097ffd..ad6535e91c 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatformServices.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatformServices.cs @@ -1,5 +1,7 @@ using System.Reflection; +using System.Runtime.InteropServices; using Avalonia.Platform; +using Avalonia.Platform.Interop; namespace Avalonia.Shared.PlatformSupport { @@ -7,9 +9,19 @@ namespace Avalonia.Shared.PlatformSupport { public static void Register(Assembly assembly = null) { + var standardPlatform = new StandardRuntimePlatform(); AvaloniaLocator.CurrentMutable - .Bind().ToSingleton() - .Bind().ToConstant(new AssetLoader(assembly)); + .Bind().ToConstant(standardPlatform) + .Bind().ToConstant(new AssetLoader(assembly)) + .Bind().ToConstant( +#if __IOS__ + new IOSLoader() +#else + standardPlatform.GetRuntimeInfo().OperatingSystem == OperatingSystemType.WinNT + ? (IDynamicLibraryLoader)new Win32Loader() + : new UnixLoader() +#endif + ); } } } From 4c9c0c0c40b451c22199b53747e573e5a756f1b1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 30 Sep 2018 18:37:41 +0300 Subject: [PATCH 089/118] OpenGL/EGL rendering support in Skia/GTK backends --- src/Avalonia.OpenGL/Avalonia.OpenGL.csproj | 12 + src/Avalonia.OpenGL/EglConsts.cs | 178 ++++ src/Avalonia.OpenGL/EglDisplay.cs | 181 ++++ src/Avalonia.OpenGL/EglGlPlatformFeature.cs | 32 + src/Avalonia.OpenGL/EglGlPlatformSurface.cs | 82 ++ src/Avalonia.OpenGL/EglInterface.cs | 91 ++ src/Avalonia.OpenGL/EntryPointAttribute.cs | 14 + src/Avalonia.OpenGL/GlConsts.cs | 789 ++++++++++++++++++ src/Avalonia.OpenGL/GlInterface.cs | 50 ++ src/Avalonia.OpenGL/GlInterfaceBase.cs | 27 + src/Avalonia.OpenGL/IGlDisplay.cs | 31 + src/Avalonia.OpenGL/IGlPlatformSurface.cs | 22 + .../IWindowingPlatformGlFeature.cs | 7 + src/Avalonia.OpenGL/OpenGlException.cs | 12 + src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 1 + src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 3 +- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 29 +- src/Skia/Avalonia.Skia/Avalonia.Skia.csproj | 1 + src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 12 +- src/Skia/Avalonia.Skia/GlRenderTarget.cs | 62 ++ .../Avalonia.Skia/PlatformRenderInterface.cs | 27 +- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 20 +- 22 files changed, 1666 insertions(+), 17 deletions(-) create mode 100644 src/Avalonia.OpenGL/Avalonia.OpenGL.csproj create mode 100644 src/Avalonia.OpenGL/EglConsts.cs create mode 100644 src/Avalonia.OpenGL/EglDisplay.cs create mode 100644 src/Avalonia.OpenGL/EglGlPlatformFeature.cs create mode 100644 src/Avalonia.OpenGL/EglGlPlatformSurface.cs create mode 100644 src/Avalonia.OpenGL/EglInterface.cs create mode 100644 src/Avalonia.OpenGL/EntryPointAttribute.cs create mode 100644 src/Avalonia.OpenGL/GlConsts.cs create mode 100644 src/Avalonia.OpenGL/GlInterface.cs create mode 100644 src/Avalonia.OpenGL/GlInterfaceBase.cs create mode 100644 src/Avalonia.OpenGL/IGlDisplay.cs create mode 100644 src/Avalonia.OpenGL/IGlPlatformSurface.cs create mode 100644 src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs create mode 100644 src/Avalonia.OpenGL/OpenGlException.cs create mode 100644 src/Skia/Avalonia.Skia/GlRenderTarget.cs diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj new file mode 100644 index 0000000000..310d0c8dba --- /dev/null +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + + + + + + + + diff --git a/src/Avalonia.OpenGL/EglConsts.cs b/src/Avalonia.OpenGL/EglConsts.cs new file mode 100644 index 0000000000..bcb90d7382 --- /dev/null +++ b/src/Avalonia.OpenGL/EglConsts.cs @@ -0,0 +1,178 @@ +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +namespace Avalonia.OpenGL +{ + public static class EglConsts + { + public const int EGL_ALPHA_SIZE = 0x3021; + public const int EGL_BAD_ACCESS = 0x3002; + public const int EGL_BAD_ALLOC = 0x3003; + public const int EGL_BAD_ATTRIBUTE = 0x3004; + public const int EGL_BAD_CONFIG = 0x3005; + public const int EGL_BAD_CONTEXT = 0x3006; + public const int EGL_BAD_CURRENT_SURFACE = 0x3007; + public const int EGL_BAD_DISPLAY = 0x3008; + public const int EGL_BAD_MATCH = 0x3009; + public const int EGL_BAD_NATIVE_PIXMAP = 0x300A; + public const int EGL_BAD_NATIVE_WINDOW = 0x300B; + public const int EGL_BAD_PARAMETER = 0x300C; + public const int EGL_BAD_SURFACE = 0x300D; + public const int EGL_BLUE_SIZE = 0x3022; + public const int EGL_BUFFER_SIZE = 0x3020; + public const int EGL_CONFIG_CAVEAT = 0x3027; + public const int EGL_CONFIG_ID = 0x3028; + public const int EGL_CORE_NATIVE_ENGINE = 0x305B; + public const int EGL_DEPTH_SIZE = 0x3025; + public const int EGL_DONT_CARE = -1; + public const int EGL_DRAW = 0x3059; + public const int EGL_EXTENSIONS = 0x3055; + public const int EGL_FALSE = 0; + public const int EGL_GREEN_SIZE = 0x3023; + public const int EGL_HEIGHT = 0x3056; + public const int EGL_LARGEST_PBUFFER = 0x3058; + public const int EGL_LEVEL = 0x3029; + public const int EGL_MAX_PBUFFER_HEIGHT = 0x302A; + public const int EGL_MAX_PBUFFER_PIXELS = 0x302B; + public const int EGL_MAX_PBUFFER_WIDTH = 0x302C; + public const int EGL_NATIVE_RENDERABLE = 0x302D; + public const int EGL_NATIVE_VISUAL_ID = 0x302E; + public const int EGL_NATIVE_VISUAL_TYPE = 0x302F; + public const int EGL_NONE = 0x3038; + public const int EGL_NON_CONFORMANT_CONFIG = 0x3051; + public const int EGL_NOT_INITIALIZED = 0x3001; + public const int EGL_NO_CONTEXT = 0; + public const int EGL_NO_DISPLAY = 0; + public const int EGL_NO_SURFACE = 0; + public const int EGL_PBUFFER_BIT = 0x0001; + public const int EGL_PIXMAP_BIT = 0x0002; + public const int EGL_READ = 0x305A; + public const int EGL_RED_SIZE = 0x3024; + public const int EGL_SAMPLES = 0x3031; + public const int EGL_SAMPLE_BUFFERS = 0x3032; + public const int EGL_SLOW_CONFIG = 0x3050; + public const int EGL_STENCIL_SIZE = 0x3026; + public const int EGL_SUCCESS = 0x3000; + public const int EGL_SURFACE_TYPE = 0x3033; + public const int EGL_TRANSPARENT_BLUE_VALUE = 0x3035; + public const int EGL_TRANSPARENT_GREEN_VALUE = 0x3036; + public const int EGL_TRANSPARENT_RED_VALUE = 0x3037; + public const int EGL_TRANSPARENT_RGB = 0x3052; + public const int EGL_TRANSPARENT_TYPE = 0x3034; + public const int EGL_TRUE = 1; + public const int EGL_VENDOR = 0x3053; + public const int EGL_VERSION = 0x3054; + public const int EGL_WIDTH = 0x3057; + public const int EGL_WINDOW_BIT = 0x0004; + + public const int EGL_BACK_BUFFER = 0x3084; + public const int EGL_BIND_TO_TEXTURE_RGB = 0x3039; + public const int EGL_BIND_TO_TEXTURE_RGBA = 0x303A; + public const int EGL_CONTEXT_LOST = 0x300E; + public const int EGL_MIN_SWAP_INTERVAL = 0x303B; + public const int EGL_MAX_SWAP_INTERVAL = 0x303C; + public const int EGL_MIPMAP_TEXTURE = 0x3082; + public const int EGL_MIPMAP_LEVEL = 0x3083; + public const int EGL_NO_TEXTURE = 0x305C; + public const int EGL_TEXTURE_2D = 0x305F; + public const int EGL_TEXTURE_FORMAT = 0x3080; + public const int EGL_TEXTURE_RGB = 0x305D; + public const int EGL_TEXTURE_RGBA = 0x305E; + public const int EGL_TEXTURE_TARGET = 0x3081; + + public const int EGL_ALPHA_FORMAT = 0x3088; + public const int EGL_ALPHA_FORMAT_NONPRE = 0x308B; + public const int EGL_ALPHA_FORMAT_PRE = 0x308C; + public const int EGL_ALPHA_MASK_SIZE = 0x303E; + public const int EGL_BUFFER_PRESERVED = 0x3094; + public const int EGL_BUFFER_DESTROYED = 0x3095; + public const int EGL_CLIENT_APIS = 0x308D; + public const int EGL_COLORSPACE = 0x3087; + public const int EGL_COLORSPACE_sRGB = 0x3089; + public const int EGL_COLORSPACE_LINEAR = 0x308A; + public const int EGL_COLOR_BUFFER_TYPE = 0x303F; + public const int EGL_CONTEXT_CLIENT_TYPE = 0x3097; + public const int EGL_DISPLAY_SCALING = 10000; + public const int EGL_HORIZONTAL_RESOLUTION = 0x3090; + public const int EGL_LUMINANCE_BUFFER = 0x308F; + public const int EGL_LUMINANCE_SIZE = 0x303D; + public const int EGL_OPENGL_ES_BIT = 0x0001; + public const int EGL_OPENVG_BIT = 0x0002; + public const int EGL_OPENGL_ES_API = 0x30A0; + public const int EGL_OPENVG_API = 0x30A1; + public const int EGL_OPENVG_IMAGE = 0x3096; + public const int EGL_PIXEL_ASPECT_RATIO = 0x3092; + public const int EGL_RENDERABLE_TYPE = 0x3040; + public const int EGL_RENDER_BUFFER = 0x3086; + public const int EGL_RGB_BUFFER = 0x308E; + public const int EGL_SINGLE_BUFFER = 0x3085; + public const int EGL_SWAP_BEHAVIOR = 0x3093; + public const int EGL_UNKNOWN = -1; + public const int EGL_VERTICAL_RESOLUTION = 0x3091; + public const int EGL_CONFORMANT = 0x3042; + public const int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + public const int EGL_MATCH_NATIVE_PIXMAP = 0x3041; + public const int EGL_OPENGL_ES2_BIT = 0x0004; + public const int EGL_VG_ALPHA_FORMAT = 0x3088; + public const int EGL_VG_ALPHA_FORMAT_NONPRE = 0x308B; + public const int EGL_VG_ALPHA_FORMAT_PRE = 0x308C; + public const int EGL_VG_ALPHA_FORMAT_PRE_BIT = 0x0040; + public const int EGL_VG_COLORSPACE = 0x3087; + public const int EGL_VG_COLORSPACE_sRGB = 0x3089; + public const int EGL_VG_COLORSPACE_LINEAR = 0x308A; + public const int EGL_VG_COLORSPACE_LINEAR_BIT = 0x0020; + public const int EGL_DEFAULT_DISPLAY = 0; + public const int EGL_MULTISAMPLE_RESOLVE_BOX_BIT = 0x0200; + public const int EGL_MULTISAMPLE_RESOLVE = 0x3099; + public const int EGL_MULTISAMPLE_RESOLVE_DEFAULT = 0x309A; + public const int EGL_MULTISAMPLE_RESOLVE_BOX = 0x309B; + public const int EGL_OPENGL_API = 0x30A2; + public const int EGL_OPENGL_BIT = 0x0008; + public const int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400; + public const int EGL_CONTEXT_MAJOR_VERSION = 0x3098; + public const int EGL_CONTEXT_MINOR_VERSION = 0x30FB; + public const int EGL_CONTEXT_OPENGL_PROFILE_MASK = 0x30FD; + public const int EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY = 0x31BD; + public const int EGL_NO_RESET_NOTIFICATION = 0x31BE; + public const int EGL_LOSE_CONTEXT_ON_RESET = 0x31BF; + public const int EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT = 0x00000001; + public const int EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT = 0x00000002; + public const int EGL_CONTEXT_OPENGL_DEBUG = 0x31B0; + public const int EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE = 0x31B1; + public const int EGL_CONTEXT_OPENGL_ROBUST_ACCESS = 0x31B2; + public const int EGL_OPENGL_ES3_BIT = 0x00000040; + public const int EGL_CL_EVENT_HANDLE = 0x309C; + public const int EGL_SYNC_CL_EVENT = 0x30FE; + public const int EGL_SYNC_CL_EVENT_COMPLETE = 0x30FF; + public const int EGL_SYNC_PRIOR_COMMANDS_COMPLETE = 0x30F0; + public const int EGL_SYNC_TYPE = 0x30F7; + public const int EGL_SYNC_STATUS = 0x30F1; + public const int EGL_SYNC_CONDITION = 0x30F8; + public const int EGL_SIGNALED = 0x30F2; + public const int EGL_UNSIGNALED = 0x30F3; + + public const int EGL_SYNC_FLUSH_COMMANDS_BIT = 0x0001; + + public const int EGL_TIMEOUT_EXPIRED = 0x30F5; + public const int EGL_CONDITION_SATISFIED = 0x30F6; + public const int EGL_NO_SYNC = 0; + public const int EGL_SYNC_FENCE = 0x30F9; + public const int EGL_GL_COLORSPACE = 0x309D; + public const int EGL_GL_COLORSPACE_SRGB = 0x3089; + public const int EGL_GL_COLORSPACE_LINEAR = 0x308A; + public const int EGL_GL_RENDERBUFFER = 0x30B9; + public const int EGL_GL_TEXTURE_2D = 0x30B1; + public const int EGL_GL_TEXTURE_LEVEL = 0x30BC; + public const int EGL_GL_TEXTURE_3D = 0x30B2; + public const int EGL_GL_TEXTURE_ZOFFSET = 0x30BD; + public const int EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x30B3; + public const int EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x30B4; + public const int EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x30B5; + public const int EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x30B6; + public const int EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x30B7; + public const int EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x30B8; + public const int EGL_IMAGE_PRESERVED = 0x30D2; + public const int EGL_NO_IMAGE = 0; + + + } +} diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs new file mode 100644 index 0000000000..852e2cae3c --- /dev/null +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -0,0 +1,181 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using Avalonia.Platform.Interop; +using static Avalonia.OpenGL.EglConsts; +namespace Avalonia.OpenGL +{ + public class EglDisplay : IGlDisplay + { + private readonly EglInterface _egl; + private readonly IntPtr _display; + private readonly IntPtr _config; + private readonly int[] _contextAttributes; + + public EglDisplay(EglInterface egl) + { + _egl = egl; + _display = _egl.GetDisplay(IntPtr.Zero); + if(_display == IntPtr.Zero) + throw new OpenGlException("eglGetDisplay failed"); + if (!_egl.Initialize(_display, out var major, out var minor)) + throw new OpenGlException("eglInitialize failed"); + + foreach (var cfg in new[] + { + new + { + Attributes = new[] {EGL_NONE}, + Api = EGL_OPENGL_API, + RenderableTypeBit = EGL_OPENGL_BIT, + Type = GlDisplayType.OpenGL2 + }, + new + { + Attributes = new[] + { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }, + Api = EGL_OPENGL_ES_API, + RenderableTypeBit = EGL_OPENGL_ES2_BIT, + Type = GlDisplayType.OpenGLES2 + } + }) + { + if (!_egl.BindApi(cfg.Api)) + continue; + + var attribs = new[] + { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE + }; + if (!_egl.ChooseConfig(_display, attribs, out _config, 1, out int numConfigs)) + continue; + if (numConfigs == 0) + continue; + _contextAttributes = cfg.Attributes; + Type = cfg.Type; + } + + if (_contextAttributes == null) + throw new OpenGlException("No suitable EGL config was found"); + + GlInterface = new GlInterface(proc => + { + using (var u = new Utf8Buffer(proc)) + return _egl.GetProcAddress(u); + }); + } + + public EglDisplay() : this(new EglInterface()) + { + + } + + public GlDisplayType Type { get; } + public GlInterface GlInterface { get; } + public IGlContext CreateContext(IGlContext share) + { + var shareCtx = (EglContext)share; + var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes); + if (ctx == IntPtr.Zero) + throw new OpenGlException("eglCreateContext failed"); + var surf = _egl.CreatePBufferSurface(_display, _config, new[] + { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_NONE + }); + if (surf == IntPtr.Zero) + throw new OpenGlException("eglCreatePbufferSurface failed"); + var rv = new EglContext(this, ctx, surf); + rv.MakeCurrent(null); + return rv; + } + + public void ClearContext() + { + if (!_egl.MakeCurrent(_display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)) + throw new OpenGlException("eglMakeCurrent failed"); + } + + public IGlSurface CreateWindowSurface(IntPtr window) + { + var s = _egl.CreateWindowSurface(_display, _config, window, new[] {EGL_NONE, EGL_NONE}); + if (s == IntPtr.Zero) + throw new OpenGlException("eglCreateWindowSurface failed"); + return new EglSurface(this, s); + } + + public int SampleCount + { + get + { + _egl.GetConfigAttrib(_display, _config, EGL_SAMPLES, out var rv); + return rv; + } + } + + public int StencilSize + { + get + { + _egl.GetConfigAttrib(_display, _config, EGL_STENCIL_SIZE, out var rv); + return rv; + } + } + + class EglSurface : SafeHandle, IGlSurface + { + private readonly EglDisplay _display; + + public EglSurface(EglDisplay display, IntPtr surface) : base(surface, true) + { + _display = display; + } + + protected override bool ReleaseHandle() + { + _display._egl.DestroySurface(_display._display, handle); + return true; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + public IGlDisplay Display => _display; + public void SwapBuffers() => _display._egl.SwapBuffers(_display._display, handle); + } + + class EglContext : IGlContext + { + private readonly EglDisplay _disp; + + public EglContext(EglDisplay display, IntPtr ctx, IntPtr offscreenSurface) + { + _disp = display; + Context = ctx; + OffscreenSurface = offscreenSurface; + } + + public IntPtr Context { get; } + public IntPtr OffscreenSurface { get; } + public IGlDisplay Display => _disp; + + public void MakeCurrent(IGlSurface surface) + { + var surf = ((EglSurface)surface)?.DangerousGetHandle() ?? OffscreenSurface; + if (!_disp._egl.MakeCurrent(_disp._display, surf, surf, Context)) + throw new OpenGlException("eglMakeCurrent failed"); + } + } + } + + +} diff --git a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs new file mode 100644 index 0000000000..f974a3d656 --- /dev/null +++ b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs @@ -0,0 +1,32 @@ +using System; +using Avalonia.Logging; + +namespace Avalonia.OpenGL +{ + + public class EglGlPlatformFeature : IWindowingPlatformGlFeature + { + public IGlDisplay Display { get; set; } + public IGlContext ImmediateContext { get; set; } + public IGlContext DeferredContext { get; set; } + + public static void TryInitialize() + { + try + { + var disp = new EglDisplay(); + var ctx = disp.CreateContext(null); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new EglGlPlatformFeature + { + Display = disp, + ImmediateContext = ctx, + DeferredContext = disp.CreateContext(ctx) + }); + } + catch(Exception e) + { + Logger.Error("OpenGL", null, "Unable to initialize EGL-based rendering: {0}", e); + } + } + } +} diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs new file mode 100644 index 0000000000..8be9e54d33 --- /dev/null +++ b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs @@ -0,0 +1,82 @@ +using System; + +namespace Avalonia.OpenGL +{ + public interface IEglWindowGlPlatformSurfaceInfo + { + IntPtr Handle { get; } + int PixelWidth { get; } + int PixelHeight { get; } + Vector Dpi { get; } + + } + + public class EglGlPlatformSurface : IGlPlatformSurface + { + private readonly EglDisplay _display; + private readonly IGlContext _context; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + + + public EglGlPlatformSurface(EglDisplay display, IGlContext context, IEglWindowGlPlatformSurfaceInfo info) + { + _display = display; + _context = context; + _info = info; + } + + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + { + var glSurface = _display.CreateWindowSurface(_info.Handle); + return new RenderTarget(_context, glSurface, _info); + } + + class RenderTarget : IGlPlatformSurfaceRenderTarget + { + private readonly IGlContext _context; + private readonly IGlSurface _glSurface; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + + public RenderTarget(IGlContext context, IGlSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) + { + _context = context; + _glSurface = glSurface; + _info = info; + } + + public void Dispose() => _glSurface.Dispose(); + + public IGlPlatformSurfaceRenderingSession BeginDraw() + { + _context.MakeCurrent(_glSurface); + return new Session(_context, _glSurface, _info); + } + + class Session : IGlPlatformSurfaceRenderingSession + { + private readonly IGlContext _context; + private readonly IGlSurface _glSurface; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + + public Session(IGlContext context, IGlSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) + { + _context = context; + _glSurface = glSurface; + _info = info; + } + + public void Dispose() + { + _context.Display.GlInterface.Flush(); + _glSurface.SwapBuffers(); + _context.Display.ClearContext(); + } + + public IGlDisplay Display => _context.Display; + public int PixelWidth => _info.PixelWidth; + public int PixelHeight => _info.PixelHeight; + public Vector Dpi => _info.Dpi; + } + } + } +} diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs new file mode 100644 index 0000000000..f80d3a7b44 --- /dev/null +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -0,0 +1,91 @@ +using System; +using Avalonia.Platform; +using Avalonia.Platform.Interop; +using static Avalonia.OpenGL.EglConsts; + +namespace Avalonia.OpenGL +{ + public class EglInterface : GlInterfaceBase + { + public EglInterface() : base(Load()) + { + + } + + public EglInterface(string library) : base(Load(library)) + { + } + + 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) + return Load("libEGL.dll"); + throw new PlatformNotSupportedException(); + } + + static Func Load(string library) + { + var dyn = AvaloniaLocator.Current.GetService(); + var lib = dyn.LoadLibrary(library); + return s => dyn.GetProcAddress(lib, s, false); + } + + + // ReSharper disable UnassignedGetOnlyAutoProperty + public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay); + [EntryPoint("eglGetDisplay")] + public EglGetDisplay GetDisplay { get; } + + public delegate bool EglInitialize(IntPtr display, out int major, out int minor); + [EntryPoint("eglInitialize")] + public EglInitialize Initialize { get; } + + public delegate IntPtr EglGetProcAddress(Utf8Buffer proc); + [EntryPoint("eglGetProcAddress")] + public EglGetProcAddress GetProcAddress { get; } + + public delegate bool EglBindApi(int api); + [EntryPoint("eglBindAPI")] + public EglBindApi BindApi { get; } + + public delegate bool EglChooseConfig(IntPtr display, int[] attribs, + out IntPtr surfaceConfig, int numConfigs, out int choosenConfig); + [EntryPoint("eglChooseConfig")] + public EglChooseConfig ChooseConfig { get; } + + public delegate IntPtr EglCreateContext(IntPtr display, IntPtr config, + IntPtr share, int[] attrs); + [EntryPoint("eglCreateContext")] + public EglCreateContext CreateContext { get; } + + public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs); + [EntryPoint("eglCreatePbufferSurface")] + public EglCreatePBufferSurface CreatePBufferSurface { get; } + + public delegate bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); + [EntryPoint("eglMakeCurrent")] + public EglMakeCurrent MakeCurrent { get; } + + public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface); + [EntryPoint("eglDestroySurface")] + public EglDisplaySurfaceVoidDelegate DestroySurface { get; } + + [EntryPoint("eglSwapBuffers")] + public EglDisplaySurfaceVoidDelegate SwapBuffers { get; } + + public delegate IntPtr + EglCreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs); + [EntryPoint("eglCreateWindowSurface")] + public EglCreateWindowSurface CreateWindowSurface { get; } + + public delegate bool EglGetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv); + [EntryPoint("eglGetConfigAttrib")] + public EglGetConfigAttrib GetConfigAttrib { get; } + + // ReSharper restore UnassignedGetOnlyAutoProperty + + } +} diff --git a/src/Avalonia.OpenGL/EntryPointAttribute.cs b/src/Avalonia.OpenGL/EntryPointAttribute.cs new file mode 100644 index 0000000000..cf8e3d4b09 --- /dev/null +++ b/src/Avalonia.OpenGL/EntryPointAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace Avalonia.OpenGL +{ + class EntryPointAttribute : Attribute + { + public string EntryPoint { get; } + + public EntryPointAttribute(string entryPoint) + { + EntryPoint = entryPoint; + } + } +} diff --git a/src/Avalonia.OpenGL/GlConsts.cs b/src/Avalonia.OpenGL/GlConsts.cs new file mode 100644 index 0000000000..3084a6f958 --- /dev/null +++ b/src/Avalonia.OpenGL/GlConsts.cs @@ -0,0 +1,789 @@ +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +namespace Avalonia.OpenGL +{ + public static class GlConsts + { + public const int GL_BYTE = 0x1400; + public const int GL_UNSIGNED_BYTE = 0x1401; + public const int GL_SHORT = 0x1402; + public const int GL_UNSIGNED_SHORT = 0x1403; + public const int GL_INT = 0x1404; + public const int GL_UNSIGNED_INT = 0x1405; + public const int GL_FLOAT = 0x1406; + public const int GL_2_BYTES = 0x1407; + public const int GL_3_BYTES = 0x1408; + public const int GL_4_BYTES = 0x1409; + public const int GL_DOUBLE = 0x140A; + public const int GL_POINTS = 0x0000; + public const int GL_LINES = 0x0001; + public const int GL_LINE_LOOP = 0x0002; + public const int GL_LINE_STRIP = 0x0003; + public const int GL_TRIANGLES = 0x0004; + public const int GL_TRIANGLE_STRIP = 0x0005; + public const int GL_TRIANGLE_FAN = 0x0006; + public const int GL_QUADS = 0x0007; + public const int GL_QUAD_STRIP = 0x0008; + public const int GL_POLYGON = 0x0009; + public const int GL_VERTEX_ARRAY = 0x8074; + public const int GL_NORMAL_ARRAY = 0x8075; + public const int GL_COLOR_ARRAY = 0x8076; + public const int GL_INDEX_ARRAY = 0x8077; + public const int GL_TEXTURE_COORD_ARRAY = 0x8078; + public const int GL_EDGE_FLAG_ARRAY = 0x8079; + public const int GL_VERTEX_ARRAY_SIZE = 0x807A; + public const int GL_VERTEX_ARRAY_TYPE = 0x807B; + public const int GL_VERTEX_ARRAY_STRIDE = 0x807C; + public const int GL_NORMAL_ARRAY_TYPE = 0x807E; + public const int GL_NORMAL_ARRAY_STRIDE = 0x807F; + public const int GL_COLOR_ARRAY_SIZE = 0x8081; + public const int GL_COLOR_ARRAY_TYPE = 0x8082; + public const int GL_COLOR_ARRAY_STRIDE = 0x8083; + public const int GL_INDEX_ARRAY_TYPE = 0x8085; + public const int GL_INDEX_ARRAY_STRIDE = 0x8086; + public const int GL_TEXTURE_COORD_ARRAY_SIZE = 0x8088; + public const int GL_TEXTURE_COORD_ARRAY_TYPE = 0x8089; + public const int GL_TEXTURE_COORD_ARRAY_STRIDE = 0x808A; + public const int GL_EDGE_FLAG_ARRAY_STRIDE = 0x808C; + public const int GL_VERTEX_ARRAY_POINTER = 0x808E; + public const int GL_NORMAL_ARRAY_POINTER = 0x808F; + public const int GL_COLOR_ARRAY_POINTER = 0x8090; + public const int GL_INDEX_ARRAY_POINTER = 0x8091; + public const int GL_TEXTURE_COORD_ARRAY_POINTER = 0x8092; + public const int GL_EDGE_FLAG_ARRAY_POINTER = 0x8093; + public const int GL_V2F = 0x2A20; + public const int GL_V3F = 0x2A21; + public const int GL_C4UB_V2F = 0x2A22; + public const int GL_C4UB_V3F = 0x2A23; + public const int GL_C3F_V3F = 0x2A24; + public const int GL_N3F_V3F = 0x2A25; + public const int GL_C4F_N3F_V3F = 0x2A26; + public const int GL_T2F_V3F = 0x2A27; + public const int GL_T4F_V4F = 0x2A28; + public const int GL_T2F_C4UB_V3F = 0x2A29; + public const int GL_T2F_C3F_V3F = 0x2A2A; + public const int GL_T2F_N3F_V3F = 0x2A2B; + public const int GL_T2F_C4F_N3F_V3F = 0x2A2C; + public const int GL_T4F_C4F_N3F_V4F = 0x2A2D; + public const int GL_MATRIX_MODE = 0x0BA0; + public const int GL_MODELVIEW = 0x1700; + public const int GL_PROJECTION = 0x1701; + public const int GL_TEXTURE = 0x1702; + public const int GL_POINT_SMOOTH = 0x0B10; + public const int GL_POINT_SIZE = 0x0B11; + public const int GL_POINT_SIZE_GRANULARITY = 0x0B13; + public const int GL_POINT_SIZE_RANGE = 0x0B12; + public const int GL_LINE_SMOOTH = 0x0B20; + public const int GL_LINE_STIPPLE = 0x0B24; + public const int GL_LINE_STIPPLE_PATTERN = 0x0B25; + public const int GL_LINE_STIPPLE_REPEAT = 0x0B26; + public const int GL_LINE_WIDTH = 0x0B21; + public const int GL_LINE_WIDTH_GRANULARITY = 0x0B23; + public const int GL_LINE_WIDTH_RANGE = 0x0B22; + public const int GL_POINT = 0x1B00; + public const int GL_LINE = 0x1B01; + public const int GL_FILL = 0x1B02; + public const int GL_CW = 0x0900; + public const int GL_CCW = 0x0901; + public const int GL_FRONT = 0x0404; + public const int GL_BACK = 0x0405; + public const int GL_POLYGON_MODE = 0x0B40; + public const int GL_POLYGON_SMOOTH = 0x0B41; + public const int GL_POLYGON_STIPPLE = 0x0B42; + public const int GL_EDGE_FLAG = 0x0B43; + public const int GL_CULL_FACE = 0x0B44; + public const int GL_CULL_FACE_MODE = 0x0B45; + public const int GL_FRONT_FACE = 0x0B46; + public const int GL_POLYGON_OFFSET_FACTOR = 0x8038; + public const int GL_POLYGON_OFFSET_UNITS = 0x2A00; + public const int GL_POLYGON_OFFSET_POINT = 0x2A01; + public const int GL_POLYGON_OFFSET_LINE = 0x2A02; + public const int GL_POLYGON_OFFSET_FILL = 0x8037; + public const int GL_COMPILE = 0x1300; + public const int GL_COMPILE_AND_EXECUTE = 0x1301; + public const int GL_LIST_BASE = 0x0B32; + public const int GL_LIST_INDEX = 0x0B33; + public const int GL_LIST_MODE = 0x0B30; + public const int GL_NEVER = 0x0200; + public const int GL_LESS = 0x0201; + public const int GL_EQUAL = 0x0202; + public const int GL_LEQUAL = 0x0203; + public const int GL_GREATER = 0x0204; + public const int GL_NOTEQUAL = 0x0205; + public const int GL_GEQUAL = 0x0206; + public const int GL_ALWAYS = 0x0207; + public const int GL_DEPTH_TEST = 0x0B71; + public const int GL_DEPTH_BITS = 0x0D56; + public const int GL_DEPTH_CLEAR_VALUE = 0x0B73; + public const int GL_DEPTH_FUNC = 0x0B74; + public const int GL_DEPTH_RANGE = 0x0B70; + public const int GL_DEPTH_WRITEMASK = 0x0B72; + public const int GL_DEPTH_COMPONENT = 0x1902; + public const int GL_LIGHTING = 0x0B50; + public const int GL_LIGHT0 = 0x4000; + public const int GL_LIGHT1 = 0x4001; + public const int GL_LIGHT2 = 0x4002; + public const int GL_LIGHT3 = 0x4003; + public const int GL_LIGHT4 = 0x4004; + public const int GL_LIGHT5 = 0x4005; + public const int GL_LIGHT6 = 0x4006; + public const int GL_LIGHT7 = 0x4007; + public const int GL_SPOT_EXPONENT = 0x1205; + public const int GL_SPOT_CUTOFF = 0x1206; + public const int GL_CONSTANT_ATTENUATION = 0x1207; + public const int GL_LINEAR_ATTENUATION = 0x1208; + public const int GL_QUADRATIC_ATTENUATION = 0x1209; + public const int GL_AMBIENT = 0x1200; + public const int GL_DIFFUSE = 0x1201; + public const int GL_SPECULAR = 0x1202; + public const int GL_SHININESS = 0x1601; + public const int GL_EMISSION = 0x1600; + public const int GL_POSITION = 0x1203; + public const int GL_SPOT_DIRECTION = 0x1204; + public const int GL_AMBIENT_AND_DIFFUSE = 0x1602; + public const int GL_COLOR_INDEXES = 0x1603; + public const int GL_LIGHT_MODEL_TWO_SIDE = 0x0B52; + public const int GL_LIGHT_MODEL_LOCAL_VIEWER = 0x0B51; + public const int GL_LIGHT_MODEL_AMBIENT = 0x0B53; + public const int GL_FRONT_AND_BACK = 0x0408; + public const int GL_SHADE_MODEL = 0x0B54; + public const int GL_FLAT = 0x1D00; + public const int GL_SMOOTH = 0x1D01; + public const int GL_COLOR_MATERIAL = 0x0B57; + public const int GL_COLOR_MATERIAL_FACE = 0x0B55; + public const int GL_COLOR_MATERIAL_PARAMETER = 0x0B56; + public const int GL_NORMALIZE = 0x0BA1; + public const int GL_CLIP_PLANE0 = 0x3000; + public const int GL_CLIP_PLANE1 = 0x3001; + public const int GL_CLIP_PLANE2 = 0x3002; + public const int GL_CLIP_PLANE3 = 0x3003; + public const int GL_CLIP_PLANE4 = 0x3004; + public const int GL_CLIP_PLANE5 = 0x3005; + public const int GL_ACCUM_RED_BITS = 0x0D58; + public const int GL_ACCUM_GREEN_BITS = 0x0D59; + public const int GL_ACCUM_BLUE_BITS = 0x0D5A; + public const int GL_ACCUM_ALPHA_BITS = 0x0D5B; + public const int GL_ACCUM_CLEAR_VALUE = 0x0B80; + public const int GL_ACCUM = 0x0100; + public const int GL_ADD = 0x0104; + public const int GL_LOAD = 0x0101; + public const int GL_MULT = 0x0103; + public const int GL_RETURN = 0x0102; + public const int GL_ALPHA_TEST = 0x0BC0; + public const int GL_ALPHA_TEST_REF = 0x0BC2; + public const int GL_ALPHA_TEST_FUNC = 0x0BC1; + public const int GL_BLEND = 0x0BE2; + public const int GL_BLEND_SRC = 0x0BE1; + public const int GL_BLEND_DST = 0x0BE0; + public const int GL_SRC_COLOR = 0x0300; + public const int GL_ONE_MINUS_SRC_COLOR = 0x0301; + public const int GL_SRC_ALPHA = 0x0302; + public const int GL_ONE_MINUS_SRC_ALPHA = 0x0303; + public const int GL_DST_ALPHA = 0x0304; + public const int GL_ONE_MINUS_DST_ALPHA = 0x0305; + public const int GL_DST_COLOR = 0x0306; + public const int GL_ONE_MINUS_DST_COLOR = 0x0307; + public const int GL_SRC_ALPHA_SATURATE = 0x0308; + public const int GL_FEEDBACK = 0x1C01; + public const int GL_RENDER = 0x1C00; + public const int GL_SELECT = 0x1C02; + public const int GL_2D = 0x0600; + public const int GL_3D = 0x0601; + public const int GL_3D_COLOR = 0x0602; + public const int GL_3D_COLOR_TEXTURE = 0x0603; + public const int GL_4D_COLOR_TEXTURE = 0x0604; + public const int GL_POINT_TOKEN = 0x0701; + public const int GL_LINE_TOKEN = 0x0702; + public const int GL_LINE_RESET_TOKEN = 0x0707; + public const int GL_POLYGON_TOKEN = 0x0703; + public const int GL_BITMAP_TOKEN = 0x0704; + public const int GL_DRAW_PIXEL_TOKEN = 0x0705; + public const int GL_COPY_PIXEL_TOKEN = 0x0706; + public const int GL_PASS_THROUGH_TOKEN = 0x0700; + public const int GL_FEEDBACK_BUFFER_POINTER = 0x0DF0; + public const int GL_FEEDBACK_BUFFER_SIZE = 0x0DF1; + public const int GL_FEEDBACK_BUFFER_TYPE = 0x0DF2; + public const int GL_SELECTION_BUFFER_POINTER = 0x0DF3; + public const int GL_SELECTION_BUFFER_SIZE = 0x0DF4; + public const int GL_FOG = 0x0B60; + public const int GL_FOG_MODE = 0x0B65; + public const int GL_FOG_DENSITY = 0x0B62; + public const int GL_FOG_COLOR = 0x0B66; + public const int GL_FOG_INDEX = 0x0B61; + public const int GL_FOG_START = 0x0B63; + public const int GL_FOG_END = 0x0B64; + public const int GL_LINEAR = 0x2601; + public const int GL_EXP = 0x0800; + public const int GL_EXP2 = 0x0801; + public const int GL_LOGIC_OP = 0x0BF1; + public const int GL_INDEX_LOGIC_OP = 0x0BF1; + public const int GL_COLOR_LOGIC_OP = 0x0BF2; + public const int GL_LOGIC_OP_MODE = 0x0BF0; + public const int GL_CLEAR = 0x1500; + public const int GL_SET = 0x150F; + public const int GL_COPY = 0x1503; + public const int GL_COPY_INVERTED = 0x150C; + public const int GL_NOOP = 0x1505; + public const int GL_INVERT = 0x150A; + public const int GL_AND = 0x1501; + public const int GL_NAND = 0x150E; + public const int GL_OR = 0x1507; + public const int GL_NOR = 0x1508; + public const int GL_XOR = 0x1506; + public const int GL_EQUIV = 0x1509; + public const int GL_AND_REVERSE = 0x1502; + public const int GL_AND_INVERTED = 0x1504; + public const int GL_OR_REVERSE = 0x150B; + public const int GL_OR_INVERTED = 0x150D; + public const int GL_STENCIL_BITS = 0x0D57; + public const int GL_STENCIL_TEST = 0x0B90; + public const int GL_STENCIL_CLEAR_VALUE = 0x0B91; + public const int GL_STENCIL_FUNC = 0x0B92; + public const int GL_STENCIL_VALUE_MASK = 0x0B93; + public const int GL_STENCIL_FAIL = 0x0B94; + public const int GL_STENCIL_PASS_DEPTH_FAIL = 0x0B95; + public const int GL_STENCIL_PASS_DEPTH_PASS = 0x0B96; + public const int GL_STENCIL_REF = 0x0B97; + public const int GL_STENCIL_WRITEMASK = 0x0B98; + public const int GL_STENCIL_INDEX = 0x1901; + public const int GL_KEEP = 0x1E00; + public const int GL_REPLACE = 0x1E01; + public const int GL_INCR = 0x1E02; + public const int GL_DECR = 0x1E03; + public const int GL_LEFT = 0x0406; + public const int GL_RIGHT = 0x0407; + public const int GL_FRONT_LEFT = 0x0400; + public const int GL_FRONT_RIGHT = 0x0401; + public const int GL_BACK_LEFT = 0x0402; + public const int GL_BACK_RIGHT = 0x0403; + public const int GL_AUX0 = 0x0409; + public const int GL_AUX1 = 0x040A; + public const int GL_AUX2 = 0x040B; + public const int GL_AUX3 = 0x040C; + public const int GL_COLOR_INDEX = 0x1900; + public const int GL_RED = 0x1903; + public const int GL_GREEN = 0x1904; + public const int GL_BLUE = 0x1905; + public const int GL_ALPHA = 0x1906; + public const int GL_LUMINANCE = 0x1909; + public const int GL_LUMINANCE_ALPHA = 0x190A; + public const int GL_ALPHA_BITS = 0x0D55; + public const int GL_RED_BITS = 0x0D52; + public const int GL_GREEN_BITS = 0x0D53; + public const int GL_BLUE_BITS = 0x0D54; + public const int GL_INDEX_BITS = 0x0D51; + public const int GL_SUBPIXEL_BITS = 0x0D50; + public const int GL_AUX_BUFFERS = 0x0C00; + public const int GL_READ_BUFFER = 0x0C02; + public const int GL_DRAW_BUFFER = 0x0C01; + public const int GL_DOUBLEBUFFER = 0x0C32; + public const int GL_STEREO = 0x0C33; + public const int GL_BITMAP = 0x1A00; + public const int GL_COLOR = 0x1800; + public const int GL_DEPTH = 0x1801; + public const int GL_STENCIL = 0x1802; + public const int GL_DITHER = 0x0BD0; + public const int GL_RGB = 0x1907; + public const int GL_RGBA = 0x1908; + public const int GL_MAX_LIST_NESTING = 0x0B31; + public const int GL_MAX_EVAL_ORDER = 0x0D30; + public const int GL_MAX_LIGHTS = 0x0D31; + public const int GL_MAX_CLIP_PLANES = 0x0D32; + public const int GL_MAX_TEXTURE_SIZE = 0x0D33; + public const int GL_MAX_PIXEL_MAP_TABLE = 0x0D34; + public const int GL_MAX_ATTRIB_STACK_DEPTH = 0x0D35; + public const int GL_MAX_MODELVIEW_STACK_DEPTH = 0x0D36; + public const int GL_MAX_NAME_STACK_DEPTH = 0x0D37; + public const int GL_MAX_PROJECTION_STACK_DEPTH = 0x0D38; + public const int GL_MAX_TEXTURE_STACK_DEPTH = 0x0D39; + public const int GL_MAX_VIEWPORT_DIMS = 0x0D3A; + public const int GL_MAX_CLIENT_ATTRIB_STACK_DEPTH = 0x0D3B; + public const int GL_ATTRIB_STACK_DEPTH = 0x0BB0; + public const int GL_CLIENT_ATTRIB_STACK_DEPTH = 0x0BB1; + public const int GL_COLOR_CLEAR_VALUE = 0x0C22; + public const int GL_COLOR_WRITEMASK = 0x0C23; + public const int GL_CURRENT_INDEX = 0x0B01; + public const int GL_CURRENT_COLOR = 0x0B00; + public const int GL_CURRENT_NORMAL = 0x0B02; + public const int GL_CURRENT_RASTER_COLOR = 0x0B04; + public const int GL_CURRENT_RASTER_DISTANCE = 0x0B09; + public const int GL_CURRENT_RASTER_INDEX = 0x0B05; + public const int GL_CURRENT_RASTER_POSITION = 0x0B07; + public const int GL_CURRENT_RASTER_TEXTURE_COORDS = 0x0B06; + public const int GL_CURRENT_RASTER_POSITION_VALID = 0x0B08; + public const int GL_CURRENT_TEXTURE_COORDS = 0x0B03; + public const int GL_INDEX_CLEAR_VALUE = 0x0C20; + public const int GL_INDEX_MODE = 0x0C30; + public const int GL_INDEX_WRITEMASK = 0x0C21; + public const int GL_MODELVIEW_MATRIX = 0x0BA6; + public const int GL_MODELVIEW_STACK_DEPTH = 0x0BA3; + public const int GL_NAME_STACK_DEPTH = 0x0D70; + public const int GL_PROJECTION_MATRIX = 0x0BA7; + public const int GL_PROJECTION_STACK_DEPTH = 0x0BA4; + public const int GL_RENDER_MODE = 0x0C40; + public const int GL_RGBA_MODE = 0x0C31; + public const int GL_TEXTURE_MATRIX = 0x0BA8; + public const int GL_TEXTURE_STACK_DEPTH = 0x0BA5; + public const int GL_VIEWPORT = 0x0BA2; + public const int GL_AUTO_NORMAL = 0x0D80; + public const int GL_MAP1_COLOR_4 = 0x0D90; + public const int GL_MAP1_INDEX = 0x0D91; + public const int GL_MAP1_NORMAL = 0x0D92; + public const int GL_MAP1_TEXTURE_COORD_1 = 0x0D93; + public const int GL_MAP1_TEXTURE_COORD_2 = 0x0D94; + public const int GL_MAP1_TEXTURE_COORD_3 = 0x0D95; + public const int GL_MAP1_TEXTURE_COORD_4 = 0x0D96; + public const int GL_MAP1_VERTEX_3 = 0x0D97; + public const int GL_MAP1_VERTEX_4 = 0x0D98; + public const int GL_MAP2_COLOR_4 = 0x0DB0; + public const int GL_MAP2_INDEX = 0x0DB1; + public const int GL_MAP2_NORMAL = 0x0DB2; + public const int GL_MAP2_TEXTURE_COORD_1 = 0x0DB3; + public const int GL_MAP2_TEXTURE_COORD_2 = 0x0DB4; + public const int GL_MAP2_TEXTURE_COORD_3 = 0x0DB5; + public const int GL_MAP2_TEXTURE_COORD_4 = 0x0DB6; + public const int GL_MAP2_VERTEX_3 = 0x0DB7; + public const int GL_MAP2_VERTEX_4 = 0x0DB8; + public const int GL_MAP1_GRID_DOMAIN = 0x0DD0; + public const int GL_MAP1_GRID_SEGMENTS = 0x0DD1; + public const int GL_MAP2_GRID_DOMAIN = 0x0DD2; + public const int GL_MAP2_GRID_SEGMENTS = 0x0DD3; + public const int GL_COEFF = 0x0A00; + public const int GL_ORDER = 0x0A01; + public const int GL_DOMAIN = 0x0A02; + public const int GL_PERSPECTIVE_CORRECTION_HINT = 0x0C50; + public const int GL_POINT_SMOOTH_HINT = 0x0C51; + public const int GL_LINE_SMOOTH_HINT = 0x0C52; + public const int GL_POLYGON_SMOOTH_HINT = 0x0C53; + public const int GL_FOG_HINT = 0x0C54; + public const int GL_DONT_CARE = 0x1100; + public const int GL_FASTEST = 0x1101; + public const int GL_NICEST = 0x1102; + public const int GL_SCISSOR_BOX = 0x0C10; + public const int GL_SCISSOR_TEST = 0x0C11; + public const int GL_MAP_COLOR = 0x0D10; + public const int GL_MAP_STENCIL = 0x0D11; + public const int GL_INDEX_SHIFT = 0x0D12; + public const int GL_INDEX_OFFSET = 0x0D13; + public const int GL_RED_SCALE = 0x0D14; + public const int GL_RED_BIAS = 0x0D15; + public const int GL_GREEN_SCALE = 0x0D18; + public const int GL_GREEN_BIAS = 0x0D19; + public const int GL_BLUE_SCALE = 0x0D1A; + public const int GL_BLUE_BIAS = 0x0D1B; + public const int GL_ALPHA_SCALE = 0x0D1C; + public const int GL_ALPHA_BIAS = 0x0D1D; + public const int GL_DEPTH_SCALE = 0x0D1E; + public const int GL_DEPTH_BIAS = 0x0D1F; + public const int GL_PIXEL_MAP_S_TO_S_SIZE = 0x0CB1; + public const int GL_PIXEL_MAP_I_TO_I_SIZE = 0x0CB0; + public const int GL_PIXEL_MAP_I_TO_R_SIZE = 0x0CB2; + public const int GL_PIXEL_MAP_I_TO_G_SIZE = 0x0CB3; + public const int GL_PIXEL_MAP_I_TO_B_SIZE = 0x0CB4; + public const int GL_PIXEL_MAP_I_TO_A_SIZE = 0x0CB5; + public const int GL_PIXEL_MAP_R_TO_R_SIZE = 0x0CB6; + public const int GL_PIXEL_MAP_G_TO_G_SIZE = 0x0CB7; + public const int GL_PIXEL_MAP_B_TO_B_SIZE = 0x0CB8; + public const int GL_PIXEL_MAP_A_TO_A_SIZE = 0x0CB9; + public const int GL_PIXEL_MAP_S_TO_S = 0x0C71; + public const int GL_PIXEL_MAP_I_TO_I = 0x0C70; + public const int GL_PIXEL_MAP_I_TO_R = 0x0C72; + public const int GL_PIXEL_MAP_I_TO_G = 0x0C73; + public const int GL_PIXEL_MAP_I_TO_B = 0x0C74; + public const int GL_PIXEL_MAP_I_TO_A = 0x0C75; + public const int GL_PIXEL_MAP_R_TO_R = 0x0C76; + public const int GL_PIXEL_MAP_G_TO_G = 0x0C77; + public const int GL_PIXEL_MAP_B_TO_B = 0x0C78; + public const int GL_PIXEL_MAP_A_TO_A = 0x0C79; + public const int GL_PACK_ALIGNMENT = 0x0D05; + public const int GL_PACK_LSB_FIRST = 0x0D01; + public const int GL_PACK_ROW_LENGTH = 0x0D02; + public const int GL_PACK_SKIP_PIXELS = 0x0D04; + public const int GL_PACK_SKIP_ROWS = 0x0D03; + public const int GL_PACK_SWAP_BYTES = 0x0D00; + public const int GL_UNPACK_ALIGNMENT = 0x0CF5; + public const int GL_UNPACK_LSB_FIRST = 0x0CF1; + public const int GL_UNPACK_ROW_LENGTH = 0x0CF2; + public const int GL_UNPACK_SKIP_PIXELS = 0x0CF4; + public const int GL_UNPACK_SKIP_ROWS = 0x0CF3; + public const int GL_UNPACK_SWAP_BYTES = 0x0CF0; + public const int GL_ZOOM_X = 0x0D16; + public const int GL_ZOOM_Y = 0x0D17; + public const int GL_TEXTURE_ENV = 0x2300; + public const int GL_TEXTURE_ENV_MODE = 0x2200; + public const int GL_TEXTURE_1D = 0x0DE0; + public const int GL_TEXTURE_2D = 0x0DE1; + public const int GL_TEXTURE_WRAP_S = 0x2802; + public const int GL_TEXTURE_WRAP_T = 0x2803; + public const int GL_TEXTURE_MAG_FILTER = 0x2800; + public const int GL_TEXTURE_MIN_FILTER = 0x2801; + public const int GL_TEXTURE_ENV_COLOR = 0x2201; + public const int GL_TEXTURE_GEN_S = 0x0C60; + public const int GL_TEXTURE_GEN_T = 0x0C61; + public const int GL_TEXTURE_GEN_R = 0x0C62; + public const int GL_TEXTURE_GEN_Q = 0x0C63; + public const int GL_TEXTURE_GEN_MODE = 0x2500; + public const int GL_TEXTURE_BORDER_COLOR = 0x1004; + public const int GL_TEXTURE_WIDTH = 0x1000; + public const int GL_TEXTURE_HEIGHT = 0x1001; + public const int GL_TEXTURE_BORDER = 0x1005; + public const int GL_TEXTURE_COMPONENTS = 0x1003; + public const int GL_TEXTURE_RED_SIZE = 0x805C; + public const int GL_TEXTURE_GREEN_SIZE = 0x805D; + public const int GL_TEXTURE_BLUE_SIZE = 0x805E; + public const int GL_TEXTURE_ALPHA_SIZE = 0x805F; + public const int GL_TEXTURE_LUMINANCE_SIZE = 0x8060; + public const int GL_TEXTURE_INTENSITY_SIZE = 0x8061; + public const int GL_NEAREST_MIPMAP_NEAREST = 0x2700; + public const int GL_NEAREST_MIPMAP_LINEAR = 0x2702; + public const int GL_LINEAR_MIPMAP_NEAREST = 0x2701; + public const int GL_LINEAR_MIPMAP_LINEAR = 0x2703; + public const int GL_OBJECT_LINEAR = 0x2401; + public const int GL_OBJECT_PLANE = 0x2501; + public const int GL_EYE_LINEAR = 0x2400; + public const int GL_EYE_PLANE = 0x2502; + public const int GL_SPHERE_MAP = 0x2402; + public const int GL_DECAL = 0x2101; + public const int GL_MODULATE = 0x2100; + public const int GL_NEAREST = 0x2600; + public const int GL_REPEAT = 0x2901; + public const int GL_CLAMP = 0x2900; + public const int GL_S = 0x2000; + public const int GL_T = 0x2001; + public const int GL_R = 0x2002; + public const int GL_Q = 0x2003; + public const int GL_VENDOR = 0x1F00; + public const int GL_RENDERER = 0x1F01; + public const int GL_VERSION = 0x1F02; + public const int GL_EXTENSIONS = 0x1F03; + public const int GL_INVALID_ENUM = 0x0500; + public const int GL_INVALID_VALUE = 0x0501; + public const int GL_INVALID_OPERATION = 0x0502; + public const int GL_STACK_OVERFLOW = 0x0503; + public const int GL_STACK_UNDERFLOW = 0x0504; + public const int GL_OUT_OF_MEMORY = 0x0505; + public const int GL_CURRENT_BIT = 0x00000001; + public const int GL_POINT_BIT = 0x00000002; + public const int GL_LINE_BIT = 0x00000004; + public const int GL_POLYGON_BIT = 0x00000008; + public const int GL_POLYGON_STIPPLE_BIT = 0x00000010; + public const int GL_PIXEL_MODE_BIT = 0x00000020; + public const int GL_LIGHTING_BIT = 0x00000040; + public const int GL_FOG_BIT = 0x00000080; + public const int GL_DEPTH_BUFFER_BIT = 0x00000100; + public const int GL_ACCUM_BUFFER_BIT = 0x00000200; + public const int GL_STENCIL_BUFFER_BIT = 0x00000400; + public const int GL_VIEWPORT_BIT = 0x00000800; + public const int GL_TRANSFORM_BIT = 0x00001000; + public const int GL_ENABLE_BIT = 0x00002000; + public const int GL_COLOR_BUFFER_BIT = 0x00004000; + public const int GL_HINT_BIT = 0x00008000; + public const int GL_EVAL_BIT = 0x00010000; + public const int GL_LIST_BIT = 0x00020000; + public const int GL_TEXTURE_BIT = 0x00040000; + public const int GL_SCISSOR_BIT = 0x00080000; + public const int GL_ALL_ATTRIB_BITS = -1; + public const int GL_PROXY_TEXTURE_1D = 0x8063; + public const int GL_PROXY_TEXTURE_2D = 0x8064; + public const int GL_TEXTURE_PRIORITY = 0x8066; + public const int GL_TEXTURE_RESIDENT = 0x8067; + public const int GL_TEXTURE_BINDING_1D = 0x8068; + public const int GL_TEXTURE_BINDING_2D = 0x8069; + public const int GL_TEXTURE_INTERNAL_FORMAT = 0x1003; + public const int GL_ALPHA4 = 0x803B; + public const int GL_ALPHA8 = 0x803C; + public const int GL_ALPHA12 = 0x803D; + public const int GL_ALPHA16 = 0x803E; + public const int GL_LUMINANCE4 = 0x803F; + public const int GL_LUMINANCE8 = 0x8040; + public const int GL_LUMINANCE12 = 0x8041; + public const int GL_LUMINANCE16 = 0x8042; + public const int GL_LUMINANCE4_ALPHA4 = 0x8043; + public const int GL_LUMINANCE6_ALPHA2 = 0x8044; + public const int GL_LUMINANCE8_ALPHA8 = 0x8045; + public const int GL_LUMINANCE12_ALPHA4 = 0x8046; + public const int GL_LUMINANCE12_ALPHA12 = 0x8047; + public const int GL_LUMINANCE16_ALPHA16 = 0x8048; + public const int GL_INTENSITY = 0x8049; + public const int GL_INTENSITY4 = 0x804A; + public const int GL_INTENSITY8 = 0x804B; + public const int GL_INTENSITY12 = 0x804C; + public const int GL_INTENSITY16 = 0x804D; + public const int GL_R3_G3_B2 = 0x2A10; + public const int GL_RGB4 = 0x804F; + public const int GL_RGB5 = 0x8050; + public const int GL_RGB8 = 0x8051; + public const int GL_RGB10 = 0x8052; + public const int GL_RGB12 = 0x8053; + public const int GL_RGB16 = 0x8054; + public const int GL_RGBA2 = 0x8055; + public const int GL_RGBA4 = 0x8056; + public const int GL_RGB5_A1 = 0x8057; + public const int GL_RGBA8 = 0x8058; + public const int GL_RGB10_A2 = 0x8059; + public const int GL_RGBA12 = 0x805A; + public const int GL_RGBA16 = 0x805B; + public const int GL_CLIENT_PIXEL_STORE_BIT = 0x00000001; + public const int GL_CLIENT_VERTEX_ARRAY_BIT = 0x00000002; + public const int GL_ALL_CLIENT_ATTRIB_BITS = -1; + public const int GL_CLIENT_ALL_ATTRIB_BITS = -1; + public const int GL_RESCALE_NORMAL = 0x803A; + public const int GL_CLAMP_TO_EDGE = 0x812F; + public const int GL_MAX_ELEMENTS_VERTICES = 0x80E8; + public const int GL_MAX_ELEMENTS_INDICES = 0x80E9; + public const int GL_BGR = 0x80E0; + public const int GL_BGRA = 0x80E1; + public const int GL_UNSIGNED_BYTE_3_3_2 = 0x8032; + public const int GL_UNSIGNED_BYTE_2_3_3_REV = 0x8362; + public const int GL_UNSIGNED_SHORT_5_6_5 = 0x8363; + public const int GL_UNSIGNED_SHORT_5_6_5_REV = 0x8364; + public const int GL_UNSIGNED_SHORT_4_4_4_4 = 0x8033; + public const int GL_UNSIGNED_SHORT_4_4_4_4_REV = 0x8365; + public const int GL_UNSIGNED_SHORT_5_5_5_1 = 0x8034; + public const int GL_UNSIGNED_SHORT_1_5_5_5_REV = 0x8366; + public const int GL_UNSIGNED_INT_8_8_8_8 = 0x8035; + public const int GL_UNSIGNED_INT_8_8_8_8_REV = 0x8367; + public const int GL_UNSIGNED_INT_10_10_10_2 = 0x8036; + public const int GL_UNSIGNED_INT_2_10_10_10_REV = 0x8368; + public const int GL_LIGHT_MODEL_COLOR_CONTROL = 0x81F8; + public const int GL_SINGLE_COLOR = 0x81F9; + public const int GL_SEPARATE_SPECULAR_COLOR = 0x81FA; + public const int GL_TEXTURE_MIN_LOD = 0x813A; + public const int GL_TEXTURE_MAX_LOD = 0x813B; + public const int GL_TEXTURE_BASE_LEVEL = 0x813C; + public const int GL_TEXTURE_MAX_LEVEL = 0x813D; + public const int GL_SMOOTH_POINT_SIZE_RANGE = 0x0B12; + public const int GL_SMOOTH_POINT_SIZE_GRANULARITY = 0x0B13; + public const int GL_SMOOTH_LINE_WIDTH_RANGE = 0x0B22; + public const int GL_SMOOTH_LINE_WIDTH_GRANULARITY = 0x0B23; + public const int GL_ALIASED_POINT_SIZE_RANGE = 0x846D; + public const int GL_ALIASED_LINE_WIDTH_RANGE = 0x846E; + public const int GL_PACK_SKIP_IMAGES = 0x806B; + public const int GL_PACK_IMAGE_HEIGHT = 0x806C; + public const int GL_UNPACK_SKIP_IMAGES = 0x806D; + public const int GL_UNPACK_IMAGE_HEIGHT = 0x806E; + public const int GL_TEXTURE_3D = 0x806F; + public const int GL_PROXY_TEXTURE_3D = 0x8070; + public const int GL_TEXTURE_DEPTH = 0x8071; + public const int GL_TEXTURE_WRAP_R = 0x8072; + public const int GL_MAX_3D_TEXTURE_SIZE = 0x8073; + public const int GL_TEXTURE_BINDING_3D = 0x806A; + public const int GL_CONSTANT_COLOR = 0x8001; + public const int GL_ONE_MINUS_CONSTANT_COLOR = 0x8002; + public const int GL_CONSTANT_ALPHA = 0x8003; + public const int GL_ONE_MINUS_CONSTANT_ALPHA = 0x8004; + public const int GL_COLOR_TABLE = 0x80D0; + public const int GL_POST_CONVOLUTION_COLOR_TABLE = 0x80D1; + public const int GL_POST_COLOR_MATRIX_COLOR_TABLE = 0x80D2; + public const int GL_PROXY_COLOR_TABLE = 0x80D3; + public const int GL_PROXY_POST_CONVOLUTION_COLOR_TABLE = 0x80D4; + public const int GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE = 0x80D5; + public const int GL_COLOR_TABLE_SCALE = 0x80D6; + public const int GL_COLOR_TABLE_BIAS = 0x80D7; + public const int GL_COLOR_TABLE_FORMAT = 0x80D8; + public const int GL_COLOR_TABLE_WIDTH = 0x80D9; + public const int GL_COLOR_TABLE_RED_SIZE = 0x80DA; + public const int GL_COLOR_TABLE_GREEN_SIZE = 0x80DB; + public const int GL_COLOR_TABLE_BLUE_SIZE = 0x80DC; + public const int GL_COLOR_TABLE_ALPHA_SIZE = 0x80DD; + public const int GL_COLOR_TABLE_LUMINANCE_SIZE = 0x80DE; + public const int GL_COLOR_TABLE_INTENSITY_SIZE = 0x80DF; + public const int GL_CONVOLUTION_1D = 0x8010; + public const int GL_CONVOLUTION_2D = 0x8011; + public const int GL_SEPARABLE_2D = 0x8012; + public const int GL_CONVOLUTION_BORDER_MODE = 0x8013; + public const int GL_CONVOLUTION_FILTER_SCALE = 0x8014; + public const int GL_CONVOLUTION_FILTER_BIAS = 0x8015; + public const int GL_REDUCE = 0x8016; + public const int GL_CONVOLUTION_FORMAT = 0x8017; + public const int GL_CONVOLUTION_WIDTH = 0x8018; + public const int GL_CONVOLUTION_HEIGHT = 0x8019; + public const int GL_MAX_CONVOLUTION_WIDTH = 0x801A; + public const int GL_MAX_CONVOLUTION_HEIGHT = 0x801B; + public const int GL_POST_CONVOLUTION_RED_SCALE = 0x801C; + public const int GL_POST_CONVOLUTION_GREEN_SCALE = 0x801D; + public const int GL_POST_CONVOLUTION_BLUE_SCALE = 0x801E; + public const int GL_POST_CONVOLUTION_ALPHA_SCALE = 0x801F; + public const int GL_POST_CONVOLUTION_RED_BIAS = 0x8020; + public const int GL_POST_CONVOLUTION_GREEN_BIAS = 0x8021; + public const int GL_POST_CONVOLUTION_BLUE_BIAS = 0x8022; + public const int GL_POST_CONVOLUTION_ALPHA_BIAS = 0x8023; + public const int GL_CONSTANT_BORDER = 0x8151; + public const int GL_REPLICATE_BORDER = 0x8153; + public const int GL_CONVOLUTION_BORDER_COLOR = 0x8154; + public const int GL_COLOR_MATRIX = 0x80B1; + public const int GL_COLOR_MATRIX_STACK_DEPTH = 0x80B2; + public const int GL_MAX_COLOR_MATRIX_STACK_DEPTH = 0x80B3; + public const int GL_POST_COLOR_MATRIX_RED_SCALE = 0x80B4; + public const int GL_POST_COLOR_MATRIX_GREEN_SCALE = 0x80B5; + public const int GL_POST_COLOR_MATRIX_BLUE_SCALE = 0x80B6; + public const int GL_POST_COLOR_MATRIX_ALPHA_SCALE = 0x80B7; + public const int GL_POST_COLOR_MATRIX_RED_BIAS = 0x80B8; + public const int GL_POST_COLOR_MATRIX_GREEN_BIAS = 0x80B9; + public const int GL_POST_COLOR_MATRIX_BLUE_BIAS = 0x80BA; + public const int GL_POST_COLOR_MATRIX_ALPHA_BIAS = 0x80BB; + public const int GL_HISTOGRAM = 0x8024; + public const int GL_PROXY_HISTOGRAM = 0x8025; + public const int GL_HISTOGRAM_WIDTH = 0x8026; + public const int GL_HISTOGRAM_FORMAT = 0x8027; + public const int GL_HISTOGRAM_RED_SIZE = 0x8028; + public const int GL_HISTOGRAM_GREEN_SIZE = 0x8029; + public const int GL_HISTOGRAM_BLUE_SIZE = 0x802A; + public const int GL_HISTOGRAM_ALPHA_SIZE = 0x802B; + public const int GL_HISTOGRAM_LUMINANCE_SIZE = 0x802C; + public const int GL_HISTOGRAM_SINK = 0x802D; + public const int GL_MINMAX = 0x802E; + public const int GL_MINMAX_FORMAT = 0x802F; + public const int GL_MINMAX_SINK = 0x8030; + public const int GL_TABLE_TOO_LARGE = 0x8031; + public const int GL_BLEND_EQUATION = 0x8009; + public const int GL_MIN = 0x8007; + public const int GL_MAX = 0x8008; + public const int GL_FUNC_ADD = 0x8006; + public const int GL_FUNC_SUBTRACT = 0x800A; + public const int GL_FUNC_REVERSE_SUBTRACT = 0x800B; + public const int GL_BLEND_COLOR = 0x8005; + public const int GL_TEXTURE0 = 0x84C0; + public const int GL_TEXTURE1 = 0x84C1; + public const int GL_TEXTURE2 = 0x84C2; + public const int GL_TEXTURE3 = 0x84C3; + public const int GL_TEXTURE4 = 0x84C4; + public const int GL_TEXTURE5 = 0x84C5; + public const int GL_TEXTURE6 = 0x84C6; + public const int GL_TEXTURE7 = 0x84C7; + public const int GL_TEXTURE8 = 0x84C8; + public const int GL_TEXTURE9 = 0x84C9; + public const int GL_TEXTURE10 = 0x84CA; + public const int GL_TEXTURE11 = 0x84CB; + public const int GL_TEXTURE12 = 0x84CC; + public const int GL_TEXTURE13 = 0x84CD; + public const int GL_TEXTURE14 = 0x84CE; + public const int GL_TEXTURE15 = 0x84CF; + public const int GL_TEXTURE16 = 0x84D0; + public const int GL_TEXTURE17 = 0x84D1; + public const int GL_TEXTURE18 = 0x84D2; + public const int GL_TEXTURE19 = 0x84D3; + public const int GL_TEXTURE20 = 0x84D4; + public const int GL_TEXTURE21 = 0x84D5; + public const int GL_TEXTURE22 = 0x84D6; + public const int GL_TEXTURE23 = 0x84D7; + public const int GL_TEXTURE24 = 0x84D8; + public const int GL_TEXTURE25 = 0x84D9; + public const int GL_TEXTURE26 = 0x84DA; + public const int GL_TEXTURE27 = 0x84DB; + public const int GL_TEXTURE28 = 0x84DC; + public const int GL_TEXTURE29 = 0x84DD; + public const int GL_TEXTURE30 = 0x84DE; + public const int GL_TEXTURE31 = 0x84DF; + public const int GL_ACTIVE_TEXTURE = 0x84E0; + public const int GL_CLIENT_ACTIVE_TEXTURE = 0x84E1; + public const int GL_MAX_TEXTURE_UNITS = 0x84E2; + public const int GL_NORMAL_MAP = 0x8511; + public const int GL_REFLECTION_MAP = 0x8512; + public const int GL_TEXTURE_CUBE_MAP = 0x8513; + public const int GL_TEXTURE_BINDING_CUBE_MAP = 0x8514; + public const int GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; + public const int GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; + public const int GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; + public const int GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; + public const int GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; + public const int GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; + public const int GL_PROXY_TEXTURE_CUBE_MAP = 0x851B; + public const int GL_MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C; + public const int GL_COMPRESSED_ALPHA = 0x84E9; + public const int GL_COMPRESSED_LUMINANCE = 0x84EA; + public const int GL_COMPRESSED_LUMINANCE_ALPHA = 0x84EB; + public const int GL_COMPRESSED_INTENSITY = 0x84EC; + public const int GL_COMPRESSED_RGB = 0x84ED; + public const int GL_COMPRESSED_RGBA = 0x84EE; + public const int GL_TEXTURE_COMPRESSION_HINT = 0x84EF; + public const int GL_TEXTURE_COMPRESSED_IMAGE_SIZE = 0x86A0; + public const int GL_TEXTURE_COMPRESSED = 0x86A1; + public const int GL_NUM_COMPRESSED_TEXTURE_FORMATS = 0x86A2; + public const int GL_COMPRESSED_TEXTURE_FORMATS = 0x86A3; + public const int GL_MULTISAMPLE = 0x809D; + public const int GL_SAMPLE_ALPHA_TO_COVERAGE = 0x809E; + public const int GL_SAMPLE_ALPHA_TO_ONE = 0x809F; + public const int GL_SAMPLE_COVERAGE = 0x80A0; + public const int GL_SAMPLE_BUFFERS = 0x80A8; + public const int GL_SAMPLES = 0x80A9; + public const int GL_SAMPLE_COVERAGE_VALUE = 0x80AA; + public const int GL_SAMPLE_COVERAGE_INVERT = 0x80AB; + public const int GL_MULTISAMPLE_BIT = 0x20000000; + public const int GL_TRANSPOSE_MODELVIEW_MATRIX = 0x84E3; + public const int GL_TRANSPOSE_PROJECTION_MATRIX = 0x84E4; + public const int GL_TRANSPOSE_TEXTURE_MATRIX = 0x84E5; + public const int GL_TRANSPOSE_COLOR_MATRIX = 0x84E6; + public const int GL_COMBINE = 0x8570; + public const int GL_COMBINE_RGB = 0x8571; + public const int GL_COMBINE_ALPHA = 0x8572; + public const int GL_SOURCE0_RGB = 0x8580; + public const int GL_SOURCE1_RGB = 0x8581; + public const int GL_SOURCE2_RGB = 0x8582; + public const int GL_SOURCE0_ALPHA = 0x8588; + public const int GL_SOURCE1_ALPHA = 0x8589; + public const int GL_SOURCE2_ALPHA = 0x858A; + public const int GL_OPERAND0_RGB = 0x8590; + public const int GL_OPERAND1_RGB = 0x8591; + public const int GL_OPERAND2_RGB = 0x8592; + public const int GL_OPERAND0_ALPHA = 0x8598; + public const int GL_OPERAND1_ALPHA = 0x8599; + public const int GL_OPERAND2_ALPHA = 0x859A; + public const int GL_RGB_SCALE = 0x8573; + public const int GL_ADD_SIGNED = 0x8574; + public const int GL_INTERPOLATE = 0x8575; + public const int GL_SUBTRACT = 0x84E7; + public const int GL_CONSTANT = 0x8576; + public const int GL_PRIMARY_COLOR = 0x8577; + public const int GL_PREVIOUS = 0x8578; + public const int GL_DOT3_RGB = 0x86AE; + public const int GL_DOT3_RGBA = 0x86AF; + public const int GL_CLAMP_TO_BORDER = 0x812D; + public const int GL_TEXTURE0_ARB = 0x84C0; + public const int GL_TEXTURE1_ARB = 0x84C1; + public const int GL_TEXTURE2_ARB = 0x84C2; + public const int GL_TEXTURE3_ARB = 0x84C3; + public const int GL_TEXTURE4_ARB = 0x84C4; + public const int GL_TEXTURE5_ARB = 0x84C5; + public const int GL_TEXTURE6_ARB = 0x84C6; + public const int GL_TEXTURE7_ARB = 0x84C7; + public const int GL_TEXTURE8_ARB = 0x84C8; + public const int GL_TEXTURE9_ARB = 0x84C9; + public const int GL_TEXTURE10_ARB = 0x84CA; + public const int GL_TEXTURE11_ARB = 0x84CB; + public const int GL_TEXTURE12_ARB = 0x84CC; + public const int GL_TEXTURE13_ARB = 0x84CD; + public const int GL_TEXTURE14_ARB = 0x84CE; + public const int GL_TEXTURE15_ARB = 0x84CF; + public const int GL_TEXTURE16_ARB = 0x84D0; + public const int GL_TEXTURE17_ARB = 0x84D1; + public const int GL_TEXTURE18_ARB = 0x84D2; + public const int GL_TEXTURE19_ARB = 0x84D3; + public const int GL_TEXTURE20_ARB = 0x84D4; + public const int GL_TEXTURE21_ARB = 0x84D5; + public const int GL_TEXTURE22_ARB = 0x84D6; + public const int GL_TEXTURE23_ARB = 0x84D7; + public const int GL_TEXTURE24_ARB = 0x84D8; + public const int GL_TEXTURE25_ARB = 0x84D9; + public const int GL_TEXTURE26_ARB = 0x84DA; + public const int GL_TEXTURE27_ARB = 0x84DB; + public const int GL_TEXTURE28_ARB = 0x84DC; + public const int GL_TEXTURE29_ARB = 0x84DD; + public const int GL_TEXTURE30_ARB = 0x84DE; + public const int GL_TEXTURE31_ARB = 0x84DF; + public const int GL_ACTIVE_TEXTURE_ARB = 0x84E0; + public const int GL_CLIENT_ACTIVE_TEXTURE_ARB = 0x84E1; + public const int GL_MAX_TEXTURE_UNITS_ARB = 0x84E2; + public const int GL_DEPTH_STENCIL_MESA = 0x8750; + public const int GL_UNSIGNED_INT_24_8_MESA = 0x8751; + public const int GL_UNSIGNED_INT_8_24_REV_MESA = 0x8752; + public const int GL_UNSIGNED_SHORT_15_1_MESA = 0x8753; + public const int GL_UNSIGNED_SHORT_1_15_REV_MESA = 0x8754; + public const int GL_ALPHA_BLEND_EQUATION_ATI = 0x883D; + + + // glext.h + + public const int GL_FRAMEBUFFER_BINDING = 0x8CA6; + } +} diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs new file mode 100644 index 0000000000..a29c6b9fed --- /dev/null +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.OpenGL +{ + public delegate IntPtr GlGetProcAddressDelegate(string procName); + + public class GlInterface : GlInterfaceBase + { + private readonly Func _getProcAddress; + + + public GlInterface(Func getProcAddress) : base(getProcAddress) + { + _getProcAddress = getProcAddress; + } + + public IntPtr GetProcAddress(string proc) => _getProcAddress(proc); + + public T GetProcAddress(string proc) => Marshal.GetDelegateForFunctionPointer(GetProcAddress(proc)); + + // ReSharper disable UnassignedGetOnlyAutoProperty + + public delegate void GlClearStencil(int s); + [EntryPoint("glClearStencil")] + public GlClearStencil ClearStencil { get; } + + public delegate void GlClearColor(int r, int g, int b, int a); + [EntryPoint("glClearColor")] + public GlClearColor ClearColor { get; } + + public delegate void GlClear(int bits); + [EntryPoint("glClear")] + public GlClear Clear { get; } + + public delegate void GlViewport(int x, int y, int width, int height); + [EntryPoint("glViewport")] + public GlViewport Viewport { get; } + + [EntryPoint("glFlush")] + public Action Flush { get; } + + public delegate void GlGetIntegerv(int name, out int rv); + [EntryPoint("glGetIntegerv")] + public GlGetIntegerv GetIntegerv { get; } + + + // ReSharper restore UnassignedGetOnlyAutoProperty + } +} diff --git a/src/Avalonia.OpenGL/GlInterfaceBase.cs b/src/Avalonia.OpenGL/GlInterfaceBase.cs new file mode 100644 index 0000000000..7c350767f6 --- /dev/null +++ b/src/Avalonia.OpenGL/GlInterfaceBase.cs @@ -0,0 +1,27 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Avalonia.OpenGL +{ + public class GlInterfaceBase + { + public GlInterfaceBase(Func getProcAddress) + { + foreach (var prop in this.GetType().GetProperties()) + { + var a = prop.GetCustomAttribute(); + if (a != null) + { + var fieldName = $"<{prop.Name}>k__BackingField"; + var field = prop.DeclaringType.GetField(fieldName, + BindingFlags.Instance | BindingFlags.NonPublic); + if (field == null) + throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}"); + field.SetValue(this, + Marshal.GetDelegateForFunctionPointer(getProcAddress(a.EntryPoint), prop.PropertyType)); + } + } + } + } +} diff --git a/src/Avalonia.OpenGL/IGlDisplay.cs b/src/Avalonia.OpenGL/IGlDisplay.cs new file mode 100644 index 0000000000..2d38878b45 --- /dev/null +++ b/src/Avalonia.OpenGL/IGlDisplay.cs @@ -0,0 +1,31 @@ +using System; + +namespace Avalonia.OpenGL +{ + public interface IGlDisplay + { + GlDisplayType Type { get; } + GlInterface GlInterface { get; } + void ClearContext(); + int SampleCount { get; } + int StencilSize { get; } + } + + public enum GlDisplayType + { + OpenGL2, + OpenGLES2 + } + + public interface IGlContext + { + IGlDisplay Display { get; } + void MakeCurrent(IGlSurface surface); + } + + public interface IGlSurface : IDisposable + { + IGlDisplay Display { get; } + void SwapBuffers(); + } +} diff --git a/src/Avalonia.OpenGL/IGlPlatformSurface.cs b/src/Avalonia.OpenGL/IGlPlatformSurface.cs new file mode 100644 index 0000000000..bf90612f8e --- /dev/null +++ b/src/Avalonia.OpenGL/IGlPlatformSurface.cs @@ -0,0 +1,22 @@ +using System; + +namespace Avalonia.OpenGL +{ + public interface IGlPlatformSurface + { + IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(); + } + + public interface IGlPlatformSurfaceRenderTarget : IDisposable + { + IGlPlatformSurfaceRenderingSession BeginDraw(); + } + + public interface IGlPlatformSurfaceRenderingSession : IDisposable + { + IGlDisplay Display { get; } + int PixelWidth { get; } + int PixelHeight { get; } + Vector Dpi { get; } + } +} diff --git a/src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs b/src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs new file mode 100644 index 0000000000..2c4a8b64b3 --- /dev/null +++ b/src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs @@ -0,0 +1,7 @@ +namespace Avalonia.OpenGL +{ + public interface IWindowingPlatformGlFeature + { + IGlContext ImmediateContext { get; } + } +} diff --git a/src/Avalonia.OpenGL/OpenGlException.cs b/src/Avalonia.OpenGL/OpenGlException.cs new file mode 100644 index 0000000000..7ba539a2b2 --- /dev/null +++ b/src/Avalonia.OpenGL/OpenGlException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.OpenGL +{ + public class OpenGlException : Exception + { + public OpenGlException(string message) : base(message) + { + + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index ef043959ad..aa569a5e6a 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -9,6 +9,7 @@ + \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 1ab0c7576c..9fae22b0a9 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -7,6 +7,7 @@ using Avalonia.Gtk3; using Avalonia.Gtk3.Interop; using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Platform.Interop; using Avalonia.Rendering; @@ -56,7 +57,7 @@ namespace Avalonia.Gtk3 .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new PlatformIconLoader()); - + EglGlPlatformFeature.TryInitialize(); } public IWindowImpl CreateWindow() => new WindowImpl(); diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 8cc7974c3e..27f4cf5b66 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -7,6 +7,7 @@ using Avalonia.Controls; using Avalonia.Gtk3.Interop; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Platform.Interop; using Avalonia.Rendering; @@ -14,12 +15,13 @@ using Avalonia.Threading; namespace Avalonia.Gtk3 { - abstract class WindowBaseImpl : IWindowBaseImpl, IPlatformHandle + abstract class WindowBaseImpl : IWindowBaseImpl, IPlatformHandle, IEglWindowGlPlatformSurfaceInfo { public readonly GtkWindow GtkWidget; private IInputRoot _inputRoot; private readonly GtkImContext _imContext; private readonly FramebufferManager _framebuffer; + private readonly EglGlPlatformSurface _egl; protected readonly List Disposables = new List(); private Size _lastSize; private Point _lastPosition; @@ -37,7 +39,13 @@ namespace Avalonia.Gtk3 { GtkWidget = gtkWidget; - _framebuffer = new FramebufferManager(this); + + var glf = AvaloniaLocator.Current.GetService() as EglGlPlatformFeature; + if (glf != null) + _egl = new EglGlPlatformSurface((EglDisplay)glf.Display, glf.DeferredContext, this); + else + _framebuffer = new FramebufferManager(this); + _imContext = Native.GtkImMulticontextNew(); Disposables.Add(_imContext); Native.GtkWidgetSetEvents(gtkWidget, 0xFFFFFE); @@ -58,12 +66,15 @@ namespace Avalonia.Gtk3 Native.GtkWidgetRealize(gtkWidget); GdkWindowHandle = this.Handle.Handle; _lastSize = ClientSize; - if (Gtk3Platform.UseDeferredRendering) + + if (_egl != null) + Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); + else if (Gtk3Platform.UseDeferredRendering) { Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); _gcHandle = GCHandle.Alloc(this); - _tickCallback = Native.GtkWidgetAddTickCallback(GtkWidget, PinnedStaticCallback, GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero); - + _tickCallback = Native.GtkWidgetAddTickCallback(GtkWidget, PinnedStaticCallback, + GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero); } } @@ -489,7 +500,7 @@ namespace Avalonia.Gtk3 } IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget)); - public IEnumerable Surfaces => new object[] {Handle, _framebuffer}; + public IEnumerable Surfaces => new object[] {Handle, _egl, _framebuffer}; public IRenderer CreateRenderer(IRenderRoot root) { @@ -498,5 +509,11 @@ namespace Avalonia.Gtk3 ? (IRenderer) new DeferredRenderer(root, loop) : new ImmediateRenderer(root); } + + + int IEglWindowGlPlatformSurfaceInfo.PixelWidth => (int)Math.Max(1, LastKnownScaleFactor * ClientSize.Width); + int IEglWindowGlPlatformSurfaceInfo.PixelHeight => (int)Math.Max(1, LastKnownScaleFactor * ClientSize.Height); + Vector IEglWindowGlPlatformSurfaceInfo.Dpi => new Vector(96*LastKnownScaleFactor, 96 * LastKnownScaleFactor); + IntPtr IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; } } diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj index 03618e00f5..d125ca0be4 100644 --- a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj +++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 685987ae80..f43bddd4d5 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -29,6 +29,7 @@ namespace Avalonia.Skia private double _currentOpacity = 1.0f; private readonly bool _canTextUseLcdRendering; private Matrix _currentTransform; + private GRContext _grContext; /// /// Context create info. @@ -54,6 +55,11 @@ namespace Avalonia.Skia /// Render text without Lcd rendering. /// public bool DisableTextLcdRendering; + + /// + /// GPU-accelerated context (optional) + /// + public GRContext GrContext; } /// @@ -67,7 +73,8 @@ namespace Avalonia.Skia _visualBrushRenderer = createInfo.VisualBrushRenderer; _disposables = disposables; _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering; - + _grContext = createInfo.GrContext; + Canvas = createInfo.Canvas; if (Canvas == null) @@ -615,7 +622,8 @@ namespace Avalonia.Skia Height = height, Dpi = dpi, Format = format, - DisableTextLcdRendering = !_canTextUseLcdRendering + DisableTextLcdRendering = !_canTextUseLcdRendering, + GrContext = _grContext }; return new SurfaceRenderTarget(createInfo); diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs new file mode 100644 index 0000000000..7ccff6d761 --- /dev/null +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -0,0 +1,62 @@ +using System; +using System.Reactive.Disposables; +using Avalonia.OpenGL; +using Avalonia.Platform; +using Avalonia.Rendering; +using SkiaSharp; +using static Avalonia.OpenGL.GlConsts; +namespace Avalonia.Skia +{ + public class GlRenderTarget : IRenderTarget + { + private readonly GRContext _grContext; + private IGlPlatformSurfaceRenderTarget _surface; + + public GlRenderTarget(GRContext grContext, IGlPlatformSurface glSurface) + { + _grContext = grContext; + _surface = glSurface.CreateGlRenderTarget(); + } + + public void Dispose() => _surface.Dispose(); + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + var session = _surface.BeginDraw(); + var disp = session.Display; + var gl = disp.GlInterface; + gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var fb); + GRBackendRenderTargetDesc desc = new GRBackendRenderTargetDesc + { + Width = session.PixelWidth, + Height = session.PixelHeight, + SampleCount = disp.SampleCount, + StencilBits = disp.StencilSize, + Config = GRPixelConfig.Rgba8888, + Origin=GRSurfaceOrigin.BottomLeft, + RenderTargetHandle = new IntPtr(fb) + }; + gl.Viewport(0, 0, desc.Width, desc.Height); + gl.ClearStencil(0); + gl.ClearColor(0, 0, 0, 0); + gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + var surface = SKSurface.Create(_grContext, desc); + + var nfo = new DrawingContextImpl.CreateInfo + { + GrContext = _grContext, + Canvas = surface.Canvas, + Dpi = session.Dpi, + VisualBrushRenderer = visualBrushRenderer, + DisableTextLcdRendering = true + }; + return new DrawingContextImpl(nfo, Disposable.Create(() => + { + surface.Canvas.Flush(); + surface.Dispose(); + session.Dispose(); + })); + + } + } +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index d4e6403dc9..5039ef7454 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -6,7 +6,9 @@ using System.Collections.Generic; using System.IO; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media; +using Avalonia.OpenGL; using Avalonia.Platform; +using SkiaSharp; namespace Avalonia.Skia { @@ -15,6 +17,22 @@ namespace Avalonia.Skia /// public class PlatformRenderInterface : IPlatformRenderInterface { + private GRContext GrContext { get; } + + public PlatformRenderInterface() + { + var gl = AvaloniaLocator.Current.GetService(); + if (gl != null) + { + var display = gl.ImmediateContext.Display; + var iface = display.Type == GlDisplayType.OpenGL2 + ? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc)) + : GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)); + gl.ImmediateContext.MakeCurrent(null); + GrContext = GRContext.Create(GRBackend.OpenGL, iface); + } + } + /// public IFormattedTextImpl CreateFormattedText( string text, @@ -78,7 +96,8 @@ namespace Avalonia.Skia Width = width, Height = height, Dpi = dpi, - DisableTextLcdRendering = false + DisableTextLcdRendering = false, + GrContext = GrContext }; return new SurfaceRenderTarget(createInfo); @@ -89,6 +108,10 @@ namespace Avalonia.Skia { foreach (var surface in surfaces) { + if (surface is IGlPlatformSurface glSurface) + { + return new GlRenderTarget(GrContext, glSurface); + } if (surface is IFramebufferPlatformSurface framebufferSurface) { return new FramebufferRenderTarget(framebufferSurface); @@ -105,4 +128,4 @@ namespace Avalonia.Skia return new WriteableBitmapImpl(width, height, format); } } -} \ No newline at end of file +} diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 88200dcfbe..914dc7d05a 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -19,6 +19,7 @@ namespace Avalonia.Skia private readonly SKSurface _surface; private readonly SKCanvas _canvas; private readonly bool _disableLcdRendering; + private readonly GRContext _grContext; /// /// Create new surface render target. @@ -30,8 +31,8 @@ namespace Avalonia.Skia PixelHeight = createInfo.Height; _dpi = createInfo.Dpi; _disableLcdRendering = createInfo.DisableTextLcdRendering; - - _surface = CreateSurface(PixelWidth, PixelHeight, createInfo.Format); + _grContext = createInfo.GrContext; + _surface = CreateSurface(createInfo.GrContext, PixelWidth, PixelHeight, createInfo.Format); _canvas = _surface?.Canvas; @@ -48,10 +49,11 @@ namespace Avalonia.Skia /// Height. /// Format. /// - private static SKSurface CreateSurface(int width, int height, PixelFormat? format) + private static SKSurface CreateSurface(GRContext gpu, int width, int height, PixelFormat? format) { var imageInfo = MakeImageInfo(width, height, format); - + if (gpu != null) + return SKSurface.Create(gpu, false, imageInfo); return SKSurface.Create(imageInfo); } @@ -73,7 +75,8 @@ namespace Avalonia.Skia Canvas = _canvas, Dpi = _dpi, VisualBrushRenderer = visualBrushRenderer, - DisableTextLcdRendering = _disableLcdRendering + DisableTextLcdRendering = _disableLcdRendering, + GrContext = _grContext }; return new DrawingContextImpl(createInfo); @@ -164,6 +167,11 @@ namespace Avalonia.Skia /// Render text without Lcd rendering. /// public bool DisableTextLcdRendering; + + /// + /// GPU-accelerated context (optional) + /// + public GRContext GrContext; } } -} \ No newline at end of file +} From a8004c7218c09e89ef9bc8bd94c601a1f84ef46e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 1 Oct 2018 13:43:02 +0300 Subject: [PATCH 090/118] Use PixelSize and Scaling for GL surfaces --- src/Avalonia.OpenGL/EglGlPlatformSurface.cs | 11 +++++------ src/Avalonia.OpenGL/IGlPlatformSurface.cs | 6 +++--- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 14 ++++++++++---- src/Skia/Avalonia.Skia/GlRenderTarget.cs | 8 +++++--- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs index 8be9e54d33..6eb4a510cb 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs @@ -5,9 +5,9 @@ namespace Avalonia.OpenGL public interface IEglWindowGlPlatformSurfaceInfo { IntPtr Handle { get; } - int PixelWidth { get; } - int PixelHeight { get; } - Vector Dpi { get; } + // TODO: Change to PixelSize struct once https://github.com/AvaloniaUI/Avalonia/pull/1889 is merged + System.Drawing.Size PixelSize { get; } + double Scaling { get; } } @@ -73,9 +73,8 @@ namespace Avalonia.OpenGL } public IGlDisplay Display => _context.Display; - public int PixelWidth => _info.PixelWidth; - public int PixelHeight => _info.PixelHeight; - public Vector Dpi => _info.Dpi; + public System.Drawing.Size PixelSize => _info.PixelSize; + public double Scaling => _info.Scaling; } } } diff --git a/src/Avalonia.OpenGL/IGlPlatformSurface.cs b/src/Avalonia.OpenGL/IGlPlatformSurface.cs index bf90612f8e..4e1adc37e5 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/IGlPlatformSurface.cs @@ -15,8 +15,8 @@ namespace Avalonia.OpenGL public interface IGlPlatformSurfaceRenderingSession : IDisposable { IGlDisplay Display { get; } - int PixelWidth { get; } - int PixelHeight { get; } - Vector Dpi { get; } + // TODO: Change to PixelSize struct once https://github.com/AvaloniaUI/Avalonia/pull/1889 is merged + System.Drawing.Size PixelSize { get; } + double Scaling { get; } } } diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 27f4cf5b66..1cae363d96 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -510,10 +510,16 @@ namespace Avalonia.Gtk3 : new ImmediateRenderer(root); } - - int IEglWindowGlPlatformSurfaceInfo.PixelWidth => (int)Math.Max(1, LastKnownScaleFactor * ClientSize.Width); - int IEglWindowGlPlatformSurfaceInfo.PixelHeight => (int)Math.Max(1, LastKnownScaleFactor * ClientSize.Height); - Vector IEglWindowGlPlatformSurfaceInfo.Dpi => new Vector(96*LastKnownScaleFactor, 96 * LastKnownScaleFactor); + System.Drawing.Size IEglWindowGlPlatformSurfaceInfo.PixelSize + { + get + { + var cs = ClientSize; + return new System.Drawing.Size((int)Math.Max(1, LastKnownScaleFactor * cs.Width), + (int)Math.Max(1, LastKnownScaleFactor * ClientSize.Height)); + } + } + double IEglWindowGlPlatformSurfaceInfo.Scaling => LastKnownScaleFactor; IntPtr IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; } } diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs index 7ccff6d761..6800bfb5b2 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -26,10 +26,12 @@ namespace Avalonia.Skia var disp = session.Display; var gl = disp.GlInterface; gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var fb); + var size = session.PixelSize; + var scaling = session.Scaling; GRBackendRenderTargetDesc desc = new GRBackendRenderTargetDesc { - Width = session.PixelWidth, - Height = session.PixelHeight, + Width = size.Width, + Height = size.Height, SampleCount = disp.SampleCount, StencilBits = disp.StencilSize, Config = GRPixelConfig.Rgba8888, @@ -46,7 +48,7 @@ namespace Avalonia.Skia { GrContext = _grContext, Canvas = surface.Canvas, - Dpi = session.Dpi, + Dpi = SkiaPlatform.DefaultDpi * scaling, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = true }; From 25919bf48a9060b8a065a4eb6ae3188d5a175f48 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 1 Oct 2018 13:43:57 +0300 Subject: [PATCH 091/118] Implemented ANGLE-based OpenGL support in Win32 backend --- src/Avalonia.OpenGL/EglConsts.cs | 18 +++++++++++ src/Avalonia.OpenGL/EglDisplay.cs | 31 ++++++++++++++++--- src/Avalonia.OpenGL/EglGlPlatformFeature.cs | 12 +++++-- src/Avalonia.OpenGL/EglInterface.cs | 12 ++++--- src/Avalonia.OpenGL/EntryPointAttribute.cs | 4 ++- src/Avalonia.OpenGL/GlInterface.cs | 6 ++-- src/Avalonia.OpenGL/GlInterfaceBase.cs | 7 +++-- .../Avalonia.Skia/PlatformRenderInterface.cs | 2 +- .../Avalonia.Win32/Avalonia.Win32.csproj | 1 + src/Windows/Avalonia.Win32/Win32GlManager.cs | 25 +++++++++++++++ src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 23 ++++++++++++-- 12 files changed, 122 insertions(+), 21 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/Win32GlManager.cs diff --git a/src/Avalonia.OpenGL/EglConsts.cs b/src/Avalonia.OpenGL/EglConsts.cs index bcb90d7382..62fb3faef6 100644 --- a/src/Avalonia.OpenGL/EglConsts.cs +++ b/src/Avalonia.OpenGL/EglConsts.cs @@ -174,5 +174,23 @@ namespace Avalonia.OpenGL public const int EGL_NO_IMAGE = 0; + public const int EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE = 0x3207; + public const int EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE = 0x3208; + + //EGL_ANGLE_platform_angle + public const int EGL_PLATFORM_ANGLE_ANGLE = 0x3202; + public const int EGL_PLATFORM_ANGLE_TYPE_ANGLE = 0x3203; + public const int EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE = 0x3204; + public const int EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE = 0x3205; + public const int EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED = 0x3451; + 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; + + //EGL_ANGLE_platform_angle_d3d + public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209; + 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; } } diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 852e2cae3c..aef7dc494e 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -14,10 +14,27 @@ namespace Avalonia.OpenGL public EglDisplay(EglInterface egl) { - _egl = egl; - _display = _egl.GetDisplay(IntPtr.Zero); + _egl = egl; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _egl.GetPlatformDisplayEXT != null) + { + foreach (var dapi in new[] {EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE}) + { + _display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, new[] + { + EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE + }); + if(_display != IntPtr.Zero) + break; + } + } + + if (_display == IntPtr.Zero) + _display = _egl.GetDisplay(IntPtr.Zero); + if(_display == IntPtr.Zero) throw new OpenGlException("eglGetDisplay failed"); + if (!_egl.Initialize(_display, out var major, out var minor)) throw new OpenGlException("eglInitialize failed"); @@ -67,10 +84,16 @@ namespace Avalonia.OpenGL if (_contextAttributes == null) throw new OpenGlException("No suitable EGL config was found"); - GlInterface = new GlInterface(proc => + GlInterface = new GlInterface((proc, optional) => { + using (var u = new Utf8Buffer(proc)) - return _egl.GetProcAddress(u); + { + var rv = _egl.GetProcAddress(u); + if (rv == IntPtr.Zero && !optional) + throw new OpenGlException("Missing function " + proc); + return rv; + } }); } diff --git a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs index f974a3d656..13304cae14 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs @@ -11,21 +11,29 @@ namespace Avalonia.OpenGL public IGlContext DeferredContext { get; set; } public static void TryInitialize() + { + var feature = TryCreate(); + if (feature != null) + AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); + } + + public static EglGlPlatformFeature TryCreate() { try { var disp = new EglDisplay(); var ctx = disp.CreateContext(null); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new EglGlPlatformFeature + return new EglGlPlatformFeature { Display = disp, ImmediateContext = ctx, DeferredContext = disp.CreateContext(ctx) - }); + }; } catch(Exception e) { Logger.Error("OpenGL", null, "Unable to initialize EGL-based rendering: {0}", e); + return null; } } } diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs index f80d3a7b44..025281ff74 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -16,21 +16,21 @@ namespace Avalonia.OpenGL { } - static Func Load() + 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) - return Load("libEGL.dll"); + return Load(@"libegl.dll"); throw new PlatformNotSupportedException(); } - static Func Load(string library) + static Func Load(string library) { var dyn = AvaloniaLocator.Current.GetService(); var lib = dyn.LoadLibrary(library); - return s => dyn.GetProcAddress(lib, s, false); + return (s, o) => dyn.GetProcAddress(lib, s, o); } @@ -38,6 +38,10 @@ namespace Avalonia.OpenGL public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay); [EntryPoint("eglGetDisplay")] public EglGetDisplay GetDisplay { get; } + + public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs); + [EntryPoint("eglGetPlatformDisplayEXT", true)] + public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; } public delegate bool EglInitialize(IntPtr display, out int major, out int minor); [EntryPoint("eglInitialize")] diff --git a/src/Avalonia.OpenGL/EntryPointAttribute.cs b/src/Avalonia.OpenGL/EntryPointAttribute.cs index cf8e3d4b09..241f517df9 100644 --- a/src/Avalonia.OpenGL/EntryPointAttribute.cs +++ b/src/Avalonia.OpenGL/EntryPointAttribute.cs @@ -5,10 +5,12 @@ namespace Avalonia.OpenGL class EntryPointAttribute : Attribute { public string EntryPoint { get; } + public bool Optional { get; } - public EntryPointAttribute(string entryPoint) + public EntryPointAttribute(string entryPoint, bool optional = false) { EntryPoint = entryPoint; + Optional = optional; } } } diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index a29c6b9fed..95479d027f 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -7,15 +7,15 @@ namespace Avalonia.OpenGL public class GlInterface : GlInterfaceBase { - private readonly Func _getProcAddress; + private readonly Func _getProcAddress; - public GlInterface(Func getProcAddress) : base(getProcAddress) + public GlInterface(Func getProcAddress) : base(getProcAddress) { _getProcAddress = getProcAddress; } - public IntPtr GetProcAddress(string proc) => _getProcAddress(proc); + public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true); public T GetProcAddress(string proc) => Marshal.GetDelegateForFunctionPointer(GetProcAddress(proc)); diff --git a/src/Avalonia.OpenGL/GlInterfaceBase.cs b/src/Avalonia.OpenGL/GlInterfaceBase.cs index 7c350767f6..33191a6567 100644 --- a/src/Avalonia.OpenGL/GlInterfaceBase.cs +++ b/src/Avalonia.OpenGL/GlInterfaceBase.cs @@ -6,7 +6,7 @@ namespace Avalonia.OpenGL { public class GlInterfaceBase { - public GlInterfaceBase(Func getProcAddress) + public GlInterfaceBase(Func getProcAddress) { foreach (var prop in this.GetType().GetProperties()) { @@ -18,8 +18,9 @@ namespace Avalonia.OpenGL BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) throw new InvalidProgramException($"Expected property {prop.Name} to have {fieldName}"); - field.SetValue(this, - Marshal.GetDelegateForFunctionPointer(getProcAddress(a.EntryPoint), prop.PropertyType)); + var proc = getProcAddress(a.EntryPoint, a.Optional); + if (proc != IntPtr.Zero) + field.SetValue(this, Marshal.GetDelegateForFunctionPointer(proc, prop.PropertyType)); } } } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 5039ef7454..80e2608dbe 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -108,7 +108,7 @@ namespace Avalonia.Skia { foreach (var surface in surfaces) { - if (surface is IGlPlatformSurface glSurface) + if (surface is IGlPlatformSurface glSurface && GrContext != null) { return new GlRenderTarget(GrContext, glSurface); } diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 5f26e4ad3e..1fb566dad0 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs new file mode 100644 index 0000000000..e0a96108ff --- /dev/null +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -0,0 +1,25 @@ +using Avalonia.OpenGL; + +namespace Avalonia.Win32 +{ + static class Win32GlManager + { + /// This property is initialized if drawing platform requests OpenGL support + public static EglGlPlatformFeature EglFeature { get; private set; } + + private static bool s_attemptedToInitialize; + public static void Initialize() + { + AvaloniaLocator.CurrentMutable.Bind().ToFunc(() => + { + if (!s_attemptedToInitialize) + { + EglFeature = EglGlPlatformFeature.TryCreate(); + s_attemptedToInitialize = true; + } + + return EglFeature; + }); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index ef2dfd3c1a..89943b5b0a 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -88,7 +88,7 @@ namespace Avalonia.Win32 .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); - + Win32GlManager.Initialize(); UseDeferredRendering = deferredRendering; _uiThread = UnmanagedMethods.GetCurrentThreadId(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 292d8a8138..d81f96a964 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Input; @@ -18,7 +19,7 @@ using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 { - public class WindowImpl : IWindowImpl + public class WindowImpl : IWindowImpl, IEglWindowGlPlatformSurfaceInfo { private static readonly List s_instances = new List(); @@ -37,6 +38,7 @@ namespace Avalonia.Win32 private WindowState _showWindowState; private WindowState _lastWindowState; private FramebufferManager _framebuffer; + private IGlPlatformSurface _gl; private OleDropTarget _dropTarget; private Size _minSize; private Size _maxSize; @@ -58,6 +60,10 @@ namespace Avalonia.Win32 #endif CreateWindow(); _framebuffer = new FramebufferManager(_hwnd); + if (Win32GlManager.EglFeature != null) + _gl = new EglGlPlatformSurface((EglDisplay)Win32GlManager.EglFeature.Display, + Win32GlManager.EglFeature.DeferredContext, this); + s_instances.Add(this); } @@ -211,7 +217,7 @@ namespace Avalonia.Win32 public IEnumerable Surfaces => new object[] { - Handle, _framebuffer + Handle, _gl, _framebuffer }; public void Activate() @@ -921,5 +927,18 @@ namespace Avalonia.Win32 _topmost = value; } + + System.Drawing.Size IEglWindowGlPlatformSurfaceInfo.PixelSize + { + get + { + RECT rect; + GetClientRect(_hwnd, out rect); + return new System.Drawing.Size( + Math.Max(1, rect.right - rect.left), + Math.Max(1, rect.bottom - rect.top)); + } + } + IntPtr IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; } } From d0c266949c82c157b4b7c76f2770f0cfeb78620f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 1 Oct 2018 14:00:13 +0300 Subject: [PATCH 092/118] Actually detect OS instead of relying on GetRuntimeInfo() --- src/Shared/PlatformSupport/StandardRuntimePlatformServices.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatformServices.cs b/src/Shared/PlatformSupport/StandardRuntimePlatformServices.cs index ad6535e91c..b34b8f7f5f 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatformServices.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatformServices.cs @@ -17,7 +17,7 @@ namespace Avalonia.Shared.PlatformSupport #if __IOS__ new IOSLoader() #else - standardPlatform.GetRuntimeInfo().OperatingSystem == OperatingSystemType.WinNT + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IDynamicLibraryLoader)new Win32Loader() : new UnixLoader() #endif From b53b3d590f6db78df737c878b1b3391e0334cada Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 24 Sep 2018 13:08:48 +0300 Subject: [PATCH 093/118] add failing unit test for listbox issue #1936 --- .../ListBoxTests.cs | 108 +++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index a5f5f8d328..eb3a6cf0c0 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -1,16 +1,17 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using System.Collections.ObjectModel; using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; -using Avalonia.Input; +using Avalonia.Data; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; -using Avalonia.Collections; namespace Avalonia.Controls.UnitTests { @@ -170,6 +171,109 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Size(100, 10), target.Scroll.Viewport); } + [Theory] + [InlineData(ItemVirtualizationMode.Simple)] + [InlineData(ItemVirtualizationMode.None)] + public void When_Added_Removed_AfterItems_Reset_Should_Work(ItemVirtualizationMode virtMode) + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = new ObservableCollection(); + + Action create = () => + { + foreach (var i in Enumerable.Range(1, 7)) + { + items.Add(i.ToString()); + } + }; + + create(); + + var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight }; + + wnd.IsVisible = true; + + var target = new ListBox() { VirtualizationMode = virtMode }; + + wnd.Content = target; + + var lm = wnd.LayoutManager; + + target.Height = 110;//working fine when <=106 or >=119 + target.Width = 50; + + target.ItemTemplate = new FuncDataTemplate(c => + { + var tb = new TextBlock() { Height = 10, Width = 30 }; + tb.Bind(TextBlock.TextProperty, new Binding()); + return tb; + }, true); + + target.DataContext = items; + + lm.ExecuteInitialLayoutPass(wnd); + + target.Bind(ItemsControl.ItemsProperty, new Binding()); + + lm.ExecuteLayoutPass(); + + var panel = target.Presenter.Panel; + + Func itemsToString = () => + string.Join(",", panel.Children.OfType().Select(l => l.Content.ToString()).ToArray()); + + Action addafter = (item, newitem) => + { + items.Insert(items.IndexOf(item) + 1, newitem); + + lm.ExecuteLayoutPass(); + }; + + Action remove = item => + { + items.Remove(item); + lm.ExecuteLayoutPass(); + }; + + addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 + + addafter("2", "2+");//expected 1,1+,2,2+,3,4,5,6 + + remove("2+");//expected 1,1+,2,3,4,5,6,7 + + //Reset items + items.Clear(); + create(); + + addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 + + addafter("2", "2+");//expected 1,1+,2,2+,3,4,5,6 + + remove("2+");//expected 1,1+,2,3,4,5,6,7 + + var sti = itemsToString(); + + var lbItems = panel.Children.OfType().ToArray(); + + Assert.Equal("1", lbItems[0].Content); + Assert.Equal("1+", lbItems[1].Content); + Assert.Equal("2", lbItems[2].Content); + Assert.Equal("3", lbItems[3].Content); //bug it's 2+ instead + Assert.Equal("4", lbItems[4].Content); + + int lbi = 0; + + //ensure all items are fine + foreach (var lb in lbItems) + { + Assert.Equal(items[lbi++], lb.Content); + } + + //Assert.Equal("1,1+,2,3,4,5,6,7", sti); + } + } + private FuncControlTemplate ListBoxTemplate() { return new FuncControlTemplate(parent => From 61d890660a4e265988aa2c6c22bbca93a94a3f09 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 1 Oct 2018 20:54:18 +0300 Subject: [PATCH 094/118] Specify EGL_STENCIL_SIZE and EGL_DEPTH_SIZE --- src/Avalonia.OpenGL/EglDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index aef7dc494e..20ed11d273 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -71,6 +71,8 @@ namespace Avalonia.OpenGL EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, + EGL_STENCIL_SIZE, 8, + EGL_DEPTH_SIZE, 8, EGL_NONE }; if (!_egl.ChooseConfig(_display, attribs, out _config, 1, out int numConfigs)) From 4f1552c6a220ee915919618fe9200fcef70493c1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 2 Oct 2018 14:49:52 +0300 Subject: [PATCH 095/118] Introduced several 5-line .cs files --- src/Avalonia.OpenGL/EglGlPlatformSurface.cs | 18 ++++++++--------- src/Avalonia.OpenGL/GlDisplayType.cs | 8 ++++++++ src/Avalonia.OpenGL/IGlContext.cs | 8 ++++++++ src/Avalonia.OpenGL/IGlDisplay.cs | 20 ------------------- src/Avalonia.OpenGL/IGlPlatformSurface.cs | 15 -------------- .../IGlPlatformSurfaceRenderTarget.cs | 9 +++++++++ .../IGlPlatformSurfaceRenderingSession.cs | 12 +++++++++++ src/Avalonia.OpenGL/IGlSurface.cs | 10 ++++++++++ src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 8 ++++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 6 +++--- 10 files changed, 63 insertions(+), 51 deletions(-) create mode 100644 src/Avalonia.OpenGL/GlDisplayType.cs create mode 100644 src/Avalonia.OpenGL/IGlContext.cs create mode 100644 src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs create mode 100644 src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs create mode 100644 src/Avalonia.OpenGL/IGlSurface.cs diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs index 6eb4a510cb..198c7de8fd 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs @@ -2,17 +2,17 @@ using System; namespace Avalonia.OpenGL { - public interface IEglWindowGlPlatformSurfaceInfo - { - IntPtr Handle { get; } - // TODO: Change to PixelSize struct once https://github.com/AvaloniaUI/Avalonia/pull/1889 is merged - System.Drawing.Size PixelSize { get; } - double Scaling { get; } - - } - public class EglGlPlatformSurface : IGlPlatformSurface { + public interface IEglWindowGlPlatformSurfaceInfo + { + IntPtr Handle { get; } + // TODO: Change to PixelSize struct once https://github.com/AvaloniaUI/Avalonia/pull/1889 is merged + System.Drawing.Size PixelSize { get; } + double Scaling { get; } + + } + private readonly EglDisplay _display; private readonly IGlContext _context; private readonly IEglWindowGlPlatformSurfaceInfo _info; diff --git a/src/Avalonia.OpenGL/GlDisplayType.cs b/src/Avalonia.OpenGL/GlDisplayType.cs new file mode 100644 index 0000000000..2e5178bc37 --- /dev/null +++ b/src/Avalonia.OpenGL/GlDisplayType.cs @@ -0,0 +1,8 @@ +namespace Avalonia.OpenGL +{ + public enum GlDisplayType + { + OpenGL2, + OpenGLES2 + } +} \ No newline at end of file diff --git a/src/Avalonia.OpenGL/IGlContext.cs b/src/Avalonia.OpenGL/IGlContext.cs new file mode 100644 index 0000000000..954567fe2e --- /dev/null +++ b/src/Avalonia.OpenGL/IGlContext.cs @@ -0,0 +1,8 @@ +namespace Avalonia.OpenGL +{ + public interface IGlContext + { + IGlDisplay Display { get; } + void MakeCurrent(IGlSurface surface); + } +} \ No newline at end of file diff --git a/src/Avalonia.OpenGL/IGlDisplay.cs b/src/Avalonia.OpenGL/IGlDisplay.cs index 2d38878b45..e7568bd5e7 100644 --- a/src/Avalonia.OpenGL/IGlDisplay.cs +++ b/src/Avalonia.OpenGL/IGlDisplay.cs @@ -1,5 +1,3 @@ -using System; - namespace Avalonia.OpenGL { public interface IGlDisplay @@ -10,22 +8,4 @@ namespace Avalonia.OpenGL int SampleCount { get; } int StencilSize { get; } } - - public enum GlDisplayType - { - OpenGL2, - OpenGLES2 - } - - public interface IGlContext - { - IGlDisplay Display { get; } - void MakeCurrent(IGlSurface surface); - } - - public interface IGlSurface : IDisposable - { - IGlDisplay Display { get; } - void SwapBuffers(); - } } diff --git a/src/Avalonia.OpenGL/IGlPlatformSurface.cs b/src/Avalonia.OpenGL/IGlPlatformSurface.cs index 4e1adc37e5..22d36b4472 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/IGlPlatformSurface.cs @@ -1,22 +1,7 @@ -using System; - namespace Avalonia.OpenGL { public interface IGlPlatformSurface { IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(); } - - public interface IGlPlatformSurfaceRenderTarget : IDisposable - { - IGlPlatformSurfaceRenderingSession BeginDraw(); - } - - public interface IGlPlatformSurfaceRenderingSession : IDisposable - { - IGlDisplay Display { get; } - // TODO: Change to PixelSize struct once https://github.com/AvaloniaUI/Avalonia/pull/1889 is merged - System.Drawing.Size PixelSize { get; } - double Scaling { get; } - } } diff --git a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs new file mode 100644 index 0000000000..53da93315c --- /dev/null +++ b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs @@ -0,0 +1,9 @@ +using System; + +namespace Avalonia.OpenGL +{ + public interface IGlPlatformSurfaceRenderTarget : IDisposable + { + IGlPlatformSurfaceRenderingSession BeginDraw(); + } +} \ No newline at end of file diff --git a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs new file mode 100644 index 0000000000..22641d7b53 --- /dev/null +++ b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.OpenGL +{ + public interface IGlPlatformSurfaceRenderingSession : IDisposable + { + IGlDisplay Display { get; } + // TODO: Change to PixelSize struct once https://github.com/AvaloniaUI/Avalonia/pull/1889 is merged + System.Drawing.Size PixelSize { get; } + double Scaling { get; } + } +} \ No newline at end of file diff --git a/src/Avalonia.OpenGL/IGlSurface.cs b/src/Avalonia.OpenGL/IGlSurface.cs new file mode 100644 index 0000000000..499d0a3c90 --- /dev/null +++ b/src/Avalonia.OpenGL/IGlSurface.cs @@ -0,0 +1,10 @@ +using System; + +namespace Avalonia.OpenGL +{ + public interface IGlSurface : IDisposable + { + IGlDisplay Display { get; } + void SwapBuffers(); + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 1cae363d96..0ec4047086 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -15,7 +15,7 @@ using Avalonia.Threading; namespace Avalonia.Gtk3 { - abstract class WindowBaseImpl : IWindowBaseImpl, IPlatformHandle, IEglWindowGlPlatformSurfaceInfo + abstract class WindowBaseImpl : IWindowBaseImpl, IPlatformHandle, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo { public readonly GtkWindow GtkWidget; private IInputRoot _inputRoot; @@ -510,7 +510,7 @@ namespace Avalonia.Gtk3 : new ImmediateRenderer(root); } - System.Drawing.Size IEglWindowGlPlatformSurfaceInfo.PixelSize + System.Drawing.Size EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.PixelSize { get { @@ -519,7 +519,7 @@ namespace Avalonia.Gtk3 (int)Math.Max(1, LastKnownScaleFactor * ClientSize.Height)); } } - double IEglWindowGlPlatformSurfaceInfo.Scaling => LastKnownScaleFactor; - IntPtr IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; + double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => LastKnownScaleFactor; + IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index d81f96a964..65580ff9c8 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -19,7 +19,7 @@ using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 { - public class WindowImpl : IWindowImpl, IEglWindowGlPlatformSurfaceInfo + public class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo { private static readonly List s_instances = new List(); @@ -928,7 +928,7 @@ namespace Avalonia.Win32 _topmost = value; } - System.Drawing.Size IEglWindowGlPlatformSurfaceInfo.PixelSize + System.Drawing.Size EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.PixelSize { get { @@ -939,6 +939,6 @@ namespace Avalonia.Win32 Math.Max(1, rect.bottom - rect.top)); } } - IntPtr IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; + IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; } } From ff23551c0128065cb980e64b08e41a0753931399 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 2 Oct 2018 15:10:17 +0300 Subject: [PATCH 096/118] Introduced GTK platform options with environment based configuration --- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 44 +++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 9fae22b0a9..e2e8a34676 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -24,8 +24,33 @@ namespace Avalonia.Gtk3 internal static string DisplayClassName; public static bool UseDeferredRendering = true; private static bool s_gtkInitialized; - public static void Initialize() + + static bool EnvOption(string option, bool def, bool? specified) { + bool? Parse(string env) + { + var v = Environment.GetEnvironmentVariable("AVALONIA_GTK3_" + env); + if (v == null) + return null; + if (v.ToLowerInvariant() == "false" || v == "0") + return false; + return true; + } + + var overridden = Parse(option + "_OVERRIDE"); + if (overridden.HasValue) + return overridden.Value; + if (specified.HasValue) + return specified.Value; + var envValue = Parse(option); + return envValue ?? def; + } + + public static void Initialize(Gtk3PlatformOptions options) + { + Resolver.Custom = options.CustomResolver; + UseDeferredRendering = EnvOption("USE_DEFERRED_RENDERING", true, options.UseDeferredRendering); + var useGpu = EnvOption("USE_GPU", false, options.UseGpuAcceleration); if (!s_gtkInitialized) { try @@ -57,7 +82,8 @@ namespace Avalonia.Gtk3 .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new PlatformIconLoader()); - EglGlPlatformFeature.TryInitialize(); + if (useGpu) + EglGlPlatformFeature.TryInitialize(); } public IWindowImpl CreateWindow() => new WindowImpl(); @@ -118,18 +144,24 @@ namespace Avalonia.Gtk3 public bool CurrentThreadIsLoopThread => s_tlsMarker; } + + public class Gtk3PlatformOptions + { + public bool? UseDeferredRendering { get; set; } + public bool? UseGpuAcceleration { get; set; } + public ICustomGtk3NativeLibraryResolver CustomResolver { get; set; } + } } namespace Avalonia { public static class Gtk3AppBuilderExtensions { - public static T UseGtk3(this AppBuilderBase builder, bool deferredRendering = true, ICustomGtk3NativeLibraryResolver resolver = null) + public static T UseGtk3(this AppBuilderBase builder, Gtk3PlatformOptions options = null) where T : AppBuilderBase, new() { - Resolver.Custom = resolver; - Gtk3Platform.UseDeferredRendering = deferredRendering; - return builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); + return builder.UseWindowingSubsystem(() => Gtk3Platform.Initialize(options ?? new Gtk3PlatformOptions()), + "GTK3"); } } } From ea5a7a8d1c7cf54b6890c9c607e62287346b27ee Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 2 Oct 2018 15:18:31 +0300 Subject: [PATCH 097/118] Pack Avalonia.OpenGL to the main package --- packages.cake | 1 + 1 file changed, 1 insertion(+) diff --git a/packages.cake b/packages.cake index 9defa3004c..fea45153f1 100644 --- a/packages.cake +++ b/packages.cake @@ -167,6 +167,7 @@ public class Packages new [] { "./src/", "Avalonia.Logging.Serilog"}, new [] { "./src/", "Avalonia.Visuals"}, new [] { "./src/", "Avalonia.Styling"}, + new [] { "./src/", "Avalonia.OpenGL"}, new [] { "./src/", "Avalonia.Themes.Default"}, new [] { "./src/Markup/", "Avalonia.Markup"}, new [] { "./src/Markup/", "Avalonia.Markup.Xaml"}, From f2bd597329a1b523f85fa85badb9b090ac3b89f4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 2 Oct 2018 13:30:04 +0100 Subject: [PATCH 098/118] whitespace. --- src/Avalonia.OpenGL/EglDisplay.cs | 3 +-- src/Avalonia.OpenGL/EglGlPlatformFeature.cs | 1 - src/Avalonia.OpenGL/EglGlPlatformSurface.cs | 3 +-- src/Avalonia.OpenGL/EglInterface.cs | 2 -- src/Avalonia.OpenGL/GlInterface.cs | 2 -- src/Skia/Avalonia.Skia/GlRenderTarget.cs | 7 ++++++- src/Windows/Avalonia.Win32/Win32GlManager.cs | 3 ++- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 20ed11d273..ea0a9bf087 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Runtime.InteropServices; using Avalonia.Platform.Interop; using static Avalonia.OpenGL.EglConsts; + namespace Avalonia.OpenGL { public class EglDisplay : IGlDisplay @@ -201,6 +202,4 @@ namespace Avalonia.OpenGL } } } - - } diff --git a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs index 13304cae14..535e32924e 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs @@ -3,7 +3,6 @@ using Avalonia.Logging; namespace Avalonia.OpenGL { - public class EglGlPlatformFeature : IWindowingPlatformGlFeature { public IGlDisplay Display { get; set; } diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs index 198c7de8fd..19894e4ec5 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs @@ -10,13 +10,11 @@ namespace Avalonia.OpenGL // TODO: Change to PixelSize struct once https://github.com/AvaloniaUI/Avalonia/pull/1889 is merged System.Drawing.Size PixelSize { get; } double Scaling { get; } - } private readonly EglDisplay _display; private readonly IGlContext _context; private readonly IEglWindowGlPlatformSurfaceInfo _info; - public EglGlPlatformSurface(EglDisplay display, IGlContext context, IEglWindowGlPlatformSurfaceInfo info) { @@ -79,3 +77,4 @@ namespace Avalonia.OpenGL } } } + diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs index 025281ff74..535f66ee1f 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -33,7 +33,6 @@ namespace Avalonia.OpenGL return (s, o) => dyn.GetProcAddress(lib, s, o); } - // ReSharper disable UnassignedGetOnlyAutoProperty public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay); [EntryPoint("eglGetDisplay")] @@ -90,6 +89,5 @@ namespace Avalonia.OpenGL public EglGetConfigAttrib GetConfigAttrib { get; } // ReSharper restore UnassignedGetOnlyAutoProperty - } } diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 95479d027f..60dc5381d4 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -9,7 +9,6 @@ namespace Avalonia.OpenGL { private readonly Func _getProcAddress; - public GlInterface(Func getProcAddress) : base(getProcAddress) { _getProcAddress = getProcAddress; @@ -44,7 +43,6 @@ namespace Avalonia.OpenGL [EntryPoint("glGetIntegerv")] public GlGetIntegerv GetIntegerv { get; } - // ReSharper restore UnassignedGetOnlyAutoProperty } } diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs index 6800bfb5b2..0435e1060a 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -5,6 +5,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using SkiaSharp; using static Avalonia.OpenGL.GlConsts; + namespace Avalonia.Skia { public class GlRenderTarget : IRenderTarget @@ -26,8 +27,10 @@ namespace Avalonia.Skia var disp = session.Display; var gl = disp.GlInterface; gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var fb); + var size = session.PixelSize; var scaling = session.Scaling; + GRBackendRenderTargetDesc desc = new GRBackendRenderTargetDesc { Width = size.Width, @@ -38,10 +41,12 @@ namespace Avalonia.Skia Origin=GRSurfaceOrigin.BottomLeft, RenderTargetHandle = new IntPtr(fb) }; + gl.Viewport(0, 0, desc.Width, desc.Height); gl.ClearStencil(0); gl.ClearColor(0, 0, 0, 0); gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + var surface = SKSurface.Create(_grContext, desc); var nfo = new DrawingContextImpl.CreateInfo @@ -52,13 +57,13 @@ namespace Avalonia.Skia VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = true }; + return new DrawingContextImpl(nfo, Disposable.Create(() => { surface.Canvas.Flush(); surface.Dispose(); session.Dispose(); })); - } } } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index e0a96108ff..585e68056b 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -7,7 +7,8 @@ namespace Avalonia.Win32 /// This property is initialized if drawing platform requests OpenGL support public static EglGlPlatformFeature EglFeature { get; private set; } - private static bool s_attemptedToInitialize; + private static bool s_attemptedToInitialize; + public static void Initialize() { AvaloniaLocator.CurrentMutable.Bind().ToFunc(() => From caa65545d7e1f1e9ffac0dc36908b5ef58474bfd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 2 Oct 2018 18:36:56 +0300 Subject: [PATCH 099/118] [SKIA] Call NotifyPixelsChanged when WritableBitmap gets unlocked --- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index ab6c399ff4..915aa77b52 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -126,6 +126,7 @@ namespace Avalonia.Skia /// public void Dispose() { + _bitmap.NotifyPixelsChanged(); _bitmap = null; } From 64629128ff00c97d86312957e7aba64d959cac4b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 3 Oct 2018 11:54:47 +0100 Subject: [PATCH 100/118] fixes a NRE in menu interaction handler --- src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index a44495b90c..6b03e67897 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -331,7 +331,7 @@ namespace Avalonia.Controls.Platform { var item = GetMenuItem(e.Source as IControl); - if (e.MouseButton == MouseButton.Left && item.HasSubMenu == false) + if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == false) { Click(item); e.Handled = true; From a865f6dddfcc7bfee925af7bdfc407601ead504b Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sat, 6 Oct 2018 01:41:08 +0300 Subject: [PATCH 101/118] introduce local functions for listbox issue #1936 --- .../ListBoxTests.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index eb3a6cf0c0..c6968f19f8 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -180,13 +180,13 @@ namespace Avalonia.Controls.UnitTests { var items = new ObservableCollection(); - Action create = () => + void create() { foreach (var i in Enumerable.Range(1, 7)) { items.Add(i.ToString()); } - }; + } create(); @@ -220,21 +220,20 @@ namespace Avalonia.Controls.UnitTests var panel = target.Presenter.Panel; - Func itemsToString = () => - string.Join(",", panel.Children.OfType().Select(l => l.Content.ToString()).ToArray()); + string itemsToString() => + string.Join(",", panel.Children.OfType().Select(l => l.Content.ToString()).ToArray()); - Action addafter = (item, newitem) => - { - items.Insert(items.IndexOf(item) + 1, newitem); - - lm.ExecuteLayoutPass(); - }; + void addafter(string item, string newitem) + { + items.Insert(items.IndexOf(item) + 1, newitem); + lm.ExecuteLayoutPass(); + } - Action remove = item => + void remove(string item) { items.Remove(item); lm.ExecuteLayoutPass(); - }; + } addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 @@ -276,7 +275,7 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate ListBoxTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate(parent => new ScrollViewer { Name = "PART_ScrollViewer", @@ -293,7 +292,7 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate ListBoxItemTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate(parent => new ContentPresenter { Name = "PART_ContentPresenter", From 6efba9837db65f3a0eef0674a9277f1e345c1731 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 7 Oct 2018 00:59:50 +0200 Subject: [PATCH 102/118] Simplified test. Simplified @donandren's test for #1936. --- .../ListBoxTests.cs | 113 +++--------------- 1 file changed, 18 insertions(+), 95 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index c6968f19f8..1debccd3c5 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.ObjectModel; using System.Linq; +using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; @@ -171,106 +172,28 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Size(100, 10), target.Scroll.Viewport); } - [Theory] - [InlineData(ItemVirtualizationMode.Simple)] - [InlineData(ItemVirtualizationMode.None)] - public void When_Added_Removed_AfterItems_Reset_Should_Work(ItemVirtualizationMode virtMode) + [Fact] + public void Containers_Correct_After_Clear_Add_Remove() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + // Issue #1936 + var items = new AvaloniaList(Enumerable.Range(0, 11).Select(x => $"Item {x}")); + var target = new ListBox { - var items = new ObservableCollection(); - - void create() - { - foreach (var i in Enumerable.Range(1, 7)) - { - items.Add(i.ToString()); - } - } - - create(); - - var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight }; - - wnd.IsVisible = true; - - var target = new ListBox() { VirtualizationMode = virtMode }; - - wnd.Content = target; - - var lm = wnd.LayoutManager; - - target.Height = 110;//working fine when <=106 or >=119 - target.Width = 50; - - target.ItemTemplate = new FuncDataTemplate(c => - { - var tb = new TextBlock() { Height = 10, Width = 30 }; - tb.Bind(TextBlock.TextProperty, new Binding()); - return tb; - }, true); - - target.DataContext = items; - - lm.ExecuteInitialLayoutPass(wnd); - - target.Bind(ItemsControl.ItemsProperty, new Binding()); - - lm.ExecuteLayoutPass(); - - var panel = target.Presenter.Panel; - - string itemsToString() => - string.Join(",", panel.Children.OfType().Select(l => l.Content.ToString()).ToArray()); - - void addafter(string item, string newitem) - { - items.Insert(items.IndexOf(item) + 1, newitem); - lm.ExecuteLayoutPass(); - } - - void remove(string item) - { - items.Remove(item); - lm.ExecuteLayoutPass(); - } - - addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 - - addafter("2", "2+");//expected 1,1+,2,2+,3,4,5,6 - - remove("2+");//expected 1,1+,2,3,4,5,6,7 - - //Reset items - items.Clear(); - create(); - - addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 - - addafter("2", "2+");//expected 1,1+,2,2+,3,4,5,6 - - remove("2+");//expected 1,1+,2,3,4,5,6,7 - - var sti = itemsToString(); - - var lbItems = panel.Children.OfType().ToArray(); - - Assert.Equal("1", lbItems[0].Content); - Assert.Equal("1+", lbItems[1].Content); - Assert.Equal("2", lbItems[2].Content); - Assert.Equal("3", lbItems[3].Content); //bug it's 2+ instead - Assert.Equal("4", lbItems[4].Content); + Template = ListBoxTemplate(), + Items = items, + ItemTemplate = new FuncDataTemplate(x => new TextBlock { Width = 20, Height = 10 }), + SelectedIndex = 0, + }; - int lbi = 0; + Prepare(target); - //ensure all items are fine - foreach (var lb in lbItems) - { - Assert.Equal(items[lbi++], lb.Content); - } + items.Clear(); + items.AddRange(Enumerable.Range(0, 11).Select(x => $"Item {x}")); + items.Remove("Item 2"); - //Assert.Equal("1,1+,2,3,4,5,6,7", sti); - } + Assert.Equal( + items, + target.Presenter.Panel.Children.Cast().Select(x => (string)x.Content)); } private FuncControlTemplate ListBoxTemplate() From c608163fe99412bf7ce1da9d898e08eeebef3341 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 7 Oct 2018 01:00:18 +0200 Subject: [PATCH 103/118] Ensure containers are ordered correctly. Fixes #1936. --- src/Avalonia.Controls/Generators/ItemContainerGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index 882d2f4ddd..f1a1f94a01 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Generators /// public class ItemContainerGenerator : IItemContainerGenerator { - private Dictionary _containers = new Dictionary(); + private SortedDictionary _containers = new SortedDictionary(); /// /// Initializes a new instance of the class. @@ -246,4 +246,4 @@ namespace Avalonia.Controls.Generators Recycled?.Invoke(this, e); } } -} \ No newline at end of file +} From dd528260febb3cfc88e7e39eb39e37758fa84417 Mon Sep 17 00:00:00 2001 From: artyom Date: Sun, 7 Oct 2018 02:18:10 +0300 Subject: [PATCH 104/118] feature: Support ReactiveUI WhenActivated --- .../AppBuilderExtensions.cs | 4 ++ .../AvaloniaActivationForViewFetcher.cs | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs diff --git a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs index 3eab54115a..d763febdf3 100644 --- a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs +++ b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Threading; using ReactiveUI; +using Splat; namespace Avalonia { @@ -15,6 +16,9 @@ namespace Avalonia return builder.AfterSetup(_ => { RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; + Locator.CurrentMutable.Register( + () => new AvaloniaActivationForViewFetcher(), + typeof(IActivationForViewFetcher)); }); } } diff --git a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs new file mode 100644 index 0000000000..828d8024e6 --- /dev/null +++ b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs @@ -0,0 +1,38 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Reflection; +using System.Reactive.Linq; +using Avalonia; +using Avalonia.VisualTree; +using ReactiveUI; + +namespace Avalonia +{ + public class AvaloniaActivationForViewFetcher : IActivationForViewFetcher + { + public int GetAffinityForView(Type view) + { + return typeof(IVisual).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0; + } + + public IObservable GetActivationForView(IActivatable view) + { + if (!(view is IVisual visual)) return Observable.Return(false); + var viewLoaded = Observable + .FromEventPattern( + x => visual.AttachedToVisualTree += x, + x => visual.DetachedFromVisualTree -= x) + .Select(args => true); + var viewUnloaded = Observable + .FromEventPattern( + x => visual.DetachedFromVisualTree += x, + x => visual.DetachedFromVisualTree -= x) + .Select(args => false); + return viewLoaded + .Merge(viewUnloaded) + .DistinctUntilChanged(); + } + } +} \ No newline at end of file From d36a55f6158cdc28186ff3b33eec9cbdb6f842ce Mon Sep 17 00:00:00 2001 From: artyom Date: Sun, 7 Oct 2018 11:45:04 +0300 Subject: [PATCH 105/118] Add ActivationForViewFetcher tests --- .../Avalonia.ReactiveUI.csproj | 5 +- .../Avalonia.ReactiveUI.UnitTests.csproj | 13 +++ .../AvaloniaActivationForViewFetcherTest.cs | 95 +++++++++++++++++++ 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj create mode 100644 tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index ae1ef60464..83dbfa41f9 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -12,6 +12,7 @@ - - + + + \ No newline at end of file diff --git a/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj b/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj new file mode 100644 index 0000000000..88f4e08886 --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj @@ -0,0 +1,13 @@ + + + netcoreapp2.0 + + + + + + + + + + diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs new file mode 100644 index 0000000000..a3c6b1a44a --- /dev/null +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs @@ -0,0 +1,95 @@ +using System; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using Avalonia.Controls; +using Avalonia.Rendering; +using Avalonia.Platform; +using Avalonia; +using ReactiveUI; +using DynamicData; +using Xunit; +using Splat; + +namespace Avalonia +{ + public class AvaloniaActivationForViewFetcherTest + { + public class TestUserControl : UserControl, IActivatable { } + + public class FakeRenderDecorator : Decorator, IRenderRoot + { + public Size ClientSize => new Size(100, 100); + + public IRenderer Renderer { get; } + + public double RenderScaling => 1; + + public IRenderTarget CreateRenderTarget() => null; + + public void Invalidate(Rect rect) { } + + public Point PointToClient(Point point) => point; + + public Point PointToScreen(Point point) => point; + } + + public class TestUserControlWithWhenActivated : UserControl, IActivatable + { + public bool Active { get; private set; } + + public TestUserControlWithWhenActivated() + { + this.WhenActivated(disposables => { + Active = true; + Disposable + .Create(() => Active = false) + .DisposeWith(disposables); + }); + } + } + + [Fact] + public void VisualElementIsActivatedAndDeactivated() + { + var userControl = new TestUserControl(); + var activationForViewFetcher = new AvaloniaActivationForViewFetcher(); + + activationForViewFetcher + .GetActivationForView(userControl) + .ToObservableChangeSet(scheduler: ImmediateScheduler.Instance) + .Bind(out var activated) + .Subscribe(); + + var fakeRenderedDecorator = new FakeRenderDecorator(); + fakeRenderedDecorator.Child = userControl; + Assert.True(activated[0]); + Assert.Equal(1, activated.Count); + + fakeRenderedDecorator.Child = null; + Assert.True(activated[0]); + Assert.False(activated[1]); + Assert.Equal(2, activated.Count); + } + + [Fact] + public void ActivationForViewFetcherShouldSupportWhenActivated() + { + var locator = new ModernDependencyResolver(); + locator.InitializeSplat(); + locator.InitializeReactiveUI(); + locator.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); + using (locator.WithResolver()) + { + var userControl = new TestUserControlWithWhenActivated(); + Assert.False(userControl.Active); + + var fakeRenderedDecorator = new FakeRenderDecorator(); + fakeRenderedDecorator.Child = userControl; + Assert.True(userControl.Active); + + fakeRenderedDecorator.Child = null; + Assert.False(userControl.Active); + } + } + } +} \ No newline at end of file From accdd502b8ea73f616ed6d8042448be0b925c2d5 Mon Sep 17 00:00:00 2001 From: artyom Date: Sun, 7 Oct 2018 11:48:24 +0300 Subject: [PATCH 106/118] Reference ReactiveUI the Avalonia way --- build/ReactiveUI.props | 2 +- src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props index acdfdd215a..1208be34b8 100644 --- a/build/ReactiveUI.props +++ b/build/ReactiveUI.props @@ -1,5 +1,5 @@ - + diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 83dbfa41f9..ae1ef60464 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -12,7 +12,6 @@ - - - + + \ No newline at end of file From 0153c769979986220f22c0177235a339ecd2aeed Mon Sep 17 00:00:00 2001 From: artyom Date: Sun, 7 Oct 2018 12:01:30 +0300 Subject: [PATCH 107/118] Add affinity tests --- .../AvaloniaActivationForViewFetcherTest.cs | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs index a3c6b1a44a..f07378fe0c 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs @@ -71,25 +71,35 @@ namespace Avalonia Assert.Equal(2, activated.Count); } + [Fact] + public void GetAffinityForViewShouldReturnNonZeroForVisualElements() + { + var userControl = new TestUserControl(); + var activationForViewFetcher = new AvaloniaActivationForViewFetcher(); + + var forUserControl = activationForViewFetcher.GetAffinityForView(userControl.GetType()); + var forNonUserControl = activationForViewFetcher.GetAffinityForView(typeof(object)); + + Assert.NotEqual(0, forUserControl); + Assert.Equal(0, forNonUserControl); + } + [Fact] public void ActivationForViewFetcherShouldSupportWhenActivated() { - var locator = new ModernDependencyResolver(); - locator.InitializeSplat(); - locator.InitializeReactiveUI(); - locator.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); - using (locator.WithResolver()) - { - var userControl = new TestUserControlWithWhenActivated(); - Assert.False(userControl.Active); + Locator.CurrentMutable.RegisterConstant( + new AvaloniaActivationForViewFetcher(), + typeof(IActivationForViewFetcher)); - var fakeRenderedDecorator = new FakeRenderDecorator(); - fakeRenderedDecorator.Child = userControl; - Assert.True(userControl.Active); + var userControl = new TestUserControlWithWhenActivated(); + Assert.False(userControl.Active); - fakeRenderedDecorator.Child = null; - Assert.False(userControl.Active); - } + var fakeRenderedDecorator = new FakeRenderDecorator(); + fakeRenderedDecorator.Child = userControl; + Assert.True(userControl.Active); + + fakeRenderedDecorator.Child = null; + Assert.False(userControl.Active); } } } \ No newline at end of file From cdc6033ed2a5eb54cee9d72b67332246a6f621f1 Mon Sep 17 00:00:00 2001 From: artyom Date: Sun, 7 Oct 2018 12:02:53 +0300 Subject: [PATCH 108/118] Use Avalonia code style for test methods names --- .../AvaloniaActivationForViewFetcherTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs index f07378fe0c..694d75f5b7 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs @@ -49,7 +49,7 @@ namespace Avalonia } [Fact] - public void VisualElementIsActivatedAndDeactivated() + public void Visual_Element_Is_Activated_And_Deactivated() { var userControl = new TestUserControl(); var activationForViewFetcher = new AvaloniaActivationForViewFetcher(); @@ -72,7 +72,7 @@ namespace Avalonia } [Fact] - public void GetAffinityForViewShouldReturnNonZeroForVisualElements() + public void Get_Affinity_For_View_Should_Return_Non_Zero_For_Visual_Elements() { var userControl = new TestUserControl(); var activationForViewFetcher = new AvaloniaActivationForViewFetcher(); @@ -85,7 +85,7 @@ namespace Avalonia } [Fact] - public void ActivationForViewFetcherShouldSupportWhenActivated() + public void Activation_For_View_Fetcher_Should_Support_When_Activated() { Locator.CurrentMutable.RegisterConstant( new AvaloniaActivationForViewFetcher(), From ebcbe99c4e4d45204891dfcfdf4cc45cc04e76e4 Mon Sep 17 00:00:00 2001 From: artyom Date: Sun, 7 Oct 2018 14:03:38 +0300 Subject: [PATCH 109/118] Add tests for ReactiveUI 9.0.1 --- build.cake | 1 + .../ViewModels/MainWindowViewModel.cs | 1 + .../Avalonia.ReactiveUI.UnitTests.csproj | 3 ++- .../AvaloniaActivationForViewFetcherTest.cs | 22 +++---------------- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/build.cake b/build.cake index 24f529ee4a..56653109ae 100644 --- a/build.cake +++ b/build.cake @@ -170,6 +170,7 @@ Task("Run-Unit-Tests-Impl") RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false); RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false); RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false); + RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", data.Parameters, false); if (data.Parameters.IsRunningOnWindows) { RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, false); diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs index eb08ef9656..eba17f92e4 100644 --- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs @@ -7,6 +7,7 @@ using System.Linq; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Primitives; +using ReactiveUI.Legacy; using ReactiveUI; namespace VirtualizationDemo.ViewModels diff --git a/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj b/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj index 88f4e08886..7c0ff79183 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj +++ b/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj @@ -8,6 +8,7 @@ - + + diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs index 694d75f5b7..97701f8437 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs @@ -4,6 +4,7 @@ using System.Reactive.Disposables; using Avalonia.Controls; using Avalonia.Rendering; using Avalonia.Platform; +using Avalonia.UnitTests; using Avalonia; using ReactiveUI; using DynamicData; @@ -16,23 +17,6 @@ namespace Avalonia { public class TestUserControl : UserControl, IActivatable { } - public class FakeRenderDecorator : Decorator, IRenderRoot - { - public Size ClientSize => new Size(100, 100); - - public IRenderer Renderer { get; } - - public double RenderScaling => 1; - - public IRenderTarget CreateRenderTarget() => null; - - public void Invalidate(Rect rect) { } - - public Point PointToClient(Point point) => point; - - public Point PointToScreen(Point point) => point; - } - public class TestUserControlWithWhenActivated : UserControl, IActivatable { public bool Active { get; private set; } @@ -60,7 +44,7 @@ namespace Avalonia .Bind(out var activated) .Subscribe(); - var fakeRenderedDecorator = new FakeRenderDecorator(); + var fakeRenderedDecorator = new TestRoot(); fakeRenderedDecorator.Child = userControl; Assert.True(activated[0]); Assert.Equal(1, activated.Count); @@ -94,7 +78,7 @@ namespace Avalonia var userControl = new TestUserControlWithWhenActivated(); Assert.False(userControl.Active); - var fakeRenderedDecorator = new FakeRenderDecorator(); + var fakeRenderedDecorator = new TestRoot(); fakeRenderedDecorator.Child = userControl; Assert.True(userControl.Active); From f907139a5c5195bf70da2b633cc30c8d3e1f9eb2 Mon Sep 17 00:00:00 2001 From: ahopper Date: Mon, 8 Oct 2018 12:54:23 +0100 Subject: [PATCH 110/118] make MONITORINFO a struct --- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 15 ++++++++++----- src/Windows/Avalonia.Win32/ScreenImpl.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index d6b95bc7b0..c7fce95fbc 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1055,12 +1055,17 @@ namespace Avalonia.Win32.Interop } [StructLayout(LayoutKind.Sequential)] - internal class MONITORINFO + internal struct MONITORINFO { - public int cbSize = Marshal.SizeOf(); - public RECT rcMonitor = new RECT(); - public RECT rcWork = new RECT(); - public int dwFlags = 0; + public int cbSize; + public RECT rcMonitor; + public RECT rcWork; + public int dwFlags; + + public static MONITORINFO NewMONITORINFO() + { + return new MONITORINFO() { cbSize = Marshal.SizeOf() }; + } public enum MonitorOptions : uint { diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index e1df24151d..6d56a5e213 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -26,7 +26,7 @@ namespace Avalonia.Win32 EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) => { - MONITORINFO monitorInfo = new MONITORINFO(); + MONITORINFO monitorInfo = MONITORINFO.NewMONITORINFO(); if (GetMonitorInfo(monitor, monitorInfo)) { RECT bounds = monitorInfo.rcMonitor; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 65580ff9c8..bd37aa6109 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -837,7 +837,7 @@ namespace Avalonia.Win32 if (monitor != IntPtr.Zero) { - MONITORINFO monitorInfo = new MONITORINFO(); + MONITORINFO monitorInfo = MONITORINFO.NewMONITORINFO(); if (GetMonitorInfo(monitor, monitorInfo)) { From 5786d51853d50d57a473949144a3bad39b01eac7 Mon Sep 17 00:00:00 2001 From: ahopper Date: Mon, 8 Oct 2018 13:51:38 +0100 Subject: [PATCH 111/118] pass MONITORINFO as ref --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- src/Windows/Avalonia.Win32/ScreenImpl.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c7fce95fbc..a2801bb170 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -987,7 +987,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32", EntryPoint = "GetMonitorInfoW", ExactSpelling = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetMonitorInfo([In] IntPtr hMonitor, [Out] MONITORINFO lpmi); + public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi); [return: MarshalAs(UnmanagedType.Bool)] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")] diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 6d56a5e213..81f6b0e65c 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -27,7 +27,7 @@ namespace Avalonia.Win32 (IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) => { MONITORINFO monitorInfo = MONITORINFO.NewMONITORINFO(); - if (GetMonitorInfo(monitor, monitorInfo)) + if (GetMonitorInfo(monitor,ref monitorInfo)) { RECT bounds = monitorInfo.rcMonitor; RECT workingArea = monitorInfo.rcWork; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bd37aa6109..bbae965a22 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -839,7 +839,7 @@ namespace Avalonia.Win32 { MONITORINFO monitorInfo = MONITORINFO.NewMONITORINFO(); - if (GetMonitorInfo(monitor, monitorInfo)) + if (GetMonitorInfo(monitor,ref monitorInfo)) { RECT rcMonitorArea = monitorInfo.rcMonitor; From 81b58e955f06782f2b89af5a8084df5e64a7b558 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 8 Oct 2018 23:40:41 +0300 Subject: [PATCH 112/118] unit tests for textbox OutOfRangeException issue #1954 --- .../TextBoxTests.cs | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 4a8c171ecf..8fbbfdd40b 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -8,7 +8,6 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; -using Avalonia.Markup.Data; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; @@ -322,6 +321,71 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void SelectionEnd_Dont_Cause_Exception() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "0123456789" + }; + + target.SelectionStart = 0; + target.SelectionEnd = 9; + + target.Text = "123"; + + RaiseTextEvent(target, "456"); + + Assert.True(true); + } + } + + [Fact] + public void SelectionStart_Dont_Cause_Exception() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "0123456789" + }; + + target.SelectionStart = 8; + target.SelectionEnd = 9; + + target.Text = "123"; + + RaiseTextEvent(target, "456"); + + Assert.True(true); + } + } + + [Fact] + public void SelectionStartEnd_Are_Valid_AterTextChange() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "0123456789" + }; + + target.SelectionStart = 8; + target.SelectionEnd = 9; + + target.Text = "123"; + + Assert.True(target.SelectionStart <= "123".Length); + Assert.True(target.SelectionEnd <= "123".Length); + } + } + private static TestServices Services => TestServices.MockThreadingInterface.With( standardCursorFactory: Mock.Of()); @@ -351,6 +415,15 @@ namespace Avalonia.Controls.UnitTests }); } + private void RaiseTextEvent(TextBox textBox, string text) + { + textBox.RaiseEvent(new TextInputEventArgs + { + RoutedEvent = InputElement.TextInputEvent, + Text = text + }); + } + private class Class1 : NotifyingBase { private int _foo; From 0959be5e01ea2e15d83a2f8a6b5179d1bf31465f Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 8 Oct 2018 23:48:19 +0300 Subject: [PATCH 113/118] fix for textbox OutOfRangeException issue #1954 --- src/Avalonia.Controls/TextBox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 388f984b78..f8f0667759 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -213,6 +213,8 @@ namespace Avalonia.Controls if (!_ignoreTextChanges) { CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0); + SelectionStart = CoerceCaretIndex(SelectionStart, value?.Length ?? 0); + SelectionEnd = CoerceCaretIndex(SelectionEnd, value?.Length ?? 0); if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing) { From 3b302006fdd1eae03bf3460e40f2f5a9f6852e23 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 9 Oct 2018 14:48:02 +0300 Subject: [PATCH 114/118] fix failing text e.g. restore carretIndex in textbox when text changed --- src/Avalonia.Controls/TextBox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index f8f0667759..2f29d229e5 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -212,9 +212,10 @@ namespace Avalonia.Controls { if (!_ignoreTextChanges) { - CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0); + var carretIndex = CaretIndex; SelectionStart = CoerceCaretIndex(SelectionStart, value?.Length ?? 0); SelectionEnd = CoerceCaretIndex(SelectionEnd, value?.Length ?? 0); + CaretIndex = CoerceCaretIndex(carretIndex, value?.Length ?? 0); if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing) { From 314ada4e4a5d0d0a02599beec132257b78bdc602 Mon Sep 17 00:00:00 2001 From: ahopper Date: Tue, 9 Oct 2018 21:56:52 +0100 Subject: [PATCH 115/118] renamed NewMONITORINFO to CreateMONITORINFO --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- src/Windows/Avalonia.Win32/ScreenImpl.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index a2801bb170..c8059ee169 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1062,7 +1062,7 @@ namespace Avalonia.Win32.Interop public RECT rcWork; public int dwFlags; - public static MONITORINFO NewMONITORINFO() + public static MONITORINFO CreateMONITORINFO() { return new MONITORINFO() { cbSize = Marshal.SizeOf() }; } diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 81f6b0e65c..b02eb64215 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -26,7 +26,7 @@ namespace Avalonia.Win32 EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) => { - MONITORINFO monitorInfo = MONITORINFO.NewMONITORINFO(); + MONITORINFO monitorInfo = MONITORINFO.CreateMONITORINFO(); if (GetMonitorInfo(monitor,ref monitorInfo)) { RECT bounds = monitorInfo.rcMonitor; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bbae965a22..114a57eb6d 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -837,7 +837,7 @@ namespace Avalonia.Win32 if (monitor != IntPtr.Zero) { - MONITORINFO monitorInfo = MONITORINFO.NewMONITORINFO(); + MONITORINFO monitorInfo = MONITORINFO.CreateMONITORINFO(); if (GetMonitorInfo(monitor,ref monitorInfo)) { From 11d676ac0b816632f1163aafc92dae9d3aab01f8 Mon Sep 17 00:00:00 2001 From: ahopper Date: Tue, 9 Oct 2018 22:03:20 +0100 Subject: [PATCH 116/118] rename CreateMONITORINFO to Create --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- src/Windows/Avalonia.Win32/ScreenImpl.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c8059ee169..5fe889f41d 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1062,7 +1062,7 @@ namespace Avalonia.Win32.Interop public RECT rcWork; public int dwFlags; - public static MONITORINFO CreateMONITORINFO() + public static MONITORINFO Create() { return new MONITORINFO() { cbSize = Marshal.SizeOf() }; } diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index b02eb64215..5cfceb68b7 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -26,7 +26,7 @@ namespace Avalonia.Win32 EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) => { - MONITORINFO monitorInfo = MONITORINFO.CreateMONITORINFO(); + MONITORINFO monitorInfo = MONITORINFO.Create(); if (GetMonitorInfo(monitor,ref monitorInfo)) { RECT bounds = monitorInfo.rcMonitor; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 114a57eb6d..45120fa21b 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -837,7 +837,7 @@ namespace Avalonia.Win32 if (monitor != IntPtr.Zero) { - MONITORINFO monitorInfo = MONITORINFO.CreateMONITORINFO(); + MONITORINFO monitorInfo = MONITORINFO.Create(); if (GetMonitorInfo(monitor,ref monitorInfo)) { From 29d02f0ad2b21269148128988645c3a17363b76e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 10 Oct 2018 12:44:46 +0100 Subject: [PATCH 117/118] Call SetProcessDPIAware() on Win32 backend so that the application is guarenteed to see the real dpi. --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 3 +++ src/Windows/Avalonia.Win32/Win32Platform.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 5fe889f41d..c3c867d3fb 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -976,6 +976,9 @@ namespace Avalonia.Win32.Interop [DllImport("shcore.dll")] public static extern void GetScaleFactorForMonitor(IntPtr hMon, out uint pScale); + [DllImport("user32.dll", SetLastError = true)] + public static extern bool SetProcessDPIAware(); + [DllImport("user32.dll")] public static extern IntPtr MonitorFromPoint(POINT pt, MONITOR dwFlags); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 89943b5b0a..5041942c63 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -77,6 +77,8 @@ namespace Avalonia.Win32 public static void Initialize(bool deferredRendering = true) { + UnmanagedMethods.SetProcessDPIAware(); + AvaloniaLocator.CurrentMutable .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) From eabf122ce76711aa8a3c12f04e9b1a56ff537e76 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Wed, 10 Oct 2018 18:08:54 +0300 Subject: [PATCH 118/118] fix nits --- src/Avalonia.Controls/TextBox.cs | 4 ++-- tests/Avalonia.Controls.UnitTests/TextBoxTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 2f29d229e5..e2d2f562ec 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -212,10 +212,10 @@ namespace Avalonia.Controls { if (!_ignoreTextChanges) { - var carretIndex = CaretIndex; + var caretIndex = CaretIndex; SelectionStart = CoerceCaretIndex(SelectionStart, value?.Length ?? 0); SelectionEnd = CoerceCaretIndex(SelectionEnd, value?.Length ?? 0); - CaretIndex = CoerceCaretIndex(carretIndex, value?.Length ?? 0); + CaretIndex = CoerceCaretIndex(caretIndex, value?.Length ?? 0); if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing) { diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 8fbbfdd40b..0d87f6d0fe 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -322,7 +322,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void SelectionEnd_Dont_Cause_Exception() + public void SelectionEnd_Doesnt_Cause_Exception() { using (UnitTestApplication.Start(Services)) { @@ -344,7 +344,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void SelectionStart_Dont_Cause_Exception() + public void SelectionStart_Doesnt_Cause_Exception() { using (UnitTestApplication.Start(Services)) {