From 66bebd0c1b5a5c4ece8c08c675c0ef926b41e3c9 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Sep 2018 16:45:27 +0200 Subject: [PATCH 01/17] Initial --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 91 ++++++++++--------- .../ExternalRenderTarget.cs | 22 +---- .../FramebufferShimRenderTarget.cs | 27 +----- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 10 +- .../Media/Direct2D1FontCollectionCache.cs | 13 +-- .../Media/DrawingContextImpl.cs | 10 +- .../Media/FormattedTextImpl.cs | 4 +- .../Avalonia.Direct2D1/Media/GeometryImpl.cs | 3 +- .../Media/Imaging/BitmapImpl.cs | 7 -- .../Media/Imaging/D2DBitmapImpl.cs | 28 +++--- .../Imaging/D2DRenderTargetBitmapImpl.cs | 39 +++----- .../Media/Imaging/WicBitmapImpl.cs | 33 +++---- .../Imaging/WicRenderTargetBitmapImpl.cs | 22 ++--- .../Media/Imaging/WriteableWicBitmapImpl.cs | 8 +- .../Media/StreamGeometryImpl.cs | 6 +- .../Avalonia.Direct2D1/RenderTarget.cs | 19 +--- .../SwapChainRenderTarget.cs | 55 +++-------- 17 files changed, 132 insertions(+), 265 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 296edcb2d9..437bae6fd1 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -4,14 +4,12 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using Avalonia.Direct2D1.Media; -using Avalonia.Media; -using Avalonia.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; -using Avalonia.Rendering; +using Avalonia.Media; +using Avalonia.Platform; namespace Avalonia { @@ -31,15 +29,16 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); - private static SharpDX.Direct2D1.Factory s_d2D1Factory; + public static SharpDX.Direct2D1.Factory1 Direct2D1Factory { get; private set; } - private static SharpDX.DirectWrite.Factory s_dwfactory; + public static SharpDX.Direct2D1.Device1 Direct2D1Device { get; private set; } - private static SharpDX.WIC.ImagingFactory s_imagingFactory; + public static SharpDX.DirectWrite.Factory1 DirectWriteFactory { get; private set; } - private static SharpDX.DXGI.Device s_dxgiDevice; + public static SharpDX.WIC.ImagingFactory ImagingFactory { get; private set; } + + public static SharpDX.DXGI.Device1 DxgiDevice { get; private set; } - private static SharpDX.Direct2D1.Device s_d2D1Device; private static readonly object s_initLock = new object(); private static bool s_initialized = false; @@ -49,13 +48,14 @@ namespace Avalonia.Direct2D1 lock (s_initLock) { if (s_initialized) + { return; + } #if DEBUG try { - s_d2D1Factory = - - new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, + Direct2D1Factory = new SharpDX.Direct2D1.Factory1( + SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); } catch @@ -63,12 +63,19 @@ namespace Avalonia.Direct2D1 // } #endif - s_dwfactory = new SharpDX.DirectWrite.Factory(); - s_imagingFactory = new SharpDX.WIC.ImagingFactory(); - if (s_d2D1Factory == null) - s_d2D1Factory = new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, + if (Direct2D1Factory == null) + { + Direct2D1Factory = new SharpDX.Direct2D1.Factory1( + SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None); + } + using (var factory = new SharpDX.DirectWrite.Factory()) + { + DirectWriteFactory = factory.QueryInterface(); + } + + ImagingFactory = new SharpDX.WIC.ImagingFactory(); var featureLevels = new[] { @@ -83,17 +90,18 @@ namespace Avalonia.Direct2D1 using (var d3dDevice = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, featureLevels)) { - s_dxgiDevice = d3dDevice.QueryInterface(); + DxgiDevice = d3dDevice.QueryInterface(); } - using (var factory1 = s_d2D1Factory.QueryInterface()) + using (var device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice)) { - s_d2D1Device = new SharpDX.Direct2D1.Device(factory1, s_dxgiDevice); + Direct2D1Device = device.QueryInterface(); } + s_initialized = true; } } @@ -101,19 +109,13 @@ namespace Avalonia.Direct2D1 public static void Initialize() { InitializeDirect2D(); - AvaloniaLocator.CurrentMutable - .Bind().ToConstant(s_instance) - .BindToSelf(s_d2D1Factory) - .BindToSelf(s_dwfactory) - .BindToSelf(s_imagingFactory) - .BindToSelf(s_dxgiDevice) - .BindToSelf(s_d2D1Device); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(s_instance); SharpDX.Configuration.EnableReleaseOnFinalizer = true; } public IBitmapImpl CreateBitmap(int width, int height) { - return new WicBitmapImpl(s_imagingFactory, width, height); + return new WicBitmapImpl(width, height); } public IFormattedTextImpl CreateFormattedText( @@ -140,14 +142,22 @@ namespace Avalonia.Direct2D1 if (s is IPlatformHandle nativeWindow) { if (nativeWindow.HandleDescriptor != "HWND") + { throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " + nativeWindow.HandleDescriptor); + } + return new HwndRenderTarget(nativeWindow); } if (s is IExternalDirect2DRenderTargetSurface external) - return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory); + { + return new ExternalRenderTarget(external); + } + if (s is IFramebufferPlatformSurface fb) - return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory); + { + return new FramebufferShimRenderTarget(fb); + } } throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } @@ -158,19 +168,12 @@ namespace Avalonia.Direct2D1 double dpiX, double dpiY) { - return new WicRenderTargetBitmapImpl( - s_imagingFactory, - s_d2D1Factory, - s_dwfactory, - width, - height, - dpiX, - dpiY); + return new WicRenderTargetBitmapImpl(width, height, dpiX, dpiY); } public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null) { - return new WriteableWicBitmapImpl(s_imagingFactory, width, height, format); + return new WriteableWicBitmapImpl(width, height, format); } public IStreamGeometryImpl CreateStreamGeometry() @@ -180,17 +183,17 @@ namespace Avalonia.Direct2D1 public IBitmapImpl LoadBitmap(string fileName) { - return new WicBitmapImpl(s_imagingFactory, fileName); + return new WicBitmapImpl(fileName); } public IBitmapImpl LoadBitmap(Stream stream) { - return new WicBitmapImpl(s_imagingFactory, stream); + return new WicBitmapImpl(stream); } public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) { - return new WicBitmapImpl(s_imagingFactory, format, data, width, height, stride); + return new WicBitmapImpl(format, data, width, height, stride); } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index 176cedd377..aad50331d2 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -1,27 +1,19 @@ -using System; -using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; -using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1 { class ExternalRenderTarget : IRenderTarget, ILayerFactory { private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; - private readonly DirectWriteFactory _dwFactory; - private readonly SharpDX.WIC.ImagingFactory _wicFactory; public ExternalRenderTarget( - IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, - DirectWriteFactory dwFactory, - SharpDX.WIC.ImagingFactory wicFactory) + IExternalDirect2DRenderTargetSurface externalRenderTargetProvider) { _externalRenderTargetProvider = externalRenderTargetProvider; - _dwFactory = dwFactory; - _wicFactory = wicFactory; } public void Dispose() @@ -33,7 +25,7 @@ namespace Avalonia.Direct2D1 { var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); _externalRenderTargetProvider.BeforeDrawing(); - return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () => + return new DrawingContextImpl(visualBrushRenderer, null, target, null, () => { try { @@ -48,12 +40,8 @@ namespace Avalonia.Direct2D1 public IRenderTargetBitmapImpl CreateLayer(Size size) { - var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); - return D2DRenderTargetBitmapImpl.CreateCompatible( - _wicFactory, - _dwFactory, - target, - size); + var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget(); + return D2DRenderTargetBitmapImpl.CreateCompatible(renderTarget, size); } } } diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs index 523cfeed46..5ae174083c 100644 --- a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs @@ -1,14 +1,9 @@ using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Text; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Direct2D1.Media; -using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Interop; -using SharpDX.Direct2D1; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -17,22 +12,14 @@ namespace Avalonia.Direct2D1 class FramebufferShimRenderTarget : IRenderTarget { private readonly IFramebufferPlatformSurface _surface; - private readonly ImagingFactory _imagingFactory; - private readonly Factory _d2DFactory; - private readonly SharpDX.DirectWrite.Factory _dwriteFactory; - public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface, - ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory) + public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface) { _surface = surface; - _imagingFactory = imagingFactory; - _d2DFactory = d2dFactory; - _dwriteFactory = dwriteFactory; } public void Dispose() - { - + { } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) @@ -44,7 +31,7 @@ namespace Avalonia.Direct2D1 throw new ArgumentException("Unsupported pixel format: " + locked.Format); } - return new FramebufferShim(locked, _imagingFactory, _d2DFactory, _dwriteFactory) + return new FramebufferShim(locked) .CreateDrawingContext(visualBrushRenderer); } @@ -52,10 +39,8 @@ namespace Avalonia.Direct2D1 { private readonly ILockedFramebuffer _target; - public FramebufferShim(ILockedFramebuffer target, - ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory - ) : base(imagingFactory, d2dFactory, dwriteFactory, - target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) + public FramebufferShim(ILockedFramebuffer target) : + base(target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) { _target = target; } @@ -76,10 +61,8 @@ namespace Avalonia.Direct2D1 } Dispose(); _target.Dispose(); - }); } } - } } diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 49402d54b9..589f85f208 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Controls.Platform.Surfaces; -using Avalonia.Platform; +using Avalonia.Platform; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.DXGI; @@ -22,7 +16,7 @@ namespace Avalonia.Direct2D1 protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) { - return new SwapChain1(dxgiFactory, DxgiDevice, _window.Handle, ref swapChainDesc); + return new SwapChain1(dxgiFactory, Direct2D1Platform.DxgiDevice, _window.Handle, ref swapChainDesc); } protected override Size2F GetWindowDpi() diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs index 4199c73c54..d60aa15a5e 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs @@ -7,16 +7,13 @@ namespace Avalonia.Direct2D1.Media internal static class Direct2D1FontCollectionCache { private static readonly ConcurrentDictionary s_cachedCollections; - private static readonly SharpDX.DirectWrite.Factory s_factory; private static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; static Direct2D1FontCollectionCache() { s_cachedCollections = new ConcurrentDictionary(); - s_factory = AvaloniaLocator.Current.GetService(); - - s_installedFontCollection = s_factory.GetSystemFontCollection(false); + s_installedFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false); } public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface) @@ -39,7 +36,7 @@ namespace Avalonia.Direct2D1.Media } return new SharpDX.DirectWrite.TextFormat( - s_factory, + Direct2D1Platform.DirectWriteFactory, fontFamilyName, fontCollection, (SharpDX.DirectWrite.FontWeight)typeface.Weight, @@ -57,9 +54,9 @@ namespace Avalonia.Direct2D1.Media { var assets = FontFamilyLoader.LoadFontAssets(key); - var fontLoader = new DWriteResourceFontLoader(s_factory, assets); + var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets); - return new SharpDX.DirectWrite.FontCollection(s_factory, fontLoader, fontLoader.Key); + return new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index ae5dd3ae13..3aec6c6eb1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -24,8 +24,6 @@ namespace Avalonia.Direct2D1.Media private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; - private readonly SharpDX.WIC.ImagingFactory _imagingFactory; - private SharpDX.DirectWrite.Factory _directWriteFactory; /// /// Initializes a new instance of the class. @@ -36,16 +34,12 @@ namespace Avalonia.Direct2D1.Media /// An object to use to create layers. May be null, in which case a /// will created when a new layer is requested. /// - /// The DirectWrite factory. - /// The WIC imaging factory. /// An optional swap chain associated with this drawing context. /// An optional delegate to be called when context is disposed. public DrawingContextImpl( IVisualBrushRenderer visualBrushRenderer, ILayerFactory layerFactory, SharpDX.Direct2D1.RenderTarget renderTarget, - SharpDX.DirectWrite.Factory directWriteFactory, - SharpDX.WIC.ImagingFactory imagingFactory, SharpDX.DXGI.SwapChain1 swapChain = null, Action finishedCallback = null) { @@ -54,8 +48,6 @@ namespace Avalonia.Direct2D1.Media _renderTarget = renderTarget; _swapChain = swapChain; _finishedCallback = finishedCallback; - _directWriteFactory = directWriteFactory; - _imagingFactory = imagingFactory; _renderTarget.BeginDraw(); } @@ -443,7 +435,7 @@ namespace Avalonia.Direct2D1.Media return new ImageBrushImpl( visualBrush, _renderTarget, - new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap), + new D2DBitmapImpl(intermediate.Bitmap), destinationSize); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index c3fe60e790..09b249e19f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -21,15 +21,13 @@ namespace Avalonia.Direct2D1.Media { Text = text; - var factory = AvaloniaLocator.Current.GetService(); - var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface); textFormat.WordWrapping = wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; TextLayout = new DWrite.TextLayout( - factory, + Direct2D1Platform.DirectWriteFactory, Text ?? string.Empty, textFormat, (float)constraint.Width, diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index 120ab71ead..7c8ddaca3f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -53,10 +53,9 @@ namespace Avalonia.Direct2D1.Media public ITransformedGeometryImpl WithTransform(Matrix transform) { - var factory = AvaloniaLocator.Current.GetService(); return new TransformedGeometryImpl( new TransformedGeometry( - factory, + Direct2D1Platform.Direct2D1Factory, GetSourceGeometry(), transform.ToDirect2D()), this); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index d58f023391..30af01283a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -1,19 +1,12 @@ using System; using System.IO; using Avalonia.Platform; -using SharpDX.WIC; using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { public abstract class BitmapImpl : IBitmapImpl, IDisposable { - public BitmapImpl(ImagingFactory imagingFactory) - { - WicImagingFactory = imagingFactory; - } - - public ImagingFactory WicImagingFactory { get; } public abstract int PixelWidth { get; } public abstract int PixelHeight { get; } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 6713cb13be..139100490c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,7 +1,6 @@ using System; using System.IO; using SharpDX.Direct2D1; -using WICFactory = SharpDX.WIC.ImagingFactory; using ImagingFactory2 = SharpDX.WIC.ImagingFactory2; using ImageParameters = SharpDX.WIC.ImageParameters; using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder; @@ -13,52 +12,51 @@ namespace Avalonia.Direct2D1.Media /// public class D2DBitmapImpl : BitmapImpl { - private Bitmap _direct2D; + private Bitmap _direct2DBitmap; /// /// Initialize a new instance of the class /// with a bitmap backed by GPU memory. /// - /// The image factory to use when saving out this bitmap. /// The GPU bitmap. /// /// This bitmap must be either from the same render target, /// or if the render target is a , /// the device associated with this context, to be renderable. /// - public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap) - : base(imagingFactory) + public D2DBitmapImpl(Bitmap d2DBitmap) { - _direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); + _direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); } - public override int PixelWidth => _direct2D.PixelSize.Width; - public override int PixelHeight => _direct2D.PixelSize.Height; + public override int PixelWidth => _direct2DBitmap.PixelSize.Width; + public override int PixelHeight => _direct2DBitmap.PixelSize.Height; public override void Dispose() { base.Dispose(); - _direct2D.Dispose(); + _direct2DBitmap.Dispose(); } public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) { - return new OptionalDispose(_direct2D, false); + return new OptionalDispose(_direct2DBitmap, false); } public override void Save(Stream stream) { - using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder)) - using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null)) + //ToDo: Not supported under Windows 7! + using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)Direct2D1Platform.ImagingFactory, null)) { var parameters = new ImageParameters( new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied), - _direct2D.DotsPerInch.Width, - _direct2D.DotsPerInch.Height, + _direct2DBitmap.DotsPerInch.Width, + _direct2DBitmap.DotsPerInch.Height, 0, 0, PixelWidth, PixelHeight); - imageEncoder.WriteFrame(_direct2D, frameEncode, parameters); + imageEncoder.WriteFrame(_direct2DBitmap, frameEncode, parameters); frameEncode.Commit(); encoder.Commit(); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 2843848fac..7ea303345a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -1,35 +1,25 @@ -using System; -using Avalonia.Platform; +using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; -using SharpDX.WIC; using D2DBitmap = SharpDX.Direct2D1.Bitmap; -using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media.Imaging { public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory { - private readonly DirectWriteFactory _dwriteFactory; - private readonly BitmapRenderTarget _target; + private readonly BitmapRenderTarget _renderTarget; - public D2DRenderTargetBitmapImpl( - ImagingFactory imagingFactory, - DirectWriteFactory dwriteFactory, - BitmapRenderTarget target) - : base(imagingFactory, target.Bitmap) + public D2DRenderTargetBitmapImpl(BitmapRenderTarget renderTarget) + : base(renderTarget.Bitmap) { - _dwriteFactory = dwriteFactory; - _target = target; + _renderTarget = renderTarget; } - public override int PixelWidth => _target.PixelSize.Width; - public override int PixelHeight => _target.PixelSize.Height; + public override int PixelWidth => _renderTarget.PixelSize.Width; + public override int PixelHeight => _renderTarget.PixelSize.Height; public static D2DRenderTargetBitmapImpl CreateCompatible( - ImagingFactory imagingFactory, - DirectWriteFactory dwriteFactory, SharpDX.Direct2D1.RenderTarget renderTarget, Size size) { @@ -37,32 +27,27 @@ namespace Avalonia.Direct2D1.Media.Imaging renderTarget, CompatibleRenderTargetOptions.None, new Size2F((float)size.Width, (float)size.Height)); - return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget); + return new D2DRenderTargetBitmapImpl(bitmapRenderTarget); } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl( - visualBrushRenderer, - this, - _target, - _dwriteFactory, - WicImagingFactory); + return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget); } public IRenderTargetBitmapImpl CreateLayer(Size size) { - return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size); + return CreateCompatible(_renderTarget, size); } public override void Dispose() { - _target.Dispose(); + _renderTarget.Dispose(); } public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) { - return new OptionalDispose(_target.Bitmap, false); + return new OptionalDispose(_renderTarget.Bitmap, false); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index 371dfcfc3e..2c41f1203f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -19,57 +19,52 @@ namespace Avalonia.Direct2D1.Media /// /// Initializes a new instance of the class. /// - /// The WIC imaging factory to use. /// The filename of the bitmap to load. - public WicBitmapImpl(ImagingFactory factory, string fileName) - : base(factory) + public WicBitmapImpl(string fileName) { - using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand)) + using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand)) { - WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); } } /// /// Initializes a new instance of the class. /// - /// The WIC imaging factory to use. /// The stream to read the bitmap from. - public WicBitmapImpl(ImagingFactory factory, Stream stream) - : base(factory) + public WicBitmapImpl(Stream stream) { - using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad)) + using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad)) { - WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); } } /// /// Initializes a new instance of the class. /// - /// The WIC imaging factory to use. /// The width of the bitmap. /// The height of the bitmap. /// Pixel format - public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null) - : base(factory) + public WicBitmapImpl(int width, int height, APixelFormat? pixelFormat = null) { if (!pixelFormat.HasValue) + { pixelFormat = APixelFormat.Bgra8888; + } PixelFormat = pixelFormat; WicImpl = new Bitmap( - factory, + Direct2D1Platform.ImagingFactory, width, height, pixelFormat.Value.ToWic(), BitmapCreateCacheOption.CacheOnLoad); } - public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride) - : base(factory) + public WicBitmapImpl(APixelFormat format, IntPtr data, int width, int height, int stride) { - WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); PixelFormat = format; using (var l = WicImpl.Lock(BitmapLockFlags.Write)) { @@ -112,14 +107,14 @@ namespace Avalonia.Direct2D1.Media /// The Direct2D bitmap. public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) { - FormatConverter converter = new FormatConverter(WicImagingFactory); + FormatConverter converter = new FormatConverter(Direct2D1Platform.ImagingFactory); converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } public override void Save(Stream stream) { - using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frame = new BitmapFrameEncode(encoder)) { frame.Initialize(); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index 0eb2608047..aa8b3ead42 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -5,26 +5,20 @@ using System; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX.Direct2D1; -using SharpDX.WIC; -using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media { public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl { - private readonly DirectWriteFactory _dwriteFactory; - private readonly WicRenderTarget _target; + private readonly WicRenderTarget _renderTarget; public WicRenderTargetBitmapImpl( - ImagingFactory imagingFactory, - Factory d2dFactory, - DirectWriteFactory dwriteFactory, int width, int height, double dpiX, double dpiY, Platform.PixelFormat? pixelFormat = null) - : base(imagingFactory, width, height, pixelFormat) + : base(width, height, pixelFormat) { var props = new RenderTargetProperties { @@ -32,17 +26,16 @@ namespace Avalonia.Direct2D1.Media DpiY = (float)dpiY, }; - _target = new WicRenderTarget( - d2dFactory, + _renderTarget = new WicRenderTarget( + Direct2D1Platform.Direct2D1Factory, WicImpl, props); - - _dwriteFactory = dwriteFactory; } public override void Dispose() { - _target.Dispose(); + _renderTarget.Dispose(); + base.Dispose(); } @@ -51,8 +44,7 @@ namespace Avalonia.Direct2D1.Media public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback) { - return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory, - finishedCallback: finishedCallback); + return new DrawingContextImpl(visualBrushRenderer, null, _renderTarget, finishedCallback: finishedCallback); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index fc931c32db..075fef5ab2 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Platform; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -11,8 +7,8 @@ namespace Avalonia.Direct2D1.Media.Imaging { class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl { - public WriteableWicBitmapImpl(ImagingFactory factory, int width, int height, PixelFormat? pixelFormat) - : base(factory, width, height, pixelFormat) + public WriteableWicBitmapImpl(int width, int height, PixelFormat? pixelFormat) + : base(width, height, pixelFormat) { } diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs index 4c1bc3d6f7..a07d5c1c72 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs @@ -31,8 +31,7 @@ namespace Avalonia.Direct2D1.Media /// public IStreamGeometryImpl Clone() { - Factory factory = AvaloniaLocator.Current.GetService(); - var result = new PathGeometry(factory); + var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory); var sink = result.Open(); ((PathGeometry)Geometry).Stream(sink); sink.Close(); @@ -47,8 +46,7 @@ namespace Avalonia.Direct2D1.Media private static Geometry CreateGeometry() { - Factory factory = AvaloniaLocator.Current.GetService(); - return new PathGeometry(factory); + return new PathGeometry(Direct2D1Platform.Direct2D1Factory); } } } diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index 6086b0c67c..a21b5571c6 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -1,14 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; -using SharpDX.Direct2D1; -using DwFactory = SharpDX.DirectWrite.Factory; -using WicFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1 { @@ -25,32 +21,21 @@ namespace Avalonia.Direct2D1 /// The render target. public RenderTarget(SharpDX.Direct2D1.RenderTarget renderTarget) { - Direct2DFactory = AvaloniaLocator.Current.GetService(); - DirectWriteFactory = AvaloniaLocator.Current.GetService(); - WicFactory = AvaloniaLocator.Current.GetService(); _renderTarget = renderTarget; } - public Factory Direct2DFactory { get; } - public DwFactory DirectWriteFactory { get; } - public WicFactory WicFactory { get; } - /// /// Creates a drawing context for a rendering session. /// /// An . public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory); + return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget); } public IRenderTargetBitmapImpl CreateLayer(Size size) { - return D2DRenderTargetBitmapImpl.CreateCompatible( - WicFactory, - DirectWriteFactory, - _renderTarget, - size); + return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size); } public void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 0a23c63498..f820129b8d 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -1,16 +1,14 @@ -using System; +using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; +using Avalonia.Rendering; + using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; + using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using Device = SharpDX.Direct2D1.Device; -using Factory = SharpDX.Direct2D1.Factory; -using Factory2 = SharpDX.DXGI.Factory2; -using Avalonia.Rendering; -using Avalonia.Direct2D1.Media; -using Avalonia.Direct2D1.Media.Imaging; +using PixelFormat = SharpDX.Direct2D1.PixelFormat; namespace Avalonia.Direct2D1 { @@ -21,23 +19,6 @@ namespace Avalonia.Direct2D1 private DeviceContext _deviceContext; private SwapChain1 _swapChain; - protected SwapChainRenderTarget() - { - DxgiDevice = AvaloniaLocator.Current.GetService(); - D2DDevice = AvaloniaLocator.Current.GetService(); - Direct2DFactory = AvaloniaLocator.Current.GetService(); - DirectWriteFactory = AvaloniaLocator.Current.GetService(); - WicImagingFactory = AvaloniaLocator.Current.GetService(); - } - - public Factory Direct2DFactory { get; } - public SharpDX.DirectWrite.Factory DirectWriteFactory { get; } - public SharpDX.WIC.ImagingFactory WicImagingFactory { get; } - - protected SharpDX.DXGI.Device DxgiDevice { get; } - - public Device D2DDevice { get; } - /// /// Creates a drawing context for a rendering session. /// @@ -54,13 +35,7 @@ namespace Avalonia.Direct2D1 CreateSwapChain(); } - return new DrawingContextImpl( - visualBrushRenderer, - this, - _deviceContext, - DirectWriteFactory, - WicImagingFactory, - _swapChain); + return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain); } public IRenderTargetBitmapImpl CreateLayer(Size size) @@ -70,11 +45,7 @@ namespace Avalonia.Direct2D1 CreateSwapChain(); } - return D2DRenderTargetBitmapImpl.CreateCompatible( - WicImagingFactory, - DirectWriteFactory, - _deviceContext, - size); + return D2DRenderTargetBitmapImpl.CreateCompatible(_deviceContext, size); } public void Dispose() @@ -85,11 +56,11 @@ namespace Avalonia.Direct2D1 private void CreateSwapChain() { - using (var dxgiAdaptor = DxgiDevice.Adapter) - using (var dxgiFactory = dxgiAdaptor.GetParent()) + using (var dxgiAdaptor = Direct2D1Platform.DxgiDevice.Adapter) + using (var dxgiFactory = dxgiAdaptor.GetParent()) { _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; + _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi }; var swapChainDesc = new SwapChainDescription1 { @@ -119,7 +90,7 @@ namespace Avalonia.Direct2D1 new BitmapProperties1( new PixelFormat { - AlphaMode = AlphaMode.Ignore, + AlphaMode = AlphaMode.Premultiplied, Format = Format.B8G8R8A8_UNorm }, _savedDpi.Width, @@ -131,7 +102,7 @@ namespace Avalonia.Direct2D1 } } - protected abstract SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc); + protected abstract SwapChain1 CreateSwapChain(SharpDX.DXGI.Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc); protected abstract Size2F GetWindowDpi(); From e76b694ebdcc4b2ca79532cb30183ea8aa98fc79 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Sep 2018 17:02:22 +0200 Subject: [PATCH 02/17] InterpolationMode.HighQuality support --- .../Media/DrawingContextImpl.cs | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 3aec6c6eb1..b842f26edb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -22,6 +22,7 @@ namespace Avalonia.Direct2D1.Media private readonly IVisualBrushRenderer _visualBrushRenderer; private readonly ILayerFactory _layerFactory; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; + private readonly DeviceContext _deviceContext; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; @@ -48,7 +49,17 @@ namespace Avalonia.Direct2D1.Media _renderTarget = renderTarget; _swapChain = swapChain; _finishedCallback = finishedCallback; - _renderTarget.BeginDraw(); + + if (_renderTarget is DeviceContext deviceContext) + { + _deviceContext = deviceContext; + } + else + { + _deviceContext = _renderTarget.QueryInterface(); + } + + _deviceContext.BeginDraw(); } /// @@ -56,14 +67,14 @@ namespace Avalonia.Direct2D1.Media /// public Matrix Transform { - get { return _renderTarget.Transform.ToAvalonia(); } - set { _renderTarget.Transform = value.ToDirect2D(); } + get { return _deviceContext.Transform.ToAvalonia(); } + set { _deviceContext.Transform = value.ToDirect2D(); } } /// public void Clear(Color color) { - _renderTarget.Clear(color.ToDirect2D()); + _deviceContext.Clear(color.ToDirect2D()); } /// @@ -72,10 +83,13 @@ namespace Avalonia.Direct2D1.Media public void Dispose() { foreach (var layer in _layerPool) + { layer.Dispose(); + } + try { - _renderTarget.EndDraw(); + _deviceContext.EndDraw(); _swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None); _finishedCallback?.Invoke(); @@ -96,29 +110,32 @@ namespace Avalonia.Direct2D1.Media /// The bitmap interpolation mode. public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { - using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) + using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) { var interpolationMode = GetInterpolationMode(bitmapInterpolationMode); - _renderTarget.DrawBitmap( + _deviceContext.DrawBitmap( d2d.Value, destRect.ToSharpDX(), (float)opacity, interpolationMode, - sourceRect.ToSharpDX()); + sourceRect.ToSharpDX(), + null); } } - private static SharpDX.Direct2D1.BitmapInterpolationMode GetInterpolationMode(BitmapInterpolationMode interpolationMode) + private static InterpolationMode GetInterpolationMode(BitmapInterpolationMode interpolationMode) { switch (interpolationMode) { case BitmapInterpolationMode.LowQuality: - return SharpDX.Direct2D1.BitmapInterpolationMode.NearestNeighbor; + return InterpolationMode.NearestNeighbor; case BitmapInterpolationMode.MediumQuality: + return InterpolationMode.Linear; case BitmapInterpolationMode.HighQuality: + return InterpolationMode.HighQualityCubic; case BitmapInterpolationMode.Default: - return SharpDX.Direct2D1.BitmapInterpolationMode.Linear; + return InterpolationMode.Linear; default: throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); } @@ -133,17 +150,17 @@ namespace Avalonia.Direct2D1.Media /// The rect in the output to draw to. public void DrawImage(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { - using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) - using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value)) + using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) + using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value)) using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) - using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) + using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_deviceContext.Factory, destRect.ToDirect2D())) { if (d2dOpacityMask.PlatformBrush != null) { d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D(); } - _renderTarget.FillGeometry( + _deviceContext.FillGeometry( geometry, sourceBrush, d2dOpacityMask.PlatformBrush); @@ -163,11 +180,11 @@ namespace Avalonia.Direct2D1.Media var size = new Rect(p1, p2).Size; using (var d2dBrush = CreateBrush(pen.Brush, size)) - using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) { - _renderTarget.DrawLine( + _deviceContext.DrawLine( p1.ToSharpDX(), p2.ToSharpDX(), d2dBrush.PlatformBrush, @@ -193,7 +210,7 @@ namespace Avalonia.Direct2D1.Media if (d2dBrush.PlatformBrush != null) { var impl = (GeometryImpl)geometry; - _renderTarget.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush); + _deviceContext.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush); } } } @@ -201,12 +218,12 @@ namespace Avalonia.Direct2D1.Media if (pen != null) { using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size)) - using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) { var impl = (GeometryImpl)geometry; - _renderTarget.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke); + _deviceContext.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke); } } } @@ -221,13 +238,13 @@ namespace Avalonia.Direct2D1.Media public void DrawRectangle(Pen pen, Rect rect, float cornerRadius) { using (var brush = CreateBrush(pen.Brush, rect.Size)) - using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (brush.PlatformBrush != null) { if (cornerRadius == 0) { - _renderTarget.DrawRectangle( + _deviceContext.DrawRectangle( rect.ToDirect2D(), brush.PlatformBrush, (float)pen.Thickness, @@ -235,7 +252,7 @@ namespace Avalonia.Direct2D1.Media } else { - _renderTarget.DrawRoundedRectangle( + _deviceContext.DrawRoundedRectangle( new RoundedRectangle { Rect = rect.ToDirect2D(), RadiusX = cornerRadius, RadiusY = cornerRadius }, brush.PlatformBrush, (float)pen.Thickness, @@ -258,7 +275,7 @@ namespace Avalonia.Direct2D1.Media var impl = (FormattedTextImpl)text; using (var brush = CreateBrush(foreground, impl.Size)) - using (var renderer = new AvaloniaTextRenderer(this, _renderTarget, brush.PlatformBrush)) + using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush)) { if (brush.PlatformBrush != null) { @@ -282,11 +299,11 @@ namespace Avalonia.Direct2D1.Media { if (cornerRadius == 0) { - _renderTarget.FillRectangle(rect.ToDirect2D(), b.PlatformBrush); + _deviceContext.FillRectangle(rect.ToDirect2D(), b.PlatformBrush); } else { - _renderTarget.FillRoundedRectangle( + _deviceContext.FillRoundedRectangle( new RoundedRectangle { Rect = new RawRectangleF( @@ -312,7 +329,7 @@ namespace Avalonia.Direct2D1.Media else { var platform = AvaloniaLocator.Current.GetService(); - var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height); + var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); var pixelSize = size * (dpi / 96); return platform.CreateRenderTargetBitmap( (int)pixelSize.Width, @@ -329,12 +346,12 @@ namespace Avalonia.Direct2D1.Media /// A disposable used to undo the clip rectangle. public void PushClip(Rect clip) { - _renderTarget.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive); + _deviceContext.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive); } public void PopClip() { - _renderTarget.PopAxisAlignedClip(); + _deviceContext.PopAxisAlignedClip(); } readonly Stack _layers = new Stack(); @@ -355,8 +372,8 @@ namespace Avalonia.Direct2D1.Media Opacity = (float)opacity, }; - var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); - _renderTarget.PushLayer(ref parameters, layer); + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); + _deviceContext.PushLayer(ref parameters, layer); _layers.Push(layer); } @@ -374,7 +391,7 @@ namespace Avalonia.Direct2D1.Media var layer = _layers.Pop(); if (layer != null) { - _renderTarget.PopLayer(); + _deviceContext.PopLayer(); _layerPool.Push(layer); } } @@ -395,21 +412,21 @@ namespace Avalonia.Direct2D1.Media if (solidColorBrush != null) { - return new SolidColorBrushImpl(solidColorBrush, _renderTarget); + return new SolidColorBrushImpl(solidColorBrush, _deviceContext); } else if (linearGradientBrush != null) { - return new LinearGradientBrushImpl(linearGradientBrush, _renderTarget, destinationSize); + return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize); } else if (radialGradientBrush != null) { - return new RadialGradientBrushImpl(radialGradientBrush, _renderTarget, destinationSize); + return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize); } else if (imageBrush?.Source != null) { return new ImageBrushImpl( imageBrush, - _renderTarget, + _deviceContext, (BitmapImpl)imageBrush.Source.PlatformImpl.Item, destinationSize); } @@ -422,7 +439,7 @@ namespace Avalonia.Direct2D1.Media if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1) { using (var intermediate = new BitmapRenderTarget( - _renderTarget, + _deviceContext, CompatibleRenderTargetOptions.None, intermediateSize.ToSharpDX())) { @@ -434,7 +451,7 @@ namespace Avalonia.Direct2D1.Media return new ImageBrushImpl( visualBrush, - _renderTarget, + _deviceContext, new D2DBitmapImpl(intermediate.Bitmap), destinationSize); } @@ -446,7 +463,7 @@ namespace Avalonia.Direct2D1.Media } } - return new SolidColorBrushImpl(null, _renderTarget); + return new SolidColorBrushImpl(null, _deviceContext); } public void PushGeometryClip(IGeometryImpl clip) @@ -458,8 +475,8 @@ namespace Avalonia.Direct2D1.Media Opacity = 1, GeometricMask = ((GeometryImpl)clip).Geometry }; - var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); - _renderTarget.PushLayer(ref parameters, layer); + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); + _deviceContext.PushLayer(ref parameters, layer); _layers.Push(layer); @@ -479,8 +496,8 @@ namespace Avalonia.Direct2D1.Media Opacity = 1, OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush }; - var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); - _renderTarget.PushLayer(ref parameters, layer); + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); + _deviceContext.PushLayer(ref parameters, layer); _layers.Push(layer); } From 0646106f8fe9d6ead0e433024ed909405cd93c6a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Sep 2018 19:53:57 +0200 Subject: [PATCH 03/17] Introduce ResizeBuffers to prevent SwapChain recreation --- .../SwapChainRenderTarget.cs | 105 ++++++++++-------- 1 file changed, 61 insertions(+), 44 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index f820129b8d..94d407ed0b 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -7,11 +7,13 @@ using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; - namespace Avalonia.Direct2D1 { + + using AlphaMode = SharpDX.Direct2D1.AlphaMode; + using DeviceContext = SharpDX.Direct2D1.DeviceContext; + using PixelFormat = SharpDX.Direct2D1.PixelFormat; + public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory { private Size2 _savedSize; @@ -32,7 +34,8 @@ namespace Avalonia.Direct2D1 { _savedSize = size; _savedDpi = dpi; - CreateSwapChain(); + + Resize(); } return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain); @@ -42,7 +45,7 @@ namespace Avalonia.Direct2D1 { if (_deviceContext == null) { - CreateSwapChain(); + CreateDeviceContext(); } return D2DRenderTargetBitmapImpl.CreateCompatible(_deviceContext, size); @@ -51,54 +54,68 @@ namespace Avalonia.Direct2D1 public void Dispose() { _deviceContext?.Dispose(); + _swapChain?.Dispose(); } + private void Resize() + { + _deviceContext?.Dispose(); + _deviceContext = null; + + _swapChain?.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None); + + CreateDeviceContext(); + } + private void CreateSwapChain() { - using (var dxgiAdaptor = Direct2D1Platform.DxgiDevice.Adapter) - using (var dxgiFactory = dxgiAdaptor.GetParent()) + var swapChainDescription = new SwapChainDescription1 { - _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi }; - - var swapChainDesc = new SwapChainDescription1 + Width = _savedSize.Width, + Height = _savedSize.Height, + Format = Format.B8G8R8A8_UNorm, + SampleDescription = new SampleDescription { - Width = _savedSize.Width, - Height = _savedSize.Height, - Format = Format.B8G8R8A8_UNorm, - Stereo = false, - SampleDescription = new SampleDescription + Count = 1, + Quality = 0, + }, + Usage = Usage.RenderTargetOutput, + BufferCount = 1, + SwapEffect = SwapEffect.Discard, + }; + + using (var dxgiAdapter = Direct2D1Platform.DxgiDevice.Adapter) + using (var dxgiFactory = dxgiAdapter.GetParent()) + { + _swapChain = CreateSwapChain(dxgiFactory, swapChainDescription); + } + } + + private void CreateDeviceContext() + { + _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi }; + + if (_swapChain == null) + { + CreateSwapChain(); + } + + using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) + using (var d2dBackBuffer = new Bitmap1( + _deviceContext, + dxgiBackBuffer, + new BitmapProperties1( + new PixelFormat { - Count = 1, - Quality = 0, + AlphaMode = AlphaMode.Premultiplied, + Format = Format.B8G8R8A8_UNorm }, - Usage = Usage.RenderTargetOutput, - BufferCount = 1, - Scaling = Scaling.Stretch, - SwapEffect = SwapEffect.Discard, - Flags = 0, - }; - - _swapChain?.Dispose(); - _swapChain = CreateSwapChain(dxgiFactory, swapChainDesc); - - using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) - using (var d2dBackBuffer = new Bitmap1( - _deviceContext, - dxgiBackBuffer, - new BitmapProperties1( - new PixelFormat - { - AlphaMode = AlphaMode.Premultiplied, - Format = Format.B8G8R8A8_UNorm - }, - _savedDpi.Width, - _savedDpi.Height, - BitmapOptions.Target | BitmapOptions.CannotDraw))) - { - _deviceContext.Target = d2dBackBuffer; - } + _savedSize.Width, + _savedSize.Height, + BitmapOptions.Target | BitmapOptions.CannotDraw))) + { + _deviceContext.Target = d2dBackBuffer; } } From 98ee428d6f4c31e5e7bfa0c2cda49a11a4a86459 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 14:05:56 +0200 Subject: [PATCH 04/17] Merge fix --- .../SwapChainRenderTarget.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 000ce23cd2..ffa0f5c8bc 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -1,19 +1,13 @@ -using Avalonia.Direct2D1.Media; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using Device = SharpDX.Direct2D1.Device; -using Factory = SharpDX.Direct2D1.Factory; -using Factory2 = SharpDX.DXGI.Factory2; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; - -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using DeviceContext = SharpDX.Direct2D1.DeviceContext; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; namespace Avalonia.Direct2D1 { @@ -109,9 +103,9 @@ namespace Avalonia.Direct2D1 _deviceContext, dxgiBackBuffer, new BitmapProperties1( - new PixelFormat + new SharpDX.Direct2D1.PixelFormat { - AlphaMode = AlphaMode.Premultiplied, + AlphaMode = SharpDX.Direct2D1.AlphaMode.Premultiplied, Format = Format.B8G8R8A8_UNorm }, _savedSize.Width, From 024e0da1da398b765422344cbb768f1efef4cfe9 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 15:47:02 +0200 Subject: [PATCH 05/17] Direct3DInterop fixes --- .../Direct3DInteropSample/MainWindow.cs | 276 ++++++++++-------- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 14 +- .../Media/Imaging/D2DBitmapImpl.cs | 29 +- .../Imaging/D2DRenderTargetBitmapImpl.cs | 5 +- .../Media/Imaging/WriteableWicBitmapImpl.cs | 5 +- 5 files changed, 176 insertions(+), 153 deletions(-) diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index 19c31a3af1..065f1a285a 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -1,81 +1,83 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + using Avalonia; using Avalonia.Controls; +using Avalonia.Direct2D1; using Avalonia.Direct2D1.Media; using Avalonia.Markup.Xaml; using Avalonia.Platform; using Avalonia.Rendering; + using SharpDX; using SharpDX.D3DCompiler; using SharpDX.Direct2D1; using SharpDX.Direct3D; using SharpDX.Direct3D11; using SharpDX.DXGI; -using SharpDX.WIC; -using SharpDX.Mathematics; + using AlphaMode = SharpDX.Direct2D1.AlphaMode; using Buffer = SharpDX.Direct3D11.Buffer; -using DeviceContext = SharpDX.Direct3D11.DeviceContext; -using Factory1 = SharpDX.DXGI.Factory1; +using DeviceContext = SharpDX.Direct2D1.DeviceContext; +using Factory2 = SharpDX.DXGI.Factory2; using InputElement = SharpDX.Direct3D11.InputElement; using Matrix = SharpDX.Matrix; using PixelFormat = SharpDX.Direct2D1.PixelFormat; +using Resource = SharpDX.Direct3D11.Resource; namespace Direct3DInteropSample { - class MainWindow : Window + public class MainWindow : Window { - private SharpDX.Direct3D11.Device _d3dDevice; - private SharpDX.DXGI.Device _dxgiDevice; - Texture2D backBuffer = null; - RenderTargetView renderView = null; - Texture2D depthBuffer = null; - DepthStencilView depthView = null; + Texture2D _backBuffer; + RenderTargetView _renderView; + Texture2D _depthBuffer; + DepthStencilView _depthView; private readonly SwapChain _swapChain; - private SwapChainDescription _desc; + private SwapChainDescription1 _desc; private Matrix _proj = Matrix.Identity; - private Matrix _view; + private readonly Matrix _view; private Buffer _contantBuffer; - private SharpDX.Direct2D1.Device _d2dDevice; - private SharpDX.Direct2D1.DeviceContext _d2dContext; - private RenderTarget _d2dRenderTarget; - private MainWindowViewModel _model; + private DeviceContext _deviceContext; + private readonly MainWindowViewModel _model; public MainWindow() { - _dxgiDevice = AvaloniaLocator.Current.GetService(); - _d3dDevice = _dxgiDevice.QueryInterface(); - _d2dDevice = AvaloniaLocator.Current.GetService(); DataContext = _model = new MainWindowViewModel(); - _desc = new SwapChainDescription() + + _desc = new SwapChainDescription1() { BufferCount = 1, - ModeDescription = - new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height, - new Rational(60, 1), Format.R8G8B8A8_UNorm), - IsWindowed = true, - OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero, + Width = (int)ClientSize.Width, + Height = (int)ClientSize.Height, + Format = Format.R8G8B8A8_UNorm, SampleDescription = new SampleDescription(1, 0), SwapEffect = SwapEffect.Discard, Usage = Usage.RenderTargetOutput }; - _swapChain = new SwapChain(new Factory1(), _d3dDevice, _desc); + using (var factory = Direct2D1Platform.DxgiDevice.Adapter.GetParent()) + { + _swapChain = new SwapChain1(factory, Direct2D1Platform.DxgiDevice, PlatformImpl?.Handle.Handle ?? IntPtr.Zero, ref _desc); + } - _d2dContext = new SharpDX.Direct2D1.DeviceContext(_d2dDevice, DeviceContextOptions.None) + _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = new Size2F(96, 96) }; CreateMesh(); + _view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY); + this.GetObservable(ClientSizeProperty).Subscribe(Resize); + Resize(ClientSize); + AvaloniaXamlLoader.Load(this); + Background = Avalonia.Media.Brushes.Transparent; } @@ -83,29 +85,32 @@ namespace Direct3DInteropSample protected override void HandlePaint(Rect rect) { var viewProj = Matrix.Multiply(_view, _proj); - var context = _d3dDevice.ImmediateContext; + var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; + // Clear views - context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0); - context.ClearRenderTargetView(renderView, Color.White); + context.ClearDepthStencilView(_depthView, DepthStencilClearFlags.Depth, 1.0f, 0); + context.ClearRenderTargetView(_renderView, Color.White); // Update WorldViewProj Matrix - var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) * - Matrix.RotationZ((float) _model.RotationZ) - * Matrix.Scaling((float) _model.Zoom) * viewProj; + var worldViewProj = Matrix.RotationX((float)_model.RotationX) * Matrix.RotationY((float)_model.RotationY) + * Matrix.RotationZ((float)_model.RotationZ) + * Matrix.Scaling((float)_model.Zoom) + * viewProj; worldViewProj.Transpose(); context.UpdateSubresource(ref worldViewProj, _contantBuffer); // Draw the cube context.Draw(36, 0); base.HandlePaint(rect); + // Present! _swapChain.Present(0, PresentFlags.None); } - - void CreateMesh() + private void CreateMesh() { - var device = _d3dDevice; + var device = Direct2D1Platform.Direct3D11Device; + // Compile Vertex and Pixel shaders var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0"); var vertexShader = new VertexShader(device, vertexShaderByteCode); @@ -114,63 +119,72 @@ namespace Direct3DInteropSample var pixelShader = new PixelShader(device, pixelShaderByteCode); var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode); + + var inputElements = new[] + { + new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), + new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) + }; + // Layout from VertexShader input signature - var layout = new InputLayout(device, signature, new[] - { - new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), - new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) - }); - + var layout = new InputLayout( + device, + signature, + inputElements); + // Instantiate Vertex buiffer from vertex data - var vertices = Buffer.Create(device, BindFlags.VertexBuffer, new[] - { - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - - new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom - new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - }); + var vertices = Buffer.Create( + device, + BindFlags.VertexBuffer, + new[] + { + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + }); // Create Constant Buffer _contantBuffer = new Buffer(device, Utilities.SizeOf(), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0); - var context = _d3dDevice.ImmediateContext; + var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; // Prepare All the stages context.InputAssembler.InputLayout = layout; @@ -181,63 +195,73 @@ namespace Direct3DInteropSample context.PixelShader.Set(pixelShader); } - void Resize(Size size) + private void Resize(Size size) { - Utilities.Dispose(ref _d2dRenderTarget); - Utilities.Dispose(ref backBuffer); - Utilities.Dispose(ref renderView); - Utilities.Dispose(ref depthBuffer); - Utilities.Dispose(ref depthView); - var context = _d3dDevice.ImmediateContext; + Utilities.Dispose(ref _deviceContext); + Utilities.Dispose(ref _backBuffer); + Utilities.Dispose(ref _renderView); + Utilities.Dispose(ref _depthBuffer); + Utilities.Dispose(ref _depthView); + var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; + // Resize the backbuffer - _swapChain.ResizeBuffers(_desc.BufferCount, (int)size.Width, (int)size.Height, Format.Unknown, SwapChainFlags.None); + _swapChain.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None); // Get the backbuffer from the swapchain - backBuffer = Texture2D.FromSwapChain(_swapChain, 0); + _backBuffer = Resource.FromSwapChain(_swapChain, 0); // Renderview on the backbuffer - renderView = new RenderTargetView(_d3dDevice, backBuffer); + _renderView = new RenderTargetView(Direct2D1Platform.Direct3D11Device, _backBuffer); // Create the depth buffer - depthBuffer = new Texture2D(_d3dDevice, new Texture2DDescription() - { - Format = Format.D32_Float_S8X24_UInt, - ArraySize = 1, - MipLevels = 1, - Width = (int)size.Width, - Height = (int)size.Height, - SampleDescription = new SampleDescription(1, 0), - Usage = ResourceUsage.Default, - BindFlags = BindFlags.DepthStencil, - CpuAccessFlags = CpuAccessFlags.None, - OptionFlags = ResourceOptionFlags.None - }); + _depthBuffer = new Texture2D( + Direct2D1Platform.Direct3D11Device, + new Texture2DDescription() + { + Format = Format.D32_Float_S8X24_UInt, + ArraySize = 1, + MipLevels = 1, + Width = (int)size.Width, + Height = (int)size.Height, + SampleDescription = new SampleDescription(1, 0), + Usage = ResourceUsage.Default, + BindFlags = BindFlags.DepthStencil, + CpuAccessFlags = CpuAccessFlags.None, + OptionFlags = ResourceOptionFlags.None + }); // Create the depth buffer view - depthView = new DepthStencilView(_d3dDevice, depthBuffer); + _depthView = new DepthStencilView(Direct2D1Platform.Direct3D11Device, _depthBuffer); // Setup targets and viewport for rendering context.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f)); - context.OutputMerger.SetTargets(depthView, renderView); + context.OutputMerger.SetTargets(_depthView, _renderView); // Setup new projection matrix with correct aspect ratio _proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f); using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) { - _d2dRenderTarget = new RenderTarget(AvaloniaLocator.Current.GetService() - , dxgiBackBuffer, new RenderTargetProperties + var renderTarget = new SharpDX.Direct2D1.RenderTarget( + Direct2D1Platform.Direct2D1Factory, + dxgiBackBuffer, + new RenderTargetProperties { DpiX = 96, DpiY = 96, Type = RenderTargetType.Default, - PixelFormat = new PixelFormat(Format.Unknown, AlphaMode.Premultiplied) + PixelFormat = new PixelFormat( + Format.Unknown, + AlphaMode.Premultiplied) }); - } + _deviceContext = renderTarget.QueryInterface(); + + renderTarget.Dispose(); + } } - class D3DRenderTarget: IRenderTarget + private class D3DRenderTarget : IRenderTarget { private readonly MainWindow _window; @@ -245,16 +269,14 @@ namespace Direct3DInteropSample { _window = window; } + public void Dispose() { - } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget, - AvaloniaLocator.Current.GetService(), - AvaloniaLocator.Current.GetService()); + return new DrawingContextImpl(visualBrushRenderer, null, _window._deviceContext); } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 437bae6fd1..38c49924bc 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -29,6 +29,8 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); + public static SharpDX.Direct3D11.Device Direct3D11Device { get; private set; } + public static SharpDX.Direct2D1.Factory1 Direct2D1Factory { get; private set; } public static SharpDX.Direct2D1.Device1 Direct2D1Device { get; private set; } @@ -88,14 +90,12 @@ namespace Avalonia.Direct2D1 SharpDX.Direct3D.FeatureLevel.Level_9_1, }; - using (var d3dDevice = new SharpDX.Direct3D11.Device( + Direct3D11Device = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | - SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, - featureLevels)) - { - DxgiDevice = d3dDevice.QueryInterface(); - } + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, + featureLevels); + + DxgiDevice = Direct3D11Device.QueryInterface(); using (var device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice)) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 5711a79c2e..b65e32bbbb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,19 +1,20 @@ using System; using System.IO; using SharpDX.Direct2D1; -using ImageParameters = SharpDX.WIC.ImageParameters; -using ImagingFactory2 = SharpDX.WIC.ImagingFactory2; -using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder; -using WICFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1.Media { + using SharpDX.WIC; + + using Bitmap = SharpDX.Direct2D1.Bitmap; + using PixelFormat = SharpDX.Direct2D1.PixelFormat; + /// /// A Direct2D Bitmap implementation that uses a GPU memory bitmap as its image. /// public class D2DBitmapImpl : BitmapImpl { - private Bitmap _direct2DBitmap; + private readonly Bitmap _direct2DBitmap; /// /// Initialize a new instance of the class @@ -29,7 +30,7 @@ namespace Avalonia.Direct2D1.Media { _direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); } - + public override int PixelWidth => _direct2DBitmap.PixelSize.Width; public override int PixelHeight => _direct2DBitmap.PixelSize.Height; @@ -47,18 +48,12 @@ namespace Avalonia.Direct2D1.Media public override void Save(Stream stream) { using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) - using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder)) - //ToDo: Not supported under Windows 7! - using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)Direct2D1Platform.ImagingFactory, null)) + using (var frame = new BitmapFrameEncode(encoder)) + using (var bitmapSource = _direct2DBitmap.QueryInterface()) { - var parameters = new ImageParameters( - new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied), - _direct2DBitmap.DotsPerInch.Width, - _direct2DBitmap.DotsPerInch.Height, - 0, 0, PixelWidth, PixelHeight); - - imageEncoder.WriteFrame(_direct2DBitmap, frameEncode, parameters); - frameEncode.Commit(); + frame.Initialize(); + frame.WriteSource(bitmapSource); + frame.Commit(); encoder.Commit(); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 7ea303345a..3646d9e9e1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -1,4 +1,7 @@ -using Avalonia.Platform; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index 075fef5ab2..5ca78ef278 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using Avalonia.Platform; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; From 2a64f133d56ac6d1272bca6cb452e964714b44af Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 7 Sep 2018 13:45:05 +0200 Subject: [PATCH 06/17] SharpDX update to current version --- build/SharpDX.props | 10 ++--- .../Direct3DInteropSample/MainWindow.cs | 2 +- .../Media/AvaloniaTextRenderer.cs | 40 ++----------------- .../Media/FormattedTextImpl.cs | 15 ++++++- 4 files changed, 24 insertions(+), 43 deletions(-) diff --git a/build/SharpDX.props b/build/SharpDX.props index 69aa817a01..fdc802f135 100644 --- a/build/SharpDX.props +++ b/build/SharpDX.props @@ -1,9 +1,9 @@  - - - - - + + + + + diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index 065f1a285a..1ac4b44a74 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -132,7 +132,7 @@ namespace Direct3DInteropSample signature, inputElements); - // Instantiate Vertex buiffer from vertex data + // Instantiate Vertex buffer from vertex data var vertices = Buffer.Create( device, BindFlags.VertexBuffer, diff --git a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs index 51b4eef62d..6e6cf47254 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs @@ -1,7 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; using SharpDX; using SharpDX.Direct2D1; using SharpDX.DirectWrite; @@ -9,7 +8,7 @@ using SharpDX.Mathematics.Interop; namespace Avalonia.Direct2D1.Media { - internal class AvaloniaTextRenderer : TextRenderer + internal class AvaloniaTextRenderer : TextRendererBase { private readonly DrawingContextImpl _context; @@ -27,18 +26,7 @@ namespace Avalonia.Direct2D1.Media _foreground = foreground; } - public IDisposable Shadow - { - get; - set; - } - - public void Dispose() - { - Shadow?.Dispose(); - } - - public Result DrawGlyphRun( + public override Result DrawGlyphRun( object clientDrawingContext, float baselineOriginX, float baselineOriginY, @@ -68,34 +56,14 @@ namespace Avalonia.Direct2D1.Media return Result.Ok; } - public Result DrawInlineObject(object clientDrawingContext, float originX, float originY, InlineObject inlineObject, bool isSideways, bool isRightToLeft, ComObject clientDrawingEffect) - { - throw new NotImplementedException(); - } - - public Result DrawStrikethrough(object clientDrawingContext, float baselineOriginX, float baselineOriginY, ref Strikethrough strikethrough, ComObject clientDrawingEffect) - { - throw new NotImplementedException(); - } - - public Result DrawUnderline(object clientDrawingContext, float baselineOriginX, float baselineOriginY, ref Underline underline, ComObject clientDrawingEffect) - { - throw new NotImplementedException(); - } - - public RawMatrix3x2 GetCurrentTransform(object clientDrawingContext) + public override RawMatrix3x2 GetCurrentTransform(object clientDrawingContext) { return _renderTarget.Transform; } - public float GetPixelsPerDip(object clientDrawingContext) + public override float GetPixelsPerDip(object clientDrawingContext) { return _renderTarget.DotsPerInch.Width / 96; } - - public bool IsPixelSnappingDisabled(object clientDrawingContext) - { - return false; - } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index 09b249e19f..e2c33a51f1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -9,6 +9,8 @@ using DWrite = SharpDX.DirectWrite; namespace Avalonia.Direct2D1.Media { + using System; + public class FormattedTextImpl : IFormattedTextImpl { public FormattedTextImpl( @@ -107,7 +109,18 @@ namespace Avalonia.Direct2D1.Media private Size Measure() { - var metrics = TextLayout.Metrics; + DWrite.TextMetrics metrics; + + // SharpDX bug + try + { + metrics = TextLayout.Metrics; + } + catch (ObjectDisposedException) + { + metrics = TextLayout.Metrics; + } + var width = metrics.WidthIncludingTrailingWhitespace; if (float.IsNaN(width)) From d531b36b9631e069424f98f22057f00584311b9c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 8 Sep 2018 19:55:36 +0200 Subject: [PATCH 07/17] Make brushes raise a Changed event. When their visual representation changes. --- src/Avalonia.Visuals/Media/Brush.cs | 36 +++++++- src/Avalonia.Visuals/Media/GradientBrush.cs | 65 +++++++++++--- src/Avalonia.Visuals/Media/GradientStop.cs | 40 ++++++--- src/Avalonia.Visuals/Media/GradientStops.cs | 23 +++++ src/Avalonia.Visuals/Media/IGradientBrush.cs | 4 +- src/Avalonia.Visuals/Media/IGradientStop.cs | 18 ++++ src/Avalonia.Visuals/Media/IMutableBrush.cs | 9 +- src/Avalonia.Visuals/Media/ImageBrush.cs | 12 ++- .../Media/Immutable/ImmutableGradientBrush.cs | 9 +- .../Media/Immutable/ImmutableGradientStop.cs | 20 +++++ .../Immutable/ImmutableLinearGradientBrush.cs | 4 +- .../Immutable/ImmutableRadialGradientBrush.cs | 4 +- .../Media/LinearGradientBrush.cs | 13 ++- .../Media/RadialGradientBrush.cs | 10 ++- src/Avalonia.Visuals/Media/SolidColorBrush.cs | 13 ++- src/Avalonia.Visuals/Media/TileBrush.cs | 7 ++ src/Avalonia.Visuals/Media/VisualBrush.cs | 12 ++- .../Controls/CustomRenderTests.cs | 2 +- .../Media/LinearGradientBrushTests.cs | 4 +- .../Media/RadialGradientBrushTests.cs | 2 +- .../Avalonia.RenderTests/OpacityMaskTests.cs | 4 +- .../Media/ImageBrushTests.cs | 27 ++++++ .../Media/LinearGradientBrushTests.cs | 86 +++++++++++++++++++ .../Media/SolidColorBrushTests.cs | 21 +++++ 24 files changed, 386 insertions(+), 59 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/GradientStops.cs create mode 100644 src/Avalonia.Visuals/Media/IGradientStop.cs create mode 100644 src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index eef6e1a43c..8ba7c1be04 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media /// Describes how an area is painted. /// [TypeConverter(typeof(BrushConverter))] - public abstract class Brush : AvaloniaObject, IBrush + public abstract class Brush : AvaloniaObject, IMutableBrush { /// /// Defines the property. @@ -18,6 +18,9 @@ namespace Avalonia.Media public static readonly StyledProperty OpacityProperty = AvaloniaProperty.Register(nameof(Opacity), 1.0); + /// + public event EventHandler Changed; + /// /// Gets or sets the opacity of the brush. /// @@ -50,5 +53,36 @@ namespace Avalonia.Media throw new FormatException($"Invalid brush string: '{s}'."); } + + /// + public abstract IBrush ToImmutable(); + + /// + /// Marks a property as affecting the brush's visual representation. + /// + /// The properties. + /// + /// After a call to this method in a brush's static constructor, any change to the + /// property will cause the event to be raised on the brush. + /// + protected static void AffectsRender(params AvaloniaProperty[] properties) + where T : Brush + { + void Invalidate(AvaloniaPropertyChangedEventArgs e) + { + (e.Sender as T)?.RaiseChanged(EventArgs.Empty); + } + + foreach (var property in properties) + { + property.Changed.Subscribe(Invalidate); + } + } + + /// + /// Raises the event. + /// + /// The event args. + protected void RaiseChanged(EventArgs e) => Changed?.Invoke(this, e); } } diff --git a/src/Avalonia.Visuals/Media/GradientBrush.cs b/src/Avalonia.Visuals/Media/GradientBrush.cs index 41c3afc8c3..c123813cee 100644 --- a/src/Avalonia.Visuals/Media/GradientBrush.cs +++ b/src/Avalonia.Visuals/Media/GradientBrush.cs @@ -1,7 +1,11 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using Avalonia.Collections; using Avalonia.Metadata; namespace Avalonia.Media @@ -20,35 +24,74 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty> GradientStopsProperty = - AvaloniaProperty.Register>(nameof(GradientStops)); + public static readonly StyledProperty GradientStopsProperty = + AvaloniaProperty.Register(nameof(GradientStops)); + + private IDisposable _gradientStopsSubscription; + + static GradientBrush() + { + GradientStopsProperty.Changed.Subscribe(GradientStopsChanged); + AffectsRender(SpreadMethodProperty); + } /// /// Initializes a new instance of the class. /// public GradientBrush() { - this.GradientStops = new List(); + this.GradientStops = new GradientStops(); } - /// - /// Gets or sets the brush's spread method that defines how to draw a gradient that - /// doesn't fill the bounds of the destination control. - /// + /// public GradientSpreadMethod SpreadMethod { get { return GetValue(SpreadMethodProperty); } set { SetValue(SpreadMethodProperty, value); } } - /// - /// Gets or sets the brush's gradient stops. - /// + /// [Content] - public IList GradientStops + public GradientStops GradientStops { get { return GetValue(GradientStopsProperty); } set { SetValue(GradientStopsProperty, value); } } + + /// + IReadOnlyList IGradientBrush.GradientStops => GradientStops; + + private static void GradientStopsChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.Sender is GradientBrush brush) + { + var oldValue = (GradientStops)e.OldValue; + var newValue = (GradientStops)e.NewValue; + + if (oldValue != null) + { + oldValue.CollectionChanged -= brush.GradientStopsChanged; + brush._gradientStopsSubscription.Dispose(); + } + + if (newValue != null) + { + newValue.CollectionChanged += brush.GradientStopsChanged; + brush._gradientStopsSubscription = newValue.TrackItemPropertyChanged(brush.GradientStopChanged); + } + + brush.RaiseChanged(EventArgs.Empty); + } + } + + private void GradientStopsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + RaiseChanged(EventArgs.Empty); + } + + private void GradientStopChanged(Tuple e) + { + RaiseChanged(EventArgs.Empty); + } } } diff --git a/src/Avalonia.Visuals/Media/GradientStop.cs b/src/Avalonia.Visuals/Media/GradientStop.cs index 78dd32a18a..00d96a0b3c 100644 --- a/src/Avalonia.Visuals/Media/GradientStop.cs +++ b/src/Avalonia.Visuals/Media/GradientStop.cs @@ -4,10 +4,22 @@ namespace Avalonia.Media { /// - /// GradientStop + /// Describes the location and color of a transition point in a gradient. /// - public sealed class GradientStop + public sealed class GradientStop : AvaloniaObject, IGradientStop { + /// + /// Describes the property. + /// + public static StyledProperty OffsetProperty = + AvaloniaProperty.Register(nameof(Offset)); + + /// + /// Describes the property. + /// + public static StyledProperty ColorProperty = + AvaloniaProperty.Register(nameof(Color)); + /// /// Initializes a new instance of the class. /// @@ -24,16 +36,18 @@ namespace Avalonia.Media Offset = offset; } - // TODO: Make these dependency properties. - - /// - /// The offset - /// - public double Offset { get; set; } + /// + public double Offset + { + get => GetValue(OffsetProperty); + set => SetValue(OffsetProperty, value); + } - /// - /// The color - /// - public Color Color { get; set; } + /// + public Color Color + { + get => GetValue(ColorProperty); + set => SetValue(ColorProperty, value); + } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/GradientStops.cs b/src/Avalonia.Visuals/Media/GradientStops.cs new file mode 100644 index 0000000000..efc11bacd6 --- /dev/null +++ b/src/Avalonia.Visuals/Media/GradientStops.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Linq; +using Avalonia.Collections; +using Avalonia.Media.Immutable; + +namespace Avalonia.Media +{ + /// + /// A collection of s. + /// + public class GradientStops : AvaloniaList + { + public GradientStops() + { + ResetBehavior = ResetBehavior.Remove; + } + + public IReadOnlyList ToImmutable() + { + return this.Select(x => new ImmutableGradientStop(x.Offset, x.Color)).ToList(); + } + } +} diff --git a/src/Avalonia.Visuals/Media/IGradientBrush.cs b/src/Avalonia.Visuals/Media/IGradientBrush.cs index 390ce6ee5b..18db0af660 100644 --- a/src/Avalonia.Visuals/Media/IGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/IGradientBrush.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media /// /// Gets the brush's gradient stops. /// - IList GradientStops { get; } + IReadOnlyList GradientStops { get; } /// /// Gets the brush's spread method that defines how to draw a gradient that doesn't fill @@ -18,4 +18,4 @@ namespace Avalonia.Media /// GradientSpreadMethod SpreadMethod { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/IGradientStop.cs b/src/Avalonia.Visuals/Media/IGradientStop.cs new file mode 100644 index 0000000000..22eb9df60d --- /dev/null +++ b/src/Avalonia.Visuals/Media/IGradientStop.cs @@ -0,0 +1,18 @@ +namespace Avalonia.Media +{ + /// + /// Describes the location and color of a transition point in a gradient. + /// + public interface IGradientStop + { + /// + /// Gets the gradient stop color. + /// + Color Color { get; } + + /// + /// Gets the gradient stop offset. + /// + double Offset { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/IMutableBrush.cs b/src/Avalonia.Visuals/Media/IMutableBrush.cs index 39dd8b80c4..762731a6a8 100644 --- a/src/Avalonia.Visuals/Media/IMutableBrush.cs +++ b/src/Avalonia.Visuals/Media/IMutableBrush.cs @@ -1,10 +1,17 @@ -namespace Avalonia.Media +using System; + +namespace Avalonia.Media { /// /// Represents a mutable brush which can return an immutable clone of itself. /// public interface IMutableBrush : IBrush { + /// + /// Raised when the brush changes visually. + /// + event EventHandler Changed; + /// /// Creates an immutable clone of the brush. /// diff --git a/src/Avalonia.Visuals/Media/ImageBrush.cs b/src/Avalonia.Visuals/Media/ImageBrush.cs index fa491ed3e1..8b42a51d9f 100644 --- a/src/Avalonia.Visuals/Media/ImageBrush.cs +++ b/src/Avalonia.Visuals/Media/ImageBrush.cs @@ -2,13 +2,14 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Media.Imaging; +using Avalonia.Media.Immutable; namespace Avalonia.Media { /// /// Paints an area with an . /// - public class ImageBrush : TileBrush, IImageBrush, IMutableBrush + public class ImageBrush : TileBrush, IImageBrush { /// /// Defines the property. @@ -16,6 +17,11 @@ namespace Avalonia.Media public static readonly StyledProperty SourceProperty = AvaloniaProperty.Register(nameof(Source)); + static ImageBrush() + { + AffectsRender(SourceProperty); + } + /// /// Initializes a new instance of the class. /// @@ -42,9 +48,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableImageBrush(this); + return new ImmutableImageBrush(this); } } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs index 6664a2b30e..1f6e3bbcfd 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; namespace Avalonia.Media.Immutable { @@ -15,7 +14,7 @@ namespace Avalonia.Media.Immutable /// The opacity of the brush. /// The spread method. protected ImmutableGradientBrush( - IList gradientStops, + IReadOnlyList gradientStops, double opacity, GradientSpreadMethod spreadMethod) { @@ -28,14 +27,14 @@ namespace Avalonia.Media.Immutable /// Initializes a new instance of the class. /// /// The brush from which this brush's properties should be copied. - protected ImmutableGradientBrush(IGradientBrush source) - : this(source.GradientStops.ToList(), source.Opacity, source.SpreadMethod) + protected ImmutableGradientBrush(GradientBrush source) + : this(source.GradientStops.ToImmutable(), source.Opacity, source.SpreadMethod) { } /// - public IList GradientStops { get; } + public IReadOnlyList GradientStops { get; } /// public double Opacity { get; } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs new file mode 100644 index 0000000000..f3e2e52fd0 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs @@ -0,0 +1,20 @@ +namespace Avalonia.Media.Immutable +{ + /// + /// Describes the location and color of a transition point in a gradient. + /// + public class ImmutableGradientStop : IGradientStop + { + public ImmutableGradientStop(double offset, Color color) + { + Offset = offset; + Color = color; + } + + /// + public double Offset { get; } + + /// + public Color Color { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs index 142eb34625..912d77d763 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs @@ -16,7 +16,7 @@ namespace Avalonia.Media.Immutable /// The start point for the gradient. /// The end point for the gradient. public ImmutableLinearGradientBrush( - IList gradientStops, + IReadOnlyList gradientStops, double opacity = 1, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? startPoint = null, @@ -31,7 +31,7 @@ namespace Avalonia.Media.Immutable /// Initializes a new instance of the class. /// /// The brush from which this brush's properties should be copied. - public ImmutableLinearGradientBrush(ILinearGradientBrush source) + public ImmutableLinearGradientBrush(LinearGradientBrush source) : base(source) { StartPoint = source.StartPoint; diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs index f36a1cd2de..e26fbab5f5 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs @@ -21,7 +21,7 @@ namespace Avalonia.Media.Immutable /// The horizontal and vertical radius of the outermost circle of the radial gradient. /// public ImmutableRadialGradientBrush( - IList gradientStops, + IReadOnlyList gradientStops, double opacity = 1, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? center = null, @@ -38,7 +38,7 @@ namespace Avalonia.Media.Immutable /// Initializes a new instance of the class. /// /// The brush from which this brush's properties should be copied. - public ImmutableRadialGradientBrush(IRadialGradientBrush source) + public ImmutableRadialGradientBrush(RadialGradientBrush source) : base(source) { Center = source.Center; diff --git a/src/Avalonia.Visuals/Media/LinearGradientBrush.cs b/src/Avalonia.Visuals/Media/LinearGradientBrush.cs index d092bebf0f..14adc0e0cd 100644 --- a/src/Avalonia.Visuals/Media/LinearGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/LinearGradientBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media.Immutable; + namespace Avalonia.Media { /// /// A brush that draws with a linear gradient. /// - public sealed class LinearGradientBrush : GradientBrush, ILinearGradientBrush, IMutableBrush + public sealed class LinearGradientBrush : GradientBrush, ILinearGradientBrush { /// /// Defines the property. @@ -24,6 +26,11 @@ namespace Avalonia.Media nameof(EndPoint), RelativePoint.BottomRight); + static LinearGradientBrush() + { + AffectsRender(StartPointProperty, EndPointProperty); + } + /// /// Gets or sets the start point for the gradient. /// @@ -43,9 +50,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableLinearGradientBrush(this); + return new ImmutableLinearGradientBrush(this); } } } diff --git a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs index 003e2e05f9..589cd83ca1 100644 --- a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media.Immutable; + namespace Avalonia.Media { /// /// Paints an area with a radial gradient. /// - public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush, IMutableBrush + public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush { /// /// Defines the property. @@ -63,9 +65,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableRadialGradientBrush(this); + return new ImmutableRadialGradientBrush(this); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/SolidColorBrush.cs b/src/Avalonia.Visuals/Media/SolidColorBrush.cs index d84e407cb4..32b87df56b 100644 --- a/src/Avalonia.Visuals/Media/SolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/SolidColorBrush.cs @@ -1,12 +1,14 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media.Immutable; + namespace Avalonia.Media { /// /// Fills an area with a solid color. /// - public class SolidColorBrush : Brush, ISolidColorBrush, IMutableBrush + public class SolidColorBrush : Brush, ISolidColorBrush { /// /// Defines the property. @@ -14,6 +16,11 @@ namespace Avalonia.Media public static readonly StyledProperty ColorProperty = AvaloniaProperty.Register(nameof(Color)); + static SolidColorBrush() + { + AffectsRender(ColorProperty); + } + /// /// Initializes a new instance of the class. /// @@ -75,9 +82,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableSolidColorBrush(this); + return new ImmutableSolidColorBrush(this); } } } diff --git a/src/Avalonia.Visuals/Media/TileBrush.cs b/src/Avalonia.Visuals/Media/TileBrush.cs index 2033754137..47f20fa285 100644 --- a/src/Avalonia.Visuals/Media/TileBrush.cs +++ b/src/Avalonia.Visuals/Media/TileBrush.cs @@ -79,6 +79,13 @@ namespace Avalonia.Media static TileBrush() { + AffectsRender( + AlignmentXProperty, + AlignmentYProperty, + DestinationRectProperty, + SourceRectProperty, + StretchProperty, + TileModeProperty); RenderOptions.BitmapInterpolationModeProperty.OverrideDefaultValue(BitmapInterpolationMode.Default); } diff --git a/src/Avalonia.Visuals/Media/VisualBrush.cs b/src/Avalonia.Visuals/Media/VisualBrush.cs index 435f4ba1b1..963ba8f4a1 100644 --- a/src/Avalonia.Visuals/Media/VisualBrush.cs +++ b/src/Avalonia.Visuals/Media/VisualBrush.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media.Immutable; using Avalonia.VisualTree; namespace Avalonia.Media @@ -8,7 +9,7 @@ namespace Avalonia.Media /// /// Paints an area with an . /// - public class VisualBrush : TileBrush, IVisualBrush, IMutableBrush + public class VisualBrush : TileBrush, IVisualBrush { /// /// Defines the property. @@ -16,6 +17,11 @@ namespace Avalonia.Media public static readonly StyledProperty VisualProperty = AvaloniaProperty.Register(nameof(Visual)); + static VisualBrush() + { + AffectsRender(VisualProperty); + } + /// /// Initializes a new instance of the class. /// @@ -42,9 +48,9 @@ namespace Avalonia.Media } /// - IBrush IMutableBrush.ToImmutable() + public override IBrush ToImmutable() { - return new Immutable.ImmutableVisualBrush(this); + return new ImmutableVisualBrush(this); } } } diff --git a/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs b/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs index 8356e78cc3..6a01536b12 100644 --- a/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs +++ b/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs @@ -124,7 +124,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls { StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), - GradientStops = new[] + GradientStops = { new GradientStop(Color.FromUInt32(0xffffffff), 0), new GradientStop(Color.FromUInt32(0x00ffffff), 1) diff --git a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs index 656e77fc31..a0d6f1e423 100644 --- a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media { StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative), EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative), - GradientStops = new[] + GradientStops = { new GradientStop { Color = Colors.Red, Offset = 0 }, new GradientStop { Color = Colors.Blue, Offset = 1 } @@ -63,7 +63,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media { StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative), - GradientStops = new[] + GradientStops = { new GradientStop { Color = Colors.Red, Offset = 0 }, new GradientStop { Color = Colors.Blue, Offset = 1 } diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs index 0017feb106..bd1d26ce70 100644 --- a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs @@ -34,7 +34,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media { Background = new RadialGradientBrush { - GradientStops = new[] + GradientStops = { new GradientStop { Color = Colors.Red, Offset = 0 }, new GradientStop { Color = Colors.Blue, Offset = 1 } diff --git a/tests/Avalonia.RenderTests/OpacityMaskTests.cs b/tests/Avalonia.RenderTests/OpacityMaskTests.cs index 4edf4daa13..2f01b03db6 100644 --- a/tests/Avalonia.RenderTests/OpacityMaskTests.cs +++ b/tests/Avalonia.RenderTests/OpacityMaskTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Direct2D1.RenderTests { StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), - GradientStops = new List + GradientStops = { new GradientStop(Color.FromUInt32(0xffffffff), 0), new GradientStop(Color.FromUInt32(0x00ffffff), 1) @@ -65,7 +65,7 @@ namespace Avalonia.Direct2D1.RenderTests { StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), - GradientStops = new List + GradientStops = { new GradientStop(Color.FromUInt32(0xffffffff), 0), new GradientStop(Color.FromUInt32(0x00ffffff), 1) diff --git a/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs new file mode 100644 index 0000000000..f843a6e333 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Moq; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class ImageBrushTests + { + [Fact] + public void Changing_Source_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new ImageBrush(bitmap1); + var raised = false; + + target.Changed += (s, e) => raised = true; + target.Source = bitmap2; + + Assert.True(raised); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs new file mode 100644 index 0000000000..62f53108e6 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs @@ -0,0 +1,86 @@ +using System; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Moq; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class LinearGradientBrushTests + { + [Fact] + public void Changing_StartPoint_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.StartPoint = new RelativePoint(); + target.Changed += (s, e) => raised = true; + target.StartPoint = new RelativePoint(10, 10, RelativeUnit.Absolute); + + Assert.True(raised); + } + + [Fact] + public void Changing_EndPoint_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.EndPoint = new RelativePoint(); + target.Changed += (s, e) => raised = true; + target.EndPoint = new RelativePoint(10, 10, RelativeUnit.Absolute); + + Assert.True(raised); + } + + [Fact] + public void Changing_GradientStops_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; + target.Changed += (s, e) => raised = true; + target.GradientStops = new GradientStops { new GradientStop(Colors.Green, 0) }; + + Assert.True(raised); + } + + [Fact] + public void Adding_GradientStop_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; + target.Changed += (s, e) => raised = true; + target.GradientStops.Add(new GradientStop(Colors.Green, 1)); + + Assert.True(raised); + } + + [Fact] + public void Changing_GradientStop_Offset_Raises_Changed() + { + var bitmap1 = Mock.Of(); + var bitmap2 = Mock.Of(); + var target = new LinearGradientBrush(); + var raised = false; + + target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; + target.Changed += (s, e) => raised = true; + target.GradientStops[0].Offset = 0.5; + + Assert.True(raised); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs new file mode 100644 index 0000000000..4e87b7081d --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs @@ -0,0 +1,21 @@ +using System; +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class SolidColorBrushTests + { + [Fact] + public void Changing_Color_Raises_Changed() + { + var target = new SolidColorBrush(Colors.Red); + var raised = false; + + target.Changed += (s, e) => raised = true; + target.Color = Colors.Green; + + Assert.True(raised); + } + } +} From afdfb28da8b507370771438133c9e0a4916ea010 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 9 Sep 2018 02:02:50 +0200 Subject: [PATCH 08/17] Added failing tests for brush invalidation. --- .../BorderTests.cs | 21 ++++++++ .../Avalonia.Controls.UnitTests/PanelTests.cs | 21 ++++++++ .../ContentPresenterTests_Standalone.cs | 20 +++++++- .../Shapes/RectangleTests.cs | 48 +++++++++++++++++++ .../TextBlockTests.cs | 38 +++++++++++++++ tests/Avalonia.UnitTests/TestRoot.cs | 7 +++ 6 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs diff --git a/tests/Avalonia.Controls.UnitTests/BorderTests.cs b/tests/Avalonia.Controls.UnitTests/BorderTests.cs index 9a6a041ec7..0ac9392bc6 100644 --- a/tests/Avalonia.Controls.UnitTests/BorderTests.cs +++ b/tests/Avalonia.Controls.UnitTests/BorderTests.cs @@ -1,6 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Media; +using Avalonia.Rendering; +using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -42,5 +46,22 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(6, 6, 0, 0), content.Bounds); } + + [Fact] + public void Changing_Background_Brush_Color_Should_Invalidate_Visual() + { + var target = new Border() + { + Background = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Background).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs index ed239120d6..4a404ea97e 100644 --- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs @@ -3,7 +3,11 @@ using System.Linq; using Avalonia.LogicalTree; +using Avalonia.Media; +using Avalonia.Rendering; +using Avalonia.UnitTests; using Avalonia.VisualTree; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -115,5 +119,22 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren()); Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren()); } + + [Fact] + public void Changing_Background_Brush_Color_Should_Invalidate_Visual() + { + var target = new Panel() + { + Background = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Background).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index 6716456c78..9d65f2cba7 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs @@ -13,6 +13,7 @@ using System; using System.Linq; using Xunit; using Avalonia.Rendering; +using Avalonia.Media; namespace Avalonia.Controls.UnitTests.Presenters { @@ -203,5 +204,22 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.NotEqual(foo, logicalChildren.First()); } + + [Fact] + public void Changing_Background_Brush_Color_Should_Invalidate_Visual() + { + var target = new ContentPresenter() + { + Background = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Background).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs new file mode 100644 index 0000000000..0ec73edec0 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.UnitTests; +using Moq; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Shapes +{ + public class RectangleTests + { + [Fact] + public void Changing_Fill_Brush_Color_Should_Invalidate_Visual() + { + var target = new Rectangle() + { + Fill = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Fill).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } + + [Fact] + public void Changing_Stroke_Brush_Color_Should_Invalidate_Visual() + { + var target = new Rectangle() + { + Stroke = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Stroke).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index 9a1140fc05..45e683455b 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs @@ -2,6 +2,10 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Data; +using Avalonia.Media; +using Avalonia.Rendering; +using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -25,5 +29,39 @@ namespace Avalonia.Controls.UnitTests "", textBlock.Text); } + + [Fact] + public void Changing_Background_Brush_Color_Should_Invalidate_Visual() + { + var target = new TextBlock() + { + Background = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Background).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } + + [Fact] + public void Changing_Foreground_Brush_Color_Should_Invalidate_Visual() + { + var target = new TextBlock() + { + Foreground = new SolidColorBrush(Colors.Red), + }; + + var root = new TestRoot(target); + var renderer = Mock.Get(root.Renderer); + renderer.ResetCalls(); + + ((SolidColorBrush)target.Foreground).Color = Colors.Green; + + renderer.Verify(x => x.AddDirty(target), Times.Once); + } } } diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 884df33fc0..972b1d78c0 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -19,6 +19,13 @@ namespace Avalonia.UnitTests public TestRoot() { + Renderer = Mock.Of(); + } + + public TestRoot(IControl child) + : this() + { + Child = child; } event EventHandler INameScope.Registered From a5a5b36ddc2f8a1334bc152438600f0eb93a7912 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 9 Sep 2018 02:06:30 +0200 Subject: [PATCH 09/17] Invalidate controls when brush changed. --- src/Avalonia.Controls/Border.cs | 3 +- src/Avalonia.Controls/Panel.cs | 1 + .../Presenters/ContentPresenter.cs | 3 +- src/Avalonia.Controls/Shapes/Shape.cs | 4 +- src/Avalonia.Controls/TextBlock.cs | 2 +- src/Avalonia.Visuals/Visual.cs | 40 +++++++++++++++++++ 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index 5f84421c64..c4bc121a27 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -43,8 +43,9 @@ namespace Avalonia.Controls /// static Border() { - AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); + AffectsRender(BorderThicknessProperty, CornerRadiusProperty); AffectsMeasure(BorderThicknessProperty); + BrushAffectsRender(BackgroundProperty, BorderBrushProperty); } /// diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index c0d211effb..9b768749df 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -30,6 +30,7 @@ namespace Avalonia.Controls /// static Panel() { + BrushAffectsRender(BackgroundProperty); ClipToBoundsProperty.OverrideDefaultValue(true); } diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 6badf91367..6c1d6e2cc1 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -90,8 +90,9 @@ namespace Avalonia.Controls.Presenters /// static ContentPresenter() { - AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); + AffectsRender(BorderThicknessProperty, CornerRadiusProperty); AffectsMeasure(BorderThicknessProperty, PaddingProperty); + BrushAffectsRender(BackgroundProperty, BorderBrushProperty); ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); ContentTemplateProperty.Changed.AddClassHandler(x => x.ContentChanged); TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 604051ef28..d2a4a37531 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -30,11 +30,11 @@ namespace Avalonia.Controls.Shapes private Geometry _renderedGeometry; bool _calculateTransformOnArrange = false; - static Shape() { AffectsMeasure(StretchProperty, StrokeThicknessProperty); - AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty); + AffectsRender(StrokeDashArrayProperty); + BrushAffectsRender(FillProperty, StrokeProperty); } public Geometry DefiningGeometry diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index e91d2e8fa7..8689d11e13 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -99,10 +99,10 @@ namespace Avalonia.Controls static TextBlock() { ClipToBoundsProperty.OverrideDefaultValue(true); - AffectsRender(ForegroundProperty); AffectsRender(FontWeightProperty); AffectsRender(FontSizeProperty); AffectsRender(FontStyleProperty); + BrushAffectsRender(BackgroundProperty, ForegroundProperty); } /// diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 81e1a93a6f..8631d2001d 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -328,6 +328,44 @@ namespace Avalonia } } + /// + /// Indicates that a brush property change should cause to be + /// called. + /// + /// The properties. + /// + /// This method should be called in a control's static constructor with each property + /// on the control which when changed should cause a redraw. It not only triggers an + /// invalidation when the property itself changes, but also when the brush raises + /// the event. + /// + protected static void BrushAffectsRender(params AvaloniaProperty[] properties) + where T : Visual + { + void Invalidate(AvaloniaPropertyChangedEventArgs e) + { + if (e.Sender is T sender) + { + if (e.OldValue is IMutableBrush oldValue) + { + oldValue.Changed -= sender.BrushChanged; + } + + if (e.NewValue is IMutableBrush newValue) + { + newValue.Changed += sender.BrushChanged; + } + + sender.InvalidateVisual(); + } + } + + foreach (var property in properties) + { + property.Changed.Subscribe(Invalidate); + } + } + /// /// Calls the method /// for this control and all of its visual descendants. @@ -530,6 +568,8 @@ namespace Avalonia OnVisualParentChanged(old, value); } + private void BrushChanged(object sender, EventArgs e) => InvalidateVisual(); + /// /// Called when the collection changes. /// From 460d63736da2bd98f5b216b4a1860aa0354ff53f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 9 Sep 2018 03:17:28 +0200 Subject: [PATCH 10/17] Use immutable brush as default Foreground value. Prevents memory leak. --- src/Avalonia.Controls/TextBlock.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 8689d11e13..ee3e3e361a 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -6,6 +6,7 @@ using System.Reactive; using System.Reactive.Linq; using Avalonia.LogicalTree; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Metadata; namespace Avalonia.Controls @@ -65,7 +66,7 @@ namespace Avalonia.Controls public static readonly AttachedProperty ForegroundProperty = AvaloniaProperty.RegisterAttached( nameof(Foreground), - new SolidColorBrush(0xff000000), + Brushes.Black, inherits: true); /// From 1bf495433469fdfdfdb9a81582fd8518f5996b27 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 10 Sep 2018 10:50:36 +0200 Subject: [PATCH 11/17] Downgrade SharpDX to 4.0.1 --- build/SharpDX.props | 10 ++--- .../Media/FormattedTextImpl.cs | 43 +++++++------------ 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/build/SharpDX.props b/build/SharpDX.props index fdc802f135..69aa817a01 100644 --- a/build/SharpDX.props +++ b/build/SharpDX.props @@ -1,9 +1,9 @@  - - - - - + + + + + diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index e2c33a51f1..7164ec7c0d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -9,8 +9,6 @@ using DWrite = SharpDX.DirectWrite; namespace Avalonia.Direct2D1.Media { - using System; - public class FormattedTextImpl : IFormattedTextImpl { public FormattedTextImpl( @@ -23,22 +21,21 @@ namespace Avalonia.Direct2D1.Media { Text = text; - var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface); - - textFormat.WordWrapping = - wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; - - TextLayout = new DWrite.TextLayout( - Direct2D1Platform.DirectWriteFactory, - Text ?? string.Empty, - textFormat, - (float)constraint.Width, - (float)constraint.Height) + using (var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface)) { - TextAlignment = textAlignment.ToDirect2D() - }; - - textFormat.Dispose(); + textFormat.WordWrapping = + wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; + + TextLayout = new DWrite.TextLayout( + Direct2D1Platform.DirectWriteFactory, + Text ?? string.Empty, + textFormat, + (float)constraint.Width, + (float)constraint.Height) + { + TextAlignment = textAlignment.ToDirect2D() + }; + } if (spans != null) { @@ -109,17 +106,7 @@ namespace Avalonia.Direct2D1.Media private Size Measure() { - DWrite.TextMetrics metrics; - - // SharpDX bug - try - { - metrics = TextLayout.Metrics; - } - catch (ObjectDisposedException) - { - metrics = TextLayout.Metrics; - } + var metrics = TextLayout.Metrics; var width = metrics.WidthIncludingTrailingWhitespace; From 50f15b1154a28e58ce40253554df2111adec4440 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 10 Sep 2018 10:54:31 +0200 Subject: [PATCH 12/17] Namespace fix --- .../Media/Imaging/D2DBitmapImpl.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index b65e32bbbb..b0edb9c3bf 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,14 +1,13 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using System.IO; -using SharpDX.Direct2D1; +using SharpDX.WIC; +using Bitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { - using SharpDX.WIC; - - using Bitmap = SharpDX.Direct2D1.Bitmap; - using PixelFormat = SharpDX.Direct2D1.PixelFormat; - /// /// A Direct2D Bitmap implementation that uses a GPU memory bitmap as its image. /// From e625f03c1659a76fbd5d601de75a2ff25d68c953 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 10 Sep 2018 20:24:26 +0200 Subject: [PATCH 13/17] Use ID2D1Device instead of ID2D1Device1 to keep wi --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 38c49924bc..c330377e20 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -33,7 +33,7 @@ namespace Avalonia.Direct2D1 public static SharpDX.Direct2D1.Factory1 Direct2D1Factory { get; private set; } - public static SharpDX.Direct2D1.Device1 Direct2D1Device { get; private set; } + public static SharpDX.Direct2D1.Device Direct2D1Device { get; private set; } public static SharpDX.DirectWrite.Factory1 DirectWriteFactory { get; private set; } @@ -97,10 +97,7 @@ namespace Avalonia.Direct2D1 DxgiDevice = Direct3D11Device.QueryInterface(); - using (var device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice)) - { - Direct2D1Device = device.QueryInterface(); - } + Direct2D1Device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice); s_initialized = true; } From ac0c9e69cd79b97e96de4cb620231ca30c68cc44 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Sep 2018 00:38:58 +0200 Subject: [PATCH 14/17] Added IAffectsRender interface... ...and use it as a general-purpose means of attaching to property values that can affect render. --- src/Avalonia.Controls/Border.cs | 7 ++-- src/Avalonia.Controls/Panel.cs | 2 +- .../Presenters/ContentPresenter.cs | 3 +- src/Avalonia.Controls/Shapes/Shape.cs | 3 +- src/Avalonia.Controls/TextBlock.cs | 3 +- src/Avalonia.Visuals/Media/Brush.cs | 10 +++--- src/Avalonia.Visuals/Media/GradientBrush.cs | 6 ++-- src/Avalonia.Visuals/Media/IAffectsRender.cs | 16 +++++++++ src/Avalonia.Visuals/Media/IMutableBrush.cs | 7 +--- src/Avalonia.Visuals/Visual.cs | 35 +++---------------- .../Media/ImageBrushTests.cs | 4 +-- .../Media/LinearGradientBrushTests.cs | 20 +++++------ .../Media/SolidColorBrushTests.cs | 4 +-- 13 files changed, 54 insertions(+), 66 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/IAffectsRender.cs diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index cd5d3952e7..0edb272015 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -43,9 +43,12 @@ namespace Avalonia.Controls /// static Border() { - AffectsRender(BorderThicknessProperty, CornerRadiusProperty); + AffectsRender( + BackgroundProperty, + BorderBrushProperty, + BorderThicknessProperty, + CornerRadiusProperty); AffectsMeasure(BorderThicknessProperty); - BrushAffectsRender(BackgroundProperty, BorderBrushProperty); } /// diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 9b768749df..5415f3974d 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -30,7 +30,7 @@ namespace Avalonia.Controls /// static Panel() { - BrushAffectsRender(BackgroundProperty); + AffectsRender(BackgroundProperty); ClipToBoundsProperty.OverrideDefaultValue(true); } diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index a8019fc306..8d703cfc1c 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -90,9 +90,8 @@ namespace Avalonia.Controls.Presenters /// static ContentPresenter() { - AffectsRender(BorderThicknessProperty, CornerRadiusProperty); + AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); AffectsMeasure(BorderThicknessProperty, PaddingProperty); - BrushAffectsRender(BackgroundProperty, BorderBrushProperty); ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); ContentTemplateProperty.Changed.AddClassHandler(x => x.ContentChanged); TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 7ca0867031..f77c43acd0 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -33,8 +33,7 @@ namespace Avalonia.Controls.Shapes static Shape() { AffectsMeasure(StretchProperty, StrokeThicknessProperty); - AffectsRender(StrokeDashArrayProperty); - BrushAffectsRender(FillProperty, StrokeProperty); + AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty); } public Geometry DefiningGeometry diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 5a98ef3ecc..af7b0f835e 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -101,10 +101,11 @@ namespace Avalonia.Controls { ClipToBoundsProperty.OverrideDefaultValue(true); AffectsRender( + BackgroundProperty, + ForegroundProperty, FontWeightProperty, FontSizeProperty, FontStyleProperty); - BrushAffectsRender(BackgroundProperty, ForegroundProperty); } /// diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index 8ba7c1be04..c2c041f073 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -19,7 +19,7 @@ namespace Avalonia.Media AvaloniaProperty.Register(nameof(Opacity), 1.0); /// - public event EventHandler Changed; + public event EventHandler Invalidated; /// /// Gets or sets the opacity of the brush. @@ -63,14 +63,14 @@ namespace Avalonia.Media /// The properties. /// /// After a call to this method in a brush's static constructor, any change to the - /// property will cause the event to be raised on the brush. + /// property will cause the event to be raised on the brush. /// protected static void AffectsRender(params AvaloniaProperty[] properties) where T : Brush { void Invalidate(AvaloniaPropertyChangedEventArgs e) { - (e.Sender as T)?.RaiseChanged(EventArgs.Empty); + (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty); } foreach (var property in properties) @@ -80,9 +80,9 @@ namespace Avalonia.Media } /// - /// Raises the event. + /// Raises the event. /// /// The event args. - protected void RaiseChanged(EventArgs e) => Changed?.Invoke(this, e); + protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); } } diff --git a/src/Avalonia.Visuals/Media/GradientBrush.cs b/src/Avalonia.Visuals/Media/GradientBrush.cs index c123813cee..8fd2dcf27f 100644 --- a/src/Avalonia.Visuals/Media/GradientBrush.cs +++ b/src/Avalonia.Visuals/Media/GradientBrush.cs @@ -80,18 +80,18 @@ namespace Avalonia.Media brush._gradientStopsSubscription = newValue.TrackItemPropertyChanged(brush.GradientStopChanged); } - brush.RaiseChanged(EventArgs.Empty); + brush.RaiseInvalidated(EventArgs.Empty); } } private void GradientStopsChanged(object sender, NotifyCollectionChangedEventArgs e) { - RaiseChanged(EventArgs.Empty); + RaiseInvalidated(EventArgs.Empty); } private void GradientStopChanged(Tuple e) { - RaiseChanged(EventArgs.Empty); + RaiseInvalidated(EventArgs.Empty); } } } diff --git a/src/Avalonia.Visuals/Media/IAffectsRender.cs b/src/Avalonia.Visuals/Media/IAffectsRender.cs new file mode 100644 index 0000000000..0024195ce5 --- /dev/null +++ b/src/Avalonia.Visuals/Media/IAffectsRender.cs @@ -0,0 +1,16 @@ +using System; + +namespace Avalonia.Media +{ + /// + /// Signals to a self-rendering control that changes to the resource should invoke + /// . + /// + public interface IAffectsRender + { + /// + /// Raised when the resource changes visually. + /// + event EventHandler Invalidated; + } +} diff --git a/src/Avalonia.Visuals/Media/IMutableBrush.cs b/src/Avalonia.Visuals/Media/IMutableBrush.cs index 762731a6a8..415db61d68 100644 --- a/src/Avalonia.Visuals/Media/IMutableBrush.cs +++ b/src/Avalonia.Visuals/Media/IMutableBrush.cs @@ -5,13 +5,8 @@ namespace Avalonia.Media /// /// Represents a mutable brush which can return an immutable clone of itself. /// - public interface IMutableBrush : IBrush + public interface IMutableBrush : IBrush, IAffectsRender { - /// - /// Raised when the brush changes visually. - /// - event EventHandler Changed; - /// /// Creates an immutable clone of the brush. /// diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index fdff16494f..e5fcf1ba1d 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -338,45 +338,20 @@ namespace Avalonia /// FrameworkPropertyMetadata.AffectsRender flag. /// protected static void AffectsRender(params AvaloniaProperty[] properties) - where T : class, IVisual - { - void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.InvalidateVisual(); - } - - foreach (var property in properties) - { - property.Changed.Subscribe(Invalidate); - } - } - - /// - /// Indicates that a brush property change should cause to be - /// called. - /// - /// The properties. - /// - /// This method should be called in a control's static constructor with each property - /// on the control which when changed should cause a redraw. It not only triggers an - /// invalidation when the property itself changes, but also when the brush raises - /// the event. - /// - protected static void BrushAffectsRender(params AvaloniaProperty[] properties) where T : Visual { void Invalidate(AvaloniaPropertyChangedEventArgs e) { if (e.Sender is T sender) { - if (e.OldValue is IMutableBrush oldValue) + if (e.OldValue is IAffectsRender oldValue) { - oldValue.Changed -= sender.BrushChanged; + oldValue.Invalidated -= sender.AffectsRenderInvalidated; } - if (e.NewValue is IMutableBrush newValue) + if (e.NewValue is IAffectsRender newValue) { - newValue.Changed += sender.BrushChanged; + newValue.Invalidated += sender.AffectsRenderInvalidated; } sender.InvalidateVisual(); @@ -582,7 +557,7 @@ namespace Avalonia OnVisualParentChanged(old, value); } - private void BrushChanged(object sender, EventArgs e) => InvalidateVisual(); + private void AffectsRenderInvalidated(object sender, EventArgs e) => InvalidateVisual(); /// /// Called when the collection changes. diff --git a/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs index f843a6e333..ff7b94105a 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs @@ -11,14 +11,14 @@ namespace Avalonia.Visuals.UnitTests.Media public class ImageBrushTests { [Fact] - public void Changing_Source_Raises_Changed() + public void Changing_Source_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); var target = new ImageBrush(bitmap1); var raised = false; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.Source = bitmap2; Assert.True(raised); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs index 62f53108e6..b3f78dfe8f 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs @@ -9,7 +9,7 @@ namespace Avalonia.Visuals.UnitTests.Media public class LinearGradientBrushTests { [Fact] - public void Changing_StartPoint_Raises_Changed() + public void Changing_StartPoint_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -17,14 +17,14 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.StartPoint = new RelativePoint(); - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.StartPoint = new RelativePoint(10, 10, RelativeUnit.Absolute); Assert.True(raised); } [Fact] - public void Changing_EndPoint_Raises_Changed() + public void Changing_EndPoint_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -32,14 +32,14 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.EndPoint = new RelativePoint(); - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.EndPoint = new RelativePoint(10, 10, RelativeUnit.Absolute); Assert.True(raised); } [Fact] - public void Changing_GradientStops_Raises_Changed() + public void Changing_GradientStops_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -47,14 +47,14 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.GradientStops = new GradientStops { new GradientStop(Colors.Green, 0) }; Assert.True(raised); } [Fact] - public void Adding_GradientStop_Raises_Changed() + public void Adding_GradientStop_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -62,14 +62,14 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.GradientStops.Add(new GradientStop(Colors.Green, 1)); Assert.True(raised); } [Fact] - public void Changing_GradientStop_Offset_Raises_Changed() + public void Changing_GradientStop_Offset_Raises_Invalidated() { var bitmap1 = Mock.Of(); var bitmap2 = Mock.Of(); @@ -77,7 +77,7 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) }; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.GradientStops[0].Offset = 0.5; Assert.True(raised); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs index 4e87b7081d..bcf63f9660 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs @@ -7,12 +7,12 @@ namespace Avalonia.Visuals.UnitTests.Media public class SolidColorBrushTests { [Fact] - public void Changing_Color_Raises_Changed() + public void Changing_Color_Raises_Invalidated() { var target = new SolidColorBrush(Colors.Red); var raised = false; - target.Changed += (s, e) => raised = true; + target.Invalidated += (s, e) => raised = true; target.Color = Colors.Green; Assert.True(raised); From df014f3b1e2ec60f7eb8651e5dd01b90c4fa8d5c Mon Sep 17 00:00:00 2001 From: ahopper Date: Wed, 12 Sep 2018 03:46:46 +0100 Subject: [PATCH 15/17] Fix drawing presenter clipping (#1882) * fix clipping offset * simplified clipping calculation --- src/Avalonia.Controls/DrawingPresenter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs index 34ce598218..b30a8668fd 100644 --- a/src/Avalonia.Controls/DrawingPresenter.cs +++ b/src/Avalonia.Controls/DrawingPresenter.cs @@ -49,7 +49,7 @@ namespace Avalonia.Controls if (Drawing != null) { using (context.PushPreTransform(_transform)) - using (context.PushClip(Bounds)) + using (context.PushClip(new Rect(Bounds.Size))) { Drawing.Draw(context); } From 4f11ecdd38a6dc7bd50a342b22537f6970de7e95 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Sep 2018 13:07:32 +0200 Subject: [PATCH 16/17] Added an example of dynamic menus to the ControlCatalog. Creates a menu using `DataTemplates` on the MenuPage. --- samples/ControlCatalog/App.xaml | 9 ++- samples/ControlCatalog/Pages/MenuPage.xaml | 59 ++++++++++++------- samples/ControlCatalog/Pages/MenuPage.xaml.cs | 28 +++++++++ 3 files changed, 72 insertions(+), 24 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 43971dec4f..95d515ec60 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -14,6 +14,11 @@ - + + + - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index 296cfa8704..6eeb86a00f 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -7,29 +7,44 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16"> - - - - - - - + + Defined in XAML + + + + + + + + + + + + + + + + + + - - - - + + - - - - - - - - - - + + + + + Dyanamically generated + + + + + + - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index d637c172e1..880d4bc59a 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Markup.Xaml; @@ -8,6 +9,27 @@ namespace ControlCatalog.Pages public MenuPage() { this.InitializeComponent(); + DataContext = new[] + { + new MenuItemViewModel + { + Header = "_File", + Items = + { + new MenuItemViewModel { Header = "_Open..." }, + new MenuItemViewModel { Header = "Save" }, + } + }, + new MenuItemViewModel + { + Header = "_Edit", + Items = + { + new MenuItemViewModel { Header = "_Copy" }, + new MenuItemViewModel { Header = "_Paste" }, + } + } + }; } private void InitializeComponent() @@ -15,4 +37,10 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } } + + public class MenuItemViewModel + { + public string Header { get; set; } + public IList Items { get; } = new List(); + } } From b44900395800eec65d3fc1b7b6bcef8152638a43 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Sep 2018 15:21:14 +0200 Subject: [PATCH 17/17] Allow MenuItems to be separators. If `MenuItem.Header == "-"` then apply the `:separator` pseudoclass to the `MenuItem` and display it as a separator. --- samples/ControlCatalog/Pages/MenuPage.xaml.cs | 16 ++++++++++++--- src/Avalonia.Controls/MenuItem.cs | 20 +++++++++++++++---- src/Avalonia.Themes.Default/MenuItem.xaml | 12 ++++++++++- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 880d4bc59a..01add3e76e 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -14,16 +14,26 @@ namespace ControlCatalog.Pages new MenuItemViewModel { Header = "_File", - Items = + Items = new[] { new MenuItemViewModel { Header = "_Open..." }, new MenuItemViewModel { Header = "Save" }, + new MenuItemViewModel { Header = "-" }, + new MenuItemViewModel + { + Header = "Recent", + Items = new[] + { + new MenuItemViewModel { Header = "File1.txt" }, + new MenuItemViewModel { Header = "File2.txt" }, + } + }, } }, new MenuItemViewModel { Header = "_Edit", - Items = + Items = new[] { new MenuItemViewModel { Header = "_Copy" }, new MenuItemViewModel { Header = "_Paste" }, @@ -41,6 +51,6 @@ namespace ControlCatalog.Pages public class MenuItemViewModel { public string Header { get; set; } - public IList Items { get; } = new List(); + public IList Items { get; set; } } } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 7b57783c5a..03a43b5164 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -105,6 +105,7 @@ namespace Avalonia.Controls ClickEvent.AddClassHandler(x => x.OnClick); SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); IsSubMenuOpenProperty.Changed.AddClassHandler(x => x.SubMenuOpenChanged); + PseudoClass(HeaderProperty, x => x as string == "-", ":separator"); } public MenuItem() @@ -357,10 +358,21 @@ namespace Avalonia.Controls { base.OnTemplateApplied(e); - _popup = e.NameScope.Get("PART_Popup"); - _popup.DependencyResolver = DependencyResolver.Instance; - _popup.Opened += PopupOpened; - _popup.Closed += PopupClosed; + if (_popup != null) + { + _popup.Opened -= PopupOpened; + _popup.Closed -= PopupClosed; + _popup.DependencyResolver = null; + } + + _popup = e.NameScope.Find("PART_Popup"); + + if (_popup != null) + { + _popup.DependencyResolver = DependencyResolver.Instance; + _popup.Opened += PopupOpened; + _popup.Closed += PopupClosed; + } } /// diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 8a2ed2a802..07faf7a632 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -73,7 +73,17 @@ - + + +