From e7247a2c12f383d7cd6e26136a1d96fab9f3102f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Aug 2017 01:17:19 +0200 Subject: [PATCH 01/20] Added methods for creating D2D render layers. Craetes a compatible render target depending on the type of `IRenderTargetBitmapImpl`. --- .../Direct3DInteropSample/MainWindow.cs | 3 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 4 +- .../ExternalRenderTarget.cs | 15 ++-- .../Avalonia.Direct2D1/ICreateLayer.cs | 10 +++ .../Media/DrawingContextImpl.cs | 13 ++-- .../Media/ImageBrushImpl.cs | 7 +- .../Media/Imaging/BitmapImpl.cs | 32 +++++++-- .../Media/Imaging/D2DBitmapImpl.cs | 47 +++++++------ .../Imaging/D2DRenderTargetBitmapImpl.cs | 68 +++++++++++++++++++ .../Media/Imaging/WicBitmapImpl.cs | 57 +++++----------- ...apImpl.cs => WicRenderTargetBitmapImpl.cs} | 6 +- .../Avalonia.Direct2D1/OptionalDispose.cs | 22 ++++++ .../Avalonia.Direct2D1/RenderTarget.cs | 22 ++---- .../SwapChainRenderTarget.cs | 40 ++++++----- 14 files changed, 225 insertions(+), 121 deletions(-) create mode 100644 src/Windows/Avalonia.Direct2D1/ICreateLayer.cs create mode 100644 src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs rename src/Windows/Avalonia.Direct2D1/Media/Imaging/{RenderTargetBitmapImpl.cs => WicRenderTargetBitmapImpl.cs} (89%) create mode 100644 src/Windows/Avalonia.Direct2D1/OptionalDispose.cs diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index ad40e81895..3fe3b896f6 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -254,7 +254,8 @@ namespace Direct3DInteropSample public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget, - AvaloniaLocator.Current.GetService()); + AvaloniaLocator.Current.GetService(), + AvaloniaLocator.Current.GetService()); } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index fd8364c03b..eedb8a0c4e 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -145,7 +145,7 @@ namespace Avalonia.Direct2D1 return new HwndRenderTarget(nativeWindow); } if (s is IExternalDirect2DRenderTargetSurface external) - return new ExternalRenderTarget(external, s_dwfactory); + return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory); } throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } @@ -156,7 +156,7 @@ namespace Avalonia.Direct2D1 double dpiX, double dpiY) { - return new RenderTargetBitmapImpl( + return new WicRenderTargetBitmapImpl( s_imagingFactory, s_d2D1Factory, s_dwfactory, diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index 307048f7b4..d735c5f8bb 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Direct2D1.Media; using Avalonia.Platform; using Avalonia.Rendering; @@ -15,11 +11,16 @@ namespace Avalonia.Direct2D1 { private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; private readonly DirectWriteFactory _dwFactory; - public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, - DirectWriteFactory dwFactory) + private readonly SharpDX.WIC.ImagingFactory _wicFactory; + + public ExternalRenderTarget( + IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, + DirectWriteFactory dwFactory, + SharpDX.WIC.ImagingFactory wicFactory) { _externalRenderTargetProvider = externalRenderTargetProvider; _dwFactory = dwFactory; + _wicFactory = wicFactory; } public void Dispose() @@ -31,7 +32,7 @@ namespace Avalonia.Direct2D1 { var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); _externalRenderTargetProvider.BeforeDrawing(); - return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () => + return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, _wicFactory, null, () => { try { diff --git a/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs b/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs new file mode 100644 index 0000000000..9214377e6f --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs @@ -0,0 +1,10 @@ +using System; +using Avalonia.Platform; + +namespace Avalonia.Direct2D1 +{ + internal interface ICreateLayer + { + IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight); + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 69b582b009..a7bb73e19f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -2,16 +2,13 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections; using System.Collections.Generic; using Avalonia.Media; using Avalonia.Platform; -using Avalonia.RenderHelpers; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; using SharpDX.Mathematics.Interop; -using IBitmap = Avalonia.Media.Imaging.IBitmap; namespace Avalonia.Direct2D1.Media { @@ -24,6 +21,7 @@ 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; /// @@ -32,12 +30,14 @@ namespace Avalonia.Direct2D1.Media /// The visual brush renderer. /// The render target to draw to. /// 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, SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.DirectWrite.Factory directWriteFactory, + SharpDX.WIC.ImagingFactory imagingFactory, SharpDX.DXGI.SwapChain1 swapChain = null, Action finishedCallback = null) { @@ -46,6 +46,7 @@ namespace Avalonia.Direct2D1.Media _swapChain = swapChain; _finishedCallback = finishedCallback; _directWriteFactory = directWriteFactory; + _imagingFactory = imagingFactory; _swapChain = swapChain; _renderTarget.BeginDraw(); } @@ -97,7 +98,7 @@ namespace Avalonia.Direct2D1.Media using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) { _renderTarget.DrawBitmap( - d2d, + d2d.Value, destRect.ToSharpDX(), (float)opacity, BitmapInterpolationMode.Linear, @@ -115,7 +116,7 @@ namespace Avalonia.Direct2D1.Media public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) - using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource)) + using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value)) using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) { @@ -397,7 +398,7 @@ namespace Avalonia.Direct2D1.Media return new ImageBrushImpl( visualBrush, _renderTarget, - new D2DBitmapImpl(intermediate.Bitmap), + new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap), destinationSize); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index ed3d78b4fd..08cfed2ace 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -10,6 +10,8 @@ namespace Avalonia.Direct2D1.Media { public sealed class ImageBrushImpl : BrushImpl { + OptionalDispose _bitmap; + public ImageBrushImpl( ITileBrush brush, SharpDX.Direct2D1.RenderTarget target, @@ -20,9 +22,10 @@ namespace Avalonia.Direct2D1.Media if (!calc.NeedsIntermediate) { + _bitmap = bitmap.GetDirect2DBitmap(target); PlatformBrush = new BitmapBrush( target, - bitmap.GetDirect2DBitmap(target), + _bitmap.Value, GetBitmapBrushProperties(brush), GetBrushProperties(brush, calc.DestinationRect)); } @@ -41,7 +44,7 @@ namespace Avalonia.Direct2D1.Media public override void Dispose() { - ((BitmapBrush)PlatformBrush)?.Bitmap.Dispose(); + _bitmap.Dispose(); base.Dispose(); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index 63596bdf54..d58f023391 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -1,20 +1,38 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Platform; -using SharpDX.Direct2D1; +using SharpDX.WIC; +using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { public abstract class BitmapImpl : IBitmapImpl, IDisposable { - public abstract Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); + public BitmapImpl(ImagingFactory imagingFactory) + { + WicImagingFactory = imagingFactory; + } + + public ImagingFactory WicImagingFactory { get; } public abstract int PixelWidth { get; } public abstract int PixelHeight { get; } - public abstract void Save(string fileName); + + public abstract OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); + + public void Save(string fileName) + { + if (Path.GetExtension(fileName) != ".png") + { + // Yeah, we need to support other formats. + throw new NotSupportedException("Use PNG, stoopid."); + } + + using (FileStream s = new FileStream(fileName, FileMode.Create)) + { + Save(s); + } + } + public abstract void Save(Stream stream); public virtual void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 5378ae3257..b03e022674 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,11 +1,10 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Platform; using SharpDX.Direct2D1; +using WICFactory = SharpDX.WIC.ImagingFactory; +using ImagingFactory2 = SharpDX.WIC.ImagingFactory2; +using ImageParameters = SharpDX.WIC.ImageParameters; +using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder; namespace Avalonia.Direct2D1.Media { @@ -26,32 +25,42 @@ namespace Avalonia.Direct2D1.Media /// or if the render target is a , /// the device associated with this context, to be renderable. /// - public D2DBitmapImpl(Bitmap d2DBitmap) + public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap) + : base(imagingFactory) { - if (d2DBitmap == null) throw new ArgumentNullException(nameof(d2DBitmap)); - - _direct2D = d2DBitmap; + _direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); } - - public override Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) => _direct2D; - + public override int PixelWidth => _direct2D.PixelSize.Width; public override int PixelHeight => _direct2D.PixelSize.Height; - public override void Save(string fileName) + public override void Dispose() { - throw new NotImplementedException(); + base.Dispose(); + _direct2D.Dispose(); } - public override void Save(Stream stream) + public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) { - throw new NotImplementedException(); + return new OptionalDispose(_direct2D, false); } - public override void Dispose() + public override void Save(Stream stream) { - base.Dispose(); - _direct2D.Dispose(); + using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder)) + using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null)) + { + var parameters = new ImageParameters( + new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied), + _direct2D.DotsPerInch.Width, + _direct2D.DotsPerInch.Height, + 0, 0, PixelWidth, PixelHeight); + + imageEncoder.WriteFrame(_direct2D, 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 new file mode 100644 index 0000000000..6162d57bad --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -0,0 +1,68 @@ +using System; +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, ICreateLayer + { + private readonly DirectWriteFactory _dwriteFactory; + private readonly BitmapRenderTarget _target; + + public D2DRenderTargetBitmapImpl( + ImagingFactory imagingFactory, + DirectWriteFactory dwriteFactory, + BitmapRenderTarget target) + : base(imagingFactory, target.Bitmap) + { + _dwriteFactory = dwriteFactory; + _target = target; + } + + public override int PixelWidth => _target.PixelSize.Width; + public override int PixelHeight => _target.PixelSize.Height; + + public static D2DRenderTargetBitmapImpl CreateCompatible( + ImagingFactory imagingFactory, + DirectWriteFactory dwriteFactory, + SharpDX.Direct2D1.RenderTarget renderTarget, + int pixelWidth, + int pixelHeight) + { + var bitmapRenderTarget = new BitmapRenderTarget( + renderTarget, + CompatibleRenderTargetOptions.None, + new Size2F(pixelWidth, pixelHeight)); + return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget); + } + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + return new DrawingContextImpl( + visualBrushRenderer, + _target, + _dwriteFactory, + WicImagingFactory); + } + + public IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight) + { + return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, pixelWidth, pixelHeight); + } + + public override void Dispose() + { + _target.Dispose(); + } + + public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) + { + return new OptionalDispose(_target.Bitmap, false); + } + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index e817dd4812..9b99b4c40a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -3,11 +3,10 @@ using System; using System.IO; -using Avalonia.Platform; using Avalonia.Win32.Interop; -using PixelFormat = SharpDX.WIC.PixelFormat; -using APixelFormat = Avalonia.Platform.PixelFormat; using SharpDX.WIC; +using APixelFormat = Avalonia.Platform.PixelFormat; +using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { @@ -16,18 +15,14 @@ namespace Avalonia.Direct2D1.Media /// public class WicBitmapImpl : BitmapImpl { - private readonly ImagingFactory _factory; - - /// /// 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) { - _factory = factory; - using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand)) { WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); @@ -40,9 +35,8 @@ namespace Avalonia.Direct2D1.Media /// The WIC imaging factory to use. /// The stream to read the bitmap from. public WicBitmapImpl(ImagingFactory factory, Stream stream) + : base(factory) { - _factory = factory; - using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad)) { WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); @@ -57,11 +51,11 @@ namespace Avalonia.Direct2D1.Media /// The height of the bitmap. /// Pixel format public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null) + : base(factory) { if (!pixelFormat.HasValue) pixelFormat = APixelFormat.Bgra8888; - _factory = factory; PixelFormat = pixelFormat; WicImpl = new Bitmap( factory, @@ -71,7 +65,8 @@ namespace Avalonia.Direct2D1.Media BitmapCreateCacheOption.CacheOnLoad); } - public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride) + public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride) + : base(factory) { WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); PixelFormat = format; @@ -112,41 +107,23 @@ namespace Avalonia.Direct2D1.Media /// /// The render target. /// The Direct2D bitmap. - public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) + public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) { - FormatConverter converter = new FormatConverter(_factory); + FormatConverter converter = new FormatConverter(WicImagingFactory); converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); - return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter); + return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } - /// - /// Saves the bitmap to a file. - /// - /// The filename. - public override void Save(string fileName) + public override void Save(Stream stream) { - if (Path.GetExtension(fileName) != ".png") - { - // Yeah, we need to support other formats. - throw new NotSupportedException("Use PNG, stoopid."); - } - - using (FileStream s = new FileStream(fileName, FileMode.Create)) + using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var frame = new BitmapFrameEncode(encoder)) { - Save(s); + frame.Initialize(); + frame.WriteSource(WicImpl); + frame.Commit(); + encoder.Commit(); } } - - public override void Save(Stream stream) - { - PngBitmapEncoder encoder = new PngBitmapEncoder(_factory); - encoder.Initialize(stream); - - BitmapFrameEncode frame = new BitmapFrameEncode(encoder); - frame.Initialize(); - frame.WriteSource(WicImpl); - frame.Commit(); - encoder.Commit(); - } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs similarity index 89% rename from src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs rename to src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index 33736b02cb..1540ee333a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -10,12 +10,12 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media { - public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl + public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl { private readonly DirectWriteFactory _dwriteFactory; private readonly WicRenderTarget _target; - public RenderTargetBitmapImpl( + public WicRenderTargetBitmapImpl( ImagingFactory imagingFactory, Factory d2dFactory, DirectWriteFactory dwriteFactory, @@ -47,7 +47,7 @@ namespace Avalonia.Direct2D1.Media public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory); + return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory, WicImagingFactory); } } } diff --git a/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs b/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs new file mode 100644 index 0000000000..cd3eee8d25 --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs @@ -0,0 +1,22 @@ +using System; + +namespace Avalonia.Direct2D1 +{ + public struct OptionalDispose : IDisposable where T : IDisposable + { + private readonly bool _dispose; + + public OptionalDispose(T value, bool dispose) + { + Value = value; + _dispose = dispose; + } + + public T Value { get; } + + public void Dispose() + { + if (_dispose) Value?.Dispose(); + } + } +} diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index b4c9b49e3f..cbeff8b2f1 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -7,6 +7,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using SharpDX.Direct2D1; using DwFactory = SharpDX.DirectWrite.Factory; +using WicFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1 { @@ -25,24 +26,13 @@ namespace Avalonia.Direct2D1 { Direct2DFactory = AvaloniaLocator.Current.GetService(); DirectWriteFactory = AvaloniaLocator.Current.GetService(); + WicFactory = AvaloniaLocator.Current.GetService(); _renderTarget = renderTarget; } - /// - /// Gets the Direct2D factory. - /// - public Factory Direct2DFactory - { - get; - } - - /// - /// Gets the DirectWrite factory. - /// - public DwFactory DirectWriteFactory - { - get; - } + public Factory Direct2DFactory { get; } + public DwFactory DirectWriteFactory { get; } + public WicFactory WicFactory { get; } /// /// Creates a drawing context for a rendering session. @@ -50,7 +40,7 @@ namespace Avalonia.Direct2D1 /// An . public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory); + return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory, WicFactory); } public void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 3c1024e73a..bd6555159c 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -10,10 +10,11 @@ using Factory = SharpDX.Direct2D1.Factory; using Factory2 = SharpDX.DXGI.Factory2; using Avalonia.Rendering; using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; namespace Avalonia.Direct2D1 { - public abstract class SwapChainRenderTarget : IRenderTarget + public abstract class SwapChainRenderTarget : IRenderTarget, ICreateLayer { private Size2 _savedSize; private Size2F _savedDpi; @@ -26,24 +27,12 @@ namespace Avalonia.Direct2D1 D2DDevice = AvaloniaLocator.Current.GetService(); Direct2DFactory = AvaloniaLocator.Current.GetService(); DirectWriteFactory = AvaloniaLocator.Current.GetService(); + WicImagingFactory = AvaloniaLocator.Current.GetService(); } - - /// - /// Gets the Direct2D factory. - /// - public Factory Direct2DFactory - { - get; - } - - /// - /// Gets the DirectWrite factory. - /// - public SharpDX.DirectWrite.Factory DirectWriteFactory - { - get; - } + public Factory Direct2DFactory { get; } + public SharpDX.DirectWrite.Factory DirectWriteFactory { get; } + public SharpDX.WIC.ImagingFactory WicImagingFactory { get; } protected SharpDX.DXGI.Device DxgiDevice { get; } @@ -69,9 +58,25 @@ namespace Avalonia.Direct2D1 visualBrushRenderer, _deviceContext, DirectWriteFactory, + WicImagingFactory, _swapChain); } + public IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight) + { + if (_deviceContext == null) + { + CreateSwapChain(); + } + + return D2DRenderTargetBitmapImpl.CreateCompatible( + WicImagingFactory, + DirectWriteFactory, + _deviceContext, + pixelWidth, + pixelHeight); + } + public void Dispose() { _deviceContext?.Dispose(); @@ -86,7 +91,6 @@ namespace Avalonia.Direct2D1 _deviceContext?.Dispose(); _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; - var swapChainDesc = new SwapChainDescription1 { Width = _savedSize.Width, From 8efd5e56a25ea3cf2a65e2d7f79a6697b9e13bd7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Aug 2017 01:17:35 +0200 Subject: [PATCH 02/20] Added missing doc comments. --- .../Platform/IDrawingContextImpl.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 14aef8463a..1bae88cb5f 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -86,6 +86,9 @@ namespace Avalonia.Platform /// The clip rectangle. void PushClip(Rect clip); + /// + /// Pops the latest pushed clip rectangle. + /// void PopClip(); /// @@ -94,10 +97,19 @@ namespace Avalonia.Platform /// The opacity. void PushOpacity(double opacity); + /// + /// Pops the latest pushed opacity value. + /// void PopOpacity(); + /// + /// Pushes an opacity mask + /// void PushOpacityMask(IBrush mask, Rect bounds); + /// + /// Pops the latest pushed opacity mask. + /// void PopOpacityMask(); /// @@ -106,6 +118,9 @@ namespace Avalonia.Platform /// The clip geometry. void PushGeometryClip(IGeometryImpl clip); + /// + /// Pops the latest pushed geometry clip. + /// void PopGeometryClip(); } } From 5607404e6cb6a3e9c7d5e6e823668d42a807b3b2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Aug 2017 13:14:19 +0200 Subject: [PATCH 03/20] Added IDrawingContextImpl.CreateLayer --- .../Direct3DInteropSample/MainWindow.cs | 2 +- .../Platform/IDrawingContextImpl.cs | 14 ++++++++++ .../SceneGraph/DeferredDrawingContextImpl.cs | 5 ++++ .../Avalonia.Cairo/Media/DrawingContext.cs | 6 +++++ src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 6 +++++ .../ExternalRenderTarget.cs | 15 +++++++++-- .../Avalonia.Direct2D1/ICreateLayer.cs | 10 ------- .../Avalonia.Direct2D1/ILayerFactory.cs | 10 +++++++ .../Media/DrawingContextImpl.cs | 26 +++++++++++++++++++ .../Imaging/D2DRenderTargetBitmapImpl.cs | 12 ++++----- .../Imaging/WicRenderTargetBitmapImpl.cs | 7 ++++- .../Avalonia.Direct2D1/RenderTarget.cs | 14 ++++++++-- .../SwapChainRenderTarget.cs | 8 +++--- 13 files changed, 109 insertions(+), 26 deletions(-) delete mode 100644 src/Windows/Avalonia.Direct2D1/ICreateLayer.cs create mode 100644 src/Windows/Avalonia.Direct2D1/ILayerFactory.cs diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index 3fe3b896f6..ffa0de0a36 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -253,7 +253,7 @@ namespace Direct3DInteropSample public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget, + return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget, AvaloniaLocator.Current.GetService(), AvaloniaLocator.Current.GetService()); } diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 1bae88cb5f..3db4527bfb 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -80,6 +80,20 @@ namespace Avalonia.Platform /// The corner radius. void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f); + /// + /// Creates a new that can be used as a render layer + /// for the current render target. + /// + /// The size of the layer in DIPs. + /// An + /// + /// Depending on the rendering backend used, a layer created via this method may be more + /// performant than a standard render target bitmap. In particular the Direct2D backend + /// has to do a format conversion each time a standard render target bitmap is rendered, + /// but a layer created via this method has no such overhead. + /// + IRenderTargetBitmapImpl CreateLayer(Size size); + /// /// Pushes a clip rectange. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 0b01960d5b..5fcd0e42bd 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -194,6 +194,11 @@ namespace Avalonia.Rendering.SceneGraph } } + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + throw new NotImplementedException(); + } + /// public void PopClip() { diff --git a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs index 99b0a2ec73..7900f2e440 100644 --- a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs +++ b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs @@ -227,6 +227,12 @@ namespace Avalonia.Cairo.Media } } + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + var surface = new Cairo.ImageSurface(Cairo.Format.Argb32, (int)size.Width, (int)size.Height); + return new RenderTargetBitmapImpl(surface); + } + /// /// Pushes a clip rectange. /// diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 7a83835b10..f01e9858e7 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -349,6 +349,12 @@ namespace Avalonia.Skia } } + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + var pixelSize = size * (_dpi / 96); + return new BitmapImpl((int)pixelSize.Width, (int)pixelSize.Height, _dpi); + } + public void PushClip(Rect clip) { Canvas.Save(); diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index d735c5f8bb..176cedd377 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; @@ -7,7 +8,7 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1 { - class ExternalRenderTarget : IRenderTarget + class ExternalRenderTarget : IRenderTarget, ILayerFactory { private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; private readonly DirectWriteFactory _dwFactory; @@ -32,7 +33,7 @@ namespace Avalonia.Direct2D1 { var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); _externalRenderTargetProvider.BeforeDrawing(); - return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, _wicFactory, null, () => + return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () => { try { @@ -44,5 +45,15 @@ namespace Avalonia.Direct2D1 } }); } + + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); + return D2DRenderTargetBitmapImpl.CreateCompatible( + _wicFactory, + _dwFactory, + target, + size); + } } } diff --git a/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs b/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs deleted file mode 100644 index 9214377e6f..0000000000 --- a/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using Avalonia.Platform; - -namespace Avalonia.Direct2D1 -{ - internal interface ICreateLayer - { - IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight); - } -} diff --git a/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs b/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs new file mode 100644 index 0000000000..99f8d4f7ac --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs @@ -0,0 +1,10 @@ +using System; +using Avalonia.Platform; + +namespace Avalonia.Direct2D1 +{ + public interface ILayerFactory + { + IRenderTargetBitmapImpl CreateLayer(Size size); + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index a7bb73e19f..6a72923ce3 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -18,6 +18,7 @@ namespace Avalonia.Direct2D1.Media public class DrawingContextImpl : IDrawingContextImpl, IDisposable { private readonly IVisualBrushRenderer _visualBrushRenderer; + private readonly ILayerFactory _layerFactory; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; @@ -29,12 +30,17 @@ namespace Avalonia.Direct2D1.Media /// /// The visual brush renderer. /// The render target to draw to. + /// + /// 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, @@ -42,6 +48,7 @@ namespace Avalonia.Direct2D1.Media Action finishedCallback = null) { _visualBrushRenderer = visualBrushRenderer; + _layerFactory = layerFactory; _renderTarget = renderTarget; _swapChain = swapChain; _finishedCallback = finishedCallback; @@ -285,6 +292,25 @@ namespace Avalonia.Direct2D1.Media } } + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + if (_layerFactory != null) + { + return _layerFactory.CreateLayer(size); + } + else + { + var platform = AvaloniaLocator.Current.GetService(); + var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height); + var pixelSize = size * (dpi / 96); + return platform.CreateRenderTargetBitmap( + (int)pixelSize.Width, + (int)pixelSize.Height, + dpi.X, + dpi.Y); + } + } + /// /// Pushes a clip rectange. /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 6162d57bad..2843848fac 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -9,7 +9,7 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media.Imaging { - public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ICreateLayer + public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory { private readonly DirectWriteFactory _dwriteFactory; private readonly BitmapRenderTarget _target; @@ -31,13 +31,12 @@ namespace Avalonia.Direct2D1.Media.Imaging ImagingFactory imagingFactory, DirectWriteFactory dwriteFactory, SharpDX.Direct2D1.RenderTarget renderTarget, - int pixelWidth, - int pixelHeight) + Size size) { var bitmapRenderTarget = new BitmapRenderTarget( renderTarget, CompatibleRenderTargetOptions.None, - new Size2F(pixelWidth, pixelHeight)); + new Size2F((float)size.Width, (float)size.Height)); return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget); } @@ -45,14 +44,15 @@ namespace Avalonia.Direct2D1.Media.Imaging { return new DrawingContextImpl( visualBrushRenderer, + this, _target, _dwriteFactory, WicImagingFactory); } - public IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight) + public IRenderTargetBitmapImpl CreateLayer(Size size) { - return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, pixelWidth, pixelHeight); + return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size); } public override void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index 1540ee333a..f1065370b5 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -47,7 +47,12 @@ namespace Avalonia.Direct2D1.Media public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory, WicImagingFactory); + return new DrawingContextImpl( + visualBrushRenderer, + null, + _target, + _dwriteFactory, + WicImagingFactory); } } } diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index cbeff8b2f1..6086b0c67c 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX.Direct2D1; @@ -11,7 +12,7 @@ using WicFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1 { - public class RenderTarget : IRenderTarget + public class RenderTarget : IRenderTarget, ILayerFactory { /// /// The render target. @@ -40,7 +41,16 @@ namespace Avalonia.Direct2D1 /// An . public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory, WicFactory); + return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory); + } + + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + return D2DRenderTargetBitmapImpl.CreateCompatible( + WicFactory, + DirectWriteFactory, + _renderTarget, + size); } public void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index bd6555159c..0a23c63498 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -14,7 +14,7 @@ using Avalonia.Direct2D1.Media.Imaging; namespace Avalonia.Direct2D1 { - public abstract class SwapChainRenderTarget : IRenderTarget, ICreateLayer + public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory { private Size2 _savedSize; private Size2F _savedDpi; @@ -56,13 +56,14 @@ namespace Avalonia.Direct2D1 return new DrawingContextImpl( visualBrushRenderer, + this, _deviceContext, DirectWriteFactory, WicImagingFactory, _swapChain); } - public IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight) + public IRenderTargetBitmapImpl CreateLayer(Size size) { if (_deviceContext == null) { @@ -73,8 +74,7 @@ namespace Avalonia.Direct2D1 WicImagingFactory, DirectWriteFactory, _deviceContext, - pixelWidth, - pixelHeight); + size); } public void Dispose() From bb5a7f0cd3d1157885f5f2e140142f5a0adbf843 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Sep 2017 20:54:06 -0700 Subject: [PATCH 04/20] Removed IRenderLayerFactory And use `IDrawingContextImpl.CreateLayer` instead. --- .../Rendering/DefaultRenderLayerFactory.cs | 34 --- .../Rendering/DeferredRenderer.cs | 153 ++++++------ .../Rendering/IRenderLayerFactory.cs | 11 - src/Avalonia.Visuals/Rendering/RenderLayer.cs | 10 +- .../Rendering/RenderLayers.cs | 9 +- .../Rendering/DeferredRendererTests.cs | 220 ++++++++---------- 6 files changed, 175 insertions(+), 262 deletions(-) delete mode 100644 src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs delete mode 100644 src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs deleted file mode 100644 index c75f948b66..0000000000 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Avalonia.Platform; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - public class DefaultRenderLayerFactory : IRenderLayerFactory - { - private IPlatformRenderInterface _renderInterface; - - public DefaultRenderLayerFactory() - : this(AvaloniaLocator.Current.GetService()) - { - } - - public DefaultRenderLayerFactory(IPlatformRenderInterface renderInterface) - { - _renderInterface = renderInterface; - } - - public IRenderTargetBitmapImpl CreateLayer( - IVisual layerRoot, - Size size, - double dpiX, - double dpiY) - { - return _renderInterface.CreateRenderTargetBitmap( - (int)Math.Ceiling(size.Width), - (int)Math.Ceiling(size.Height), - dpiX, - dpiY); - } - } -} diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index c30fb3bdc3..d42f2bd8a4 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -26,7 +26,6 @@ namespace Avalonia.Rendering private readonly IVisual _root; private readonly ISceneBuilder _sceneBuilder; private readonly RenderLayers _layers; - private readonly IRenderLayerFactory _layerFactory; private bool _running; private Scene _scene; @@ -45,13 +44,11 @@ namespace Avalonia.Rendering /// The control to render. /// The render loop. /// The scene builder to use. Optional. - /// The layer factory to use. Optional. /// The dispatcher to use. Optional. public DeferredRenderer( IRenderRoot root, IRenderLoop renderLoop, ISceneBuilder sceneBuilder = null, - IRenderLayerFactory layerFactory = null, IDispatcher dispatcher = null) { Contract.Requires(root != null); @@ -59,8 +56,7 @@ namespace Avalonia.Rendering _dispatcher = dispatcher ?? Dispatcher.UIThread; _root = root; _sceneBuilder = sceneBuilder ?? new SceneBuilder(); - _layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); - _layers = new RenderLayers(_layerFactory); + _layers = new RenderLayers(); _renderLoop = renderLoop; } @@ -70,15 +66,13 @@ namespace Avalonia.Rendering /// The control to render. /// The render target. /// The scene builder to use. Optional. - /// The layer factory to use. Optional. /// /// This constructor is intended to be used for unit testing. /// public DeferredRenderer( IVisual root, IRenderTarget renderTarget, - ISceneBuilder sceneBuilder = null, - IRenderLayerFactory layerFactory = null) + ISceneBuilder sceneBuilder = null) { Contract.Requires(root != null); Contract.Requires(renderTarget != null); @@ -86,8 +80,7 @@ namespace Avalonia.Rendering _root = root; _renderTarget = renderTarget; _sceneBuilder = sceneBuilder ?? new SceneBuilder(); - _layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); - _layers = new RenderLayers(_layerFactory); + _layers = new RenderLayers(); } /// @@ -180,38 +173,56 @@ namespace Avalonia.Rendering bool renderOverlay = DrawDirtyRects || DrawFps; bool composite = false; + if (_renderTarget == null) + { + _renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + } + if (renderOverlay) { _dirtyRectsDisplay.Tick(); } - if (scene != null && scene.Size != Size.Empty) + try { - if (scene.Generation != _lastSceneId) + using (var context = _renderTarget.CreateDrawingContext(this)) { - _layers.Update(scene); - RenderToLayers(scene); - - if (DebugFramesPath != null) + if (scene != null && scene.Size != Size.Empty) { - SaveDebugFrames(scene.Generation); - } + if (scene.Generation != _lastSceneId) + { + _layers.Update(scene, context); - _lastSceneId = scene.Generation; + RenderToLayers(scene); - composite = true; - } + if (DebugFramesPath != null) + { + SaveDebugFrames(scene.Generation); + } - if (renderOverlay) - { - RenderOverlay(scene); - RenderComposite(scene); - } - else if(composite) - { - RenderComposite(scene); + _lastSceneId = scene.Generation; + + composite = true; + } + + if (renderOverlay) + { + RenderOverlay(scene, context); + RenderComposite(scene, context); + } + else if (composite) + { + RenderComposite(scene, context); + } + } } } + catch (RenderTargetCorruptedException ex) + { + Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); + _renderTarget?.Dispose(); + _renderTarget = null; + } } private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds) @@ -273,11 +284,11 @@ namespace Avalonia.Rendering } } - private void RenderOverlay(Scene scene) + private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent) { if (DrawDirtyRects) { - var overlay = GetOverlay(scene.Size, scene.Scaling); + var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling); using (var context = overlay.CreateDrawingContext(this)) { @@ -301,61 +312,44 @@ namespace Avalonia.Rendering } } - private void RenderComposite(Scene scene) + private void RenderComposite(Scene scene, IDrawingContextImpl context) { - try + var clientRect = new Rect(scene.Size); + + foreach (var layer in scene.Layers) { - if (_renderTarget == null) + var bitmap = _layers[layer.LayerRoot].Bitmap; + var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); + + if (layer.GeometryClip != null) { - _renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + context.PushGeometryClip(layer.GeometryClip); } - using (var context = _renderTarget.CreateDrawingContext(this)) + if (layer.OpacityMask == null) { - var clientRect = new Rect(scene.Size); - - foreach (var layer in scene.Layers) - { - var bitmap = _layers[layer.LayerRoot].Bitmap; - var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); - - if (layer.GeometryClip != null) - { - context.PushGeometryClip(layer.GeometryClip); - } - - if (layer.OpacityMask == null) - { - context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect); - } - else - { - context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect); - } - - if (layer.GeometryClip != null) - { - context.PopGeometryClip(); - } - } - - if (_overlay != null) - { - var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight); - context.DrawImage(_overlay, 0.5, sourceRect, clientRect); - } + context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect); + } + else + { + context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect); + } - if (DrawFps) - { - RenderFps(context, clientRect, true); - } + if (layer.GeometryClip != null) + { + context.PopGeometryClip(); } } - catch (RenderTargetCorruptedException ex) + + if (_overlay != null) { - Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); - _renderTarget?.Dispose(); - _renderTarget = null; + var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight); + context.DrawImage(_overlay, 0.5, sourceRect, clientRect); + } + + if (DrawFps) + { + RenderFps(context, clientRect, true); } } @@ -422,7 +416,10 @@ namespace Avalonia.Rendering } } - private IRenderTargetBitmapImpl GetOverlay(Size size, double scaling) + private IRenderTargetBitmapImpl GetOverlay( + IDrawingContextImpl parentContext, + Size size, + double scaling) { size = new Size(size.Width * scaling, size.Height * scaling); @@ -431,7 +428,7 @@ namespace Avalonia.Rendering _overlay.PixelHeight != size.Height) { _overlay?.Dispose(); - _overlay = _layerFactory.CreateLayer(null, size, 96 * scaling, 96 * scaling); + _overlay = parentContext.CreateLayer(size); } return _overlay; diff --git a/src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs b/src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs deleted file mode 100644 index ed2751cd64..0000000000 --- a/src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using Avalonia.Platform; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - public interface IRenderLayerFactory - { - IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size, double dpiX, double dpiY); - } -} diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index df9497af6f..ed33295db6 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs @@ -7,16 +7,16 @@ namespace Avalonia.Rendering { public class RenderLayer { - private readonly IRenderLayerFactory _factory; + private readonly IDrawingContextImpl _drawingContext; public RenderLayer( - IRenderLayerFactory factory, + IDrawingContextImpl drawingContext, Size size, double scaling, IVisual layerRoot) { - _factory = factory; - Bitmap = factory.CreateLayer(layerRoot, size * scaling, 96 * scaling, 96 * scaling); + _drawingContext = drawingContext; + Bitmap = drawingContext.CreateLayer(size); Size = size; Scaling = scaling; LayerRoot = layerRoot; @@ -31,7 +31,7 @@ namespace Avalonia.Rendering { if (Size != size || Scaling != scaling) { - var resized = _factory.CreateLayer(LayerRoot, size * scaling, 96 * scaling, 96 * scaling); + var resized = _drawingContext.CreateLayer(size); using (var context = resized.CreateDrawingContext(null)) { diff --git a/src/Avalonia.Visuals/Rendering/RenderLayers.cs b/src/Avalonia.Visuals/Rendering/RenderLayers.cs index e1b22c55e0..bafd644603 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayers.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayers.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.VisualTree; @@ -8,19 +9,17 @@ namespace Avalonia.Rendering { public class RenderLayers : IEnumerable { - private readonly IRenderLayerFactory _factory; private List _inner = new List(); private Dictionary _index = new Dictionary(); - public RenderLayers(IRenderLayerFactory factory) + public RenderLayers() { - _factory = factory; } public int Count => _inner.Count; public RenderLayer this[IVisual layerRoot] => _index[layerRoot]; - public void Update(Scene scene) + public void Update(Scene scene, IDrawingContextImpl context) { for (var i = scene.Layers.Count - 1; i >= 0; --i) { @@ -29,7 +28,7 @@ namespace Avalonia.Rendering if (!_index.TryGetValue(src.LayerRoot, out layer)) { - layer = new RenderLayer(_factory, scene.Size, scene.Scaling, src.LayerRoot); + layer = new RenderLayer(context, scene.Size, scene.Scaling, src.LayerRoot); _inner.Add(layer); _index.Add(src.LayerRoot, layer); } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index a9b27ed601..047cb05c26 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -30,7 +30,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: MockSceneBuilder(root).Object, - layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher.Object); target.Start(); @@ -55,7 +54,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder.Object, - layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher); target.Start(); @@ -75,7 +73,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder.Object, - layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher); target.Start(); @@ -111,7 +108,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder.Object, - layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher); target.Start(); @@ -133,100 +129,102 @@ namespace Avalonia.Visuals.UnitTests.Rendering [Fact] public void Frame_Should_Create_Layer_For_Root() { - var loop = new Mock(); - var root = new TestRoot(); - var rootLayer = new Mock(); - var dispatcher = new ImmediateDispatcher(); - - var sceneBuilder = new Mock(); - sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) - .Callback(scene => - { - scene.Size = root.ClientSize; - scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); - }); - - var layers = new Mock(); - layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); - - var renderInterface = new Mock(); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder.Object, - layerFactory: layers.Object, - dispatcher: dispatcher); - - target.Start(); - RunFrame(loop); - - layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96)); + throw new NotImplementedException(); + //var loop = new Mock(); + //var root = new TestRoot(); + //var rootLayer = new Mock(); + //var dispatcher = new ImmediateDispatcher(); + + //var sceneBuilder = new Mock(); + //sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) + // .Callback(scene => + // { + // scene.Size = root.ClientSize; + // scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); + // }); + + //var layers = new Mock(); + //layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); + + //var renderInterface = new Mock(); + + //var target = new DeferredRenderer( + // root, + // loop.Object, + // sceneBuilder: sceneBuilder.Object, + // layerFactory: layers.Object, + // dispatcher: dispatcher); + + //target.Start(); + //RunFrame(loop); + + //layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96)); } [Fact] public void Should_Create_And_Delete_Layers_For_Transparent_Controls() { - Border border; - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = new Border - { - Background = Brushes.Red, - Child = border = new Border - { - Background = Brushes.Green, - } - } - }; - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var loop = new Mock(); - var layerFactory = new MockRenderLayerFactory(new Dictionary - { - { root, CreateLayer() }, - { border, CreateLayer() }, - }); - - var target = new DeferredRenderer( - root, - loop.Object, - layerFactory: layerFactory, - dispatcher: new ImmediateDispatcher()); - root.Renderer = target; - - target.Start(); - RunFrame(loop); - - var rootContext = layerFactory.GetMockDrawingContext(root); - var borderContext = layerFactory.GetMockDrawingContext(border); - - rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); - - rootContext.ResetCalls(); - borderContext.ResetCalls(); - border.Opacity = 0.5; - RunFrame(loop); - - rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never); - borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - - rootContext.ResetCalls(); - borderContext.ResetCalls(); - border.Opacity = 1; - RunFrame(loop); - - layerFactory.GetMockBitmap(border).Verify(x => x.Dispose()); - rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + throw new NotImplementedException(); + //Border border; + //var root = new TestRoot + //{ + // Width = 100, + // Height = 100, + // Child = new Border + // { + // Background = Brushes.Red, + // Child = border = new Border + // { + // Background = Brushes.Green, + // } + // } + //}; + + //root.Measure(Size.Infinity); + //root.Arrange(new Rect(root.DesiredSize)); + + //var loop = new Mock(); + //var layerFactory = new MockRenderLayerFactory(new Dictionary + //{ + // { root, CreateLayer() }, + // { border, CreateLayer() }, + //}); + + //var target = new DeferredRenderer( + // root, + // loop.Object, + // layerFactory: layerFactory, + // dispatcher: new ImmediateDispatcher()); + //root.Renderer = target; + + //target.Start(); + //RunFrame(loop); + + //var rootContext = layerFactory.GetMockDrawingContext(root); + //var borderContext = layerFactory.GetMockDrawingContext(border); + + //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + //borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + + //rootContext.ResetCalls(); + //borderContext.ResetCalls(); + //border.Opacity = 0.5; + //RunFrame(loop); + + //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never); + //borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + + //rootContext.ResetCalls(); + //borderContext.ResetCalls(); + //border.Opacity = 1; + //RunFrame(loop); + + //layerFactory.GetMockBitmap(border).Verify(x => x.Dispose()); + //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + //borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } private void IgnoreFirstFrame(Mock loop, Mock sceneBuilder) @@ -246,13 +244,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering x.CreateDrawingContext(It.IsAny()) == Mock.Of()); } - private Mock MockLayerFactory(IRenderRoot root) - { - var result = new Mock(); - result.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); - return result; - } - private Mock MockSceneBuilder(IRenderRoot root) { var result = new Mock(); @@ -260,34 +251,5 @@ namespace Avalonia.Visuals.UnitTests.Rendering .Callback(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize))); return result; } - - private class MockRenderLayerFactory : IRenderLayerFactory - { - private IDictionary _layers; - - public MockRenderLayerFactory(IDictionary layers) - { - _layers = layers; - } - - public IRenderTargetBitmapImpl CreateLayer( - IVisual layerRoot, - Size size, - double dpiX, - double dpiY) - { - return _layers[layerRoot]; - } - - public Mock GetMockBitmap(IVisual layerRoot) - { - return Mock.Get(_layers[layerRoot]); - } - - public Mock GetMockDrawingContext(IVisual layerRoot) - { - return Mock.Get(_layers[layerRoot].CreateDrawingContext(null)); - } - } } } From d141acab30da31254360613b12184180239fa6c4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Sep 2017 20:59:41 -0700 Subject: [PATCH 05/20] Use correct size for overlay. --- src/Avalonia.Visuals/Rendering/DeferredRenderer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index d42f2bd8a4..81a2998fd2 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -421,11 +421,11 @@ namespace Avalonia.Rendering Size size, double scaling) { - size = new Size(size.Width * scaling, size.Height * scaling); + var pixelSize = size * scaling; if (_overlay == null || - _overlay.PixelWidth != size.Width || - _overlay.PixelHeight != size.Height) + _overlay.PixelWidth != pixelSize.Width || + _overlay.PixelHeight != pixelSize.Height) { _overlay?.Dispose(); _overlay = parentContext.CreateLayer(size); From b0d8ff69ea300769018181a6d604bbde1f4118dc Mon Sep 17 00:00:00 2001 From: Dmitriy Arndt Date: Tue, 26 Sep 2017 00:11:32 +0300 Subject: [PATCH 06/20] Fixed: MenuItem with sub-items is not closed when other menu item without sub-items is selected #226 --- src/Avalonia.Controls/MenuItem.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 926c240e57..dadd3b910b 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -307,6 +307,21 @@ namespace Avalonia.Controls () => IsSubMenuOpen = true, TimeSpan.FromMilliseconds(400)); } + else + { + var parentItem = Parent as MenuItem; + if (parentItem != null) + { + foreach (var sibling in parentItem.Items + .OfType() + .Where(x => x != this && x.IsSubMenuOpen)) + { + sibling.CloseSubmenus(); + sibling.IsSubMenuOpen = false; + sibling.IsSelected = false; + } + } + } } /// From 12ec059acb3e83b718eabda9cf7f7ac6b8ed5043 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 18:08:37 -0500 Subject: [PATCH 07/20] Initial code to enable binding to a method. --- .../Data/ExpressionObserver.cs | 1 + .../Data/Plugins/MethodAccessorPlugin.cs | 91 +++++++++++++++++++ .../Data/ExpressionObserverTests_Method.cs | 65 +++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs create mode 100644 tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs b/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs index 1e55e17195..66d3beb907 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs +++ b/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs @@ -25,6 +25,7 @@ namespace Avalonia.Markup.Data new List { new AvaloniaPropertyAccessorPlugin(), + new MethodAccessorPlugin(), new InpcPropertyAccessorPlugin(), }; diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs new file mode 100644 index 0000000000..ef4281c314 --- /dev/null +++ b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Data; +using System.Reflection; +using System.Linq; + +namespace Avalonia.Markup.Data.Plugins +{ + class MethodAccessorPlugin : IPropertyAccessorPlugin + { + public bool Match(object obj, string propertyName) + => obj.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName) != null; + + public IPropertyAccessor Start(WeakReference reference, string propertyName) + { + Contract.Requires(reference != null); + Contract.Requires(propertyName != null); + + var instance = reference.Target; + var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName); + + if (method != null) + { + return new Accessor(reference, method); + } + else + { + var message = $"Could not find CLR method '{propertyName}' on '{instance}'"; + var exception = new MissingMemberException(message); + return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); + } + } + + private class Accessor : PropertyAccessorBase + { + public Accessor(WeakReference reference, MethodInfo method) + { + Contract.Requires(reference != null); + Contract.Requires(method != null); + + var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray(); + var returnType = method.ReturnType; + + // TODO: Throw exception if more than 8 parameters or more than 7 + return type. + // Do this here or in the caller? Here probably + if (returnType == typeof(void)) + { + if (paramTypes.Length == 0) + { + PropertyType = typeof(Action); + } + else + { + PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes); + } + } + else + { + var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); + PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters); + } + + // TODO: Is this going to leak? + // TODO: Static methods? + Value = method.CreateDelegate(PropertyType, reference.Target); + } + + public override Type PropertyType { get; } + + public override object Value { get; } + + public override bool SetValue(object value, BindingPriority priority) => false; + + protected override void SubscribeCore(IObserver observer) + { + SendCurrentValue(); + } + + private void SendCurrentValue() + { + try + { + var value = Value; + Observer.OnNext(value); + } + catch { } + } + } + } +} diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs new file mode 100644 index 0000000000..9929e4e986 --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs @@ -0,0 +1,65 @@ +using Avalonia.Data; +using Avalonia.Markup.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Avalonia.Markup.UnitTests.Data +{ + public class ExpressionObserverTests_Method + { + private class TestObject + { + public void MethodWithoutReturn() { } + + public int MethodWithReturn() => 0; + + public int MethodWithReturnAndParameters(int i) => i; + } + + [Fact] + public async Task Should_Get_Method() + { + var data = new TestObject(); + var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithoutReturn)); + var result = await observer.Take(1); + + Assert.NotNull(result); + + GC.KeepAlive(data); + } + + [Theory] + [InlineData(nameof(TestObject.MethodWithoutReturn), typeof(Action))] + [InlineData(nameof(TestObject.MethodWithReturn), typeof(Func))] + [InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func))] + public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType) + { + var data = new TestObject(); + var observer = new ExpressionObserver(data, methodName); + var result = await observer.Take(1); + + Assert.IsType(expectedType, result); + + GC.KeepAlive(data); + } + + [Fact] + public async Task Can_Call_Method_Returned_From_Observer() + { + var data = new TestObject(); + var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithReturnAndParameters)); + var result = await observer.Take(1); + + var callback = (Func)result; + + Assert.Equal(1, callback(1)); + + GC.KeepAlive(data); + } + } +} From 250f6acef0e702cc420fa68460d6d04122d73e7e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 18:37:52 -0500 Subject: [PATCH 08/20] Add delegate -> command conversion as a default value conversion. --- .../AlwaysEnabledDelegateCommand.cs | 36 ++++++++++++++++++ .../Avalonia.Markup/DefaultValueConverter.cs | 6 +++ .../DefaultValueConverterTests.cs | 38 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs diff --git a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs new file mode 100644 index 0000000000..c675b876ae --- /dev/null +++ b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Input; + +namespace Avalonia.Markup +{ + class AlwaysEnabledDelegateCommand : ICommand + { + private readonly Delegate action; + + public AlwaysEnabledDelegateCommand(Delegate action) + { + this.action = action; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter) + { + return true; + } + + public void Execute(object parameter) + { + if (action.Method.GetParameters().Length == 0) + { + action.DynamicInvoke(); + } + else + { + action.DynamicInvoke(parameter); + } + } + } +} diff --git a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs index b56291a653..bb6bd85827 100644 --- a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs +++ b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs @@ -5,6 +5,7 @@ using System; using System.Globalization; using Avalonia.Data; using Avalonia.Utilities; +using System.Windows.Input; namespace Avalonia.Markup { @@ -34,6 +35,11 @@ namespace Avalonia.Markup return AvaloniaProperty.UnsetValue; } + if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d) + { + return new AlwaysEnabledDelegateCommand(d); + } + if (TypeUtilities.TryConvert(targetType, value, culture, out object result)) { return result; diff --git a/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs b/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs index fd28f2e900..0aa2e00c0f 100644 --- a/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs +++ b/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs @@ -5,6 +5,8 @@ using System.Globalization; using Avalonia.Controls; using Avalonia.Data; using Xunit; +using System.Windows.Input; +using System; namespace Avalonia.Markup.UnitTests { @@ -118,6 +120,42 @@ namespace Avalonia.Markup.UnitTests Assert.IsType(result); } + [Fact] + public void Can_Convert_From_Delegate_To_Command() + { + int commandResult = 0; + + var result = DefaultValueConverter.Instance.Convert( + (Action)((int i) => { commandResult = i; }), + typeof(ICommand), + null, + CultureInfo.InvariantCulture); + + Assert.IsAssignableFrom(result); + + (result as ICommand).Execute(5); + + Assert.Equal(5, commandResult); + } + + [Fact] + public void Can_Convert_From_Delegate_To_Command_No_Parameters() + { + int commandResult = 0; + + var result = DefaultValueConverter.Instance.Convert( + (Action)(() => { commandResult = 1; }), + typeof(ICommand), + null, + CultureInfo.InvariantCulture); + + Assert.IsAssignableFrom(result); + + (result as ICommand).Execute(null); + + Assert.Equal(1, commandResult); + } + private enum TestEnum { Foo, From 81168ac09433147f8fe055bf7f8c618ea88d3fde Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 21:55:26 -0500 Subject: [PATCH 09/20] Add error handling for methods with too many parameters. Add support for static methods. --- .../AlwaysEnabledDelegateCommand.cs | 5 +-- .../Data/Plugins/MethodAccessorPlugin.cs | 41 ++++++++++--------- .../Data/ExpressionObserverTests_Method.cs | 22 ++++++++++ 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs index c675b876ae..1d417a55a6 100644 --- a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs +++ b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs @@ -16,10 +16,7 @@ namespace Avalonia.Markup public event EventHandler CanExecuteChanged; - public bool CanExecute(object parameter) - { - return true; - } + public bool CanExecute(object parameter) => true; public void Execute(object parameter) { diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs index ef4281c314..c602fa4f4d 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs @@ -9,24 +9,30 @@ namespace Avalonia.Markup.Data.Plugins { class MethodAccessorPlugin : IPropertyAccessorPlugin { - public bool Match(object obj, string propertyName) - => obj.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName) != null; + public bool Match(object obj, string methodName) + => obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName); - public IPropertyAccessor Start(WeakReference reference, string propertyName) + public IPropertyAccessor Start(WeakReference reference, string methodName) { Contract.Requires(reference != null); - Contract.Requires(propertyName != null); + Contract.Requires(methodName != null); var instance = reference.Target; - var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName); + var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName); if (method != null) { + if (method.GetParameters().Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) + { + var exception = new ArgumentException("Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", nameof(method)); + return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); + } + return new Accessor(reference, method); } else { - var message = $"Could not find CLR method '{propertyName}' on '{instance}'"; + var message = $"Could not find CLR method '{methodName}' on '{instance}'"; var exception = new MissingMemberException(message); return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); } @@ -41,9 +47,7 @@ namespace Avalonia.Markup.Data.Plugins var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray(); var returnType = method.ReturnType; - - // TODO: Throw exception if more than 8 parameters or more than 7 + return type. - // Do this here or in the caller? Here probably + if (returnType == typeof(void)) { if (paramTypes.Length == 0) @@ -60,10 +64,8 @@ namespace Avalonia.Markup.Data.Plugins var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters); } - - // TODO: Is this going to leak? - // TODO: Static methods? - Value = method.CreateDelegate(PropertyType, reference.Target); + + Value = method.IsStatic ? method.CreateDelegate(PropertyType) : method.CreateDelegate(PropertyType, reference.Target); } public override Type PropertyType { get; } @@ -73,19 +75,18 @@ namespace Avalonia.Markup.Data.Plugins public override bool SetValue(object value, BindingPriority priority) => false; protected override void SubscribeCore(IObserver observer) - { - SendCurrentValue(); - } - - private void SendCurrentValue() { try { - var value = Value; - Observer.OnNext(value); + Observer.OnNext(Value); } catch { } } + + private void SendCurrentValue() + { + + } } } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs index 9929e4e986..439e9dc542 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs @@ -19,6 +19,11 @@ namespace Avalonia.Markup.UnitTests.Data public int MethodWithReturn() => 0; public int MethodWithReturnAndParameters(int i) => i; + + public static void StaticMethod() { } + + public static void TooManyParameters(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { } + public static int TooManyParametersWithReturnType(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) => 1; } [Fact] @@ -37,6 +42,7 @@ namespace Avalonia.Markup.UnitTests.Data [InlineData(nameof(TestObject.MethodWithoutReturn), typeof(Action))] [InlineData(nameof(TestObject.MethodWithReturn), typeof(Func))] [InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func))] + [InlineData(nameof(TestObject.StaticMethod), typeof(Action))] public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType) { var data = new TestObject(); @@ -61,5 +67,21 @@ namespace Avalonia.Markup.UnitTests.Data GC.KeepAlive(data); } + + [Theory] + [InlineData(nameof(TestObject.TooManyParameters))] + [InlineData(nameof(TestObject.TooManyParametersWithReturnType))] + public async Task Should_Return_Error_Notification_If_Too_Many_Parameters(string methodName) + { + var data = new TestObject(); + var observer = new ExpressionObserver(data, methodName); + var result = await observer.Take(1); + + Assert.IsType(result); + + Assert.Equal(BindingErrorType.Error, ((BindingNotification)result).ErrorType); + + GC.KeepAlive(data); + } } } From 6dc6ec84e975bef1f65d75816a8b558e48b4bd46 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 21:57:47 -0500 Subject: [PATCH 10/20] Add leak test to make sure the method accessor doesn't leak memory. --- .../ExpressionObserverTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Avalonia.LeakTests/ExpressionObserverTests.cs b/tests/Avalonia.LeakTests/ExpressionObserverTests.cs index 3dbc62424f..d0f892bebb 100644 --- a/tests/Avalonia.LeakTests/ExpressionObserverTests.cs +++ b/tests/Avalonia.LeakTests/ExpressionObserverTests.cs @@ -71,6 +71,28 @@ namespace Avalonia.LeakTests Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } + [Fact] + public void Should_Not_Keep_Source_Alive_MethodBinding() + { + Func run = () => + { + var source = new { Foo = new MethodBound() }; + var target = new ExpressionObserver(source, "Foo.A"); + target.Subscribe(_ => { }); + return target; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + + private class MethodBound + { + public void A() { } + } + private class NonIntegerIndexer : NotifyingBase { private readonly Dictionary _storage = new Dictionary(); From 05b6ab91c9873885e639e6ba4153b6828dde4edf Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 22:14:56 -0500 Subject: [PATCH 11/20] Disable unused event warning --- src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs index 1d417a55a6..6de9be80cc 100644 --- a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs +++ b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs @@ -14,7 +14,9 @@ namespace Avalonia.Markup this.action = action; } +#pragma warning disable 0067 public event EventHandler CanExecuteChanged; +#pragma warning restore 0067 public bool CanExecute(object parameter) => true; From 685a509c5cf7b87f1157d2e8c5ac7f1c2497e0a9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 3 Oct 2017 17:00:49 +0200 Subject: [PATCH 12/20] Reimplement commented-out tests. --- tests/Avalonia.UnitTests/TestRoot.cs | 2 - .../Rendering/DeferredRendererTests.cs | 178 +++++++++--------- 2 files changed, 87 insertions(+), 93 deletions(-) diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 90532d64a1..9ec053f075 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -49,8 +49,6 @@ namespace Avalonia.UnitTests public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService(); - public IRenderTarget RenderTarget => null; - public IRenderer Renderer { get; set; } public IAccessKeyHandler AccessKeyHandler => null; diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 047cb05c26..cec95d4807 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -129,102 +129,98 @@ namespace Avalonia.Visuals.UnitTests.Rendering [Fact] public void Frame_Should_Create_Layer_For_Root() { - throw new NotImplementedException(); - //var loop = new Mock(); - //var root = new TestRoot(); - //var rootLayer = new Mock(); - //var dispatcher = new ImmediateDispatcher(); - - //var sceneBuilder = new Mock(); - //sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) - // .Callback(scene => - // { - // scene.Size = root.ClientSize; - // scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); - // }); - - //var layers = new Mock(); - //layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); - - //var renderInterface = new Mock(); - - //var target = new DeferredRenderer( - // root, - // loop.Object, - // sceneBuilder: sceneBuilder.Object, - // layerFactory: layers.Object, - // dispatcher: dispatcher); - - //target.Start(); - //RunFrame(loop); - - //layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96)); + var loop = new Mock(); + var root = new TestRoot(); + var rootLayer = new Mock(); + var dispatcher = new ImmediateDispatcher(); + + var sceneBuilder = new Mock(); + sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) + .Callback(scene => + { + scene.Size = root.ClientSize; + scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); + }); + + var renderInterface = new Mock(); + + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: sceneBuilder.Object, + //layerFactory: layers.Object, + dispatcher: dispatcher); + + target.Start(); + RunFrame(loop); + + var context = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null)); + context.Verify(x => x.CreateLayer(root.ClientSize)); } [Fact] public void Should_Create_And_Delete_Layers_For_Transparent_Controls() { - throw new NotImplementedException(); - //Border border; - //var root = new TestRoot - //{ - // Width = 100, - // Height = 100, - // Child = new Border - // { - // Background = Brushes.Red, - // Child = border = new Border - // { - // Background = Brushes.Green, - // } - // } - //}; - - //root.Measure(Size.Infinity); - //root.Arrange(new Rect(root.DesiredSize)); - - //var loop = new Mock(); - //var layerFactory = new MockRenderLayerFactory(new Dictionary - //{ - // { root, CreateLayer() }, - // { border, CreateLayer() }, - //}); - - //var target = new DeferredRenderer( - // root, - // loop.Object, - // layerFactory: layerFactory, - // dispatcher: new ImmediateDispatcher()); - //root.Renderer = target; - - //target.Start(); - //RunFrame(loop); - - //var rootContext = layerFactory.GetMockDrawingContext(root); - //var borderContext = layerFactory.GetMockDrawingContext(border); - - //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - //borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); - - //rootContext.ResetCalls(); - //borderContext.ResetCalls(); - //border.Opacity = 0.5; - //RunFrame(loop); - - //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never); - //borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - - //rootContext.ResetCalls(); - //borderContext.ResetCalls(); - //border.Opacity = 1; - //RunFrame(loop); - - //layerFactory.GetMockBitmap(border).Verify(x => x.Dispose()); - //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - //borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + Border border; + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = new Border + { + Background = Brushes.Red, + Child = border = new Border + { + Background = Brushes.Green, + } + } + }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var rootLayer = CreateLayer(); + var borderLayer = CreateLayer(); + var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null)); + renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny())) + .Returns(rootLayer) + .Returns(borderLayer); + + var loop = new Mock(); + var target = new DeferredRenderer( + root, + loop.Object, + dispatcher: new ImmediateDispatcher()); + root.Renderer = target; + + target.Start(); + RunFrame(loop); + + var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null)); + var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null)); + + rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + + rootContext.ResetCalls(); + borderContext.ResetCalls(); + border.Opacity = 0.5; + RunFrame(loop); + + rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never); + borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + + rootContext.ResetCalls(); + borderContext.ResetCalls(); + border.Opacity = 1; + RunFrame(loop); + + Mock.Get(borderLayer).Verify(x => x.Dispose()); + rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } private void IgnoreFirstFrame(Mock loop, Mock sceneBuilder) From cf9e8cfede19978fbd55f731325d52c9a94fda91 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 3 Oct 2017 17:36:09 +0200 Subject: [PATCH 13/20] Use NotSupportedException --- .../Rendering/SceneGraph/DeferredDrawingContextImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 5fcd0e42bd..29c482c336 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -196,7 +196,7 @@ namespace Avalonia.Rendering.SceneGraph public IRenderTargetBitmapImpl CreateLayer(Size size) { - throw new NotImplementedException(); + throw new NotSupportedException("Creating layers on a deferred drawing context not supported"); } /// From bdbe95b9b1649ccfd7857cbe77caccec1e723028 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 6 Oct 2017 14:59:28 +0300 Subject: [PATCH 14/20] Fixed DispatcherTimer's priorities --- .../AndroidThreadingInterface.cs | 2 +- .../Platform/IPlatformThreadingInterface.cs | 3 ++- src/Avalonia.Base/Threading/Dispatcher.cs | 13 +++++++++++++ src/Avalonia.Base/Threading/DispatcherTimer.cs | 18 ++++-------------- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 6 ++---- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 6 ++---- .../PlatformThreadingInterface.cs | 4 ++-- .../PlatformThreadingInterface.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- .../Avalonia.iOS/PlatformThreadingInterface.cs | 2 +- .../AvaloniaObjectTests_Threading.cs | 2 +- tests/Avalonia.RenderTests/TestBase.cs | 2 +- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index 2e5b2902f4..77dfc60b83 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -30,7 +30,7 @@ namespace Avalonia.Android return; } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { if (interval.TotalMilliseconds < 10) interval = TimeSpan.FromMilliseconds(10); diff --git a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs index 68f9e2c631..9f5417ca95 100644 --- a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs @@ -17,10 +17,11 @@ namespace Avalonia.Platform /// /// Starts a timer. /// + /// /// The interval. /// The action to call on each tick. /// An used to stop the timer. - IDisposable StartTimer(TimeSpan interval, Action tick); + IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick); void Signal(DispatcherPriority priority); diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index a60b663bed..a4b1aaafbc 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -84,6 +84,19 @@ namespace Avalonia.Threading _jobRunner?.Post(action, priority); } + /// + /// This is needed for platform backends that don't have internal priority system (e. g. win32) + /// To ensure that there are no jobs with higher priority + /// + /// + internal void EnsurePriority(DispatcherPriority currentPriority) + { + if (currentPriority == DispatcherPriority.MaxValue) + return; + currentPriority += 1; + _jobRunner.RunJobs(currentPriority); + } + /// /// Allows unit tests to change the platform threading interface. /// diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index 972fdb2049..4a8a9d673f 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -17,13 +17,11 @@ namespace Avalonia.Threading private readonly DispatcherPriority _priority; private TimeSpan _interval; - - private readonly Action _raiseTickAction; - + /// /// Initializes a new instance of the class. /// - public DispatcherTimer() : this(DispatcherPriority.Normal) + public DispatcherTimer() : this(DispatcherPriority.Background) { } @@ -34,7 +32,6 @@ namespace Avalonia.Threading public DispatcherTimer(DispatcherPriority priority) { _priority = priority; - _raiseTickAction = RaiseTick; } /// @@ -187,7 +184,7 @@ namespace Avalonia.Threading throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered."); } - _timer = threading.StartTimer(Interval, InternalTick); + _timer = threading.StartTimer(_priority, Interval, InternalTick); } } @@ -210,14 +207,7 @@ namespace Avalonia.Threading /// private void InternalTick() { - Dispatcher.UIThread.InvokeAsync(_raiseTickAction, _priority); - } - - /// - /// Raises the event. - /// - private void RaiseTick() - { + Dispatcher.UIThread.EnsurePriority(_priority); Tick?.Invoke(this, EventArgs.Empty); } } diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 10405037ab..aefd873155 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -70,15 +70,13 @@ namespace Avalonia.Gtk3 Native.GtkMainIteration(); } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { var msec = interval.TotalMilliseconds; - if (msec <= 0) - throw new ArgumentException("Don't know how to create a timer with zero or negative interval"); var imsec = (uint) msec; if (imsec == 0) imsec = 1; - return GlibTimeout.StarTimer(imsec, tick); + return GlibTimeout.StartTimer(GlibPriority.FromDispatcherPriority(priority), imsec, tick); } private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1]; diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index 5253be5afb..0ab4ef980c 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -48,12 +48,10 @@ namespace Avalonia.Gtk3.Interop } } - public static IDisposable StarTimer(uint interval, Action tick) + public static IDisposable StartTimer(int priority, uint interval, Action tick) { - if (interval == 0) - throw new ArgumentException("Don't know how to create a timer with zero or negative interval"); var timer = new Timer (); - GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Normal), interval, + GlibTimeout.Add(priority, interval, () => { if (timer.Stopped) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs b/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs index 3aef6944af..e6d21fca36 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs @@ -17,7 +17,7 @@ namespace Avalonia.LinuxFramebuffer public PlatformThreadingInterface() { TlsCurrentThreadIsLoopThread = true; - StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); + StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); } private readonly AutoResetEvent _signaled = new AutoResetEvent(false); @@ -74,7 +74,7 @@ namespace Avalonia.LinuxFramebuffer } } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { return new WatTimer(new System.Threading.Timer(delegate { diff --git a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs index 80c854f5a5..184416e77a 100644 --- a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs +++ b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs @@ -16,7 +16,7 @@ namespace Avalonia.MonoMac public event Action Signaled; - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) => NSTimer.CreateRepeatingScheduledTimer(interval, () => tick()); public void Signal(DispatcherPriority prio) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index a260efd9b9..e265749249 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -114,7 +114,7 @@ namespace Avalonia.Win32 } } - public IDisposable StartTimer(TimeSpan interval, Action callback) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action callback) { UnmanagedMethods.TimerProc timerDelegate = (hWnd, uMsg, nIDEvent, dwTime) => callback(); diff --git a/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs b/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs index 6d6a5e22ca..43a620cccd 100644 --- a/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs +++ b/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs @@ -51,7 +51,7 @@ namespace Avalonia.iOS } }*/ - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) => NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick()); public void Signal(DispatcherPriority prio) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs index 229a34643d..09aedbdf9c 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs @@ -160,7 +160,7 @@ namespace Avalonia.Base.UnitTests throw new NotImplementedException(); } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 409870ed0f..84860eefdb 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -161,7 +161,7 @@ namespace Avalonia.Direct2D1.RenderTests throw new NotImplementedException(); } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { throw new NotImplementedException(); } From cc7098583e420ca70198fda7bf916eb74c1731af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Fri, 6 Oct 2017 16:57:32 +0100 Subject: [PATCH 15/20] Fixed Assert.Null warnings. --- .../Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs | 2 +- .../Generators/ItemContainerGeneratorTests.cs | 2 +- .../Presenters/ContentPresenterTests_InTemplate.cs | 4 ++-- .../Presenters/ContentPresenterTests_Standalone.cs | 2 +- .../Primitives/SelectingItemsControlTests.cs | 8 ++++---- .../Primitives/SelectingItemsControlTests_Multiple.cs | 2 +- .../Data/BindingTests_RelativeSource.cs | 2 +- .../Templates/MemberSelectorTests.cs | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 05339c43b0..e9cb2bf450 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -187,7 +187,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(45); - Assert.Equal(null, target.Foo); + Assert.Null(target.Foo); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs index 01b550fb3b..9b4be59647 100644 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs @@ -66,7 +66,7 @@ namespace Avalonia.Controls.UnitTests.Generators target.Dematerialize(1, 1); Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); - Assert.Equal(null, target.ContainerFromIndex(1)); + Assert.Null(target.ContainerFromIndex(1)); Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs index e32c703409..a524ca3e89 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs @@ -107,7 +107,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = child; target.Content = null; - Assert.Equal(null, child.GetLogicalParent()); + Assert.Null(child.GetLogicalParent()); Assert.Empty(target.GetLogicalChildren()); } @@ -120,7 +120,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = child; target.Content = null; - Assert.Equal(null, child.GetVisualParent()); + Assert.Null(child.GetVisualParent()); Assert.Empty(target.GetVisualChildren()); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index 589b1d67d2..032928d673 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs @@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = "bar"; target.UpdateChild(); - Assert.Equal(null, foo.Parent); + Assert.Null(foo.Parent); logicalChildren = target.GetLogicalChildren(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 7ee9fbbf52..a60074fa43 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -171,7 +171,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.SelectedItem = new Item(); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } @@ -278,7 +278,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.Items = null; - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } @@ -305,7 +305,7 @@ namespace Avalonia.Controls.UnitTests.Primitives items.RemoveAt(1); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } @@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests.Primitives items.Clear(); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index d8600f472d..642f594e4d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -76,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.SelectedItems = new AvaloniaList(); Assert.Equal(-1, target.SelectedIndex); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); } [Fact] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs index e912770470..c46fb6fce2 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs @@ -117,7 +117,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data }; target.Bind(TextBox.TextProperty, binding); - Assert.Equal(null, target.Text); + Assert.Null(target.Text); } [Fact] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs index 49a88e8fae..aa1e56f2a5 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs @@ -63,7 +63,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates { var selector = new MemberSelector() { MemberName = "StringValue" }; - Assert.Equal(null, selector.Select(null)); + Assert.Null(selector.Select(null)); } [Fact] @@ -73,7 +73,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates var data = new Item() { StringValue = "Value1" }; - Assert.Same(null, selector.Select(data)); + Assert.Null(selector.Select(data)); } [Fact] From abe47da717f1269e0d62fa84e0e187a97c7a7fa3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 7 Oct 2017 19:05:51 +0200 Subject: [PATCH 16/20] Fix flicker on resize under D2D. Still getting a "rubber banding" effect that needs to be fixed though. --- .../Rendering/DeferredRenderer.cs | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 81a2998fd2..5434a35464 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -185,36 +185,40 @@ namespace Avalonia.Rendering try { - using (var context = _renderTarget.CreateDrawingContext(this)) + if (scene != null && scene.Size != Size.Empty) { - if (scene != null && scene.Size != Size.Empty) + IDrawingContextImpl context = null; + + if (scene.Generation != _lastSceneId) { - if (scene.Generation != _lastSceneId) - { - _layers.Update(scene, context); + context = _renderTarget.CreateDrawingContext(this); + _layers.Update(scene, context); - RenderToLayers(scene); + RenderToLayers(scene); - if (DebugFramesPath != null) - { - SaveDebugFrames(scene.Generation); - } + if (DebugFramesPath != null) + { + SaveDebugFrames(scene.Generation); + } - _lastSceneId = scene.Generation; + _lastSceneId = scene.Generation; - composite = true; - } + composite = true; + } - if (renderOverlay) - { - RenderOverlay(scene, context); - RenderComposite(scene, context); - } - else if (composite) - { - RenderComposite(scene, context); - } + if (renderOverlay) + { + context = context ?? _renderTarget.CreateDrawingContext(this); + RenderOverlay(scene, context); + RenderComposite(scene, context); + } + else if (composite) + { + context = context ?? _renderTarget.CreateDrawingContext(this); + RenderComposite(scene, context); } + + context?.Dispose(); } } catch (RenderTargetCorruptedException ex) From 120eb2dde6d9671e712f8c456c684bda467a0c8d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 7 Oct 2017 15:00:46 -0500 Subject: [PATCH 17/20] PR Feedback. --- .../Data/Plugins/MethodAccessorPlugin.cs | 5 - .../Avalonia.Markup/DefaultValueConverter.cs | 2 +- .../Data/BindingTests_Method.cs | 106 ++++++++++++++++++ 3 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs index c602fa4f4d..db0d3e0299 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs @@ -82,11 +82,6 @@ namespace Avalonia.Markup.Data.Plugins } catch { } } - - private void SendCurrentValue() - { - - } } } } diff --git a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs index bb6bd85827..28d64eb561 100644 --- a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs +++ b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs @@ -35,7 +35,7 @@ namespace Avalonia.Markup return AvaloniaProperty.UnsetValue; } - if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d) + if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) { return new AlwaysEnabledDelegateCommand(d); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs new file mode 100644 index 0000000000..031f14d686 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs @@ -0,0 +1,106 @@ +// 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.Reactive.Subjects; +using System.Windows.Input; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Data +{ + public class BindingTests_Method + { + [Fact] + public void Binding_Method_To_Command_Works() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + +